OpenSees 소스를 읽는 것은 왜 실무 기술인가: 클래스 구조·내성·재현 가능한 워크플로

프레임워크 전체를 이루는 다섯 추상 기반 클래스, 실제 파일 수를 센 소스 트리 지도, Python 호출 하나를 C++ 생성자까지 추적하기, 소스 40줄로 답하는 Steel01 이동경화, 오타 철자는 동작하고 올바른 철자는 조용히 아무것도 반환하지 않는 응답 키, Python 쪽 내성, setParame

OpenSees 소스를 읽는 것은 왜 실무 기술인가: 클래스 구조·내성·재현 가능한 워크플로

OpenSees는 묻지 않은 질문에도 답한다. 빈 결과로, 오류 메시지 없이. 요소에 'deformations'를 물어보면 — 영어로는 맞고 문자열로는 틀린 — []가 돌아온다. 같은 키를 recorder에 걸면 해석이 끝날 때까지 빈 파일을 태연히 쓴다. 이 사실이 문서화된 유일한 곳은 소스이고, 그것도 오타가 들어 있는 C++ 한 줄이다.

이 글의 주장이 그것이다. OpenSees 소스를 읽는 것은 개발자만의 고급 활동이 아니라, 문서가 답하지 않은 질문에 답하는 평범한 방법이다. 그리고 문서가 답하지 않은 질문이야말로 모델을 틀리게 만드는 질문이다. 이 글은 라이브러리 구조를 그리고, Python 호출 하나를 C++ 생성자까지 따라가고, 실제 질문 둘을 소스에서 답하고, C++를 아예 읽지 않아도 되는 Python 쪽 내성(introspection)을 보이고, 0.2초 안에 도는 검증 시험 모음과 재현 가능한 워크플로로 시리즈를 마무리한다. 소스에 대한 모든 주장은 2026-08-30 기준 github.com/OpenSees/OpenSees에서 확인했고, 모든 측정은 OpenSeesPy 3.8.0에서 생성했다.

1. 다섯 개의 추상 기반 클래스가 전부를 조직한다

1~4부에서 쓴 모든 명령은 다섯 추상 클래스 중 하나의 구체 하위 클래스를 만들었다. 그것이 구조의 전부이고, 이를 알면 "큰 C++ 프로젝트 어딘가"가 "특정 폴더 하나"로 바뀐다.

기반 클래스계약어떤 명령으로 만났나

DomainComponentDomain 이 소유하고 태그로 지목할 수 있는 것node(), element(), fix(), pattern()

Element시행 변위를 받아 강성행렬과 저항력을 돌려준다Truss, elasticBeamColumn, forceBeamColumn

Material변형을 받아 응력과 접선을 돌려주고 확정 상태를 기억한다Steel01, Concrete01, section('Fiber')

Analysis 구성요소Domain 을 한 확정 상태에서 다음으로 옮긴다1부의 여섯 객체

RecorderDomain 을 바꾸지 않고 관찰한다recorder('Node', ...)

이것이 실무적으로 중요한 이유. 무언가 예상과 다르게 거동할 때 첫 질문은 "OpenSees의 무엇이 잘못됐나"가 아니라 "이 다섯 중 어느 것이 오작동하고 그 계약은 무엇인가"다. 접선을 잘못 돌려주는 재료, 자기 단면과 어긋나는 단부력을 보고하는 요소, 한계점을 통과하지 못하는 적분기는 각각 다른 폴더이고 다른 종류의 질문이다.

2. 소스 트리와 대략의 규모

저장소는 크지만 실제로 열게 될 곳은 몇 안 되고 이름이 예측 가능하다.

경로무엇이 있는가규모

SRC/domain/Domain 자체, Node, Element 기반 클래스, 구속, 하중 패턴pattern/ 에만 28 .cpp

SRC/element/요소 계열마다 폴더 하나씩하위 폴더 46개

SRC/material/uniaxial/Steel01, Concrete01, 각종 래퍼166 .cpp

SRC/material/section/파이버 단면, 합성 단면, 단면 래퍼41 .cpp

