Skip to main content

DynaResp

PyPI Python Documentation License

DynaResp generates structural-dynamics responses from validated matrix-defined MDOF systems and analytical Euler-Bernoulli beams. Use model.excite() for one response, or Study to repeat an experiment with built-in distributions or your own NumPy RNG-driven sampling functions. A study can retain complete responses and model matrices as well as compact sensor measurements.

DynaResp is not a finite-element solver. You can provide any compatible mass, damping, and stiffness matrices, while the analytical beam model does not create a mesh. Version 0.1 stores dense generated datasets as compressed NPZ files.

Install

DynaResp requires Python 3.11-3.13.

pip install .

For the locked development environment:

uv sync --group dev

Guided notebook

The executable DynaResp walkthrough covers interactive force controls, MDOF simulations, an animated two-axle car crossing a beam, sensors, custom sampling functions, complete studies, dataset persistence, and reproductions of published MIT MDOF and Georgia Tech beam benchmarks. It is a reactive marimo notebook:

uv sync --group notebooks
uv run --group notebooks marimo edit examples/dynaresp_walkthrough.py

Force functions

Forces are ordinary functions, not classes. Calling harmonic_force(...), moving_force(...), or another force-generating function immediately returns a NumPy matrix. These functions receive one-dimensional time_dimension and space_dimension vectors and return a matrix with shape (len(space_dimension), len(time_dimension)): rows are spatial DOFs and columns are time samples. Helper functions such as combine_forces(...) instead operate on force matrices that have already been created.

Simulate one MDOF response

import numpy as np

from dynaresp import MDOF, harmonic_force

model = MDOF(
    mass=np.diag([2_000.0, 1_500.0]),
    stiffness=np.array([[3.0e6, -1.0e6], [-1.0e6, 1.0e6]]),
    damping=np.array([[1_200.0, -400.0], [-400.0, 800.0]]),
    labels=("lower", "upper"),
)

time_dimension = np.arange(0.0, 5.0, 1.0 / 500.0)
load = harmonic_force(
    time_dimension,
    space_dimension=model.space_dimension,
    dof=1,
    amplitude=10_000.0,
    frequency=3.0,
)

mdof_response = model.excite(load, time_dimension)
print(mdof_response.acceleration.shape)  # (2, 2500)

The response contains displacement, velocity, acceleration, force, and time_dimension. Forces use zero-order hold by default, which preserves step and pulse switching times. Pass interpolate_force=True to model.excite() for linear interpolation of smooth sampled histories.

Assemble masses in series

MassesInSeries builds the full matrices from one value per level. The first spring and damper connect the first mass to ground. Every following spring and damper connects its mass to the mass below it.

from dynaresp import MassesInSeries

model = MassesInSeries(
    mass=[2_000.0, 1_500.0],
    stiffness=[2.0e6, 1.0e6],
    damping=[800.0, 400.0],
    labels=("lower", "upper"),
)

print(model.mass)
print(model.damping)
print(model.stiffness)
print(model.natural_frequencies)
print(model.mode_shapes)

MDOF mode-shape matrices contain one mode per row.

Simulate an analytical beam

import numpy as np

from dynaresp import Beam, moving_force

beam = Beam(
    length=20.0,
    young_modulus=30.0e9,
    density=2_500.0,
    area=1.2,
    second_moment=0.18,
    boundary="simply_supported",
    damping=0.02,
    n_modes=4,
)

time_dimension = np.arange(0.0, 2.0, 1.0 / 1_000.0)
axle = moving_force(
    time_dimension,
    space_dimension=beam.space_dimension,
    mode_shapes=beam.mode_shapes,
    length=beam.length,
    amplitude=80_000.0,
    speed=15.0,
)

beam_response = beam.excite(axle, time_dimension)
midspan_acceleration = beam_response.at_positions(
    [10.0],
    quantity="acceleration",
)
print(midspan_acceleration.shape)  # (1, 2000)

Beam parameters are mutable and remain validated when changed. Derived properties such as modal_mass, natural_frequencies, mass, damping, and stiffness are recalculated from the current values whenever they are accessed.

Measure a response

Sensor selects one response quantity at one location:

Sensor quantity Response returned
"acc" acceleration
"vel" velocity
"dis" displacement

For a Beam, location is a physical position:

from dynaresp import Sensor

midspan_sensor = Sensor("acc", location=beam.length / 2.0)
midspan_acceleration = midspan_sensor.measure(beam_response)
print(midspan_acceleration.shape)  # (2000,)

For an MDOF model, location is the zero-based DOF index:

dof_sensor = Sensor("dis", location=1)
dof_displacement = dof_sensor.measure(mdof_response)

In both cases, measure(...) returns one vector with the same length as the response's time_dimension.

Generate a study

Study runs the same experiment many times. On every run it:

  1. samples the environmental and operational variables (EOVs),
  2. builds the model with the sampled values,
  3. builds and applies the load,
  4. records every sensor, and
  5. records the requested model outputs.

The model and load arguments are normal Python functions. Study calls them for every sample; the load function then calls harmonic_force(...), moving_force(...), or whichever force function the experiment needs.

import numpy as np

