Skip to main content

Domain

Struct Domain 

Source
pub struct Domain<const NDIM: usize = PLANAR_NDIM, const NDOF: usize = NDF, NId = NodeId, E = Element>
where NId: Key, E: ElementOps<NDIM, NDOF, NId>,
{ /* private fields */ }
Expand description

Owns all nodes, elements, and load patterns. No serialization/broker machinery — this is the whole model, in memory, for one worker. Clone backs Analysis/TransientAnalysis’s snapshot-and-restore-on- failure (a failed step shouldn’t leave nodal displacement/velocity/ acceleration partway through a discarded Newton iteration) — see Material’s doc comment for why materials themselves don’t need this protection. Also what makes multi-phase analysis composition possible: Analysis::into_domain/TransientAnalysis::into_domain hand this same Domain (nodal state, committed material history, and load-pattern freeze state all intact) to the next phase’s builder.

Generic over the kinematic profile: NDIM/NDOF/ELEMENT_DOF (const generics, kept as separate parameters rather than computed from each other — stable Rust can’t evaluate 2 * NDOF inside a generic item), the node ID type, and E: ElementOps<...> (the element catalog — Element or Element3; its Domain-element-store key comes from E::Id, an ElementOps associated type rather than a further generic parameter here — see ElementOps::Id’s doc comment for why: a truly independent EId parameter had nothing else to constrain it and broke type inference at every Domain::new() call site). This bookkeeping (equation numbering, sparse assembly, state gather/scatter) doesn’t care about element physics, only about DOF counts and ElementOps’s uniform shape, so it’s genuinely one implementation rather than two — unlike Truss/Truss3, whose actual formulas differ and stay separate concrete types (see ElementOps’s doc comment for why that part can’t be unified the same way).

All five parameters default to the planar profile, the same trick Node uses, so every existing bare Domain usage (Domain::new(), fn f(d: &Domain), …) keeps compiling unchanged. Domain3 is the spatial instantiation.

Implementations§

Source§

impl<const NDIM: usize, const NDOF: usize, NId, E> Domain<NDIM, NDOF, NId, E>
where NId: Key, E: ElementOps<NDIM, NDOF, NId>,

Source

pub fn new() -> Self

A fresh, empty model — already carrying one LoadPattern (default_pattern, LoadSeries::Linear { slope: 1.0 }, unscaled) so simple single-pattern models (the common case) don’t need to name a pattern at all: domain.load_node(id, dof, value) reaches for it implicitly. Reproduces, exactly, the single-pattern behavior every Analysis had before multi-pattern support existed.

Source

pub fn add_node(&mut self, node: Node<NDIM, NDOF>) -> NId

Source

pub fn add_element(&mut self, element: E) -> E::Id

Source

pub fn node(&self, id: NId) -> &Node<NDIM, NDOF>

Source

pub fn element(&self, id: E::Id) -> &E

Source

pub fn element_local_force(&self, id: E::Id) -> ElementForce

An element’s local nodal force at its current committed state — see ElementOps::local_force’s doc comment. Never called from the assembly hot loop (only from results recording), so looking the element and its nodes up here rather than caching anything is fine.

Source

pub fn element_load_component( &self, id: E::Id, pseudo_time: f64, component: usize, ) -> f64

Component component of the load id carries at pseudo_time — every pattern’s load on it, scaled by that pattern’s factor (frozen patterns at their frozen value), summed; 0.0 for an unloaded element. Local axes: ElementLoad is [wx, wy], ElementLoad3 is [wx, wy, wz].

Source

pub fn element_end_force(&self, id: E::Id, pseudo_time: f64) -> ElementForce

element_local_force including the fixed-end effect of element loads: the member end forces a loaded element actually carries (k·d − Σ factor·f_eq, OpenSees’s K·u + p0), with every pattern’s factor evaluated at pseudo_time. Identical to element_local_force for an element with no load.

Source

pub fn element_fiber_responses(&self, id: E::Id) -> Option<Vec<Vec<(f64, f64)>>>

Every integration point’s per-fiber (strain, stress) at id’s current committed state — None for every element kind that isn’t fiber-discretized (ElementOps::fiber_responses’s doc comment). Outer Vec (when Some) is one entry per integration point, inner Vec one entry per fiber, in the order each was originally given to the element’s constructor — a fiber recorder resolves its (section, fiber) indices against that same order.

Source

pub fn element_gauss_point_count(&self, id: E::Id) -> usize

Number of Gauss points of the continuum element id (zero for any other kind).

Source

pub fn element_gauss_component_count(&self, id: E::Id) -> usize