SRC/material/nD/평면응력, 솔리드, 지반 구성모델79 .cpp

SRC/analysis/integrator/LoadControl, Newmark, HHT, 호장법 등62 .cpp

SRC/system_of_eqn/linearSOE/모든 솔버 백엔드하위 폴더 14개

SRC/convergenceTest/NormDispIncr, NormUnbalance 등12 .cpp

SRC/interpreter/Python·Tcl 명령에서 생성자로 가는 다리OpenSees*Commands.cpp

가져갈 관찰이 둘 있다. 단축재료 구현이 166개나 되므로 "이미 이런 재료가 있지 않을까?"는 새로 만들기 전에 거의 항상 물어볼 값어치가 있다. 그리고 요소 폴더가 46개라는 사실이 요소 이름이 일관되어 보이지 않는 이유다. 각 계열이 서로 다른 시기에 서로 다른 그룹에서 기여되었고, 이름은 계획이 아니라 그 역사를 반영한다.

3. 명령 하나를 Python 에서 C++ 까지 따라가기

메커니즘은 생각보다 단순하고, 한 번 따라가 보면 어떤 명령이든 1분이면 추적할 수 있다.

ops.element('elasticBeamColumn', ...)을 보자. SRC/interpreter/OpenSeesElementCommands.cpp에는 정확히 이런 형태의 줄로 채워진 조회표가 있다.

functionMap.insert(std::make_pair("elasticBeamColumn", &OPS_ElasticBeam)); // 799행 functionMap.insert(std::make_pair("forceBeamColumn", &OPS_ForceBeamColumn)); // 804행 functionMap.insert(std::make_pair("Truss", &OPS_TrussElement)); // 704행

OPS_ElasticBeam은 모델의 ndm을 읽어 OPS_ElasticBeam2d나 OPS_ElasticBeam3d를 부르는 얇은 분기다. 2차원 팩토리는 SRC/element/elasticBeamColumn/ElasticBeam2d.cpp 앞부분에 있다.

