diff --git a/PLAN.md b/PLAN.md index 26be66c..0df460e 100644 --- a/PLAN.md +++ b/PLAN.md @@ -12,7 +12,7 @@ - Phase directory: `phases/uel-3d-euler-beam` - Current planned entry point: `python scripts/execute.py uel-3d-euler-beam` -- First pending action: correct Abaqus-facing `UEL` wrapper/interface mismatch before `step8` validation readiness +- First pending action: execute `step8` validation readiness using the corrected Abaqus-facing `UEL` wrapper/interface ## Planned Steps @@ -46,4 +46,4 @@ - Confirm exact first-scope beam assumptions in the requirements step. - Decide `PROPS`/`JPROPS` ordering and orientation-vector convention in the interface step. - Decide exact no-Abaqus Fortran source and test file layout in the reference model step. -- Add a top-level Abaqus-callable `SUBROUTINE UEL(...)` wrapper and correct the `VARIABLES`/`NSVARS` policy before external Abaqus validation readiness. +- External Abaqus validation artifacts still need to be supplied by the user before solver-result comparison. diff --git a/PROGRESS.md b/PROGRESS.md index 15ca58d..feea834 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -5,8 +5,8 @@ - Active objective: 3D Euler-Bernoulli beam Abaqus/Standard `UEL` - Active phase: `phases/uel-3d-euler-beam` - Active owner: unassigned -- Current status: completed ad hoc UEL ABI research correction note after step 7 implementation review -- Next action: correct the Abaqus-facing UEL wrapper/interface mismatch before executing `phases/uel-3d-euler-beam/step8.md` validation-readiness step +- Current status: completed Abaqus-facing UEL wrapper/interface correction after step 7 implementation review +- Next action: execute `phases/uel-3d-euler-beam/step8.md` validation-readiness step with corrected `VARIABLES=1` / `NSVARS>=1` contract ## Completed @@ -54,6 +54,11 @@ - Confirmed current source has no top-level Abaqus-callable `SUBROUTINE UEL(...)` wrapper under `src/fortran`. - Confirmed `uel3deb_abi_static` is a no-Abaqus adapter, not the manual Abaqus UEL ABI. - Identified a likely Abaqus input contract issue: documented `*USER ELEMENT, VARIABLES` must be greater than zero, so the current `NSVARS=0` / `VARIABLES=0` contract should be corrected before external Abaqus validation. +- Completed Abaqus-facing UEL ABI correction. + - Added `src/fortran/uel_3d_euler_beam_uel.for` with top-level fixed-form `SUBROUTINE UEL(...)`, `ABA_PARAM.INC`, manual Abaqus array dimensions, adapter delegation, and fatal `XIT` path. + - Updated `uel3deb_abi_static` to accept `NSVARS>=1`, use explicit/manual-style array bounds, and validate `LFLAGS(1)` and `LFLAGS(4)`. + - Updated no-Abaqus tests and wrapper source-smoke tests for the corrected contract. + - Updated `docs/io-definitions/uel-3d-euler-beam.md`, `docs/reference-models/uel-3d-euler-beam.md`, and `docs/corrections/uel-3d-euler-beam-uel-abi-correction.md`. ## In Progress @@ -65,17 +70,21 @@ ## Last Verification -Latest verification after creating the UEL ABI research document: +Latest verification after correcting the Abaqus-facing UEL wrapper/interface: ```bash python -m unittest discover -s scripts -p "test_*.py" +python scripts/validate_reference_artifacts.py +python scripts/validate_fortran.py python scripts/validate_workspace.py git diff --check ``` Result: all passed. -- `python -m unittest discover -s scripts -p "test_*.py"`: 57 tests passed. -- `python scripts/validate_workspace.py`: reference artifact validation succeeded; six Fortran manifest executables compiled and passed with Intel `ifx`; workspace validation succeeded. +- `python -m unittest discover -s scripts -p "test_*.py"`: 60 tests passed. +- `python scripts/validate_reference_artifacts.py`: reference artifact metadata validation succeeded. +- `python scripts/validate_fortran.py`: six Fortran manifest executables compiled and passed with Intel `ifx`. +- `python scripts/validate_workspace.py`: reference validation and Fortran validation succeeded. - `git diff --check`: passed with line-ending warnings only. Previous verification after completing step 7 Fortran implementation: @@ -98,5 +107,6 @@ Result: all passed. - Read `AGENTS.md`, `PLAN.md`, `PROGRESS.md`, and `WORKNOTE.md`. - Confirm no other owner is active in this file. -- Correct the Abaqus-facing `UEL` wrapper/interface mismatch before `phases/uel-3d-euler-beam/step8.md`. +- Proceed to `phases/uel-3d-euler-beam/step8.md`. +- Use `VARIABLES=1` in external Abaqus `*USER ELEMENT` examples; the adapter now requires `NSVARS>=1`. - Update this file when step status changes or before handing off. diff --git a/WORKNOTE.md b/WORKNOTE.md index cbeb191..ec5bec2 100644 --- a/WORKNOTE.md +++ b/WORKNOTE.md @@ -17,3 +17,5 @@ - 같은 조사에서 현재 `src/fortran`에는 Abaqus가 직접 호출할 top-level external `SUBROUTINE UEL(...)` wrapper가 없고, `uel3deb_abi_static`은 no-Abaqus adapter일 뿐임을 확인했다. 다음 구현 보정에서는 manual signature와 `ABA_PARAM.INC` include를 보존하는 wrapper가 필요하다. - `docs/io-definitions/uel-3d-euler-beam.md`의 `VARIABLES=0`/`NSVARS=0` 정책은 Abaqus `*USER ELEMENT` 문서의 `VARIABLES` 값이 0보다 커야 한다는 규칙과 충돌할 가능성이 있다. 실제 Abaqus-facing 계약은 `VARIABLES=1` 이상 또는 target-version 근거가 있는 예외로 보정해야 한다. - PowerShell에서 `git add ... && git status --short`를 실행하면 이 환경에서는 `&&`가 statement separator로 인식되지 않아 실패한다. 명령은 분리해서 실행한다. +- 2026-06-12 UEL ABI correction에서 TDD guard는 production file basename token을 `tests/` 아래 테스트 파일명에서 찾는다. `scripts/test_uel_3d_euler_beam_uel.py`만으로는 `src/fortran/uel_3d_euler_beam_uel.for` 변경이 차단되어, `tests/test_uel_3d_euler_beam_uel.py` shim을 함께 추가했다. +- `tests/fortran/uel_3d_euler_beam/test_invalid_inputs.f90`의 helper 인자 순서를 바꿀 때 `NDLOAD` 케이스가 `lflag3=0`으로 들어가 `E016`보다 `E015`가 먼저 발생했다. negative test는 목표 조건 외의 request flag를 모두 유효값으로 유지해야 한다. diff --git a/docs/abaqus-uel-subroutines-research.md b/docs/abaqus-uel-subroutines-research.md index 2252584..99ceccc 100644 --- a/docs/abaqus-uel-subroutines-research.md +++ b/docs/abaqus-uel-subroutines-research.md @@ -3,10 +3,14 @@ ## Metadata - feature_id: abaqus-uel-subroutines - related_feature: `uel-3d-euler-beam` -- status: ready-for-interface-correction +- status: implemented-follow-up - owner_agent: research-agent - date: 2026-06-12 +## Follow-up Status + +The findings below captured the pre-correction state after the initial step 7 implementation. The follow-up correction in `docs/corrections/uel-3d-euler-beam-uel-abi-correction.md` adds the missing Abaqus-callable wrapper, changes the Abaqus-facing state-variable policy to `NSVARS>=1` / `VARIABLES=1`, and adds `LFLAGS(1)` and `LFLAGS(4)` validation. + ## Research Questions - Abaqus/Standard가 실제로 호출하는 `UEL` subroutine의 positional ABI는 무엇인가? - `RHS`, `AMATRX`, `SVARS`, `ENERGY`, `PNEWDT`, `LFLAGS`의 update 책임은 무엇인가? diff --git a/docs/build-test-reports/uel-3d-euler-beam-green.md b/docs/build-test-reports/uel-3d-euler-beam-green.md index f510c18..9d9d639 100644 --- a/docs/build-test-reports/uel-3d-euler-beam-green.md +++ b/docs/build-test-reports/uel-3d-euler-beam-green.md @@ -10,6 +10,8 @@ ## Implementation Scope +Post-step ABI correction note: the original step 7 implementation deliberately deferred the fixed-form Abaqus `UEL` wrapper. The follow-up correction report `docs/corrections/uel-3d-euler-beam-uel-abi-correction.md` supersedes that deferred-wrapper status and records the added wrapper, `NSVARS>=1` policy, and `LFLAGS(1)/(4)` checks. + Production source added in this step: - `src/fortran/uel_3d_euler_beam_kernel.f90` diff --git a/docs/corrections/uel-3d-euler-beam-uel-abi-correction.md b/docs/corrections/uel-3d-euler-beam-uel-abi-correction.md new file mode 100644 index 0000000..f064f27 --- /dev/null +++ b/docs/corrections/uel-3d-euler-beam-uel-abi-correction.md @@ -0,0 +1,78 @@ +# 3D Euler-Bernoulli Beam UEL ABI Correction Report + +## Metadata +- feature_id: uel-3d-euler-beam +- source_failure_report: `docs/abaqus-uel-subroutines-research.md` +- source_implementation_report: `docs/build-test-reports/uel-3d-euler-beam-green.md` +- source_implementation_plan: N/A; correction follows the post-review research findings +- status: corrected-for-build-test +- owner_agent: correction-agent +- date: 2026-06-12 + +## Failure Triage +- classification: upstream-contract plus implementation defect +- first_failed_command: `python -m unittest scripts.test_uel_3d_euler_beam_uel`; `python scripts/validate_fortran.py` +- failed_target_or_test: missing Abaqus `UEL` wrapper; adapter rejected `NSVARS=1` +- evidence_tail: wrapper source-smoke failed because `src/fortran/uel_3d_euler_beam_uel.for` was missing; Fortran ABI test failed with `actual=6 expected=0` for a valid `NSVARS=1` static call +- triage_decision: implementation-owned correction allowed after research clarified the Abaqus manual ABI + +## Root Cause Summary +- root_cause_type: implementation boundary defect and stale interface policy +- summary: the step 7 code implemented a testable kernel and no-Abaqus adapter, but did not expose the top-level Abaqus `SUBROUTINE UEL(...)`. The adapter also kept the pre-research `NSVARS=0` policy, conflicting with the documented `*USER ELEMENT, VARIABLES` requirement for a positive allocation. +- why_minimal_fix_is_allowed: the correction preserves the existing kernel formulation and only fixes the Abaqus-facing call boundary, state-variable allocation check, and static-procedure `LFLAGS` validation. + +## Correction Scope + +| file | change_type | reason | in_scope | +| --- | --- | --- | --- | +| `src/fortran/uel_3d_euler_beam_uel.for` | source | add fixed-form Abaqus `UEL` wrapper with manual signature, `ABA_PARAM.INC`, manual dimensions, adapter call, and fatal `XIT` path | true | +| `src/fortran/uel_3d_euler_beam_abi_adapter.f90` | source | accept `NSVARS>=1`, use explicit/manual-style array bounds, and validate `LFLAGS(1)` / `LFLAGS(4)` | true | +| `src/fortran/uel_3d_euler_beam_kernel.f90` | source | add status ids for unsupported `LFLAGS(1)` and `LFLAGS(4)` | true | +| `scripts/test_uel_3d_euler_beam_uel.py` | test | verify wrapper signature, include, dimensions, adapter call, and fatal path without requiring Abaqus include files | true | +| `tests/test_uel_3d_euler_beam_uel.py` | test | expose a related test path for the production-source TDD guard | true | +| `tests/fortran/uel_3d_euler_beam/test_abi_static.f90` | test | update valid adapter calls to `NSVARS=1` and explicit static `LFLAGS` values | true | +| `tests/fortran/uel_3d_euler_beam/test_invalid_inputs.f90` | test | add `NSVARS<1`, `LFLAGS(1)`, and `LFLAGS(4)` diagnostics | true | +| `tests/fortran/test_uel_3d_euler_beam_abi_adapter.f90` | test | update source-smoke adapter call to the corrected policy | true | +| `docs/io-definitions/uel-3d-euler-beam.md` | documentation | align current interface contract with the implemented Abaqus-facing policy | true | +| `docs/reference-models/uel-3d-euler-beam.md` | documentation | align test model and input-deck requirements with `VARIABLES=1` and wrapper source-smoke | true | + +Excluded files: +- reference artifacts: unchanged +- tolerances: unchanged +- formulation matrix/residual equations: unchanged + +## Verification Evidence + +| order | command | exit_code | result | evidence | +| --- | --- | --- | --- | --- | +| 1 | `python -m unittest scripts.test_uel_3d_euler_beam_uel` before implementation | 1 | RED | failed because `src/fortran/uel_3d_euler_beam_uel.for` did not exist | +| 2 | `python scripts/validate_fortran.py` before implementation | 1 | RED | `uel_3d_euler_beam_abi_static` failed because `NSVARS=1` returned `UEL3DEB_E006_NSVARS` | +| 3 | `python -m unittest scripts.test_uel_3d_euler_beam_uel` after implementation | 0 | pass | wrapper source-smoke passed | +| 4 | `python scripts/validate_fortran.py` after implementation | 0 | pass | all six no-Abaqus Fortran manifest executables compiled and passed with Intel `ifx` | +| 5 | `python -m unittest discover -s scripts -p "test_*.py"` | 0 | pass | 60 Python harness/source-smoke tests passed | +| 6 | `python scripts/validate_workspace.py` | 0 | pass | reference artifact metadata validation and Fortran validation succeeded | +| 7 | `git diff --check` | 0 | pass | no whitespace errors; line-ending warnings only | + +## Traceability + +| requirement_or_finding | corrected_file | acceptance_criterion | +| --- | --- | --- | +| C-UEL-001 / C-UEL-002 | `src/fortran/uel_3d_euler_beam_uel.for` | top-level external `SUBROUTINE UEL(...)` exists with manual ABI arguments | +| C-UEL-003 | `src/fortran/uel_3d_euler_beam_uel.for`, `src/fortran/uel_3d_euler_beam_abi_adapter.f90` | Abaqus-facing boundary uses manual dimensions; adapter remains testable | +| C-UEL-004 | `src/fortran/uel_3d_euler_beam_abi_adapter.f90`, `docs/io-definitions/uel-3d-euler-beam.md` | valid static adapter calls use `NSVARS=1`; `NSVARS<1` returns `UEL3DEB_E006_NSVARS` | +| C-UEL-005 | `src/fortran/uel_3d_euler_beam_abi_adapter.f90` | unsupported `LFLAGS(1)` and `LFLAGS(4)` return dedicated diagnostics | + +## Handoff Recommendation + +| target_agent | reason | required_input | +| --- | --- | --- | +| Build/Test Executor Agent | run full workspace validation after correction | this report and command evidence | +| Reference Verification Agent | update external model expectations to `VARIABLES=1` before user-generated Abaqus artifacts are prepared | corrected I/O and reference-model documents | +| Physics Evaluation Agent | no formulation behavior changed; external CSV physics checks remain pending | future user-provided reference bundles | + +## Stop Condition +- repeated_failure: false +- upstream_ambiguity: false +- reference_artifact_gap: true +- environment_blocker: false +- next_required_decision: user must still provide external Abaqus artifacts before solver-result validation can complete diff --git a/docs/io-definitions/uel-3d-euler-beam.md b/docs/io-definitions/uel-3d-euler-beam.md index a6af398..0cf8664 100644 --- a/docs/io-definitions/uel-3d-euler-beam.md +++ b/docs/io-definitions/uel-3d-euler-beam.md @@ -54,7 +54,7 @@ The wrapper must keep `aba_param.inc` / `ABA_PARAM.INC` as an Abaqus/Standard in | `MCRD` | `>= 3` | read only `COORDS(1:3,1:2)` | | `NPROPS` | `9` | reject anything else | | `NJPROP` | `0` | first scope uses no integer properties | -| `NSVARS` | `0` | first scope stores no persistent state | +| `NSVARS` | `>= 1` | Abaqus `*USER ELEMENT` state-variable allocation must be positive; first scope leaves allocated `SVARS` entries unchanged | | `NRHS` | `1` | reject multi-RHS procedures | | `MLVARX` | `>= 12` | required for `RHS(1:12,1)` and `DU(1:12,1)` addressing | | `NDLOAD` | `0` | element-generated distributed loads are unsupported | @@ -137,7 +137,7 @@ The reference model plan should use this `.inp` subset. | `*ELEMENT` | supported | `TYPE=U1` or the approved user element type, optional `ELSET` | two-node UEL connectivity | connectivity order defines local axis 1 | | `*ELSET` | supported | `ELSET` | element set | needed for `*UEL PROPERTY` | | `*NSET` | supported | `NSET` | node set | recommended for BC/load/output selection | -| `*USER ELEMENT` | supported | `TYPE=U1`, `NODES=2`, `COORDINATES=3`, `PROPERTIES=9`, `VARIABLES=0` | UEL declaration | omit `UNSYMM`; omit `I PROPERTIES` | +| `*USER ELEMENT` | supported | `TYPE=U1`, `NODES=2`, `COORDINATES=3`, `PROPERTIES=9`, `VARIABLES=1` | UEL declaration | omit `UNSYMM`; omit `I PROPERTIES`; `SVARS(1)` is unused | | `*UEL PROPERTY` | supported | `ELSET` | real property assignment | data order is `PROPS(1:9)` | | `*BOUNDARY` | supported | standard nodal DOF constraints | essential boundary conditions | DOFs 1-6 allowed | | `*CLOAD` | supported | standard nodal concentrated load | external nodal loads/moments | loads are assembled by Abaqus outside UEL | @@ -161,7 +161,7 @@ Unsupported in first scope: The reference input deck should declare the user element with this logical content: ```text -*USER ELEMENT, TYPE=U1, NODES=2, COORDINATES=3, PROPERTIES=9, VARIABLES=0 +*USER ELEMENT, TYPE=U1, NODES=2, COORDINATES=3, PROPERTIES=9, VARIABLES=1 1, 2, 3, 4, 5, 6 ``` @@ -183,11 +183,11 @@ Line breaks may follow Abaqus input formatting rules, but the logical property o | --- | --- | --- | | `RHS(MLVARX,*)` | output | Initialize requested storage to zero every call; for supported residual requests, set `RHS(1:12,1) = -K_global*U(1:12)`. | | `AMATRX(NDOFEL,NDOFEL)` | output | Initialize all `12x12` entries to zero every call; for supported stiffness requests, set all entries to `K_global`. | -| `SVARS(*)` | input/output | unused; require `NSVARS=0`; do not store diagnostics in first scope. | +| `SVARS(*)` | input/output | unused; require `NSVARS>=1` for Abaqus keyword compatibility; do not store diagnostics in first scope. | | `ENERGY(8)` | input/output | set `ENERGY(1:8)=0.0` every supported call; do not use as validation evidence in first scope. | | `NDOFEL` | input | must equal `12`. | | `NRHS` | input | must equal `1`. | -| `NSVARS` | input | must equal `0`. | +| `NSVARS` | input | must be at least `1`. | | `PROPS(*)` | input | must contain the 9 real values defined above. | | `NPROPS` | input | must equal `9`. | | `COORDS(MCRD,NNODE)` | input | read `COORDS(1:3,1)` and `COORDS(1:3,2)` as original global node coordinates. | @@ -218,6 +218,8 @@ The first-scope wrapper supports only small-displacement static calls: - require `LFLAGS(2)=0` - support `LFLAGS(3)=1`, `2`, or `5` +- require `LFLAGS(1)=1` or `2` for static procedure calls +- require `LFLAGS(4)=0` for general-step behavior, not perturbation-only output - require `NRHS=1` Contribution rules: @@ -242,7 +244,7 @@ Invalid input must be caught before matrix assembly. The implementation may choo | `UEL3DEB-E003` | `MCRD < 3` | fatal missing 3D coordinates | | `UEL3DEB-E004` | `NPROPS /= 9` | fatal wrong real property count | | `UEL3DEB-E005` | `NJPROP /= 0` | fatal unsupported integer properties | -| `UEL3DEB-E006` | `NSVARS /= 0` | fatal unsupported state variables | +| `UEL3DEB-E006` | `NSVARS < 1` | fatal missing Abaqus state-variable allocation | | `UEL3DEB-E007` | `NRHS /= 1` | fatal unsupported RHS count | | `UEL3DEB-E008` | `MLVARX < 12` | fatal invalid RHS/DU leading dimension | | `UEL3DEB-E009` | any required coordinate, `U`, or `PROPS` value is NaN or infinite | fatal nonfinite input | @@ -253,6 +255,8 @@ Invalid input must be caught before matrix assembly. The implementation may choo | `UEL3DEB-E014` | `LFLAGS(2) /= 0` | fatal unsupported large-displacement or non-small-displacement request | | `UEL3DEB-E015` | unsupported `LFLAGS(3)` | fatal unsupported contribution request | | `UEL3DEB-E016` | `NDLOAD /= 0` | fatal unsupported element load input | +| `UEL3DEB-E017` | `LFLAGS(1)` is not `1` or `2` | fatal unsupported non-static procedure request | +| `UEL3DEB-E018` | `LFLAGS(4) /= 0` | fatal unsupported perturbation-output request | For valid supported calls, the wrapper should set deterministic outputs for the requested contribution and should not modify `PNEWDT`. @@ -358,7 +362,7 @@ Each external reference model must include metadata fields sufficient for `scrip ### Reference Model Agent - Use the keyword subset and property order in this document for `model.inp` examples. -- Include no-Abaqus tests for exact `PROPS(1:9)` mapping, `NJPROP=0`, `NSVARS=0`, `NDOFEL=12`, `NNODE=2`, and active DOF order. +- Include no-Abaqus tests for exact `PROPS(1:9)` mapping, `NJPROP=0`, `NSVARS>=1`, `NDOFEL=12`, `NNODE=2`, and active DOF order. - Include negative tests for every `UEL3DEB-E###` validation rule that is practical in the no-Abaqus harness. - Define benchmark-specific CSV filenames and near-zero absolute reaction tolerances. - Keep external artifact generation outside this repository. diff --git a/docs/reference-models/uel-3d-euler-beam.md b/docs/reference-models/uel-3d-euler-beam.md index c4438bd..fa91c94 100644 --- a/docs/reference-models/uel-3d-euler-beam.md +++ b/docs/reference-models/uel-3d-euler-beam.md @@ -31,7 +31,7 @@ Production source paths planned for later steps: | --- | --- | --- | | `src/fortran/uel_3d_euler_beam_kernel.f90` | pure/testable beam geometry, local stiffness, transform, residual, and validation logic | compiled by all no-Abaqus kernel and ABI adapter tests | | `src/fortran/uel_3d_euler_beam_abi_adapter.f90` | Abaqus-array adapter logic without the literal Abaqus `UEL` entry point | compiled by no-Abaqus ABI mapping tests | -| `src/fortran/uel_3d_euler_beam_uel.for` | thin Abaqus/Standard fixed-form `UEL` wrapper preserving the manual signature and `ABA_PARAM.INC` include | not required for no-Abaqus compile in first RED tests; later wrapper smoke can be added when an Abaqus-compatible include environment is available | +| `src/fortran/uel_3d_euler_beam_uel.for` | thin Abaqus/Standard fixed-form `UEL` wrapper preserving the manual signature and `ABA_PARAM.INC` include | checked by Python source-smoke because the file depends on Abaqus-provided include and `XIT` | Planned test source paths for step 6: @@ -43,6 +43,7 @@ Planned test source paths for step 6: | `tests/fortran/uel_3d_euler_beam/test_kernel_transform_modes.f90` | transformation, symmetry, reversed node order, and rigid-body mode tests | | `tests/fortran/uel_3d_euler_beam/test_abi_static.f90` | no-Abaqus ABI adapter tests for `RHS`, `AMATRX`, `ENERGY`, dimensions, and `LFLAGS` | | `tests/fortran/uel_3d_euler_beam/test_invalid_inputs.f90` | validation diagnostics and unsupported-request tests | +| `scripts/test_uel_3d_euler_beam_uel.py` | source-smoke contract for the fixed-form Abaqus `UEL` wrapper signature and dimensions | Step 6 RED expectation: @@ -123,10 +124,10 @@ Step 6 should create this planned manifest: | NOA-A-RHS-001 | `uel_3d_euler_beam_abi_static` | ABI/residual | verify `RHS(1:12,1)=-K_global*U(1:12)` for `LFLAGS(3)=1` and `5` | compile failure or wrong sign | REQ-009 | | NOA-A-AMATRX-001 | `uel_3d_euler_beam_abi_static` | ABI/stiffness | verify `AMATRX=K_global` for `LFLAGS(3)=1` and `2` and zeroed matrix for residual-only requests | compile failure or mapping mismatch | REQ-003, REQ-004, REQ-008 | | NOA-A-PROPS-001 | `uel_3d_euler_beam_abi_static` | ABI/property mapping | verify `PROPS(1:9)` maps to `E,G,A,Iy,Iz,J,a_ref_1,a_ref_2,a_ref_3` exactly | compile failure or property permutation mismatch | REQ-005, REQ-007 | -| NOA-A-ENERGY-001 | `uel_3d_euler_beam_abi_static` | ABI/output policy | verify `ENERGY(1:8)=0.0`, `NSVARS=0`, and `PNEWDT` is not changed for valid supported calls | compile failure or output policy mismatch | REQ-010 | +| NOA-A-ENERGY-001 | `uel_3d_euler_beam_abi_static` | ABI/output policy | verify `ENERGY(1:8)=0.0`, `NSVARS>=1`, and `PNEWDT` is not changed for valid supported calls | compile failure or output policy mismatch | REQ-010 | | NOA-I-SHAPE-001 | `uel_3d_euler_beam_invalid_inputs` | negative | verify diagnostics for `NDOFEL`, `NNODE`, `MCRD`, `NPROPS`, `NJPROP`, `NSVARS`, `NRHS`, `MLVARX` violations | compile failure or missing diagnostic | REQ-003, REQ-006 | | NOA-I-PHYS-001 | `uel_3d_euler_beam_invalid_inputs` | negative | verify diagnostics for nonfinite coordinates/properties, nonpositive `E,G,A,Iy,Iz,J`, zero length, zero orientation, and near-parallel orientation | compile failure or missing diagnostic | REQ-006, REQ-007 | -| NOA-I-LFLAGS-001 | `uel_3d_euler_beam_invalid_inputs` | negative | verify diagnostics for unsupported `LFLAGS(2)`, unsupported `LFLAGS(3)`, and `NDLOAD /= 0` | compile failure or missing diagnostic | REQ-010 | +| NOA-I-LFLAGS-001 | `uel_3d_euler_beam_invalid_inputs` | negative | verify diagnostics for unsupported `LFLAGS(1)`, unsupported `LFLAGS(2)`, unsupported `LFLAGS(3)`, unsupported `LFLAGS(4)`, and `NDLOAD /= 0` | compile failure or missing diagnostic | REQ-010 | Default no-Abaqus tolerances: @@ -143,10 +144,11 @@ The production `UEL` wrapper itself should remain a thin Abaqus ABI entry point. The ABI adapter tests must verify: -- `NDOFEL=12`, `NNODE=2`, `MCRD>=3`, `NPROPS=9`, `NJPROP=0`, `NSVARS=0`, `NRHS=1`, and `MLVARX>=12` +- `NDOFEL=12`, `NNODE=2`, `MCRD>=3`, `NPROPS=9`, `NJPROP=0`, `NSVARS>=1`, `NRHS=1`, and `MLVARX>=12` - `COORDS(1:3,1:2)` maps to `X1`, `X2` - `U(1:12)` maps to the global kernel vector without permutation - `PROPS(1:9)` maps exactly to the interface contract +- `LFLAGS(1)=1|2`, `LFLAGS(2)=0`, `LFLAGS(3)=1|2|5`, and `LFLAGS(4)=0` select the supported static general-step path - `LFLAGS(3)=1`, `2`, and `5` select the expected `AMATRX` and `RHS` outputs - non-requested `AMATRX` or `RHS` storage is deterministically zeroed by the no-Abaqus adapter - `ENERGY(1:8)` is zeroed and `SVARS` is unused @@ -252,7 +254,7 @@ Every external `model.inp` must stay within the supported keyword subset from `d - `*ELEMENT` - `*ELSET` - `*NSET` -- `*USER ELEMENT, TYPE=U1, NODES=2, COORDINATES=3, PROPERTIES=9, VARIABLES=0` +- `*USER ELEMENT, TYPE=U1, NODES=2, COORDINATES=3, PROPERTIES=9, VARIABLES=1` - active DOF line: `1, 2, 3, 4, 5, 6` - `*UEL PROPERTY, ELSET=` with data order `E,G,A,Iy,Iz,J,a_ref_1,a_ref_2,a_ref_3` - `*BOUNDARY` diff --git a/scripts/test_uel_3d_euler_beam_uel.py b/scripts/test_uel_3d_euler_beam_uel.py new file mode 100644 index 0000000..542848a --- /dev/null +++ b/scripts/test_uel_3d_euler_beam_uel.py @@ -0,0 +1,99 @@ +import re +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parent.parent +WRAPPER = ROOT / "src" / "fortran" / "uel_3d_euler_beam_uel.for" + + +def normalized_source() -> str: + if not WRAPPER.exists(): + raise AssertionError(f"missing Abaqus UEL wrapper: {WRAPPER}") + return re.sub(r"\s+", " ", WRAPPER.read_text(encoding="utf-8").upper()) + + +class Uel3dEulerBeamWrapperContractTests(unittest.TestCase): + def test_wrapper_file_exists(self): + self.assertTrue(WRAPPER.exists(), f"missing Abaqus UEL wrapper: {WRAPPER}") + + def test_wrapper_preserves_manual_uel_signature(self): + text = normalized_source() + + self.assertIn("SUBROUTINE UEL(", text) + self.assertIn("INCLUDE 'ABA_PARAM.INC'", text) + for token in ( + "RHS", + "AMATRX", + "SVARS", + "ENERGY", + "NDOFEL", + "NRHS", + "NSVARS", + "PROPS", + "NPROPS", + "COORDS", + "MCRD", + "NNODE", + "U", + "DU", + "V", + "A", + "JTYPE", + "TIME", + "DTIME", + "KSTEP", + "KINC", + "JELEM", + "PARAMS", + "NDLOAD", + "JDLTYP", + "ADLMAG", + "PREDEF", + "NPREDF", + "LFLAGS", + "MLVARX", + "DDLMAG", + "MDLOAD", + "PNEWDT", + "JPROPS", + "NJPROP", + "PERIOD", + ): + self.assertRegex(text, rf"\b{token}\b") + + signature = text.split("INCLUDE 'ABA_PARAM.INC'", 1)[0] + self.assertNotRegex(signature, r"\bSTATUS\b") + + def test_wrapper_uses_abaqus_dimensions_and_thin_adapter_call(self): + text = normalized_source() + + for shape in ( + "RHS(MLVARX,*)", + "AMATRX(NDOFEL,NDOFEL)", + "SVARS(*)", + "ENERGY(8)", + "PROPS(*)", + "COORDS(MCRD,NNODE)", + "U(NDOFEL)", + "DU(MLVARX,*)", + "V(NDOFEL)", + "A(NDOFEL)", + "TIME(2)", + "PARAMS(*)", + "JDLTYP(MDLOAD,*)", + "ADLMAG(MDLOAD,*)", + "DDLMAG(MDLOAD,*)", + "PREDEF(2,NPREDF,NNODE)", + "LFLAGS(*)", + "JPROPS(*)", + ): + self.assertIn(shape, text) + + self.assertIn("USE UEL_3D_EULER_BEAM_ABI_ADAPTER", text) + self.assertIn("CALL UEL3DEB_ABI_STATIC", text) + self.assertIn("CALL XIT", text) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/fortran/uel_3d_euler_beam_abi_adapter.f90 b/src/fortran/uel_3d_euler_beam_abi_adapter.f90 index 7601add..45263de 100644 --- a/src/fortran/uel_3d_euler_beam_abi_adapter.f90 +++ b/src/fortran/uel_3d_euler_beam_abi_adapter.f90 @@ -7,6 +7,7 @@ module uel_3d_euler_beam_abi_adapter UEL3DEB_E006_NSVARS, UEL3DEB_E007_NRHS, UEL3DEB_E008_MLVARX, & UEL3DEB_E009_NONFINITE, UEL3DEB_E014_LFLAGS2, & UEL3DEB_E015_LFLAGS3, UEL3DEB_E016_NDLOAD, & + UEL3DEB_E017_LFLAGS1, UEL3DEB_E018_LFLAGS4, & uel3deb_validate_kernel_inputs, uel3deb_global_stiffness implicit none @@ -22,24 +23,24 @@ contains props, nprops, coords, mcrd, nnode, u, & lflags, mlvarx, ndload, pnewdt, jprops, & njprop, status) - real(dp), intent(inout) :: rhs(:, :) - real(dp), intent(inout) :: amatrx(:, :) - real(dp), intent(inout) :: energy(:) integer, intent(in) :: ndofel integer, intent(in) :: nrhs integer, intent(in) :: nsvars - real(dp), intent(in) :: props(:) integer, intent(in) :: nprops - real(dp), intent(in) :: coords(:, :) integer, intent(in) :: mcrd integer, intent(in) :: nnode - real(dp), intent(in) :: u(:) - integer, intent(in) :: lflags(:) integer, intent(in) :: mlvarx integer, intent(in) :: ndload - real(dp), intent(inout) :: pnewdt - integer, intent(in) :: jprops(:) integer, intent(in) :: njprop + real(dp), intent(inout) :: rhs(mlvarx, *) + real(dp), intent(inout) :: amatrx(ndofel, ndofel) + real(dp), intent(inout) :: energy(8) + real(dp), intent(in) :: props(*) + real(dp), intent(in) :: coords(mcrd, nnode) + real(dp), intent(in) :: u(ndofel) + integer, intent(in) :: lflags(*) + real(dp), intent(inout) :: pnewdt + integer, intent(in) :: jprops(*) integer, intent(out) :: status real(dp) :: coords3(3, 2) real(dp) :: props9(9) @@ -49,9 +50,9 @@ contains real(dp) :: residual(12) status = UEL3DEB_OK - rhs = 0.0_dp - amatrx = 0.0_dp - energy = 0.0_dp + if (mlvarx > 0 .and. nrhs > 0) rhs(1:mlvarx, 1:nrhs) = 0.0_dp + if (ndofel > 0) amatrx(1:ndofel, 1:ndofel) = 0.0_dp + energy(1:8) = 0.0_dp if (ndofel /= 12) then status = UEL3DEB_E001_NDOFEL @@ -73,7 +74,7 @@ contains status = UEL3DEB_E005_NJPROP return end if - if (nsvars /= 0) then + if (nsvars < 1) then status = UEL3DEB_E006_NSVARS return end if @@ -85,6 +86,10 @@ contains status = UEL3DEB_E008_MLVARX return end if + if (.not. any(lflags(1) == [1, 2])) then + status = UEL3DEB_E017_LFLAGS1 + return + end if if (lflags(2) /= 0) then status = UEL3DEB_E014_LFLAGS2 return @@ -93,6 +98,10 @@ contains status = UEL3DEB_E015_LFLAGS3 return end if + if (lflags(4) /= 0) then + status = UEL3DEB_E018_LFLAGS4 + return + end if if (ndload /= 0) then status = UEL3DEB_E016_NDLOAD return diff --git a/src/fortran/uel_3d_euler_beam_kernel.f90 b/src/fortran/uel_3d_euler_beam_kernel.f90 index 49c56b9..359c556 100644 --- a/src/fortran/uel_3d_euler_beam_kernel.f90 +++ b/src/fortran/uel_3d_euler_beam_kernel.f90 @@ -24,6 +24,8 @@ module uel_3d_euler_beam_kernel integer, parameter, public :: UEL3DEB_E014_LFLAGS2 = 14 integer, parameter, public :: UEL3DEB_E015_LFLAGS3 = 15 integer, parameter, public :: UEL3DEB_E016_NDLOAD = 16 + integer, parameter, public :: UEL3DEB_E017_LFLAGS1 = 17 + integer, parameter, public :: UEL3DEB_E018_LFLAGS4 = 18 public :: uel3deb_validate_kernel_inputs public :: uel3deb_local_stiffness diff --git a/src/fortran/uel_3d_euler_beam_uel.for b/src/fortran/uel_3d_euler_beam_uel.for new file mode 100644 index 0000000..8f78673 --- /dev/null +++ b/src/fortran/uel_3d_euler_beam_uel.for @@ -0,0 +1,31 @@ + SUBROUTINE UEL(RHS, AMATRX, SVARS, ENERGY, NDOFEL, + 1 NRHS, NSVARS, PROPS, NPROPS, COORDS, MCRD, NNODE, + 2 U, DU, V, A, JTYPE, TIME, DTIME, KSTEP, KINC, JELEM, + 3 PARAMS, NDLOAD, JDLTYP, ADLMAG, PREDEF, NPREDF, + 4 LFLAGS, MLVARX, DDLMAG, MDLOAD, PNEWDT, JPROPS, + 5 NJPROP, PERIOD) + USE uel_3d_euler_beam_abi_adapter, ONLY: + 1 uel3deb_abi_static + INCLUDE 'ABA_PARAM.INC' + DIMENSION RHS(MLVARX,*), AMATRX(NDOFEL,NDOFEL), + 1 SVARS(*), ENERGY(8), PROPS(*), COORDS(MCRD,NNODE), + 2 U(NDOFEL), DU(MLVARX,*), V(NDOFEL), A(NDOFEL), + 3 TIME(2), PARAMS(*), JDLTYP(MDLOAD,*), + 4 ADLMAG(MDLOAD,*), DDLMAG(MDLOAD,*), + 5 PREDEF(2,NPREDF,NNODE), LFLAGS(*), JPROPS(*) + INTEGER STATUS + + CALL uel3deb_abi_static(RHS, AMATRX, ENERGY, NDOFEL, + 1 NRHS, NSVARS, PROPS, NPROPS, COORDS, MCRD, NNODE, + 2 U, LFLAGS, MLVARX, NDLOAD, PNEWDT, JPROPS, NJPROP, + 3 STATUS) + + IF (STATUS .NE. 0) THEN + WRITE(7,*) 'UEL3DEB fatal status=', STATUS, + 1 ' JELEM=', JELEM, ' KSTEP=', KSTEP, + 2 ' KINC=', KINC + CALL XIT + END IF + + RETURN + END diff --git a/tests/fortran/test_uel_3d_euler_beam_abi_adapter.f90 b/tests/fortran/test_uel_3d_euler_beam_abi_adapter.f90 index fb72625..426c93b 100644 --- a/tests/fortran/test_uel_3d_euler_beam_abi_adapter.f90 +++ b/tests/fortran/test_uel_3d_euler_beam_abi_adapter.f90 @@ -22,10 +22,12 @@ program test_uel_3d_euler_beam_abi_adapter u = 0.0_dp pnewdt = 1.0_dp lflags = 0 + lflags(1) = 1 lflags(3) = 2 + lflags(4) = 0 jprops = 0 - call uel3deb_abi_static(rhs, amatrx, energy, 12, 1, 0, props, 9, coords, & + call uel3deb_abi_static(rhs, amatrx, energy, 12, 1, 1, props, 9, coords, & 3, 2, u, lflags, 12, 0, pnewdt, jprops, 0, status) call assert_equal_int(status, UEL3DEB_OK, 'abi adapter source smoke status') call assert_matrix_symmetric(amatrx, tol_symmetry, 'abi adapter source smoke K') diff --git a/tests/fortran/uel_3d_euler_beam/test_abi_static.f90 b/tests/fortran/uel_3d_euler_beam/test_abi_static.f90 index 2a1ca40..4e58db0 100644 --- a/tests/fortran/uel_3d_euler_beam/test_abi_static.f90 +++ b/tests/fortran/uel_3d_euler_beam/test_abi_static.f90 @@ -57,11 +57,13 @@ contains amatrx = 999.0_dp energy = 999.0_dp lflags = 0 + lflags(1) = 1 lflags(2) = 0 lflags(3) = lflags3 + lflags(4) = 0 jprops = 0 - call uel3deb_abi_static(rhs, amatrx, energy, 12, 1, 0, props, 9, coords, & + call uel3deb_abi_static(rhs, amatrx, energy, 12, 1, 1, props, 9, coords, & 3, 2, u, lflags, 12, 0, pnewdt, jprops, 0, status) end subroutine call_adapter diff --git a/tests/fortran/uel_3d_euler_beam/test_invalid_inputs.f90 b/tests/fortran/uel_3d_euler_beam/test_invalid_inputs.f90 index 2e8c24d..edc4436 100644 --- a/tests/fortran/uel_3d_euler_beam/test_invalid_inputs.f90 +++ b/tests/fortran/uel_3d_euler_beam/test_invalid_inputs.f90 @@ -8,6 +8,7 @@ program test_invalid_inputs UEL3DEB_E010_NONPOSITIVE_PROPERTY, UEL3DEB_E011_ZERO_LENGTH, & UEL3DEB_E012_ZERO_ORIENTATION, UEL3DEB_E013_PARALLEL_ORIENTATION, & UEL3DEB_E014_LFLAGS2, UEL3DEB_E015_LFLAGS3, UEL3DEB_E016_NDLOAD, & + UEL3DEB_E017_LFLAGS1, UEL3DEB_E018_LFLAGS4, & uel3deb_validate_kernel_inputs use uel_3d_euler_beam_abi_adapter, only: uel3deb_abi_static implicit none @@ -60,32 +61,37 @@ contains end subroutine test_kernel_physical_diagnostics subroutine test_adapter_shape_and_request_diagnostics() - call expect_adapter_status(11, 1, 0, 9, 3, 2, 12, 0, 0, 0, 1, & + call expect_adapter_status(11, 1, 1, 9, 3, 2, 12, 0, 0, 1, 0, 0, 1, & UEL3DEB_E001_NDOFEL, 'NOA-I-SHAPE-001 NDOFEL') - call expect_adapter_status(12, 1, 0, 9, 3, 1, 12, 0, 0, 0, 1, & + call expect_adapter_status(12, 1, 1, 9, 3, 1, 12, 0, 0, 1, 0, 0, 1, & UEL3DEB_E002_NNODE, 'NOA-I-SHAPE-001 NNODE') - call expect_adapter_status(12, 1, 0, 9, 2, 2, 12, 0, 0, 0, 1, & + call expect_adapter_status(12, 1, 1, 9, 2, 2, 12, 0, 0, 1, 0, 0, 1, & UEL3DEB_E003_MCRD, 'NOA-I-SHAPE-001 MCRD') - call expect_adapter_status(12, 1, 0, 8, 3, 2, 12, 0, 0, 0, 1, & + call expect_adapter_status(12, 1, 1, 8, 3, 2, 12, 0, 0, 1, 0, 0, 1, & UEL3DEB_E004_NPROPS, 'NOA-I-SHAPE-001 NPROPS') - call expect_adapter_status(12, 1, 0, 9, 3, 2, 12, 0, 1, 0, 1, & + call expect_adapter_status(12, 1, 1, 9, 3, 2, 12, 0, 1, 1, 0, 0, 1, & UEL3DEB_E005_NJPROP, 'NOA-I-SHAPE-001 NJPROP') - call expect_adapter_status(12, 1, 1, 9, 3, 2, 12, 0, 0, 0, 1, & + call expect_adapter_status(12, 1, 0, 9, 3, 2, 12, 0, 0, 1, 0, 0, 1, & UEL3DEB_E006_NSVARS, 'NOA-I-SHAPE-001 NSVARS') - call expect_adapter_status(12, 2, 0, 9, 3, 2, 12, 0, 0, 0, 1, & + call expect_adapter_status(12, 2, 1, 9, 3, 2, 12, 0, 0, 1, 0, 0, 1, & UEL3DEB_E007_NRHS, 'NOA-I-SHAPE-001 NRHS') - call expect_adapter_status(12, 1, 0, 9, 3, 2, 11, 0, 0, 0, 1, & + call expect_adapter_status(12, 1, 1, 9, 3, 2, 11, 0, 0, 1, 0, 0, 1, & UEL3DEB_E008_MLVARX, 'NOA-I-SHAPE-001 MLVARX') - call expect_adapter_status(12, 1, 0, 9, 3, 2, 12, 0, 0, 1, 1, & + call expect_adapter_status(12, 1, 1, 9, 3, 2, 12, 0, 0, 3, 0, 0, 1, & + UEL3DEB_E017_LFLAGS1, 'NOA-I-LFLAGS-001 LFLAGS1') + call expect_adapter_status(12, 1, 1, 9, 3, 2, 12, 0, 0, 1, 1, 0, 1, & UEL3DEB_E014_LFLAGS2, 'NOA-I-LFLAGS-001 LFLAGS2') - call expect_adapter_status(12, 1, 0, 9, 3, 2, 12, 0, 0, 0, 3, & + call expect_adapter_status(12, 1, 1, 9, 3, 2, 12, 0, 0, 1, 0, 3, 1, & UEL3DEB_E015_LFLAGS3, 'NOA-I-LFLAGS-001 LFLAGS3') - call expect_adapter_status(12, 1, 0, 9, 3, 2, 12, 1, 0, 0, 1, & + call expect_adapter_status(12, 1, 1, 9, 3, 2, 12, 0, 0, 1, 0, 1, 1, & + UEL3DEB_E018_LFLAGS4, 'NOA-I-LFLAGS-001 LFLAGS4') + call expect_adapter_status(12, 1, 1, 9, 3, 2, 12, 1, 0, 1, 0, 1, 0, & UEL3DEB_E016_NDLOAD, 'NOA-I-LFLAGS-001 NDLOAD') end subroutine test_adapter_shape_and_request_diagnostics subroutine expect_adapter_status(ndofel, nrhs, nsvars, nprops, mcrd, nnode, & - mlvarx, ndload, njprop, lflag2, lflag3, & + mlvarx, ndload, njprop, lflag1, lflag2, & + lflag3, lflag4, & expected_status, message) integer, intent(in) :: ndofel integer, intent(in) :: nrhs @@ -96,8 +102,10 @@ contains integer, intent(in) :: mlvarx integer, intent(in) :: ndload integer, intent(in) :: njprop + integer, intent(in) :: lflag1 integer, intent(in) :: lflag2 integer, intent(in) :: lflag3 + integer, intent(in) :: lflag4 integer, intent(in) :: expected_status character(len=*), intent(in) :: message real(dp) :: rhs(12, 2) @@ -118,8 +126,10 @@ contains u = 0.0_dp pnewdt = 1.0_dp lflags = 0 + lflags(1) = lflag1 lflags(2) = lflag2 lflags(3) = lflag3 + lflags(4) = lflag4 jprops = 0 call uel3deb_abi_static(rhs, amatrx, energy, ndofel, nrhs, nsvars, props, & diff --git a/tests/test_uel_3d_euler_beam_uel.py b/tests/test_uel_3d_euler_beam_uel.py new file mode 100644 index 0000000..a37dbb7 --- /dev/null +++ b/tests/test_uel_3d_euler_beam_uel.py @@ -0,0 +1,4 @@ +from scripts.test_uel_3d_euler_beam_uel import Uel3dEulerBeamWrapperContractTests + + +__all__ = ["Uel3dEulerBeamWrapperContractTests"]