Components per Gauss-point strain/stress vector of id (3 for plane elements, 8 for shells).

Source

pub fn element_gauss_responses(&self, id: E::Id) -> Option<Vec<GaussResponse>>

Every Gauss point’s committed strain and stress at id — None for every element kind that is not a continuum element.

Source

pub fn element_shell_responses(&self, id: E::Id) -> Option<Vec<ShellResponse>>

Every Gauss point’s committed generalized strain and resultants at the shell id — None for every element kind that is not a shell.

Source

pub fn reaction(&self, node: NId, dof: usize, pseudo_time: f64) -> f64

Support/equilibrium reaction at (node, dof): net internal element resistance there minus the total applied reference load at pseudo_time (Chopra’s standard reaction definition) — most useful at a fixed DOF (a free DOF’s residual is, by construction, what Algorithm::Newton already drives to ~0 at convergence, so reading its “reaction” is redundant but harmless). Needs no equation number — unlike every other accessor here, this bypasses dof_terms/the free-DOF system entirely by summing every element’s and pattern’s own contribution to this one (node, dof) directly (the same per-element/per-pattern loops assemble_stiffness_ triplets/assemble_load_with run, just for one DOF instead of every free one), so it’s well-defined even at a DOF number_dofs never assigned an equation number to.

Ground-motion excitation (TransientAnalysis) never needs a separate term here. This returns the structural reaction; TransientAnalysis::reaction adds the equivalent ground inertial load at restrained DOFs. Relative velocity/acceleration remain zero there.

Source

pub fn lumped_mass_at(&self, node: NId, dof: usize) -> f64

Total lumped mass at a physical node/DOF, including attached elements. Unlike the reduced mass diagonal this also includes restrained DOFs.

Source

pub fn prescribe_displacement( &mut self, node: NId, dof: usize, value: f64, ) -> bool

Imposes value as the displacement of a fixed (single-point constrained) DOF — a non-homogeneous SP constraint. A fixed DOF has no equation number, so the imposed value simply lives in the node’s displacement and every element reads it like any other nodal displacement; the free DOFs then respond to it on the next Analysis::step (see Analysis::step_prescribed, which also makes the update atomic with respect to a failed step). reaction reads the force needed to hold it there. Returns false, changing nothing, if the DOF isn’t fixed or value is nonfinite.

Source

pub fn can_prescribe(&self, node: NId, dof: usize, value: f64) -> bool

Whether prescribe_displacement would accept these arguments.

A DOF that a constraint uses as a master is refused: constraints are homogeneous and have already eliminated a fixed master’s contribution, so moving it would silently leave its slaves behind.

Source

pub fn default_pattern(&self) -> LoadPatternId

Domain::new()’s always-present pattern — see its doc comment.

Source

pub fn add_load_pattern(&mut self, series: LoadSeries) -> LoadPatternId

Add a new, independently-scaled LoadPattern (scale factor 1.0; chain .with_scale_factor via add_load_pattern_scaled if needed). Xara/OpenSees’s pattern Plain <tag> <series> {...}.

Source

pub fn add_load_pattern_scaled( &mut self, series: LoadSeries, scale_factor: f64, ) -> LoadPatternId

Same as add_load_pattern, with an explicit scale factor (e.g. a ground-motion PGA scale, or a load-factor-vs-displacement-target unit conversion) applied on top of the series’ own factor.

Source

pub fn add_nodal_load( &mut self, pattern: LoadPatternId, node: NId, dof: usize, value: f64, )

Add a nodal load to pattern — Xara/OpenSees’s load <node> <...> inside a pattern block.

Source

pub fn load_node(&mut self, node: NId, dof: usize, value: f64)

Convenience for the common single-pattern case: adds value to default_pattern() directly, so simple models don’t need to name a pattern (domain.load_node(id, 0, force) instead of domain.add_nodal_load(domain.default_pattern(), id, 0, force)).

Source

pub fn add_element_load( &mut self, pattern: LoadPatternId, element: E::Id, load: E::Load, )

Add an element load (e.g. a beam-column’s uniform transverse load) to pattern — Xara/OpenSees’s eleLoad inside a pattern block. load’s type is this profile’s ElementOps::Load — ElementLoad for the planar profile, ElementLoad3 for the spatial one (see ElementOps’s doc comment).

Source

pub fn hold_pattern_constant( &mut self, pattern: LoadPatternId, pseudo_time: f64, )

