Development#

This document is a guide for developers who want to contribute to the project or understand its internal workings in more detail.

Please refer to CONTRIBUTING.md for how to best contribute to Newton and relevant legal information (CLA).

Installation#

For regular end-user installation, see the Installation guide.

To install Newton from source for development or contribution, first clone the repository:

git clone https://github.com/newton-physics/newton.git
cd newton

Method 2: Using pip in a Virtual Environment#

To manually manage a virtual environment, create and activate one first:

python -m venv .venv
source .venv/bin/activate
python -m venv .venv
.venv\Scripts\activate.bat
python -m venv .venv
.venv\Scripts\Activate.ps1

Then locally install Newton in editable mode with its development dependencies:

pip install -e ".[dev]" --extra-index-url https://pypi.nvidia.com/

The --extra-index-url flag points pip to the NVIDIA package index, which is required to find warp-lang versions newer than those available on PyPI.

Python Dependency Management#

uv lockfile management#

When using uv, the lockfile (uv.lock) is used to resolve project dependencies into exact versions for reproducibility among different machines.

We maintain a lockfile in the root of the repository that pins exact versions of all dependencies and their transitive dependencies.

Sometimes, a dependency in the lockfile needs to be updated to a newer version. This can be done by running uv lock -P <package-name>:

uv lock -P warp-lang --prerelease allow

uv lock -P mujoco-warp

The --prerelease allow flag is needed for dependencies that use pre-release versions (e.g. warp-lang).

uv also provides a command to update all dependencies in the lockfile:

uv lock -U

Remember to commit uv.lock after running a command that updates the lockfile.

Running the tests#

The Newton test suite supports both uv and standard venv workflows, and by default runs in up to eight parallel processes. The tests can be run in a serial manner with --serial-fallback.

Pass --help to either run method below to see all available flags.

# install development extras and run tests
uv run --extra dev -m newton.tests
# install dev extras (including testing & coverage deps)
python -m pip install -e ".[dev]"
# run tests
python -m newton.tests

Most tests run when the dev extras are installed. The tests using PyTorch to run inference on an RL policy are skipped if the torch dependency is not installed. In order to run these tests, include the torch-cu12 or torch-cu13 extras matching your NVIDIA driver’s CUDA support:

# install development extras and run tests
uv run --extra dev --extra torch-cu12 -m newton.tests
# install both dev and torch-cu12 extras (need to pull from PyTorch CUDA 12.8 wheel index)
python -m pip install --extra-index-url https://download.pytorch.org/whl/cu128 -e ".[dev,torch-cu12]"
# run tests
python -m newton.tests

Specific Newton examples can be tested in isolation via the -k argument:

# test the basic_shapes example
uv run --extra dev -m newton.tests.test_examples -k test_basic.example_basic_shapes
# test the basic_shapes example
python -m newton.tests.test_examples -k test_basic.example_basic_shapes

To generate a coverage report:

# append the coverage flags:
uv run --extra dev -m newton.tests --coverage --coverage-html htmlcov
# append the coverage flags and make sure `coverage[toml]` is installed (it comes in `[dev]`)
python -m newton.tests --coverage --coverage-html htmlcov

The file htmlcov/index.html can be opened with a web browser to view the coverage report.

Code formatting and linting#

Ruff is used for Python linting and code formatting. pre-commit can be used to ensure that local code complies with Newton’s checks. From the top of the repository, run:

uvx pre-commit run -a
python -m pip install pre-commit
pre-commit run -a

To automatically run pre-commit hooks with git commit:

uvx pre-commit install
pre-commit install

The hooks can be uninstalled with pre-commit uninstall.

Typos#

To proactively catch spelling mistakes, Newton uses the typos tool. Typos scans source files for common misspellings and is integrated into our pre-commit hooks, so spelling errors in both code and documentation are flagged when you run or install pre-commit (see above). You can also run typos manually if needed. Refer to the typos documentation for more details on usage and configuration options.

Dealing with false positives#

