This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Architecture and Conventions

What DPsim is built from, and the rules the code follows.

Start here. These pages describe the shape of the library, the modules it divides into and the class hierarchy underneath them, together with the conventions any change is expected to follow.

1 - Architecture

The modules DPsim is built from and the class hierarchy underneath them.

Modules and Dependencies

The figure below shows the main components of the DPsim library and their dependencies on other software projects. All functionality is implemented in the C++ core, which can be used standalone or together with the Python interface. The Python interface is a thin wrapper of the C++ core. Jupyter notebooks can either use the DPsim Python interface to run simulations or call executables implemented in C++. The data analysis and plotting is always done in Python using common libraries like Matplotlib. To collect the simulation results from within Python, one can use the villas-dataprocessing Python package.

image

Another approach to get data in or out of DPsim is the VILLASnode interface, which does not depend on Python at all. The main purpose of the VILLASnode interface is to exchange data during the simulation runtime, for example, in real-time simulation experiments. The data could be send to other simulators, hardware or other software components like databases. Storing the data in databases can be another way of managing (also offline) simulation results if the Python CSV method is not desireable.

The CIM reader is based on the CIM++ library and provides a comfortable alternative to defining the grid manually in C++ or Python. In principle, it calls the same functions to create elements, which are also used in the C++ defined example scenarios, but automatically. DPsim also provides a way to visualize the defined networks before simulation.

The main solver of DPsim is currently the MNA solver because it enables a rather deterministic computation time per simulation time step, which is necessary for real-time simulation. Apart from that, it is also well established in offline circuit simulation. The only dependency of the MNA solver is the linear algebra library Eigen. For some component models, it is possible to use the Sundials ODE solver in combination with the MNA solver. In that case, the component is solved by the ODE solver whereas the network is still handled by the MNA solver. A DAE solver is currently under development. Its main purpose will be offline simulation, for example, to provide reference results where simulation runtime and real-time execution are not relevant.

The component models depend mostly on the Eigen library. Even if components are used in combination with Sundials ODE / DAE solvers, we try to keep the specific functions required by these solvers independent of the Sundials package.

Class Hierarchy

The Simulation class holds references to instances of Interface, Solver, Logger and SystemTopology. For a simulation scenario, the minimum description would include a SystemTopology and a solver type. The Solver instance is then created by the Simulation.

image

An important function of the Simulation is to collect all tasks, which have to be executed during the simulation. These tasks include computation steps of the individual power system component models as well as read and write tasks of the interfaces and logging variables etc. Before the scheduling is done, Simulation calls getTasks() to retrieve the tasks from three of these classes: Solver, Interface and Logger. The power system component and signal-model tasks are collected by the Solver instances and relayed to the Simulation, while interfaces and loggers contribute their own tasks directly.

All power system element classes inherit from the IdentifiedObject class. This class corresponds with the IdentifiedObject of the IEC61970 CIM and has a uid and name attribute as well.

image

The next layer of specialization includes information on the topological connection between network elements. An electrical bus and network nodes in general are represented by the TopologiclaNode class. The connection of electrical components, TopologicalPowerComp, is managed via terminals of type TopologicalTerminal. These three types describe the electrical connections of the network, which are bidirectional and include voltages and currents. The signal type elements, TopologicalSignalComp, can only have unidirectional components, which are not expressed using node and terminals. Instead, the attribute system is used to define signal type connections.

2 - Build

Building DPsim from source, with and without the optional features.

All builds start from a checkout of the repository. To build and read the code, cloning over HTTPS needs no account:

git clone https://github.com/sogno-platform/dpsim.git
cd dpsim

If you intend to contribute, clone your own fork over SSH instead, since contributions are accepted from forks only and pushing needs an authenticated remote:

git clone git@github.com:<your-user>/dpsim.git
cd dpsim
git remote add upstream https://github.com/sogno-platform/dpsim.git

The container route below is the most reproducible, because the image already carries every dependency at the version CI uses. The native routes need those dependencies installed by hand.

Container based

The commands below use docker, but the images are ordinary OCI images, so podman works as a drop-in replacement throughout. On Fedora and Rocky, podman is usually the one already installed. Substitute podman for docker in every command if you prefer it.

