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

Return to the regular view of this page.

User Guide

Installing DPsim and running simulations with it.

For readers who want to run simulations: installation, building from source, and the tasks that come up while setting up and running a study.

To change DPsim itself rather than use it, see the developer guide. For the mathematics behind what the solvers compute, see concepts.

1 - Install

Installing DPsim and its Python package.

DPsim is a Python module and C++ library for dynamic power system simulation.

The quickest route to a result is the Python module: install it, then work through the tutorials, which build up one idea at a time from a source and a resistor. If you would rather read a finished study than build one, the example notebooks run complete scenarios and plot them.

Building from source is only needed for a platform without a published wheel, or to work on DPsim itself; see build.

Try it without installing

The example notebooks run in the browser with no local installation:

Binder

Python package

DPsim is published on PyPI and installs like any other Python package:

python3 -m venv venv
source venv/bin/activate
pip install dpsim

If you prefer conda, the equivalent is:

conda create -n dpsim python=3.13
conda activate dpsim
pip install dpsim

Docker

You need Docker installed first. The prepared image on Docker Hub bundles the module together with a JupyterLab session:

docker run -p 8888:8888 sogno/dpsim

Then open http://localhost:8888/lab?token=3adaa57df44cea75e60c0169e1b2a98ae8f7de130481b5bc.

Note that the image pins that access token in its startup command, so it is the same for everyone who runs the image. Publish the port on localhost only, as above, and do not expose it to an untrusted network.

To build the image yourself rather than pulling it, see build.

2 - Examples

Runnable notebooks and C++ examples.

If you are starting out, start with the tutorials instead. They work through one idea at a time in order, each as a complete script. This page is the inventory of what else the repository carries, which is the right thing once you know what you are looking for.

The examples come in both languages. The Python notebooks run a scenario and plot the result in one place, so they are the better way to see a complete study. The C++ examples are the better reference for using DPsim as a library, and are what the real time and co-simulation scenarios are written in.

Where each kind fits

If you want toGo to
Learn how a simulation is put togetherTutorials
See a complete study with plotsNotebooks, below
Use DPsim from an applicationC++ examples, below
Look up what a model doesConcepts

Notebooks

Run them in the browser with no local installation:

Binder

Locally, they live under examples/Notebooks and need the Python package on the path, as described in the build section.

CategoryContents
Quickstart GuideA single notebook covering a first simulation end to end
CircuitsSmall networks exercising one modelling aspect at a time
ComponentsOne component at a time, often comparing domains against each other
GridsPublished test systems such as the WSCC 9 bus and CIGRE networks
FeaturesCross-cutting capabilities rather than a specific network
PerformanceTiming and scaling comparisons
StateSpaceState-space extraction from a running simulation

Understanding_DP.ipynb is worth reading early if dynamic phasors are new to you, since it builds the intuition the concepts section then formalises.

Continuing from a tutorial

Each tutorial ends at the point where the notebooks take over, so a category is usually the natural next step from the rung that introduced the idea.

After this tutorialThese notebooks go further
Adding dynamicsCircuits, for larger passive networks
Two-bus networkGrids, for published test systems solved the same way
Comparing domainsComponents, which compare one model across domains
Adding a machineCircuits, for the SMIB and multi-machine fault studies
Exchanging dataThe co-simulation examples below

C++ examples

Under dpsim/examples/cxx, built as part of a normal build and produced as executables in the build directory.

DirectoryContents
CircuitsNetworks assembled directly in C++
ComponentsSingle component scenarios
CIMReading network data from CIM and CGMES files
StateSpaceState-space extraction
RealTimeScenarios run against the wall clock
DAE, signals, timerSmaller scenarios for the DAE solver, signal models and timing

The target name is the source file name without its extension, regardless of which directory the source sits in, and the executables are written flat into the build tree. So to build and run a single example from your build directory:

cmake --build . --target DP_VS_RL1
./dpsim/examples/cxx/DP_VS_RL1

Co-simulation

The dpsim-villas examples exchange data with other simulators or with hardware through VILLASnode. These need WITH_VILLAS enabled and are therefore not available on Windows. See interfaces for how the coupling works and real time for running them against the wall clock.

