From 4e7fd1087da5acc24706363822ad5ef0b4d2a4ca Mon Sep 17 00:00:00 2001 From: NINI Date: Fri, 12 Jun 2026 01:31:31 +0900 Subject: [PATCH] docs: add solver core skeleton phase --- phases/index.json | 8 ++ phases/solver-core-skeleton/index.json | 87 ++++++++++++ phases/solver-core-skeleton/step0.md | 83 ++++++++++++ phases/solver-core-skeleton/step1.md | 105 +++++++++++++++ phases/solver-core-skeleton/step2.md | 175 +++++++++++++++++++++++++ phases/solver-core-skeleton/step3.md | 97 ++++++++++++++ phases/solver-core-skeleton/step4.md | 112 ++++++++++++++++ phases/solver-core-skeleton/step5.md | 112 ++++++++++++++++ phases/solver-core-skeleton/step6.md | 114 ++++++++++++++++ phases/solver-core-skeleton/step7.md | 118 +++++++++++++++++ phases/solver-core-skeleton/step8.md | 89 +++++++++++++ 11 files changed, 1100 insertions(+) create mode 100644 phases/index.json create mode 100644 phases/solver-core-skeleton/index.json create mode 100644 phases/solver-core-skeleton/step0.md create mode 100644 phases/solver-core-skeleton/step1.md create mode 100644 phases/solver-core-skeleton/step2.md create mode 100644 phases/solver-core-skeleton/step3.md create mode 100644 phases/solver-core-skeleton/step4.md create mode 100644 phases/solver-core-skeleton/step5.md create mode 100644 phases/solver-core-skeleton/step6.md create mode 100644 phases/solver-core-skeleton/step7.md create mode 100644 phases/solver-core-skeleton/step8.md diff --git a/phases/index.json b/phases/index.json new file mode 100644 index 0000000..6eba160 --- /dev/null +++ b/phases/index.json @@ -0,0 +1,8 @@ +{ + "phases": [ + { + "dir": "solver-core-skeleton", + "status": "pending" + } + ] +} diff --git a/phases/solver-core-skeleton/index.json b/phases/solver-core-skeleton/index.json new file mode 100644 index 0000000..6460d03 --- /dev/null +++ b/phases/solver-core-skeleton/index.json @@ -0,0 +1,87 @@ +{ + "project": "FESA Structural Solver", + "phase": "solver-core-skeleton", + "steps": [ + { + "step": 0, + "name": "cmake-ctest-bootstrap", + "status": "pending", + "allowed_paths": [ + "CMakeLists.txt", + "tests/" + ] + }, + { + "step": 1, + "name": "core-primitives", + "status": "pending", + "allowed_paths": [ + "src/fesa/core/", + "tests/unit/core_*_test.cpp" + ] + }, + { + "step": 2, + "name": "domain-model-entities", + "status": "pending", + "allowed_paths": [ + "src/fesa/model/", + "tests/unit/model_*_test.cpp" + ] + }, + { + "step": 3, + "name": "analysis-model-view", + "status": "pending", + "allowed_paths": [ + "src/fesa/analysis/", + "tests/unit/analysis_model_*_test.cpp" + ] + }, + { + "step": 4, + "name": "dof-manager", + "status": "pending", + "allowed_paths": [ + "src/fesa/fem/", + "tests/unit/dof_manager_*_test.cpp" + ] + }, + { + "step": 5, + "name": "analysis-state", + "status": "pending", + "allowed_paths": [ + "src/fesa/analysis/", + "tests/unit/analysis_state_*_test.cpp" + ] + }, + { + "step": 6, + "name": "analysis-template-flow", + "status": "pending", + "allowed_paths": [ + "src/fesa/analysis/", + "tests/unit/analysis_flow_*_test.cpp" + ] + }, + { + "step": 7, + "name": "results-containers", + "status": "pending", + "allowed_paths": [ + "src/fesa/results/", + "tests/unit/results_*_test.cpp" + ] + }, + { + "step": 8, + "name": "solver-skeleton-integration-report", + "status": "pending", + "allowed_paths": [ + "tests/integration/", + "docs/build-test-reports/" + ] + } + ] +} diff --git a/phases/solver-core-skeleton/step0.md b/phases/solver-core-skeleton/step0.md new file mode 100644 index 0000000..d8aa68d --- /dev/null +++ b/phases/solver-core-skeleton/step0.md @@ -0,0 +1,83 @@ +# Step 0: cmake-ctest-bootstrap + +## 읽어야 할 파일 + +먼저 아래 파일들을 읽고 프로젝트의 아키텍처와 검증 규칙을 파악하라: + +- `/AGENTS.md` +- `/docs/PRD.md` +- `/docs/ARCHITECTURE.md` +- `/docs/ADR.md` +- `/scripts/validate_workspace.py` + +## 작업 + +C++ solver skeleton을 구현할 수 있도록 최소 CMake/CTest 부트스트랩을 만든다. + +요구사항: + +- 루트 `/CMakeLists.txt`를 생성한다. +- C++ 표준은 C++17 이상으로 고정한다. +- MSVC에서 warning-as-error를 강제하지 않는다. +- 아직 production source가 없으므로 solver target은 `INTERFACE` library `fesa_core`로 시작한다. +- `fesa_core`는 repository root의 `src`를 include directory로 노출한다. +- `enable_testing()`과 `tests/` 하위 CMake 구성을 연결한다. +- `/tests/CMakeLists.txt`, `/tests/unit/CMakeLists.txt`, `/tests/integration/CMakeLists.txt`를 생성한다. +- `tests/unit/*_test.cpp`와 `tests/integration/*_test.cpp` 파일을 각각 독립 test executable로 등록한다. +- 각 test executable은 `fesa_core`에 link한다. +- `/tests/unit/harness_smoke_test.cpp`를 생성하고, 표준 라이브러리만 사용해 CTest가 동작함을 확인하는 최소 `main()`을 둔다. +- npm, JavaScript, TypeScript fallback은 추가하지 않는다. + +권장 CMake 구조: + +```cmake +cmake_minimum_required(VERSION 3.20) +project(FESA LANGUAGES CXX) + +add_library(fesa_core INTERFACE) +target_compile_features(fesa_core INTERFACE cxx_std_17) +target_include_directories(fesa_core INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/src) + +enable_testing() +add_subdirectory(tests) +``` + +`tests/unit/CMakeLists.txt`와 `tests/integration/CMakeLists.txt`는 `file(GLOB CONFIGURE_DEPENDS ...)`와 `foreach`를 사용해 새 `*_test.cpp`가 자동으로 CTest에 등록되도록 만든다. + +## Tests To Write First + +- `/tests/unit/harness_smoke_test.cpp` + - `main()`이 `std::string{"fesa"}.size() == 4` 같은 deterministic smoke assertion을 검증한다. + - 실패 시 non-zero를 반환한다. + +RED 확인: + +1. `tests/unit/harness_smoke_test.cpp`를 먼저 만든다. +2. `ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R harness_smoke_test`를 실행해 아직 CMake 구성이 없어 실패함을 확인한다. +3. 그 뒤 CMake 파일을 작성한다. + +## Acceptance Criteria + +```powershell +python -m unittest discover -s scripts -p "test_*.py" +python scripts/validate_workspace.py +ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R harness_smoke_test +``` + +## 검증 절차 + +1. 위 AC 커맨드를 실행한다. +2. 아키텍처 체크리스트를 확인한다: + - ARCHITECTURE.md의 `src/`, `tests/unit/` 구조를 따르는가? + - ADR-002의 C++17/MSVC/CMake/CTest 기본값을 벗어나지 않았는가? + - AGENTS.md의 `python scripts/validate_workspace.py` 기본 검증 경로를 유지하는가? +3. 결과에 따라 `phases/solver-core-skeleton/index.json`의 step 0을 업데이트한다: + - 성공: `"status": "completed"`, `"summary": "CMake/CTest bootstrap with fesa_core interface target and smoke test"` + - 3회 수정 시도 후 실패: `"status": "error"`, `"error_message": "구체적 에러 내용"` + - 사용자 개입 필요: `"status": "blocked"`, `"blocked_reason": "구체적 사유"` 후 중단 + +## 금지사항 + +- C++ production solver class를 이 step에서 만들지 마라. +- 외부 test framework를 추가하지 마라. +- JavaScript/TypeScript/npm fallback을 추가하지 마라. diff --git a/phases/solver-core-skeleton/step1.md b/phases/solver-core-skeleton/step1.md new file mode 100644 index 0000000..9bb2586 --- /dev/null +++ b/phases/solver-core-skeleton/step1.md @@ -0,0 +1,105 @@ +# Step 1: core-primitives + +## 읽어야 할 파일 + +먼저 아래 파일들을 읽고 프로젝트의 아키텍처와 이전 step 산출물을 파악하라: + +- `/AGENTS.md` +- `/docs/PRD.md` +- `/docs/ARCHITECTURE.md` +- `/docs/ADR.md` +- `/CMakeLists.txt` +- `/tests/unit/CMakeLists.txt` + +이전 step에서 만들어진 CMake/CTest 구성을 꼼꼼히 읽고, 새 unit test가 자동 등록되는 방식과 일관성을 유지하라. + +## 작업 + +solver skeleton의 공통 primitive를 `/src/fesa/core/` 아래 header-only로 구현한다. + +필수 파일: + +- `/src/fesa/core/ids.hpp` +- `/src/fesa/core/diagnostic.hpp` +- `/src/fesa/core/status.hpp` +- `/tests/unit/core_primitives_test.cpp` + +필수 interface: + +```cpp +namespace fesa::core { + +struct NodeId { int value; }; +struct ElementId { int value; }; +struct MaterialId { int value; }; +struct PropertyId { int value; }; +struct StepId { int value; }; + +enum class Severity { info, warning, error }; + +struct Diagnostic { + Severity severity; + std::string code; + std::string message; +}; + +class Status { +public: + static Status ok(); + static Status failure(Diagnostic diagnostic); + + bool is_ok() const; + const std::vector& diagnostics() const; + void add(Diagnostic diagnostic); +}; + +} // namespace fesa::core +``` + +구현 규칙: + +- 모든 ID type은 strong typedef 역할을 하며 서로 암시적으로 대체되지 않아야 한다. +- ID에는 equation id 또는 DOF numbering 정보를 넣지 않는다. +- `Status::ok()`는 diagnostics가 비어 있고 `is_ok() == true`여야 한다. +- `Status::failure(...)`는 최소 하나의 diagnostic을 포함하고 `is_ok() == false`여야 한다. +- 외부 라이브러리에 의존하지 않는다. + +## Tests To Write First + +- `/tests/unit/core_primitives_test.cpp` + - `NodeId{1}`과 `ElementId{1}`이 서로 다른 타입임을 compile-time으로 확인한다. + - `Status::ok().is_ok()`가 true임을 확인한다. + - `Status::failure(...)`가 false이고 diagnostic code/message를 보존함을 확인한다. + - `Status::add(...)`로 warning을 추가해도 diagnostic 순서가 보존됨을 확인한다. + +RED 확인: + +1. 테스트 파일을 먼저 작성한다. +2. `python scripts/validate_workspace.py` 또는 targeted CTest를 실행해 `fesa/core/ids.hpp` 등 missing include로 실패함을 확인한다. +3. 그 뒤 production header를 작성한다. + +## Acceptance Criteria + +```powershell +python -m unittest discover -s scripts -p "test_*.py" +python scripts/validate_workspace.py +ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R core_primitives_test +``` + +## 검증 절차 + +1. 위 AC 커맨드를 실행한다. +2. 아키텍처 체크리스트를 확인한다: + - `core`가 외부 라이브러리에 의존하지 않는가? + - ID type에 equation numbering을 저장하지 않는가? + - C++ production header 변경에 대응하는 test file이 있는가? +3. 결과에 따라 `phases/solver-core-skeleton/index.json`의 step 1을 업데이트한다: + - 성공: `"status": "completed"`, `"summary": "Core ID, diagnostic, and status primitives added with tests"` + - 3회 수정 시도 후 실패: `"status": "error"`, `"error_message": "구체적 에러 내용"` + - 사용자 개입 필요: `"status": "blocked"`, `"blocked_reason": "구체적 사유"` 후 중단 + +## 금지사항 + +- Node, Element, Domain 같은 model class를 이 step에서 만들지 마라. +- MKL, TBB, HDF5 API를 include하지 마라. +- JavaScript/TypeScript/npm fallback을 추가하지 마라. diff --git a/phases/solver-core-skeleton/step2.md b/phases/solver-core-skeleton/step2.md new file mode 100644 index 0000000..bf8124f --- /dev/null +++ b/phases/solver-core-skeleton/step2.md @@ -0,0 +1,175 @@ +# Step 2: domain-model-entities + +## 읽어야 할 파일 + +먼저 아래 파일들을 읽고 프로젝트의 아키텍처와 이전 step 산출물을 파악하라: + +- `/AGENTS.md` +- `/docs/PRD.md` +- `/docs/ARCHITECTURE.md` +- `/docs/ADR.md` +- `/src/fesa/core/ids.hpp` +- `/src/fesa/core/status.hpp` +- `/tests/unit/core_primitives_test.cpp` + +이전 step에서 만들어진 core primitive를 꼼꼼히 읽고, ID ownership과 diagnostic convention을 유지하라. + +## 작업 + +입력 파일에서 생성된 전체 semantic model을 소유하는 최소 model layer를 `/src/fesa/model/`에 구현한다. + +필수 파일: + +- `/src/fesa/model/node.hpp` +- `/src/fesa/model/element.hpp` +- `/src/fesa/model/material.hpp` +- `/src/fesa/model/property.hpp` +- `/src/fesa/model/boundary_condition.hpp` +- `/src/fesa/model/load.hpp` +- `/src/fesa/model/analysis_step.hpp` +- `/src/fesa/model/domain.hpp` +- `/tests/unit/model_domain_test.cpp` + +필수 interface: + +```cpp +namespace fesa::model { + +class Node { +public: + Node(core::NodeId id, std::array coordinates); + core::NodeId id() const; + const std::array& coordinates() const; +}; + +enum class ElementTopology { truss2, bar2, unknown }; + +class Element { +public: + Element(core::ElementId id, ElementTopology topology, + std::vector node_ids, core::PropertyId property_id); + core::ElementId id() const; + ElementTopology topology() const; + const std::vector& node_ids() const; + core::PropertyId property_id() const; +}; + +class Material { +public: + Material(core::MaterialId id, std::string name); + core::MaterialId id() const; + const std::string& name() const; +}; + +class Property { +public: + Property(core::PropertyId id, std::string name, core::MaterialId material_id); + core::PropertyId id() const; + const std::string& name() const; + core::MaterialId material_id() const; +}; + +enum class DofComponent { ux, uy, uz, rx, ry, rz, temperature }; + +class BoundaryCondition { +public: + BoundaryCondition(core::NodeId node_id, DofComponent component, double value); + core::NodeId node_id() const; + DofComponent component() const; + double value() const; +}; + +class Load { +public: + Load(core::NodeId node_id, DofComponent component, double value); + core::NodeId node_id() const; + DofComponent component() const; + double value() const; +}; + +class AnalysisStep { +public: + AnalysisStep(core::StepId id, std::string name); + core::StepId id() const; + const std::string& name() const; + void add_boundary_condition(BoundaryCondition bc); + void add_load(Load load); + const std::vector& boundary_conditions() const; + const std::vector& loads() const; +}; + +class Domain { +public: + void add_node(Node node); + void add_element(Element element); + void add_material(Material material); + void add_property(Property property); + void add_step(AnalysisStep step); + + const std::vector& nodes() const; + const std::vector& elements() const; + const std::vector& materials() const; + const std::vector& properties() const; + const std::vector& steps() const; + + const Node* find_node(core::NodeId id) const; + const Element* find_element(core::ElementId id) const; + const Material* find_material(core::MaterialId id) const; + const Property* find_property(core::PropertyId id) const; + const AnalysisStep* find_step(core::StepId id) const; +}; + +} // namespace fesa::model +``` + +구현 규칙: + +- Domain은 semantic model 객체를 소유한다. +- `nodes()`, `elements()`, `materials()`, `properties()`, `steps()`는 const reference를 반환한다. +- `find_*`는 없으면 `nullptr`을 반환한다. +- Node와 Element 내부에 equation id, constrained/free equation mapping, sparse pattern 정보를 저장하지 않는다. +- `DofComponent`는 아직 equation number가 아니라 물리 DOF component만 표현한다. +- Abaqus keyword 문자열이나 parser detail을 model object에 저장하지 않는다. + +## Tests To Write First + +- `/tests/unit/model_domain_test.cpp` + - Node가 id와 3D coordinates를 보존한다. + - Element가 topology, connectivity, property id를 보존한다. + - Material과 Property가 id/name/material link를 보존한다. + - AnalysisStep이 boundary condition과 load를 저장한다. + - Domain이 add/find를 통해 각 객체를 조회한다. + - 없는 id는 `nullptr`을 반환한다. + - Node/Element public interface에 equation id를 노출하지 않는다는 점을 테스트 코드 사용 방식으로 확인한다. + +RED 확인: + +1. 테스트 파일을 먼저 작성한다. +2. targeted CTest를 실행해 missing model headers로 실패함을 확인한다. +3. 그 뒤 production headers를 작성한다. + +## Acceptance Criteria + +```powershell +python -m unittest discover -s scripts -p "test_*.py" +python scripts/validate_workspace.py +ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R model_domain_test +``` + +## 검증 절차 + +1. 위 AC 커맨드를 실행한다. +2. 아키텍처 체크리스트를 확인한다: + - Domain이 전체 모델 정의를 소유하는가? + - model layer가 parser keyword 문자열이나 analysis state를 소유하지 않는가? + - Node/Element에 equation id가 분산 저장되지 않는가? +3. 결과에 따라 `phases/solver-core-skeleton/index.json`의 step 2를 업데이트한다: + - 성공: `"status": "completed"`, `"summary": "Model entities and Domain ownership API added with tests"` + - 3회 수정 시도 후 실패: `"status": "error"`, `"error_message": "구체적 에러 내용"` + - 사용자 개입 필요: `"status": "blocked"`, `"blocked_reason": "구체적 사유"` 후 중단 + +## 금지사항 + +- Parser, assembler, solver backend를 만들지 마라. +- `Domain`을 실행 중 state container로 사용하지 마라. +- JavaScript/TypeScript/npm fallback을 추가하지 마라. diff --git a/phases/solver-core-skeleton/step3.md b/phases/solver-core-skeleton/step3.md new file mode 100644 index 0000000..54ca4e6 --- /dev/null +++ b/phases/solver-core-skeleton/step3.md @@ -0,0 +1,97 @@ +# Step 3: analysis-model-view + +## 읽어야 할 파일 + +먼저 아래 파일들을 읽고 프로젝트의 아키텍처와 이전 step 산출물을 파악하라: + +- `/AGENTS.md` +- `/docs/PRD.md` +- `/docs/ARCHITECTURE.md` +- `/docs/ADR.md` +- `/src/fesa/model/domain.hpp` +- `/src/fesa/model/analysis_step.hpp` +- `/tests/unit/model_domain_test.cpp` + +이전 step에서 만들어진 Domain과 AnalysisStep을 꼼꼼히 읽고, Domain 복사 없는 step view를 유지하라. + +## 작업 + +현재 step에서 활성화되는 해석 객체 view를 제공하는 `AnalysisModel`을 `/src/fesa/analysis/`에 구현한다. + +필수 파일: + +- `/src/fesa/analysis/analysis_model.hpp` +- `/tests/unit/analysis_model_view_test.cpp` + +필수 interface: + +```cpp +namespace fesa::analysis { + +class AnalysisModel { +public: + AnalysisModel(const model::Domain& domain, core::StepId step_id); + + const model::Domain& domain() const; + const model::AnalysisStep& step() const; + const std::vector& active_elements() const; + const std::vector& active_boundary_conditions() const; + const std::vector& active_loads() const; + + const model::Property* property_for(const model::Element& element) const; + const model::Material* material_for(const model::Property& property) const; +}; + +} // namespace fesa::analysis +``` + +구현 규칙: + +- `AnalysisModel`은 `Domain`을 복사하지 않고 const reference 또는 pointer view만 보유한다. +- Phase skeleton에서는 모든 Domain elements가 active라고 간주한다. +- Boundary condition과 load는 선택된 `AnalysisStep`에서 가져온다. +- `property_for`와 `material_for`는 Domain lookup을 사용한다. +- 없는 step id는 구조화된 exception 대신 `std::invalid_argument`로 실패해도 된다. 이 skeleton 단계에서는 별도 error hierarchy를 만들지 않는다. +- `AnalysisModel`은 displacement, residual, equation number를 소유하지 않는다. + +## Tests To Write First + +- `/tests/unit/analysis_model_view_test.cpp` + - Domain에 node/element/material/property/step을 만든다. + - `AnalysisModel(domain, step_id)`가 원본 Domain reference를 유지함을 pointer identity로 확인한다. + - 모든 Domain element가 `active_elements()`에 const pointer로 나타난다. + - step의 boundary condition과 load가 active view에 const pointer로 나타난다. + - `property_for(element)`와 `material_for(property)`가 Domain 소유 객체를 반환한다. + - 없는 step id는 `std::invalid_argument`를 던진다. + +RED 확인: + +1. 테스트 파일을 먼저 작성한다. +2. targeted CTest를 실행해 missing `analysis_model.hpp`로 실패함을 확인한다. +3. 그 뒤 production header를 작성한다. + +## Acceptance Criteria + +```powershell +python -m unittest discover -s scripts -p "test_*.py" +python scripts/validate_workspace.py +ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R analysis_model_view_test +``` + +## 검증 절차 + +1. 위 AC 커맨드를 실행한다. +2. 아키텍처 체크리스트를 확인한다: + - AnalysisModel이 Domain을 복사하지 않는가? + - AnalysisModel이 현재 step view만 제공하고 mutable state를 소유하지 않는가? + - active BC/load가 AnalysisStep에서 온 것임이 테스트되는가? +3. 결과에 따라 `phases/solver-core-skeleton/index.json`의 step 3을 업데이트한다: + - 성공: `"status": "completed"`, `"summary": "AnalysisModel step view added without Domain copies"` + - 3회 수정 시도 후 실패: `"status": "error"`, `"error_message": "구체적 에러 내용"` + - 사용자 개입 필요: `"status": "blocked"`, `"blocked_reason": "구체적 사유"` 후 중단 + +## 금지사항 + +- DofManager나 equation numbering을 이 step에서 구현하지 마라. +- AnalysisState를 이 step에서 구현하지 마라. +- JavaScript/TypeScript/npm fallback을 추가하지 마라. diff --git a/phases/solver-core-skeleton/step4.md b/phases/solver-core-skeleton/step4.md new file mode 100644 index 0000000..cd6e96f --- /dev/null +++ b/phases/solver-core-skeleton/step4.md @@ -0,0 +1,112 @@ +# Step 4: dof-manager + +## 읽어야 할 파일 + +먼저 아래 파일들을 읽고 프로젝트의 아키텍처와 이전 step 산출물을 파악하라: + +- `/AGENTS.md` +- `/docs/PRD.md` +- `/docs/ARCHITECTURE.md` +- `/docs/ADR.md` +- `/src/fesa/model/boundary_condition.hpp` +- `/src/fesa/analysis/analysis_model.hpp` +- `/tests/unit/analysis_model_view_test.cpp` + +이전 step에서 만들어진 model object와 AnalysisModel을 꼼꼼히 읽고, equation numbering이 Node/Element로 새지 않도록 유지하라. + +## 작업 + +node별 DOF 정의, constrained/free mapping, equation numbering, sparse pattern ownership의 최소 골격을 `/src/fesa/fem/`에 구현한다. + +필수 파일: + +- `/src/fesa/fem/dof_key.hpp` +- `/src/fesa/fem/dof_manager.hpp` +- `/tests/unit/dof_manager_numbering_test.cpp` + +필수 interface: + +```cpp +namespace fesa::fem { + +struct DofKey { + core::NodeId node_id; + model::DofComponent component; +}; + +bool operator==(const DofKey& lhs, const DofKey& rhs); + +class DofManager { +public: + void define_node_dofs(core::NodeId node_id, std::vector components); + void apply_boundary_condition(const model::BoundaryCondition& bc); + void number_equations(); + + int total_dof_count() const; + int free_dof_count() const; + int constrained_dof_count() const; + + bool is_constrained(DofKey key) const; + int equation_id(DofKey key) const; + std::optional free_equation_id(DofKey key) const; + + std::vector expand_free_vector(const std::vector& free_values) const; + const std::vector>& sparse_pattern() const; +}; + +} // namespace fesa::fem +``` + +구현 규칙: + +- Equation id는 `DofManager` 내부에만 저장한다. +- `equation_id`는 전체 DOF ordering의 dense id를 반환한다. +- `free_equation_id`는 constrained DOF면 `std::nullopt`를 반환한다. +- `number_equations()`는 deterministic ordering을 보장한다: + - node id value 오름차순 + - component enum 순서 오름차순 +- `expand_free_vector`는 constrained DOF 값을 0.0으로 채우고 free DOF 값만 배치한다. +- `sparse_pattern()`은 skeleton 단계에서 free equation ids의 dense full matrix pattern을 deterministic pair list로 보유해도 된다. +- missing DOF 조회는 `std::invalid_argument`를 던진다. + +## Tests To Write First + +- `/tests/unit/dof_manager_numbering_test.cpp` + - 두 node에 `ux`, `uy`를 정의하고 deterministic equation id ordering을 확인한다. + - boundary condition 적용 후 constrained/free count가 맞는지 확인한다. + - constrained key의 `free_equation_id`가 `std::nullopt`임을 확인한다. + - free vector가 full vector로 재구성될 때 constrained DOF가 0.0으로 채워지는지 확인한다. + - sparse pattern pair list가 free equation id 기반 deterministic dense pattern을 가진다. + - model::Node나 model::Element를 수정하지 않고도 equation numbering이 가능함을 확인한다. + +RED 확인: + +1. 테스트 파일을 먼저 작성한다. +2. targeted CTest를 실행해 missing `dof_manager.hpp`로 실패함을 확인한다. +3. 그 뒤 production headers를 작성한다. + +## Acceptance Criteria + +```powershell +python -m unittest discover -s scripts -p "test_*.py" +python scripts/validate_workspace.py +ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R dof_manager_numbering_test +``` + +## 검증 절차 + +1. 위 AC 커맨드를 실행한다. +2. 아키텍처 체크리스트를 확인한다: + - DofManager가 equation numbering을 전담하는가? + - Node/Element public interface가 equation id로 오염되지 않았는가? + - sparse pattern ownership이 DofManager 내부에 있는가? +3. 결과에 따라 `phases/solver-core-skeleton/index.json`의 step 4를 업데이트한다: + - 성공: `"status": "completed"`, `"summary": "DofManager deterministic numbering and constrained/free mapping added"` + - 3회 수정 시도 후 실패: `"status": "error"`, `"error_message": "구체적 에러 내용"` + - 사용자 개입 필요: `"status": "blocked"`, `"blocked_reason": "구체적 사유"` 후 중단 + +## 금지사항 + +- Solver backend, assembly matrix, MKL adapter를 구현하지 마라. +- Node 또는 Element에 equation id field를 추가하지 마라. +- JavaScript/TypeScript/npm fallback을 추가하지 마라. diff --git a/phases/solver-core-skeleton/step5.md b/phases/solver-core-skeleton/step5.md new file mode 100644 index 0000000..01e95db --- /dev/null +++ b/phases/solver-core-skeleton/step5.md @@ -0,0 +1,112 @@ +# Step 5: analysis-state + +## 읽어야 할 파일 + +먼저 아래 파일들을 읽고 프로젝트의 아키텍처와 이전 step 산출물을 파악하라: + +- `/AGENTS.md` +- `/docs/PRD.md` +- `/docs/ARCHITECTURE.md` +- `/docs/ADR.md` +- `/src/fesa/fem/dof_manager.hpp` +- `/tests/unit/dof_manager_numbering_test.cpp` + +이전 step에서 만들어진 DofManager를 꼼꼼히 읽고, 해석 중 변하는 물리량은 AnalysisState가 소유하도록 유지하라. + +## 작업 + +displacement 중심의 최소 `AnalysisState`를 `/src/fesa/analysis/`에 구현한다. + +필수 파일: + +- `/src/fesa/analysis/analysis_state.hpp` +- `/tests/unit/analysis_state_vectors_test.cpp` + +필수 interface: + +```cpp +namespace fesa::analysis { + +struct IterationState { + double time = 0.0; + int increment = 0; + int iteration = 0; +}; + +class AnalysisState { +public: + explicit AnalysisState(int total_dof_count); + + const std::vector& displacement() const; + const std::vector& velocity() const; + const std::vector& acceleration() const; + const std::vector& temperature() const; + const std::vector& external_force() const; + const std::vector& internal_force() const; + const std::vector& residual() const; + + void set_displacement(std::vector values); + void set_external_force(std::vector values); + void set_internal_force(std::vector values); + void update_residual(); + + IterationState& iteration_state(); + const IterationState& iteration_state() const; + + void set_element_state(core::ElementId element_id, std::vector state); + const std::vector* element_state(core::ElementId element_id) const; +}; + +} // namespace fesa::analysis +``` + +구현 규칙: + +- 모든 vector는 `total_dof_count` 크기로 초기화한다. +- `update_residual()`은 `external_force - internal_force`를 component-wise로 계산한다. +- setter는 입력 vector 크기가 맞지 않으면 `std::invalid_argument`를 던진다. +- temperature는 Phase skeleton에서 0.0 vector로 둔다. +- element state는 향후 integration point state 확장을 위한 최소 map으로 둔다. +- AnalysisState는 Domain, AnalysisModel, DofManager를 소유하지 않는다. + +## Tests To Write First + +- `/tests/unit/analysis_state_vectors_test.cpp` + - 생성 시 displacement/velocity/acceleration/temperature/force/residual vector 크기와 0.0 초기값을 확인한다. + - displacement setter가 값을 보존한다. + - external/internal force 설정 후 residual이 `Fext - Fint`가 되는지 확인한다. + - 크기가 맞지 않는 vector setter가 `std::invalid_argument`를 던진다. + - time/increment/iteration 값이 저장된다. + - element state를 element id로 저장/조회한다. + +RED 확인: + +1. 테스트 파일을 먼저 작성한다. +2. targeted CTest를 실행해 missing `analysis_state.hpp`로 실패함을 확인한다. +3. 그 뒤 production header를 작성한다. + +## Acceptance Criteria + +```powershell +python -m unittest discover -s scripts -p "test_*.py" +python scripts/validate_workspace.py +ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R analysis_state_vectors_test +``` + +## 검증 절차 + +1. 위 AC 커맨드를 실행한다. +2. 아키텍처 체크리스트를 확인한다: + - AnalysisState가 해석 중 변하는 물리량을 소유하는가? + - Domain이나 AnalysisModel을 복사/소유하지 않는가? + - displacement 중심이되 velocity/acceleration/temperature 확장 지점을 유지하는가? +3. 결과에 따라 `phases/solver-core-skeleton/index.json`의 step 5를 업데이트한다: + - 성공: `"status": "completed"`, `"summary": "AnalysisState vector ownership and residual update added"` + - 3회 수정 시도 후 실패: `"status": "error"`, `"error_message": "구체적 에러 내용"` + - 사용자 개입 필요: `"status": "blocked"`, `"blocked_reason": "구체적 사유"` 후 중단 + +## 금지사항 + +- Solver backend나 numerical integration loop를 구현하지 마라. +- HDF5 writer를 이 step에서 구현하지 마라. +- JavaScript/TypeScript/npm fallback을 추가하지 마라. diff --git a/phases/solver-core-skeleton/step6.md b/phases/solver-core-skeleton/step6.md new file mode 100644 index 0000000..002d791 --- /dev/null +++ b/phases/solver-core-skeleton/step6.md @@ -0,0 +1,114 @@ +# Step 6: analysis-template-flow + +## 읽어야 할 파일 + +먼저 아래 파일들을 읽고 프로젝트의 아키텍처와 이전 step 산출물을 파악하라: + +- `/AGENTS.md` +- `/docs/PRD.md` +- `/docs/ARCHITECTURE.md` +- `/docs/ADR.md` +- `/src/fesa/analysis/analysis_model.hpp` +- `/src/fesa/analysis/analysis_state.hpp` +- `/src/fesa/fem/dof_manager.hpp` +- `/tests/unit/analysis_state_vectors_test.cpp` + +이전 step에서 만들어진 AnalysisModel, DofManager, AnalysisState를 꼼꼼히 읽고, ARCHITECTURE.md의 Template Method 실행 흐름과 일관성을 유지하라. + +## 작업 + +공통 해석 실행 흐름을 고정하는 `Analysis` base class와 선형 정적 해석 skeleton을 `/src/fesa/analysis/`에 구현한다. + +필수 파일: + +- `/src/fesa/analysis/analysis.hpp` +- `/src/fesa/analysis/linear_static_analysis.hpp` +- `/tests/unit/analysis_flow_template_test.cpp` + +필수 interface: + +```cpp +namespace fesa::analysis { + +class Analysis { +public: + virtual ~Analysis() = default; + void run(); + +protected: + virtual void initialize() {} + virtual void build_analysis_model() {} + virtual void build_dof_map() {} + virtual void build_sparse_pattern() {} + virtual void assemble() {} + virtual void apply_boundary_conditions() {} + virtual void solve() {} + virtual void update_state() {} + virtual void write_results() {} +}; + +class LinearStaticAnalysis : public Analysis { +public: + LinearStaticAnalysis(const model::Domain& domain, core::StepId step_id); + + const AnalysisModel* analysis_model() const; + const AnalysisState* state() const; + +protected: + void build_analysis_model() override; + void build_dof_map() override; + void update_state() override; +}; + +} // namespace fesa::analysis +``` + +구현 규칙: + +- `Analysis::run()`은 반드시 다음 순서로 hook을 호출한다: + `initialize -> build_analysis_model -> build_dof_map -> build_sparse_pattern -> assemble -> apply_boundary_conditions -> solve -> update_state -> write_results` +- `LinearStaticAnalysis`는 skeleton 단계에서 실제 stiffness assembly나 solve를 하지 않는다. +- `LinearStaticAnalysis::build_analysis_model()`은 `AnalysisModel`을 생성한다. +- `LinearStaticAnalysis::build_dof_map()`은 active element connectivity에서 등장한 node에 `ux`, `uy`, `uz`를 정의하고 active BC를 적용한 뒤 equation numbering을 수행한다. +- `LinearStaticAnalysis::update_state()`는 total DOF count 크기의 `AnalysisState`를 준비한다. +- MKL, TBB, HDF5 adapter는 만들지 않는다. + +## Tests To Write First + +- `/tests/unit/analysis_flow_template_test.cpp` + - test-only derived `RecordingAnalysis`가 hook 호출 순서를 vector에 기록한다. + - `run()` 호출 후 ARCHITECTURE.md 순서와 정확히 일치하는지 확인한다. + - 최소 Domain과 Step으로 `LinearStaticAnalysis`를 실행하면 `analysis_model()`과 `state()`가 null이 아니게 되는지 확인한다. + - `LinearStaticAnalysis`가 실제 solver 결과를 계산한다고 주장하지 않음을 테스트 이름과 assertion 범위에 반영한다. + +RED 확인: + +1. 테스트 파일을 먼저 작성한다. +2. targeted CTest를 실행해 missing `analysis.hpp` 또는 `linear_static_analysis.hpp`로 실패함을 확인한다. +3. 그 뒤 production headers를 작성한다. + +## Acceptance Criteria + +```powershell +python -m unittest discover -s scripts -p "test_*.py" +python scripts/validate_workspace.py +ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R analysis_flow_template_test +``` + +## 검증 절차 + +1. 위 AC 커맨드를 실행한다. +2. 아키텍처 체크리스트를 확인한다: + - Analysis::run()이 Template Method 흐름을 고정하는가? + - LinearStaticAnalysis가 skeleton 범위를 넘어 solver backend를 구현하지 않는가? + - 외부 라이브러리 API가 solver core에 노출되지 않는가? +3. 결과에 따라 `phases/solver-core-skeleton/index.json`의 step 6을 업데이트한다: + - 성공: `"status": "completed"`, `"summary": "Analysis template method and LinearStaticAnalysis skeleton added"` + - 3회 수정 시도 후 실패: `"status": "error"`, `"error_message": "구체적 에러 내용"` + - 사용자 개입 필요: `"status": "blocked"`, `"blocked_reason": "구체적 사유"` 후 중단 + +## 금지사항 + +- 실제 stiffness matrix assembly, linear solve, MKL PARDISO adapter를 구현하지 마라. +- HDF5 writer를 구현하지 마라. +- JavaScript/TypeScript/npm fallback을 추가하지 마라. diff --git a/phases/solver-core-skeleton/step7.md b/phases/solver-core-skeleton/step7.md new file mode 100644 index 0000000..da36519 --- /dev/null +++ b/phases/solver-core-skeleton/step7.md @@ -0,0 +1,118 @@ +# Step 7: results-containers + +## 읽어야 할 파일 + +먼저 아래 파일들을 읽고 프로젝트의 아키텍처와 이전 step 산출물을 파악하라: + +- `/AGENTS.md` +- `/docs/PRD.md` +- `/docs/ARCHITECTURE.md` +- `/docs/ADR.md` +- `/src/fesa/analysis/analysis_state.hpp` +- `/src/fesa/analysis/analysis.hpp` +- `/tests/unit/analysis_flow_template_test.cpp` + +이전 step에서 만들어진 AnalysisState와 Analysis flow를 꼼꼼히 읽고, 결과 container가 HDF5 API에 직접 의존하지 않도록 유지하라. + +## 작업 + +HDF5 writer 구현 전 단계의 results data model skeleton을 `/src/fesa/results/`에 구현한다. + +필수 파일: + +- `/src/fesa/results/results.hpp` +- `/tests/unit/results_containers_test.cpp` + +필수 interface: + +```cpp +namespace fesa::results { + +enum class FieldLocation { nodal, element, integration_point }; + +struct FieldOutput { + std::string name; + FieldLocation location; + std::vector components; + std::vector entity_ids; + std::vector values; +}; + +struct HistoryOutput { + std::string name; + std::vector time; + std::vector values; +}; + +class ResultFrame { +public: + ResultFrame(int frame_id, double time); + int frame_id() const; + double time() const; + void add_field_output(FieldOutput output); + void add_history_output(HistoryOutput output); + const std::vector& field_outputs() const; + const std::vector& history_outputs() const; +}; + +class ResultStep { +public: + explicit ResultStep(std::string name); + const std::string& name() const; + ResultFrame& add_frame(int frame_id, double time); + const std::vector& frames() const; +}; + +} // namespace fesa::results +``` + +구현 규칙: + +- Result hierarchy는 `ResultStep -> ResultFrame -> FieldOutput/HistoryOutput` 구조를 따른다. +- Field output은 row identity를 위해 `entity_ids`를 보존한다. +- Field output values layout은 skeleton 단계에서 row-major flat vector로 둔다. +- HDF5 file/dataset, schema writer, CSV export는 구현하지 않는다. +- validation은 크기 consistency만 최소로 확인한다: + - `components`가 비어 있으면 `std::invalid_argument` + - `entity_ids.size() * components.size() == values.size()`가 아니면 `std::invalid_argument` + +## Tests To Write First + +- `/tests/unit/results_containers_test.cpp` + - ResultStep이 이름과 frame 목록을 보존한다. + - ResultFrame이 frame id/time을 보존한다. + - nodal displacement FieldOutput을 추가하면 components/entity ids/values가 보존된다. + - invalid FieldOutput shape가 `std::invalid_argument`를 던진다. + - HistoryOutput이 time/value series를 보존한다. + +RED 확인: + +1. 테스트 파일을 먼저 작성한다. +2. targeted CTest를 실행해 missing `results.hpp`로 실패함을 확인한다. +3. 그 뒤 production header를 작성한다. + +## Acceptance Criteria + +```powershell +python -m unittest discover -s scripts -p "test_*.py" +python scripts/validate_workspace.py +ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R results_containers_test +``` + +## 검증 절차 + +1. 위 AC 커맨드를 실행한다. +2. 아키텍처 체크리스트를 확인한다: + - Results hierarchy가 ARCHITECTURE.md와 일치하는가? + - HDF5 API가 results container에 직접 노출되지 않는가? + - reference comparison을 위한 entity row identity가 보존되는가? +3. 결과에 따라 `phases/solver-core-skeleton/index.json`의 step 7을 업데이트한다: + - 성공: `"status": "completed"`, `"summary": "ResultStep, ResultFrame, FieldOutput, and HistoryOutput containers added"` + - 3회 수정 시도 후 실패: `"status": "error"`, `"error_message": "구체적 에러 내용"` + - 사용자 개입 필요: `"status": "blocked"`, `"blocked_reason": "구체적 사유"` 후 중단 + +## 금지사항 + +- HDF5 writer/reader를 구현하지 마라. +- deterministic CSV export를 구현하지 마라. +- JavaScript/TypeScript/npm fallback을 추가하지 마라. diff --git a/phases/solver-core-skeleton/step8.md b/phases/solver-core-skeleton/step8.md new file mode 100644 index 0000000..cccebe5 --- /dev/null +++ b/phases/solver-core-skeleton/step8.md @@ -0,0 +1,89 @@ +# Step 8: solver-skeleton-integration-report + +## 읽어야 할 파일 + +먼저 아래 파일들을 읽고 프로젝트의 아키텍처와 이전 step 산출물을 파악하라: + +- `/AGENTS.md` +- `/docs/PRD.md` +- `/docs/ARCHITECTURE.md` +- `/docs/ADR.md` +- `/src/fesa/model/domain.hpp` +- `/src/fesa/analysis/linear_static_analysis.hpp` +- `/src/fesa/results/results.hpp` +- `/tests/unit/results_containers_test.cpp` +- `/docs/build-test-reports/README.md` + +이전 step에서 만들어진 전체 skeleton API를 꼼꼼히 읽고, integration smoke test가 실제 solver 수치해석 완료를 주장하지 않도록 범위를 제한하라. + +## 작업 + +solver skeleton의 주요 class가 함께 컴파일되고 기본 data flow를 구성할 수 있음을 통합 테스트와 build/test report로 남긴다. + +필수 파일: + +- `/tests/integration/solver_core_skeleton_integration_test.cpp` +- `/docs/build-test-reports/solver-core-skeleton.md` + +통합 테스트 요구사항: + +- Domain에 두 개 Node, 하나 Element, Material, Property, AnalysisStep을 구성한다. +- AnalysisStep에는 최소 하나의 BoundaryCondition과 Load를 추가한다. +- `LinearStaticAnalysis`를 생성하고 `run()`을 호출한다. +- `analysis_model()`과 `state()`가 생성되는지 확인한다. +- `ResultStep`과 `ResultFrame`을 만들고 nodal displacement `FieldOutput`을 추가한다. +- 이 테스트는 stiffness assembly, linear solve, HDF5 write, reference comparison을 검증하지 않는다. + +보고서 요구사항: + +- `docs/build-test-reports/solver-core-skeleton.md`에 아래 항목을 기록한다. + - phase: solver-core-skeleton + - scope: C++ skeleton classes only + - commands run + - exit code summary + - CTest tests added + - known limitations +- known limitations에는 최소한 다음을 명시한다: + - 실제 element stiffness/residual/tangent 계산 없음 + - 실제 linear solver backend 없음 + - HDF5 writer 없음 + - Abaqus parser 및 reference comparison 없음 + +## Tests To Write First + +- `/tests/integration/solver_core_skeleton_integration_test.cpp` + - 전체 skeleton header를 include한다. + - 위 통합 테스트 요구사항을 `main()` assertion으로 검증한다. + +RED 확인: + +1. 통합 테스트 파일을 먼저 작성한다. +2. targeted CTest를 실행해 아직 integration test registration 또는 required API 문제로 실패하면 실패 내용을 확인한다. +3. Step 0의 CMake glob이 `tests/integration/*_test.cpp`를 자동 등록해야 한다. 등록되지 않는다면 현재 step에서 CMake 파일을 수정하지 말고 `blocked`로 표시하고 사용자에게 allowed_paths 확장을 요청한다. + +## Acceptance Criteria + +```powershell +python -m unittest discover -s scripts -p "test_*.py" +python scripts/validate_workspace.py +ctest --test-dir build/msvc-debug --output-on-failure -C Debug -R solver_core_skeleton_integration_test +``` + +## 검증 절차 + +1. 위 AC 커맨드를 실행한다. +2. 아키텍처 체크리스트를 확인한다: + - Domain -> AnalysisModel -> DofManager/AnalysisState -> Results data flow가 컴파일되는가? + - 통합 테스트가 skeleton 범위를 넘어 수치 정확성을 주장하지 않는가? + - build/test report가 실제 실행 evidence와 known limitations를 기록하는가? +3. 결과에 따라 `phases/solver-core-skeleton/index.json`의 step 8을 업데이트한다: + - 성공: `"status": "completed"`, `"summary": "Solver skeleton integration test and build/test report added"` + - 3회 수정 시도 후 실패: `"status": "error"`, `"error_message": "구체적 에러 내용"` + - 사용자 개입 필요: `"status": "blocked"`, `"blocked_reason": "구체적 사유"` 후 중단 + +## 금지사항 + +- 실제 FEM stiffness, residual, tangent, material law 계산을 구현하지 마라. +- Abaqus reference artifact를 생성, 수정, 복원하지 마라. +- HDF5 writer를 구현하지 마라. +- JavaScript/TypeScript/npm fallback을 추가하지 마라.