Freeze pattern’s contribution at its value as of pseudo_time — Xara/OpenSees’s loadConst -time <pseudo_time> (there, applied to every pattern in the domain; here, one pattern at a time, so a caller composing phases can freeze only the pattern(s) that should stop ramping, e.g. gravity, while others continue). Call this with whatever pseudo-time the phase you’re ending actually stopped at (StepResult::load_factor / TransientStepResult::time) — see LoadPattern’s doc comment for why the frozen value is captured explicitly here rather than lazily cached on every assemble call.

Source

pub fn add_constraint( &mut self, slave: (NId, usize), terms: &[(NId, usize, f64)], )

Constrains slave’s DOF to a linear combination of master DOFs on any nodes: u_slave = sum(coeff * u_master). Repeated masters are merged and vanishing terms dropped; an empty result means u_slave = 0. Problems that cannot be seen from one constraint (a slave defined twice, a fixed slave, a dependency cycle, a master that carries a prescribed value, an inconsistent initial state) are reported by validate. Requires ConstraintHandler::Transformation.

Source

pub fn equal_dof(&mut self, retained: NId, constrained: NId, dofs: &[usize])

Tie dofs of constrained exactly to the same DOFs of retained (u_c = u_r for each listed dof) — Xara/OpenSees’s equalDOF. Requires ConstraintHandler::Transformation; see its doc comment for how this is resolved.

Source

pub fn equation_of(&self, node: NId, dof: usize) -> Option<usize>

Equation number of a node’s DOF if it is a free unknown (a DOF identity-tied to one shares its equation), or None for a fixed DOF, an inactive DOF, or a constrained DOF that is a combination of several unknowns. It says where an unknown lives, not what a DOF’s value is: to read a node DOF out of a free-DOF-indexed vector use value_at. Used internally by Integrator::DisplacementControl to locate its controlled DOF in the free-DOF system, and pub (not just pub(crate)) so a caller holding a free-DOF-indexed vector — e.g. modal_analysis’s Mode::shape — can map one of its entries back to a specific node/DOF without core needing to expose any of gather_displacement/scatter_state’s broader internal-state surface just for that one lookup.

Source

pub fn value_at(&self, q: &DVector<f64>, node: NId, dof: usize) -> f64

The value of (node, dof) in the free-DOF-indexed vector q: the entry for a free unknown, the constraint’s combination of entries for a constrained DOF, and 0.0 for a fixed or inactive DOF. This is how a mode shape, an influence vector, or any other free-DOF vector is read back onto nodes; equation_of cannot do it for a constrained DOF that depends on several unknowns (a rigid-diaphragm slave).

Source

pub fn is_active(&self, node: NId, dof: usize) -> bool

Whether (node, dof) can take part in the analysis: it is fixed, constrained, stiffened by some element, used as a constraint master, or carries a nodal mass. A DOF that is none of these has no equation and nothing to resist a load. Reflects the last numbering (validate/AnalysisBuilder::build run it).

Source

pub fn validate(&mut self) -> Result<(), ModelError>

Numbers the DOFs and checks the model for mistakes that would otherwise be silently wrong: constraint problems (a slave defined twice or fixed, a dependency cycle, a master carrying a prescribed value, mass on a slave that combines several unknowns), an element rejecting its own geometry, nodal loads on DOFs nothing uses, and a constrained slave whose initial state contradicts its masters. Also makes the initial state consistent with the constraints: a slave whose initial displacement, velocity or acceleration is exactly zero (the default) takes the value its masters imply; a nonzero value that disagrees is an error, never overwritten. Called by AnalysisBuilder::build, modal_analysis and TransientAnalysis.

Source

pub fn num_free_dofs(&self) -> usize

Source

pub fn has_mp_constraints(&self) -> bool

Whether any equal_dof/rigid_diaphragm constraint has been added — used by AnalysisBuilder<Ready>::build to reject ConstraintHandler ::Plain (which can’t resolve them) early, at model-construction time rather than as a silently-wrong solve. Also used by callers (e.g. carapace-wasm’s decoder) to pick ConstraintHandler:: Transformation automatically whenever a domain actually has any.

Source

pub fn assemble_mass_diagonal(&mut self) -> DVector<f64>

Number DOFs (idempotent given a fixed set of nodes) and assemble the lumped-mass matrix’s diagonal over free DOFs — user-assigned Node::mass plus each element’s own lumped mass (Element:: form_mass; zero unless a Truss/ElasticBeamColumn was given a nonzero density). Needed by modal and time-history analysis. Just the diagonal, not a full (dense or sparse) N×N matrix — M is diagonal by construction (lumped mass), so storing anything more is pure waste, the same class of mistake a dense stiffness matrix would be (see SparseMatrix’s doc comment).

Source

pub fn direction_incidence(&self, dof_direction: usize) -> DVector<f64>

