DynaResp
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:
- samples the environmental and operational variables (EOVs),
- builds the model with the sampled values,
- builds and applies the load,
- records every sensor, and
- 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
eovvalue:(samples,) force,displacement,velocity, andacceleration:(samples, dof, time)mass,damping, andstiffness:(samples, dof, dof)natural_frequencies:(samples, modes)when requestedmode_shapes:(samples, modes, positions)when requestedtime_dimension,sensor_quantities,sensor_locations, andmodel_types- JSON-safe
metadatadescribing 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.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| dynaresp-0.1.0.tar.gz | 45.2 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| dynaresp-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 79.7 kB
Release files / dynaresp-0.1.0.tar.gz
| Download URL | dynaresp-0.1.0.tar.gz |
|---|---|
| Size | 45.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f84455424a2e89e23885c2bcd8b842e105e67ec66d7fc49f31567d3d3d862a06
|
|
BLAKE2b-256 checksum How to use checksums |
26bebc70d4f7c50bf9482c8e177c1bed1bc0a99a2a5a0965660364670992de2a
|
| 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.0-py3-none-any.whl
| Download URL | dynaresp-0.1.0-py3-none-any.whl |
|---|---|
| Size | 34.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3cc5b26fbf684a1c31c92c6650cc820998188fedd66be2daeb662927e60bd9f5
|
|
BLAKE2b-256 checksum How to use checksums |
255d9ca232ee973e507c59363e32304ffc33a22383eaf3af8256d0a0d40ceeb6
|
| 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.0
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.