Typos may occasionally flag legitimate project-specific terminology, domain terms, or variable names as misspellings (false positives). To handle these, the Newton codebase configures typos in pyproject.toml at the repository root.

False positives are managed as follows:

  • File exclusions: The [tool.typos] section includes files.extend-exclude to ignore matching files and directories, such as examples/assets and specific model or asset file types (e.g., *.urdf, *.usd).

  • Word allowlist: Words or acronyms that would otherwise be flagged can be listed in [tool.typos.default.extend-words] (e.g., ba, HAA).

  • Identifier allowlist: Specific identifiers, such as variable or constant names, can be declared in [tool.typos.default.extend-identifiers] (e.g., PNGs).

When typos reports a word that is valid within the Newton codebase, you can add it to the appropriate section in pyproject.toml to suppress future warnings. After updating, re-run typos (or pre-commit) to confirm that the word is ignored. Use these options to keep the codebase clean while ensuring needed flexibility for accepted project-specific words and identifiers.

License headers#

Every source file in the repository must carry an SPDX license header. A CI check (pr_license_check.yml) enforces this on every pull request using Apache SkyWalking Eyes.

The required headers depend on the file type:

  • Python files (.py) — Apache-2.0. See .licenserc.yaml for the exact template.

  • Documentation files (.rst) — CC-BY-4.0. See .licenserc-docs.yaml for the exact template.

  • Jupyter notebooks (.ipynb) — CC-BY-4.0. Copy the header from an existing notebook.

When adding a new file, copy the header from an existing file of the same type. If the license check fails on your PR, add the appropriate header to the top of each flagged file.

Using a local Warp installation with uv#

Use the following steps to run Newton with a local build of Warp:

uv venv
source .venv/bin/activate
uv sync --extra dev
uv pip install -e "warp-lang @ ../warp"

The Warp initialization message should then properly reflect the local Warp installation instead of the locked version, e.g. when running python -m newton.examples basic_pendulum.

Building the documentation#

To build the documentation locally, ensure you have the documentation dependencies installed.

rm -rf docs/_build
uv run --extra docs --extra sim sphinx-build -W -b html docs docs/_build/html
python -m pip install -e ".[docs]"
cd path/to/newton/docs && make html

The built documentation will be available in docs/_build/html.

Note

The documentation build requires pandoc for converting Jupyter notebooks. While pypandoc_binary is included in the [docs] dependencies, some systems may require pandoc to be installed separately:

Serving the documentation locally#

After building the documentation, you can serve it locally using the docs/serve.py script. This is particularly useful for testing interactive features like the Viser 3D visualizations in the tutorial notebooks, which require proper MIME types for WebAssembly and JavaScript modules.

uv run docs/serve.py
python docs/serve.py

Then open http://localhost:8000 in your browser. You can specify a custom port with --port:

uv run docs/serve.py --port 8080
python docs/serve.py --port 8080

Note

Using Python’s built-in http.server or simply opening the HTML files directly will not work correctly for the interactive Viser visualizations, as they require specific CORS headers and MIME types that serve.py provides.

Documentation Versioning#

Newton’s documentation is versioned and hosted on GitHub Pages. Multiple versions are available simultaneously, with a version switcher dropdown in the navigation bar.

How It Works#

The gh-pages branch contains versioned documentation in subdirectories:

/
├── index.html      # Redirects to /stable/
├── switcher.json   # Version manifest for dropdown
├── stable/         # Copy of latest release
├── latest/         # Dev docs from main branch
├── 1.1.0/          # Release versions
└── 1.0.0/

Two GitHub Actions workflows manage deployment:

  • docs-dev.yml: Deploys to /latest/ on every push to main

  • docs-release.yml: Deploys to /X.Y.Z/ and updates /stable/ on version tags

Deploying Documentation#

Dev docs are deployed automatically when changes are pushed to main.

Release docs are deployed when a version tag is pushed:

git tag v1.0.0
git push origin v1.0.0

