examples: improve the pendulum simulation, with several modes and diagrams (#13446)
parent
a74d28ae5f
commit
4391ae563d
|
@ -164,6 +164,14 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession {
|
|||
skip_files << 'examples/database/orm.v' // try fix it
|
||||
}
|
||||
}
|
||||
$if windows {
|
||||
// TODO: remove when closures on windows are supported
|
||||
skip_files << 'examples/pendulum-simulation/animation.v'
|
||||
skip_files << 'examples/pendulum-simulation/full.v'
|
||||
skip_files << 'examples/pendulum-simulation/parallel.v'
|
||||
skip_files << 'examples/pendulum-simulation/parallel_with_iw.v'
|
||||
skip_files << 'examples/pendulum-simulation/sequential.v'
|
||||
}
|
||||
if testing.github_job != 'sokol-shaders-can-be-compiled' {
|
||||
// These examples need .h files that are produced from the supplied .glsl files,
|
||||
// using by the shader compiler tools in https://github.com/floooh/sokol-tools-bin/archive/pre-feb2021-api-changes.tar.gz
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Executables files
|
||||
test
|
||||
test.exe
|
||||
|
||||
# Temporary files
|
||||
fns.txt
|
||||
|
||||
!/bin/test
|
||||
|
||||
/docs/build
|
||||
*.ppm
|
||||
main
|
||||
parallel
|
||||
parallel_with_iw
|
||||
sequential
|
||||
animation
|
||||
full
|
||||
*.log
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2021-2022 Ulises Jeremias Cornejo Fandos
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,93 @@
|
|||
<div align="center">
|
||||
<p>
|
||||
<img
|
||||
style="width: 250px"
|
||||
width="250"
|
||||
src="https://user-images.githubusercontent.com/17727170/153699135-a63e9644-1a29-4c04-9de3-c9100b06001d.png"
|
||||
>
|
||||
</p>
|
||||
|
||||
<h1>Pendulum Simulation in V</h1>
|
||||
|
||||
You can see the origin implementation among with some benchmarks at
|
||||
[ulises-jeremias/v-pendulum-simulation](https://github.com/ulises-jeremias/v-pendulum-simulation).
|
||||
|
||||
[vlang.io](https://vlang.io) |
|
||||
[Docs](https://ulises-jeremias.github.io/v-pendulum-simulation) |
|
||||
[Contributing](https://github.com/ulises-jeremias/v-pendulum-simulation/blob/main/CONTRIBUTING.md)
|
||||
|
||||
</div>
|
||||
<div align="center">
|
||||
|
||||
[![Build Status][workflowbadge]][workflowurl]
|
||||
[![Docs Validation][validatedocsbadge]][validatedocsurl]
|
||||
[![License: MIT][licensebadge]][licenseurl]
|
||||
|
||||
</div>
|
||||
|
||||
## Run the Simulations
|
||||
|
||||
### Sequential Simulation
|
||||
|
||||
```sh
|
||||
$ v -gc boehm -prod sequential.v
|
||||
$ ./sequential # execute ./sequential -h for more info
|
||||
```
|
||||
|
||||
### Parallel Simulation
|
||||
|
||||
```sh
|
||||
$ v -gc boehm -prod parallel.v
|
||||
$ ./parallel # execute ./parallel -h for more info
|
||||
```
|
||||
|
||||
![image](https://user-images.githubusercontent.com/17727170/153718774-1c93b158-aee3-4be1-bb47-fe601fed7336.png)
|
||||
|
||||
### Parallel Simulation with Image Worker
|
||||
|
||||
```sh
|
||||
$ v -gc boehm -prod parallel_with_iw.v
|
||||
$ ./parallel_with_iw # execute ./parallel_with_iw -h for more info
|
||||
```
|
||||
|
||||
![image](https://user-images.githubusercontent.com/17727170/153718769-eabb334d-454f-469f-a51a-14ffe67507de.png)
|
||||
|
||||
### Parallel Simulation with Graphic User Interface
|
||||
|
||||
```sh
|
||||
$ v -gc boehm -prod animation.v
|
||||
$ ./animation # execute ./animation -h for more info
|
||||
```
|
||||
|
||||
### Full Parallel Simulation with Graphic User Interface and Image Output
|
||||
|
||||
```sh
|
||||
$ v -gc boehm -prod full.v
|
||||
$ ./full # execute ./full -h for more info
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
To test the module, just type the following command:
|
||||
|
||||
```sh
|
||||
$ v test .
|
||||
```
|
||||
|
||||
## Benchmark
|
||||
|
||||
Check the original repository for tools to run benchmark tests. In there you can execute
|
||||
the following command to execute benchmark tests to get a full comparison between implementations:
|
||||
|
||||
```sh
|
||||
$ ./bin/run-benchmark-test --help
|
||||
```
|
||||
|
||||
![image](https://user-images.githubusercontent.com/17727170/152750137-98e7c5a3-936b-4bc8-b71a-1b182c0bbf50.png)
|
||||
|
||||
[workflowbadge]: https://github.com/ulises-jeremias/v-pendulum-simulation/workflows/Build%20and%20Test%20with%20deps/badge.svg
|
||||
[validatedocsbadge]: https://github.com/ulises-jeremias/v-pendulum-simulation/workflows/Validate%20Docs/badge.svg
|
||||
[licensebadge]: https://img.shields.io/badge/License-MIT-blue.svg
|
||||
[workflowurl]: https://github.com/ulises-jeremias/v-pendulum-simulation/commits/main
|
||||
[validatedocsurl]: https://github.com/ulises-jeremias/v-pendulum-simulation/commits/main
|
||||
[licenseurl]: https://github.com/ulises-jeremias/v-pendulum-simulation/blob/main/LICENSE
|
|
@ -0,0 +1,37 @@
|
|||
module main
|
||||
|
||||
import benchmark
|
||||
import sim
|
||||
import sim.anim
|
||||
import sim.args as simargs
|
||||
|
||||
fn main() {
|
||||
args := simargs.parse_args(extra_workers: 1) ? as simargs.ParallelArgs
|
||||
|
||||
mut app := anim.new_app(args)
|
||||
mut workers := []thread{cap: args.workers}
|
||||
|
||||
mut bmark := benchmark.start()
|
||||
|
||||
defer {
|
||||
app.request_chan.close()
|
||||
sim.log('Waiting for workers to finish')
|
||||
workers.wait()
|
||||
app.result_chan.close()
|
||||
sim.log('Workers finished!')
|
||||
bmark.measure(@FN)
|
||||
sim.log('Done!')
|
||||
}
|
||||
|
||||
for id in 0 .. args.workers {
|
||||
workers << go sim.sim_worker(id, app.request_chan, [app.result_chan])
|
||||
}
|
||||
|
||||
handle_request := fn [app] (request &sim.SimRequest) ? {
|
||||
app.request_chan <- request
|
||||
}
|
||||
|
||||
go app.gg.run()
|
||||
|
||||
sim.run(args.params, grid: args.grid, on_request: sim.SimRequestHandler(handle_request))
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
module main
|
||||
|
||||
import benchmark
|
||||
import sim
|
||||
import sim.anim
|
||||
import sim.args as simargs
|
||||
import sim.img
|
||||
|
||||
fn main() {
|
||||
args := simargs.parse_args(extra_workers: 2) ? as simargs.ParallelArgs
|
||||
|
||||
img_settings := img.image_settings_from_grid(args.grid)
|
||||
|
||||
mut writer := img.ppm_writer_for_fname(args.filename, img_settings) ?
|
||||
|
||||
mut app := anim.new_app(args)
|
||||
mut workers := []thread{cap: args.workers + 1}
|
||||
|
||||
mut bmark := benchmark.start()
|
||||
|
||||
img_result_chan := chan &sim.SimResult{cap: args.workers}
|
||||
|
||||
defer {
|
||||
image_worker := workers.pop()
|
||||
app.request_chan.close()
|
||||
sim.log('Waiting for workers to finish')
|
||||
workers.wait()
|
||||
app.result_chan.close()
|
||||
img_result_chan.close()
|
||||
sim.log('Waiting for image writer to finish')
|
||||
image_worker.wait()
|
||||
sim.log('Workers finished!')
|
||||
bmark.measure(@FN)
|
||||
sim.log('Closing writer file')
|
||||
writer.close()
|
||||
sim.log('Done!')
|
||||
}
|
||||
|
||||
// start a worker on each core
|
||||
for id in 0 .. app.args.workers {
|
||||
workers << go sim.sim_worker(id, app.request_chan, [app.result_chan, img_result_chan])
|
||||
}
|
||||
|
||||
handle_request := fn [app] (request &sim.SimRequest) ? {
|
||||
app.request_chan <- request
|
||||
}
|
||||
|
||||
workers << go img.image_worker(mut writer, img_result_chan, img_settings)
|
||||
|
||||
go app.gg.run()
|
||||
|
||||
sim.run(app.args.params,
|
||||
grid: app.args.grid
|
||||
on_request: sim.SimRequestHandler(handle_request)
|
||||
)
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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))
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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 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
|
||||
}
|
||||
}
|
|
@ -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,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))
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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')
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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,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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
module main
|
||||
|
||||
import benchmark
|
||||
import sim
|
||||
import sim.args as simargs
|
||||
import sim.img
|
||||
|
||||
fn main() {
|
||||
args := simargs.parse_args() ? as simargs.ParallelArgs
|
||||
|
||||
img_settings := img.image_settings_from_grid(args.grid)
|
||||
|
||||
width := img_settings.width
|
||||
height := img_settings.height
|
||||
total_pixels := width * height
|
||||
|
||||
request_chan := chan &sim.SimRequest{cap: args.workers}
|
||||
result_chan := chan &sim.SimResult{cap: args.workers}
|
||||
|
||||
mut writer := img.ppm_writer_for_fname(args.filename, img_settings) ?
|
||||
mut image_writer := img.new_image_writer(mut writer, img_settings)
|
||||
|
||||
mut workers := []thread{cap: args.workers}
|
||||
mut bmark := benchmark.start()
|
||||
|
||||
defer {
|
||||
request_chan.close()
|
||||
sim.log('Waiting for workers to finish')
|
||||
workers.wait()
|
||||
result_chan.close()
|
||||
bmark.measure(@FN)
|
||||
sim.log('Closing writer file')
|
||||
writer.close()
|
||||
sim.log('Done!')
|
||||
}
|
||||
|
||||
for id in 0 .. args.workers {
|
||||
workers << go sim.sim_worker(id, request_chan, [result_chan])
|
||||
}
|
||||
|
||||
mut x := 0
|
||||
mut y := 0
|
||||
mut request_index := 0
|
||||
|
||||
for {
|
||||
// setup state conditions
|
||||
position := sim.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 := sim.vector(x: 0, y: 0, z: 0)
|
||||
|
||||
mut state := sim.new_state(
|
||||
position: position
|
||||
velocity: velocity
|
||||
)
|
||||
|
||||
state.satisfy_rope_constraint(args.params)
|
||||
request := &sim.SimRequest{
|
||||
id: request_index
|
||||
state: state
|
||||
params: args.params
|
||||
}
|
||||
select {
|
||||
result := <-result_chan {
|
||||
image_writer.handle(result) or { break }
|
||||
}
|
||||
else {
|
||||
if request.id == total_pixels {
|
||||
continue
|
||||
}
|
||||
request_chan <- request
|
||||
x++
|
||||
if x == width {
|
||||
x = 0
|
||||
y++
|
||||
sim.log('y: ${y + 1}')
|
||||
}
|
||||
request_index++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
module main
|
||||
|
||||
import benchmark
|
||||
import sim
|
||||
import sim.args as simargs
|
||||
import sim.img
|
||||
|
||||
fn main() {
|
||||
args := simargs.parse_args(extra_workers: 1) ? as simargs.ParallelArgs
|
||||
|
||||
img_settings := img.image_settings_from_grid(args.grid)
|
||||
|
||||
request_chan := chan &sim.SimRequest{cap: args.workers}
|
||||
result_chan := chan &sim.SimResult{cap: args.workers}
|
||||
|
||||
mut writer := img.ppm_writer_for_fname(args.filename, img_settings) ?
|
||||
|
||||
mut workers := []thread{cap: args.workers + 1}
|
||||
mut bmark := benchmark.start()
|
||||
|
||||
defer {
|
||||
image_worker := workers.pop()
|
||||
request_chan.close()
|
||||
sim.log('Waiting for workers to finish')
|
||||
workers.wait()
|
||||
result_chan.close()
|
||||
sim.log('Waiting for image writer to finish')
|
||||
image_worker.wait()
|
||||
sim.log('Workers finished!')
|
||||
bmark.measure(@FN)
|
||||
sim.log('Closing writer file')
|
||||
writer.close()
|
||||
sim.log('Done!')
|
||||
}
|
||||
|
||||
for id in 0 .. args.workers {
|
||||
workers << go sim.sim_worker(id, request_chan, [result_chan])
|
||||
}
|
||||
|
||||
workers << go img.image_worker(mut writer, result_chan, img_settings)
|
||||
|
||||
handle_request := fn [request_chan] (request &sim.SimRequest) ? {
|
||||
request_chan <- request
|
||||
}
|
||||
|
||||
sim.run(args.params,
|
||||
grid: args.grid
|
||||
on_request: sim.SimRequestHandler(handle_request)
|
||||
)
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
module main
|
||||
|
||||
import benchmark
|
||||
import sim
|
||||
import sim.args as simargs
|
||||
import sim.img
|
||||
|
||||
fn main() {
|
||||
args := simargs.parse_args(sequential: true) ? as simargs.SequentialArgs
|
||||
|
||||
mut bmark := benchmark.start()
|
||||
defer {
|
||||
bmark.measure(@FN)
|
||||
}
|
||||
|
||||
mut writer := img.ppm_writer_for_fname(args.filename, img.image_settings_from_grid(args.grid)) ?
|
||||
defer {
|
||||
writer.close()
|
||||
}
|
||||
|
||||
handle_request := fn [mut writer] (request &sim.SimRequest) ? {
|
||||
result := sim.compute_result(request)
|
||||
pixel := img.compute_pixel(result)
|
||||
return writer.handle_pixel(pixel)
|
||||
}
|
||||
|
||||
sim.run(args.params, grid: args.grid, on_request: sim.SimRequestHandler(handle_request))
|
||||
|
||||
writer.write() ?
|
||||
}
|
|
@ -1,366 +0,0 @@
|
|||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
// sim.v * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
// created by: jordan bonecutter * * * * * * * * * * * * * * * * * * *
|
||||
// jpbonecutter@gmail.com * * * * * * * * * * * * * * * * * * * * * *
|
||||
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
||||
//
|
||||
// I wrote the pendulum simulator to learn V, I think it could be a
|
||||
// good addition to the examples directory.
|
||||
// Essentially, the pendulum sim runs a simulation of a pendulum with
|
||||
// a metallic tip swinging over three magnets.
|
||||
// I run this simulation with the initial position at each pixel in an
|
||||
// image and color the pixel according to the magnet over which it
|
||||
// finally rests.
|
||||
// I used some fun features in V like coroutines, channels,
|
||||
// struct embedding, mutability, methods, and the like.
|
||||
import math
|
||||
import os
|
||||
import term
|
||||
import runtime
|
||||
|
||||
// customisable through setting VJOBS
|
||||
const parallel_workers = runtime.nr_jobs()
|
||||
|
||||
const width = 800
|
||||
|
||||
const height = 600
|
||||
|
||||
struct Vec3D {
|
||||
x f64
|
||||
y f64
|
||||
z f64
|
||||
}
|
||||
|
||||
fn (v Vec3D) add(v2 Vec3D) Vec3D {
|
||||
return Vec3D{
|
||||
x: v.x + v2.x
|
||||
y: v.y + v2.y
|
||||
z: v.z + v2.z
|
||||
}
|
||||
}
|
||||
|
||||
fn (v Vec3D) dot(v2 Vec3D) f64 {
|
||||
return (v.x * v2.x) + (v.y * v2.y) + (v.z * v2.z)
|
||||
}
|
||||
|
||||
fn (v Vec3D) scale(scalar f64) Vec3D {
|
||||
return Vec3D{
|
||||
x: v.x * scalar
|
||||
y: v.y * scalar
|
||||
z: v.z * scalar
|
||||
}
|
||||
}
|
||||
|
||||
fn (v Vec3D) norm_squared() f64 {
|
||||
return v.dot(v)
|
||||
}
|
||||
|
||||
fn (v Vec3D) norm() f64 {
|
||||
return math.sqrt(v.norm_squared())
|
||||
}
|
||||
|
||||
struct SimState {
|
||||
mut:
|
||||
position Vec3D
|
||||
velocity Vec3D
|
||||
accel Vec3D
|
||||
}
|
||||
|
||||
// magnets lie at [
|
||||
// math.cos(index * 2 * math.pi / 3) * magnet_spacing
|
||||
// math.sin(index * 2 * math.pi / 3) * magnet_spacing
|
||||
// -magnet_height
|
||||
// ]
|
||||
struct SimParams {
|
||||
rope_length f64
|
||||
bearing_mass f64
|
||||
magnet_spacing f64
|
||||
magnet_height f64
|
||||
magnet_strength f64
|
||||
gravity f64
|
||||
}
|
||||
|
||||
fn (params SimParams) get_rope_vector(state SimState) Vec3D {
|
||||
rope_origin := Vec3D{
|
||||
x: 0
|
||||
y: 0
|
||||
z: params.rope_length
|
||||
}
|
||||
|
||||
return state.position.add(rope_origin.scale(-1))
|
||||
}
|
||||
|
||||
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 = Vec3D{
|
||||
x: 0
|
||||
y: 0
|
||||
z: params.rope_length
|
||||
}.add(rope_vector)
|
||||
}
|
||||
|
||||
fn (params SimParams) get_grav_force(state SimState) Vec3D {
|
||||
return Vec3D{
|
||||
x: 0
|
||||
y: 0
|
||||
z: -params.bearing_mass * params.gravity
|
||||
}
|
||||
}
|
||||
|
||||
fn (params SimParams) get_magnet_position(theta f64) Vec3D {
|
||||
return Vec3D{
|
||||
x: math.cos(theta) * params.magnet_spacing
|
||||
y: math.sin(theta) * params.magnet_spacing
|
||||
z: -params.magnet_height
|
||||
}
|
||||
}
|
||||
|
||||
fn (params SimParams) get_magnet_force(theta f64, state SimState) Vec3D {
|
||||
magnet_position := params.get_magnet_position(theta)
|
||||
mut diff := magnet_position.add(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)
|
||||
}
|
||||
|
||||
fn (params SimParams) get_magnet_dist(theta f64, state SimState) f64 {
|
||||
return params.get_magnet_position(theta).add(state.position.scale(-1)).norm()
|
||||
}
|
||||
|
||||
fn (params SimParams) get_magnet1_force(state SimState) Vec3D {
|
||||
return params.get_magnet_force(0.0 * math.pi / 3.0, state)
|
||||
}
|
||||
|
||||
fn (params SimParams) get_magnet2_force(state SimState) Vec3D {
|
||||
return params.get_magnet_force(2.0 * math.pi / 3.0, state)
|
||||
}
|
||||
|
||||
fn (params SimParams) get_magnet3_force(state SimState) Vec3D {
|
||||
return params.get_magnet_force(4.0 * math.pi / 3.0, state)
|
||||
}
|
||||
|
||||
fn (params SimParams) get_tension_force(state SimState, f_passive Vec3D) Vec3D {
|
||||
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.dot(f_passive))
|
||||
}
|
||||
|
||||
fn (mut state SimState) increment(delta_t f64, params SimParams) {
|
||||
// basically just add up all forces =>
|
||||
// get an accelleration =>
|
||||
// add to velocity =>
|
||||
// ensure rope constraint is satisfied
|
||||
|
||||
// force due to gravity
|
||||
f_gravity := params.get_grav_force(state)
|
||||
|
||||
// force due to each magnet
|
||||
f_magnet1 := params.get_magnet1_force(state)
|
||||
|
||||
// force due to each magnet
|
||||
f_magnet2 := params.get_magnet2_force(state)
|
||||
|
||||
// force due to each magnet
|
||||
f_magnet3 := params.get_magnet3_force(state)
|
||||
|
||||
// passive forces
|
||||
f_passive := f_gravity.add(f_magnet1.add(f_magnet2.add(f_magnet3)))
|
||||
|
||||
// force due to tension of the rope
|
||||
f_tension := params.get_tension_force(state, f_passive)
|
||||
|
||||
// sum up all the fores
|
||||
f_sum := f_tension.add(f_passive)
|
||||
|
||||
// get the acceleration
|
||||
accel := f_sum.scale(1.0 / params.bearing_mass)
|
||||
state.accel = accel
|
||||
|
||||
// update the velocity
|
||||
state.velocity = state.velocity.add(accel.scale(delta_t))
|
||||
|
||||
// update the position
|
||||
state.position = state.position.add(state.velocity.scale(delta_t))
|
||||
|
||||
// ensure the position satisfies rope constraint
|
||||
state.satisfy_rope_constraint(params)
|
||||
}
|
||||
|
||||
fn (state SimState) done() bool {
|
||||
return state.velocity.norm() < 0.05 && state.accel.norm() < 0.01
|
||||
}
|
||||
|
||||
struct PPMWriter {
|
||||
mut:
|
||||
file os.File
|
||||
}
|
||||
|
||||
struct ImageSettings {
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
struct Pixel {
|
||||
r byte
|
||||
g byte
|
||||
b byte
|
||||
}
|
||||
|
||||
fn (mut writer PPMWriter) start_for_file(fname string, settings ImageSettings) {
|
||||
writer.file = os.create(fname) or { panic("can't create file $fname") }
|
||||
writer.file.writeln('P6 $settings.width $settings.height 255') or {}
|
||||
}
|
||||
|
||||
fn (mut writer PPMWriter) next_pixel(p Pixel) {
|
||||
writer.file.write([p.r, p.g, p.b]) or {}
|
||||
}
|
||||
|
||||
fn (mut writer PPMWriter) finish() {
|
||||
writer.file.close()
|
||||
}
|
||||
|
||||
fn sim_runner(mut state SimState, params SimParams) Pixel {
|
||||
// do the simulation!
|
||||
for _ in 0 .. 1000 {
|
||||
state.increment(0.0005, params)
|
||||
if state.done() {
|
||||
println('done!')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// find the closest magnet
|
||||
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)
|
||||
|
||||
if m1_dist < m2_dist && m1_dist < m3_dist {
|
||||
return Pixel{
|
||||
r: 255
|
||||
g: 0
|
||||
b: 0
|
||||
}
|
||||
} else if m2_dist < m1_dist && m2_dist < m3_dist {
|
||||
return Pixel{
|
||||
r: 0
|
||||
g: 255
|
||||
b: 0
|
||||
}
|
||||
} else {
|
||||
return Pixel{
|
||||
r: 0
|
||||
g: 0
|
||||
b: 255
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SimResult {
|
||||
id u64
|
||||
p Pixel
|
||||
}
|
||||
|
||||
struct SimRequest {
|
||||
id u64
|
||||
params SimParams
|
||||
mut:
|
||||
initial SimState
|
||||
}
|
||||
|
||||
fn sim_worker(request_chan chan SimRequest, result_chan chan SimResult) {
|
||||
// serve sim requests as they come in
|
||||
for {
|
||||
mut request := <-request_chan or { break }
|
||||
|
||||
result_chan <- SimResult{
|
||||
id: request.id
|
||||
p: sim_runner(mut request.initial, request.params)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ValidPixel {
|
||||
Pixel
|
||||
mut:
|
||||
valid bool
|
||||
}
|
||||
|
||||
fn image_worker(mut writer PPMWriter, result_chan chan SimResult, total_pixels u64) {
|
||||
// as new pixels come in, write them to the image file
|
||||
mut current_index := u64(0)
|
||||
mut pixel_buf := []ValidPixel{len: int(total_pixels), init: ValidPixel{
|
||||
valid: false
|
||||
}}
|
||||
for {
|
||||
result := <-result_chan or { break }
|
||||
pixel_buf[result.id].Pixel = result.p
|
||||
pixel_buf[result.id].valid = true
|
||||
|
||||
for current_index < total_pixels && pixel_buf[current_index].valid {
|
||||
writer.next_pixel(pixel_buf[current_index].Pixel)
|
||||
current_index++
|
||||
}
|
||||
|
||||
if current_index >= total_pixels {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
params := SimParams{
|
||||
rope_length: 0.25
|
||||
bearing_mass: 0.03
|
||||
magnet_spacing: 0.05
|
||||
magnet_height: 0.03
|
||||
magnet_strength: 10.0
|
||||
gravity: 4.9
|
||||
}
|
||||
|
||||
mut writer := PPMWriter{}
|
||||
writer.start_for_file('test.ppm', ImageSettings{
|
||||
width: width
|
||||
height: height
|
||||
})
|
||||
defer {
|
||||
writer.finish()
|
||||
}
|
||||
|
||||
result_chan := chan SimResult{}
|
||||
request_chan := chan SimRequest{}
|
||||
|
||||
// start a worker on each core
|
||||
for _ in 0 .. parallel_workers {
|
||||
go sim_worker(request_chan, result_chan)
|
||||
}
|
||||
|
||||
go fn (request_chan chan SimRequest, params SimParams) {
|
||||
mut index := u64(0)
|
||||
println('')
|
||||
for y in 0 .. height {
|
||||
term.clear_previous_line()
|
||||
println('Line: $y')
|
||||
for x in 0 .. width {
|
||||
// setup initial conditions
|
||||
mut state := SimState{}
|
||||
state.position = Vec3D{
|
||||
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
|
||||
}
|
||||
state.velocity = Vec3D{}
|
||||
state.satisfy_rope_constraint(params)
|
||||
request_chan <- SimRequest{
|
||||
id: index
|
||||
initial: state
|
||||
params: params
|
||||
}
|
||||
index++
|
||||
}
|
||||
}
|
||||
request_chan.close()
|
||||
}(request_chan, params)
|
||||
|
||||
image_worker(mut writer, result_chan, width * height)
|
||||
}
|
Loading…
Reference in New Issue