"""Load-combination integration: 1.2×D + 1.6×L by superposition == one combined run.""" from __future__ import annotations import math import pytest ops = pytest.importorskip("openseespy.opensees") # skip if OpenSeesPy not installed from otko.core import ( ElasticBeamColumn, ElasticSection, LinearTimeSeries, LoadCombination, LoadCombinationItem, NodalLoad, Node, PlainLoadPattern, Project, StaticCase, ) from otko.services import OpenSeesRunner, evaluate_combination from otko.services.results import StaticResults pytestmark = pytest.mark.slow def _two_pattern_cantilever() -> Project: 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=(5.0, 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), LinearTimeSeries(id=2)], load_patterns=[ PlainLoadPattern( id=1, name="Dead", time_series_id=1, nodal_loads=[NodalLoad(node_id=2, forces=(0.0, -1000.0, 0.0, 0.0, 0.0, 0.0))], ), PlainLoadPattern( id=2, name="Live", time_series_id=2, nodal_loads=[NodalLoad(node_id=2, forces=(0.0, -500.0, 0.0, 0.0, 0.0, 0.0))], ), ], analyses=[ StaticCase(id=1, name="Dead", pattern_ids=[1]), StaticCase(id=2, name="Live", pattern_ids=[2]), ], ) def test_superposition_matches_combined_run() -> None: """1.2*Dead + 1.6*Live via evaluate_combination == single StaticCase run.""" proj = _two_pattern_cantilever() dead = OpenSeesRunner(proj).run(proj.analyses[0]) live = OpenSeesRunner(proj).run(proj.analyses[1]) combo = LoadCombination( id=1, name="1.2D+1.6L", kind="Linear", items=[ LoadCombinationItem(case_id=1, factor=1.2), LoadCombinationItem(case_id=2, factor=1.6), ], ) combined = evaluate_combination({1: dead, 2: live}, combo) assert isinstance(combined, StaticResults) # Single run with both patterns factored identically. direct = OpenSeesRunner(proj).run( StaticCase(id=3, name="direct", pattern_ids=[1, 2], pattern_factors={1: 1.2, 2: 1.6}) ) assert math.isclose( combined.disp(node_id=2, dof=2), direct.disp(node_id=2, dof=2), rel_tol=1e-9 )