from dynaresp import Beam, Dataset, Normal, Sensor, Study, moving_force


def make_model(eov):
    return Beam(
        length=20.0,
        young_modulus=eov["young_modulus"],
        density=2_500.0,
        area=1.2,
        second_moment=0.18,
        boundary="simply_supported",
        damping=0.02,
        n_modes=4,
    )


def make_load(eov, model, time_dimension):
    return moving_force(
        time_dimension,
        space_dimension=model.space_dimension,
        mode_shapes=model.mode_shapes,
        length=model.length,
        amplitude=eov["axle_load"],
        speed=eov["speed"],
    )


def sample_speed(rng):
    # Any callable receiving numpy.random.Generator can be a study variable.
    return rng.triangular(12.0, 15.0, 20.0)


study = Study(
    model=make_model,
    load=make_load,
    sensors=[Sensor("acc", location=10.0)],
    variability={
        "young_modulus": Normal(30.0e9, 1.0e9),
        "axle_load": Normal(80_000.0, 4_000.0),
        "speed": sample_speed,
    },
    duration=2.0,
    sample_rate=1_000.0,
    outputs=("natural_frequencies", "mode_shapes"),
    mode_shape_positions=np.linspace(0.0, 20.0, 101),
)

dataset = study.generate(100, seed=42)
dataset.save("traffic-study.npz")

loaded = Dataset.load("traffic-study.npz")

print(dataset.measurements.shape)  # (100, 1, 2000)
print(dataset.eov["speed"].shape)  # (100,)
print(dataset.natural_frequencies.shape)  # (100, 4)
print(dataset.mode_shapes.shape)  # (100, 4, 101)

Normal(mean, standard_deviation) and Uniform(low, high) are convenient built-in distributions. Any function with the shape sampler(rng) -> float can implement a triangular, log-normal, empirical, correlated, or domain-specific distribution. The study passes the same seeded numpy.random.Generator through the sampling sequence, and every returned value is saved in dataset.eov.

The main dataset arrays are:

  • measurements: (samples, sensors, time)
  • each eov value: (samples,)
  • force, displacement, velocity, and acceleration: (samples, dof, time)
  • mass, damping, and stiffness: (samples, dof, dof)
  • natural_frequencies: (samples, modes) when requested
  • mode_shapes: (samples, modes, positions) when requested
  • time_dimension, sensor_quantities, sensor_locations, and model_types
  • JSON-safe metadata describing the seed, study configuration, samplers, and every generated model

The second axis of measurements follows the order of the sensors list passed to Study. For example, dataset.measurements[:, 0, :] contains the first sensor for every generated sample.

For a Beam study, mode_shape_positions gives the physical positions where the mode shapes are evaluated. For an MDOF study, the DOF positions are stored automatically.

Complete response histories and matrices are recorded by default. Set record_full_response=False for large studies that only need sensor histories and selected modal outputs.

The versioned NPZ file is compressed and pickle-free. It contains generated numeric values and descriptive metadata, not executable Python model, load, or sampler functions. Keep the study code under version control when exact reproduction is important. Files written by the original schema version remain loadable; unknown future schema versions are rejected instead of being silently misinterpreted.

Scope and physical assumptions

  • Inputs use a consistent SI unit system; DynaResp does not attach or convert units.
  • MDOF mass and stiffness matrices must be finite and symmetric, mass must be positive definite, and stiffness must be positive semidefinite.
  • A Beam response is a truncated analytical modal expansion, not a spatial discretization.
  • Traffic loads are prescribed moving forces. Vehicle suspension, road roughness, and coupled vehicle-bridge interaction are outside version 0.1.
  • Sensors are ideal selectors. Noise, filtering, clipping, and dropout are not added automatically.

Development

uv run --group dev ruff check .
uv run --group dev ruff format --check .
uv run --group dev mypy
uv run --group dev pytest --cov=dynaresp
uv build
uv run --group dev twine check dist/*

Install the configured commit-message hook with:

uv run --group dev prek install --hook-type commit-msg

Metadata

Release files for dynaresp 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dynaresp 0.1.1
File Size Uploaded Provenance
dynaresp-0.1.1.tar.gz 46.4 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for dynaresp 0.1.1
File Interpreter ABI Platform
dynaresp-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 81.1 kB

Release files / dynaresp-0.1.1.tar.gz

Download URL dynaresp-0.1.1.tar.gz
Size 46.4 kB
Tags Source
SHA-256 checksum
How to use checksums
41e903fb0df1854b175d5b4d3c0b70ae6500756819e7ecba022d2e95a0512e11
BLAKE2b-256 checksum
How to use checksums
5fff268c7bf0b15c219b912f2609e38b83790365ec1cb47cfea733a548741895
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / dynaresp-0.1.1-py3-none-any.whl

Download URL dynaresp-0.1.1-py3-none-any.whl
Size 34.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
894d011c43b945b5dad8b34be76a1b93e7c4dfe54aaf258b97da9e328c408242
BLAKE2b-256 checksum
How to use checksums
21d8ab9fedccc6c43491c1b080815a9d56bd31440c92047f626d10a79de3940b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for dynaresp 0.1.1

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page