adapy covers the whole finite element loop: model → mesh → solver deck → run → results →
post-processing and visualisation. This page follows that loop through the code.
flowchart TB
subgraph design["Design model"]
PART["Part / Assembly<br/>beams · plates · shapes"]
CONC["ConceptFEM<br/>concept loads & constraints"]
end
subgraph mesh["Meshing"]
GMSH["fem/meshing<br/>GmshSession"]
end
subgraph femodel["FE model"]
FEM["FEM on Part.fem<br/>nodes · elements · sets · sections<br/>steps · loads · bcs · constraints"]
STORE["api/mesh · MeshArrays<br/>(packed numpy store)"]
end
subgraph solve["Solver"]
DECK["deck writer<br/>to_fem_*"]
RUN["execute_fem<br/>run_*"]
end
subgraph post["Results"]
READ["result readers<br/>read_*_file"]
RES["FEAResult<br/>Mesh + field data"]
BAKE["artefact bake<br/>FEAStreamReader → bake_artefacts"]
end
DECKIN[(".inp / .fem / .med")]
RESF[(".rmed / .frd / .SIN / .SIF / .odb / .radanim")]
VIEW(["viewer · GLB · VTU"])
PART --> GMSH --> FEM
CONC --> FEM
DECKIN -- "from_fem" --> FEM
FEM --- STORE
FEM --> DECK --> RUN --> RESF
RESF --> READ --> RES --> VIEW
RESF --> BAKE --> VIEW
DECKIN --> BAKE
click PART href "architecture/core_model/" "The object model"
click CONC href "https://github.com/Krande/adapy/tree/main/src/ada/fem/concept" "ConceptFEM: concept-level loads and constraints"
click GMSH href "https://github.com/Krande/adapy/tree/main/src/ada/fem/meshing" "GmshSession and partitioning"
click FEM href "https://github.com/Krande/adapy/blob/main/src/ada/fem/base.py" "class FEM"
click STORE href "https://github.com/Krande/adapy/tree/main/src/ada/api/mesh" "MeshArrays, ArrayNodes/ArrayElements, proxies"
click DECK href "https://github.com/Krande/adapy/blob/main/src/ada/fem/formats/general.py" "write_to_fem and the per-solver dispatch"
click RUN href "https://github.com/Krande/adapy/blob/main/src/ada/fem/formats/execute.py" "execute_fem"
click READ href "https://github.com/Krande/adapy/blob/main/src/ada/fem/formats/postprocess.py" "postprocess → FEAResult"
click RES href "https://github.com/Krande/adapy/blob/main/src/ada/fem/results/common.py" "FEAResult, Mesh, FemNodes, ElementBlock"
click BAKE href "https://github.com/Krande/adapy/tree/main/src/ada/fem/results/artefacts" "Streaming viewer bake"
Concept-level constraints are converted to FE constraints by
fem/concept/to_fem.add_constraint_concepts_to_fem. Concept loads are carried to and from
Genie XML (cadit/gxml).
sequenceDiagram
autonumber
participant P as Part.to_fem_obj
participant G as GmshSession
participant F as FEM
P->>G: open session, add_obj(beams, plates, shapes)
P->>G: partition_plates() · partition_beams()
P->>G: mesh(size, use_quads, use_hex)
G->>F: get_fem() → nodes, elements, sections
P->>F: add point masses
P->>F: remove_standalones()
P->>F: add_constraint_concepts_to_fem()
P->>F: check_conformal_mesh()
With Config().meshing_array_backed on (the default; opt out with
ADA_MESHING_ARRAY_BACKED=false), a FEM's nodes and elements are thin facades over one
packed numpy store, MeshArrays. Node / Elem objects are minted on demand as lazy
proxies.
classDiagram
direction LR
class FEM {
nodes: ArrayNodes
elements: ArrayElements
}
class ArrayNodes {
store: MeshArrays
from_id(id) NodeProxy
renumber(map) · move(…)
to_fem_nodes() FemNodes
}
class ArrayElements {
store: MeshArrays
_overflow: Mass / Spring / Connector
renumber(map)
to_elem_blocks() list~ElementBlock~
}
class MeshArrays {
coords: float64[n,3]
node_ids: int64[n]
blocks: dict~ctype, ElemArrayBlock~
id2idx (lazy)
adjacency: CSRAdjacency (lazy)
proxy caches (weak)
}
class ElemArrayBlock {
ctype
conn: int32[m,k] row indices
el_ids: int64[m]
fem_secs · elsets · formulations
ecc · hinge · metadata (sparse)
}
class NodeProxy {
store, row
id → node_ids[row]
p → coords[row]
}
class ElemProxy {
store, ctype, row
}
FEM --> ArrayNodes
FEM --> ArrayElements
ArrayNodes --> MeshArrays
ArrayElements --> MeshArrays
MeshArrays "1" o-- "*" ElemArrayBlock
ArrayNodes ..> NodeProxy : mints
ArrayElements ..> ElemProxy : mints
Connectivity holds row indices, not node ids. Renumbering nodes only rewrites
node_ids. Handing the mesh to the results side (FEM.to_mesh()) is zero-copy:
to_elem_blocks() passes conn and el_ids through as
ElementBlock(..., node_refs_are_indices=True).
Node→element adjacency is a CSR incidence (CSRAdjacency), built lazily, instead of
per-node reference lists.
The Sesam, Abaqus and Code_Aster readers fill a MeshArrays directly from the deck
(Sesam can stream the file). Other FEMs are converted with to_array_backed(fem).
GmshSession.get_fem() still produces object containers, which
fem/formats/utils.convert_part_objects converts.
fem/concat.py merges multi-part models at store level, offsetting ids and re-keying
sets, sections, boundary conditions and masses.
sequenceDiagram
autonumber
participant U as User
participant A as Assembly.to_fem
participant W as general.write_to_fem
participant X as execute.execute_fem
participant S as Solver
participant P as postprocess.postprocess
participant R as FEAResult
U->>A: to_fem(name, fem_format, execute=True)
A->>W: merge parts (except Abaqus) → Setup.default_pre_processor
W-->>A: analysis_dir/deck
A->>X: Setup.default_executor
X->>S: run solver, write run_log.txt
S-->>X: result file
A->>P: Setup.default_post_processor
P-->>R: FEAResult
R-->>U: to_gltf · to_vtu · show · get_eig_summary
ada.from_fem_res(path) starts from an existing result file at the postprocess step.
Interoperability lists each solver's functions. Abaqus
runs can wait for FlexNet licence tokens (abaqus/licensing.abaqus_license_slot, opt-in via
ADA_ABAQUS_LICENSE_WAIT_S).
FEA bundles for the verification report (bake_fea_bundles, collect_fea_bundles, restore_fea_bundles)
Result readers use the ids from the source file. node_refs_are_indices=False (the
default) means node_refs holds node ids, which consumers map to rows. Only
FEM.to_mesh() produces row-index blocks.
Large results are not loaded into the browser as one GLB. The bake
(fem/results/artefacts) streams a result source into a set of files that the viewer fetches
piece by piece:
Fields are written one step at a time (FieldBlobWriter, ElementFieldBlobWriter), so
peak memory does not grow with the number of steps. Each blob has a fixed JSON header and
then a contiguous [steps × entities × components] array that the viewer range-fetches
per step.
FEAResultStreamAdapter makes any eager reader that returns a FEAResult fit the protocol.
beam_solids.py / beam_compact.py extrude beam elements to solids with their real
section profiles. The compact format stores one instance per beam and lets the viewer
expand them.
Extras: mode_normalization (mode-shape scale factors), step_subset
(restrict_to_steps), history (time-history records), posters (offscreen PNG posters
via pygfx, bake_with_posters).
On the platform, the fea_artefacts job runs bake_fea_artefacts_from_source and writes to
_derived/<source>.fea/. The REST routes in routes/fea.py serve the artefacts and the
manifest (see Viewer platform).
flowchart TB
T["verification/tasks.py<br/>@task DAG"] --> D["design"] --> ME["mesh<br/>(geom repr × order × hex/quad × reduced int.)"]
ME --> RE["run_eig<br/>(fan-out over solvers)"]
RE --> PP["postprocess"]
PP --> OUT["eig_tables · modal_tables · freq_plot · fea_outputs"]
CACHE[(".cache/ · .cache-plate/<br/>Abaqus & Sesam replays")] --> RE
OUT --> PD["paradoc build<br/>(report/ markdown)"]
PD --> WEB["docs/_static/fea-report/<br/>static web bundle"]
PD --> FILES["fea-report.pdf / .docx / .odt<br/>docs/_static/fea-report-files/"]
click T href "https://github.com/Krande/adapy/blob/main/verification/tasks.py" "The report's task DAG"
click CACHE href "https://github.com/Krande/adapy/tree/main/verification/.cache" "Committed Abaqus / Sesam results"
click PD href "https://github.com/Krande/adapy/blob/main/verification/paradoc.toml" "paradoc build profiles"
click WEB href "fea/verification/" "Open the report page"
Code_Aster and CalculiX run on every build. Abaqus and Sesam need licences, so their results
replay from committed caches (_CACHE_ONLY_SOLVERS). fea_outputs bakes the mode-shape
artefact bundles that the report's interactive 3D views load.