The repository ships a development image with all required dependencies:

docker build -t sogno/dpsim:dev -f packaging/Docker/Dockerfile.dev .

Alternatively, pull the prebuilt image instead of building it:

docker pull sogno/dpsim:dev

Then start an interactive session with the working copy mounted into the container:

docker run -it -p 8888:8888 -v $(pwd):/dpsim --privileged sogno/dpsim:dev bash

The -p option maps port 8888 so a JupyterLab instance inside the container is reachable from the host. The --privileged option is required for debug builds. On Windows, the current directory is spelled differently:

docker run -it -p 8888:8888 -v ${pwd}:/dpsim --privileged sogno/dpsim:dev bash

Inside the container, the C++ and Python libraries build as follows:

cd /dpsim
mkdir build && cd build
cmake ..
cmake --build . --target dpsimpy

Targets that are not built by default have to be named explicitly, for example:

cmake --build . --target dpsimpy dpsimpyvillas

To build everything:

cmake --build .

Optional features are enabled through the CMake options defined in the CMakeLists.txt files, for example:

cmake .. -DWITH_GSL=ON

To use the freshly built Python package without installing it, put both the compiled extension and the pure Python package on the path:

cd /dpsim/build
export PYTHONPATH=$(pwd):$(pwd)/../python/src

This is the setup most contributors work with, since it picks up a rebuild immediately without any reinstall step.

Do not use pip install -e . for this. An editable install only links the pure Python sources; dpsimpy is a compiled extension, so edits to the C++ are not picked up and you keep running whatever binary was built at install time. The failure is silent, since the import still succeeds and simply gives you stale behaviour. Either rebuild and rely on PYTHONPATH as above, or reinstall the package after every C++ change.

To summarise the three ways to get DPsim, in increasing order of involvement: pip install dpsim for a released Linux wheel, a native build plus PYTHONPATH for development, and make install to place a build system wide.

If you develop inside a conda environment, the equivalent is to register the same two directories from within the active environment. This needs conda-build installed:

cd /dpsim/build
conda develop $(pwd) && conda develop $(pwd)/../python/src

Note that this writes into the environment, so it becomes specific to your setup.

To run JupyterLab against it:

cd /dpsim
jupyter lab --ip="0.0.0.0" --allow-root --no-browser

To install DPsim system wide instead:

cd /dpsim/build
sudo make install

CMake for Linux

The authoritative dependency list is whatever the Dockerfiles install, since that is what CI builds against. See packaging/Docker/Dockerfile.dev for the Fedora set, and install-fedora-deps.sh or install-ubuntu-deps.sh for scripts that install them.

Both libcimpp and villas-node are optional. Neither needs to be built from source, though the images do not yet take the same route for both.

libcimpp publishes prebuilt .deb and .rpm packages per CIM version as release assets. The Fedora and Debian images install those directly, while the Rocky image still builds it from source:

# Pick the package matching your distribution and the CIM version you need.
wget https://github.com/sogno-platform/libcimpp/releases/download/release%2Fv2.2.0/libcimpp_CGMES_2.4.15_16FEB2016-2.2.0-Linux.deb
sudo apt-get install -y ./libcimpp_CGMES_2.4.15_16FEB2016-2.2.0-Linux.deb
sudo ldconfig

VILLASnode is served from the package repositories at https://packages.fein-aachen.org, which carry both debian/ and redhat/. Note that the images currently still build it from source, pinned to a specific commit, so the packaged version is the more convenient route for a local build but is not what CI exercises.

Building either from source remains supported, and the deps scripts above do that, which is what you want when you need a specific commit rather than a release.

Sundials is only needed for the DAE solver. If your distribution does not package it, the version CI uses is:

git clone --branch v3.2.1 --recurse-submodules --depth 1 https://github.com/LLNL/sundials.git
mkdir -p sundials/build && cd sundials/build
cmake .. -DCMAKE_BUILD_TYPE=Release
make -j$(nproc) install

Cloning, building and installing then work exactly as in the container section above.

CMake for Windows

Windows is built in CI on windows-latest, so the recipe below mirrors what .github/workflows/build_test_windows.yaml runs. You need Visual Studio with the C++ desktop development workload, CMake and Git for Windows. For Python support, install Python 3 and add it to your PATH. Let CMake pick the default generator rather than naming a Visual Studio version, so the build follows whichever Visual Studio you have.