Only strict semver tags (vX.Y.Z) trigger release deployments. Pre-release tags like v1.0.0-rc.1 are ignored.

Manual Operations#

Removing a version (rare):

  1. Check out the gh-pages branch

  2. Delete the version directory (e.g., rm -rf 1.0.0)

  3. Edit switcher.json to remove the entry

  4. Commit and push

Rebuilding all docs (disaster recovery): Check out each version tag, build its docs with Sphinx, and deploy to the corresponding directory on gh-pages. Update switcher.json after each version using scripts/ci/update_docs_switcher.py.

API documentation#

Newton’s API reference is auto-generated from the __all__ lists of its public modules. The script docs/generate_api.py produces reStructuredText files under docs/api/ (git-ignored) that Sphinx processes via autosummary to create individual pages for every public symbol.

Whenever you add, remove, or rename a public symbol in one of the public modules (newton, newton.geometry, newton.solvers, newton.sensors, etc.), regenerate the API pages:

uv run python docs/generate_api.py
python docs/generate_api.py

After running the script, rebuild the documentation to verify the result (see Building the documentation above).

Note

Only symbols listed in a module’s __all__ (or, as a fallback, its public attributes) are included. If a new class or function in newton/_src/ should be visible to users, re-export it through the appropriate public module first.

Testing documentation code snippets#

The doctest Sphinx builder is used to ensure that code snippets in the documentation remain up-to-date.

The doctests can be run with:

uv run --extra docs --extra sim sphinx-build -W -b doctest docs docs/_build/doctest
python -m sphinx -W -b doctest docs docs/_build/doctest

For more information, see the sphinx.ext.doctest documentation.

Changelog#

Newton maintains a CHANGELOG.md at the repository root.

When a pull request modifies user-facing behavior, add an entry under the [Unreleased] section in the appropriate category:

  • Added — new features

  • Changed — changes to existing functionality (include migration guidance)

  • Deprecated — features that will be removed (include migration guidance, e.g. “Deprecate Model.geo_meshes in favor of Model.shapes”)

  • Removed — removed features (include migration guidance)

  • Fixed — bug fixes

Use imperative present tense (“Add X”, not “Added X”) and keep entries concise. Internal implementation details (refactors, CI tweaks) that do not affect users should not be listed.

Style Guide#

  • Follow PEP 8 for Python code.

  • Use Google-style docstrings (compatible with Napoleon extension).

  • Write clear, concise commit messages.

  • Keep pull requests focused on a single feature or bug fix.

  • Use kebab-case instead of snake_case for command line arguments, e.g. --use-cuda-graph instead of --use_cuda_graph.

Writing examples#

Examples live in newton/examples/<category>/example_<category>_<name>.py (e.g. newton/examples/basic/example_basic_pendulum.py). Each file defines an Example class with the following interface:

class Example:
    def __init__(self, viewer, args):
        """Build the model, create solver/state/control, and set up the viewer."""
        ...

    def step(self):
        """Advance the simulation by one frame (typically with substeps)."""
        ...

    def render(self):
        """Update the viewer with the current state."""
        ...

    def test_final(self):
        """Validate the final simulation state. Required for CI."""
        ...

    def test_post_step(self):
        """Optional per-step validation, called after every step() in test mode."""
        ...

Every example must implement test_final() (or test_post_step(), or both). The test harness runs examples with --viewer null --test and calls these methods to verify simulation correctness. An example that implements neither will raise NotImplementedError in CI.

Discovery and registration#

Examples are discovered automatically: any file matching newton/examples/<category>/example_*.py is picked up by newton.examples.get_examples(). The short name used on the command line is the filename without the example_ prefix and .py extension (e.g. basic_pendulum).

New examples must also be registered in the examples README.md with a python -m newton.examples <example_name> command and a 320x320 jpg screenshot.

# list all available examples
uv run -m newton.examples

# run an example by short name
uv run -m newton.examples basic_pendulum

# run in headless test mode (used by CI)
uv run -m newton.examples basic_pendulum --viewer null --test
# list all available examples
python -m newton.examples

