OpenSees bridge¶
apeGmsh's OpenSees deck is constructed via the explicit-constructor pattern after the session closes:
from apeGmsh.opensees import apeSees
fem = g.mesh.queries.get_fem_data(dim=3)
ops = apeSees(fem)
ops.model(ndm=3, ndf=6)
# … typed-primitive declarations, explicit fix / mass / patterns …
ops.tcl("model.tcl") # or ops.py(...), ops.h5(...), ops.run()
The legacy g.opensees session composite and its sub-composites
(materials / elements / ingest / inspect /
export) were removed in Phase 8 of the bridge teardown (ADR
0009). apeSees has no ingest and no auto-resolution — loads,
masses, and SPs must be re-declared explicitly on ops; MP
constraints are deferred (see the gold reference in
skills/apegmsh/references/opensees-bridge.md).
Public surface¶
apeGmsh.opensees.apeSees ¶
The OpenSees bridge.
Construct with a :class:~apeGmsh.mesh.FEMData snapshot:
.. code-block:: python
ops = apeSees(fem)
ops.model(ndm=3, ndf=6)
steel = ops.uniaxialMaterial.Steel02(fy=420e6, E=200e9, b=0.01)
...
The bridge holds declared state. apeSees.build() returns a
:class:BuiltModel (immutable) that emitters consume.
Parameters¶
fem
The FEM snapshot the bridge is built against.
default_orientation
Orientation field substituted on any
ops.geomTransf.<Type>() call where the user supplied
neither orientation= nor vecxz=. Defaults to
Cartesian() (Z-up) which matches the prevailing structural
convention. Pass an explicit None for 2D models, where
vecxz is omitted at emit time and an orientation field makes
no sense. Pass a custom orientation (e.g.
Cartesian(reference_axis=(0,1,0)) for a Y-up CAD import)
to set the model-wide default once.
Source code in src/apeGmsh/opensees/apesees.py
model ¶
domain_capture ¶
domain_capture(spec: 'DomainCaptureSpec', *, path: 'str | Path', ops: Any = None) -> 'DomainCapture'
Open a :class:DomainCapture for in-process recording.
Live entry point that resolves the supplied
:class:DomainCaptureSpec against the bridge's fem
snapshot using the bridge's ndm / ndf, then returns a
:class:DomainCapture context manager writing to path.
Per Phase 9 D8 ndm / ndf are sourced implicitly from
the bridge — the user must have called ops.model(ndm=,
ndf=) first. Use :meth:DomainCapture.from_h5 instead when
no live bridge is available (sources ndm / ndf from a
model.h5 /meta block).
Example::
ops.model(ndm=3, ndf=6)
spec = DomainCaptureSpec(opensees=ops)
spec.nodes(pg="Top", components=["displacement"])
with ops.domain_capture(spec, path="run.h5") as cap:
cap.begin_stage("gravity", kind="static")
for _ in range(n):
ops.analyze(1, 1.0)
cap.step(t=ops.getTime())
cap.end_stage()
Raises¶
RuntimeError
If ops.model(ndm=, ndf=) has not been called yet.
Source code in src/apeGmsh/opensees/apesees.py
fix ¶
fix(*, pg: str | None = None, nodes: Iterable[int | Node] | None = None, dofs: tuple[int, ...]) -> None
Apply homogeneous SP constraints (fix).
Exactly one of pg / nodes must be supplied. nodes
accepts a mix of plain integer tags and :class:Node
instances (from ops.nodes.get(...)); both are normalized
to tags. The build pipeline expands pg to a per-node
fan-out at emit time.
Source code in src/apeGmsh/opensees/apesees.py
mass ¶
mass(*, pg: str | None = None, nodes: Iterable[int | Node] | None = None, values: tuple[float, ...]) -> None
Attach lumped nodal mass.
Exactly one of pg / nodes must be supplied. nodes
accepts plain integers or :class:Node instances.
Source code in src/apeGmsh/opensees/apesees.py
analyze ¶
Build + emit + run the analysis chain via the live emitter.
Builds a :class:BuiltModel, drives a
:class:~apeGmsh.opensees.emitter.live.LiveOpsEmitter end-to-
end, then issues the analyze call. Returns the openseespy
analyze return value (0 on success).
Raises :class:BridgeError if the analysis chain is incomplete
(one or more of constraints / numberer / system / test /
algorithm / integrator / analysis is missing).
Source code in src/apeGmsh/opensees/apesees.py
tcl ¶
Emit a Tcl deck to path; optionally subprocess OpenSees.
Source code in src/apeGmsh/opensees/apesees.py
py ¶
Emit an openseespy Python deck to path; optionally run it.
Source code in src/apeGmsh/opensees/apesees.py
run ¶
Drive an in-process LiveOpsEmitter through the full deck.
This emits every primitive but does NOT call analyze —
that is the user's call (or :meth:analyze's). Useful when
the user wants to declare a model, populate openseespy state,
and then run their own analysis driver.
Source code in src/apeGmsh/opensees/apesees.py
h5 ¶
h5(path: str, *, model_name: str | None = None, cuts: 'Sequence[SectionCutDef]' = (), sweeps: 'Sequence[SectionSweepDef]' = ()) -> None
Emit a model-definition HDF5 archive at path.
Phase 8.5 composes the file in two layers:
- The broker (
self._fem) writes/meta+ the neutral zone (/nodes,/elements/{type},/physical_groups,/labels,/constraints/{kind},/loads/{kind}/{pattern},/masses). Broker writers live in :mod:apeGmsh.mesh._femdata_h5_io. - The bridge (an :class:
H5Emitterdriven through the :class:BuiltModel) appends/opensees/...enrichment. - apeGmsh.cuts v4: if
cutsand / orsweepsare supplied, they're persisted under/opensees/cuts/and/opensees/sweeps/(writer in :mod:apeGmsh.cuts._h5_io).
If self._fem does not expose a real :class:FEMData
surface (e.g. integration tests using a hand-rolled stub),
the broker step is skipped: the file ends up with the
bridge's own /meta plus /opensees/..., but no neutral
zone. Real callers always get the full file shape.
Parameters¶
path
File path to write the HDF5 archive to.
model_name
Optional human-readable name written to /meta/model_name.
Defaults to the path's stem.
cuts
Optional sequence of :class:apeGmsh.cuts.SectionCutDef
to persist under /opensees/cuts/cut_{i}. Each cut
travels with the model definition; the viewer auto-loads
them on the next results.viewer(model_h5=path).
sweeps
Optional sequence of :class:apeGmsh.cuts.SectionSweepDef
to persist under /opensees/sweeps/sweep_{i}. Each
sweep group carries its own cuts/ sub-group in sweep
order (see apeGmsh/cuts/ARCHITECTURE.md "## v4").
Source code in src/apeGmsh/opensees/apesees.py
703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 | |
register ¶
tag_for ¶
build ¶
Freeze the declarations into a :class:BuiltModel.
Source code in src/apeGmsh/opensees/apesees.py
Orientation helpers¶
Used as the orientation= argument on the typed geom_transf
primitives (Linear / PDelta / Corotational).
apeGmsh.opensees.Cartesian ¶
Constant Cartesian triad. reference_axis defines e3;
e1 and e2 are picked deterministically from the global
axis least aligned with e3.
The default reference_axis = (0, 0, 1) reproduces the legacy
"Z up" convention: horizontal beams get vecxz = (0, 0, 1) and
vertical columns fall back to vecxz = (-1, 0, 0) (the sign
follows the tangent direction; see :ref:shoebuckle).
Parameters¶
reference_axis : 3-vector
The axis e3. Need not be unit length.
Example¶
::
from apeGmsh.opensees import Cartesian
# Standard structural convention: Z is vertical
orientation = Cartesian() # reference_axis = +Z
# Mechanical CAD convention: Y is vertical
orientation = Cartesian(reference_axis=(0, 1, 0))
Source code in src/apeGmsh/opensees/_orientation.py
apeGmsh.opensees.Cylindrical ¶
Cylindrical orientation about an axis of revolution.
At a point p:
e1= radial outward, perpendicular toaxise2= circumferential,axis × e1e3=axis(constant) ← reference axis for the rule
Use this for ring beams, tank stiffeners, and any beam set whose natural "vertical" is the axis of revolution.
Parameters¶
origin : 3-vector Any point on the axis of revolution. axis : 3-vector Direction of the axis of revolution. Need not be unit length.
Example¶
::
from apeGmsh.opensees import Cylindrical
# Vertical tank
orientation = Cylindrical(origin=(0, 0, 0), axis=(0, 0, 1))
Source code in src/apeGmsh/opensees/_orientation.py
apeGmsh.opensees.Spherical ¶
Spherical orientation about a fixed origin. Polar axis is global +Z.
At a point p (with r = |p − origin|):
e1=e_θ— along the meridian (south at the equator)e2=e_φ— along the parallel (east)e3=e_r— outward radial ← reference axis for the rule
Useful for fan vaults, geodesic ribs, and any beam network with
natural radial structure. Note: for a planar curved beam (e.g.
a vertical-plane arch), :class:Cartesian with reference_axis
in the plane gives the same answer with less ceremony.
Parameters¶
origin : 3-vector Centre of the sphere.
Example¶
::
from apeGmsh.opensees import Spherical
orientation = Spherical(origin=(0, 0, 0))
Source code in src/apeGmsh/opensees/_orientation.py
Recorders¶
Standalone recorder declaration helper. Recorder declarations live on
ops.recorder.* in the apeSees bridge.
apeGmsh.opensees.recorder ¶
Typed recorder primitives.
Phase 3B ships three concrete recorder classes mirroring the OpenSees
recorder command:
- :class:
Node—recorder Node ... - :class:
Element—recorder Element ... - :class:
MPCO—recorder mpco ...(HDF5)
Each class is a @dataclass(frozen=True, kw_only=True, slots=True);
the matching :class:apeGmsh.opensees._internal.ns.recorder._RecorderNS
methods take the same kwargs and call self._bridge._register(Cls(...)).
Recorders never compose other primitives (dependencies() returns
()). They are leaves in the dependency graph; the build pipeline
emits them after the topology + analysis chain so that each recorder
command sees fully-allocated node and element tags.
The pg= form (physical-group fan-out into node/element tags) is
declared on the type signatures for forward-compatibility but
:meth:_emit raises :class:NotImplementedError until the Phase 4
build pipeline materializes the FEM-snapshot lookup. Recorders
constructed today supply explicit nodes= / elements= lists.
OpenSees command shapes¶
::
recorder Node -file fname [-time] [-dT dT] [-node n...]
-dof d... response
recorder Element -file fname [-time] [-dT dT] [-ele e...]
response_tokens...
recorder mpco fname.mpco [-N nodal_responses...]
[-E elem_responses...]
[-T dt $dt | -T nsteps $n]
The -time flag (when time_format="dt") instructs OpenSees to
include the simulation-time column in the output file. The default
time_format="step" writes only the response columns.
Node
dataclass
¶
Node(*, file: str, response: str, nodes: tuple[int, ...] | None = None, pg: str | None = None, dofs: tuple[int, ...], dT: float | None = None, time_format: str = 'step')
Bases: Recorder
recorder Node — record nodal response history.
OpenSees command::
recorder Node -file fname [-time] [-dT dT]
(-node n1 n2 ... | -nodeRange first last)
-dof d1 d2 ... response
Exactly one of nodes= (explicit list) or pg= (physical-group
label) must be supplied; the build pipeline (Phase 4) materializes
the pg= form into a concrete node-tag list. Until then, the
pg= path raises :class:NotImplementedError from :meth:_emit.
Parameters¶
file
Output file path.
response
OpenSees response token ("disp", "vel", "accel",
"reaction", "unbalance", ...).
nodes
Explicit tuple of node tags. Mutually exclusive with pg.
pg
Physical-group label whose nodes the recorder targets.
Mutually exclusive with nodes. Build-pipeline only.
dofs
DOF indices (1-based, OpenSees convention). At least one
required.
dT
Optional cadence — record only every dT simulation
seconds. None records every step.
time_format
"step" (default) writes only response columns;
"dt" emits the OpenSees -time flag, prepending the
simulation-time column.
Element
dataclass
¶
Element(*, file: str, response: tuple[str, ...], elements: tuple[int, ...] | None = None, pg: str | None = None, dT: float | None = None, time_format: str = 'step')
Bases: Recorder
recorder Element — record element-level response history.
OpenSees command::
recorder Element -file fname [-time] [-dT dT]
(-ele e1 e2 ... | -eleRange first last)
response_tokens...
response is a tuple of OpenSees response tokens — the simplest
case is ("globalForce",) or ("stresses",); element types
that nest responses (e.g. fiber sections) take multi-token forms
such as ("section", "1", "force").
Exactly one of elements= (explicit list) or pg= (physical-
group label) must be supplied; pg= is deferred to Phase 4.
Parameters¶
file
Output file path.
response
Tuple of OpenSees response tokens (at least one).
elements
Explicit tuple of element tags. Mutually exclusive with pg.
pg
Physical-group label whose elements the recorder targets.
Mutually exclusive with elements. Build-pipeline only.
dT
Optional cadence — record only every dT simulation
seconds. None records every step.
time_format
"step" (default) writes only response columns;
"dt" emits the OpenSees -time flag.
MPCO
dataclass
¶
MPCO(*, file: str, nodal_responses: tuple[str, ...] = (), elem_responses: tuple[str, ...] = (), dT: float | None = None, nsteps: int | None = None)
Bases: Recorder
recorder mpco — write a single HDF5 .mpco file.
OpenSees command::
recorder mpco fname.mpco [-N nodal_responses...]
[-E elem_responses...]
[-T dt $dt | -T nsteps $n]
The MPCO recorder captures the full response tensor for each
requested token (no per-DOF selection at write time); STKO /
apeGmsh consumers filter at read time. At least one of
nodal_responses or elem_responses must be non-empty.
Cadence is selected by exactly one of dT (seconds) or
nsteps (analysis steps). Supplying both raises ValueError;
supplying neither records every analysis step.
Parameters¶
file
Output .mpco (HDF5) file path.
nodal_responses
Tuple of MPCO -N tokens (e.g. ("displacement",
"reactionForce")). Empty tuple means no nodal recording.
elem_responses
Tuple of MPCO -E tokens (e.g. ("stresses",
"section.fiber.stress")). Empty tuple means no element
recording.
dT
Optional time-based cadence (seconds). Mutually exclusive
with nsteps.
nsteps
Optional step-based cadence (every N analysis steps).
Mutually exclusive with dT.
RecorderRecord
dataclass
¶
RecorderRecord(*, category: str, components: tuple[str, ...] = (), raw: tuple[str, ...] = (), pg: tuple[str, ...] = (), label: tuple[str, ...] = (), selection: tuple[str, ...] = (), ids: tuple[int, ...] | None = None, dt: float | None = None, n_steps: int | None = None, name: str | None = None, n_modes: int | None = None, element_class_name: str | None = None)
One category-level declaration entry within a RecorderDeclaration.
Stores already-expanded canonical components (or raw OpenSees
tokens via the raw= escape hatch). Shorthand expansion
("displacement" → displacement_x/y/z) happens at
construction in the namespace method (Phase 9 commit 3), not in
this dataclass — by the time a record is built, components are
fully expanded.
Parameters¶
category
One of :data:ALL_RECORDER_CATEGORIES.
components
Tuple of canonical component names. Validated against
:data:_CATEGORY_CANONICALS per category, plus indexed
canonicals (state_variable_<n>, fiber_stress_<n>,
spring_force_<n>) recognized via :func:is_canonical.
raw
Escape hatch for non-canonical OpenSees tokens (e.g. a
custom recorder response). Bypasses canonical validation.
pg / label / selection / ids
Target selectors. ids= is mutually exclusive with the
named selectors. Resolution against FEMData happens at
emit time (commit 3).
dt / n_steps
Recording cadence. At most one may be set; both None
records every step.
name
Optional user-supplied name for this record; auto-generated
when None.
n_modes
Required for category="modal"; rejected for other
categories.
element_class_name
Optional OpenSees C++ class name override for element-level
records. Used by the .out transcoder to disambiguate
elements that share a flat response size (e.g. tri31 vs
SSPquad). Carried from the legacy Recorders.elements
contract.
RecorderDeclaration
dataclass
¶
RecorderDeclaration(*, records: tuple[RecorderRecord, ...], name: str = 'default', ndm: int = 3, ndf: int = 6, file_root: str = '.')
Bases: Recorder
A bundle of recorder records, registered as a single Primitive.
Captures the bridge's ndm and ndf at construction time
(Phase 9 D8 — implicit source-of-truth binding). Drives the
file-emit path via :func:emit_recorder_spec in
:mod:apeGmsh.opensees._internal.build.
Parameters¶
records
Tuple of :class:RecorderRecord entries. Each is one
category-level declaration; emit fans them out into one or
more concrete OpenSees recorder commands.
name
Identifier for this declaration (defaults to "default").
Multiple named declarations can coexist on one bridge.
ndm, ndf
Snapshot of the bridge's ndm/ndf at construction time.
Used downstream for shorthand expansion and validation. The
bridge passes these in (Phase 9 D8 — user never repeats
ops.model(ndm=, ndf=) values).
file_root
Directory prefix for emitted .out files. Each record fans
out to <file_root>/<decl.name>__<record_name>__<token>.out.
Defaults to "." (current working directory).
Numberer¶
apeGmsh.mesh._numberer.Numberer ¶
Renumbers a FEM mesh for solver consumption.
Parameters¶
fem_data : dict
Output of Mesh.get_fem_data(). Must contain:
node_tags, node_coords, elem_tags,
connectivity, used_tags.
Source code in src/apeGmsh/mesh/_numberer.py
renumber ¶
Produce a solver-ready mesh with contiguous IDs.
Parameters¶
method : "simple" or "rcm"
"simple" — preserves relative order, just makes IDs
contiguous. Fast, no optimisation.
``"rcm"`` — Reverse Cuthill-McKee bandwidth minimisation.
Reorders nodes so that the assembled stiffness matrix has
minimal bandwidth. Recommended for direct solvers.
int
Starting ID (default 1 = Fortran/OpenSees convention; use 0 for C/Python convention).
bool
If True (default), only include nodes that appear in at least one element (skip orphan nodes). Set False to include all nodes from the mesh.
Returns¶
NumberedMesh
Source code in src/apeGmsh/mesh/_numberer.py
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 383 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 | |
apeGmsh.mesh._numberer.NumberedMesh
dataclass
¶
NumberedMesh(node_ids: ndarray, node_coords: ndarray, elem_ids: ndarray, connectivity: ndarray, n_nodes: int = 0, n_elems: int = 0, bandwidth: int = 0, method: str = 'simple', gmsh_to_solver_node: dict[int, int] = dict(), solver_to_gmsh_node: dict[int, int] = dict(), gmsh_to_solver_elem: dict[int, int] = dict(), solver_to_gmsh_elem: dict[int, int] = dict())
Solver-ready mesh with contiguous IDs and bidirectional maps.
All IDs are 1-based (the standard in structural FEM solvers
like OpenSees, Abaqus, SAP2000). Set base=0 in
:meth:Numberer.renumber for 0-based if your solver needs it.
Attributes¶
node_ids : ndarray(N,)
New contiguous node IDs.
node_coords : ndarray(N, 3)
Nodal coordinates, same order as node_ids.
elem_ids : ndarray(E,)
New contiguous element IDs.
connectivity : ndarray(E, npe)
Element connectivity in terms of new node IDs.
n_nodes : int
n_elems : int
bandwidth : int
Semi-bandwidth of the resulting adjacency.
method : str
Numbering method used ("simple" or "rcm").
Maps ~~~~ gmsh_to_solver_node : dict[int, int] Gmsh node tag -> solver node ID. solver_to_gmsh_node : dict[int, int] Solver node ID -> Gmsh node tag. gmsh_to_solver_elem : dict[int, int] Gmsh element tag -> solver element ID. solver_to_gmsh_elem : dict[int, int] Solver element ID -> Gmsh element tag.