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
|
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' {
|
if testing.github_job != 'sokol-shaders-can-be-compiled' {
|
||||||
// These examples need .h files that are produced from the supplied .glsl files,
|
// 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
|
// 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