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

Return to the regular view of this page.

Contributing

How to get a change into DPsim.

Contributions of all kinds are welcome, including code, documentation, examples, models, bug reports and reviews. Open a pull request or get in touch through GitHub Discussions.

These pages cover the process. For how the code is organised and the conventions it follows, see the developer guide.

Quick start

  1. Fork the repository and clone your fork.

  2. Run pre-commit install to activate the automated checks (formatting, notebook output stripping).

  3. Create a branch with a descriptive prefix (feature/, fix/, docs/).

  4. Commit with a sign-off, using the conventional commit style:

    git commit -s -m "fix: correct node voltage initialization in DiakopticsSolver"
    
  5. Keep your branch up to date by rebasing; do not merge the target branch into your branch:

    git fetch upstream
    git rebase upstream/master
    git push --force-with-lease
    
  6. Open a pull request from your fork against sogno-platform/dpsim:master.

Pull Requests

There are no strict formal requirements besides the following:

  1. Developer Certificate of Origin (DCO)

    We require a Developer Certificate of Origin. See more here.

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

  3. Development in Forks Only

    We accept contributions made in forks only. The main repository is not intended for contributor-specific branches.

  4. 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
     */
    

    Keep the SPDX tags on adjacent lines with no blank line between them, as the tooling reads them as a block.

  5. Linear History (no merge commits)

    DPsim maintains a linear git history. Never merge the target branch into your feature branch; rebase instead:

    git rebase upstream/master
    git push --force-with-lease
    
  6. No Saved Notebook Outputs

    Jupyter notebooks must be committed without saved cell outputs (images, plots, printed text). Saved outputs bloat diffs and can break the notebook test collector, which re-executes notebooks and re-extracts their outputs. Strip outputs before committing:

    jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace <notebook.ipynb>
    

    A pre-commit hook does this automatically once you have run pre-commit install; CI also rejects a pull request that changes a notebook still carrying saved outputs.

Creating New Releases (info for maintainers)

DPsim currently uses 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:

  • Update version in pyproject.toml
  • Update VERSION in the top level CMakeLists.txt
  • Update sonar.projectVersion in sonar-project.properties
  • Update CHANGELOG.md and include all the unreleased changes in the list
  • Create a new tag with an increased version number, which can be done during the release in GitHub

Python Packages

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.

Container Images

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.

Planning

Short-term planning for new features is done on the GitHub Project board.

You can also check the Issues List or the Pull Requests on GitHub.

Provenance and responsibility

Parts of this documentation were drafted with the help of large language models. The maintainers edit and curate that output; they do not vouch for every line as if it were written from first-hand knowledge, and the documentation carries no warranty.

That is a statement about how it is produced, not an excuse. The working rules exist precisely because generated prose is confidently wrong in ways that read well:

  • A page is written after running its code, never from reading the API. Every tutorial has a script in the repository and quotes it; if the two disagree, the page is wrong.
  • A claim about the code is traced to where the value is used before it is written down. Two hazard notes here described correct code as broken because a line was read in isolation; both were corrected once the value was followed through.
  • Anything measurable is measured by a script that reads the source, not asserted from having read a lot of it. That is what generate_model_availability.py, check_docs_pairing.py and check_docs_hazards.py are for.
  • No reference is cited that the repository does not already contain. Invented citations look exactly like real ones.

A notice in the site footer says the same to readers. If you find something wrong, open an issue. That is more useful than assuming a page is authoritative because it is detailed.

How the documentation is organised

Six sections, split by what the reader is trying to do rather than by topic. Putting a page in the right one is most of the work; the rest is deciding which half of the subject it covers.

SectionFor someone who wants toNames C++ classes
User Guideinstall DPsim and run a simulationno
Tutorialslearn by working through one idea at a timeonly Python API calls
Developer Guidechange DPsim, or understand how it works insideyes
Conceptsknow the mathematics behind a model or methodnever
Referencelook up what exists and wheregenerated
Contributingcontribute to the projectn/a

Every subject is written twice

Concepts carries the mathematics: what the model represents, the equations, and what they assume. It names no class, no file and no example, so a reader could follow it while implementing the model in something else entirely.

Developer Guide carries the arrangement in code: the class hierarchy, how the component interfaces with the solver, its attributes and state layout, the traps in configuring it, and links to the source and examples.

The two link to each other. scripts/docs/check_docs_pairing.py reports any Concepts page without a counterpart and exits non-zero, with an exemption list for method pages that have no single implementation behind them.

What is generated rather than written

Do not hand-edit these.

  • The model availability matrix comes from the component headers via scripts/docs/generate_model_availability.py. A model class it does not recognise makes it fail rather than silently omit the model.
  • The Python and C++ API references are generated by Sphinx and Doxygen from the source, so they are improved by writing better doc comments, not by editing the site.
  • Tutorial figures come from scripts/docs/generate_tutorial_figures.py. The architecture diagrams do not: they are editable draw.io SVGs and must be edited in draw.io.

Conventions

Every page carries a description, which appears under its title in section listings, and a weight, which sets its order in the sidebar. A page without a weight falls to alphabetical, which is how sections drift into arbitrary order.

Terminology follows standard electrical engineering usage. Avoid words that collide with class names: “signal” reads as SimSignalComp rather than as a recorded quantity, so prefer “attribute”.

Highlighting what a reader must not miss

Documentation prose is read in order, so anything that will silently cost someone an afternoon has to break out of the prose. Use the Docsy alert shortcode, with one of four titles, and keep the vocabulary small so the colours stay meaningful.

