feat: add linear static workflow model
This commit is contained in:
@@ -13,7 +13,7 @@ Every new agent session must read this file together with `PROGRESS.md` before p
|
|||||||
- If an item becomes obsolete, move it to `PROGRESS.md` with a short reason instead of silently deleting it.
|
- If an item becomes obsolete, move it to `PROGRESS.md` with a short reason instead of silently deleting it.
|
||||||
|
|
||||||
## Current Objective
|
## Current Objective
|
||||||
Continue the new Phase 1 rebaseline plan in `phases/1-linear-static-mitc4-rebaseline`, starting with P1R-13 linear static workflow revalidation from input to `U`/`RF` result fields. The old `phases/1-linear-static-mitc4` path is historical and superseded by the paper-based MITC4 formulation reset.
|
Continue the new Phase 1 rebaseline plan in `phases/1-linear-static-mitc4-rebaseline`, starting with P1R-14 stored-reference displacement regression using accepted Phase 1-compatible S4 cases. The old `phases/1-linear-static-mitc4` path is historical and superseded by the paper-based MITC4 formulation reset.
|
||||||
|
|
||||||
## Required Reading For New Agents
|
## Required Reading For New Agents
|
||||||
1. `AGENTS.md`
|
1. `AGENTS.md`
|
||||||
@@ -36,7 +36,7 @@ Continue the new Phase 1 rebaseline plan in `phases/1-linear-static-mitc4-rebase
|
|||||||
## Active Phase Files
|
## Active Phase Files
|
||||||
- Active phase directory: `phases/1-linear-static-mitc4-rebaseline`
|
- Active phase directory: `phases/1-linear-static-mitc4-rebaseline`
|
||||||
- Execute with: `python scripts/execute.py 1-linear-static-mitc4-rebaseline`
|
- Execute with: `python scripts/execute.py 1-linear-static-mitc4-rebaseline`
|
||||||
- Step numbering is zero-based. `step0.md` is complete and recorded in `phases/1-linear-static-mitc4-rebaseline/step0-audit.md`; `step1.md` is complete and created the `quad_02_phase1.inp` normalized reference path; `step2.md` is complete and revalidated core harness guardrails; `step3.md` is complete and revalidated the Phase 1 parser/domain subset; `step4.md` is complete and strengthened validation/singular diagnostics; `step5.md` is complete and revalidated the DofManager/reaction foundation; `step6.md` is complete and revalidated the minimum result model plus displacement CSV comparator; `step7.md` is complete and revalidated MITC4 natural coordinates, tying points, center directors, and integration bases; `step8.md` is complete and revalidated degenerated-continuum displacement, direct covariant strain rows, and MITC shear tying rows; `step9.md` is complete and revalidated plane-stress material, convected-to-local transform, and `2 x 2 x 2` material integration scaffolding; `step10.md` is complete and revalidated MITC4 stiffness, internal force, six-DOF transform, and drilling stabilization; `step11.md` is complete and added MITC4 membrane, bending, shear, twist, drilling-sensitivity, and thin-cantilever locking-sensitivity tests; `step12.md` is complete and revalidated full-space assembly, reduced projection, deterministic sparse-pattern scaffold, solver adapter injection, and full-vector internal/reaction force state; `step15.md` is the independent evaluator closeout.
|
- Step numbering is zero-based. `step0.md` is complete and recorded in `phases/1-linear-static-mitc4-rebaseline/step0-audit.md`; `step1.md` is complete and created the `quad_02_phase1.inp` normalized reference path; `step2.md` is complete and revalidated core harness guardrails; `step3.md` is complete and revalidated the Phase 1 parser/domain subset; `step4.md` is complete and strengthened validation/singular diagnostics; `step5.md` is complete and revalidated the DofManager/reaction foundation; `step6.md` is complete and revalidated the minimum result model plus displacement CSV comparator; `step7.md` is complete and revalidated MITC4 natural coordinates, tying points, center directors, and integration bases; `step8.md` is complete and revalidated degenerated-continuum displacement, direct covariant strain rows, and MITC shear tying rows; `step9.md` is complete and revalidated plane-stress material, convected-to-local transform, and `2 x 2 x 2` material integration scaffolding; `step10.md` is complete and revalidated MITC4 stiffness, internal force, six-DOF transform, and drilling stabilization; `step11.md` is complete and added MITC4 membrane, bending, shear, twist, drilling-sensitivity, and thin-cantilever locking-sensitivity tests; `step12.md` is complete and revalidated full-space assembly, reduced projection, deterministic sparse-pattern scaffold, solver adapter injection, and full-vector internal/reaction force state; `step13.md` is complete and revalidated active AnalysisModel construction plus input-to-AnalysisState-to-U/RF result workflow; `step15.md` is the independent evaluator closeout.
|
||||||
- Every step file contains a sprint contract with objective, required reading, scope, allowed files, explicit non-goals, tests to write first, reference artifacts, acceptance command, evaluator checklist, and handoff requirements.
|
- Every step file contains a sprint contract with objective, required reading, scope, allowed files, explicit non-goals, tests to write first, reference artifacts, acceptance command, evaluator checklist, and handoff requirements.
|
||||||
- Historical phase directory: `phases/1-linear-static-mitc4`
|
- Historical phase directory: `phases/1-linear-static-mitc4`
|
||||||
- Historical phase status: blocked/superseded. Do not resume the old P1-15/P1-16 path unless the user explicitly requests recovery of that exact phase.
|
- Historical phase status: blocked/superseded. Do not resume the old P1-15/P1-16 path unless the user explicitly requests recovery of that exact phase.
|
||||||
@@ -77,7 +77,7 @@ Each gate should be satisfied before moving to the next implementation band unle
|
|||||||
| G2 - Parser and domain | satisfied | Parser subset revalidated in step 3; validation and singular diagnostics revalidated in step 4. | Parser acceptance/rejection tests, validation negative tests, and validation output |
|
| G2 - Parser and domain | satisfied | Parser subset revalidated in step 3; validation and singular diagnostics revalidated in step 4. | Parser acceptance/rejection tests, validation negative tests, and validation output |
|
||||||
| G3 - DOF/math/results infrastructure | satisfied | Core aliases, DOF mapping, validation harness, model diagnostic context, DofManager, sparse-connectivity inputs, full-vector reaction formula, result model metadata, displacement CSV comparator, full-space assembly, reduced projection, sparse-pattern scaffold, and solver adapter boundary were revalidated in steps 2, 5, 6, and 12. | P1R-02, P1R-05, P1R-06, and P1R-12 validation output |
|
| G3 - DOF/math/results infrastructure | satisfied | Core aliases, DOF mapping, validation harness, model diagnostic context, DofManager, sparse-connectivity inputs, full-vector reaction formula, result model metadata, displacement CSV comparator, full-space assembly, reduced projection, sparse-pattern scaffold, and solver adapter boundary were revalidated in steps 2, 5, 6, and 12. | P1R-02, P1R-05, P1R-06, and P1R-12 validation output |
|
||||||
| G4 - MITC4 element readiness | satisfied | MITC4 formulation was rewritten from local papers; Steps 7 through 11 rebuilt geometry/director/local-basis scaffolding, displacement interpolation, direct covariant strain rows, MITC shear tying rows, plane-stress material, convected-to-local transform, `2 x 2 x 2` material integration scaffolding, stiffness/internal force, six-DOF transform, drilling stabilization, and patch/locking-sensitivity tests. | P1R-07 through P1R-11 validation output |
|
| G4 - MITC4 element readiness | satisfied | MITC4 formulation was rewritten from local papers; Steps 7 through 11 rebuilt geometry/director/local-basis scaffolding, displacement interpolation, direct covariant strain rows, MITC shear tying rows, plane-stress material, convected-to-local transform, `2 x 2 x 2` material integration scaffolding, stiffness/internal force, six-DOF transform, drilling stabilization, and patch/locking-sensitivity tests. | P1R-07 through P1R-11 validation output |
|
||||||
| G5 - End-to-end solver | reopened | Linear static path must be revalidated through steps 13 and 14 after the MITC4 rebuild and `quad_02` compatibility path. | Future integration/reference regression output |
|
| G5 - End-to-end solver | partial | Linear static input-to-result workflow is revalidated through step 13; stored `quad_02_phase1` displacement regression remains for step 14. | P1R-13 validation output; future reference regression output |
|
||||||
|
|
||||||
## Phase 1 Implementation Milestones
|
## Phase 1 Implementation Milestones
|
||||||
All milestones are intended to become one or more self-contained sprint contracts or `phases/{phase}/stepN.md` files. Each sprint must follow `docs/HARNESS_ENGINEERING.md` and be evaluated independently.
|
All milestones are intended to become one or more self-contained sprint contracts or `phases/{phase}/stepN.md` files. Each sprint must follow `docs/HARNESS_ENGINEERING.md` and be evaluated independently.
|
||||||
@@ -94,7 +94,7 @@ All milestones are intended to become one or more self-contained sprint contract
|
|||||||
| P1R-10 | completed | MITC4 generator | Assemble MITC4 stiffness/internal force with six-DOF transform and drilling stabilization. | P1R-09, P1R-05 | Symmetry, rigid body, drilling sensitivity tests |
|
| P1R-10 | completed | MITC4 generator | Assemble MITC4 stiffness/internal force with six-DOF transform and drilling stabilization. | P1R-09, P1R-05 | Symmetry, rigid body, drilling sensitivity tests |
|
||||||
| P1R-11 | completed | verification generator | Add MITC4 patch, locking-sensitivity, and benchmark tests. | P1R-10 | Membrane/bending/shear/twist/locking tests |
|
| P1R-11 | completed | verification generator | Add MITC4 patch, locking-sensitivity, and benchmark tests. | P1R-10 | Membrane/bending/shear/twist/locking tests |
|
||||||
| P1R-12 | completed | assembly generator | Rebuild assembly, solver adapter boundary, constrained solve, and full-vector RF recovery. | P1R-05, P1R-10 | Assembly and full-vector reaction tests |
|
| P1R-12 | completed | assembly generator | Rebuild assembly, solver adapter boundary, constrained solve, and full-vector RF recovery. | P1R-05, P1R-10 | Assembly and full-vector reaction tests |
|
||||||
| P1R-13 | pending | analysis generator | Rebuild linear static workflow from input to U/RF result fields. | P1R-03, P1R-04, P1R-06, P1R-12 | End-to-end linear static tests |
|
| P1R-13 | completed | analysis generator | Rebuild linear static workflow from input to U/RF result fields. | P1R-03, P1R-04, P1R-06, P1R-12 | End-to-end linear static tests |
|
||||||
| P1R-14 | pending | reference generator | Run stored reference displacement regression using accepted Phase 1-compatible S4 cases. | P1R-13 | At least one automated CSV displacement regression |
|
| P1R-14 | pending | reference generator | Run stored reference displacement regression using accepted Phase 1-compatible S4 cases. | P1R-13 | At least one automated CSV displacement regression |
|
||||||
| P1R-15 | pending | evaluator | Independent Phase 1 evaluator closeout. | P1R-14 | Pass/fail report, synchronized PLAN/PROGRESS |
|
| P1R-15 | pending | evaluator | Independent Phase 1 evaluator closeout. | P1R-14 | Pass/fail report, synchronized PLAN/PROGRESS |
|
||||||
|
|
||||||
|
|||||||
+28
-1
@@ -13,10 +13,37 @@ Every new agent session must read this file together with `PLAN.md` before plann
|
|||||||
- Do not remove history unless the user explicitly asks for archival cleanup.
|
- Do not remove history unless the user explicitly asks for archival cleanup.
|
||||||
|
|
||||||
## Current Status
|
## Current Status
|
||||||
Phase 1 has a new rebaseline phase definition in `phases/1-linear-static-mitc4-rebaseline`. Steps 0 through 12 are complete. `quad_02_phase1.inp` is now the normalized Phase 1-compatible input path for the stored `quad_02` S4 reference pair, while the original `quad_02.inp` remains preserved unsupported provenance. Core numeric aliases, DOF mapping, validation harness, model diagnostic context, the Phase 1 parser/domain subset, validation/singular diagnostics, DofManager/reaction foundation, minimum result model metadata, displacement CSV comparator foundation, MITC4 geometry/director scaffolding, MITC4 displacement/strain/tying row scaffolding, MITC4 material/transform/integration scaffolding, MITC4 stiffness/drilling/internal-force scaffolding, MITC4 patch/locking-sensitivity tests, full-space assembly, reduced projection, sparse-pattern scaffold, solver adapter injection, and full-vector internal/reaction force state have been revalidated. The old `phases/1-linear-static-mitc4` path is historical and superseded after the MITC4 formulation reset.
|
Phase 1 has a new rebaseline phase definition in `phases/1-linear-static-mitc4-rebaseline`. Steps 0 through 13 are complete. `quad_02_phase1.inp` is now the normalized Phase 1-compatible input path for the stored `quad_02` S4 reference pair, while the original `quad_02.inp` remains preserved unsupported provenance. Core numeric aliases, DOF mapping, validation harness, model diagnostic context, the Phase 1 parser/domain subset, validation/singular diagnostics, DofManager/reaction foundation, minimum result model metadata, displacement CSV comparator foundation, MITC4 geometry/director scaffolding, MITC4 displacement/strain/tying row scaffolding, MITC4 material/transform/integration scaffolding, MITC4 stiffness/drilling/internal-force scaffolding, MITC4 patch/locking-sensitivity tests, full-space assembly, reduced projection, sparse-pattern scaffold, solver adapter injection, full-vector internal/reaction force state, active AnalysisModel construction, and input-to-AnalysisState-to-U/RF result workflow have been revalidated. The old `phases/1-linear-static-mitc4` path is historical and superseded after the MITC4 formulation reset.
|
||||||
|
|
||||||
## Completed Work
|
## Completed Work
|
||||||
|
|
||||||
|
### 2026-05-04 - P1R-13 linear static workflow completed
|
||||||
|
Author: Codex
|
||||||
|
|
||||||
|
Changed files:
|
||||||
|
- `include/fesa/fesa.hpp`
|
||||||
|
- `tests/test_main.cpp`
|
||||||
|
- `phases/1-linear-static-mitc4-rebaseline/index.json`
|
||||||
|
- `PLAN.md`
|
||||||
|
- `PROGRESS.md`
|
||||||
|
|
||||||
|
Summary:
|
||||||
|
- Added an explicit `AnalysisModel` value object for the active Phase 1 linear static step, including active element ids, boundary/load indices, shell section indices, and material keys.
|
||||||
|
- Added `buildLinearStaticAnalysisModel()` and routed `LinearStaticAnalysis` through it before DOF mapping, assembly, solve, state update, and result writing.
|
||||||
|
- Preserved the one-step Phase 1 execution boundary with a diagnostic for multiple parsed steps instead of silently executing them.
|
||||||
|
- Added `runLinearStaticInputString()` so parser diagnostics, domain validation diagnostics, active model construction, solve, `AnalysisState`, and `U`/`RF` result writing are exercised as one workflow.
|
||||||
|
- Extended the in-memory result model with element type metadata and ensured result writing uses the active model step name.
|
||||||
|
- Added tests for active model activation without mutating `Domain`, multiple-step rejection, input-to-result `U`/`RF` schema readiness, full-vector reaction balance, and parse/validation error propagation through the workflow.
|
||||||
|
|
||||||
|
Verification:
|
||||||
|
- First ran `python scripts/validate_workspace.py` after adding Step 13 tests; it failed as expected because `AnalysisModel` and input-string workflow APIs did not exist yet.
|
||||||
|
- After implementation, `python scripts/validate_workspace.py` configured CMake, built `fesa_core` and `fesa_tests`, and ran CTest successfully.
|
||||||
|
- CTest result: 1 test executable passed.
|
||||||
|
|
||||||
|
Follow-up:
|
||||||
|
- Continue with P1R-14 stored-reference displacement regression using `references/quad_02_phase1.inp` and `references/quad_02_displacements.csv`.
|
||||||
|
- Step 13 intentionally did not add nonlinear, dynamic, pressure, thermal, HDF5, multi-step execution, or stored Abaqus CSV regression behavior.
|
||||||
|
|
||||||
### 2026-05-04 - P1R-12 assembly sparse solver path completed
|
### 2026-05-04 - P1R-12 assembly sparse solver path completed
|
||||||
Author: Codex
|
Author: Codex
|
||||||
|
|
||||||
|
|||||||
+93
-2
@@ -229,6 +229,14 @@ struct Node {
|
|||||||
|
|
||||||
enum class ElementType { MITC4 };
|
enum class ElementType { MITC4 };
|
||||||
|
|
||||||
|
inline std::string elementTypeLabel(ElementType type) {
|
||||||
|
switch (type) {
|
||||||
|
case ElementType::MITC4:
|
||||||
|
return "MITC4";
|
||||||
|
}
|
||||||
|
return "UNKNOWN";
|
||||||
|
}
|
||||||
|
|
||||||
struct Element {
|
struct Element {
|
||||||
GlobalId id = 0;
|
GlobalId id = 0;
|
||||||
ElementType type = ElementType::MITC4;
|
ElementType type = ElementType::MITC4;
|
||||||
@@ -953,6 +961,61 @@ inline std::vector<Diagnostic> validateDomain(const Domain& domain) {
|
|||||||
return diagnostics;
|
return diagnostics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct AnalysisModel {
|
||||||
|
StepDefinition step;
|
||||||
|
std::vector<GlobalId> active_element_ids;
|
||||||
|
std::vector<std::size_t> active_boundary_condition_indices;
|
||||||
|
std::vector<std::size_t> active_load_indices;
|
||||||
|
std::vector<std::size_t> active_shell_section_indices;
|
||||||
|
std::vector<std::string> active_material_keys;
|
||||||
|
std::vector<Diagnostic> diagnostics;
|
||||||
|
|
||||||
|
bool ok() const {
|
||||||
|
return !hasError(diagnostics);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline AnalysisModel buildLinearStaticAnalysisModel(const Domain& domain, LocalIndex step_index = 0) {
|
||||||
|
AnalysisModel model;
|
||||||
|
if (domain.steps.empty()) {
|
||||||
|
model.step = {"Step-1", "linear_static"};
|
||||||
|
} else {
|
||||||
|
if (step_index < 0 || step_index >= static_cast<LocalIndex>(domain.steps.size())) {
|
||||||
|
model.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-ANALYSIS-STEP-INDEX",
|
||||||
|
"Requested analysis step index is out of range", "analysis model"));
|
||||||
|
model.step = domain.steps.front();
|
||||||
|
} else {
|
||||||
|
model.step = domain.steps[static_cast<std::size_t>(step_index)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (domain.steps.size() > 1) {
|
||||||
|
model.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-ANALYSIS-MULTIPLE-STEPS",
|
||||||
|
"Phase 1 execution supports one active linear static step", "analysis model"));
|
||||||
|
}
|
||||||
|
if (model.step.analysis_type != "linear_static") {
|
||||||
|
model.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-ANALYSIS-UNSUPPORTED-STEP",
|
||||||
|
"Only linear static steps are supported in Phase 1", "analysis model"));
|
||||||
|
}
|
||||||
|
for (const auto& [element_id, element] : domain.elements) {
|
||||||
|
(void)element;
|
||||||
|
model.active_element_ids.push_back(element_id);
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i < domain.boundary_conditions.size(); ++i) {
|
||||||
|
model.active_boundary_condition_indices.push_back(i);
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i < domain.loads.size(); ++i) {
|
||||||
|
model.active_load_indices.push_back(i);
|
||||||
|
}
|
||||||
|
for (std::size_t i = 0; i < domain.shell_sections.size(); ++i) {
|
||||||
|
model.active_shell_section_indices.push_back(i);
|
||||||
|
}
|
||||||
|
for (const auto& [material_key, material] : domain.materials) {
|
||||||
|
(void)material;
|
||||||
|
model.active_material_keys.push_back(material_key);
|
||||||
|
}
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
struct DofAddress {
|
struct DofAddress {
|
||||||
GlobalId node_id = 0;
|
GlobalId node_id = 0;
|
||||||
Dof dof = Dof::UX;
|
Dof dof = Dof::UX;
|
||||||
@@ -2291,6 +2354,7 @@ struct ResultFile {
|
|||||||
std::vector<GlobalId> node_ids;
|
std::vector<GlobalId> node_ids;
|
||||||
std::vector<Vec3> coordinates;
|
std::vector<Vec3> coordinates;
|
||||||
std::vector<GlobalId> element_ids;
|
std::vector<GlobalId> element_ids;
|
||||||
|
std::vector<std::string> element_types;
|
||||||
std::vector<std::array<GlobalId, 4>> connectivity;
|
std::vector<std::array<GlobalId, 4>> connectivity;
|
||||||
std::vector<ResultStep> steps;
|
std::vector<ResultStep> steps;
|
||||||
};
|
};
|
||||||
@@ -2298,6 +2362,12 @@ struct ResultFile {
|
|||||||
class InMemoryResultsWriter {
|
class InMemoryResultsWriter {
|
||||||
public:
|
public:
|
||||||
void writeLinearStatic(const Domain& domain, const DofManager& dofs, const std::vector<Real>& u_full, const std::vector<Real>& rf_full) {
|
void writeLinearStatic(const Domain& domain, const DofManager& dofs, const std::vector<Real>& u_full, const std::vector<Real>& rf_full) {
|
||||||
|
const auto model = buildLinearStaticAnalysisModel(domain);
|
||||||
|
writeLinearStatic(domain, model, dofs, u_full, rf_full);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeLinearStatic(const Domain& domain, const AnalysisModel& model, const DofManager& dofs,
|
||||||
|
const std::vector<Real>& u_full, const std::vector<Real>& rf_full) {
|
||||||
result_ = ResultFile{};
|
result_ = ResultFile{};
|
||||||
for (const auto& [node_id, node] : domain.nodes) {
|
for (const auto& [node_id, node] : domain.nodes) {
|
||||||
result_.node_ids.push_back(node_id);
|
result_.node_ids.push_back(node_id);
|
||||||
@@ -2305,10 +2375,11 @@ class InMemoryResultsWriter {
|
|||||||
}
|
}
|
||||||
for (const auto& [element_id, element] : domain.elements) {
|
for (const auto& [element_id, element] : domain.elements) {
|
||||||
result_.element_ids.push_back(element_id);
|
result_.element_ids.push_back(element_id);
|
||||||
|
result_.element_types.push_back(elementTypeLabel(element.type));
|
||||||
result_.connectivity.push_back(element.node_ids);
|
result_.connectivity.push_back(element.node_ids);
|
||||||
}
|
}
|
||||||
ResultStep step;
|
ResultStep step;
|
||||||
step.name = domain.steps.empty() ? "Step-1" : domain.steps.front().name;
|
step.name = model.step.name.empty() ? "Step-1" : model.step.name;
|
||||||
ResultFrame frame;
|
ResultFrame frame;
|
||||||
frame.frame_id = 0;
|
frame.frame_id = 0;
|
||||||
frame.field_outputs["U"] = buildNodalField("U", displacementComponentLabels(), "Nodal displacement and rotation", domain, dofs, u_full);
|
frame.field_outputs["U"] = buildNodalField("U", displacementComponentLabels(), "Nodal displacement and rotation", domain, dofs, u_full);
|
||||||
@@ -2355,6 +2426,7 @@ struct AnalysisState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct AnalysisResult {
|
struct AnalysisResult {
|
||||||
|
AnalysisModel model;
|
||||||
AnalysisState state;
|
AnalysisState state;
|
||||||
ResultFile result_file;
|
ResultFile result_file;
|
||||||
std::vector<Diagnostic> diagnostics;
|
std::vector<Diagnostic> diagnostics;
|
||||||
@@ -2391,6 +2463,11 @@ class LinearStaticAnalysis final : public Analysis {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
void solve(const Domain& domain, AnalysisResult& result) const override {
|
void solve(const Domain& domain, AnalysisResult& result) const override {
|
||||||
|
result.model = buildLinearStaticAnalysisModel(domain);
|
||||||
|
result.diagnostics.insert(result.diagnostics.end(), result.model.diagnostics.begin(), result.model.diagnostics.end());
|
||||||
|
if (hasError(result.diagnostics)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
DofManager dofs(domain);
|
DofManager dofs(domain);
|
||||||
if (dofs.freeDofCount() == 0) {
|
if (dofs.freeDofCount() == 0) {
|
||||||
result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-SINGULAR-NO-FREE-DOFS",
|
result.diagnostics.push_back(makeDiagnostic(Severity::Error, "FESA-SINGULAR-NO-FREE-DOFS",
|
||||||
@@ -2424,7 +2501,7 @@ class LinearStaticAnalysis final : public Analysis {
|
|||||||
result.state.reaction_full = recoverFullReaction(assembly.k_full, result.state.u_full, result.state.f_external_full);
|
result.state.reaction_full = recoverFullReaction(assembly.k_full, result.state.u_full, result.state.f_external_full);
|
||||||
result.state.converged = true;
|
result.state.converged = true;
|
||||||
InMemoryResultsWriter writer;
|
InMemoryResultsWriter writer;
|
||||||
writer.writeLinearStatic(domain, dofs, result.state.u_full, result.state.reaction_full);
|
writer.writeLinearStatic(domain, result.model, dofs, result.state.u_full, result.state.reaction_full);
|
||||||
result.result_file = writer.result();
|
result.result_file = writer.result();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2437,6 +2514,20 @@ class LinearStaticAnalysis final : public Analysis {
|
|||||||
const LinearSolver* solver_ = nullptr;
|
const LinearSolver* solver_ = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline AnalysisResult runLinearStaticInputString(const std::string& text,
|
||||||
|
const std::string& source_name = "<memory>",
|
||||||
|
const LinearSolver* solver = nullptr) {
|
||||||
|
AbaqusInputParser parser;
|
||||||
|
const auto parsed = parser.parseString(text, source_name);
|
||||||
|
if (!parsed.ok()) {
|
||||||
|
AnalysisResult result;
|
||||||
|
result.diagnostics = parsed.diagnostics;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
LinearStaticAnalysis analysis(solver);
|
||||||
|
return analysis.run(parsed.domain);
|
||||||
|
}
|
||||||
|
|
||||||
struct CsvDisplacementRow {
|
struct CsvDisplacementRow {
|
||||||
GlobalId node_id = 0;
|
GlobalId node_id = 0;
|
||||||
std::array<Real, 6> values{};
|
std::array<Real, 6> values{};
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
{ "step": 10, "name": "mitc4-stiffness-drilling", "status": "completed" },
|
{ "step": 10, "name": "mitc4-stiffness-drilling", "status": "completed" },
|
||||||
{ "step": 11, "name": "mitc4-patch-benchmark-tests", "status": "completed" },
|
{ "step": 11, "name": "mitc4-patch-benchmark-tests", "status": "completed" },
|
||||||
{ "step": 12, "name": "assembly-sparse-solver-path", "status": "completed" },
|
{ "step": 12, "name": "assembly-sparse-solver-path", "status": "completed" },
|
||||||
{ "step": 13, "name": "linear-static-workflow", "status": "pending" },
|
{ "step": 13, "name": "linear-static-workflow", "status": "completed" },
|
||||||
{ "step": 14, "name": "stored-reference-regression", "status": "pending" },
|
{ "step": 14, "name": "stored-reference-regression", "status": "pending" },
|
||||||
{ "step": 15, "name": "phase1-evaluator-closeout", "status": "pending" }
|
{ "step": 15, "name": "phase1-evaluator-closeout", "status": "pending" }
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -327,6 +327,24 @@ FESA_TEST(parser_accepts_phase1_subset) {
|
|||||||
FESA_CHECK(parsed.domain.loads.size() == 2);
|
FESA_CHECK(parsed.domain.loads.size() == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FESA_TEST(analysis_model_activates_single_linear_static_step) {
|
||||||
|
auto domain = parsedPhase1Domain();
|
||||||
|
const auto node_count = domain.nodes.size();
|
||||||
|
const auto element_count = domain.elements.size();
|
||||||
|
const auto model = fesa::buildLinearStaticAnalysisModel(domain);
|
||||||
|
|
||||||
|
FESA_CHECK(model.ok());
|
||||||
|
FESA_CHECK(model.step.name == "Step-1");
|
||||||
|
FESA_CHECK(model.step.analysis_type == "linear_static");
|
||||||
|
FESA_CHECK(model.active_element_ids == std::vector<fesa::GlobalId>({1}));
|
||||||
|
FESA_CHECK(model.active_boundary_condition_indices == std::vector<std::size_t>({0, 1, 2}));
|
||||||
|
FESA_CHECK(model.active_load_indices == std::vector<std::size_t>({0, 1}));
|
||||||
|
FESA_CHECK(model.active_shell_section_indices == std::vector<std::size_t>({0}));
|
||||||
|
FESA_CHECK(model.active_material_keys == std::vector<std::string>({"steel"}));
|
||||||
|
FESA_CHECK(domain.nodes.size() == node_count);
|
||||||
|
FESA_CHECK(domain.elements.size() == element_count);
|
||||||
|
}
|
||||||
|
|
||||||
FESA_TEST(parser_accepts_repeated_and_generated_sets) {
|
FESA_TEST(parser_accepts_repeated_and_generated_sets) {
|
||||||
const std::string text = R"inp(
|
const std::string text = R"inp(
|
||||||
*Node
|
*Node
|
||||||
@@ -519,6 +537,21 @@ FESA_TEST(quad02_phase1_normalized_input_uses_supported_subset) {
|
|||||||
FESA_CHECK(parsed.domain.shell_sections.front().thickness == 1.0);
|
FESA_CHECK(parsed.domain.shell_sections.front().thickness == 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FESA_TEST(analysis_model_rejects_multiple_steps_for_phase1_execution) {
|
||||||
|
const std::string text = phase1Input() + R"inp(
|
||||||
|
*Step, name=Step-2
|
||||||
|
*Static
|
||||||
|
*End Step
|
||||||
|
)inp";
|
||||||
|
fesa::AbaqusInputParser parser;
|
||||||
|
auto parsed = parser.parseString(text);
|
||||||
|
FESA_CHECK(parsed.ok());
|
||||||
|
FESA_CHECK(parsed.domain.steps.size() == 2);
|
||||||
|
const auto model = fesa::buildLinearStaticAnalysisModel(parsed.domain);
|
||||||
|
FESA_CHECK(!model.ok());
|
||||||
|
FESA_CHECK(fesa::containsDiagnostic(model.diagnostics, "FESA-ANALYSIS-MULTIPLE-STEPS"));
|
||||||
|
}
|
||||||
|
|
||||||
FESA_TEST(domain_validation_reports_missing_property_and_targets) {
|
FESA_TEST(domain_validation_reports_missing_property_and_targets) {
|
||||||
fesa::Domain domain;
|
fesa::Domain domain;
|
||||||
domain.nodes[1] = {1, {0, 0, 0}};
|
domain.nodes[1] = {1, {0, 0, 0}};
|
||||||
@@ -881,6 +914,7 @@ FESA_TEST(results_writer_uses_step_frame_fields_for_u_and_rf) {
|
|||||||
FESA_CHECK(result.sign_convention == "Abaqus-compatible");
|
FESA_CHECK(result.sign_convention == "Abaqus-compatible");
|
||||||
FESA_CHECK(result.precision == "double");
|
FESA_CHECK(result.precision == "double");
|
||||||
FESA_CHECK(result.index_type == "int64");
|
FESA_CHECK(result.index_type == "int64");
|
||||||
|
FESA_CHECK(result.element_types == std::vector<std::string>({"MITC4"}));
|
||||||
FESA_CHECK(result.steps.size() == 1);
|
FESA_CHECK(result.steps.size() == 1);
|
||||||
FESA_CHECK(result.steps[0].name == "Step-1");
|
FESA_CHECK(result.steps[0].name == "Step-1");
|
||||||
FESA_CHECK(result.steps[0].frames.size() == 1);
|
FESA_CHECK(result.steps[0].frames.size() == 1);
|
||||||
@@ -1705,6 +1739,105 @@ FESA_TEST(linear_static_analysis_solves_u_and_recovers_full_vector_rf) {
|
|||||||
FESA_CHECK_NEAR(total_rf_z, 2.0, 1.0e-8);
|
FESA_CHECK_NEAR(total_rf_z, 2.0, 1.0e-8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FESA_TEST(linear_static_input_workflow_produces_schema_ready_u_and_rf_fields) {
|
||||||
|
const auto result = fesa::runLinearStaticInputString(phase1Input(), "phase1-workflow.inp");
|
||||||
|
FESA_CHECK(result.ok());
|
||||||
|
FESA_CHECK(result.model.ok());
|
||||||
|
FESA_CHECK(result.model.step.name == "Step-1");
|
||||||
|
FESA_CHECK(result.model.active_element_ids == std::vector<fesa::GlobalId>({1}));
|
||||||
|
FESA_CHECK(result.state.converged);
|
||||||
|
FESA_CHECK(result.state.u_full.size() == 24);
|
||||||
|
FESA_CHECK(result.state.f_external_full.size() == 24);
|
||||||
|
FESA_CHECK(result.state.f_internal_full.size() == 24);
|
||||||
|
FESA_CHECK(result.state.reaction_full.size() == 24);
|
||||||
|
|
||||||
|
const auto& result_file = result.result_file;
|
||||||
|
FESA_CHECK(result_file.schema_name == "FESA_RESULTS");
|
||||||
|
FESA_CHECK(result_file.schema_version == 1);
|
||||||
|
FESA_CHECK(result_file.solver_name == "FESA");
|
||||||
|
FESA_CHECK(result_file.dof_convention == "UX,UY,UZ,RX,RY,RZ");
|
||||||
|
FESA_CHECK(result_file.sign_convention == "Abaqus-compatible");
|
||||||
|
FESA_CHECK(result_file.precision == "double");
|
||||||
|
FESA_CHECK(result_file.index_type == "int64");
|
||||||
|
FESA_CHECK(result_file.node_ids == std::vector<fesa::GlobalId>({1, 2, 3, 4}));
|
||||||
|
FESA_CHECK(result_file.element_ids == std::vector<fesa::GlobalId>({1}));
|
||||||
|
FESA_CHECK(result_file.element_types == std::vector<std::string>({"MITC4"}));
|
||||||
|
FESA_CHECK(result_file.connectivity.front() == (std::array<fesa::GlobalId, 4>{1, 2, 3, 4}));
|
||||||
|
FESA_CHECK(result_file.steps.size() == 1);
|
||||||
|
FESA_CHECK(result_file.steps[0].name == "Step-1");
|
||||||
|
FESA_CHECK(result_file.steps[0].frames.size() == 1);
|
||||||
|
|
||||||
|
const auto& frame = result_file.steps[0].frames[0];
|
||||||
|
FESA_CHECK(frame.frame_id == 0);
|
||||||
|
FESA_CHECK(frame.increment == 1);
|
||||||
|
FESA_CHECK(frame.iteration == 0);
|
||||||
|
FESA_CHECK(frame.converged);
|
||||||
|
FESA_CHECK_NEAR(frame.step_time, 1.0, 1.0e-15);
|
||||||
|
FESA_CHECK_NEAR(frame.total_time, 1.0, 1.0e-15);
|
||||||
|
FESA_CHECK(frame.field_outputs.count("U") == 1);
|
||||||
|
FESA_CHECK(frame.field_outputs.count("RF") == 1);
|
||||||
|
const auto& u = frame.field_outputs.at("U");
|
||||||
|
const auto& rf = frame.field_outputs.at("RF");
|
||||||
|
FESA_CHECK(u.component_labels == fesa::displacementComponentLabels());
|
||||||
|
FESA_CHECK(rf.component_labels == fesa::reactionComponentLabels());
|
||||||
|
FESA_CHECK(u.entity_ids == result_file.node_ids);
|
||||||
|
FESA_CHECK(rf.entity_ids == result_file.node_ids);
|
||||||
|
FESA_CHECK(u.values.size() == result_file.node_ids.size());
|
||||||
|
FESA_CHECK(rf.values.size() == result_file.node_ids.size());
|
||||||
|
|
||||||
|
fesa::Real total_rf_z = 0.0;
|
||||||
|
for (const auto& values : rf.values) {
|
||||||
|
total_rf_z += values[2];
|
||||||
|
}
|
||||||
|
FESA_CHECK_NEAR(total_rf_z, 2.0, 1.0e-8);
|
||||||
|
FESA_CHECK_NEAR(u.values[0][2], 0.0, 1.0e-15);
|
||||||
|
FESA_CHECK_NEAR(u.values[3][2], 0.0, 1.0e-15);
|
||||||
|
}
|
||||||
|
|
||||||
|
FESA_TEST(linear_static_input_workflow_routes_parse_errors) {
|
||||||
|
const std::string text = R"inp(
|
||||||
|
*Part, name=P1
|
||||||
|
*Node
|
||||||
|
1, 0, 0, 0
|
||||||
|
*Element, type=S4R
|
||||||
|
1, 1, 2, 3, 4
|
||||||
|
*Step, nlgeom=YES
|
||||||
|
*Static
|
||||||
|
*End Step
|
||||||
|
)inp";
|
||||||
|
const auto result = fesa::runLinearStaticInputString(text, "unsupported-workflow.inp");
|
||||||
|
FESA_CHECK(!result.ok());
|
||||||
|
FESA_CHECK(!result.state.converged);
|
||||||
|
FESA_CHECK(fesa::containsDiagnostic(result.diagnostics, "FESA-PARSE-UNSUPPORTED-KEYWORD"));
|
||||||
|
FESA_CHECK(fesa::containsDiagnostic(result.diagnostics, "FESA-PARSE-UNSUPPORTED-ELEMENT"));
|
||||||
|
FESA_CHECK(fesa::containsDiagnostic(result.diagnostics, "FESA-PARSE-UNSUPPORTED-NLGEOM"));
|
||||||
|
}
|
||||||
|
|
||||||
|
FESA_TEST(linear_static_input_workflow_routes_validation_errors_without_bypassing_validator) {
|
||||||
|
const std::string text = R"inp(
|
||||||
|
*Node
|
||||||
|
1, 0, 0, 0
|
||||||
|
2, 1, 0, 0
|
||||||
|
3, 1, 1, 0
|
||||||
|
4, 0, 1, 0
|
||||||
|
*Element, type=S4
|
||||||
|
1, 1, 2, 3, 4
|
||||||
|
*Nset, nset=FIXED
|
||||||
|
1, 4
|
||||||
|
*Boundary
|
||||||
|
FIXED, 1, 6
|
||||||
|
*Cload
|
||||||
|
2, 3, -1
|
||||||
|
*Step, name=Step-1
|
||||||
|
*Static
|
||||||
|
*End Step
|
||||||
|
)inp";
|
||||||
|
const auto result = fesa::runLinearStaticInputString(text, "missing-property-workflow.inp");
|
||||||
|
FESA_CHECK(!result.ok());
|
||||||
|
FESA_CHECK(!result.state.converged);
|
||||||
|
FESA_CHECK(fesa::containsDiagnostic(result.diagnostics, "FESA-VALIDATION-MISSING-PROPERTY"));
|
||||||
|
}
|
||||||
|
|
||||||
FESA_TEST(linear_static_analysis_uses_solver_adapter_and_reconstructs_full_vectors) {
|
FESA_TEST(linear_static_analysis_uses_solver_adapter_and_reconstructs_full_vectors) {
|
||||||
auto domain = parsedPhase1Domain();
|
auto domain = parsedPhase1Domain();
|
||||||
RecordingSolver solver({0.25, -0.50});
|
RecordingSolver solver({0.25, -0.50});
|
||||||
|
|||||||
Reference in New Issue
Block a user