otko/tests/integration/test_pattern_factors.py
smill f361fee969 feat: named case-result load combinations with full GUI support
Snapshots the current development tree, headlined by proper load
combinations (user request): a reusable LoadCombination entity of
weighted completed static-case results (e.g. 1.2xDead + 1.6xLive).

- core: LoadCombination/LoadCombinationItem entities, Project
  integration (lookup, unique ids, reference validation)
- services: combinations.py (linear superposition + envelope),
  exported via services __init__
- commands: undoable Add/Delete/Update for combinations
- GUI: Load Combinations manager dialog, Run-dialog evaluation,
  envelope display in Results panel, Combinations tab in Table dock
- tests: unit coverage (validation, math, error paths) + integration
  superposition check vs a single factored run
2026-09-11 13:19:59 -04:00

65 lines
1.8 KiB
Python

"""Pattern-factor integration: scaled static load doubles the tip displacement."""
from __future__ import annotations
import math
import pytest
ops = pytest.importorskip("openseespy.opensees") # skip if OpenSeesPy not installed
from otko.core import ( # noqa: E402
ElasticBeamColumn,
ElasticSection,
LinearTimeSeries,
NodalLoad,
Node,
PlainLoadPattern,
Project,
StaticCase,
)
from otko.services import OpenSeesRunner # noqa: E402
pytestmark = pytest.mark.slow
def _cantilever() -> Project:
length = 5.0
load = 1000.0
elastic_mod = 200e9
area = 0.01
inertia = 8.333e-6
return Project(
ndm=2,
ndf=3,
nodes=[
Node(
id=1,
coords=(0.0, 0.0, 0.0),
restraint=(True, True, False, False, False, True),
),
Node(id=2, coords=(length, 0.0, 0.0)),
],
sections=[ElasticSection(id=1, E=elastic_mod, A=area, Iz=inertia)],
elements=[ElasticBeamColumn(id=1, nodes=(1, 2), section_id=1)],
time_series=[LinearTimeSeries(id=1)],
load_patterns=[
PlainLoadPattern(
id=1,
time_series_id=1,
nodal_loads=[NodalLoad(node_id=2, forces=(0.0, -load, 0.0, 0.0, 0.0, 0.0))],
)
],
)
def test_pattern_factor_doubles_tip_disp() -> None:
"""StaticCase pattern_factors={1: 2.0} must double the tip displacement."""
base = OpenSeesRunner(_cantilever()).run(StaticCase(id=1, name="base", pattern_ids=[1]))
scaled = OpenSeesRunner(_cantilever()).run(
StaticCase(id=1, name="scaled", pattern_ids=[1], pattern_factors={1: 2.0})
)
d_base = base.disp(node_id=2, dof=2)
d_scaled = scaled.disp(node_id=2, dof=2)
assert d_base != 0.0
assert math.isclose(d_scaled, 2.0 * d_base, rel_tol=1e-9)