From 4391ae563dc39d894f78b7b4a9a09ba79402360c Mon Sep 17 00:00:00 2001 From: Ulises Jeremias Cornejo Fandos Date: Sat, 12 Feb 2022 14:38:07 -0300 Subject: [PATCH] examples: improve the pendulum simulation, with several modes and diagrams (#13446) --- cmd/tools/modules/testing/common.v | 8 + examples/pendulum-simulation/.gitignore | 18 + examples/pendulum-simulation/LICENSE | 21 + examples/pendulum-simulation/README.md | 93 +++++ examples/pendulum-simulation/animation.v | 37 ++ examples/pendulum-simulation/full.v | 56 +++ .../modules/sim/anim/app.v | 64 +++ .../modules/sim/anim/worker.v | 19 + .../modules/sim/args/parser.v | 158 ++++++++ .../pendulum-simulation/modules/sim/img/ppm.v | 74 ++++ .../modules/sim/img/worker.v | 40 ++ .../modules/sim/img/writer.v | 68 ++++ .../pendulum-simulation/modules/sim/log.v | 9 + .../pendulum-simulation/modules/sim/params.v | 96 +++++ .../modules/sim/params_test.v | 125 ++++++ .../pendulum-simulation/modules/sim/runner.v | 96 +++++ .../pendulum-simulation/modules/sim/sim.v | 47 +++ .../modules/sim/sim_test.v | 64 +++ .../pendulum-simulation/modules/sim/vec.v | 50 +++ .../modules/sim/vec_test.v | 64 +++ .../pendulum-simulation/modules/sim/worker.v | 67 ++++ .../modules/sim/worker_test.v | 63 +++ examples/pendulum-simulation/parallel.v | 84 ++++ .../pendulum-simulation/parallel_with_iw.v | 50 +++ examples/pendulum-simulation/sequential.v | 30 ++ examples/pendulum_sim/sim.v | 366 ------------------ 26 files changed, 1501 insertions(+), 366 deletions(-) create mode 100644 examples/pendulum-simulation/.gitignore create mode 100644 examples/pendulum-simulation/LICENSE create mode 100644 examples/pendulum-simulation/README.md create mode 100644 examples/pendulum-simulation/animation.v create mode 100644 examples/pendulum-simulation/full.v create mode 100644 examples/pendulum-simulation/modules/sim/anim/app.v create mode 100644 examples/pendulum-simulation/modules/sim/anim/worker.v create mode 100644 examples/pendulum-simulation/modules/sim/args/parser.v create mode 100644 examples/pendulum-simulation/modules/sim/img/ppm.v create mode 100644 examples/pendulum-simulation/modules/sim/img/worker.v create mode 100644 examples/pendulum-simulation/modules/sim/img/writer.v create mode 100644 examples/pendulum-simulation/modules/sim/log.v create mode 100644 examples/pendulum-simulation/modules/sim/params.v create mode 100644 examples/pendulum-simulation/modules/sim/params_test.v create mode 100644 examples/pendulum-simulation/modules/sim/runner.v create mode 100644 examples/pendulum-simulation/modules/sim/sim.v create mode 100644 examples/pendulum-simulation/modules/sim/sim_test.v create mode 100644 examples/pendulum-simulation/modules/sim/vec.v create mode 100644 examples/pendulum-simulation/modules/sim/vec_test.v create mode 100644 examples/pendulum-simulation/modules/sim/worker.v create mode 100644 examples/pendulum-simulation/modules/sim/worker_test.v create mode 100644 examples/pendulum-simulation/parallel.v create mode 100644 examples/pendulum-simulation/parallel_with_iw.v create mode 100644 examples/pendulum-simulation/sequential.v delete mode 100644 examples/pendulum_sim/sim.v diff --git a/cmd/tools/modules/testing/common.v b/cmd/tools/modules/testing/common.v index d11b8c9826..ac34462065 100644 --- a/cmd/tools/modules/testing/common.v +++ b/cmd/tools/modules/testing/common.v @@ -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 diff --git a/examples/pendulum-simulation/.gitignore b/examples/pendulum-simulation/.gitignore new file mode 100644 index 0000000000..5e967630af --- /dev/null +++ b/examples/pendulum-simulation/.gitignore @@ -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 diff --git a/examples/pendulum-simulation/LICENSE b/examples/pendulum-simulation/LICENSE new file mode 100644 index 0000000000..1d17ebf251 --- /dev/null +++ b/examples/pendulum-simulation/LICENSE @@ -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. diff --git a/examples/pendulum-simulation/README.md b/examples/pendulum-simulation/README.md new file mode 100644 index 0000000000..bc1e67b5fa --- /dev/null +++ b/examples/pendulum-simulation/README.md @@ -0,0 +1,93 @@ +
+

+ +

+ +

Pendulum Simulation in V

+ +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) + +
+
+ +[![Build Status][workflowbadge]][workflowurl] +[![Docs Validation][validatedocsbadge]][validatedocsurl] +[![License: MIT][licensebadge]][licenseurl] + +
+ +## 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 diff --git a/examples/pendulum-simulation/animation.v b/examples/pendulum-simulation/animation.v new file mode 100644 index 0000000000..369efd2645 --- /dev/null +++ b/examples/pendulum-simulation/animation.v @@ -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)) +} diff --git a/examples/pendulum-simulation/full.v b/examples/pendulum-simulation/full.v new file mode 100644 index 0000000000..301e629caf --- /dev/null +++ b/examples/pendulum-simulation/full.v @@ -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) + ) +} diff --git a/examples/pendulum-simulation/modules/sim/anim/app.v b/examples/pendulum-simulation/modules/sim/anim/app.v new file mode 100644 index 0000000000..3453e7bc2e --- /dev/null +++ b/examples/pendulum-simulation/modules/sim/anim/app.v @@ -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) +} diff --git a/examples/pendulum-simulation/modules/sim/anim/worker.v b/examples/pendulum-simulation/modules/sim/anim/worker.v new file mode 100644 index 0000000000..a3b149e024 --- /dev/null +++ b/examples/pendulum-simulation/modules/sim/anim/worker.v @@ -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)) +} diff --git a/examples/pendulum-simulation/modules/sim/args/parser.v b/examples/pendulum-simulation/modules/sim/args/parser.v new file mode 100644 index 0000000000..88578982e1 --- /dev/null +++ b/examples/pendulum-simulation/modules/sim/args/parser.v @@ -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) +} diff --git a/examples/pendulum-simulation/modules/sim/img/ppm.v b/examples/pendulum-simulation/modules/sim/img/ppm.v new file mode 100644 index 0000000000..b7d5ba3f92 --- /dev/null +++ b/examples/pendulum-simulation/modules/sim/img/ppm.v @@ -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() +} diff --git a/examples/pendulum-simulation/modules/sim/img/worker.v b/examples/pendulum-simulation/modules/sim/img/worker.v new file mode 100644 index 0000000000..0b335700de --- /dev/null +++ b/examples/pendulum-simulation/modules/sim/img/worker.v @@ -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') } +} diff --git a/examples/pendulum-simulation/modules/sim/img/writer.v b/examples/pendulum-simulation/modules/sim/img/writer.v new file mode 100644 index 0000000000..0e2816987c --- /dev/null +++ b/examples/pendulum-simulation/modules/sim/img/writer.v @@ -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 + } +} diff --git a/examples/pendulum-simulation/modules/sim/log.v b/examples/pendulum-simulation/modules/sim/log.v new file mode 100644 index 0000000000..7f4dca5aa0 --- /dev/null +++ b/examples/pendulum-simulation/modules/sim/log.v @@ -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) + } +} diff --git a/examples/pendulum-simulation/modules/sim/params.v b/examples/pendulum-simulation/modules/sim/params.v new file mode 100644 index 0000000000..ad19116368 --- /dev/null +++ b/examples/pendulum-simulation/modules/sim/params.v @@ -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)) +} diff --git a/examples/pendulum-simulation/modules/sim/params_test.v b/examples/pendulum-simulation/modules/sim/params_test.v new file mode 100644 index 0000000000..b48ff9555d --- /dev/null +++ b/examples/pendulum-simulation/modules/sim/params_test.v @@ -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 +} diff --git a/examples/pendulum-simulation/modules/sim/runner.v b/examples/pendulum-simulation/modules/sim/runner.v new file mode 100644 index 0000000000..bd4e92fa58 --- /dev/null +++ b/examples/pendulum-simulation/modules/sim/runner.v @@ -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') + } + } +} diff --git a/examples/pendulum-simulation/modules/sim/sim.v b/examples/pendulum-simulation/modules/sim/sim.v new file mode 100644 index 0000000000..0e7fd0c2fb --- /dev/null +++ b/examples/pendulum-simulation/modules/sim/sim.v @@ -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 +} diff --git a/examples/pendulum-simulation/modules/sim/sim_test.v b/examples/pendulum-simulation/modules/sim/sim_test.v new file mode 100644 index 0000000000..c27ce1e8f6 --- /dev/null +++ b/examples/pendulum-simulation/modules/sim/sim_test.v @@ -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 +} diff --git a/examples/pendulum-simulation/modules/sim/vec.v b/examples/pendulum-simulation/modules/sim/vec.v new file mode 100644 index 0000000000..fa00815eeb --- /dev/null +++ b/examples/pendulum-simulation/modules/sim/vec.v @@ -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()) +} diff --git a/examples/pendulum-simulation/modules/sim/vec_test.v b/examples/pendulum-simulation/modules/sim/vec_test.v new file mode 100644 index 0000000000..9a5a519420 --- /dev/null +++ b/examples/pendulum-simulation/modules/sim/vec_test.v @@ -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 +} diff --git a/examples/pendulum-simulation/modules/sim/worker.v b/examples/pendulum-simulation/modules/sim/worker.v new file mode 100644 index 0000000000..2b0e22a57e --- /dev/null +++ b/examples/pendulum-simulation/modules/sim/worker.v @@ -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 + } +} diff --git a/examples/pendulum-simulation/modules/sim/worker_test.v b/examples/pendulum-simulation/modules/sim/worker_test.v new file mode 100644 index 0000000000..be4bf9e1e1 --- /dev/null +++ b/examples/pendulum-simulation/modules/sim/worker_test.v @@ -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 +} diff --git a/examples/pendulum-simulation/parallel.v b/examples/pendulum-simulation/parallel.v new file mode 100644 index 0000000000..809976dadd --- /dev/null +++ b/examples/pendulum-simulation/parallel.v @@ -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++ + } + } + } +} diff --git a/examples/pendulum-simulation/parallel_with_iw.v b/examples/pendulum-simulation/parallel_with_iw.v new file mode 100644 index 0000000000..c2feed3708 --- /dev/null +++ b/examples/pendulum-simulation/parallel_with_iw.v @@ -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) + ) +} diff --git a/examples/pendulum-simulation/sequential.v b/examples/pendulum-simulation/sequential.v new file mode 100644 index 0000000000..9ec41a38d3 --- /dev/null +++ b/examples/pendulum-simulation/sequential.v @@ -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() ? +} diff --git a/examples/pendulum_sim/sim.v b/examples/pendulum_sim/sim.v deleted file mode 100644 index f8ef11d0db..0000000000 --- a/examples/pendulum_sim/sim.v +++ /dev/null @@ -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) -}