examples: improve the pendulum simulation, with several modes and diagrams (#13446)

This commit is contained in:
Ulises Jeremias Cornejo Fandos 2022-02-12 14:38:07 -03:00 committed by GitHub
parent a74d28ae5f
commit 4391ae563d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
26 changed files with 1501 additions and 366 deletions

View file

@ -0,0 +1,64 @@
module anim
import gg
import gx
import sim
import sim.args as simargs
const bg_color = gx.white
struct Pixel {
x f32
y f32
color gx.Color
}
struct App {
pub:
args simargs.ParallelArgs
request_chan chan &sim.SimRequest
result_chan chan &sim.SimResult
pub mut:
gg &gg.Context = 0
iidx int
pixels []u32
}
pub fn new_app(args simargs.ParallelArgs) &App {
total_pixels := args.grid.height * args.grid.width
mut app := &App{
args: args
pixels: []u32{len: total_pixels}
request_chan: chan &sim.SimRequest{cap: args.grid.width}
}
app.gg = gg.new_context(
width: args.grid.width
height: args.grid.height
create_window: true
window_title: 'V Pendulum Simulation'
user_data: app
bg_color: anim.bg_color
frame_fn: frame
init_fn: init
)
return app
}
fn init(mut app App) {
app.iidx = app.gg.new_streaming_image(app.args.grid.width, app.args.grid.height, 4,
pixel_format: .rgba8)
go pixels_worker(mut app)
}
fn frame(mut app App) {
app.gg.begin()
app.draw()
app.gg.end()
}
fn (mut app App) draw() {
mut istream_image := app.gg.get_cached_image_by_idx(app.iidx)
istream_image.update_pixel_data(&app.pixels[0])
app.gg.draw_image(0, 0, app.args.grid.width, app.args.grid.height, istream_image)
}

View file

@ -0,0 +1,19 @@
module anim
import benchmark
import sim
import sim.img
fn pixels_worker(mut app App) {
mut bmark := benchmark.new_benchmark()
for {
result := <-app.result_chan or { break }
bmark.step()
// find the closest magnet
pixel_color := img.compute_pixel(result)
app.pixels[result.id] = u32(pixel_color.abgr8())
bmark.ok()
}
bmark.stop()
println(bmark.total_message(@FN))
}

View file

@ -0,0 +1,158 @@
module args
import flag
import os
import runtime
import sim
import math
// customisable through setting VJOBS
const max_parallel_workers = runtime.nr_jobs()
[params]
pub struct ParserSettings {
sequential bool
img bool
extra_workers int
}
pub struct SequentialArgs {
pub:
params sim.SimParams
grid sim.GridSettings
filename string
}
pub struct ParallelArgs {
SequentialArgs
pub:
workers int = args.max_parallel_workers
}
pub type SimArgs = ParallelArgs | SequentialArgs
pub fn parse_args(config ParserSettings) ?SimArgs {
if config.sequential {
args := parse_sequential_args() ?
return SimArgs(args)
} else {
args := parse_parallel_args(config.extra_workers) ?
return SimArgs(args)
}
}
fn parse_sequential_args() ?SequentialArgs {
mut fp := flag.new_flag_parser(os.args)
fp.application('vps')
fp.version('v0.1.0')
fp.limit_free_args(0, 0) ?
fp.description('This is a pendulum simulation written in pure V')
fp.skip_executable()
// output parameters
width := fp.int('width', `w`, sim.default_width, 'width of the image output. Defaults to $sim.default_width')
height := fp.int('height', `h`, sim.default_height, 'height of the image output. Defaults to $sim.default_height')
filename := fp.string('output', `o`, 'out.ppm', 'name of the image output. Defaults to out.ppm')
// simulation parameters
rope_length := fp.float('rope-length', 0, sim.default_rope_length, 'rope length to use on simulation. Defaults to $sim.default_rope_length')
bearing_mass := fp.float('bearing-mass', 0, sim.default_bearing_mass, 'bearing mass to use on simulation. Defaults to $sim.default_bearing_mass')
magnet_spacing := fp.float('magnet-spacing', 0, sim.default_magnet_spacing, 'magnet spacing to use on simulation. Defaults to $sim.default_magnet_spacing')
magnet_height := fp.float('magnet-height', 0, sim.default_magnet_height, 'magnet height to use on simulation. Defaults to $sim.default_magnet_height')
magnet_strength := fp.float('magnet-strength', 0, sim.default_magnet_strength, 'magnet strength to use on simulation. Defaults to $sim.default_magnet_strength')
gravity := fp.float('gravity', 0, sim.default_gravity, 'gravity to use on simulation. Defaults to $sim.default_gravity')
fp.finalize() or {
println(fp.usage())
return none
}
params := sim.sim_params(
rope_length: rope_length
bearing_mass: bearing_mass
magnet_spacing: magnet_spacing
magnet_height: magnet_height
magnet_strength: magnet_strength
gravity: gravity
)
grid := sim.new_grid_settings(
width: width
height: height
)
args := SequentialArgs{
params: params
filename: filename
grid: grid
}
sim.log('$args')
return args
}
fn parse_parallel_args(extra_workers int) ?ParallelArgs {
mut fp := flag.new_flag_parser(os.args)
fp.application('vps')
fp.version('v0.1.0')
fp.limit_free_args(0, 0) ?
fp.description('This is a pendulum simulation written in pure V')
fp.skip_executable()
workers := fp.int('workers', 0, args.max_parallel_workers, 'amount of workers to use on simulation. Defaults to $args.max_parallel_workers')
// output parameters
width := fp.int('width', `w`, sim.default_width, 'width of the image output. Defaults to $sim.default_width')
height := fp.int('height', `h`, sim.default_height, 'height of the image output. Defaults to $sim.default_height')
filename := fp.string('output', `o`, 'out.ppm', 'name of the image output. Defaults to out.ppm')
// simulation parameters
rope_length := fp.float('rope-length', 0, sim.default_rope_length, 'rope length to use on simulation. Defaults to $sim.default_rope_length')
bearing_mass := fp.float('bearing-mass', 0, sim.default_bearing_mass, 'bearing mass to use on simulation. Defaults to $sim.default_bearing_mass')
magnet_spacing := fp.float('magnet-spacing', 0, sim.default_magnet_spacing, 'magnet spacing to use on simulation. Defaults to $sim.default_magnet_spacing')
magnet_height := fp.float('magnet-height', 0, sim.default_magnet_height, 'magnet height to use on simulation. Defaults to $sim.default_magnet_height')
magnet_strength := fp.float('magnet-strength', 0, sim.default_magnet_strength, 'magnet strength to use on simulation. Defaults to $sim.default_magnet_strength')
gravity := fp.float('gravity', 0, sim.default_gravity, 'gravity to use on simulation. Defaults to $sim.default_gravity')
fp.finalize() or {
println(fp.usage())
return none
}
params := sim.sim_params(
rope_length: rope_length
bearing_mass: bearing_mass
magnet_spacing: magnet_spacing
magnet_height: magnet_height
magnet_strength: magnet_strength
gravity: gravity
)
grid := sim.new_grid_settings(
width: width
height: height
)
args := ParallelArgs{
params: params
filename: filename
grid: grid
workers: get_workers(workers, extra_workers)
}
sim.log('$args')
return args
}
[inline]
fn get_workers(workers int, extra_workers int) int {
result := if workers + extra_workers <= args.max_parallel_workers {
workers
} else {
args.max_parallel_workers - extra_workers
}
return math.max(1, result)
}

View file

@ -0,0 +1,74 @@
module img
import gx
import os
import sim
[params]
pub struct ImageSettings {
pub:
width int = sim.default_width
height int = sim.default_height
cache_size int = 200
}
pub fn new_image_settings(settings ImageSettings) ImageSettings {
return ImageSettings{
...settings
}
}
pub fn image_settings_from_grid(grid sim.GridSettings) ImageSettings {
return ImageSettings{
width: grid.width
height: grid.height
}
}
pub fn (s ImageSettings) to_grid_settings() sim.GridSettings {
return sim.GridSettings{
width: s.width
height: s.height
}
}
pub struct PPMWriter {
mut:
file os.File
cache []byte
cache_size int
}
pub fn ppm_writer_for_fname(fname string, settings ImageSettings) ?&PPMWriter {
mut writer := &PPMWriter{
cache_size: settings.cache_size
cache: []byte{cap: settings.cache_size}
}
writer.start_for_file(fname, settings) ?
return writer
}
pub fn (mut writer PPMWriter) start_for_file(fname string, settings ImageSettings) ? {
writer.file = os.create(fname) ?
writer.file.writeln('P6 $settings.width $settings.height 255') ?
}
pub fn (mut writer PPMWriter) handle_pixel(p gx.Color) ? {
if writer.cache.len >= writer.cache_size {
writer.write() ?
writer.flush() ?
}
writer.cache << [p.r, p.g, p.b]
}
pub fn (mut writer PPMWriter) flush() ? {
writer.cache.clear()
}
pub fn (mut writer PPMWriter) write() ? {
writer.file.write(writer.cache) ?
}
pub fn (mut writer PPMWriter) close() {
writer.file.close()
}

View file

@ -0,0 +1,40 @@
module img
import benchmark
import sim
pub fn image_worker(mut writer PPMWriter, result_chan chan &sim.SimResult, settings ImageSettings) {
width := settings.width
height := settings.height
total_pixels := width * height
// as new pixels come in, write them to the image file
mut current_index := u64(0)
mut pixel_buf := []ValidColor{len: total_pixels, init: ValidColor{
valid: false
}}
mut bmark := benchmark.new_benchmark()
for {
result := <-result_chan or { break }
// find the closest magnet
pixel_buf[result.id].Color = compute_pixel(result)
pixel_buf[result.id].valid = true
for current_index < total_pixels && pixel_buf[current_index].valid {
bmark.step()
writer.handle_pixel(pixel_buf[current_index].Color) or {
bmark.fail()
sim.log(@MOD + '.' + @FN + ': pixel handler failed. Error $err')
break
}
bmark.ok()
current_index++
}
}
bmark.stop()
println(bmark.total_message(@FN))
writer.write() or { panic('Could not write image') }
}

View file

@ -0,0 +1,68 @@
module img
import gx
import sim
pub struct ValidColor {
gx.Color
pub mut:
valid bool
}
pub struct ImageWritter {
settings ImageSettings
pub mut:
writer PPMWriter
current_index int
buffer []ValidColor
}
pub fn new_image_writer(mut writer PPMWriter, settings ImageSettings) &ImageWritter {
total_pixels := settings.width * settings.height
mut buffer := []ValidColor{len: total_pixels, init: ValidColor{
valid: false
}}
return &ImageWritter{
writer: writer
settings: settings
buffer: buffer
}
}
pub fn (mut iw ImageWritter) handle(result sim.SimResult) ?int {
total_pixels := iw.settings.width * iw.settings.height
// find the closest magnet
iw.buffer[result.id].Color = compute_pixel(result)
iw.buffer[result.id].valid = true
for iw.current_index < total_pixels && iw.buffer[iw.current_index].valid {
iw.writer.handle_pixel(iw.buffer[iw.current_index].Color) or {
sim.log(@MOD + '.' + @FN + ': pixel handler failed. Error $err')
break
}
iw.current_index++
}
if iw.current_index == total_pixels {
iw.writer.write() or { panic('Could not write image') }
return none
}
return iw.current_index
}
pub fn compute_pixel(result sim.SimResult) gx.Color {
closest_to_m1 := result.magnet1_distance < result.magnet2_distance
&& result.magnet1_distance < result.magnet3_distance
closest_to_m2 := result.magnet2_distance < result.magnet1_distance
&& result.magnet2_distance < result.magnet3_distance
if closest_to_m1 {
return gx.red
} else if closest_to_m2 {
return gx.green
} else {
return gx.blue
}
}

View file

@ -0,0 +1,9 @@
module sim
// log is a helper function to print debug info
[inline]
pub fn log(info string) {
$if verbose ? {
println(info)
}
}

View file

@ -0,0 +1,96 @@
module sim
import math
pub const (
default_rope_length = 0.25
default_bearing_mass = 0.03
default_magnet_spacing = 0.05
default_magnet_height = 0.03
default_magnet_strength = 10.0
default_gravity = 4.9
)
[params]
pub struct SimParams {
rope_length f64 = sim.default_rope_length
bearing_mass f64 = sim.default_bearing_mass
magnet_spacing f64 = sim.default_magnet_spacing
magnet_height f64 = sim.default_magnet_height
magnet_strength f64 = sim.default_magnet_strength
gravity f64 = sim.default_gravity
}
pub fn sim_params(params SimParams) SimParams {
return SimParams{
...params
}
}
pub fn (params SimParams) get_rope_vector(state SimState) Vector3D {
rope_origin := vector(z: params.rope_length)
return state.position + rope_origin.scale(-1)
}
pub fn (params SimParams) get_forces_sum(state SimState) Vector3D {
// force due to gravity
f_gravity := params.get_grav_force(state)
// force due to magnets
f_magnet1 := params.get_magnet1_force(state)
f_magnet2 := params.get_magnet2_force(state)
f_magnet3 := params.get_magnet3_force(state)
mut f_passive := vector(x: 0.0, y: 0.0, z: 0.0)
for force in [f_gravity, f_magnet1, f_magnet2, f_magnet3] {
f_passive = f_passive + force
}
// force due to tension of the rope
f_tension := params.get_tension_force(state, f_passive)
return f_passive + f_tension
}
pub fn (params SimParams) get_grav_force(state SimState) Vector3D {
return vector(z: -params.bearing_mass * params.gravity)
}
pub fn (params SimParams) get_magnet_position(theta f64) Vector3D {
return vector(
x: math.cos(theta) * params.magnet_spacing
y: math.sin(theta) * params.magnet_spacing
z: -params.magnet_height
)
}
pub fn (params SimParams) get_magnet_force(theta f64, state SimState) Vector3D {
magnet_position := params.get_magnet_position(theta)
mut diff := magnet_position + state.position.scale(-1)
distance_squared := diff.norm_squared()
diff = diff.scale(1.0 / math.sqrt(distance_squared))
return diff.scale(params.magnet_strength / distance_squared)
}
pub fn (params SimParams) get_magnet_dist(theta f64, state SimState) f64 {
return (params.get_magnet_position(theta) + state.position.scale(-1)).norm()
}
pub fn (params SimParams) get_magnet1_force(state SimState) Vector3D {
return params.get_magnet_force(0.0 * math.pi / 3.0, state)
}
pub fn (params SimParams) get_magnet2_force(state SimState) Vector3D {
return params.get_magnet_force(2.0 * math.pi / 3.0, state)
}
pub fn (params SimParams) get_magnet3_force(state SimState) Vector3D {
return params.get_magnet_force(4.0 * math.pi / 3.0, state)
}
pub fn (params SimParams) get_tension_force(state SimState, f_passive Vector3D) Vector3D {
rope_vector := params.get_rope_vector(state)
rope_vector_norm := rope_vector.scale(1.0 / rope_vector.norm())
return rope_vector_norm.scale(-1.0 * (rope_vector_norm * f_passive))
}

View file

@ -0,0 +1,125 @@
module sim
import math
const (
params_test_mock_params = SimParams{
rope_length: 0.25
bearing_mass: 0.03
magnet_spacing: 0.05
magnet_height: 0.03
magnet_strength: 10
gravity: 4.9
}
params_test_mock_state = SimState{
position: vector(
x: -0.016957230930171364
y: -0.02937078552673521
z: 0.002311063475327252
)
velocity: vector(
x: -7.251158929833104
y: -12.559375680227724
z: -105.91539687686381
)
accel: vector(
x: -8.337034766251843e-11
y: -2.842170943040401e-10
z: 1.2126596023639044e-10
)
}
params_test_mock_tetha = 2.0 * math.pi / 3.0
)
pub fn test_get_rope_vector() {
result := sim.params_test_mock_params.get_rope_vector(sim.params_test_mock_state)
expected := vector(
x: -0.016957230930171364
y: -0.02937078552673521
z: -0.24768893652467275
)
assert result == expected
}
pub fn test_get_forces_sum() {
result := sim.params_test_mock_params.get_forces_sum(sim.params_test_mock_state)
expected := vector(
x: 3.637978807091713e-12
y: 5.229594535194337e-12
z: 9.094947017729282e-13
)
assert result == expected
}
pub fn test_get_grav_force() {
result := sim.params_test_mock_params.get_grav_force(sim.params_test_mock_state)
expected := vector(
z: -0.147
)
assert result == expected
}
pub fn test_get_magnet_position() {
result := sim.params_test_mock_params.get_magnet_position(sim.params_test_mock_tetha)
expected := vector(
x: -0.024999999999999988
y: 0.043301270189221946
z: -0.03
)
assert result == expected
}
pub fn test_get_magnet_force() {
result := sim.params_test_mock_params.get_magnet_force(sim.params_test_mock_tetha,
sim.params_test_mock_state)
expected := vector(
x: -157.45722976925555
y: 1422.736432604726
z: -632.5695169850264
)
assert result == expected
}
pub fn test_get_magnet_dist() {
result := sim.params_test_mock_params.get_magnet_dist(sim.params_test_mock_tetha,
sim.params_test_mock_state)
expected := 0.07993696666249227
assert result == expected
}
pub fn test_get_magnet1_force() {
result := sim.params_test_mock_params.get_magnet1_force(sim.params_test_mock_state)
expected := vector(
x: 1310.8545084099674
y: 575.0062553126633
z: -632.5695169850262
)
assert result == expected
}
pub fn test_get_magnet2_force() {
result := sim.params_test_mock_params.get_magnet2_force(sim.params_test_mock_state)
expected := vector(
x: -157.45722976925555
y: 1422.736432604726
z: -632.5695169850264
)
assert result == expected
}
pub fn test_get_magnet3_force() {
result := sim.params_test_mock_params.get_magnet3_force(sim.params_test_mock_state)
expected := vector(
x: -1710.46541088048
y: -2962.612996234165
z: -6871.632889552589
)
assert result == expected
}
pub fn test_get_tension_force() {
result := sim.params_test_mock_params.get_tension_force(sim.params_test_mock_state,
vector(x: 0.0, y: 0.0, z: 0.0))
expected := vector(x: 0.0, y: 0.0, z: 0.0)
assert result == expected
}

View file

@ -0,0 +1,96 @@
module sim
import benchmark
import term
pub type SimRequestHandler = fn (request &SimRequest) ?
pub type SimStartHandler = fn () ?
pub type SimFinishHandler = fn () ?
pub const (
default_width = 600
default_height = 600
)
[params]
pub struct GridSettings {
pub:
width int = sim.default_width
height int = sim.default_height
}
pub fn new_grid_settings(settings GridSettings) GridSettings {
return GridSettings{
...settings
}
}
[params]
pub struct RunnerSettings {
pub:
grid GridSettings
on_request SimRequestHandler
on_start SimStartHandler
on_finish SimFinishHandler
}
pub fn run(params SimParams, settings RunnerSettings) {
height := settings.grid.height
width := settings.grid.width
if !isnil(settings.on_start) {
settings.on_start() or {
log(@MOD + '.' + @FN + ': Simulation start handler failed. Error $err')
}
}
mut index := 0
log('')
mut bmark := benchmark.new_benchmark()
for y in 0 .. height {
$if verbose ? {
term.clear_previous_line()
}
log(@MOD + '.' + @FN + ': y: ${y + 1}')
for x in 0 .. width {
bmark.step()
// setup state conditions
position := vector(
x: 0.1 * ((f64(x) - 0.5 * f64(width - 1)) / f64(width - 1))
y: 0.1 * ((f64(y) - 0.5 * f64(height - 1)) / f64(height - 1))
z: 0.0
)
velocity := vector(x: 0, y: 0, z: 0)
mut state := new_state(
position: position
velocity: velocity
)
state.satisfy_rope_constraint(params)
request := &SimRequest{
id: index
state: state
params: params
}
settings.on_request(request) or {
log(@MOD + '.' + @FN + ': request handler failed. Error $err')
bmark.fail()
break
}
index++
bmark.ok()
}
}
bmark.stop()
println(bmark.total_message(@FN))
if !isnil(settings.on_finish) {
settings.on_finish() or {
log(@MOD + '.' + @FN + ': Simulation stop handler failed. Error $err')
}
}
}

View file

@ -0,0 +1,47 @@
module sim
pub struct SimState {
mut:
position Vector3D
velocity Vector3D
accel Vector3D
}
pub fn new_state(state SimState) SimState {
return SimState{
...state
}
}
pub fn (mut state SimState) satisfy_rope_constraint(params SimParams) {
mut rope_vector := params.get_rope_vector(state)
rope_vector = rope_vector.scale(params.rope_length / rope_vector.norm())
state.position = vector(z: params.rope_length) + rope_vector
}
pub fn (mut state SimState) increment(delta_t f64, params SimParams) {
// 1. add up all forces
// 2. get an accelleration
// 3. add to velocity
// 4. ensure rope constraint is satisfied
// sum up all forces
forces_sum := params.get_forces_sum(state)
// get the acceleration
accel := forces_sum.scale(1.0 / params.bearing_mass)
state.accel = accel
// update the velocity
state.velocity = state.velocity + accel.scale(delta_t)
// update the position
state.position = state.position + state.velocity.scale(delta_t)
// ensure the position satisfies rope constraint
state.satisfy_rope_constraint(params)
}
pub fn (state SimState) done() bool {
return state.velocity.norm() < 0.05 && state.accel.norm() < 0.01
}

View file

@ -0,0 +1,64 @@
module sim
const (
sim_test_mock_params = SimParams{
rope_length: 0.25
bearing_mass: 0.03
magnet_spacing: 0.05
magnet_height: 0.03
magnet_strength: 10
gravity: 4.9
}
sim_test_mock_state = SimState{
position: vector(
x: -0.016957230930171364
y: -0.02937078552673521
z: 0.002311063475327252
)
velocity: vector(
x: -7.251158929833104
y: -12.559375680227724
z: -105.91539687686381
)
accel: vector(
x: -8.337034766251843e-11
y: -2.842170943040401e-10
z: 1.2126596023639044e-10
)
}
)
pub fn test_satisfy_rope_constraint() {
mut state := SimState{
...sim.sim_test_mock_state
}
state.satisfy_rope_constraint(sim.sim_test_mock_params)
assert state.position.x == -0.016957230930171364
assert state.position.y == -0.02937078552673521
assert state.position.z == 0.002311063475327252
assert state.velocity.x == -7.251158929833104
assert state.velocity.y == -12.559375680227724
assert state.velocity.z == -105.91539687686381
assert state.accel.x == -8.337034766251843e-11
assert state.accel.y == -2.842170943040401e-10
assert state.accel.z == 1.2126596023639044e-10
}
pub fn test_increment() {
mut state := SimState{
...sim.sim_test_mock_state
}
delta_t := 0.0005
state.increment(delta_t, sim.sim_test_mock_params)
assert state.position.x == -0.016957230930171364
assert state.position.y == -0.02937078552673524
assert state.position.z == 0.0023110634753272796
assert state.velocity.x == -7.251158929833044
assert state.velocity.y == -12.559375680227637
assert state.velocity.z == -105.9153968768638
assert state.accel.x == 1.2126596023639044e-10
assert state.accel.y == 1.7431981783981126e-10
assert state.accel.z == 3.031649005909761e-11
}

View file

@ -0,0 +1,50 @@
module sim
import math
// Vector3D is a 3D vector
pub struct Vector3D {
x f64
y f64
z f64
}
// vector creates a Vector3D passing x,y,z as parameteres
pub fn vector(data Vector3D) Vector3D {
return Vector3D{
...data
}
}
// addition
pub fn (v Vector3D) + (v2 Vector3D) Vector3D {
return Vector3D{
x: v.x + v2.x
y: v.y + v2.y
z: v.z + v2.z
}
}
// dot product
pub fn (v Vector3D) * (v2 Vector3D) f64 {
return (v.x * v2.x) + (v.y * v2.y) + (v.z * v2.z)
}
// scale gets a scaled vector
pub fn (v Vector3D) scale(scalar f64) Vector3D {
return Vector3D{
x: v.x * scalar
y: v.y * scalar
z: v.z * scalar
}
}
// norm_squared returns the square of the norm of the vector
pub fn (v Vector3D) norm_squared() f64 {
return v * v
}
// norm returns the norm of the vector
pub fn (v Vector3D) norm() f64 {
return math.sqrt(v.norm_squared())
}

View file

@ -0,0 +1,64 @@
module sim
fn test_add() {
v := vector(
x: -0.016957230930171364
y: -0.02937078552673521
z: 0.002311063475327252
)
result := v + v
expected := vector(
x: -0.03391446186034273
y: -0.05874157105347042
z: 0.004622126950654504
)
assert result == expected
}
fn test_dot() {
v := vector(
x: -0.016957230930171364
y: -0.02937078552673521
z: 0.002311063475327252
)
result := v * v
expected := 0.0011555317376636305
assert result == expected
}
fn test_scale() {
v := vector(
x: -0.016957230930171364
y: -0.02937078552673521
z: 0.002311063475327252
)
result := v.scale(2.0)
expected := vector(
x: -0.03391446186034273
y: -0.05874157105347042
z: 0.004622126950654504
)
assert result == expected
}
fn test_norm_squared() {
v := vector(
x: -0.016957230930171364
y: -0.02937078552673521
z: 0.002311063475327252
)
result := v.norm_squared()
expected := 0.0011555317376636305
assert result == expected
}
fn test_norm() {
v := vector(
x: -0.016957230930171364
y: -0.02937078552673521
z: 0.002311063475327252
)
result := v.norm()
expected := 0.033993113091678295
assert result == expected
}

View file

@ -0,0 +1,67 @@
module sim
import math
import benchmark
const (
max_iterations = 1000
simulation_delta_t = 0.0005
)
pub struct SimRequest {
params SimParams
state SimState
pub:
id int
}
pub struct SimResult {
state SimState
pub:
id int
magnet1_distance f64
magnet2_distance f64
magnet3_distance f64
}
pub fn sim_worker(id int, request_chan chan &SimRequest, result_channels []chan &SimResult) {
mut bmark := benchmark.new_benchmark()
for {
request := <-request_chan or { break }
bmark.step()
result := compute_result(request)
for ch in result_channels {
ch <- result
}
bmark.ok()
}
bmark.stop()
println(bmark.total_message(@FN + ': worker $id'))
}
pub fn compute_result(request SimRequest) &SimResult {
mut state := request.state
params := request.params
for _ in 0 .. sim.max_iterations {
state.increment(sim.simulation_delta_t, params)
if state.done() {
println('done!')
break
}
}
m1_dist := params.get_magnet_dist(0, state)
m2_dist := params.get_magnet_dist(2.0 * math.pi / 3.0, state)
m3_dist := params.get_magnet_dist(4.0 * math.pi / 3.0, state)
id := request.id
return &SimResult{
id: id
state: state
magnet1_distance: m1_dist
magnet2_distance: m2_dist
magnet3_distance: m3_dist
}
}

View file

@ -0,0 +1,63 @@
module sim
const (
worker_test_mock_params = SimParams{
rope_length: 0.25
bearing_mass: 0.03
magnet_spacing: 0.05
magnet_height: 0.03
magnet_strength: 10
gravity: 4.9
}
worker_test_mock_state = SimState{
position: vector(
x: -0.016957230930171364
y: -0.02937078552673521
z: 0.002311063475327252
)
velocity: vector(
x: -7.251158929833104
y: -12.559375680227724
z: -105.91539687686381
)
accel: vector(
x: -8.337034766251843e-11
y: -2.842170943040401e-10
z: 1.2126596023639044e-10
)
}
)
fn test_compute_result() {
request := SimRequest{
id: 0
params: sim.worker_test_mock_params
state: sim.worker_test_mock_state
}
expected_state := SimState{
position: vector(
x: -0.01695723093017133
y: -0.02937078552673517
z: 0.002311063475327252
)
velocity: vector(
x: -7.251158929832518
y: -12.559375680226692
z: -105.91539687685668
)
accel: vector(
x: -3.789561257387201e-12
y: 3.410605131648481e-11
z: 3.031649005909761e-11
)
}
expected := &SimResult{
state: expected_state
id: 0
magnet1_distance: 0.07993696666249224
magnet2_distance: 0.07993696666249223
magnet3_distance: 0.03609361938278009
}
result := compute_result(request)
assert result == expected
}