Dataset generation¶
Build each structural realization in ordinary Python. You supply one parameter mapping per realization; DynaResp runs the complete simulation and records only the result fields and case values you select.
Define cases and builders¶
System and load builders receive an ordinary mapping and decide what to construct. A separate case builder can create random values explicitly with the deterministic NumPy generator supplied for each sample.
import numpy as np
import dynaresp as dr
time = np.arange(1000) / 100.0
positions = np.arange(3)
def build_case(rng):
return {
"masses": rng.uniform(800.0, 1200.0, size=3),
"stiffnesses": np.exp(rng.uniform(np.log(5.0e5), np.log(2.0e6), size=3)),
"damping_ratio": rng.uniform(0.005, 0.03),
"load_rms": np.exp(rng.uniform(np.log(5.0), np.log(20.0))),
}
def build_system(case):
return dr.ShearBuilding(
masses=np.asarray(case["masses"], dtype=float),
stiffnesses=np.asarray(case["stiffnesses"], dtype=float),
damping_ratio=float(case["damping_ratio"]),
)
def build_load(case):
return dr.loads.WhiteNoise(
position=0,
rms=float(case["load_rms"]),
)
The same case is passed to build_system and then build_load. This makes
conditional and coupled definitions straightforward without hiding how values
were chosen.
Generate the data¶
layout = [dr.sensors.Accelerometer(position=i) for i in range(3)]
dataset = dr.generate_dataset(
n_samples=10_000,
case_builder=build_case,
system_builder=build_system,
load_builder=build_load,
sensors=layout,
time=time,
positions=positions,
record=[
dr.Record.signal,
dr.Record.modal_parameter.frequencies_hz,
dr.Record.case_parameter,
dr.Record.time,
dr.Record.positions,
dr.Record.metadata.seed,
dr.Record.metadata.generation,
],
storage_dtype=np.float32,
seed=42,
workers=4,
progress=True,
)
system_builder must return a StructuralSystem; load_builder must return a
Load. Sensors are supplied directly; case generation, stochastic loads, and
effects use a reproducible per-sample generator derived from the dataset
seed.
Set progress=True to display completed samples with tqdm; it works with
both serial and parallel generation.
Select recorded fields¶
record is required. It accepts typed leaf fields from Record, grouped into
response, system_parameter, modal_parameter, load, and metadata
families. The family objects are namespaces, not selectors: use
Record.system_parameter.mass_matrix, for example, rather than
Record.system_parameter. See the schema reference for the full
selector tree and shapes.
Record.case_parameter selects every numeric value in the final case mapping.
Those values are stored once under parameters/ and exposed through both the
parameter and target views. Without that selector, sample.target and
the parameter mappings are empty, and parameters is absent from
sample.metadata.
The selection happens after run_simulation has produced its normal float64
result. It reduces the retained in-memory or Zarr dataset, but it does not skip
solver work or change simulation precision. storage_dtype defaults to
np.float64; another NumPy floating dtype such as np.float32 casts only
selected floating arrays. Integer case values and the per-sample seed keep
exact integer values in int64 and uint64 storage, respectively.
For example, a bridge-monitoring campaign can retain measured signals, modal frequencies, cases, axes, and provenance without writing response histories, load rasters, matrices, or mode shapes:
bridge = dr.generate_dataset(
cases=bridge_cases,
system_builder=build_beam,
load_builder=build_bridge_load,
sensors=accelerometers,
time=time_axis,
positions=reporting_positions,
record=[
dr.Record.signal,
dr.Record.modal_parameter.frequencies_hz,
dr.Record.case_parameter,
dr.Record.time,
dr.Record.positions,
dr.Record.metadata.seed,
dr.Record.metadata.generation,
],
storage_dtype=np.float32,
output="data/bridge.zarr",
seed=42,
workers=4,
)
Builders receive the same plain dictionary in pipeline order: system first,
then load. A builder can add a numeric derived value, and every later stage sees
it. When Record.case_parameter is selected, the final augmented dictionary is
stored as ground truth:
def build_system(case):
case["total_mass"] = float(np.sum(case["masses"]))
return dr.ShearBuilding(case["masses"], case["stiffnesses"])
def build_load(case):
# Added by build_system and later available as sample.target["total_mass"].
return dr.loads.WhiteNoise(position=0, rms=0.01 * case["total_mass"])
DynaResp validates the dictionary's numeric contents when the case enters the pipeline and again before storage, but does not inspect which builder uses each value.
case_builder is called once per sample and must return a numeric mapping. It
is mutually exclusive with cases and requires n_samples. Use it for random
or correlated studies that should stream without first allocating a case list.
Controlled variability without a distribution¶
Use cases when you want chosen values rather than samples from a probability
distribution. Each mapping describes exactly one realization.
cases = [
{"mass_scale": 0.90, "stiffness_scale": 0.80, "load_rms": 5.0},
{"mass_scale": 1.00, "stiffness_scale": 1.00, "load_rms": 10.0},
{"mass_scale": 1.10, "stiffness_scale": 1.20, "load_rms": 15.0},
]
def controlled_system(case):
mass_scale = float(case["mass_scale"])
stiffness_scale = float(case["stiffness_scale"])
return dr.ShearBuilding(
masses=np.array([1000.0, 900.0, 800.0]) * mass_scale,
stiffnesses=np.array([1.0e6, 8.0e5, 6.0e5]) * stiffness_scale,
)
def controlled_load(case):
return dr.loads.WhiteNoise(
position=0,
rms=float(case["load_rms"]),
)
dataset = dr.generate_dataset(
cases=cases,
system_builder=controlled_system,
load_builder=controlled_load,
sensors=layout,
time=time,
positions=positions,
record=[
dr.Record.signal,
dr.Record.case_parameter,
],
seed=42,
)
n_samples is inferred from cases. Every case must contain the same names
and value shapes so the stored arrays remain rectangular.
For a full factorial design, construct the cases with parameter_grid:
cases = dr.parameter_grid(
mass_scale=[0.9, 1.0, 1.1],
damping_ratio=[0.01, 0.02],
load_rms=[5.0, 10.0],
)
This produces all 12 combinations in stable input order. Use a normal list of mappings when factors are paired, correlated, conditional, or otherwise not a Cartesian product.
Full control with NumPy¶
Use the supplied NumPy generator for correlations, conditional models, or other
random designs. Put every value that should be available to the builders in the
returned mapping; select Record.case_parameter when those values should also
be retained as ground truth.
covariance = np.array([[1.0, 0.7], [0.7, 1.0]])
def build_correlated_case(rng):
return {
"latent": rng.multivariate_normal([0.0, 0.0], covariance),
"load_rms": 10.0,
}
def build_correlated_system(case):
latent = np.asarray(case["latent"], dtype=float)
masses = 1000.0 + 80.0 * latent
return dr.ShearBuilding(
masses=masses,
stiffnesses=[1.0e6, 8.0e5],
)
Builders may contain arbitrary validation and branching. All cases in one dataset must still contain the same parameter names and shapes, and every selected array must keep a stable shape because the stored result is rectangular.
In-memory and Zarr output¶
Without output, records are stacked in memory. For large studies, provide a
new Zarr path:
dataset = dr.generate_dataset(
n_samples=100_000,
case_builder=build_case,
system_builder=build_system,
load_builder=build_load,
sensors=layout,
time=time,
positions=positions,
record=[
dr.Record.signal,
dr.Record.modal_parameter.frequencies_hz,
dr.Record.case_parameter,
dr.Record.time,
dr.Record.positions,
dr.Record.metadata.seed,
dr.Record.metadata.generation,
],
storage_dtype=np.float32,
output="data/building.zarr",
seed=42,
workers=8,
)
Samples are committed in index order with a bounded worker buffer. Reopen the lazy dataset without rerunning the builders:
Inspect and export¶
sample = dataset[0]
sample.signal
sample.target
sample.modal["frequencies_hz"]
sample.time
sample.positions
sample.metadata["seed"]
sample.metadata["generation"]
Unrecorded scalar fields on StructuralSample are None; unrecorded grouped
fields are absent from the system, modal, and load dictionaries. Accessing
an unrecorded dataset property or requesting it through an adapter raises a
clear KeyError.
Machine-learning and analysis views remain available:
torch_dataset = dataset.to_torch(
inputs="signal",
targets="damping_ratio",
extras=["frequencies"],
)
labeled = dataset.to_xarray()
properties = dataset.properties()
Versioned beam-damage benchmark¶
from dynaresp.datasets import BeamDamage
train = BeamDamage(
root="data",
split="train",
version="1.0",
generate=True,
)
This predefined dataset uses the same public builder API and fixed seeds. Its
beam targets include damaged_element and stiffness_reduction; inspect
sample.target for its recorded parameter set.