Feature List#
This page aims to give an overview of Utopia features and improve feature discoverability. The descriptions are brief and aim to convey the functionality of some feature and link it to internal names and the corresponding documentation.
This page links heavily to other pages which provide more information on the features. Lines starting with the 📚 symbol also denote further reading material.
Note
Please inform us about any outdated information or broken links on this page!
Model Building#
The Model
Base Class#
Utopia Models are specializations of this base class
It provides commonly needed functionality (see below) and allows control by the frontend.
This common interface also allows nesting of models, thus allowing a model hierarchy.
The following methods allow specialization of the model:
perform_step
: Performs the iteration step. You have total freedom here.write_data
: Specifies the data that is to be stored to the associated HDF5 file.monitor
: (Optional) Provides monitoring data to the frontend to control the simulationprolog
: (Optional) Provides the possibility to perform a task before the model’s first iteration. By default this includes writing of the initial state.epilog
: (Optional) Provides the possibility to perform a task after the model’s last iteration.
Model traits can be used to specify the default types used by the model, e.g. the random number generator.
Basic data writing#
By default, the
write_data
method is invoked each time step. Thewrite_every
andwrite_start
configuration arguments can be used to further control the time at which the data should be written.The
Model
base class provides two convenience methods to create datasets which already have the correct dimension names and coordinate labels associated:create_dset
andcreate_cm_dset
.
Random Numbers#
All Utopia models have access to a shared random number generator with which you can create a random number through
distr(*this->_rng)
from your specified distributiondistr
.By controlling the
seed
of this shared RNG, the generated random numbers allow replication.The default generator is the Mersenne Twister specified in the Utopia core type
Utopia::DefaultRNG
.
Logging#
Based on spdlog, logging fast yet conveniently using the fmt library for string parsing. No more
std::cout
!Available as
_log
member in everyModel
. Example:_log->debug("Creating {} entities now ...", num_new_entities); create_entities(num_new_entities); _log->info("Added {} new entities. Have a total of {} entities now", num_new_entities, entities.size());
Verbosity can be controlled for each
Model
using thelog_level
config entry. Default log levels are specified via the meta configuration, see the base configuration for examples.📚 Doxygen
Monitoring the state of the model#
Each
Model
contains aMonitor
that regularly provides information to the frontend.The
monitor()
method is the place to provide that informationIt can be used for information purposes, but also to dynamically stop a simulation depending on the provided monitoring information (so-called stop conditions).
📚 Doxygen
Model Configuration#
All parameters a model is initialized with
Available via
_cfg
member;Model
base class takes care of supplying it.Each model needs to specify a default model configuration, but it is combined with other configurations before reaching the model instance, see below.
Reading Config Entries#
Extract a config entry through, optionally providing a default value:
# Extract an entry; throws KeyError if the key is missing auto foo = get_as<int>("foo", this->_cfg); # Provide a default value when the key is missing auto bar = get_as<int>("bar", this->_cfg, 42)
Supported types for
get_as<T>
are defined by yaml-cpp library and include basic types as well as some container types (std::vector
,std::array
, also in nested form)There exist specializations to conveniently load entries as Armadillo types (vectors, matrices, …)
The Physical Space
a Model is embedded in#
Contains information on dimensionality, periodicity, and physical extent
Each
Model
has, by default, a 2D space attached; periodicity and extent is set by the baseModel
using the model configuration.Is used by managers to map a grid to or control agent movement.
📚 Doxygen
The CellManager
#
Creates a grid discretization of the physical space and aims for being controllable from the configuration while providing a good performance.
The grid discretization can be a
square
or ahexagonal
lattice. This can be changed via the configuration, allowing easy evaluation of the effects of different discretizations.For example usage, see implemented models.
The AgentManager
#
Manage agents in a space and let them move to a relative or absolute position
Makes sure that the agent does not leave the bounds specified by the associated physical space the model is embedded in.
Note: Currently no efficient algorithm present to detect nearby agents.
The apply_rule
Interface – rule-based formulation of model dynamics#
Apply a rule on Utopia
Entity
objects, e.g.Cell
,Agent
, orGraphEntity
. This can be used to change the state of an entity.Rules can be applied synchronously (in parallel) or asynchronously (sequentially)
For asynchronous updates, the iteration order can be shuffled for each invocation. This avoids artifacts originating from a fixed application order.
Code example:
// Apply a rule to all cells of a cell manager apply_rule<Update::async, // Apply the rule asynchronously, // one cell after the other. Shuffle::off> // Do not shuffle the container // before applying the rule ( [](const auto& cell){ // Operate on a cell auto& state = cell->state; // Get the state reference state.age += 1; // Increment the age member // return state; // Optional for async update. // REQUIRED for sync update }, _cm.cells() // Apply the rule to all cells in the cell manager. // This can however, also be any container of // Utopia entities. ); // Apply a rule to all vertices of a graph apply_rule<IterateOver::vertices, Update::async, Shuffle::off>( [](auto vertex, auto& g){ g[vertex].state.property = 42; }, g // The graph to iterate over );
With a rule that accepts more than one argument, additional container-like arguments can be passed to
apply_rule
, leading to azip
-iteration. For each entity, the arguments from the containers are then unpacked into the respective call to the rule function.apply_rule
for manual state updates offers overloads with parallel execution policies. The rule will then be applied according to the selected policy, similar to a parallel STL algorithm (it actually uses them internally). Even with a sequential policy (or none), internals of theapply_rule
algorithms may parallelize if the feature is enabled. Enabling parallel features happens through the parameter space configuration, or explicitly, see Parallel STL Algorithms.// Apply a rule with multithreading apply_rule<Update::sync>( ExecPolicy::par, // NOTE: Rule must avoid data races! [](const auto& cell){ return cell->state + 1; }, _cm.cells() );
📚 Doxygen, apply_rule on graph entities, parallel STL algorithm overloads
The select
Interface – Selecting entities using some condition#
Can be used to select entities from a manager in many different ways: sampling, via a probability, with a custom condition,…
For the
CellManager
: supports a clustering algorithm, selection of boundary cells, and creation of lanes in the grid to create different compartments.Fully controllable from the configuration.
Graph Creation#
Create a graph with the
create_graph
function using a selection of generating algorithms and a configuration-based interfaceAvailable algorithms for k-regular, fully-connected, random (Erdös-Renyi), small-world (Watts-Strogatz), highly clustered small-world (Klemm-Eguíluz), and scale-free (Barabási-Albert and Bollobás-Riordan) graphs (see here).
Load a graph directly from GraphML or DOT (Graphviz) files. See here for more details.
📚 Doxygen, Graph documentation entry, Graph Creation requirements for the apply_rule on Graphs
Iterate Over Graph Entities#
Conveniently loop over graph entities:
include <utopia/graph/iterator.hh> // ... // Loop over all vertices and print their states for (auto vertex : range<IterateOver::vertex>(g)) { std::cout << g[vertex].property << "\n"; } // Loop over all neighbors of vertex '0' and print their states for (auto neighbor : range<IterateOver::neighbor>(boost::vertex(0, g), g)) { std::cout << g[vertex].property << "\n"; }
Parallel STL Algorithms#
Utopia overloads several STL algorithms with runtime execution policies for multithreading and vectorization. The code is agnostic to whether the optional dependencies for multithreading are installed.
#include <utopia/core/parallel.hh> // Enable parallel execution (will do nothing if requirements are not met) Utopia::ParallelExecution::set(Utopia::ParallelExecution::Setting::enabled); // Copy in parallel, if enabled std::vector<double> in(1E6, 1.0), out(1E6); std::copy(Utopia::ExecPolicy::par_unseq, begin(in), end(in), begin(out));
Utopia
ExecPolicy
mirrors STL execution policies.Parallel features can be controlled via the meta-configuration. The
PseudoParent
enables or disables them depending on theparallel_execution
node in the parameter space.Depending on the execution policies, programmers will have to avoid data races.
Separate models repository#
For advanced modelling projects, one typically wants to implement models in their own repository, with their own dependencies and separate version control. We maintain a template project which can be used to quickly generate such a models repository.
Writing Model Data#
The Utopia HDF5 Library#
The DataManager
#
While writing simple data structures can easily be done directly with the Utopia HDF5 library, this becomes rather difficult in more complex scenarios, e.g. when the number of agents in a system change.
The Utopia
DataManager
allows to define the possible write operations and then control their execution mostly via the configuration file.
Saving Static Graphs#
Use the
create_graph_group
function to create a graph group in which to save the graph using thesave_graph
functions to flawlessly recreate the graph in your plotting function.📚 Doxygen
Saving Dynamic Graphs#
Save a dynamic graph and its properties in a Utopia frontend compatible way with a single function.
Simulation Control & Configuration#
To generate simulation data from a model, a model needs to be executed.
This is controlled via the command line interface integrated into the Python frontend of Utopia, the utopya
package.
The utopia
Command Line Interface (CLI)#
Basic interface to control the generation of simulation data and its analysis
utopia run MyModel # ... using all defaults utopia run MyModel path/to/run_cfg.yml # Custom run config utopia eval MyModel # Evaluate the last run utopia eval MyModel --plots-cfg path_to/plots_cfg.yml # Custom plots
Available in Utopia’s virtual environment,
utopia-env
.Allows setting parameters directly from the command line (have access to the whole meta configuration):
utopia run MyModel --num-steps 1000 --set-params log_levels.model=debug --set-model-params my_param=12.345
Debug Mode: by adding the
--debug
flag, logger verbosity is increased and errors are raised; this makes debugging easier.Configuration Sets: models may provide example configuration sets, which are basically pairs of run and eval configuration files. These can be selected via the
--cfg-set
command line argument and simplify running different scenarios of a model. See Configuration sets for more information.Interactive Plotting: for
utopia run
orutopia eval
, pass the--interactive
flag to not quit the CLI after the plotting routine has finished.The CLI will then give the option to change the plotting-related arguments, e.g. which plots are to be created or from which configuration file they should be created.
The already-loaded data is kept in memory and thus speeds-up the creation of plots, especially when large amounts of data are to be loaded.
Not to confused with the feature to work interactively with
utopya
using the Python interface, e.g. via IPython or Jupyter Notebook. See below for more info on that feature.
Copying a model: The utopia CLI helps a lot with that by copying all relevant files, renaming them, and even refactoring them. Copying between Utopia projects is also possible.
utopia models copy CopyMeGraph --new-name MyFancyGraphModel
To learn about all possible commands:
utopia --help # Shows all available subcommands utopia run --help # Help for running a model utopia eval --help # Help for evaluating a model run utopia config --help # Help regarding the Utopia configuration utopia models --help # Help regarding the model registry
Meta-Configuration – Controlling the Model Simulation#
Every frontend-related option in Utopia can be set through a configuration parameter. The complete set of configuration options of a simulation run is gathered in the so-called meta configuration.
There are multiple configuration levels, sequentially updating the defaults to arrive at the final meta configuration:
Base configuration: the base layer with a large number of default values
Framework configuration: an update layer that is specific to the framework a model is used in, here: Utopia itself.
Project configuration: if using Utopia with a separate models repository, that repository can also provide defaults. Within Utopia itself, this configuration layer is not used.
Model configuration: model-specific defaults
Defined alongside the respective models, typically as
<model_name>_cfg.yml
file.Provides defaults not for the whole meta configuration but only for the respective model’s entry at
parameter_space.<model_name>
.
User configuration: user- or machine-specific updates to the above
Run configuration: updates for a specific simulation run
This may also come from a
run.yml
file as part of a model’s configuration set.
Temporary changes: additional updates given via the Utopia CLI
All involved configuration files are backed up into the model run’s output directory. This includes information about the state of the involved repositories: the latest git commit and potentially existing differences from it.
The
parameter_space
key of the meta config is what is passed to the Utopia C++ backend; it can be conveniently sweeped over (see below).Model parameters can be validated by the frontend. This helps detecting wrongly-specified simulation runs before starting them and allows to reduce model implementation code.
📚 Hierarchy of Multiverse Configuration Levels, Multiverse Base Configuration,
Multiverse
, Validating model parameters, FAQ Entry
The Utopia Multiverse – Parallelization of Simulations#
Comparing the simulation results for a set of different parameters is often required for the analysis of the model system. This is very easy in Utopia. First, some definitions:
A Utopia Universe refers to a single simulation carried out with Utopia, i.e. a specific model implementation that received a specific configuration as input.
A Utopia Multiverse refers to a set of such Universes with different configurations as input.
These Universes can be naively parallelized, because they do not depend on each other. By default, when performing a multiverse run, Utopia automatically parallelizes their execution in this way.
To control the behaviour, see the
worker_manager
entry of the meta configuration.For the easy definition of different such configurations, see below.
Cluster Support#
The
Multiverse
also supports distributed execution, e.g. on a cluster. It detects which set of compute nodes a run is performed on and distributes the tasks accordingly.Cluster mode is controlled via the
cluster_mode
andcluster_params
of the meta configuration.📚 Multiverse Base Configuration, bwForCluster Support Project
Reporter#
The frontend also provides the
Reporter
classses which inform about the progress of the current tasks.They can be customized to do specific reporting tasks at defined trigger points, e.g. after a task (the simulation of a universe) was finished
By default, they show an adaptive progress bar during simulation and generate a
_report.txt
file after the run which shows some run statistics.📚 Multiverse Base Configuration,
Reporter
,WorkerManagerReporter
Defining Parameter Sweeps#
The
parameter_space
key of the meta config is interpreted as a multidimensional object, aParamSpace
. The dimensions of this space are parameters that are assigned not a single value, but a set of values, a so-called parameter dimension or sweep dimension. TheParamSpace
then contains all cartesian combinations of parameters. The Multiverse can then iterate over all points in parameter space.To define parameter dimensions, simply use the
!sweep
and YAML tags in your run configuration. In the example below, a \(25 \times 4 \times 101\)-sized parameter space is created.# Run configuration for MyModel --- parameter_space: seed: !sweep # ... to have some statistics ... default: 42 range: [25] # unpacked to [0, 1, 2, ..., 24] using range(*args) MyModel: my_first_param: !sweep default: 42 values: [-23, 0, 23, 42] my_second_param: !sweep default: 0. linspace: [0., 10., 101] # also available: logspace another_param: 123. # No sweep here
The
!coupled-sweep
tag can be used to move one parameter along with another parameter dimension.# Run configuration for MyModel --- parameter_space: seed: !sweep default: 42 values: [1, 2, 4, 8] MyModel: my_coupled_param: !coupled-sweep target_name: my_first_param # default and values from my_first_param used my_other_coupled_param: !coupled-sweep target_name: my_first_param default: foo values: [foo, bar, baz, spam] # has to have same length as target
Sweeps are also possible for plot configurations!
📚
ParamSpace
,ParamDim
,CoupledParamDim
, Multidimensional Model Runs
Stop Conditions – Dynamically stop simulations#
Dynamically evaluate whether a certain simulation (or the whole run) should be stopped
Reasons for stopping can be: timeout of individual simulation, timeout of multiverse run, or some specific monitor entry.
Total timeout is controlled via
run_kwargs.timeout
key of meta configuration.Can be configured via meta configuration by passing a list of conditions to the
run_kwargs.stop_conditions
key. Example:run_kwargs: # The total timeout (in seconds) for finishing _all_ simulations timeout: !expr 60*60*24 # 24h in this case. Default: None # A list of StopCondition objects to check during the run _for each worker_. # The entries of the following list are OR-connected, i.e. it suffices that # one is fulfilled for the corresponding worker to be stopped. stop_conditions: # First condition (using the shortest possible syntax) - func: timeout_wall seconds: 0.4 # further arguments to utopya.stopcond_funcs.timeout_wall # Second condition (for showcasing the long syntax) - name: single simulation timeout (in long syntax) description: terminates a worker if it ran for too long enabled: true to_check: # The functions specified here are AND-connected, i.e.: _all_ # need to return True for the stop condition to be fulfilled - func: timeout_wall # will look for utopya.stopcond_funcs.timeout_wall method seconds: 0.4 # further arguments to utopya.stopcond_funcs.timeout_wall # Third condition: Terminating depending on a monitor entry - name: extinction description: terminates a simulation if entity density goes to zero to_check: - func: check_monitor_entry entry_name: MyModel.density # TODO set your monitor entry name here operator: <= value: 0. # Will stop if: {monitor entry} <= 0.
The utopia-env
#
A python virtual environment where all Utopia-related installation takes place; this insulates the installation of frontend dependencies from the rest of your system.
Contains the
utopya
frontend package.Is created as part of the build process; checks dependencies and installs them if required.
In order to be able to run the
utopia
CLI command, make sure to have activated the virtual environment:$ source utopia/build/activate (utopia-env) $ utopia run dummy
📚 README
Data Analysis & Plotting#
Data analysis and plotting is implemented in the Python frontend of Utopia, the utopya
package.
It interfaces with the dantro package to supply a data evaluation pipeline, directly connected to the running of simulations.
Data handling with the DataManager
#
Is used to load all generated simulation data and represent it in a hierachical fashion (the “data tree”) with a uniform interface
Is implemented in dantro and specialized for Utopia via the
DataManager
class and thedata_manager
key of the meta configuration.Makes use of xarray to provide labelled dimensions and coordinates. This information is extracted from the HDF5 attributes.
Supports lazy loading of data using so-called proxies; these are only resolved when the data is actually needed (saves you a lot of RAM!). When the data is too large for the machine’s memory, the dask framework makes it possible to still work with the data.
Can load data in parallel, which can speed up loading for data structures that are CPU-limited during loading. See the
--load-parallel
CLI option.⚠️ This should not be confused with the backend
DataManager
used for writing data, see above.📚 dantro documentation, Handling Data,
DataManager
, Multiverse Base Configuration, Loading files in parallel
Automatic Model Plots#
Utopia couples tightly with the dantro framework and makes it easy to define plots alongside the model implementation.
It is possible to configure a set of default plots which are automatically created after a model is run. For more control, plot configuration files specify the plots that are to be created.
Configuring Plots#
Plots can be specified in a plot configuration file.
Plot configurations can make use of so-called base plot configurations to reduce copy-pasting. This also supports multiple inheritance.
Custom Plot functions#
Models can make use of both generic plot functions (implemented in utopya) or model-specific plot functions, which are defined in
python/model_plots
. This allows a large flexibility in how the simulation data is analyzed and visualized.Plot functions can also be implemented in separate files.
The Data Transformation Framework – Generic Data Processing#
This framework generalizes operations on data such that arbitrary transformations on the loaded data can be defined right from the configuration. It is implemented in dantro and integrated tightly with the plotting framework.
Given some arguments, it creates a directed, acyclic graph (DAG), where each node is a transformation operation: given some input, it performs an operation, and creates some output.
This allows generalized plot functions which can focus on visualizing the data they are provided with (instead of doing both: data analysis and visualization).
The DAG framework provides a file cache that can store intermediate results such that they need not be re-computed every time the plots are generated. This makes sense for data transformations that take a long time to compute but only very little time to store to a file and load back in from there.
Work interactively with utopya and dantro#
The
Model
class makes it very easy to set up a model multiverse, run it, and load its data.import utopya # Create the model object for the ContDisease model cdm = utopya.Model(name="ContDisease") # Create a multiverse (mv), let it run with some config file, and then # load the data into the DataManager (dm) mv, dm = cdm.create_run_load() # mv, dm = cdm.create_run_load(run_cfg_path="path/to/my/run_cfg.yml") # ... do something with the loaded data or the PlotManager (mv.pm)
📚 Working Interactively with Utopia,
Model
class,create_mv()
,create_run_load()
,create_frozen_mv()
(when loading data from an existing run)
Batch-run and batch-evaluate simulations#
The
BatchTaskManager
allows to configure multiple run and eval configurations, all from a single so-called “batch file”. That way, all configuration options are in one place. This has several benefits:Configuration options can be easily shared within the batch file, e.g. to define a common aesthetic for plots.
Creating output from multiple simulations becomes easier to replicate.
The batch file is self-documenting and can, in principle, be used as a lab book.
This feature can be especially helpful if performing data evaluation for a talk or thesis: one can set a specific output directory (e.g.
my_thesis/figures
) and easily re-create plot output.
The batch feature is available via the CLI by calling
utopia batch
📚 Batch Framework,
utopia batch --help
,BatchTaskManager
class, Default Batch Configuration
Model Testing Framework#
Defining tests alongside a model improves the reliability and trust into the model implementation. This can already be useful during the implementation of a model, e.g. when following a test-driven development approach.
Utopia makes it easy to define tests by providing both a C++- and a Python-based testing framework.
C++: Boost-based Model Tests#
Testing parts of a model implementation can be best done on C++ side, where you have access to the individual parts of the implementation. The Boost.Test framework offers a lot of support in defining tests for a model.
To build and run only model-specific tests, use
make test_model_<name>
. Consult the README for more information on available test targets.Model code coverage can also be evaluated; again, see Installation.
Python-based Model Tests#
Python-based tests are most useful for the macroscopic perspective, i.e.: given some configuration, testing that the model data is as expected.
A test case can be as simple as this:
def test_stones(): """Tests that stones are created and are constant over time""" # Create the multiverse, run it with the stones config, and load data mv, dm = mtc.create_run_load(from_cfg="stones.yml") # Go over all the created universes for uni_no, uni in dm["multiverse"].items(): # Get the kind of each cell data = uni["data"]["ForestFire"]["kind"] # There is a non-zero number of stone cells (encoded by kind == 4) assert (data == 4).sum() > 0 # ... which is constant over time assert ((data == 4).sum(["x", "y"]).diff("time") == 0).all()
The tests make use of the pytest framework and some Utopia-specific helper classes which make running simulations and loading data easy. For example, test-specific configuration files can be passed to the
utopya.model.Model.create_run_load()
method of theutopya.testtools.ModelTest
class… just as in the CLI.Tests are located on a per-model basis in the
python/model_tests
directory; have a look there for some more examples on how to define tests.The tests can be invoked using
python -m pytest -v python/model_tests/MyModel
Consult the Installation and the pytest documentation for more information on test invocation.
📚 Writing Unit Tests (for general remarks),
Model
,ModelTest
, pytest
Miscellaneous#
These are not really features of Utopia itself, but of the way it is set up on the GitLab. This environment provides some useful functionality you should know about.
Issue Board & Merge Request#
Any questions, bug reports, feature suggestions… write an issue by visiting the issue board. Also seemingly minor questions have a place here!
Want to contribute code to the framework repository? Open a merge request. Looking forward to your contributions! :)
When writing issues, MR descriptions, notes, or other content on the GitLab, take note of the many features of GitLab MarkDown, e.g. for posting syntax-highlighted code, tables, simple diagrams, … and much more.
To add more involved diagrams like class diagrams or sequence diagrams, the GitLab also provides access to PlantUML, simply by defining a code block with
plantuml
as syntax:```plantuml Bob -> Alice : hello Alice -> Bob : hi ```
Pipelines – Automatic Builds & Test Execution#
When pushing to the Utopia project, an automatically triggered pipeline performs a number of tasks to assert Utopia’s functionality:
All code is built with different compilers and different release types
All framework tests are run
All implemented model tests are run
The documentation is built and deployed to a test environment to view its current state
Having these tasks being run automatically takes the burden off the developers’ shoulders to assert that Utopia is still working as it should.
Code changes can be merged into the master only when the pipeline succeeds and a code review has taken place.