Loads — g.loads¶
Solver-agnostic load definitions, records, and resolver. Loads are
declared on geometry (with optional case grouping) and
resolved on the mesh by g.mesh.queries.get_fem_data.
Two-stage pipeline¶
Stage 1 — declare before meshing. The factory methods on
g.loads (point.force, point.moment, point.force_closest,
point.moment_closest, line, surface.pressure,
surface.traction, surface.force_resultant_center_mass, gravity,
volume) store
LoadDef dataclasses describing
intent at the geometry level. The active
case
context tags every def created inside it.
Stage 2 — resolve after meshing.
LoadResolver converts each
def to a list of resolved records. Records land on the FEM broker
according to type:
| Record family | Lives on | Emitted by |
|---|---|---|
NodalLoadRecord |
fem.nodes.loads |
tributary / consistent reductions |
ElementLoadRecord |
fem.elements.loads |
target_form="element" (eleLoad style) |
SPRecord |
fem.nodes.sp |
prescribed displacements via g.displacements (no longer a g.loads method) |
Cases¶
Loads (and only loads — not constraints, not masses) are grouped
under named cases via the
case
context manager (geometry-side case vs bridge-side OpenSees
pattern, per ADR 0051):
with g.loads.case("Dead"):
g.loads.gravity("Slab", density=2400)
g.loads.line("BeamEdge", magnitude=-15e3)
with g.loads.case("Live"):
g.loads.surface.pressure("Slab", -2.5e3)
Defs declared outside any case block belong to the implicit
"default" case. Downstream solvers emit one
timeSeries/pattern block per case.
Reduction & emission form¶
Three of the distributed-load factories (line, surface,
gravity, volume) accept two orthogonal flags that change how the
load is converted to records:
reduction |
target_form |
Effect |
|---|---|---|
"tributary" |
"nodal" |
Default. Length/area/volume-weighted nodal lumping — one NodalLoadRecord per node |
"consistent" |
"nodal" |
Shape-function (Gauss-quadrature) integration — required for higher-order elements |
"tributary" |
"element" |
Skip nodal lumping entirely; emit one ElementLoadRecord per element |
"consistent" |
"element" |
Same as above; the solver's element handles the integration |
Use element form for beam-element line loads
(eleLoad -beamUniform), shell pressures handled inside the
element, or any solver-side load that you don't want decomposed at
the apeGmsh layer.
Target identification¶
All factory methods accept a flexible positional target argument
plus three explicit keyword overrides (pg=, label=, tag=) that
pin the lookup source. The auto path tries, in order: raw
(dim, tag) list → mesh selection → label → physical group → part
label. The first match wins. See the
LoadsComposite
class docstring for the full disambiguation rules.
Worked example¶
from apeGmsh import apeGmsh
with apeGmsh(model_name="frame") as g:
# ... geometry + Parts already imported ...
with g.loads.case("Dead"):
g.loads.gravity("Slab", density=2400) # body load
g.loads.line("BeamEdge", magnitude=-15e3, # distributed
direction=(0, 0, -1), # line load
reduction="tributary")
with g.loads.case("Push"):
g.loads.point.force_closest( # snaps to
xyz=(5.0, 2.5, 3.0), within="Slab", # nearest
force=(120e3, 0.0, 0.0), # mesh node
)
g.mesh.generation.generate(dim=3)
fem = g.mesh.queries.get_fem_data(dim=3)
# Pattern-by-pattern emission into OpenSees
for pat in g.loads.cases():
ops.timeSeries("Linear", pat_tag(pat))
ops.pattern("Plain", pat_tag(pat), pat_tag(pat))
for r in fem.nodes.loads.by_pattern(pat):
ops.load(r.node_id, *(r.force_xyz or (0,)*3))
Composite¶
apeGmsh.core.LoadsComposite.LoadsComposite ¶
Loads composite — define + resolve loads.
Surface (dimension-indexed, ADR 0050)¶
Load verbs are grouped by the dimension of their target. Single-verb dimensions are plain callables; multi-verb dimensions are namespaces::
g.loads.point.force / .moment / .force_closest / .moment_closest
g.loads.line(...) # distributed q
g.loads.surface.pressure / .traction / .force_resultant_center_mass
g.loads.volume(...) # body force
g.loads.gravity(...) # self-weight
Target resolution¶
Every load verb accepts a flexible positional target argument
plus three explicit keyword overrides::
g.loads.point.force("my_pt", force=(0, 0, -1)) # auto
g.loads.point.force(pg="my_pg", force=(0, 0, -1)) # force PG
g.loads.point.force(label="top", force=(0, 0, -1)) # force label
g.loads.point.force(tag=[(0, 7)], force=(0, 0, -1)) # raw DimTag
When the caller passes target=... (the auto path),
:meth:_resolve_target tries each of these in order until one
matches:
=== ======================== =============================
Source Provided by¶
=== ======================== =============================
1 raw list[(dim, tag)] the caller
2 mesh selection name g.mesh_selection
3 label (Tier 1, prefixed) _label: physical groups
4 physical group (Tier 2) user-authored PGs
5 part label g.parts._instances
=== ======================== =============================
The first match wins. If two namespaces share a name (e.g. a label
and a PG both called "top"), label wins because it is checked
first. To bypass auto resolution and pin a specific source use the
keyword form: pg= skips straight to step 4, label= to step
3, tag= to step 1.
A KeyError is raised if auto resolution exhausts all five
sources without finding the name.
Load cases¶
All load definitions inherit the pattern field of the active
:meth:case context (default "default"). A case is a grouping
label only — it carries no time series and no stage. The OpenSees
timeSeries / pattern is chosen later, on the apeSees bridge
(ADR 0051: case on the geometry, pattern on the bridge).
Source code in src/apeGmsh/core/LoadsComposite.py
case ¶
Group subsequent load definitions under a named load case.
A case is a grouping label only (no time series, no stage). The
OpenSees timeSeries / pattern is chosen on the apeSees
bridge when the case is imported (ADR 0051).
Example¶
::
with g.loads.case("dead"):
g.loads.gravity("concrete", g=(0, 0, -9.81), density=2400)
g.loads.line("beams", magnitude=-2e3, direction="z")
with g.loads.case("live"):
g.loads.surface.pressure("slabs", magnitude=-3e3)
Source code in src/apeGmsh/core/LoadsComposite.py
line ¶
line(target=None, *, pg=None, label=None, tag=None, magnitude=None, direction=(0.0, 0.0, -1.0), q_xyz=None, normal=False, away_from=None, reduction='tributary', target_form='nodal', basis='lagrange', name=None) -> LineLoadDef
Distributed load (force per unit length) along the curve(s) of target.
Three ways to specify the load vector:
magnitude+direction: scalar magnitude along a fixed unit vector (or axis name"x"/"y"/"z").q_xyz: explicit(qx, qy, qz)force-per-length vector.normal=True+away_from: edge-by-edge in-plane pressure. The pressure direction is the edge normal lying in the plane of the loaded curves (any plane — XY, XZ, YZ, or arbitrary; the plane is fitted from the curve geometry), sign-flipped per edge so it points away fromaway_from(a reference point representing the source of the load — e.g. the centre of an arched cavity loaded by internal pressure). Positivemagnitudethen pushes into the structure.
For normal=True without away_from, apeGmsh consults
the parent surface's Gmsh-oriented normal + boundary loop to
decide which side is "into the structure" (also plane-
general). If the curve has no adjacent surface, or bounds
more than one, the resolver raises ValueError —
disambiguate by passing away_from, or fall back to
direction/q_xyz. If the loaded curves are collinear
or non-planar the in-plane normal is undefined and the
resolver raises ValueError.
Reduction and emission form¶
reduction="tributary"(default): split each edge's length-weighted load equally between its two end nodes. Emits :class:NodalLoadRecordonfem.nodes.loads.reduction="consistent": shape-function integration (line2 / line3) — equivalent to the FEM consistent load vector. Required for higher-order elements where simple tributary lumping is wrong.target_form="element": skip nodal lumping entirely and emit oneElementLoadRecordper beam element withload_type="beamUniform"— the solver's element formulation handles the integration.
Parameters¶
target : str or list of (dim, tag), optional
Curve(s) to load.
pg, label, tag :
Explicit-source overrides. See class docstring.
magnitude : float or callable, optional
Scalar force per unit length. Required if q_xyz is
None. Required when normal=True.
May also be a **callable** ``q(xyz) -> float`` that
receives the ``(x, y, z)`` coordinate as a length-3
array and returns the local force-per-length — for a
spatially varying load such as a depth-dependent ground
/ convergence pressure, e.g.
``magnitude=lambda p: gamma * (z_top - p[2])``. Works
with ``normal=True`` and with ``direction=``, in every
``target_form``; mutually exclusive with ``q_xyz``.
Accuracy of the varying field depends on ``reduction``:
* ``reduction="consistent"`` — the field is integrated
against the shape functions at the element **Gauss
points** (:func:`integrate_edge_scaled`), i.e. the
exact consistent load vector to quadrature order. No
over/undershoot of the resultant; mesh-converged even
on a coarse mesh.
* ``reduction="tributary"`` (default) — sampled once at
each edge **midpoint** and lumped (the tributary model
is itself a lumping approximation). This is the
midpoint rule: ``O(h^2)`` and exact for a linear field
on straight edges, but it can over/undershoot the true
∫q on curved edges or steep gradients — pass
``reduction="consistent"`` or refine the mesh.
direction : tuple or {"x", "y", "z"}, default (0, 0, -1)
Unit direction for magnitude. Ignored when
q_xyz or normal=True is given.
q_xyz : (qx, qy, qz), optional
Explicit force-per-length vector — overrides
magnitude × direction.
normal : bool, default False
If True, treat the load as a pressure normal to each
edge, acting in the plane of the loaded curves (fitted
from the curve geometry — any plane, not just XY).
away_from : (x, y, z), optional
Reference point for normal=True direction
disambiguation.
reduction : "tributary" or "consistent", default
"tributary"
How distributed loads are reduced to nodal records.
target_form : "nodal" or "element", default
"nodal"
Output record type. "element" skips nodal lumping
and emits eleLoad-style records.
basis : "lagrange" or "bernstein", default
"lagrange"
Shape-function family for reduction="consistent"
(ADR 0091). Use "bernstein" when the loaded edges
belong to Ladruno Bézier elements (BezierTri6 /
BezierTet10), whose DOFs are Bernstein control values:
a uniform q then loads all three edge control points
equally (q·L/3) instead of the Lagrange
(q·L/6, q·L/6, 4q·L/6). Only valid with
reduction="consistent", target_form="nodal".
name : str, optional
Friendly name.
Returns¶
LineLoadDef
Raises¶
ValueError
If neither magnitude nor q_xyz is supplied, or
normal=True is set without magnitude.
KeyError
If target doesn't resolve.
Examples¶
Uniform vertical line load on a beam edge::
g.loads.line(
"BeamEdge",
magnitude=-15e3,
direction=(0, 0, -1),
)
Internal pressure on a curved 2-D arch::
g.loads.line(
"InnerArc",
magnitude=p_int,
normal=True,
away_from=(0.0, 0.0, 0.0),
)
Element-form output for a beam carrying its own eleLoad
per element::
g.loads.line(
"Girder",
magnitude=-25e3,
direction=(0, 0, -1),
target_form="element",
)
Source code in src/apeGmsh/core/LoadsComposite.py
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 | |
gravity ¶
gravity(target=None, *, pg=None, label=None, tag=None, g=(0.0, 0.0, -9.81), density=None, reduction='tributary', target_form='nodal', name=None) -> GravityLoadDef
Body weight (ρ · g) over the continuum region(s) of target.
Convenience wrapper over :meth:volume for the common case of
gravity loading. The total per-element load is
density × element_measure × g_vec, distributed to the
element's nodes. element_measure is the element volume
for a 3D (dim=3) target and its area for a 2D
(dim=2) target — so density is mass-per-volume in a 3D
model and mass-per-area in a 2D model. The target's dimension
is detected from the mesh, so the same call works for both.
Reduction and emission form¶
reduction="tributary"(default): split each element's weight equally among its corner nodes. Requiresdensity.reduction="consistent": for tet4 / hex8 with constant density, reduces to the same per-node share as tributary (so behaviourally equivalent today, but the path is kept separate for higher-order extensions).target_form="element": emit oneElementLoadRecordper volume element withload_type="bodyForce"carryingganddensity; the solver's element formulation handles integration.density=Noneis allowed in this form — the solver reads it from the assigned material.
Parameters¶
target : str or list of (dim, tag)
Volume(s) carrying body weight.
pg, label, tag :
Explicit-source overrides.
g : (gx, gy, gz), default (0, 0, -9.81)
Gravitational acceleration vector. Unit-sensitive —
use (0, 0, -9810) for mm models with kg-mm-s units,
etc.
density : float, optional
Material density (mass per unit volume). Required when
target_form="nodal"; optional in element form.
reduction : "tributary" or "consistent", default
"tributary"
Lumping scheme.
target_form : "nodal" or "element", default
"nodal"
Output record type.
name : str, optional
Friendly name.
Returns¶
GravityLoadDef
Raises¶
ValueError
If density is missing for target_form="nodal".
KeyError
If target doesn't resolve.
See Also¶
volume : Generic per-volume body force vector.
masses.volume : Add the same density as nodal mass for
inertial response (don't double-count if the OpenSees
material already carries rho).
Examples¶
Self-weight of a concrete slab (kg-m-s, ρ = 2400 kg/m³)::
with g.loads.case("Dead"):
g.loads.gravity("Slab", density=2400)
Element-form gravity reading density from the material::
g.loads.gravity(
"ConcreteBlock",
target_form="element",
)
Source code in src/apeGmsh/core/LoadsComposite.py
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 | |
volume ¶
volume(target=None, *, pg=None, label=None, tag=None, force_per_volume=(0.0, 0.0, 0.0), reduction='tributary', target_form='nodal', name=None) -> BodyLoadDef
Generic body force on the continuum region(s) of target.
General sibling of :meth:gravity — accepts an arbitrary
intensity vector. The total per-element load is
force_per_volume × element_measure, distributed to the
element's nodes. element_measure is volume for a 3D target
and area for a 2D target — so the supplied intensity is
force-per-volume in a 3D model and force-per-area in a 2D one.
Use cases beyond gravity:
- Centrifugal / rotational body force.
- Magnetic body force in coupled-physics models.
- Thermal expansion modelled as an equivalent body force.
- Any prescribed loading proportional to volume.
Parameters¶
target : str or list of (dim, tag)
Volume(s) to load.
pg, label, tag :
Explicit-source overrides.
force_per_volume : (bx, by, bz), default (0, 0, 0)
Body force vector in force per unit volume.
reduction : "tributary" or "consistent", default
"tributary"
Lumping scheme. "consistent" falls back to
tributary for tet4/hex8 (same per-node share for
constant body force).
target_form : "nodal" or "element", default
"nodal"
Output record type. "element" emits one
ElementLoadRecord per volume element with
load_type="bodyForce" and params={"bf": ...}.
name : str, optional
Friendly name.
Returns¶
BodyLoadDef
See Also¶
gravity : Convenience wrapper for ρ · g body force.
Examples¶
Centrifugal body force ρ · ω² · r evaluated as a
constant approximation::
g.loads.volume(
"Rotor",
force_per_volume=(omega**2 * rho * r_cg, 0, 0),
)
Source code in src/apeGmsh/core/LoadsComposite.py
validate_pre_mesh ¶
Validate every registered load's target can be resolved.
Called by :meth:Mesh.generate before meshing so typos fail
fast instead of after minutes of meshing. Raw (dim, tag)
lists are skipped — only string targets are looked up.
Source code in src/apeGmsh/core/LoadsComposite.py
resolve ¶
resolve(node_tags, node_coords, elem_tags=None, connectivity=None, *, node_map=None, face_map=None) -> LoadSet
Resolve all stored LoadDefs into a :class:LoadSet.
Source code in src/apeGmsh/core/LoadsComposite.py
summary ¶
DataFrame of the declared load intent — one row per def.
Columns: kind, name, case, target, source, reduction,
target_form, basis, params. params is a short stringified
view of the kind-specific fields (force, magnitude, direction,
...).
Source code in src/apeGmsh/core/LoadsComposite.py
Base classes¶
apeGmsh._kernel.defs.loads.LoadDef
dataclass
¶
LoadDef(kind: str, target: object, pattern: str = 'default', name: str | None = None, reduction: str = 'tributary', target_form: str = 'nodal', target_source: str = 'auto', basis: str = 'lagrange')
Base class for all load definitions.
basis qualifies the consistent reduction only (ADR 0091): the
shape functions the field is integrated against. "lagrange"
(default) targets interpolatory nodal-value elements
(TenNodeTetrahedron, SixNodeTri, ...); "bernstein" targets the
Ladruno fork's Bézier control-value elements (BezierTet10 /
BezierTri6), whose DOFs are Bernstein control values — a
Lagrange-consistent vector applied to them represents a strongly
oscillatory traction. Identical for degree-1 elements.
apeGmsh._kernel.records._loads.LoadRecord
dataclass
¶
Base class for all resolved load records.
Concentrated loads¶
Concentrated forces and moments — applied either to nodes that already exist on a named target, or to the mesh node nearest a world coordinate.
apeGmsh._kernel.defs.loads.PointLoadDef
dataclass
¶
PointLoadDef(kind: str, target: object, pattern: str = 'default', name: str | None = None, reduction: str = 'tributary', target_form: str = 'nodal', target_source: str = 'auto', basis: str = 'lagrange', force_xyz: tuple[float, float, float] | None = None, moment_xyz: tuple[float, float, float] | None = None)
Bases: LoadDef
Concentrated force/moment at a node (or set of nodes).
All targeted nodes receive the same force/moment. Use
:meth:PointLoadDef.force_xyz for translational forces and
:meth:PointLoadDef.moment_xyz for moments (3D rotational DOFs).
Either may be None.
apeGmsh._kernel.defs.loads.PointClosestLoadDef
dataclass
¶
PointClosestLoadDef(kind: str, target: object, pattern: str = 'default', name: str | None = None, reduction: str = 'tributary', target_form: str = 'nodal', target_source: str = 'auto', basis: str = 'lagrange', force_xyz: tuple[float, float, float] | None = None, moment_xyz: tuple[float, float, float] | None = None, xyz_request: tuple[float, float, float] = (0.0, 0.0, 0.0), within: object | None = None, within_source: str = 'auto', tol: float | None = None, snap_distance: float | None = None)
Bases: PointLoadDef
Concentrated load at the mesh node(s) closest to a coordinate.
Coordinate-driven targeting (no PG/label required). At resolve time,
the composite snaps xyz_request to the nearest mesh node — or, if
tol is given, to every node within that radius. Pass within
(PG/label/part/DimTag list) to restrict the candidate node pool.
The actual snap distance is written back to snap_distance after
:meth:LoadsComposite.resolve, so it surfaces in summary().
Distributed loads¶
Length-, area-, or volume-distributed loads. All four accept the
reduction × target_form flags described above.
apeGmsh._kernel.defs.loads.LineLoadDef
dataclass
¶
LineLoadDef(kind: str, target: object, pattern: str = 'default', name: str | None = None, reduction: str = 'tributary', target_form: str = 'nodal', target_source: str = 'auto', basis: str = 'lagrange', magnitude: object = 0.0, direction: object = (0.0, 0.0, -1.0), q_xyz: tuple[float, float, float] | None = None, normal: bool = False, away_from: tuple[float, float, float] | None = None)
Bases: LoadDef
Distributed load along a 1-D entity (curve / beam element).
Three ways to specify the load vector:
magnitude+direction— scalar magnitude (force per unit length) and a direction vector or axis name ("x","y","z").q_xyz— explicit(qx, qy, qz)force-per-length vector.normal=True+away_from=(x0, y0, z0)— pressure perpendicular to each edge, in the plane of the loaded curves (any plane; fitted from the geometry). The in-plane normal is sign-flipped per edge so it points away fromaway_from;magnitudeis then force per unit length along that normal.
magnitude may be a constant float or a callable
q(xyz) -> float evaluated per edge midpoint (spatially varying
line load); the resolver in :mod:apeGmsh.mesh._load_resolver
handles both.
apeGmsh._kernel.defs.loads.SurfaceLoadDef
dataclass
¶
SurfaceLoadDef(kind: str, target: object, pattern: str = 'default', name: str | None = None, reduction: str = 'tributary', target_form: str = 'nodal', target_source: str = 'auto', basis: str = 'lagrange', magnitude: float = 0.0, mode: str = 'pressure', direction: tuple[float, float, float] = (0.0, 0.0, -1.0))
Bases: LoadDef
Pressure, traction, or in-plane shear on a 2-D entity (ADR 0050).
mode selects the regime (replaces the old normal bool —
a bool can't carry three states):
"pressure": scalarmagnitudeperpendicular to each face (positive into the face).directionignored."traction": free vector per area in global coordinates;directionis the full vector,magnitudeits norm."shear": strict in-plane traction —directionis a global reference vector projected onto each face's tangent plane (normal component removed). Fail-loud where the projection vanishes (purely-normal input).
apeGmsh._kernel.defs.loads.GravityLoadDef
dataclass
¶
GravityLoadDef(kind: str, target: object, pattern: str = 'default', name: str | None = None, reduction: str = 'tributary', target_form: str = 'nodal', target_source: str = 'auto', basis: str = 'lagrange', g: tuple[float, float, float] = (0.0, 0.0, -9.81), density: float | None = None)
Bases: LoadDef
Body load from gravity = ρ·g over a volume.
If density is None, the solver bridge is expected to read
density from the assigned material/section.
apeGmsh._kernel.defs.loads.BodyLoadDef
dataclass
¶
Face load and face SP¶
Face-centroid versions used when you want to apply a centroidal force/moment or prescribed motion to a whole face without introducing a reference node and a coupling constraint.
apeGmsh._kernel.defs.loads.FaceLoadDef
dataclass
¶
FaceLoadDef(kind: str, target: object, pattern: str = 'default', name: str | None = None, reduction: str = 'tributary', target_form: str = 'nodal', target_source: str = 'auto', basis: str = 'lagrange', force_xyz: tuple[float, float, float] | None = None, moment_xyz: tuple[float, float, float] | None = None, magnitude: float = 0.0, normal: bool = False, direction: tuple[float, float, float] | None = None)
Bases: LoadDef
Concentrated force/moment at face centroid, distributed to face nodes.
force_xyz is split equally among all face nodes (F / N).
moment_xyz is converted to statically equivalent nodal forces
via a least-norm distribution such that Sum(r_i x f_i) = M and
Sum(f_i) = 0.
A scalar magnitude (total Newtons, NOT pressure) can be combined
with either normal=True or an explicit direction to produce
the equivalent force_xyz without manually computing the face
normal. Sign convention: magnitude * direction_unit always —
i.e. +magnitude with normal=True acts along the
area-weighted average outward normal +n_avg (and
-magnitude flips it, matching :class:SurfaceLoadDef's
"into-face" pressure when desired). Composes with moment_xyz;
combining with force_xyz is an error.
Use this instead of a reference node + coupling when you only need to apply a load to a face without structural coupling to another element.
apeGmsh._kernel.defs.loads.FaceSPDef
dataclass
¶
FaceSPDef(kind: str, target: object, pattern: str = 'default', name: str | None = None, reduction: str = 'tributary', target_form: str = 'nodal', target_source: str = 'auto', basis: str = 'lagrange', dofs: list[int] = (lambda: [1, 1, 1])(), disp_xyz: tuple[float, float, float] | None = None, rot_xyz: tuple[float, float, float] | None = None, magnitude: float = 0.0, normal: bool = False, direction: tuple[float, float, float] | None = None)
Bases: LoadDef
Prescribed displacement/rotation at face centroid, mapped to face nodes.
Maps a rigid-body motion at the face centroid to per-node
displacements using u_i = disp_xyz + rot_xyz x r_i.
When disp_xyz, rot_xyz, and magnitude are all None /
zero, the result is a homogeneous fix.
A scalar magnitude (displacement, in mesh length units) can be
combined with normal=True or an explicit direction to
derive the centroid translation without computing the face normal
by hand. Sign convention matches :class:FaceLoadDef: total =
magnitude * unit_direction along +n_avg (or the normalised
direction). Composes with rot_xyz; combining with
disp_xyz is an error.
Parameters¶
dofs : list[int]
Restraint mask — 1 for constrained DOFs, 0 for free.
disp_xyz : tuple or None
Prescribed translation at the face centroid.
rot_xyz : tuple or None
Prescribed rotation about the face centroid.
magnitude : float
Scalar centroid translation, routed via normal/direction.
normal : bool
When True, use the area-weighted face normal as the direction.
direction : tuple or None
Explicit unit direction (auto-normalised); mutually exclusive
with normal=True.
Resolved records¶
What ends up on the FEM broker after meshing.
apeGmsh._kernel.records._loads.NodalLoadRecord
dataclass
¶
NodalLoadRecord(kind: str, pattern: str = 'default', name: str | None = None, node_id: int = 0, force_xyz: tuple[float, float, float] | None = None, moment_xyz: tuple[float, float, float] | None = None, basis: str | None = None)
Bases: LoadRecord
Force and/or moment at a single node.
force_xyz and moment_xyz are pure 3D spatial vectors (or
None when absent). The record is DOF-agnostic — mapping onto
a solver's DOF space is the caller's responsibility.
basis (ADR 0091) records which shape-function family a
consistent reduction integrated against — "lagrange" or
"bernstein" — so the OpenSees bridge can warn when e.g. a
Lagrange-consistent surface load is imported onto Bézier
control-value elements (the TIMs T2 mechanism). None for every
basis-insensitive record: point loads, tributary lumping,
resultants, and the gravity/volume equal split (which is exact in
both bases for constant fields).
apeGmsh._kernel.records._loads.ElementLoadRecord
dataclass
¶
apeGmsh._kernel.records._loads.SPRecord
dataclass
¶
SPRecord(kind: str, pattern: str = 'default', name: str | None = None, node_id: int = 0, dof: int = 1, value: float = 0.0, is_homogeneous: bool = True)
Bases: LoadRecord
Single-point constraint: prescribed displacement or homogeneous fix.
One record per DOF per node. When is_homogeneous is True
the downstream emitter can use ops.fix(); otherwise it must use
ops.sp(node, dof, value).
Resolver¶
apeGmsh._kernel.resolvers._load_resolver.LoadResolver ¶
LoadResolver(node_tags: ndarray, node_coords: ndarray, elem_tags: ndarray | None = None, connectivity: ndarray | None = None)
Convert :class:LoadDef instances to :class:LoadRecord lists.
Pure mesh math — receives raw arrays and a DOF context, returns record lists. No Gmsh queries (the composite handles target resolution before calling here).
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
face_area ¶
Polygonal face area via fan triangulation from node[0].
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
face_normal ¶
Outward normal estimate from the first three nodes.
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
element_volume ¶
Volume of one solid element.
n == 4(tet4): exact analytic scalar triple product.n == 8(hex8): exact 6-tetrahedron decomposition.- any other catalog type (wedge6, tet10, hex20, hex27):
isoparametric
V = ∫_{Ω_ref} |J(ξ)| dξvia the shared shape-function Jacobian + reference quadrature — exact for affine elements, the standard high-accuracy approximation for curved higher-order ones. (Mirrors :meth:MassResolver.element_volume; previously this path returned the bounding-box volume, which overshoots a tet10 by ~6x — gravity/volume loads on quadratic solid meshes were wrong by that factor.) - unknown element type: bounding-box last resort.
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
element_volumes_bulk ¶
Vectorized volumes for a list of element connectivities.
Bit-identical to [self.element_volume(e) for e in elements] but
computes the hex8 / tet4 bulk with a few whole-array passes instead of
per-element np.cross (which spent ~95% of its time in axis-handling
overhead). Non-hex8/tet4 types fall back to the scalar path. Mirrors
:meth:apeGmsh._kernel.resolvers._mass_resolver.MassResolver.element_volumes_bulk.
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
element_measures_bulk ¶
Bulk element measures: volume (dim == 3) or area (dim == 2).
dim == 3 vectorizes the hex8/tet4 volume via
:meth:element_volumes_bulk; dim == 2 falls back to the per-element
scalar :meth:element_measure (face-area path — not the profiled
bottleneck). Bit-identical to [element_measure(e, dim) for e in …].
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
element_measure ¶
Geometric measure of a continuum element.
dim == 3 → element volume; dim == 2 → element area
(from the corner nodes, so higher-order tri6/quad8/quad9 use
their straight-edge corners). Lets gravity / body tributary
lumping serve 2D models (per-area intensity) and 3D models
(per-volume intensity) through one code path.
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_point ¶
Apply the same force/moment to every node in node_set.
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_line_tributary ¶
Distribute a line load by length-weighted nodal share.
edges is a list of (node_a, node_b) pairs covering the loaded
curve. Each node receives magnitude * Σ(adjacent_edge_len/2)
in the direction vector.
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_line_per_edge_tributary ¶
resolve_line_per_edge_tributary(defn: LineLoadDef, items: list[tuple[int, int, ndarray]]) -> list[NodalLoadRecord]
Tributary line-load reduction with a per-edge force-per-length.
items is a list of (n1, n2, q_xyz) triples; each q_xyz
is the force-per-length vector applied to that single edge.
Used by the composite for normal=True loads where the
direction varies edge-by-edge.
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_surface_tributary ¶
resolve_surface_tributary(defn: SurfaceLoadDef, faces: list[list[int]], outwards: list[ndarray] | None = None) -> list[NodalLoadRecord]
Distribute a surface load by tributary area.
faces is a list of node-id lists (one per face element).
For each face, total = magnitude * area and is split
equally among the face's nodes. normal=True projects
along the face normal; otherwise the explicit direction vector.
outwards, when given, supplies a per-face physical
outward unit normal that overrides the connectivity-derived
:meth:face_normal. This is needed for embedded crack
faces (whose connectivity normal can disagree with physical
outward) and for tilted faces with unpredictable connectivity
orientation; the composite layer fills it in via
:meth:LoadsComposite._face_outward_normals. When None,
the connectivity normal is used (preserving backward compat
for direct callers that don't go through the composite).
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_gravity_tributary ¶
resolve_gravity_tributary(defn: GravityLoadDef, elements: list[ndarray], dim: int = 3) -> list[NodalLoadRecord]
Distribute body weight equally to element nodes.
elements is a list of connectivity rows (each is an array
of node IDs). Each element contributes ρ·measure·g total,
split equally among its nodes, where measure is the element
volume for dim == 3 and its area for dim == 2 (so
density is mass-per-volume in 3D, mass-per-area in 2D).
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_body_tributary ¶
resolve_body_tributary(defn: BodyLoadDef, elements: list[ndarray], dim: int = 3) -> list[NodalLoadRecord]
Distribute a body force equally to element nodes.
force_per_volume is multiplied by the element measure —
volume for dim == 3, area for dim == 2 (so the
supplied intensity is force-per-area in a 2D model).
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_line_consistent ¶
Consistent line-load reduction via shape-function integration.
edges is a list of node-id sequences; each sequence's length determines element order:
2 nodes -> line2 (linear), 2-pt Gauss
3 nodes -> line3 (quadratic), 3-pt Gauss
Any other node count raises :class:NotImplementedError rather
than silently producing wrong numbers.
defn.basis selects the shape-function family — Lagrange
(nodal values) or Bernstein (Bézier control values, ADR 0091).
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_line_per_edge_consistent ¶
resolve_line_per_edge_consistent(defn: LineLoadDef, items: list[tuple[list[int], ndarray]]) -> list[NodalLoadRecord]
Consistent line-load reduction with a per-edge force-per-length.
items is a list of (node_seq, q_xyz) pairs; each
q_xyz is treated as constant along that edge. Shape-
function integration is otherwise identical to
:meth:resolve_line_consistent.
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_line_per_edge_consistent_varying ¶
Consistent reduction of a spatially varying line load.
items is a list of (node_seq, dir_vec, scalar_fn) tuples.
For each edge the scalar force-per-length scalar_fn(xyz) is
integrated against the shape functions at the element's Gauss
points (:func:integrate_edge_scaled) and applied along the
per-edge dir_vec (the in-plane normal for normal=True,
or the direction vector otherwise). This is the exact
consistent load vector to quadrature order, so a varying
magnitude does not over/undershoot the way a single midpoint
sample does.
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_surface_consistent ¶
Consistent surface load via shape-function integration.
Each face is a node-id sequence whose length determines the face type:
3 -> tri3, 4 -> quad4, 6 -> tri6, 8 -> quad8, 9 -> quad9
For mode="pressure" the pressure follows the curved face
normal evaluated at each Gauss point. Any other node count
raises :class:NotImplementedError.
For mode="shear" the global reference vector is projected
onto each face's average tangent plane (exact for flat faces;
for curved higher-order faces the face-average normal is used).
defn.basis selects the shape-function family — Lagrange
(nodal values) or Bernstein (Bézier control values, e.g. the
faces of the Ladruno BezierTet10; ADR 0091). Under a uniform
traction the Bernstein branch loads all six tri6 control points
equally (q·A/6) where the Lagrange branch gives corners ~0
and midsides q·A/3.
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_gravity_consistent ¶
resolve_gravity_consistent(defn: GravityLoadDef, elements: list[ndarray], dim: int = 3) -> list[NodalLoadRecord]
Consistent gravity reduction.
For tet4 / hex8 with constant density, the consistent vector equals the tributary vector (each node gets V/n × ρ × g).
The same equal split is exact for the quadratic Bernstein
simplex elements (basis="bernstein", ADR 0091): every
BezierTet10 basis function integrates to V/10 and every
BezierTri6 one to A/6, so a constant body force lands equally
on all control points. For higher-order Lagrange elements
(tet10/hex20) the equal split remains an approximation with the
correct per-element resultant (the true Lagrange-consistent
vector puts negative shares on tet10 corners).
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_line_element ¶
Emit one ElementLoadRecord per beam element with beamUniform params.
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_line_element_varying ¶
resolve_line_element_varying(defn: LineLoadDef, items: list[tuple[int, tuple[float, float, float]]]) -> list[ElementLoadRecord]
Per-element beamUniform for a spatially varying line load.
items is a list of (element_id, (wx, wy, wz)) pairs — the
composite has already sampled the callable magnitude at each
element's midpoint, so each element gets its own constant
beamUniform (the only thing OpenSees eleLoad supports).
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_surface_element ¶
Emit one ElementLoadRecord per face element with surfacePressure.
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_gravity_element ¶
Emit one ElementLoadRecord per volume element with bodyForce.
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_body_element ¶
Emit one ElementLoadRecord per volume element with bodyForce.
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
resolve_face_load ¶
resolve_face_load(defn: FaceLoadDef, face_node_ids: list[int], faces: list[list[int]] | None = None, outwards: list[ndarray] | None = None) -> list[NodalLoadRecord]
Distribute centroidal force/moment to face nodes.
force_xyz: equal share F / N per node.
moment_xyz: least-norm nodal forces satisfying
Sum(f_i) = 0 and Sum(r_i x f_i) = M.
magnitude + normal/direction: equivalent force_xyz
derived from face geometry. Requires faces (per-element
node-id lists) when normal=True so the area-weighted
average normal can be computed.
outwards, when given, supplies a per-face physical outward
unit normal that overrides the connectivity-derived
:meth:face_normal in the area-weighted average. See the
outwards= discussion on
:meth:resolve_surface_tributary.
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 | |
resolve_face_sp ¶
resolve_face_sp(defn: FaceSPDef, face_node_ids: list[int], faces: list[list[int]] | None = None, outwards: list[ndarray] | None = None) -> list[SPRecord]
Map centroidal rigid-body motion to per-node SP constraints.
For each constrained DOF d and each node i:
u_i = disp_xyz + rot_xyz x r_i, then emit
SPRecord(node_id=i, dof=d, value=u_i[d-1]).
magnitude + normal/direction derive an additional
translation contribution (along +n_avg for normal=True,
otherwise along the normalised direction). Requires
faces when normal=True.
outwards, when given, supplies a per-face physical outward
unit normal that overrides the connectivity-derived
:meth:face_normal. See
:meth:resolve_surface_tributary.
Source code in src/apeGmsh/_kernel/resolvers/_load_resolver.py
988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 | |
resolve_point_sp ¶
Prescribed displacement/rotation applied directly at nodes.
For each targeted node and each constrained DOF d
(defn.dofs[d] == 1) emit SPRecord(node_id, dof=d+1,
value=values[d]) — the value taken from defn.values
(None → homogeneous 0). No centroid / rigid-body mapping:
the value is applied verbatim at every node.