3 - Logging Results

Choosing what a simulation records, where it goes, and what it costs.

A simulation records nothing unless asked. Every quantity you want afterwards has to be named before the run, because the solver keeps only what the current step needs and discards the rest.

dpsimpy.Logger is the one to reach for. It is the CSV data logger and it is what nearly every example and notebook uses. There is also a real-time data logger, which is the right choice under a real-time timer for the reason given below, and an interface for sending results somewhere other than a file.

Only attributes can be logged

So the question is never “how do I log this value” but “is this value an attribute”. If it is, one line records it. If it is not, no logger option will reach it, and the answer is to expose it as an attribute in the model, which is a code change described under attributes.

print_attribute_list() on any object prints what it publishes, which is the reliable way to find out. Anything absent from that list cannot be logged.

The same fact explains a convenience: because attributes are the unit, a derived quantity that is itself an attribute is logged the same way as a terminal voltage, with no special handling.

Registering an attribute

logger = dpsimpy.Logger("my_simulation")
logger.log_attribute("n1.v", "v", n1)
logger.log_attribute("load.i_intf", "i_intf", load)

sim.add_logger(logger)

The three arguments are the column name you want in the output, the name of the attribute on the object, and the object itself. The first is yours to choose and is what you will key on when reading the file back; the second must match an attribute the object actually publishes.

Common attribute names are v on a node, and i_intf and v_intf on a component for the current through it and the voltage across it. Components publish their own states as well: a machine offers w_r, delta and Te, a converter its control states.

Naming an attribute that does not exist fails when the logger is set up, not at the end of the run, so a typo costs a second rather than a simulation.

Where the file goes

By default the file is logs/<logger name>.csv under the working directory, where the name is the one given to the Logger constructor.

Logger.set_log_dir changes that directory and Logger.get_log_dir reports it. Calling it is what produces the nested logs/<something>/<name>.csv layout the example notebooks use, so a script that does not call it gets the flat form. Setting it is worth doing when one script runs several simulations, since two loggers with the same name otherwise write to the same file.

The same setting governs the diagnostic text log, which is why the two land side by side.

The file is plain CSV with a time column first and one column per logged attribute, in the order they were registered. A three-phase quantity becomes three columns suffixed _0, _1, _2, and a complex quantity in an envelope domain is written as a complex value that the reading side parses back.

Reading it back

import villas.dataprocessing.readtools as rt

results = rt.read_timeseries_dpsim("logs/my_simulation.csv")
voltage = results["n1.v"]

The keys are the column names you chose. Each value carries time and values arrays. In an envelope domain the values are complex, so abs() gives the magnitude, and frequency_shift_list recovers the waveform as shown in comparing domains.

The first row is written before the first solve, so it holds the state the simulation started from rather than a result. A plot that appears to begin at zero usually begins at that row.

What it costs

Logging is opt-in and costs nothing for what you do not ask for, but what you do ask for is written every step. A run of 100 000 steps logging 50 attributes writes five million values, and on a large network the file, not the solve, becomes the slow part.

Three things help. Log the attributes you will actually look at rather than everything available. Prefer a specific attribute over a whole matrix; the rows_max and cols_max arguments to log_attribute cap how much of a matrix quantity is written. And note that a large time step reduces the file in exact proportion, so a study that only needs the envelope does not need the step of one that needs the waveform.

Data logging against diagnostic logging

The word covers two unrelated things and they are easy to confuse.

What this page describes is the data logger: numerical results, CSV, opt-in per attribute. The other is the diagnostic log, the text file recording what the solver did, controlled by dpsimpy.LogLevel and passed to component constructors. Raising a component’s log level makes it describe its own initialization and stamping in prose; it has no effect on the CSV.

They land in the same logs/ directory side by side, one as .csv and the other as .log, which is why they get mistaken for each other. A component constructed with LogLevel.debug produces a great deal of text and no additional results.

The real-time data logger mentioned at the top is a data logger like the first, not a third kind of log. It records the same results and differs only in buffering them in memory and writing at the end, because a disk write inside a real-time step has no bound on how long it takes. See real-time.