void *OPS_ElasticBeam2d(const ID &info) { // ElasticBeam2d.cpp 63행

이 함수가 ElasticBeam2d를 생성하고, 인터프리터가 그것을 Domain::addElement(Element *)(SRC/domain/domain/Domain.h 102행)에 넘긴다. 경로 전체가 이것이다. 문자열 → 팩토리 → 생성자 → Domain.

어떤 명령에도 통하는 실무 절차는 이렇다.

입력한 문자열을 대소문자까지 그대로 저장소에서 검색한다. "elasticBeamColumn"은 조회표에 딱 한 번 나온다.

옆에 붙은 함수 포인터를 따라간다. 그 함수가 인수 목록의 최종 근거다. 인수를 순서대로 읽고, 틀렸을 때 보이는 사용법 메시지를 출력하는 곳도 거기다.

그 함수가 생성하는 클래스는 요소 계열 이름의 폴더에 있고, 그 클래스의 setResponse 메서드가 보고 가능한 항목의 최종 목록이다.

4. 사례 1: Steel01 은 왜 이동경화인가

3부는 Steel01이 반전 후 0 부근에서 항복을 재개한다는 것 — 이동경화 거동 — 을 측정했고, 문서는 그렇게 명시하지 않는다. 소스 40줄이면 정리된다.

SRC/material/uniaxial/Steel01.h는 선택적 매개변수 네 개와 그 이름을 밝히는 주석을 함께 선언한다.

Steel01(int tag, double fy, double E0, double b, double a1 = STEEL_01_DEFAULT_A1, double a2 = STEEL_01_DEFAULT_A2, double a3 = STEEL_01_DEFAULT_A3, double a4 = STEEL_01_DEFAULT_A4); ... double a1; double a2; double a3; double a4; // a1 through a4 are coefficients for isotropic hardening

Steel01.cpp의 determineTrialState는 항복 후 계수를 Esh = b*E0로 계산하고, 항복면 위치를 TshiftP와 TshiftN 두 이동 계수로 잡는다. 이 둘은 1.0으로 초기화되며, 값이 바뀌는 유일한 곳이 a1과 a2가 들어가는 줄이다. a 매개변수를 주지 않으면 해석 내내 1.0으로 남는다. 항복면이 소성변형과 함께 이동할 뿐 커지지 않는다는 뜻이고, 그것이 이동경화의 정의이며, 3부의 실험이 측정한 바로 그 거동이다.

이 읽기가 무엇을 했는지 보자. 클래스 전체를 이해할 필요가 없었다. 답을 결정하는 변수 둘을 찾았을 뿐이다. 이 소스를 읽는 정상적인 방식이 그것이다. 상태 변수를 찾고, 갱신되는 지점을 찾고, 거기서 멈춘다.

5. 사례 2: 이 요소는 실제로 무엇을 보고할 수 있나

모든 요소는 setResponse(argv, ...)를 구현하며, 그것은 문자열 비교의 연쇄다. ElasticBeam2d가 받는 키는 소스에 그대로 보인다.

// ElasticBeam2d.cpp, setResponse() "force" / "forces" / "globalForce" / "globalForces" 1538행 "localForce" / "localForces" 1551행 "basicForce" / "basicForces" 1563행 "basicStiffness" 1573행 "RayleighForces" / "rayleighForces" 1615행 "deformatons" / "basicDeformations" / "basicDeformation" 1627행 "chordRotation" / "chordDeformation" 1637행

1627행을 다시 보자. 문자열이 "deformatons"다. 몇 년째 파일에 남아 있는 오타이고, 철자가 맞는 "deformations"는 목록에 없다. 그것을 요청하면 아무것도 돌아오지 않는다.

>>> ops.eleResponse(1, 'basicDeformation') [0.0, 0.0006, -0.0003] >>> ops.eleResponse(1, 'deformatons') # 오타 철자 [0.0, 0.0006, -0.0003] >>> ops.eleResponse(1, 'deformations') # 올바른 영어 [] >>> ops.eleResponse(1, 'nonsenseKey') []

모르는 키와 오타는 구분되지 않는다. 둘 다 빈 리스트를 돌려준다. 더 나쁘게도 recorder에서도 같다. 'deformations'로 등록한 recorder는 해석 전체를 돌고 빈 파일을 쓴다.

recorder 'basicForce' -> '0 30 -3.55271e-15' recorder 'deformations' -> ''

경고도, 0이 아닌 반환값도, 아무것도 없다. 여기서 비선형 모델에 넣어둘 가장 쓸모 있는 단언 하나가 나온다.

# 요소가 요청한 항목을 보고할 수 없으면 크게 실패시킨다 for key in ('basicForce', 'localForce', 'basicDeformation'): assert ops.eleResponse(eleTag, key), f"element cannot report {key!r}"

6. C++ 를 열지 않고 Python 에서 내성하기

대부분의 질문은 Python 쪽만으로 답할 수 있고, 그쪽이 더 빠르다. 네 가지 기법이 거의 전부를 덮는다.

질문묻는 방법

내 모델에 실제로 무엇이 들었나?getNodeTags(), getEleTags(), nodeCoord(), eleNodes()

방정식 번호는 어떻게 배치되었나?첫 analyze() 이후 systemSize(), nodeDOFs(tag)

이 요소는 무엇을 보고할 수 있나?후보 키로 eleResponse 를 찔러본다. 비어 있지 않으면 지원

모델 전체는?printModel() 또는 printModel('-file', 'model.txt')

방정식 번호 질의는 보기보다 유용하다. 구속된 자유도는 −1을 받고 자유 자유도는 방정식 번호를 받는다.

ops.analyze(1) print(ops.systemSize()) # -> 2 print(ops.nodeDOFs(1)) # -> [-1, -1] 두 자유도 모두 구속 print(ops.nodeDOFs(3)) # -> [0, 1] 방정식 두 개

이 한 번의 질의가 "왜 행렬이 특이하지" 조사의 대부분을 몇 초에 끝낸다. 구속했다고 믿었던 자유도가 방정식 번호를 가지고 있거나, 살아 있다고 믿었던 자유도가 −1로 나타난다.

7. 모델을 다시 짓지 않는 매개변수 연구

1부는 wipe()를 부르고 다시 지어 스윕을 만들었다. 두 번째 경로가 있다. 요소는 setParameter를 통해 이름 붙은 매개변수를 노출하고, 받는 이름은 역시 소스의 문자열 비교 목록이다. ElasticBeam2d는 E, A, I, G, Av, rho, release를 받는다.

ops.setParameter('-val', 2 * I, '-ele', 1, 'I') # 관성모멘트를 제자리에서 두 배로 assert ops.analyze(1) == 0

선단 처짐, 처음 I -1.945525 mm 손계산 -1.945525 mm 선단 처짐, 2I 이후 -0.972763 mm 손계산 -0.972763 mm 비율 2.000000

정확히 2배이고, 두 경우 모두 PL³/3EI와 일치한다. 알아둘 이유가 둘이다. 큰 모델에서 반복문 안에서 다시 짓는 것보다 훨씬 빠르고, OpenSees 민감도 해석의 바탕이 되는 메커니즘이다. 동시에 오용하기도 쉽다. 비선형 모델에서 해석 도중 매개변수를 바꾸는 것은 하중을 받고 있는 구조물을 바꾸는 것이며, 단계 시공에서는 의미가 있고 실수로 하면 무의미하다.

8. 같은 답, 다른 대가: 솔버 고르기

1부는 system()이 무엇을 푸는지가 아니라 강성행렬을 어떻게 저장하고 분해하는지를 정한다고 했다. 확인할 수 있는 주장이다. 자유도 1890개인 20경간·30층 탄성 라멘을 각 백엔드로 한 번씩 풀면 이렇다.

system()풀이 시간절점 수평변위

ProfileSPD8.3 ms68.655487 mm

BandSPD8.6 ms68.655487 mm

SparseGeneral10.6 ms68.655487 mm

BandGeneral12.8 ms68.655487 mm

UmfPack13.0 ms68.655487 mm

FullGeneral1228.1 ms68.655487 mm

여섯 개가 마지막 출력 자리까지 일치하고, 행렬 전체를 조밀하게 저장하는 FullGeneral은 가장 빠른 것보다 약 145배 오래 걸린다. 번호 부여기도 영향이 있다. 정도는 덜하지만 BandGeneral에서 RCM은 11.0 ms, Plain은 15.2 ms였다. 대역폭을 줄이면 계산량이 줄기 때문이다.

요점은 특정 시간값이 아니다. 그것은 기계와 모델에 따라 달라진다. 요점은 솔버 선택이 답이 동일한 순수한 비용 결정이라는 것이다. 그러므로 관습이 아니라 측정으로 조정할 수 있고 조정해야 하며, 잘못 고르면 한 시간이면 될 연구가 일주일이 될 수 있다.

9. 재현 가능한 실행은 스크립트 하나가 아니라 네 개의 산출물이다

이 시리즈의 모든 것은 독립적인 무언가와 대조해 검증했다. 그것을 반복 가능하게 만드는 것은 의지가 아니라 구조의 문제다.

버전을 고정한다. 잠금 파일에 openseespy==3.8.0.0을 적고, 모든 출력 파일에 ops.version()을 찍는다. 요소 정식화와 기본 동작은 버전 사이에 바뀐다. 버전 없는 결과는 재현 가능하지 않다.

모델을 wipe()로 시작하는 함수로 감싼다. 인터프리터가 전역 모델 하나를 유지하므로, 함수가 아닌 모델은 실행 사이에 상태가 새는 모델이다. 시그니처는 매개변수를 받고 결과를 반환하며, 모듈 수준 변수에 의존하지 않아야 한다.

def portal_frame(*, span, height, I_col, I_beam, load): ops.wipe() # 언제나 먼저 ops.model('basic', '-ndm', 2, '-ndf', 3) ... assert ops.analyze(1) == 0, "static analysis failed" ops.reactions() return {'drift_mm': ops.nodeDisp(2, 1) * 1000, 'base_shear': -ops.nodeReaction(1)[0], 'version': ops.version()}

검증 시험을 연구 뒤가 아니라 앞에서 돌린다. 결과 400개를 만드는 스윕은 같은 방식으로 틀릴 기회가 400번인 것이다. 아래 시험 모음은 0.2초 안에 끝난다.

출력을 그것을 만든 입력과 함께 저장한다. 매개변수 열을 나중에 재구성한 결과 CSV는 추측이다. 매개변수 사전을 결과와 같은 레코드에 쓴다.

10. 검증 시험 모음 전문

아래의 모든 확인은 이 시리즈에서 진단한 실패 중 하나를 시험으로 바꾼 것이다. 어느 프로젝트에나 붙여 넣을 수 있을 만큼 일부러 짧게 두었다.

"""OpenSees 모델을 위한 최소 검증 시험 모음. 실행: pytest -q test_model_verification.py 단위: kN, m, ton""" import math import pytest import openseespy.opensees as ops E, A, I = 200.0e6, 8.45e-3, 2.313e-4 # kN/m2, m2, m4 L, P, W = 3.0, 10.0, 20.0 # m, kN, kN/m RHO = 7.85 * A # t/m @pytest.fixture(autouse=True) def clean_domain(): ops.wipe() yield ops.wipe() def cantilever(n_ele=1, mass=False): ops.model('basic', '-ndm', 2, '-ndf', 3) for i in range(n_ele + 1): ops.node(i + 1, L * i / n_ele, 0.0) ops.fix(1, 1, 1, 1) ops.geomTransf('Linear', 1) extra = ['-mass', RHO, '-cMass'] if mass else [] for i in range(n_ele): ops.element('elasticBeamColumn', i + 1, i + 1, i + 2, A, E, I, 1, *extra) return n_ele + 1 def static_setup(): ops.constraints('Plain'); ops.numberer('RCM'); ops.system('BandGeneral') ops.test('NormDispIncr', 1e-12, 50); ops.algorithm('Linear') ops.integrator('LoadControl', 1.0); ops.analysis('Static') def test_units_are_consistent(): assert 1.0 * 9.80665 == pytest.approx(9.80665) def test_tip_deflection_matches_closed_form(): tip = cantilever() ops.timeSeries('Linear', 1); ops.pattern('Plain', 1, 1) ops.load(tip, 0.0, -P, 0.0) static_setup() assert ops.analyze(1) == 0 assert ops.nodeDisp(tip, 2) == pytest.approx(-P * L ** 3 / (3 * E * I), rel=1e-10) def test_reactions_balance_applied_load(): tip = cantilever(4) ops.timeSeries('Linear', 1); ops.pattern('Plain', 1, 1) for e in range(1, 5): ops.eleLoad('-ele', e, '-type', '-beamUniform', -W) static_setup() assert ops.analyze(1) == 0 ops.reactions() assert ops.nodeReaction(1)[1] == pytest.approx(W * L, rel=1e-10) assert ops.nodeReaction(1)[2] == pytest.approx(W * L ** 2 / 2, rel=1e-10) def test_elastic_response_is_mesh_independent(): values = [] for n in (1, 2, 4, 8): ops.wipe() tip = cantilever(n) ops.timeSeries('Linear', 1); ops.pattern('Plain', 1, 1) ops.load(tip, 0.0, -P, 0.0) static_setup() assert ops.analyze(1) == 0 values.append(ops.nodeDisp(tip, 2)) assert max(values) - min(values) < 1e-12 def test_element_end_moment_equals_end_section_moment(): tip = cantilever() ops.timeSeries('Linear', 1); ops.pattern('Plain', 1, 1) ops.load(tip, 0.0, -P, 0.0) static_setup() assert ops.analyze(1) == 0 assert abs(ops.basicForce(1)[1]) == pytest.approx(P * L, rel=1e-10) def test_first_period_matches_euler_bernoulli(): cantilever(16, mass=True) beta_L = 1.8751040687 exact = (beta_L ** 2) * math.sqrt(E * I / (RHO * L ** 4)) lam = ops.eigen('-fullGenLapack', 1) assert math.sqrt(lam[0]) == pytest.approx(exact, rel=1e-5) def test_requested_response_keys_actually_exist(): tip = cantilever() ops.timeSeries('Linear', 1); ops.pattern('Plain', 1, 1) ops.load(tip, 0.0, -P, 0.0) static_setup() assert ops.analyze(1) == 0 for key in ('basicForce', 'localForce', 'basicDeformation'): assert ops.eleResponse(1, key), f"element cannot report {key!r}"

$ pytest -q test_model_verification.py ....... [100%] 7 passed in 0.18s

모든 시험 전후로 wipe 하는 autouse 픽스처는 장식이 아니다. 그것이 없으면 모델을 남긴 시험이 다음 시험의 결과를 바꾸고, pytest 의 실행 순서 때문에 그 실패는 간헐적이고 미치게 만든다. 전역 상태에는 알려진 경계에서의 명시적 초기화가 필요하고, OpenSees에서 그 경계는 시험이다.

11. 라이브러리에 필요한 것이 없을 때

비용이 커지는 순서로 네 경로가 있다. 통하는 것 중 가장 싼 것을 고른다.

기존 재료를 조합한다. Series와 Parallel이 두 재료를 합성하고, MinMax는 변형률 한계를 넘으면 재료를 제거하며, Fatigue는 손상 누적을 감싸고, InitStressMaterial은 초기응력을 더한다. "맞춤 재료가 필요하다"는 요구의 상당수가 래퍼 하나로 끝난다.

단면을 합성한다. section('Aggregator', ...)는 축력과 휨만 모사하는 파이버 단면에 전단·비틀림 같은 독립 단축 응답을 붙인다. 파이버 모델이 원래 가질 수 없는 전단 파괴 모드를 얻는 방법이 이것이다.

Python 에서 해석을 구동한다. 해석이 한 단계씩 전진하므로, 진행하고 검사하고 모델을 수정하고 계속할 수 있다. 요소 제거, 단계 시공, 제어 알고리즘이 C++를 건드리지 않고 여기에 들어간다. 단계당 Python 오버헤드가 대가다.

C++ 클래스를 작성한다. UniaxialMaterial을 상속해 setTrialStrain, getStress, getTangent, commitState, revertToLastCommit을 구현하고, 팩토리 함수와 인터프리터 조회표의 한 줄을 더한다. 최고 속도를 얻지만 그때부터 큰 C++ 프로젝트의 포크를 유지해야 한다.

네 번째까지 간다면 같은 검증 규율이 더 강하게 적용된다. 새 재료는 단면 안에 넣기 전에 3부의 단위 시험 장치에서 자기 방정식과 대조해야 한다.

12. 같은 논리를 다섯 단계 깊이로

입문자는 명령을 쓰기 전에 OpenSeesPy 문서 페이지를 읽는다. 초급 실무자는 가정하지 않고 getEleTags와 eleResponse로 모델을 찔러본다. 실무 해석자는 문서와 동작이 어긋날 때 명령 문자열을 소스에서 grep 하고, 모든 연구 전에 도는 검증 시험 모음을 유지한다. 선임 엔지니어는 각 문제가 어느 기반 클래스에 속하는지 알고 그 객체를 분리해 시험한다. 전문가는 재료나 요소의 상태 변수를 읽어 그것이 무엇을 표현할 수 없는지 말할 수 있고, 그 한계를 결과와 함께 보고한다.

13. OpenSees 결과를 신뢰하기 위한 최소 점검표

OpenSees 버전이 누군가의 기억이 아니라 출력에 기록되어 있는가?

모델이 wipe()로 시작하는 함수여서 실행끼리 오염되지 않는가?

검증 시험 모음이 연구 전에 돌았고, 오늘 이 기계에서 통과했는가?

모든 analyze() 반환값을 검사했는가?

기록하는 모든 응답 키가 찔러봤을 때 비어 있지 않은 결과를 주는가?

최소 한 결과를 독립적인 방법으로 재현했는가?

반력이 힘과 모멘트 양쪽에서 작용하중과 균형을 이루는가?

메시, 적분점, 시간간격을 세분해도 답이 살아남는가?

고른 재료와 요소가 표현할 수 없는 물리 메커니즘이 결과 옆에 적혀 있는가?

동료가 저장된 입력만으로, 나에게 아무것도 묻지 않고 그 숫자를 재현할 수 있는가?

마지막 항목이 이 시리즈 전체의 요점이다. OpenSees는 모델이 틀렸을 때 알려주지 않는다. 무엇을 의도했는지 알 방법이 없기 때문이다. 대신 확실하게 해주는 것이 있다. 같은 질문에 같은 답을 주고, 자신을 이루는 모든 객체를 드러내며, 그중 무엇이든 자신에게 의존하지 않는 것과 대조할 수 있게 해준다. 닫힌해·독립 풀이·자기 소스와 대조한 다섯 편의 검증에서 프레임워크가 틀린 적은 한 번도 없었다. 그 과정에서 찾은 모든 오류는 모델에 있었고, 모두 프레임워크에 직접 질문하면 몇 분 안에 찾을 수 있는 것이었다.

OpenSees 시리즈 전체

각 편은 닫힌해·독립적인 풀이·OpenSees 소스 중 하나와 대조해 검증했다. 순서대로 읽도록 썼지만 각 편이 자기 전제를 스스로 밝힌다.

1부 — 도메인 모델과 해석 조립

2부 — 단면·기하변환·분포하중

3부 — 파이버 단면·모멘트–곡률·푸시오버

4부 — 질량·감쇠·시간적분

5부 — 라이브러리 읽기와 재현 가능한 워크플로 — 지금 보는 글

6부 — 철근콘크리트 파이버 단면과 구속

7부 — 실제 지진기록·층간변위·증분동적해석

8부 — 3차원: 강막·비틀림·leaning column

9부 — 수렴하지 않을 때: 실패·알고리즘·한계점

10부 — Tcl 읽기와 공개 스크립트 이식

11부 — 쉘·솔리드·지반: 락킹·메시·부지응답

12부 — 후처리: recorder·질의, 그리고 그림이 증명하는 것

13부 — 좌굴과 한계점: 분기점·스냅백·호장법

14부 — Windows: 설치·파일 형식·조용한 실패

15부 — 검증된 정답이 딸린 연습문제와 한영 용어집

16부 — 어떻게 동작하는가: 시행상태·확정상태·해석 루프

17부 — 커뮤니티가 말하는 것, 측정해보면

18부 — 절점 변위에서 파이버 응력까지

19부 — 면진과 감쇠: 속도 의존 요소와 베어링

참고 자료

OpenSees Developers, OpenSees 소스 저장소 — 위에 인용한 모든 파일·행번호·개수의 출처.

OpenSees 소스, OpenSeesElementCommands.cpp — 명령 조회표.

OpenSees 소스, ElasticBeam2d.cpp — 팩토리 함수, setResponse, setParameter.

OpenSees 소스, Steel01.cpp, Steel01.h — 경화 상태 변수.

OpenSees 소스, Domain.h — Domain 의 공개 인터페이스.

McKenna, F., OpenSees: A Framework for Earthquake Engineering Simulation, Computing in Science & Engineering 13(4), 2011 — 주 개발자가 쓴 구조 설명.

Zhu, M., OpenSeesPy Documentation, 특히 eleResponse, setParameter, printModel.

Scott, M. H., OpenSees Digital — 소스 수준 동작을 꾸준히 다루는 OpenSees 개발자 블로그.

Pacific Earthquake Engineering Research Center, OpenSees Wiki.

Python Package Index, openseespy — 고정할 버전.

자료 확인 2026-08-30. 소스 행번호는 그날의 master 브랜치 기준이며 저장소가 바뀌면 이동할 수 있다. 파일 경로와 그것이 보여주는 메커니즘은 안정적이다. 모든 측정은 OpenSeesPy 3.8.0에서 생성되었고 위의 코드로 재현할 수 있다. 프레임워크의 동작을 보여주는 것이지 프로젝트별 해석이나 기준 적합성 검토를 대신하지 않는다.