# 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.

Note

## 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 simulation

• prolog: (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. The write_every and write_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 and create_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 distribution distr.

• 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 every Model. 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 the log_level config entry. Default log levels are specified via the meta configuration, see the base configuration for examples.

#### Monitoring the state of the model¶

• Each Model contains a Monitor that regularly provides information to the frontend.

• The monitor() method is the place to provide that information

• It can be used for information purposes, but also to dynamically stop a simulation depending on the provided monitoring information (so-called stop conditions).

### 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.

• 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 base Model using the model configuration.

• Is used by managers to map a grid to or control agent movement.

### 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 a hexagonal 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, or GraphEntity. 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 a zip-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 the apply_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()
);


#### The shared Utopia Entity type¶

• A shared type that holds a state; the Agent and Cell types are derived from this base class.

• Makes the apply_rule interface possible.

### 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 interface

• Available 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.

### 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 the parallel_execution node in the parameter space.

• Depending on the execution policies, programmers will have to avoid data races.

## Writing Model Data¶

### The Utopia HDF5 Library¶

• This library makes the HDF5 C library accessible in a convenient way.

• Beside the interface to the C library, it provides an intelligent chunking algorithm.

### 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 the save_graph functions to flawlessly recreate the graph in your plotting function.

### 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 or utopia 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 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 CopyMe --new-name MyFancyModel

• 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 option in Utopia can be set through a configuration parameter. The complete set of configuration options of a simulation run is gathered in a meta configuration.

• Configuration levels, sequentially updating the defaults to arrive at the final meta configuration:

1. Base configuration: all the default values

2. Model configurations: model-specific defaults

• Defined alongside the respective models, see above

• Provide defaults not for the whole meta configuration but for the respective models; can be imported where needed.

3. User configuration: user- or machine-specific updates to the defaults

• Used for all simulation runs, regardless of the model.

• Nonexistent by default. Deploy using utopia config user --deploy; see utopia config --help for more info. The deployed version contains descriptions of all possible settings.

4. Run configuration: updates for a specific simulation run

• The parameter_space key of the meta config is passed to the model; 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.

### 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

• 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 and cluster_params of the meta configuration.

#### 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.

### Defining Parameter Sweeps¶

• The parameter_space key of the meta config is interpreted as a multidimensional object, a ParamSpace. 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. The ParamSpace 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!

### YAML and YAML Tags – Configuration files on steroids¶

• YAML has many benefits as a configuration language, not only for Defining Parameter Sweeps or stop conditions.

• Anchors and inheritance make it easy to re-use definitions; avoid copy-paste at all costs! This is a built-in functionality of YAML:

# Anchors: define with &, use with *
some_value: &some_value 42
some_other_value: *some_value  # ... will also be 42

# Inheritance
some_mapping: &some_mapping
foo: bar
spam: spam
some_other_mapping_based_on_the_first_mapping:
<<: [*some_mapping]          # Can also specify multiple anchors here
spam: SPAM                   # Overwrite an inherited value

• Additional YAML tags help in creating configuration entries:

seconds: !expr 60*60*24 + 1.5  # Evaluate mathematical expressions
a_slice: !slice [10,100,5]     # Create a python slice object
a_range: !range [0, 10, 2]     # Invokes python range(*args)
bool1: !any [true, false]      # Evaluates a sequence of booleans
bool2: !all [true, true]


### 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)
- !stop-condition
func: timeout_wall
seconds: 0.4
# further arguments to utopya.stopcond_funcs.timeout_wall

# Second condition (for showcasing the long syntax)
- !stop-condition
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
- !stop-condition
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


## 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 the data_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.

### 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.

### 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

# 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 the utopya.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.

### 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.