4 - Real-Time

Running a simulation against a wall clock, and what that requires.

Normally a simulation runs as fast as it can: a one-second study finishes in whatever time the solver needs. In a real-time simulation, one second of simulated time takes one second of wall clock time, no faster and no slower.

When you need it

Only when something outside the simulation has its own clock. A controller running on real hardware, another simulator you are coupled to, or a person turning a dial all move at their own pace, and the simulation has to move with them. If nothing outside is waiting, running in real time only makes the study slower.

This is why co-simulation and real-time usually appear together.

What it changes

Two things. The simulation waits at the end of each step until the wall clock catches up, so a run takes as long as the time it simulates. And a step that takes longer than its own duration is an overrun: the simulation has missed its deadline and can no longer claim to be in step with the outside world.

Overruns are the whole difficulty. Everything else about real-time execution is arranging for them not to happen: keeping the step’s work bounded, and keeping the operating system from interrupting it.

What it takes

A time step that comfortably exceeds the work done in it, models that do nothing slow inside the step, and a host that will not interrupt at the wrong moment. Millisecond steps are undemanding; microsecond steps need a tuned kernel and careful models, and are where most of the effort goes.

The requirements on the host and on the models are in real-time execution. With that tuning, steps as low as 5 us synchronised to an FPGA through VILLASnode have been achieved.

Running a Real-Time Simulation

Before running a simulation, you can run the following commands as root:

echo "evacuating cores"
tuna isolate -c 9,11,13,15

echo "disabling RT throttling"
echo -1 > /proc/sys/kernel/sched_rt_runtime_us

echo "stopping systemd services"
systemctl stop polkit
systemctl stop containerd
systemctl stop crond
systemctl stop chronyd

As a reference, real-time simulation examples are provided in the dpsim/examples/cxx and dpsim-villas/examples/cxx folder of the DPsim repository.

To benefit from the PREEMPT_RT feature and the isolated cores, the simulation has to be started using the chrt command to set the scheduling policy and priority, and the taskset command to pin the process to the isolated cores.

In the following example, we set the FIFO scheduling policy with the highest priority (99) and pin the execution of the simulation to CPU cores 9,11,13,15 which have been reserved previously (see above).

# the simple RT_DP_CS_R_1 simulation
taskset -c 9,11,13,15 chrt -f 99 build/dpsim/examples/cxx/RT_DP_CS_R_1

# Cosimulation using VILLASnode, FPGA synchronized time step, and exchanging data via Aurora interface.
# Here we need sudo, to interact with the FPGA. We disable logging (log=false) and set the time step to 50 us (-t 0.00005).
sudo taskset -c 9,11,13,15 chrt -f 99 build/dpsim-villas/examples/cxx/FpgaCosim3PhInfiniteBus -o log=false -t 0.00005 -d 10

5 - Co-simulation and Interfaces

Exchanging signals with other simulators, services or hardware during a run.

Interfaces can be used to exchange simulation signals between a DPsim simulation and other soft- or hardware, for example an MQTT-broker or an FPGA. Simulation signals in the form of Attributes can be imported or exported once per simulation time step. Interfaces are subclasses of Interface and implement the methods addExport and addImport, which add dependencies to the passed attribute that forward the attribute value from or to the interface. This way, attributes that are imported are read from the interface before they are used in any DPsim component. Attributes that are exported are written to the interface after they are set by a DPsim component.

Where the boundary is, and where to read further

This page documents only DPsim’s side of the interface: which attributes are exchanged, when they are read and written relative to the time step, and how the simulation is synchronized. Everything on the other side of the JSON configuration belongs to VILLASnode and is documented there rather than here, so the two should be read together.

The parts most often needed are these:

  • Node types is the reference for the type key and its per-type options. Which protocols are available, and what each one requires, is decided here rather than in DPsim.
  • Nodes covers the surrounding configuration structure that the node object sits in.
  • Hooks describe the processing that can be applied to samples in transit, such as scaling, limiting or statistics. Anything that can be done with a hook does not need to be done in the simulation, which is usually the better place for it.