# run an example by short name
python -m newton.examples basic_pendulum

# run in headless test mode (used by CI)
python -m newton.examples basic_pendulum --viewer null --test

Roadmap and Future Work#

(Placeholder for future roadmap and planned features)

  • Advanced solver coupling

  • More comprehensive sensor models

  • Expanded robotics examples

See the GitHub Discussions and GitHub Roadmap for ongoing feature planning.

Benchmarking with airspeed velocity#

The Newton repository contains a benchmarking suite implemented using the airspeed velocity framework. The full set of benchmarks is intended to be run on a machine with a CUDA-capable GPU.

To get started, install airspeed velocity from PyPI:

python -m pip install asv

Tip

With uv, airspeed velocity can be run without installing it into the project environment by using uvx:

uvx --with virtualenv asv run --launch-method spawn ...

If airspeed velocity has not been previously run on the machine, it will need to be initialized with:

asv machine --yes

To run the benchmarks, run the following command from the root of the repository:

asv run --launch-method spawn main^!
asv run --launch-method spawn main^^!

Note

On Windows CMD, the ^ character is an escape character, so it must be doubled (^^) to be interpreted literally.

The benchmarks discovered by airspeed velocity are in the asv/benchmarks directory. This command runs the benchmark code from the asv/benchmarks directory against the code state of the main branch. Note that the benchmark definitions themselves are not checked out from different branches—only the code being benchmarked is.

Benchmarks can also be run against a range of commits using the commit1...commit2 syntax. This is useful for comparing performance across several recent changes:

asv run --launch-method spawn HEAD~4..HEAD
asv run --launch-method spawn HEAD~4..HEAD

Commit hashes can be used instead of relative references:

asv run --launch-method spawn abc1234..def5678
asv run --launch-method spawn abc1234..def5678

Running benchmarks standalone#

Benchmark files can also be run directly as Python scripts, without the airspeed velocity harness. This is useful for quick iteration during development since it skips the environment setup that airspeed velocity performs. Each benchmark file under asv/benchmarks/ supports a --bench flag to select specific benchmark classes:

uv run python asv/benchmarks/simulation/bench_mujoco.py --bench FastAllegro
python asv/benchmarks/simulation/bench_mujoco.py --bench FastAllegro

When --bench is omitted, all benchmarks in the file are run. The --bench flag can be repeated to select multiple benchmarks:

uv run python asv/benchmarks/simulation/bench_mujoco.py --bench FastAllegro --bench FastG1
python asv/benchmarks/simulation/bench_mujoco.py --bench FastAllegro --bench FastG1

Tips for writing benchmarks#

Rather than running the entire benchmark suite, use the --bench BENCH, -b BENCH flag to filter the benchmarks to just the ones under development:

asv run --launch-method spawn main^! --bench FastG1
asv run --launch-method spawn main^^! --bench FastG1

The most time-consuming benchmarks are those that measure the time it takes to load and run one frame of the example starting from an empty kernel cache. These benchmarks have names ending with time_load. It is sometimes convenient to exclude these benchmarks from running by using the following command:

asv run --launch-method spawn main^! -b '^(?!.*time_load$).*'
asv run --launch-method spawn main^^! -b "^^(?!.*time_load$).*"

While airspeed velocity has built-in mechanisms to determine automatically how to collect measurements, it is often useful to manually specify benchmark attributes like repeat and number to control the number of times a benchmark is run and the number of times a benchmark is repeated.

class PretrainedSimulate:
    repeat = 3
    number = 1

As the airspeed documentation on benchmark attributes notes, the setup and teardown methods are not run between the number iterations that make up a sample.

These benchmark attributes should be tuned to ensure that the benchmark runs in a reasonable amount of time while also ensuring that the benchmark is run a sufficient number of times to get a statistically meaningful result.

The --durations all flag can be passed to the asv run command to show the durations of all benchmarks, which is helpful for ensuring that a single benchmark is not requiring an abnormally long amount of time compared to the other benchmarks.