{"id":29058877,"url":"https://github.com/haath/incerto","last_synced_at":"2026-04-30T07:33:55.310Z","repository":{"id":301398409,"uuid":"1008843955","full_name":"haath/incerto","owner":"haath","description":"Blazing-fast™ Monte Carlo simulations.","archived":false,"fork":false,"pushed_at":"2025-09-16T16:46:42.000Z","size":295,"stargazers_count":0,"open_issues_count":0,"forks_count":1,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-30T07:33:14.127Z","etag":null,"topics":["bevy","carlo","monte","multi-threading","random","rng","simulation"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/haath.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-26T07:17:42.000Z","updated_at":"2025-09-15T07:33:30.000Z","dependencies_parsed_at":"2025-06-26T17:39:37.697Z","dependency_job_id":"b083b45d-39b2-438f-96a2-76abe92ca48e","html_url":"https://github.com/haath/incerto","commit_stats":null,"previous_names":["haath/incerto"],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/haath/incerto","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haath%2Fincerto","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haath%2Fincerto/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haath%2Fincerto/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haath%2Fincerto/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/haath","download_url":"https://codeload.github.com/haath/incerto/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/haath%2Fincerto/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32458237,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T22:27:22.272Z","status":"online","status_checked_at":"2026-04-30T02:00:05.929Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bevy","carlo","monte","multi-threading","random","rng","simulation"],"created_at":"2025-06-27T07:01:54.767Z","updated_at":"2026-04-30T07:33:55.304Z","avatar_url":"https://github.com/haath.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# incerto\n\n[![Crates.io Version](https://img.shields.io/crates/v/incerto)](https://crates.io/crates/incerto) [![docs.rs](https://img.shields.io/docsrs/incerto)](https://docs.rs/incerto/latest/incerto/) [![Crates.io License](https://img.shields.io/crates/l/incerto)](https://github.com/haath/incerto/blob/main/LICENSE)\n\nRust crate for heavyweight multi-threaded Monte Carlo simulations.\n\n[![](assets/trader_net_worths.png)](examples/traders.rs)\n\n## Installation\n\nThe crate can be installed from [crates.io](https://crates.io/crates/incerto).\nCurrently the only dependency is [bevy@0.16](https://github.com/bevyengine/bevy), and there are no cargo features.\nDue to an [issue](https://github.com/rust-lang/docs.rs/issues/1588#issuecomment-1008292478) with macro crate linking, it is necessary to explicitly add bevy as a dependency as well.\n\n```toml\n[dependencies]\nbevy = { version = \"0.16\", default-features = false }\nincerto = \"*\"\n```\n\n## Usage\n\nThis crate is powered by [Bevy](https://github.com/bevyengine/bevy), which is a high-performance ECS framework.\n\nThis means that simulations are set up and executed using [Entities](https://bevy-cheatbook.github.io/programming/ec.html) and [Systems](https://bevy-cheatbook.github.io/programming/systems.html).\n\nIn-depth knowledge of Bevy's internals is not required however, since we have abstracted away most interactions with Bevy. Instead, we expect the user to only:\n\n- Define components.\n- Spawn entities, each a collection of one or more components.\n- Implement systems that update the entities on each simulation step.\n\n```rust\nuse incerto::prelude::*;\n\nlet simulation: Simulation = SimulationBuilder::new()\n                                // add one or more entity spawners\n                                .add_entity_spawner(...)\n                                .add_entity_spawner(...)\n                                // add one or more systems\n                                .add_systems(...)\n                                .add_systems(...)\n                                // finish\n                                .build();\n```\n\nIt is recommended to start with the [examples](examples/).\n\n#### Define components\n\nComponents will be the primary data type in the simulation.\nThey can be anything, so long as they can derive the `Component` trait.\n\n```rust\n#[derive(Component, Default)]\nstruct Counter\n{\n    count: usize,\n}\n```\n\nEmpty components, typically called _Markers_, are also sometimes useful to pick out specific entities.\n\n```rust\n#[derive(Component)]\nstruct GroupA;\n\n#[derive(Component)]\nstruct GroupB;\n```\n\n#### Spawn entities\n\nEntities are spawned at the beginning of each simulation using user-provided functions like this one.\n\n```rust\nfn spawn_coin_tosser(spawner: \u0026mut Spawner)\n{\n    spawner.spawn(Counter::default());\n}\n```\n\nNote that entities are in fact collections of one or more components, as such the `spawn()` function accepts a [Bundle](https://bevy-cheatbook.github.io/programming/bundle.html). A bundle can be a single component like above, or a tuple with multiple components.\n\n```rust\nfn spawn_coin_tossers_in_groups(spawner: \u0026mut Spawner)\n{\n    for _ in 0..100\n    {\n        spawner.spawn((GroupA, Counter::default()));\n    }\n    for _ in 0..100\n    {\n        spawner.spawn((GroupB, Counter::default()));\n    }\n}\n```\n\n#### Implement systems\n\nSystems are the processing logic of the simulation.\nDuring each step, every user-defined system is executed once.\nSystems use [queries](https://bevy-cheatbook.github.io/programming/queries.html) to interact with and update entities in the simulation.\n\n```rust\n/// Increment all counters by one in each simulation step.\nfn counters_increment_system(mut query: Query\u003c\u0026mut Counter\u003e)\n{\n    for mut counter in \u0026mut query\n    {\n        counter.count += 1;\n    }\n}\n```\n\nQueries may use the `With` and `Without` keywords to filter their scope.\n\n```rust\nfn counters_increment_group_a(mut query: Query\u003c\u0026mut Counter, With\u003cGroupA\u003e\u003e) { ... }\n\nfn counters_increment_group_b(mut query: Query\u003c\u0026mut Counter, With\u003cGroupB\u003e\u003e) { ... }\n```\n\nThey may also select multiple components, mutably or immutably. (note the use of `mut`, `\u0026` and `\u0026mut`)\n\n```rust\nfn update_multiple_components(mut query: Query\u003c(\u0026mut Counter, \u0026OtherComponent)\u003e) { ... }\n\nfn read_only_system(query: Query\u003c\u0026Counter\u003e) { ... }\n```\n\nSystems may also work with multiple queries. This allows for entities in the simulation to interact with each other.\n\n```rust\nfn multiple_queries(\n    read_from_group_a: Query\u003c\u0026Counter, With\u003cGroupA\u003e\u003e,\n    mut write_to_group_b: Query\u003c\u0026mut Counter, With\u003cGroupB\u003e\u003e,\n) { ... }\n```\n\n#### Running the simulation\n\nThe simulation may be executed using the `run()`, method.\n\n```rust\n// Run the simulation for 100 steps.\nsimulation.run(100);\n\n// Continue the same simulation for another 200 steps.\nsimulation.run(200);\n```\n\n### Collecting results\n\n#### Counting entities\n\nThe number of entities with a given component can be sampled at any time using `count()`.\n\n```rust\nlet num_still_alive = simulation.count::\u003cWith\u003cAlive\u003e\u003e();\nlet num_still_healthy = simulation.count::\u003c(With\u003cAlive\u003e, Without\u003cSick\u003e)\u003e();\n```\n\n#### Sample entity\n\nA value may be sampled from a specific entity by attaching an `Identifier` to it.\n\n```rust\n// 1. Blanket implementation for Identifier exists for any: Component + Copy + Eq + Hash\n#[derive(Component, Clone, Copy, PartialEq, Eq, Hash)]\nenum EntityId { Bob, Alice }\n\n// 2. Implement this trait for the component to be sampled.\nimpl Sample\u003cf64\u003e for NetWorth {\n    fn sample(component: \u0026Self) -\u003e f64 {\n        // 3. Sample the value of the component as needed.\n        component.value\n    }\n}\n\n// 4. Fetch the sampled value of the component from the simulation.\nlet bobs_net_worth = simulation.sample::\u003cNetWorth, _, _\u003e(\u0026EntityId::Bob);\n```\n\n#### Sample single\n\nAttaching an `Identifier` to an entity can be skipped, if it is expected that only a single entity with the `C: Sample` will exist.\nIn this case it can be sampled using `sample_single()`.\n\n```rust\nlet net_worth = simulation.sample_single::\u003cNetWorth, _\u003e();\n```\n\n#### Sample aggregate\n\nA value may be sampled as the aggregate from many components in the simulation.\n\n```rust\n// 1. Implement this trait for the component to be sampled.\nimpl SampleAggregate\u003cf64\u003e for NetWorth {\n    fn sample_aggregate(components: \u0026[\u0026Self]) -\u003e f64 {\n        // 2. Aggregate the values of all components as needed.\n        components.map(|nw| nw.value).mean()\n    }\n}\n\n// 3. Sample the aggregate value from the simulation.\nlet average_net_worth = simulation.sample_aggregate::\u003cNetWorth, _\u003e();\n\n//    ... or with a filter\nlet average_net_worth_blue_hair = simulation.sample_aggregate_filtered::\u003cNetWorth, With\u003cBlueHair\u003e, _\u003e();\n```\n\n#### Time series\n\nCollecting a time series from the simulation is similar to the sampling described above.\nThe component to be sampled into the time series still needs to implement either `Sample` or `SampleAggregate` depending on whether the sampling is done per-entity or in aggregate.\n\nThe difference is that the recording of time series values needs to be set up on the `SimulationBuilder`.\n\n```rust\n// 1. Set up the time series recording as needed.\nbuilder.record_time_series::\u003cNetWorth, EntityId, f64\u003e();\nbuilder.record_aggregate_time_series::\u003cNetWorth, f64\u003e();\nbuilder.record_aggregate_time_series_filtered::\u003cNetWorth, With\u003cBlueHair\u003e, f64\u003e();\n\n// 2. Collect the results from the simulation.\nlet bobs_net_worth_series: Vec\u003cf64\u003e = simulation.get_time_series::\u003cNetWorth, _, _\u003e(\u0026EntityId::Bob).unwrap();\nlet average_net_worth_series: Vec\u003cf64\u003e = simulation.get_aggregate_time_series::\u003cNetWorth, _\u003e().unwrap();\nlet average_net_worth_series_blue_hair: Vec\u003cf64\u003e = simulation.get_aggregate_time_series_filtered::\u003cNetWorth, With\u003cBlueHair\u003e, _\u003e().unwrap();\n```\n\n### Built-in aggregators\n\nSeveral built-in aggregators are available for numeric types.\nThese are automatically implemented for any component that implements sampling to a numeric type, for example `Sample\u003cf32\u003e`.\n\n```rust\n// compute the median net worth from all NetWorth components\nlet median = simulation.sample_aggregate::\u003cNetWorth, Median\u003c_\u003e\u003e().unwrap();\n```\n\nSome available aggregators are:\n\n- `Minimum\u003cT\u003e`\n- `Maximum\u003cT\u003e`\n- `Mean\u003cT\u003e`\n- `Median\u003cT\u003e`\n- `Percentile\u003cT, P\u003e` (computes the P-th percentile)\n\n## Performance\n\nWhen it comes to experiments like Monte Carlo, performance is typically of paramount importance since it defines their limits in terms of scope, size, length and granularity. Hence why I made the decision build this crate on top of bevy. The ECS architecture on offer here is likely the most memory-efficient and parallelizable way one can build such simulations, while still maintaining some agency of high-level programming.\n\nBevy has proven that it can handle worlds with hundreds of thousands (maybe even millions) of entities without slowing down enough to compromise 3D rendering at 60 frames per second.\nAnd given that this crate adds practically no runtime overhead, your monte carlo experiments will likely be limited only by your hardware and your imagination.\n\nYou get to enjoy all the performance gains of the ECS automatically. However there are a few things you may want to keep in mind.\n\n- **Temporal granularity:**\n  This is just a fancy way of saying `how much time is each simulated step?`. The crate itself makes no mention of time, and treats each simulation as a series of discrete equitemporal steps. Whether each step represents one minute, one hour, or one day, is up to the user and likely contextual to the kind of experiment being conducted. For example, each step might represent one hour when modelling the weather, or one day when modelling pandemic infection rates.\n  As such, there are great performance gains to be found by moving up a level in granularity. If you can manage to model the changes in the simulation in 5-minute steps instead of 1-minute steps, the simulation will magically run in one fifth of the time!\n- **System parallelization:**\n  Bevy's scheduler will automatically place disjoint systems on separate threads whenever possible.\n  Two systems are disjoint when one's queries do not mutate components that the other is also accessing.\n  The rule of thumb to achieve this whenever possible, is to design each system such that:\n  - It has a singular purpose.\n  - Only queries for components that it definitely needs.\n- **Singular components:**\n  It may be tempting to simplify entity design by putting all of an entity's data in a single component, especially if one is used to object-oriented languages. However, doing so will impact your performance in the long term since it would render system parallelization neigh impossible.\n  The general recommendation is to favor composition, meaning that each distinct attribute of an entity should be in a separate component. Imagine, for example, how since a person's age and body temperature are largely independent, systems attempting to read or update these values should be allowed to run in parallel.\n- **Entity archetypes:**\n  Bevy likes to put similar-looking entities together in groups called _archetypes_, which enables it to more efficiently store such entities in shared tables. So if components are added to or removed from existing entities at runtime the archetype tables have to be remade, which is a drain on performance.\n  So in case where an entity's state needs to change often in the simulation, consider using persistent enums instead.\n\n## Planned work\n\n- Add some utilities to the crate for easy access to random values, noise etc\n- Add some support for data plotting.\n\n## Credits\n\nThe name as well as the initial motivation behind this project came from the brilliant [Incerto](https://www.goodreads.com/series/164555-incerto) book series by Nassim Nicholas Taleb.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhaath%2Fincerto","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhaath%2Fincerto","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhaath%2Fincerto/lists"}