Skip to content

Commit

Permalink
Add an introductory page to the API documentation (#617)
Browse files Browse the repository at this point in the history
The contents of `docs/main_page.md` will be the first thing the user
sees upon browsing the API documentation.

This fixes #616.
  • Loading branch information
kyllingstad authored Oct 9, 2020
1 parent 90fd66d commit 5e1750a
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 1 deletion.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ if(LIBCOSIM_BUILD_APIDOC)
set(DOXYGEN_QT_AUTOBRIEF "YES")
set(DOXYGEN_MULTILINE_CPP_IS_BRIEF "YES")
set(DOXYGEN_EXCLUDE_PATTERNS "*.cpp" "*.c" "*/fmuproxy/*service*")
set(doxygenInputs "${CMAKE_SOURCE_DIR}/include")
set(doxygenInputs "${CMAKE_SOURCE_DIR}/docs" "${CMAKE_SOURCE_DIR}/include")
if(LIBCOSIM_BUILD_PRIVATE_APIDOC)
list(APPEND doxygenInputs
"${CMAKE_SOURCE_DIR}/src/cpp"
Expand Down
166 changes: 166 additions & 0 deletions docs/main_page.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
\mainpage Introduction

The [libcosim](https://open-simulation-platform.github.io/libcosim) API can
roughly be divided into two parts, corresponding to the main phases of a
co-simulation. These are:

* The _setup phase_, which is everything that happens before you actually
run a simulation, such as reading configuration files, loading FMUs,
setting up the system for simulation, and so on.
* The _execution phase_, where we run a simulation, solving the model
equations, logging and/or displaying output, et cetera.

These phases are not distinguished in any particular way in the API. Most
symbols simply live in the top-level `#cosim` namespace, and there is no
specific naming convention to set them apart. Nor are they entirely distinct
in functionality, as several classes and functions are used in both phases.
Nevertheless, the distinction between "setup" and "execution" provides a good
framework for learning and understanding the libcosim API.

## Setting up a simulation

The central class in the setup phase is `cosim::system_structure`. This
contains a representation of the modelled system, consisting of the entities
(simulators and functions) in it and the connections between them. It requires
a description of each simulator or function, which must be given in the form
of a `cosim::model` or `cosim::function_type` object, respectively. At this
stage, the entities themselves are not instantiated, so no simulator/function
code is actually run.

You can build the system structure from scratch, starting with an empty
`cosim::system_structure` object, or you can read it from a file. libcosim
supports two file formats:

* The [Open Simulation Platform Interface Specification](https://opensimulationplatform.com/specification/)
(OSP-IS) system structure format, which we consider our "native" format
and therefore support fully. Use `cosim::load_osp_config()` to read OSP-IS
system structure files.
* The [System Structure & Parametrization](https://ssp-standard.org/) (SSP)
format, for which we have limited support, but which we aim to support
more fully in the future. Use `cosim::ssp_loader` to read SSP files.

In both of these formats, the models used in the simulation are specified by
URIs that point to FMUs. These URIs need to be converted into `cosim::model`
objects that represent the FMUs in question. This is the job of a
`cosim::model_uri_resolver`, and usually, you should start with the one
created by `cosim::default_model_uri_resolver()`. You can customise it with
support for additional URI schemes if necessary.

## Executing a simulation

The central class in the execution phase is `cosim::execution`. It manages the
entities involved in a single co-simulation run (i.e., an _execution_) and
provides a high-level interface for driving the simulation process.

By far, the easiest way to set up an execution is to first create a
`cosim::system_structure`, as described above, and inject this structure
into an empty `cosim::execution` using `cosim::inject_system_structure()`.
However, it is also possible to build the execution "by hand" by adding
simulators and functions to it in the form of `cosim::async_slave` and
`cosim::function` objects, respectively.

When would you choose which method? Starting with `cosim::system_structure`
has several advantages:

* `cosim::system_structure` refers to entities by _name_, whereas
`cosim::execution` refers to them by numeric IDs.
* A single `cosim::system_structure` can be used to set up multiple
`cosim::execution` objects. This is useful in many cases, for example if
you want to simulate the same system with different initial conditions.
* `cosim::system_structure` only requires entity _descriptions_, whereas
`cosim::execution` requires actual entity instances. This has practical
and performance-related implications, in that the latter could entail
loading of DLLs with model code, and even network communication in the
case of distributed co-simulations.

However, if you only need to run a single simulation and therefore need to
instantiate all entities exactly once anyway, and you are OK with using
numerical indices to manipulate them (which you must, anyway, once the
execution is up and running), then reaching straight for `cosim::execution`
may be the right thing to do.

### Getting results

To obtain the results of an execution, i.e. the values of various simulator
variables at various times, create an _observer_. More specifically, add a
`cosim::observer` object using `cosim::execution::add_observer()`.
The `cosim::observer` interface is designed to support many use cases, from
file output to real-time visualisation.

libcosim itself supplies three implementations of this interface, namely:

* `cosim::file_observer`, which logs output to CSV files
* `cosim::time_series_observer`, which buffers time series in memory
* `cosim::last_value_observer`, which simply keeps track of the most
recent variable values.

### Manipulating the system

Sometimes, you need to influence the simulation in some way. This could be to
change parameters, override variable values, or even transform them in more or
less subtle ways. In libcosim, this is done via the `cosim::manipulator`
interface.

`cosim::manipulator` is very similar to `cosim::observer`, but where the
latter merely has a read-only view of the system, a manipulator has much
extended powers.
More precisely, a `cosim::manipulator` can apply _any transformation_ to _any
variable_ in the system.

libcosim supplies two manipulators:

* `cosim::override_manipulator`, which, as the name suggests, lets you
override the value of any variable with a value of your choosing.
* `cosim::scenario_manager`, which manipulates variables according to a
given _scenario_.

A scenario can be loaded from a
[file](https://open-simulation-platform.github.io/libcosim/scenario), or it
can be specified in code as a `cosim::scenario::scenario` object. In both
cases, it consists of a series of _events_ that occur at predetermined times.
At each event, some variable is modified in some way.

### Changing the algorithm

Under the hood, `cosim::execution` delegates much of its responsibilities to
a _co-simulation algorithm_ (sometimes called _master_ algorithm). These
responsibilities include:

* Deciding when to step various simulators forward in time, and how far
* Keeping simulators synchronised in time
* Routing data between simulators

In libcosim, a co-simulation algorithm must be implemented as a subclass of
the `cosim::algorithm` interface.

There exist several co-simulation algorithms, and libcosim has been designed
to support even the more advanced ones. The library itself provides only one
option: `cosim::fixed_step_algorithm`. For the most part, this is a rather
simple, fixed-step (i.e., non-adaptive) algorithm. However, it has a nice
extra feature in that it allows the use of different step sizes for different
simulators, as long as they're all multiples of the same base step size.

## Customising libcosim

The library was designed with a large number of use cases in mind, but we are
only able to support so many of them out of the box. If there is something you
need to do, and there is no way to do it with libcosim's built-in
functionality, you may be able to achieve it by _extending_ libcosim in
various ways.

Here is a list of some important customisation points. All of these are
interfaces (pure virtual classes) of which you can make your own
implementations. Many have already been mentioned, but we list them again for
completeness and ease of reference:

* `cosim::algorithm` – to implement alternative co-simulation algorithms
* `cosim::observer` – to implement your own observers
* `cosim::manipulator` – to implement your own manipulators
* `cosim::model`, `cosim::async_slave` and `cosim::slave` – to implement
simulators that don't necessarily come in the form of FMUs
* `cosim::function_type` and `cosim::function` – to implement additional
function types
* `cosim::model_uri_sub_resolver` – to implement support for additional
model URI schemes
* `cosim::file_cache` – to change the way libcosim caches files (such as
the unpacked contents of FMUs)
12 changes: 12 additions & 0 deletions include/cosim/osp_config_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,26 @@ namespace cosim
{


/// The information contained in an OSP-IS system structure file.
struct osp_config
{
/// The system structure
cosim::system_structure system_structure;

/// The default start time for a simulation
time_point start_time;

/// The default/recommended step size for a simulation
duration step_size;

/// A set of default initial values
variable_value_map initial_values;
};


/**
* Loads an OSP-IS system structure file.
*/
osp_config load_osp_config(
const boost::filesystem::path& configPath,
cosim::model_uri_resolver& resolver);
Expand Down

0 comments on commit 5e1750a

Please sign in to comment.