A unit “influence vector” over free DOFs: 1.0 at every DOF whose local index is dof_direction, 0.0 elsewhere — Chopra’s ι, what TransientAnalysis’s ground-motion excitation needs to turn a scalar ground acceleration into an effective nodal force vector (-M·ι·ag(t), mass-proportional, not reference-load-proportional — see GroundMotion). Computed once at TransientAnalysis::new time, not every step (like mass itself).

Source§

impl Domain

Tie the x-translation DOF of every node in constrained to retained’s — the standard 2D-frame simplification for a rigid floor diaphragm (Xara/OpenSees’s rigidDiaphragm is inherently 3D: it ties in-plane translations and rotation-about-axis through a lever arm to nodes in a plane perpendicular to a given axis, which doesn’t map onto a single-plane 2D model). Equivalent to calling equal_dof(retained, c, &[0]) for each c in constrained.

Planar-only, in its own impl block rather than the shared generic one: naively reusing this identity-tie approach for the spatial profile would silently produce a wrong diaphragm (missing the lever-arm term) instead of refusing to compile — see Domain3::rigid_diaphragm for the real (affine, lever-arm-aware) spatial version below.

Source

pub fn rigid_diaphragm(&mut self, retained: NodeId, constrained: &[NodeId])

Rigid link: slave moves with master as a rigid body. Its rotation equals the master’s and its translations equal the master’s plus the small-rotation lever arm, u_s = u_m + theta_m x d with d = pos(slave) - pos(master) from the reference coordinates at the time of the call (ux_s = ux_m - theta*dy, uy_s = uy_m + theta*dx). Requires ConstraintHandler::Transformation.

Source§

impl Domain<SPATIAL_NDIM, SPATIAL_NDF, Node3Id, Element3>

Spatial rigid diaphragm: for each node in constrained, ties its two in-plane translational dofs (the two coordinate axes other than normal) to retained’s same in-plane translations plus a lever-arm term from retained’s rotation about normal — a genuine general linear constraint (ConstraintHandler::Transformation), not a further identity alias like planar Domain::rigid_diaphragm (see its doc comment for why identity aliasing can’t express a real diaphragm’s lever-arm kinematics).

Deliberately narrower than Xara/OpenSees’s rigidDiaphragm: only the two in-plane translational dofs of each constrained node are tied. Every rotational dof of the constrained node — including its rotation about normal itself — and its out-of-plane translation are left completely free, unconstrained by this call. A floor diaphragm is rigid in its own plane; it doesn’t impose a shared rotation on whatever’s attached to it, and a linear frame model gives it no out-of-plane (vertical) stiffness of its own. This matches planar rigid_diaphragm’s existing scope (which likewise only ties translation, never rz), just generalized from “the one translational in-plane dof a 2D model has” to “the two a 3D diaphragm plane has, with the lever-arm term a real 3D diaphragm needs”.

Kinematics (small-rotation rigid-body motion in the plane perpendicular to normal, at retained node r, evaluated at constrained node c): with n̂ the unit normal and d = pos(c) - pos(r), a rotation theta_n about n̂ displaces c by theta_n * (n̂ × d). Writing a = (normal+1) % 3, b = (normal+2) % 3 for the other two axes (so (n, a, b) is a right-handed triad):

u_c[a] = u_r[a] - theta_r[normal] * (coord[b]_c - coord[b]_r)
u_c[b] = u_r[b] + theta_r[normal] * (coord[a]_c - coord[a]_r)

Cross-checked against Xara/OpenSees’s RigidDiaphragm constraint equations for the z-normal case (perpDirn = 3, their default: Ux_c = Ux_r - Rz_r*(Yc-Yr), Uy_c = Uy_r + Rz_r*(Xc-Xr)) — substituting normal = Z (a = X, b = Y) into the formula above reproduces exactly those two equations. Verified through the full Domain3/Analysis3 stack for rigid-rotation invariance and lever-arm displacement magnitude (core/tests/constraints/rigid_diaphragm_3d.rs), not just asserted by that cross-check.

Lever arms are computed once here, from each node’s fixed reference coords at the time this is called — not re-evaluated from current displacement, matching every other Linear/Linear3 (small- displacement) geometric transform’s scope in this crate.

Planar-only, in its own impl block: normal requires a full 3D coordinate/rotation set (Axis3, and a rotation-about-normal dof) that only the spatial profile’s Node3/SpatialDof provide.

Source

pub fn rigid_diaphragm(&mut self, retained: Node3Id, constrained: &[Node3Id])

