Environment
We recommend the following development tools:
- Editor: Visual Studio Code
- Docker
- CMake
- Git
Please follow the build instructions to checkout your code and install the basic dependencies and tools.
This is the multi-page printable view of this section. Click here to print.
We recommend the following development tools:
Please follow the build instructions to checkout your code and install the basic dependencies and tools.
This page gives practical rules for deciding when a model variable should be a DPsim attribute. For details on the attribute mechanism itself, see Attributes.
Use an attribute if the value must be visible to DPsim infrastructure, for example logging, interfaces, Python access, string-based lookup, or scheduling.
Otherwise, prefer a normal C++ member variable or a local variable.
Before adding a new attribute, ask:
If the answer to all questions is no, do not make it an attribute.
Use an attribute if the value:
Typical examples are interface voltages and currents, source references, controller setpoints, and values exchanged through VILLASnode.
Prefer a normal C++ variable if the value:
Do not create attributes for every variable in the model equations.
If decided that a value should be an attribute, choose the simplest suitable attribute type.
Prefer a static attribute when the value is stored directly by the component:
const Attribute<Real>::Ptr mPower;
MyComponent::MyComponent(const String& name)
: IdentifiedObject(name),
mPower(mAttributes->create<Real>("P", 0.0)) {}
Use dynamic, referenced, or derived attributes only when the value must depend on another attribute.
For example, use a derived attribute when exporting one coefficient of a matrix or vector attribute:
intf->exportAttribute(component->mIntfCurrent->deriveCoeff<Complex>(0, 0), 0, true);
Avoid long chains of dynamic, referenced, or derived attributes unless they are really needed.
In model code, prefer typed attribute members:
mPower->set(power);
const Real power = **mPower;
Use string-based lookup mainly in generic code, logging, interfaces, tests, or Python-style access:
logger->logAttribute("P", component->mPower);
intf->exportAttribute(component->attribute("P"), 0, true);
When assigning a new value, prefer set() if update tasks should be triggered or if the assignment should be explicit. Direct mutable access through **attribute can be used for simple static attributes, but it does not express this intent as clearly.
This value is stored as a member because it is used by several functions of the class. It is still internal to the implementation: it does not need to be logged, imported or exported, used by the scheduler, or accessed by name.
class MyComponent : public IdentifiedObject {
private:
Real mConductance = 0.0; // used internally by several methods
};
This value is a model output. It may be useful for logging, plotting, interfaces, tests, or Python access, so it should be registered as an attribute.
class MyComponent : public IdentifiedObject {
public:
const Attribute<Real>::Ptr mPower;
explicit MyComponent(const String& name)
: IdentifiedObject(name),
mPower(mAttributes->create<Real>("P", 0.0)) {}
void updatePower(Real power) {
mPower->set(power);
}
};
This value is a model input or setpoint. It may be changed from outside the component, for example through Python, an interface, or a test setup.
class MySource : public IdentifiedObject {
public:
const Attribute<Real>::Ptr mVoltageRef;
explicit MySource(const String& name)
: IdentifiedObject(name),
mVoltageRef(mAttributes->create<Real>("V_ref", 0.0)) {}
void setParameters(Real voltageRef) {
mVoltageRef->set(voltageRef);
}
};
This value is not stored separately. It is a scalar view of an existing vector or matrix attribute, which avoids duplicating data manually.
intf->exportAttribute(component->mIntfCurrent->deriveCoeff<Complex>(0, 0), 0, true);
Your vscode launch.json should have two configurations, one to launch the python process and one to attach gdb:
{
"version": "0.2.0",
"configurations": [
{
"name": "Python: Current File",
"type": "python",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal",
"stopOnEntry": true,
"env": {"PYTHONPATH": "${workspaceFolder}/build${pathSeparator}${env:PYTHONPATH}"}
},
{
"name": "(gdb) Attach",
"type": "cppdbg",
"request": "attach",
"program": "/usr/bin/python",
"processId": "${command:pickProcess}",
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}
The python debugger will stop on entry (“stopOnEntry”: true). Make sure to adapt your PYTHONPATH variable if necessary.
The C++ code has to be build in debug mode
cmake .. -DCMAKE_BUILD_TYPE=Debug
You can automate this by using the vscode extension “Python C++ Debugger” and by adding this configuration to the launch.json above:
{
"name": "Python C++ Debugger",
"type": "pythoncpp",
"request": "launch",
"pythonConfig": "custom",
"pythonLaunchName": "Python: Current File",
"cppConfig": "default (gdb) Attach"
}
This will automatically run both debuggers and select the current process.
It can take a while before the debugger hits the C++ breakpoints.
Use the following launch.json for vscode and set the program path:
{
"version": "0.2.0",
"configurations": [
{
"name": "(gdb) Launch",
"type": "cppdbg",
"request": "launch",
"program": "${workspaceFolder}/dpsim/build/Examples/Cxx/example",
"args": [],
"stopAtEntry": true,
"cwd": "${workspaceFolder}",
"environment": [],
"externalConsole": false,
"MIMode": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}
This is a summary of general guidelines for the development of DPsim.
Voltage quantities are expressed either as phase-to-phase RMS values (denominated as RMS3PH) or as phase-to-ground peak values (denominated as PEAK1PH):
initialSingleVoltage of SimPowerComp) as RMS3PH valuesSP and DP domain (e.g. mIntfVoltage of DP::Ph1::PiLine) as RMS3PH valuesEMT domain (e.g. mIntfVoltage of EMT::Ph3::Transformer) as PEAK1PH valuesCurrent quantities are expressed either as RMS or as PEAK values:
SP and DP domain (e.g. mIntfCurrent of DP::Ph1::PiLine) as RMS valuesEMT domain (e.g. mIntfCurrent of EMT::Ph3::Transformer) as PEAK valuesDebug 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.
There are no strict formal requirements besides the following:
Developer Certificate of Origin (DCO)
We require a Developer Certificate of Origin. See more here.
Code Formatting with pre-commit
We enforce code formatting automatically using pre-commit. Please run pre-commit install the first time you clone the repository to run pre-commit before each commit automatically. If you forgot to do this, you will need to use the command pre-commit run --all-files one time to format your changes.
Development in Forks Only
We accept contributions made in forks only. The main repository is not intended for contributor-specific branches.
SPDX Headers
Use SPDX headers to indicate copyright and licensing information, especially when introducing new files to the codebase. For example:
/* Author: John Smith <John.Smith@example.com>
* SPDX-FileCopyrightText: 2025 Example.com
* SPDX-License-Identifier: MPL-2.0
*/
DPsim currently uses to Semantic Versioning. The periodic creation of new versions can help to mark significant changes and to analyze new portions of code using tools like SonarCloud.
A new version of DPsim has to be indicated as follows:
Due to the creation of a new tag, a new PyPi package will be deployed automatically.
Only Linux packages are currently available, other platforms will be supported in the future.
To release an updated Docker image, the container workflow needs to be triggered manually.
If a Pull Request changes a container image, this is not updated automatically in the container image register.