For the C++ libraries only:

mkdir build
cd build
cmake -DWITH_PYBIND=OFF ..
cmake --build . --target dpsim --target dpsim-models --parallel

For the Python bindings, install pybind11 first:

pip install pybind11[global]
mkdir build
cd build
cmake -DWITH_PYBIND=ON ..
cmake --build . --target dpsimpy --parallel

If CMake rejects the spdlog dependency because of its minimum policy version, add -DCMAKE_POLICY_VERSION_MINIMUM=3.5, which is what CI currently does as a workaround.

The dpsim-villas library is not available on Windows, since it requires VILLASnode, which does not build there. WITH_VILLAS therefore stays off and the dpsimpyvillas target does not exist, so co-simulation examples cannot be built on Windows. The CIM reader is likewise not part of the CI Windows build, as libcimpp is not installed there.

CMake for macOS

macOS is not covered by CI, so treat this as a starting point rather than a supported path. Install the dependencies with Homebrew:

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
brew install gcc git cmake graphviz python3 gsl eigen spdlog

Then build as in the container section. Building on Apple Silicon is known to fail while building libcimpp, see issue #609. Configure with -DWITH_CIM=OFF if you do not need the CIM reader.

Python package

Wheels are produced by cibuildwheel in the publish_to_pypi workflow, currently for manylinux x86_64 and CPython 3.9 through 3.13. To build a source distribution locally:

python3 -m build --sdist

Nix

DPsim can be built using Nix, a declarative package manager for reproducible builds. The following steps require a working single-user or multi-user installation of Nix, but not necessarily NixOS.

DPsim uses the Flakes feature, which has to be enabled:

echo "experimental-features=nix-command flakes" > ~/.config/nix/nix.conf

Building DPsim, including all its dependencies:

nix build github:sogno-platform/dpsim

The result is placed in the result folder of the current directory. For development, a local environment can be set up with:

nix develop github:sogno-platform/dpsim

The Flake reference above can be replaced by a local path such as . when the repository is already checked out.

Documentation

The Python and C++ references are generated by separate CMake targets. Both are also built and published by the documentation workflow on every push to master.

Python

Install Sphinx or use the Docker image, then:

mkdir -p build && cd build
cmake ..
make docs

The result is generated in build/docs/sphinx/html/. Note that this target requires the Python bindings, so it is only available when configured with -DWITH_PYBIND=ON.

C++

Install Doxygen or use the Docker image, then:

mkdir -p build && cd build
cmake ..
make docs_cxx

The result is generated in build/docs/doxygen/html/.

Website

The surrounding website is a Hugo site under docs/hugo. It needs the Hugo version pinned in the documentation workflow, since the theme does not build with arbitrary versions:

cd docs/hugo
npm ci
hugo --minify

3 - Coding Conventions

Scaling of quantities and logging rules that code in DPsim has to follow.

Conventions that apply across the codebase. For the process of getting a change merged, see contributing.

This is a summary of general guidelines for the development of DPsim.

Scaling of Voltages and Currents

Voltage quantities are expressed either as phase-to-phase RMS values (denominated as RMS3PH) or as phase-to-ground peak values (denominated as PEAK1PH):

  • Initialisation quantities (e.g. initialSingleVoltage of SimPowerComp) as RMS3PH values
  • Simulation quantities in both SP and DP domain (e.g. mIntfVoltage of DP::Ph1::PiLine) as RMS3PH values
  • Simulation quantities in the EMT domain (e.g. mIntfVoltage of EMT::Ph3::Transformer) as PEAK1PH values

Current quantities are expressed either as RMS or as PEAK values:

  • Simulation quantities in both SP and DP domain (e.g. mIntfCurrent of DP::Ph1::PiLine) as RMS values
  • Simulation quantities in the EMT domain (e.g. mIntfCurrent of EMT::Ph3::Transformer) as PEAK values

Logging

Debug or trace should be the default log level for information that might be nice to have but not necessary for every simulation case.

Calls to the logger that might occur during simulation must use spdlog macros, like SPDLOG_LOGGER_INFO.