rigid_diaphragm_about with normal = Axis3::Y — the “up” axis in this crate’s y-up global convention, so the diaphragm plane is the horizontal x-z plane: the common case (an ordinary floor diaphragm).

Source

pub fn rigid_diaphragm_about( &mut self, retained: Node3Id, constrained: &[Node3Id], normal: Axis3, )

rigid_diaphragm with an explicit diaphragm-plane normal — see this impl block’s doc comment for the kinematics and scope.

Rigid link: slave moves with master as a rigid body. All six slave DOFs are tied: its rotations equal the master’s, and its translations equal the master’s plus the small-rotation lever arm, u_s = u_m + theta_m x d with d = pos(slave) - pos(master) taken from the reference coordinates at the time of the call. Joins, for example, a beam end to the edge of a shell, or an eccentric load point to the member it loads. Requires ConstraintHandler::Transformation.

Trait Implementations§

Source§

impl<const NDIM: usize, const NDOF: usize, NId, E> Clone for Domain<NDIM, NDOF, NId, E>
where NId: Key, E: ElementOps<NDIM, NDOF, NId> + Clone, E::Load: Clone,

Manual rather than #[derive(Clone)]: the derive macro only adds E: Clone, not E::Load: Clone (it can’t see through the associated type), so it under-constrains this struct and fails to compile at every call site instead. Analysis::step’s snapshot-and-restore-on-failure needs this — see Domain’s doc comment.

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<const NDIM: usize, const NDOF: usize, NId, E> Debug for Domain<NDIM, NDOF, NId, E>
where NId: Key + Debug, E: ElementOps<NDIM, NDOF, NId> + Debug, E::Id: Debug, E::Load: Debug,

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<const NDIM: usize, const NDOF: usize, NId, E> Default for Domain<NDIM, NDOF, NId, E>
where NId: Key, E: ElementOps<NDIM, NDOF, NId>,

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

§

impl<const NDIM: usize, const NDOF: usize, NId, E> Freeze for Domain<NDIM, NDOF, NId, E>

§

impl<const NDIM: usize, const NDOF: usize, NId, E> RefUnwindSafe for Domain<NDIM, NDOF, NId, E>
where NId: RefUnwindSafe, E: RefUnwindSafe, <E as ElementOps<NDIM, NDOF, NId>>::Id: RefUnwindSafe, <E as ElementOps<NDIM, NDOF, NId>>::Load: RefUnwindSafe,

§

impl<const NDIM: usize, const NDOF: usize, NId, E> Send for Domain<NDIM, NDOF, NId, E>
where NId: Send, E: Send, <E as ElementOps<NDIM, NDOF, NId>>::Id: Send, <E as ElementOps<NDIM, NDOF, NId>>::Load: Send,

§

impl<const NDIM: usize, const NDOF: usize, NId, E> Sync for Domain<NDIM, NDOF, NId, E>
where NId: Sync, E: Sync, <E as ElementOps<NDIM, NDOF, NId>>::Id: Sync, <E as ElementOps<NDIM, NDOF, NId>>::Load: Sync,

§

impl<const NDIM: usize, const NDOF: usize, NId, E> Unpin for Domain<NDIM, NDOF, NId, E>
where NId: Unpin, E: Unpin, <E as ElementOps<NDIM, NDOF, NId>>::Id: Unpin, <E as ElementOps<NDIM, NDOF, NId>>::Load: Unpin,

§

impl<const NDIM: usize, const NDOF: usize, NId, E> UnsafeUnpin for Domain<NDIM, NDOF, NId, E>

§

impl<const NDIM: usize, const NDOF: usize, NId, E> UnwindSafe for Domain<NDIM, NDOF, NId, E>
where NId: UnwindSafe, E: UnwindSafe, <E as ElementOps<NDIM, NDOF, NId>>::Id: UnwindSafe, <E as ElementOps<NDIM, NDOF, NId>>::Load: UnwindSafe,

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> ByRef<T> for T

§

fn by_ref(&self) -> &T

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<T, U> Imply<T> for U
where T: ?Sized, U: ?Sized,

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
§

impl<SS, SP> SupersetOf<SS> for SP
where SS: SubsetOf<SP>,

§

fn to_subset(&self) -> Option<SS>

The inverse inclusion map: attempts to construct self from the equivalent element of its superset. Read more
§

fn is_in_subset(&self) -> bool

Checks if self is actually part of its subset T (and can be converted to it).
§

fn to_subset_unchecked(&self) -> SS

Use with care! Same as self.to_subset but without any property checks. Always succeeds.
§

fn from_subset(element: &SS) -> SP

The inclusion map: converts self to the equivalent element of its superset.
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.