Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

@@ -0,0 +1,64 @@
module anim
import gg
import sim
import sim.args as simargs
const bg_color = gg.white
struct Pixel {
x f32
y f32
color gg.Color
}
pub struct App {
pub:
args simargs.ParallelArgs
request_chan chan &sim.SimRequest
result_chan chan &sim.SimResult
pub mut:
gg &gg.Context = unsafe { nil }
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: 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
)
spawn 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(unsafe { &u8(&app.pixels[0]) })
app.gg.draw_image(0, 0, app.args.grid.width, app.args.grid.height, istream_image)
}
@@ -0,0 +1,18 @@
module anim
import benchmark
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))
}
@@ -0,0 +1,169 @@
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 {
pub:
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 = 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 error('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, max_parallel_workers,
'amount of workers to use on simulation. Defaults to ${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 error('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 {
return math.max(1, workers + extra_workers)
}
@@ -0,0 +1,74 @@
module img
import gg
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 []u8
cache_size int
}
pub fn ppm_writer_for_fname(fname string, settings ImageSettings) !&PPMWriter {
mut writer := &PPMWriter{
cache_size: settings.cache_size
cache: []u8{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 gg.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()
}
@@ -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') }
}
@@ -0,0 +1,68 @@
module img
import gg
import sim
pub struct ValidColor {
gg.Color
pub mut:
valid bool
}
pub struct ImageWriter {
settings ImageSettings
pub mut:
writer PPMWriter
current_index int
buffer []ValidColor
}
pub fn new_image_writer(mut writer PPMWriter, settings ImageSettings) &ImageWriter {
total_pixels := settings.width * settings.height
mut buffer := []ValidColor{len: total_pixels, init: ValidColor{
valid: false
}}
return &ImageWriter{
writer: writer
settings: settings
buffer: buffer
}
}
pub fn (mut iw ImageWriter) 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 error('none')
}
return iw.current_index
}
pub fn compute_pixel(result sim.SimResult) gg.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 gg.red
} else if closest_to_m2 {
return gg.green
} else {
return gg.blue
}
}
@@ -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)
}
}
@@ -0,0 +1,95 @@
module sim
import math
pub const default_rope_length = 0.25
pub const default_bearing_mass = 0.03
pub const default_magnet_spacing = 0.05
pub const default_magnet_height = 0.03
pub const default_magnet_strength = 10.0
pub const default_gravity = 4.9
@[params]
pub struct SimParams {
pub:
rope_length f64 = default_rope_length
bearing_mass f64 = default_bearing_mass
magnet_spacing f64 = default_magnet_spacing
magnet_height f64 = default_magnet_height
magnet_strength f64 = default_magnet_strength
gravity f64 = 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))
}
@@ -0,0 +1,126 @@
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
}
const 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
)
}
const params_test_mock_tetha = 2.0 * math.pi / 3.0
pub fn test_get_rope_vector() {
result := params_test_mock_params.get_rope_vector(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 := params_test_mock_params.get_forces_sum(params_test_mock_state)
expected := vector(
x: 3.410605131648481e-12
y: 5.229594535194337e-12
z: 9.094947017729282e-13
)
assert result == expected
}
pub fn test_get_grav_force() {
result := params_test_mock_params.get_grav_force(params_test_mock_state)
expected := vector(
z: -0.147
)
assert result == expected
}
pub fn test_get_magnet_position() {
result := params_test_mock_params.get_magnet_position(params_test_mock_tetha)
expected := vector(
x: -0.02499999999999999
y: 0.04330127018922194
z: -0.03
)
assert result == expected
}
pub fn test_get_magnet_force() {
result := params_test_mock_params.get_magnet_force(params_test_mock_tetha,
params_test_mock_state)
expected := vector(
x: -157.4572297692556
y: 1422.736432604726
z: -632.5695169850264
)
assert result == expected
}
pub fn test_get_magnet_dist() {
result := params_test_mock_params.get_magnet_dist(params_test_mock_tetha,
params_test_mock_state)
expected := 0.07993696666249227
assert result == expected
}
pub fn test_get_magnet1_force() {
result := params_test_mock_params.get_magnet1_force(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 := params_test_mock_params.get_magnet2_force(params_test_mock_state)
expected := vector(
x: -157.4572297692556
y: 1422.736432604726
z: -632.5695169850264
)
assert result == expected
}
pub fn test_get_magnet3_force() {
result := params_test_mock_params.get_magnet3_force(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 := params_test_mock_params.get_tension_force(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
}
@@ -0,0 +1,94 @@
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
pub const default_height = 600
@[params]
pub struct GridSettings {
pub:
width int = default_width
height int = default_height
}
pub fn new_grid_settings(settings GridSettings) GridSettings {
return GridSettings{
...settings
}
}
@[params]
pub struct RunnerSettings {
pub:
grid GridSettings
on_request SimRequestHandler = unsafe { nil }
on_start SimStartHandler = unsafe { nil }
on_finish SimFinishHandler = unsafe { nil }
}
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}')
}
}
}
@@ -0,0 +1,47 @@
module sim
pub struct SimState {
pub 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 acceleration
// 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
}
@@ -0,0 +1,62 @@
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
}
const 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_test_mock_state
}
state.satisfy_rope_constraint(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_test_mock_state
}
delta_t := 0.0005
state.increment(delta_t, 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.251158929833047
assert state.velocity.y == -12.559375680227637
assert state.velocity.z == -105.9153968768638
assert state.accel.x == 1.1368683772161604e-10
assert state.accel.y == 1.7431981783981126e-10
assert state.accel.z == 3.031649005909761e-11
}
@@ -0,0 +1,51 @@
module sim
import math
// Vector3D is a 3D vector
pub struct Vector3D {
pub:
x f64
y f64
z f64
}
// vector creates a Vector3D passing x,y,z as parameters
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())
}
@@ -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
}
@@ -0,0 +1,65 @@
module sim
import math
import benchmark
const max_iterations = 1000
const simulation_delta_t = 0.0005
pub struct SimRequest {
pub:
params SimParams
state SimState
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 .. max_iterations {
state.increment(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
}
}
@@ -0,0 +1,61 @@
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
}
const 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: worker_test_mock_params
state: worker_test_mock_state
}
expected_state := SimState{
position: vector(
x: -0.01695723093017135
y: -0.029370785526735183
z: 0.002311063475327252
)
velocity: vector(
x: -7.251158929831753
y: -12.55937568022536
z: -105.91539687684447
)
accel: vector(
x: -1.1368683772161603e-11
y: -1.5916157281026244e-10
z: 0
)
}
expected := &SimResult{
state: expected_state
id: 0
magnet1_distance: 0.07993696666249225
magnet2_distance: 0.07993696666249224
magnet3_distance: 0.03609361938278008
}
result := compute_result(request)
assert result == expected
}