{{% alert title="Requires a build with VILLASnode" color="info" %}}
Something the reader must have before this page works at all.
{{% /alert %}}
ColourUse it forTitle starts with
infoa prerequisite: a build flag, an optional dependency, a package that is not installed by defaultRequires
warninga trap: correct code that silently does the wrong thing, a parameter that means two things, an ordering that mattersWatch out:
dangera known defect in DPsim itself, not something the reader can avoid by being carefulDefect: or Suspected defect:
primarya genuine aside that is neither a prerequisite nor a hazardNote

Use exactly these four prefixes. Earlier pages mixed Trap:, Watch out: and Gap: for the same colour, which made the severity unreadable: if two words mean one thing, neither means anything.

Three rules keep this useful. Mark a hazard where the reader meets it, not only in a reference page they may never open, so the same trap can appear on a tutorial and on an implementation page. Reserve danger for defects: if a reader can avoid the problem by knowing about it, it is a warning, and a page full of red stops being read. And a missing capability is a Note, not a Watch out:, because there is no hazard to avoid, only something that cannot be done.

Do not use blockquotes for this. They carry no colour and no title, so they read as ordinary text.

1 - LLM Pull Request Review

How the automated pull request review works and what it checks.

Overview

DPsim ships an optional, non-blocking pull-request reviewer that runs a series of specialised passes over the diff of a pull request using a large language model and posts a single review comment. It is intended as an assistive first pass: it never requests changes and cannot block a merge, so a human review remains authoritative.

The reviewer lives under .github/llm-review/ (the prompts in prompts.py and a pure-standard-library runner in review.py) and is driven by two workflows: llm-review-collect.yml, which runs on the pull request itself, and llm-review.yml, which performs the review. The split is what makes reviewing pull requests from forks safe (see Fork pull requests). It communicates with any OpenAI-compatible chat endpoint, configured through the environment variables described below.

How it works

For each pull request the runner reads the base..head diff and sends it, in turn, to a set of focused review stages, each with its own prompt. The stages cover model equations and their derivation, MNA stamping and domain modeling, numerical correctness, task scheduling and attribute usage, real-time safety, C++ class design and reuse, naming and in-code documentation, logging discipline, the Python bindings, input parsing, the build system and dependencies, testing and component coverage, and licensing and pull-request hygiene. Each stage returns a strict JSON list of findings. A final synthesis pass deduplicates and prioritises them, and the runner posts them as one review, anchoring inline comments only to lines present in the diff.

The prompts encode DPsim’s documented conventions (see Guidelines) and the recurring points raised in past reviews, so the feedback stays specific to this project rather than generic.

Configuration

The workflow requires one repository secret:

  • RWTH_LLM_TOKEN: the bearer API key for the chat endpoint.

The following repository Actions variables are optional and override the defaults baked into the workflow:

  • LLM_BASE_URL: the OpenAI-compatible base URL.
  • LLM_MODEL: the model identifier.
  • LLM_CHAT_PATH: the chat path appended to the base URL (default /chat/completions).
  • LLM_REVIEW_RUNNER: the runner label (default ubuntu-latest; see Runner selection).

The workflow’s baked-in defaults target an OpenAI-compatible deployment; override LLM_BASE_URL and LLM_MODEL to point at a different endpoint or model.

Obtaining an API key

Obtain a bearer API key from the chosen OpenAI-compatible provider and store it as the RWTH_LLM_TOKEN repository secret. The key is only exposed to workflow runs on pull requests from the repository itself, never from forks.

Before storing the secret, a single request confirms that the key reaches the model:

curl -sS -X POST "$LLM_BASE_URL/chat/completions" \
  -H "Authorization: Bearer $RWTH_LLM_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"model\":\"$LLM_MODEL\",\"messages\":[{\"role\":\"user\",\"content\":\"ok\"}]}"

Running locally

The runner has a dry-run mode that executes the full pipeline and prints the assembled review instead of posting it. It needs no GitHub token and no Actions runner, only network access to the endpoint:

cd .github/llm-review
export LLM_BASE_URL='<openai-compatible-base-url>'
export LLM_MODEL='<model-identifier>'
export LLM_CHAT_PATH='/chat/completions'
export LLM_API_KEY="$RWTH_LLM_TOKEN"
export BASE_SHA=$(git rev-parse origin/main) HEAD_SHA=$(git rev-parse HEAD) PR_NUMBER=0
python3 review.py --dry-run

Runner selection

The workflow defaults to a GitHub-hosted ubuntu-latest runner. If the chosen endpoint is only reachable from within a particular network, set the LLM_REVIEW_RUNNER variable to a self-hosted runner label registered inside that network; no change to the workflow is required.

Fork pull requests

Reviewing pull requests from forks requires care, because a fork’s code is untrusted and must never gain access to the secret. The reviewer uses the workflow_run pattern for this, rather than pull_request_target, and splits the work into two workflows:

  • llm-review-collect.yml runs on the pull_request event, including from forks. GitHub withholds secrets from fork pull_request runs, so this job has no key. It checks out nothing and runs no code from the pull request; it only records the PR number and commit SHAs, taken from trusted GitHub context, into an artifact.
  • llm-review.yml runs on workflow_run, after the collect job completes, in the base repository context where the secret is available. It checks out the base repository’s own code, never the pull request’s, and reads the diff as data through the GitHub API. It never builds or executes anything from the pull request.

Two properties keep the key safe. First, the key is only ever sent as an Authorization header to the configured LLM endpoint, and is never placed in the model prompt, so a prompt-injection payload in the diff cannot reveal it. Second, the privileged job runs only trusted base-repository code, so untrusted pull request code never executes with the secret in scope. The PR metadata read from the collect artifact is validated (numeric PR number, hexadecimal SHAs) before use. For this to operate, both the workflows and the secret must reside on the repository the pull requests target.