civilpy.structural.stm_topology package
Strut-and-tie topology optimization: turns a D-region description into an optimized truss (SIMP density optimization on a structured quad mesh, truss extraction, member design, and cost estimation).
Submodules
civilpy.structural.stm_topology.cost module
Reinforcement sizing, node checks, material take-off, and an ODOT cost estimate for a solved strut-and-tie truss.
Forces come from the solved StrutAndTieModel (tension +, kips); ties are sized by inverting the AASHTO
5.8.2.4 tie resistance, struts/nodes are checked with 5.8.2.5. Lengths are in
feet, areas in in^2; the cost model uses ODOT-style unit prices.
- class civilpy.structural.stm_topology.cost.CostEstimate(concrete_cy: 'float', steel_lb: 'float', concrete_cost: 'float', steel_cost: 'float', total: 'float', unit_prices: 'dict')[source]
Bases:
object
- class civilpy.structural.stm_topology.cost.DesignReport(ties: 'list' = <factory>, node_checks: 'list' = <factory>, concrete_volume_cf: 'float' = 0.0, steel_volume_cf: 'float' = 0.0, cost: 'CostEstimate | None' = None)[source]
Bases:
object- cost: CostEstimate | None = None
- class civilpy.structural.stm_topology.cost.TieDesign(member: 'tuple', force: 'float', length: 'float', a_st_required: 'float', bar_size: 'int', bar_count: 'int', a_st_provided: 'float', check: 'object')[source]
Bases:
object
- civilpy.structural.stm_topology.cost.check_nodes(model, problem) list[source]
Bearing/nodal-zone check (AASHTO 5.8.2.5) at each supported and loaded node where a bearing width is given. Classifies the node CCC/CCT/CTT from the solved member signs meeting it.
- civilpy.structural.stm_topology.cost.design_report(model, problem, density_result, *, threshold: float = 0.3, **price_kwargs) DesignReport[source]
- civilpy.structural.stm_topology.cost.estimate_cost(concrete_cf, steel_cf, *, price_concrete_cy: float | None = None, price_steel_lb: float | None = None, prices=None) CostEstimate[source]
ODOT-style cost: concrete by the cubic yard, reinforcing by the pound.
Unit prices come from (highest priority first) the explicit
price_*arguments, then thepricesmapping, then the registered price provider, thenDEFAULT_PRICES(seeset_price_provider()).
- civilpy.structural.stm_topology.cost.material_takeoff(density_result, problem, ties, *, threshold: float = 0.3)[source]
Concrete volume from the optimized density field and steel volume from the sized ties (cubic feet).
- civilpy.structural.stm_topology.cost.resolve_prices(prices=None) dict[source]
Resolve unit prices in priority order: explicit
pricesmapping > the registered provider >DEFAULT_PRICES.
- civilpy.structural.stm_topology.cost.set_price_provider(provider)[source]
Register a zero-argument callable that returns a unit-price mapping with keys
concrete_cy($/cy) andsteel_lb($/lb) — and optionallysource. It is consulted whenever a take-off is requested without an explicitpricesargument. civilpy falls back toDEFAULT_PRICESif the provider is unset, returns nothing, or raises. Returns the previous provider (passNoneto clear).
civilpy.structural.stm_topology.design module
Pier-cap dimension design driven by girder reactions.
The everyday workflow this targets: an engineer pulls factored girder reactions and their bearing locations out of a line-girder run (AASHTOWare BrR, Merlin DASH, …), and wants the most economical cap depth that still produces a valid, code-checkable strut-and-tie model for that specific layout.
optimize_pier_cap() sweeps the cap depth, runs the topology-optimization
pipeline (optimize_to_stm()) at
each depth, and returns the shallowest depth — i.e. the least concrete, which
dominates cost — that satisfies the design gates:
the strut-and-tie model is a complete, solved load path (every girder and column present, global equilibrium satisfied);
the flattest primary strut meets the AASHTO 5.8.2.5 / ACI 318 Ch. 23 nodal angle limit (default 25 deg), equivalently the deep-beam
a/dlimit; andevery nodal zone has capacity.
The cap length is fixed by the girder layout plus an edge distance, and the width by the columns/bearings, so depth is the free dimension.
- class civilpy.structural.stm_topology.design.DepthCandidate(depth: float, cost: float, concrete_cost: float, steel_lb: float, strut_angle: float, node_ratio: float, max_tie: float, complete: bool, feasible: bool, result: object)[source]
Bases:
objectOne depth evaluated in the sweep.
- class civilpy.structural.stm_topology.design.PierCapDesign(optimal: DepthCandidate | None, candidates: list[DepthCandidate] = <factory>, span: float = 0.0, thickness: float = 0.0, min_strut_angle: float = 25.0)[source]
Bases:
objectResult of
optimize_pier_cap().- candidates: list[DepthCandidate]
- property model
- optimal: DepthCandidate | None
- plot_tradeoff(ax=None)[source]
Cost and strut angle vs depth, with the infeasible band shaded and the optimum marked.
- property report
- civilpy.structural.stm_topology.design.governing_strut_angle(load_xs, column_xs, depth) float[source]
Inclination (deg) of the flattest primary strut: the largest shear span
a(girder-to-nearest-column distance) withtheta = atan(depth / a).This geometric
a/dmeasure is robust and monotonic in depth, unlike the angle read off whichever discrete truss the extractor produced at a given depth, so it is what gates the sweep.
- civilpy.structural.stm_topology.design.optimize_pier_cap(loads, load_xs, column_xs, *, f_c: float = 5.0, f_y: float = 60.0, thickness: float = 4.0, depth_bounds: tuple[float, float] = (4.0, 12.0), n_depths: int = 9, edge: float = 2.5, vol_frac: float = 0.3, nelx: int = 72, column_bearing: float = 2.0, load_bearing: float = 1.0, min_strut_angle: float = 25.0, price_concrete_cy: float | None = None, price_steel_lb: float | None = None, prices=None, pin_column: int | None = None) PierCapDesign[source]
Find the most economical pier-cap depth for a set of girder reactions.
- Parameters:
loads – Factored girder reactions (kip, magnitudes) and their bearing locations (ft) along the cap — typically straight from a BrR / DASH run.
load_xs – Factored girder reactions (kip, magnitudes) and their bearing locations (ft) along the cap — typically straight from a BrR / DASH run.
column_xs – Column centreline locations (ft).
thickness – Out-of-plane cap width
b(ft).depth_bounds – Range and sampling of cap depths to evaluate.
n_depths – Range and sampling of cap depths to evaluate.
edge – Cap overhang beyond the outermost girder/column (ft); sets the length.
column_bearing – In-plane bearing widths (ft) for the nodal-zone checks.
load_bearing – In-plane bearing widths (ft) for the nodal-zone checks.
min_strut_angle – AASHTO 5.8.2.5 nodal angle limit (deg); raise it to force a stockier, more clearly deep-beam cap.
pin_column – Index of the laterally-restrained (pinned) column; defaults to the one nearest mid-length, the rest acting as vertical rollers.
- Return type:
PierCapDesignwhoseoptimalis the shallowest feasible depth.
civilpy.structural.stm_topology.extract module
Truss extraction: turn a SIMP density field into a strut-and-tie truss.
Pipeline: threshold the density to a binary load path, thin it to a one-pixel
skeleton (Zhang–Suen), walk the skeleton into a node/edge graph, force a node at
every support and load, simplify each traced path to straight members
(Douglas–Peucker), collapse near-coincident nodes, and emit a
StrutAndTieModel with the supports and
loads attached. Pure NumPy — the thinning is hand-rolled so no scikit-image
dependency is required.
- civilpy.structural.stm_topology.extract.extract_truss(result, *, threshold: float = 0.3, merge: float | None = None) StrutAndTieModel[source]
Extract a
StrutAndTieModelfrom aDensityResult.- Parameters:
threshold – Density above which a cell is part of the load path.
merge – Node-merge tolerance in pixels (default scales with the mesh). Each traced skeleton path becomes a single straight member between its real graph nodes, so truss joints land only at supports, loads, and genuine junctions.
- civilpy.structural.stm_topology.extract.is_stable(model, tol: float = 1e-08) bool[source]
True when the truss has no mechanism — its free-DOF stiffness matrix (unit member stiffness, supports applied) is non-singular.
This is the honest stability test the pipeline needs: the member/reaction count (
StrutAndTieModel.degree_of_indeterminacy()) is necessary but not sufficient, andnp.linalg.solvecan return garbage for a near- mechanism instead of raising. We compare the smallest singular value of the free-free stiffness block against the largest.
- civilpy.structural.stm_topology.extract.layout_optimize_truss(model, density_result, *, keep: float = 0.02, min_density: float = 0.12, search: float | None = None, betweenness: float | None = None, cluster_tol: float | None = None, symmetric: bool = True, boundary_chords: bool = True, boundary_discount: float = 0.3, max_crossing_iter: int = 3)[source]
Extract a strut-and-tie model by LP plastic layout optimization.
Uses
model’s joints (cleaned: clustered, and mirrored when the problem is symmetric) as the node set, lays a density-filtered ground structure between them, and solves a single LP for the minimum-volume equilibrium load path. Returns a newStrutAndTieModelwith member forces and reactions already populated, orNoneif the program is infeasible (caller falls back to the FSD refinement).Two geometric rules give an ACI 318-detailable result:
boundary_chordsadds a continuous chord along each loaded edge (adjacent supports along the bottom, adjacent loads along the top) and keeps it even when lightly loaded — the conventional flexural tie / top compression chord an engineer expects for detailing and crack control.intersecting members are never left un-noded: after each solve, a joint is inserted at every crossing of two load-carrying members and the layout is re-optimized (up to
max_crossing_itertimes), so every crossing is a real nodal zone (ACI 318 Ch. 23) rather than two members sharing a point in space.
- civilpy.structural.stm_topology.extract.refine_truss(model, density_result, *, method: str = 'lp', **kwargs)[source]
Turn a raw skeleton truss into a clean, solved strut-and-tie model.
method="lp"(default) runslayout_optimize_truss()— a single global plastic truss layout optimization that is inherently symmetric for a symmetric problem. It returns a new model.method="fsd"(or anlpfailure) falls back to the in-place ground-structure fully-stressed-design refinement_refine_fsd().Returns the model to use (possibly a new object), so callers must use the return value rather than assuming in-place mutation.
civilpy.structural.stm_topology.mesh module
Structured-quad meshing for the topology optimizer.
A fixed grid of square Q4 plane-stress elements covers the region’s bounding
box; elements whose centroid falls outside the boundary polygon (or inside a
void) are passive empty, elements inside a solid keep-in polygon are
passive full, and the rest are active (optimized). This “fixed-grid FEM”
handles arbitrary polygons while keeping the regular grid that makes the density
filter and skeletonization trivial — the deliberate trade chosen in
docs/Rhino Design Philosophy.md (structured quads, not a body-fitted mesh).
Conventions follow the classic top88 topology-optimization code so its
element stiffness drops straight into simp:
grids are
(nely, nelx)with row 0 at the top;nodes are numbered column-major,
n = (nely+1)*col + row(node 0 top-left);element
(row, col)has linear indexel = row + nely*col(Fortran order), soactive.ravel(order="F")lines up withedof_matrix()rows.
- class civilpy.structural.stm_topology.mesh.GroundMesh(problem, nelx: int = 120)[source]
Bases:
objectA structured grid of unit-topology square elements over a region.
civilpy.structural.stm_topology.pipeline module
End-to-end strut-and-tie solver pipeline: region → mesh → SIMP → truss →
solve → design report (Phases 1–5 of docs/StrutAndTieSolver.md).
optimize_to_stm is the one call the notebooks (manual or Rhino) use. It is
also reachable as DRegionProblem.solve().
- class civilpy.structural.stm_topology.pipeline.STMResult(problem: object, density: DensityResult, model: object, forces: dict, report: DesignReport, merge_used: float, stable: bool)[source]
Bases:
objectEverything the pipeline produces, ready for review or write-back.
- density: DensityResult
- property mesh
- report: DesignReport
- civilpy.structural.stm_topology.pipeline.optimize_to_stm(problem, *, nelx: int = 120, threshold: float = 0.3, merge: float | None = None, penal: float = 3.0, rmin: float | None = None, max_iter: int = 120, fully_stressed: bool = True, verbose: bool = False, **price_kwargs) STMResult[source]
Run the full pipeline and return an
STMResult.Extraction skeletonizes the SIMP field for node positions, then solves an LP plastic truss layout optimization for the discrete load path; the resulting model carries its own member forces and reactions (an equilibrium load path, so no separate solve is needed).
civilpy.structural.stm_topology.problem module
The D-region optimization problem — the data contract between the front end
(manual Python or a tagged Rhino .3dm) and the topology-optimization
pipeline.
The same object drives both authoring workflows described in
docs/Rhino Design Philosophy.md: geometry carries what is spatial, tags
carry what is scalar. A DRegionProblem is the boundary polygon of the
concrete region plus its thickness/material and the supports and loads, and it is
consumed by civilpy.structural.stm_topology.simp (the SIMP optimizer) and
extract (truss extraction).
Units follow the Rhino contract: lengths (boundary, thickness, bearing)
in feet, forces in kips, f_c/E in ksi.
- class civilpy.structural.stm_topology.problem.DRegionProblem(boundary: list[tuple[float, float]], thickness: float, material: ~civilpy.structural.stm_topology.problem.Material = <factory>, supports: list[~civilpy.structural.stm_topology.problem.Support] = <factory>, loads: list[~civilpy.structural.stm_topology.problem.Load] = <factory>, voids: list[list[tuple[float, float]]] = <factory>, solids: list[list[tuple[float, float]]] = <factory>, vol_frac: float = 0.3)[source]
Bases:
objectA concrete D-region to be filled with an optimized strut-and-tie truss.
- Parameters:
boundary (list[tuple[float, float]]) – Outer polygon as
[(x, y), ...]in feet (closed automatically).thickness (float) – Out-of-plane thickness
bin feet (plane-stress depth).material (civilpy.structural.stm_topology.problem.Material) –
Material.supports – Boundary conditions, shared with the drawn-truss workflow.
loads – Boundary conditions, shared with the drawn-truss workflow.
voids – Optional inner polygons:
voidsare mandatory holes (passive empty),solidsare keep-in zones (passive full).solids – Optional inner polygons:
voidsare mandatory holes (passive empty),solidsare keep-in zones (passive full).vol_frac (float) – Target fraction of the region to keep as concrete (SIMP constraint).
- classmethod from_3dm(path, **kwargs)[source]
Read a tagged Rhino
.3dmauthored with the region workflow (astm.kind=regionclosed curve plus supports and loads). Thin wrapper overcivilpy.structural.rhino_stm.problem_from_3dm()(needs the optionalrhino3dmdependency).
- classmethod rectangle(width, height, thickness, origin=(0.0, 0.0), **kwargs)[source]
A rectangular region with its lower-left corner at
origin.
- solve(**kwargs)[source]
Run the full pipeline: mesh → SIMP → extract truss → solve → check. Returns a
civilpy.structural.stm_topology.pipeline.STMResult.
- class civilpy.structural.stm_topology.problem.Load(x: float, y: float, fx: float = 0.0, fy: float = 0.0, bearing: float | None = None)[source]
Bases:
objectA factored point load (kips) applied on the region. Direction is the
(fx, fy)vector;bearingis the in-plane bearing width (feet).
- class civilpy.structural.stm_topology.problem.Material(f_c: float = 5.0, E: float | None = None, nu: float = 0.2, unit_weight: float = 0.145)[source]
Bases:
objectConcrete material for the optimization and the capacity checks.
- class civilpy.structural.stm_topology.problem.Support(x: float, y: float, fix_x: bool = True, fix_y: bool = True, bearing: float | None = None)[source]
Bases:
objectA restrained point on the region boundary (becomes an FEA boundary condition and an STM support).
bearingis the in-plane loaded width (feet) over which the reaction is spread;Nonedefaults to the mesh size at solve time.
civilpy.structural.stm_topology.simp module
SIMP topology optimization for the strut-and-tie solver.
Solid Isotropic Material with Penalization on the structured quad grid: a
linear plane-stress FEA (the top88 Q4 element) inside an optimality-criteria
loop with a density filter, driven by the region’s supports and loads. The
result is a (nely, nelx) density field — the picture of where concrete wants
to be — which extract turns into a truss.
Pure NumPy/SciPy; no scikit-fem needed because the structured grid lets us
use the closed-form element stiffness directly.
- class civilpy.structural.stm_topology.simp.DensityResult(density: ndarray, mesh: GroundMesh, compliance: float, iterations: int, history: list)[source]
Bases:
objectOutput of the SIMP optimizer.
- mesh: GroundMesh
- civilpy.structural.stm_topology.simp.element_stiffness(nu: float) ndarray[source]
8x8 plane-stress Q4 element stiffness for unit modulus and size (Andreassen et al. 2011, Efficient topology optimization in MATLAB).
- civilpy.structural.stm_topology.simp.optimize_density(mesh: GroundMesh, *, vol_frac: float = 0.3, penal: float = 3.0, rmin: float | None = None, max_iter: int = 120, move: float = 0.2, tol: float = 0.01, nu: float = 0.2, verbose: bool = False) DensityResult[source]
Run SIMP on
meshand return the optimized density field.vol_fracis the target solid fraction of the active (optimizable) region.rmin(filter radius, element units) defaults to ~3% of the width. Supports/loads come frommesh.problem.
Module contents
Topology-optimized strut-and-tie generation.
Turn a concrete D-region (a drawn rectangle or arbitrary polygon, with supports
and loads) into an optimized strut-and-tie truss via SIMP topology optimization
and skeleton extraction, then size and cost it. The method is documented
in docs/StrutAndTieSolver.md.
Typical use:
from civilpy.structural.stm_topology import DRegionProblem
p = DRegionProblem.rectangle(20, 10, thickness=2.0, vol_frac=0.35)
p.add_support(1, 0, bearing=1.0)
p.add_support(19, 0, fix_x=False, bearing=1.0)
p.add_load(10, 10, fy=-600, bearing=1.0)
result = p.solve()
print(result.summary())
result.plot()
- class civilpy.structural.stm_topology.DRegionProblem(boundary: list[tuple[float, float]], thickness: float, material: ~civilpy.structural.stm_topology.problem.Material = <factory>, supports: list[~civilpy.structural.stm_topology.problem.Support] = <factory>, loads: list[~civilpy.structural.stm_topology.problem.Load] = <factory>, voids: list[list[tuple[float, float]]] = <factory>, solids: list[list[tuple[float, float]]] = <factory>, vol_frac: float = 0.3)[source]
Bases:
objectA concrete D-region to be filled with an optimized strut-and-tie truss.
- Parameters:
boundary (list[tuple[float, float]]) – Outer polygon as
[(x, y), ...]in feet (closed automatically).thickness (float) – Out-of-plane thickness
bin feet (plane-stress depth).material (civilpy.structural.stm_topology.problem.Material) –
Material.supports – Boundary conditions, shared with the drawn-truss workflow.
loads – Boundary conditions, shared with the drawn-truss workflow.
voids – Optional inner polygons:
voidsare mandatory holes (passive empty),solidsare keep-in zones (passive full).solids – Optional inner polygons:
voidsare mandatory holes (passive empty),solidsare keep-in zones (passive full).vol_frac (float) – Target fraction of the region to keep as concrete (SIMP constraint).
- classmethod from_3dm(path, **kwargs)[source]
Read a tagged Rhino
.3dmauthored with the region workflow (astm.kind=regionclosed curve plus supports and loads). Thin wrapper overcivilpy.structural.rhino_stm.problem_from_3dm()(needs the optionalrhino3dmdependency).
- classmethod rectangle(width, height, thickness, origin=(0.0, 0.0), **kwargs)[source]
A rectangular region with its lower-left corner at
origin.
- solve(**kwargs)[source]
Run the full pipeline: mesh → SIMP → extract truss → solve → check. Returns a
civilpy.structural.stm_topology.pipeline.STMResult.
- class civilpy.structural.stm_topology.DensityResult(density: ndarray, mesh: GroundMesh, compliance: float, iterations: int, history: list)[source]
Bases:
objectOutput of the SIMP optimizer.
- mesh: GroundMesh
- class civilpy.structural.stm_topology.DepthCandidate(depth: float, cost: float, concrete_cost: float, steel_lb: float, strut_angle: float, node_ratio: float, max_tie: float, complete: bool, feasible: bool, result: object)[source]
Bases:
objectOne depth evaluated in the sweep.
- class civilpy.structural.stm_topology.GroundMesh(problem, nelx: int = 120)[source]
Bases:
objectA structured grid of unit-topology square elements over a region.
- class civilpy.structural.stm_topology.Load(x: float, y: float, fx: float = 0.0, fy: float = 0.0, bearing: float | None = None)[source]
Bases:
objectA factored point load (kips) applied on the region. Direction is the
(fx, fy)vector;bearingis the in-plane bearing width (feet).
- class civilpy.structural.stm_topology.Material(f_c: float = 5.0, E: float | None = None, nu: float = 0.2, unit_weight: float = 0.145)[source]
Bases:
objectConcrete material for the optimization and the capacity checks.
- class civilpy.structural.stm_topology.PierCapDesign(optimal: DepthCandidate | None, candidates: list[DepthCandidate] = <factory>, span: float = 0.0, thickness: float = 0.0, min_strut_angle: float = 25.0)[source]
Bases:
objectResult of
optimize_pier_cap().- candidates: list[DepthCandidate]
- property model
- optimal: DepthCandidate | None
- plot_tradeoff(ax=None)[source]
Cost and strut angle vs depth, with the infeasible band shaded and the optimum marked.
- property report
- class civilpy.structural.stm_topology.STMResult(problem: object, density: DensityResult, model: object, forces: dict, report: DesignReport, merge_used: float, stable: bool)[source]
Bases:
objectEverything the pipeline produces, ready for review or write-back.
- density: DensityResult
- property mesh
- report: DesignReport
- class civilpy.structural.stm_topology.Support(x: float, y: float, fix_x: bool = True, fix_y: bool = True, bearing: float | None = None)[source]
Bases:
objectA restrained point on the region boundary (becomes an FEA boundary condition and an STM support).
bearingis the in-plane loaded width (feet) over which the reaction is spread;Nonedefaults to the mesh size at solve time.
- civilpy.structural.stm_topology.estimate_cost(concrete_cf, steel_cf, *, price_concrete_cy: float | None = None, price_steel_lb: float | None = None, prices=None) CostEstimate[source]
ODOT-style cost: concrete by the cubic yard, reinforcing by the pound.
Unit prices come from (highest priority first) the explicit
price_*arguments, then thepricesmapping, then the registered price provider, thenDEFAULT_PRICES(seeset_price_provider()).
- civilpy.structural.stm_topology.extract_truss(result, *, threshold: float = 0.3, merge: float | None = None) StrutAndTieModel[source]
Extract a
StrutAndTieModelfrom aDensityResult.- Parameters:
threshold – Density above which a cell is part of the load path.
merge – Node-merge tolerance in pixels (default scales with the mesh). Each traced skeleton path becomes a single straight member between its real graph nodes, so truss joints land only at supports, loads, and genuine junctions.
- civilpy.structural.stm_topology.governing_strut_angle(load_xs, column_xs, depth) float[source]
Inclination (deg) of the flattest primary strut: the largest shear span
a(girder-to-nearest-column distance) withtheta = atan(depth / a).This geometric
a/dmeasure is robust and monotonic in depth, unlike the angle read off whichever discrete truss the extractor produced at a given depth, so it is what gates the sweep.
- civilpy.structural.stm_topology.is_stable(model, tol: float = 1e-08) bool[source]
True when the truss has no mechanism — its free-DOF stiffness matrix (unit member stiffness, supports applied) is non-singular.
This is the honest stability test the pipeline needs: the member/reaction count (
StrutAndTieModel.degree_of_indeterminacy()) is necessary but not sufficient, andnp.linalg.solvecan return garbage for a near- mechanism instead of raising. We compare the smallest singular value of the free-free stiffness block against the largest.
- civilpy.structural.stm_topology.layout_optimize_truss(model, density_result, *, keep: float = 0.02, min_density: float = 0.12, search: float | None = None, betweenness: float | None = None, cluster_tol: float | None = None, symmetric: bool = True, boundary_chords: bool = True, boundary_discount: float = 0.3, max_crossing_iter: int = 3)[source]
Extract a strut-and-tie model by LP plastic layout optimization.
Uses
model’s joints (cleaned: clustered, and mirrored when the problem is symmetric) as the node set, lays a density-filtered ground structure between them, and solves a single LP for the minimum-volume equilibrium load path. Returns a newStrutAndTieModelwith member forces and reactions already populated, orNoneif the program is infeasible (caller falls back to the FSD refinement).Two geometric rules give an ACI 318-detailable result:
boundary_chordsadds a continuous chord along each loaded edge (adjacent supports along the bottom, adjacent loads along the top) and keeps it even when lightly loaded — the conventional flexural tie / top compression chord an engineer expects for detailing and crack control.intersecting members are never left un-noded: after each solve, a joint is inserted at every crossing of two load-carrying members and the layout is re-optimized (up to
max_crossing_itertimes), so every crossing is a real nodal zone (ACI 318 Ch. 23) rather than two members sharing a point in space.
- civilpy.structural.stm_topology.optimize_density(mesh: GroundMesh, *, vol_frac: float = 0.3, penal: float = 3.0, rmin: float | None = None, max_iter: int = 120, move: float = 0.2, tol: float = 0.01, nu: float = 0.2, verbose: bool = False) DensityResult[source]
Run SIMP on
meshand return the optimized density field.vol_fracis the target solid fraction of the active (optimizable) region.rmin(filter radius, element units) defaults to ~3% of the width. Supports/loads come frommesh.problem.
- civilpy.structural.stm_topology.optimize_pier_cap(loads, load_xs, column_xs, *, f_c: float = 5.0, f_y: float = 60.0, thickness: float = 4.0, depth_bounds: tuple[float, float] = (4.0, 12.0), n_depths: int = 9, edge: float = 2.5, vol_frac: float = 0.3, nelx: int = 72, column_bearing: float = 2.0, load_bearing: float = 1.0, min_strut_angle: float = 25.0, price_concrete_cy: float | None = None, price_steel_lb: float | None = None, prices=None, pin_column: int | None = None) PierCapDesign[source]
Find the most economical pier-cap depth for a set of girder reactions.
- Parameters:
loads – Factored girder reactions (kip, magnitudes) and their bearing locations (ft) along the cap — typically straight from a BrR / DASH run.
load_xs – Factored girder reactions (kip, magnitudes) and their bearing locations (ft) along the cap — typically straight from a BrR / DASH run.
column_xs – Column centreline locations (ft).
thickness – Out-of-plane cap width
b(ft).depth_bounds – Range and sampling of cap depths to evaluate.
n_depths – Range and sampling of cap depths to evaluate.
edge – Cap overhang beyond the outermost girder/column (ft); sets the length.
column_bearing – In-plane bearing widths (ft) for the nodal-zone checks.
load_bearing – In-plane bearing widths (ft) for the nodal-zone checks.
min_strut_angle – AASHTO 5.8.2.5 nodal angle limit (deg); raise it to force a stockier, more clearly deep-beam cap.
pin_column – Index of the laterally-restrained (pinned) column; defaults to the one nearest mid-length, the rest acting as vertical rollers.
- Return type:
PierCapDesignwhoseoptimalis the shallowest feasible depth.
- civilpy.structural.stm_topology.optimize_to_stm(problem, *, nelx: int = 120, threshold: float = 0.3, merge: float | None = None, penal: float = 3.0, rmin: float | None = None, max_iter: int = 120, fully_stressed: bool = True, verbose: bool = False, **price_kwargs) STMResult[source]
Run the full pipeline and return an
STMResult.Extraction skeletonizes the SIMP field for node positions, then solves an LP plastic truss layout optimization for the discrete load path; the resulting model carries its own member forces and reactions (an equilibrium load path, so no separate solve is needed).
- civilpy.structural.stm_topology.refine_truss(model, density_result, *, method: str = 'lp', **kwargs)[source]
Turn a raw skeleton truss into a clean, solved strut-and-tie model.
method="lp"(default) runslayout_optimize_truss()— a single global plastic truss layout optimization that is inherently symmetric for a symmetric problem. It returns a new model.method="fsd"(or anlpfailure) falls back to the in-place ground-structure fully-stressed-design refinement_refine_fsd().Returns the model to use (possibly a new object), so callers must use the return value rather than assuming in-place mutation.
- civilpy.structural.stm_topology.resolve_prices(prices=None) dict[source]
Resolve unit prices in priority order: explicit
pricesmapping > the registered provider >DEFAULT_PRICES.
- civilpy.structural.stm_topology.set_price_provider(provider)[source]
Register a zero-argument callable that returns a unit-price mapping with keys
concrete_cy($/cy) andsteel_lb($/lb) — and optionallysource. It is consulted whenever a take-off is requested without an explicitpricesargument. civilpy falls back toDEFAULT_PRICESif the provider is unset, returns nothing, or raises. Returns the previous provider (passNoneto clear).