Initial project import
This commit is contained in:
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
|
||||
}
|
||||
Reference in new issue
Block a user