139 lines
5.2 KiB
Python
139 lines
5.2 KiB
Python
|
|
"""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()
|