otko/examples/rc_frame_pushover.py

139 lines
5.2 KiB
Python
Raw Permalink Normal View History

2026-09-08 02:12:15 -04:00
"""RC Frame Pushover Analysis — OpenSees Examples Manual, Example 3.2.
Sources the Example 3 gravity model and extends it with a lateral
reference load pattern + DisplacementControl pushover on the top-left
joint (node 3, DOF 1 = Ux). Target displacement 15 in with dU = 0.1
in per step. Matches the Tcl walkthrough at:
https://opensees.berkeley.edu/wiki/index.php?title=RC_Portal_Frame_Pushover_Analysis
Model (kip-in-ksi):
- Geometry + section + elements = Example 3 (rc_frame_gravity).
- Gravity preload = a ``StaticCase`` with 10 × LoadControl(0.1)
steps under a Linear TS — same recipe the Tcl uses
(``analyze 10; loadConst -time 0.0``). Referenced by the
PushoverCase via ``preload_case_ids``.
- Lateral pattern: H = 10 kip at nodes 3 and 4 in +X, Linear TS,
scaled by DisplacementControl.
GUI walkthrough: File → Open → rc_frame_pushover.osmodel → Analyze →
Run → Pushover → Display → Show Pushover Curve.
"""
from __future__ import annotations
from pathlib import Path
from otko.core import (
LinearTimeSeries,
NodalLoad,
PlainLoadPattern,
PushoverCase,
StaticCase,
)
from otko.services import load_project, save_project
# Reuse the Example 3 gravity project as the foundation — identical
# geometry, materials, sections, elements. Only the load patterns and
# analysis case change for the pushover. Works both when importing as
# ``examples.rc_frame_pushover`` (pytest) and when running this file
# directly (``python examples/rc_frame_pushover.py``).
try:
from examples.rc_frame_gravity import build_rc_frame_gravity, P_LOAD
except ImportError:
import sys
sys.path.insert(0, str(Path(__file__).parent))
from rc_frame_gravity import build_rc_frame_gravity, P_LOAD # type: ignore
# Pushover parameters from the Tcl reference.
H_LATERAL = 10.0 # kip — reference lateral load
D_STEP = 0.1 # in — DisplacementControl increment
D_TARGET = 15.0 # in — total pushover displacement
def build_rc_frame_pushover(): # type: ignore[no-untyped-def]
"""Start from the Ex3 gravity model and re-plumb for pushover.
Three edits to the gravity project:
1. Add a ``LinearTimeSeries`` + ``PlainLoadPattern`` for the
lateral reference load (H = 10 kip at nodes 3 & 4, +X).
2. Keep the gravity pattern on its own Linear TS — the preload
runs as a 10-step ``LoadControl(0.1)`` ramp, exactly as the
Tcl walkthrough does.
3. Replace the StaticCase with two cases: a preload ``StaticCase``
(id 100) for gravity, and a ``PushoverCase`` whose
``preload_case_ids=[100]`` references it. The pushover's own
``pattern_ids`` holds only the lateral reference.
"""
proj = build_rc_frame_gravity()
proj.meta.name = "RC Frame Pushover (OpenSees Ex 3.2)"
proj.meta.description = (
"Ex 3 gravity preload (StaticCase) + lateral reference load + "
"DisplacementControl pushover on node 3 (DOF 1) to 15 in, "
"chained via preload_case_ids."
)
proj.time_series = [
LinearTimeSeries(id=1, name="Gravity"),
LinearTimeSeries(id=2, name="Lateral"),
]
proj.load_patterns = [
PlainLoadPattern(
id=1, name="Gravity",
time_series_id=1,
nodal_loads=[
NodalLoad(node_id=3, forces=(0, -P_LOAD, 0, 0, 0, 0)),
NodalLoad(node_id=4, forces=(0, -P_LOAD, 0, 0, 0, 0)),
],
),
# Lateral reference — scaled by the DisplacementControl factor.
PlainLoadPattern(
id=2, name="Lateral",
time_series_id=2,
nodal_loads=[
NodalLoad(node_id=3, forces=(H_LATERAL, 0, 0, 0, 0, 0)),
NodalLoad(node_id=4, forces=(H_LATERAL, 0, 0, 0, 0, 0)),
],
),
]
proj.analyses = [
StaticCase(
id=100, name="Gravity-Preload",
pattern_ids=[1],
n_steps=10, load_factor_increment=0.1,
system="BandGeneral", constraints="Transformation",
integrator="LoadControl", algorithm="Newton",
test="NormDispIncr", tolerance=1e-12, max_iter=10,
),
PushoverCase(
id=1, name="Pushover",
preload_case_ids=[100],
pattern_ids=[2], # lateral only
control_node=3, control_dof=1, # Ux at top-left joint
target_disp=D_TARGET,
step_size=D_STEP,
base_nodes=[1, 2],
system="BandGeneral", constraints="Transformation",
algorithm="Newton",
test="NormDispIncr", tolerance=1e-12, max_iter=10,
),
]
return proj
def main() -> None:
project = build_rc_frame_pushover()
project.validate_references()
print(f"Built '{project.meta.name}'")
print(f" H = {H_LATERAL} kip reference, dU = {D_STEP} in, target = {D_TARGET} in")
print(f" Steps = {int(D_TARGET / D_STEP)}")
out_path = Path(__file__).with_suffix(".osmodel")
save_project(project, out_path)
print(f"Saved -> {out_path}")
restored = load_project(out_path)
restored.validate_references()
assert restored.model_dump(by_alias=True) == project.model_dump(by_alias=True)
print("Round-trip OK.")
if __name__ == "__main__":
main()