otko/tests/integration/test_moment_curvature_example.py
smillmorel ba783718d4 chore: adopt remaining local development state
Catch-all for the intermixed residue of the unpushed otko-development
work ported into this tree: combinations/console-dock/quick-guide wiring
across commands, core, services, views and tests; repo-wide ruff-format
normalization; README/CONTRIBUTING updates; and the toolbar default
(both toolbars now open in the top area, quick guide text updated).

Splitting this further would require hunk-level surgery with low
confidence; the preceding commits in this branch isolate the
self-contained features.
2026-09-16 12:03:22 -04:00

56 lines
1.8 KiB
Python

"""Round-trip + physics check on the shipped moment-curvature example."""
from __future__ import annotations
import pytest
pytest.importorskip("openseespy")
from otko.services import load_project # noqa: E402
from otko.services.opensees_runner import OpenSeesRunner # noqa: E402
pytestmark = pytest.mark.slow
def test_moment_curvature_example_round_trips_and_converges(tmp_path) -> None: # type: ignore[no-untyped-def]
"""build_moment_curvature() → save → load → run → expected shape."""
from examples.moment_curvature import (
build_moment_curvature,
COL_DEPTH,
COVER,
E_STEEL,
FY,
MU,
NUM_INCR,
)
proj = build_moment_curvature()
proj.validate_references()
# Save + reload — catches schema drift.
from otko.services import save_project
path = tmp_path / "mk.osmodel"
save_project(proj, path)
reloaded = load_project(path)
reloaded.validate_references()
assert reloaded.meta.units.value.startswith("US (in,")
result = OpenSeesRunner(reloaded).run(reloaded.analyses[0])
# Yield curvature estimate.
d = COL_DEPTH - COVER
ky = (FY / E_STEEL) / (0.7 * d)
# At least half of the NUM_INCR pushover steps converged.
assert (
len(result.control_disp) > NUM_INCR * 0.5
), "Pushover bailed out prematurely — check Concrete01 softening"
# Reached the mu * Ky target.
assert result.control_disp[-1] == pytest.approx(MU * ky, rel=1e-2)
# Moment at yield curvature is plausible: > 3 kip·in and < 10 kip·in per rebar
# → for 8 bars total, the section moment capacity is roughly O(3000-6000) kip·in.
peak_moment = max(abs(m) for m in result.base_shear)
assert 2000 < peak_moment < 10000, (
f"Peak moment {peak_moment:.1f} kip·in is outside the " "expected RC section range"
)