diff --git a/PLAN.md b/PLAN.md index a6ebd4a..cb21261 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 step: `step0` requirements +- First pending step: `step7` Fortran implementation ## Planned Steps @@ -22,8 +22,8 @@ 4. Numerical review: `docs/numerical-reviews/uel-3d-euler-beam.md` 5. Interface contract: `docs/io-definitions/uel-3d-euler-beam.md` 6. Test/reference model plan: `docs/reference-models/uel-3d-euler-beam.md` -7. RED no-Abaqus Fortran tests and manifest -8. GREEN Fortran implementation +7. RED no-Abaqus Fortran tests and manifest - completed +8. GREEN Fortran implementation - next 9. Validation, physics sanity, and readiness audit ## Success Criteria diff --git a/PROGRESS.md b/PROGRESS.md index 4803927..3ca775a 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 `phases/uel-3d-euler-beam/step5.md` test/reference model planning step -- Next action: execute `phases/uel-3d-euler-beam/step6.md` RED no-Abaqus test creation step +- Current status: completed `phases/uel-3d-euler-beam/step6.md` RED no-Abaqus test creation step +- Next action: execute `phases/uel-3d-euler-beam/step7.md` Fortran implementation step ## Completed @@ -31,6 +31,15 @@ - Completed step 5 test/reference model planning for `uel-3d-euler-beam`. - Created `docs/reference-models/uel-3d-euler-beam.md`. - Updated `phases/uel-3d-euler-beam/index.json` step 5 to `completed`. +- Completed step 6 RED no-Abaqus test creation for `uel-3d-euler-beam`. + - Created `tests/fortran/manifest.json`. + - Created `tests/fortran/uel_3d_euler_beam/test_support.f90`. + - Created `tests/fortran/uel_3d_euler_beam/test_kernel_stiffness.f90`. + - Created `tests/fortran/uel_3d_euler_beam/test_kernel_transform_modes.f90`. + - Created `tests/fortran/uel_3d_euler_beam/test_abi_static.f90`. + - Created `tests/fortran/uel_3d_euler_beam/test_invalid_inputs.f90`. + - Created `docs/build-test-reports/uel-3d-euler-beam-red.md`. + - Updated `phases/uel-3d-euler-beam/index.json` step 6 to `completed`. ## In Progress @@ -42,22 +51,28 @@ ## Last Verification -Latest verification after completing step 5 test/reference model planning: +Latest verification after completing step 6 RED no-Abaqus test creation: ```bash +python -m json.tool tests/fortran/manifest.json 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 +$env:HARNESS_FORTRAN_VALIDATION='detect'; python scripts/validate_workspace.py ``` -Result: all passed. +Result: RED evidence recorded. +- `python -m json.tool tests/fortran/manifest.json`: passed. - `python -m unittest discover -s scripts -p "test_*.py"`: 56 tests passed. - `python scripts/validate_reference_artifacts.py`: reference artifact metadata validation succeeded. -- `python scripts/validate_workspace.py`: succeeded; `validate_fortran.py` reported no Fortran validation commands configured because `tests/fortran/manifest.json` does not exist yet. +- `python scripts/validate_fortran.py`: default process failed before RED because child `cmd.exe` saw empty `%PATH%`; with minimal `PATH` normalization it reached `ifx` and failed as expected on missing `src/fortran/uel_3d_euler_beam_kernel.f90`. +- `python scripts/validate_workspace.py`: default process failed at the same environment issue; with minimal `PATH` normalization it failed at the expected missing production kernel source. +- `$env:HARNESS_FORTRAN_VALIDATION='detect'; python scripts/validate_workspace.py`: passed. ## Next Agent Checklist - Read `AGENTS.md`, `PLAN.md`, `PROGRESS.md`, and `WORKNOTE.md`. - Confirm no other owner is active in this file. -- Start `phases/uel-3d-euler-beam/step6.md`. +- Start `phases/uel-3d-euler-beam/step7.md`. - Update this file when step status changes or before handing off. diff --git a/WORKNOTE.md b/WORKNOTE.md index a1d8bc3..64904de 100644 --- a/WORKNOTE.md +++ b/WORKNOTE.md @@ -11,3 +11,4 @@ - PowerShell에서 기본 출력 인코딩에 따라 Korean text가 깨져 보일 수 있다. `Get-Content -Raw -Encoding UTF8 AGENTS.md`처럼 UTF-8을 명시하면 정상 확인 가능하다. - `scripts/execute.py`는 phase step을 Codex subprocess로 실행하고, 각 step이 `phases//index.json`의 status를 직접 갱신해야 한다. Step 지시문에는 이 요구가 명확해야 한다. - `python scripts/validate_workspace.py`는 Fortran manifest가 없으면 `validate_fortran.py`에서 "No Fortran validation commands configured."를 출력하고 성공할 수 있다. 이것은 구현 전 scaffold 상태에서는 정상이다. +- Step 6에서 `tests/fortran/manifest.json`을 추가한 뒤 기본 `python scripts/validate_fortran.py`가 nested `cmd` lookup에서 먼저 실패했다. PowerShell/Python은 `cmd.exe`를 찾지만 child `cmd.exe`가 `%PATH%`를 빈 값으로 보았다. RED compile evidence는 `$env:PATH='C:\Windows\System32;C:\Windows;C:\Users\user\miniforge3'; C:\Users\user\miniforge3\python.exe scripts/validate_fortran.py`로 확인했으며, 이때 `ifx`가 실행되고 missing `src/fortran/uel_3d_euler_beam_kernel.f90` compile failure가 발생했다. diff --git a/docs/build-test-reports/uel-3d-euler-beam-red.md b/docs/build-test-reports/uel-3d-euler-beam-red.md new file mode 100644 index 0000000..19a1940 --- /dev/null +++ b/docs/build-test-reports/uel-3d-euler-beam-red.md @@ -0,0 +1,67 @@ +# 3D Euler-Bernoulli Beam UEL RED Test Report + +## Metadata +- feature_id: uel-3d-euler-beam +- source_reference_models: `docs/reference-models/uel-3d-euler-beam.md` +- status: red-evidence-recorded +- owner_agent: implementation-agent +- date: 2026-06-11 + +## Scope + +This report records the step 6 RED evidence after adding no-Abaqus Fortran tests and `tests/fortran/manifest.json`. + +Production Fortran source was not added in this step. The expected RED condition is a compile failure from missing planned production source: + +- `src/fortran/uel_3d_euler_beam_kernel.f90` +- `src/fortran/uel_3d_euler_beam_abi_adapter.f90` + +## Test Files Added Before Production Code + +| manifest_test | source file | +| --- | --- | +| `uel_3d_euler_beam_kernel_stiffness` | `tests/fortran/uel_3d_euler_beam/test_kernel_stiffness.f90` | +| `uel_3d_euler_beam_kernel_transform_modes` | `tests/fortran/uel_3d_euler_beam/test_kernel_transform_modes.f90` | +| `uel_3d_euler_beam_abi_static` | `tests/fortran/uel_3d_euler_beam/test_abi_static.f90` | +| `uel_3d_euler_beam_invalid_inputs` | `tests/fortran/uel_3d_euler_beam/test_invalid_inputs.f90` | +| shared support | `tests/fortran/uel_3d_euler_beam/test_support.f90` | + +## Command Log Summary + +| order | command | exit_code | result | evidence | +| --- | --- | --- | --- | --- | +| 1 | `python -m json.tool tests/fortran/manifest.json` | 0 | pass | manifest JSON parsed successfully | +| 2 | `python -m unittest discover -s scripts -p "test_*.py"` | 0 | pass | 56 Python harness tests passed | +| 3 | `python scripts/validate_reference_artifacts.py` | 0 | pass | reference artifact metadata validation succeeded | +| 4 | `python scripts/validate_fortran.py` | 1 | environment failure before RED | current `cmd.exe` child process sees empty `%PATH%`; nested `cmd` command is not found | +| 5 | `$env:PATH='C:\Windows\System32;C:\Windows;C:\Users\user\miniforge3'; C:\Users\user\miniforge3\python.exe scripts/validate_fortran.py` | 1 | expected RED | `ifx` reached compile and failed because `src/fortran/uel_3d_euler_beam_kernel.f90` does not exist | +| 6 | `python scripts/validate_workspace.py` | 1 | environment failure before RED | reference validation passed, then Fortran validation hit the same nested `cmd` lookup issue | +| 7 | `$env:PATH='C:\Windows\System32;C:\Windows;C:\Users\user\miniforge3'; C:\Users\user\miniforge3\python.exe scripts/validate_workspace.py` | 1 | expected RED | reference validation passed, then Fortran validation failed on missing production kernel source | +| 8 | `$env:HARNESS_FORTRAN_VALIDATION='detect'; python scripts/validate_workspace.py` | 0 | pass | reference validation passed; Fortran manifest/compiler detection mode produced no compile commands | + +## RED Failure Classification + +- classification: `fortran-compile` +- first implementation-owned failure after environment normalization: missing planned production source +- primary evidence tail: + +```text +xfortcom: Severe: No such file or directory +... file is 'C:\git\AbaqusSubroutineDev\src\fortran\uel_3d_euler_beam_kernel.f90' +compilation aborted for C:\git\AbaqusSubroutineDev\src\fortran\uel_3d_euler_beam_kernel.f90 (code 1) +``` + +The subsequent module import errors in `test_kernel_stiffness.f90` are secondary to the missing kernel module source. + +## Environment Note + +This Codex process can find `cmd.exe` from PowerShell and Python, but a child `cmd.exe` invoked during `validate_fortran.py` sees an empty `%PATH%` unless a minimal `PATH` is set explicitly. The normalized command above confirms that Intel oneAPI `ifx` is available through the configured oneAPI env script and that the actionable RED failure is the missing production source, not a missing compiler. + +## Handoff to Step 7 + +Implement the minimum production Fortran needed to turn the four manifest tests GREEN: + +- `src/fortran/uel_3d_euler_beam_kernel.f90` +- `src/fortran/uel_3d_euler_beam_abi_adapter.f90` + +The Abaqus fixed-form `UEL` wrapper remains planned for the implementation gate but is not required to satisfy these no-Abaqus RED tests unless the step 7 plan explicitly includes it. diff --git a/phases/uel-3d-euler-beam/index.json b/phases/uel-3d-euler-beam/index.json index b6b87b8..51f3c3b 100644 --- a/phases/uel-3d-euler-beam/index.json +++ b/phases/uel-3d-euler-beam/index.json @@ -41,7 +41,8 @@ { "step": 6, "name": "tdd-tests", - "status": "pending" + "status": "completed", + "summary": "Created approved no-Abaqus Fortran manifest and test drivers under tests/fortran/uel_3d_euler_beam. Recorded RED evidence in docs/build-test-reports/uel-3d-euler-beam-red.md: Python harness and reference artifact validation passed; normalized oneAPI Fortran validation reached ifx and failed as expected because planned production kernel source is not implemented yet." }, { "step": 7, diff --git a/tests/fortran/manifest.json b/tests/fortran/manifest.json new file mode 100644 index 0000000..9683d2a --- /dev/null +++ b/tests/fortran/manifest.json @@ -0,0 +1,38 @@ +{ + "tests": [ + { + "name": "uel_3d_euler_beam_kernel_stiffness", + "sources": [ + "tests/fortran/uel_3d_euler_beam/test_support.f90", + "src/fortran/uel_3d_euler_beam_kernel.f90", + "tests/fortran/uel_3d_euler_beam/test_kernel_stiffness.f90" + ] + }, + { + "name": "uel_3d_euler_beam_kernel_transform_modes", + "sources": [ + "tests/fortran/uel_3d_euler_beam/test_support.f90", + "src/fortran/uel_3d_euler_beam_kernel.f90", + "tests/fortran/uel_3d_euler_beam/test_kernel_transform_modes.f90" + ] + }, + { + "name": "uel_3d_euler_beam_abi_static", + "sources": [ + "tests/fortran/uel_3d_euler_beam/test_support.f90", + "src/fortran/uel_3d_euler_beam_kernel.f90", + "src/fortran/uel_3d_euler_beam_abi_adapter.f90", + "tests/fortran/uel_3d_euler_beam/test_abi_static.f90" + ] + }, + { + "name": "uel_3d_euler_beam_invalid_inputs", + "sources": [ + "tests/fortran/uel_3d_euler_beam/test_support.f90", + "src/fortran/uel_3d_euler_beam_kernel.f90", + "src/fortran/uel_3d_euler_beam_abi_adapter.f90", + "tests/fortran/uel_3d_euler_beam/test_invalid_inputs.f90" + ] + } + ] +} diff --git a/tests/fortran/uel_3d_euler_beam/test_abi_static.f90 b/tests/fortran/uel_3d_euler_beam/test_abi_static.f90 new file mode 100644 index 0000000..2a1ca40 --- /dev/null +++ b/tests/fortran/uel_3d_euler_beam/test_abi_static.f90 @@ -0,0 +1,127 @@ +program test_abi_static + use uel_3d_euler_beam_test_support + use uel_3d_euler_beam_kernel, only: & + UEL3DEB_OK, uel3deb_global_stiffness + use uel_3d_euler_beam_abi_adapter, only: uel3deb_abi_static + implicit none + + call test_static_full_contribution() + call test_stiffness_only_and_residual_only() + write(*, '(A)') 'PASS uel_3d_euler_beam_abi_static' + +contains + + subroutine valid_problem(coords, props, u) + real(dp), intent(out) :: coords(3, 2) + real(dp), intent(out) :: props(9) + real(dp), intent(out) :: u(12) + + coords(:, 1) = [0.0_dp, 0.0_dp, 0.0_dp] + coords(:, 2) = [2.0_dp, 0.0_dp, 0.0_dp] + props = [210.0e9_dp, 80.0e9_dp, 3.0e-3_dp, 4.0e-6_dp, 7.0e-6_dp, & + 2.5e-6_dp, 0.0_dp, 1.0_dp, 0.0_dp] + u = [1.0e-4_dp, -2.0e-4_dp, 3.0e-4_dp, 4.0e-5_dp, -5.0e-5_dp, & + 6.0e-5_dp, -7.0e-4_dp, 8.0e-4_dp, -9.0e-4_dp, 1.0e-4_dp, & + -1.1e-4_dp, 1.2e-4_dp] + end subroutine valid_problem + + subroutine expected_outputs(coords, props, u, expected_k, expected_rhs) + real(dp), intent(in) :: coords(3, 2) + real(dp), intent(in) :: props(9) + real(dp), intent(in) :: u(12) + real(dp), intent(out) :: expected_k(12, 12) + real(dp), intent(out) :: expected_rhs(12) + real(dp) :: transform(12, 12) + integer :: status + + call uel3deb_global_stiffness(coords, props, expected_k, transform, status) + call assert_equal_int(status, UEL3DEB_OK, 'expected K status') + expected_rhs = -matmul(expected_k, u) + end subroutine expected_outputs + + subroutine call_adapter(lflags3, rhs, amatrx, energy, pnewdt, status) + integer, intent(in) :: lflags3 + real(dp), intent(out) :: rhs(12, 1) + real(dp), intent(out) :: amatrx(12, 12) + real(dp), intent(out) :: energy(8) + real(dp), intent(inout) :: pnewdt + integer, intent(out) :: status + real(dp) :: coords(3, 2) + real(dp) :: props(9) + real(dp) :: u(12) + integer :: lflags(5) + integer :: jprops(1) + + call valid_problem(coords, props, u) + rhs = 999.0_dp + amatrx = 999.0_dp + energy = 999.0_dp + lflags = 0 + lflags(2) = 0 + lflags(3) = lflags3 + jprops = 0 + + call uel3deb_abi_static(rhs, amatrx, energy, 12, 1, 0, props, 9, coords, & + 3, 2, u, lflags, 12, 0, pnewdt, jprops, 0, status) + end subroutine call_adapter + + subroutine test_static_full_contribution() + real(dp) :: coords(3, 2) + real(dp) :: props(9) + real(dp) :: u(12) + real(dp) :: rhs(12, 1) + real(dp) :: amatrx(12, 12) + real(dp) :: energy(8) + real(dp) :: expected_k(12, 12) + real(dp) :: expected_rhs(12) + real(dp) :: pnewdt + integer :: status + + call valid_problem(coords, props, u) + call expected_outputs(coords, props, u, expected_k, expected_rhs) + + pnewdt = 0.25_dp + call call_adapter(1, rhs, amatrx, energy, pnewdt, status) + + call assert_equal_int(status, UEL3DEB_OK, 'NOA-A-AMATRX-001 status LFLAGS3=1') + call assert_matrix_close(amatrx, expected_k, tol_stiff, 'NOA-A-AMATRX-001 K') + call assert_vector_close(rhs(:, 1), expected_rhs, tol_vector, 'NOA-A-RHS-001 RHS') + call assert_vector_close(energy, spread(0.0_dp, 1, 8), tol_vector, 'NOA-A-ENERGY-001 ENERGY') + call assert_close(pnewdt, 0.25_dp, tol_vector, 'NOA-A-ENERGY-001 PNEWDT') + end subroutine test_static_full_contribution + + subroutine test_stiffness_only_and_residual_only() + real(dp) :: coords(3, 2) + real(dp) :: props(9) + real(dp) :: u(12) + real(dp) :: rhs(12, 1) + real(dp) :: amatrx(12, 12) + real(dp) :: energy(8) + real(dp) :: expected_k(12, 12) + real(dp) :: expected_rhs(12) + real(dp) :: zeros12(12) + real(dp) :: zeros_k(12, 12) + real(dp) :: pnewdt + integer :: status + + call valid_problem(coords, props, u) + call expected_outputs(coords, props, u, expected_k, expected_rhs) + zeros12 = 0.0_dp + zeros_k = 0.0_dp + + pnewdt = 0.5_dp + call call_adapter(2, rhs, amatrx, energy, pnewdt, status) + call assert_equal_int(status, UEL3DEB_OK, 'NOA-A-AMATRX-001 status LFLAGS3=2') + call assert_matrix_close(amatrx, expected_k, tol_stiff, 'NOA-A-AMATRX-001 K only') + call assert_vector_close(rhs(:, 1), zeros12, tol_vector, 'NOA-A-AMATRX-001 zero RHS') + call assert_close(pnewdt, 0.5_dp, tol_vector, 'NOA-A-ENERGY-001 PNEWDT K only') + + pnewdt = 0.75_dp + call call_adapter(5, rhs, amatrx, energy, pnewdt, status) + call assert_equal_int(status, UEL3DEB_OK, 'NOA-A-RHS-001 status LFLAGS3=5') + call assert_matrix_close(amatrx, zeros_k, tol_stiff, 'NOA-A-RHS-001 zero AMATRX') + call assert_vector_close(rhs(:, 1), expected_rhs, tol_vector, 'NOA-A-RHS-001 RHS only') + call assert_close(pnewdt, 0.75_dp, tol_vector, 'NOA-A-ENERGY-001 PNEWDT RHS only') + end subroutine test_stiffness_only_and_residual_only + +end program test_abi_static diff --git a/tests/fortran/uel_3d_euler_beam/test_invalid_inputs.f90 b/tests/fortran/uel_3d_euler_beam/test_invalid_inputs.f90 new file mode 100644 index 0000000..2e8c24d --- /dev/null +++ b/tests/fortran/uel_3d_euler_beam/test_invalid_inputs.f90 @@ -0,0 +1,131 @@ +program test_invalid_inputs + use ieee_arithmetic, only: ieee_quiet_nan, ieee_value + use uel_3d_euler_beam_test_support + use uel_3d_euler_beam_kernel, only: & + UEL3DEB_E001_NDOFEL, UEL3DEB_E002_NNODE, UEL3DEB_E003_MCRD, & + UEL3DEB_E004_NPROPS, UEL3DEB_E005_NJPROP, UEL3DEB_E006_NSVARS, & + UEL3DEB_E007_NRHS, UEL3DEB_E008_MLVARX, UEL3DEB_E009_NONFINITE, & + 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_validate_kernel_inputs + use uel_3d_euler_beam_abi_adapter, only: uel3deb_abi_static + implicit none + + call test_kernel_physical_diagnostics() + call test_adapter_shape_and_request_diagnostics() + write(*, '(A)') 'PASS uel_3d_euler_beam_invalid_inputs' + +contains + + subroutine valid_kernel_inputs(coords, props) + real(dp), intent(out) :: coords(3, 2) + real(dp), intent(out) :: props(9) + + coords(:, 1) = [0.0_dp, 0.0_dp, 0.0_dp] + coords(:, 2) = [2.0_dp, 0.0_dp, 0.0_dp] + props = [210.0e9_dp, 80.0e9_dp, 3.0e-3_dp, 4.0e-6_dp, 7.0e-6_dp, & + 2.5e-6_dp, 0.0_dp, 1.0_dp, 0.0_dp] + end subroutine valid_kernel_inputs + + subroutine test_kernel_physical_diagnostics() + real(dp) :: coords(3, 2) + real(dp) :: props(9) + integer :: status + + call valid_kernel_inputs(coords, props) + coords(1, 1) = ieee_value(coords(1, 1), ieee_quiet_nan) + call uel3deb_validate_kernel_inputs(coords, props, status) + call assert_equal_int(status, UEL3DEB_E009_NONFINITE, 'NOA-I-PHYS-001 nonfinite coordinate') + + call valid_kernel_inputs(coords, props) + props(1) = -1.0_dp + call uel3deb_validate_kernel_inputs(coords, props, status) + call assert_equal_int(status, UEL3DEB_E010_NONPOSITIVE_PROPERTY, 'NOA-I-PHYS-001 nonpositive E') + + call valid_kernel_inputs(coords, props) + coords(:, 2) = coords(:, 1) + call uel3deb_validate_kernel_inputs(coords, props, status) + call assert_equal_int(status, UEL3DEB_E011_ZERO_LENGTH, 'NOA-I-PHYS-001 zero length') + + call valid_kernel_inputs(coords, props) + props(7:9) = [0.0_dp, 0.0_dp, 0.0_dp] + call uel3deb_validate_kernel_inputs(coords, props, status) + call assert_equal_int(status, UEL3DEB_E012_ZERO_ORIENTATION, 'NOA-I-PHYS-001 zero orientation') + + call valid_kernel_inputs(coords, props) + props(7:9) = [1.0_dp, 0.0_dp, 0.0_dp] + call uel3deb_validate_kernel_inputs(coords, props, status) + call assert_equal_int(status, UEL3DEB_E013_PARALLEL_ORIENTATION, 'NOA-I-PHYS-001 parallel orientation') + 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, & + UEL3DEB_E001_NDOFEL, 'NOA-I-SHAPE-001 NDOFEL') + call expect_adapter_status(12, 1, 0, 9, 3, 1, 12, 0, 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, & + UEL3DEB_E003_MCRD, 'NOA-I-SHAPE-001 MCRD') + call expect_adapter_status(12, 1, 0, 8, 3, 2, 12, 0, 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, & + UEL3DEB_E005_NJPROP, 'NOA-I-SHAPE-001 NJPROP') + call expect_adapter_status(12, 1, 1, 9, 3, 2, 12, 0, 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, & + UEL3DEB_E007_NRHS, 'NOA-I-SHAPE-001 NRHS') + call expect_adapter_status(12, 1, 0, 9, 3, 2, 11, 0, 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, & + UEL3DEB_E014_LFLAGS2, 'NOA-I-LFLAGS-001 LFLAGS2') + call expect_adapter_status(12, 1, 0, 9, 3, 2, 12, 0, 0, 0, 3, & + UEL3DEB_E015_LFLAGS3, 'NOA-I-LFLAGS-001 LFLAGS3') + call expect_adapter_status(12, 1, 0, 9, 3, 2, 12, 1, 0, 0, 1, & + 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, & + expected_status, message) + integer, intent(in) :: ndofel + integer, intent(in) :: nrhs + integer, intent(in) :: nsvars + integer, intent(in) :: nprops + integer, intent(in) :: mcrd + integer, intent(in) :: nnode + integer, intent(in) :: mlvarx + integer, intent(in) :: ndload + integer, intent(in) :: njprop + integer, intent(in) :: lflag2 + integer, intent(in) :: lflag3 + integer, intent(in) :: expected_status + character(len=*), intent(in) :: message + real(dp) :: rhs(12, 2) + real(dp) :: amatrx(12, 12) + real(dp) :: energy(8) + real(dp) :: coords(3, 2) + real(dp) :: props(9) + real(dp) :: u(12) + real(dp) :: pnewdt + integer :: lflags(5) + integer :: jprops(1) + integer :: status + + call valid_kernel_inputs(coords, props) + rhs = 0.0_dp + amatrx = 0.0_dp + energy = 0.0_dp + u = 0.0_dp + pnewdt = 1.0_dp + lflags = 0 + lflags(2) = lflag2 + lflags(3) = lflag3 + jprops = 0 + + call uel3deb_abi_static(rhs, amatrx, energy, ndofel, nrhs, nsvars, props, & + nprops, coords, mcrd, nnode, u, lflags, mlvarx, & + ndload, pnewdt, jprops, njprop, status) + call assert_equal_int(status, expected_status, message) + end subroutine expect_adapter_status + +end program test_invalid_inputs diff --git a/tests/fortran/uel_3d_euler_beam/test_kernel_stiffness.f90 b/tests/fortran/uel_3d_euler_beam/test_kernel_stiffness.f90 new file mode 100644 index 0000000..ae4e69c --- /dev/null +++ b/tests/fortran/uel_3d_euler_beam/test_kernel_stiffness.f90 @@ -0,0 +1,134 @@ +program test_kernel_stiffness + use uel_3d_euler_beam_test_support + use uel_3d_euler_beam_kernel, only: & + UEL3DEB_OK, uel3deb_local_stiffness + implicit none + + call test_local_stiffness_entries() + call test_analytical_responses() + write(*, '(A)') 'PASS uel_3d_euler_beam_kernel_stiffness' + +contains + + subroutine beam_values(length, young, shear, area, iy, iz, torsion_j) + real(dp), intent(out) :: length + real(dp), intent(out) :: young + real(dp), intent(out) :: shear + real(dp), intent(out) :: area + real(dp), intent(out) :: iy + real(dp), intent(out) :: iz + real(dp), intent(out) :: torsion_j + + length = 2.0_dp + young = 210.0e9_dp + shear = 80.0e9_dp + area = 3.0e-3_dp + iy = 4.0e-6_dp + iz = 7.0e-6_dp + torsion_j = 2.5e-6_dp + end subroutine beam_values + + subroutine build_local(k, length, young, shear, area, iy, iz, torsion_j) + real(dp), intent(out) :: k(12, 12) + real(dp), intent(out) :: length + real(dp), intent(out) :: young + real(dp), intent(out) :: shear + real(dp), intent(out) :: area + real(dp), intent(out) :: iy + real(dp), intent(out) :: iz + real(dp), intent(out) :: torsion_j + integer :: status + + call beam_values(length, young, shear, area, iy, iz, torsion_j) + call uel3deb_local_stiffness(length, young, shear, area, iy, iz, torsion_j, k, status) + call assert_equal_int(status, UEL3DEB_OK, 'local stiffness status') + end subroutine build_local + + subroutine test_local_stiffness_entries() + real(dp) :: k(12, 12) + real(dp) :: length, young, shear, area, iy, iz, torsion_j + real(dp) :: axial, torsion, b2_12, b2_6, b2_4, b2_2 + real(dp) :: b3_12, b3_6, b3_4, b3_2 + + call build_local(k, length, young, shear, area, iy, iz, torsion_j) + + axial = young * area / length + torsion = shear * torsion_j / length + b2_12 = 12.0_dp * young * iy / length**3 + b2_6 = 6.0_dp * young * iy / length**2 + b2_4 = 4.0_dp * young * iy / length + b2_2 = 2.0_dp * young * iy / length + b3_12 = 12.0_dp * young * iz / length**3 + b3_6 = 6.0_dp * young * iz / length**2 + b3_4 = 4.0_dp * young * iz / length + b3_2 = 2.0_dp * young * iz / length + + call assert_matrix_symmetric(k, tol_symmetry, 'NOA-K-SYM-001 local symmetry') + + call assert_close(k(1, 1), axial, tol_stiff, 'NOA-K-STIFF-001 axial k11') + call assert_close(k(1, 7), -axial, tol_stiff, 'NOA-K-STIFF-001 axial k17') + call assert_close(k(7, 7), axial, tol_stiff, 'NOA-K-STIFF-001 axial k77') + call assert_close(k(4, 4), torsion, tol_stiff, 'NOA-K-STIFF-001 torsion k44') + call assert_close(k(4, 10), -torsion, tol_stiff, 'NOA-K-STIFF-001 torsion k4,10') + call assert_close(k(10, 10), torsion, tol_stiff, 'NOA-K-STIFF-001 torsion k10,10') + + call assert_close(k(3, 3), b2_12, tol_stiff, 'NOA-K-BEND2-001 k33') + call assert_close(k(3, 5), -b2_6, tol_stiff, 'NOA-K-BEND2-001 k35') + call assert_close(k(3, 9), -b2_12, tol_stiff, 'NOA-K-BEND2-001 k39') + call assert_close(k(3, 11), -b2_6, tol_stiff, 'NOA-K-BEND2-001 k3,11') + call assert_close(k(5, 5), b2_4, tol_stiff, 'NOA-K-BEND2-001 k55') + call assert_close(k(5, 9), b2_6, tol_stiff, 'NOA-K-BEND2-001 k59') + call assert_close(k(5, 11), b2_2, tol_stiff, 'NOA-K-BEND2-001 k5,11') + call assert_close(k(11, 11), b2_4, tol_stiff, 'NOA-K-BEND2-001 k11,11') + + call assert_close(k(2, 2), b3_12, tol_stiff, 'NOA-K-BEND3-001 k22') + call assert_close(k(2, 6), b3_6, tol_stiff, 'NOA-K-BEND3-001 k26') + call assert_close(k(2, 8), -b3_12, tol_stiff, 'NOA-K-BEND3-001 k28') + call assert_close(k(2, 12), b3_6, tol_stiff, 'NOA-K-BEND3-001 k2,12') + call assert_close(k(6, 6), b3_4, tol_stiff, 'NOA-K-BEND3-001 k66') + call assert_close(k(6, 8), -b3_6, tol_stiff, 'NOA-K-BEND3-001 k68') + call assert_close(k(6, 12), b3_2, tol_stiff, 'NOA-K-BEND3-001 k6,12') + call assert_close(k(12, 12), b3_4, tol_stiff, 'NOA-K-BEND3-001 k12,12') + call assert_close(k(2, 3), 0.0_dp, tol_stiff, 'NOA-K-STIFF-001 uncoupled k23') + end subroutine test_local_stiffness_entries + + subroutine test_analytical_responses() + real(dp) :: k(12, 12) + real(dp) :: force(12) + real(dp) :: u(12) + real(dp) :: length, young, shear, area, iy, iz, torsion_j + real(dp) :: axial, torsion, b2_12, b3_12 + + call build_local(k, length, young, shear, area, iy, iz, torsion_j) + + axial = young * area / length + torsion = shear * torsion_j / length + b2_12 = 12.0_dp * young * iy / length**3 + b3_12 = 12.0_dp * young * iz / length**3 + + u = 0.0_dp + u(7) = 1.0e-2_dp + force = matmul(k, u) + call assert_close(force(1), -axial * u(7), tol_vector, 'NOA-K-STIFF-002 f1') + call assert_close(force(7), axial * u(7), tol_vector, 'NOA-K-STIFF-002 f7') + + u = 0.0_dp + u(10) = 2.0e-2_dp + force = matmul(k, u) + call assert_close(force(4), -torsion * u(10), tol_vector, 'NOA-K-STIFF-003 m1') + call assert_close(force(10), torsion * u(10), tol_vector, 'NOA-K-STIFF-003 m2') + + u = 0.0_dp + u(9) = 1.0e-2_dp + force = matmul(k, u) + call assert_close(force(3), -b2_12 * u(9), tol_vector, 'NOA-K-BEND2-001 f3') + call assert_close(force(9), b2_12 * u(9), tol_vector, 'NOA-K-BEND2-001 f9') + + u = 0.0_dp + u(8) = 1.0e-2_dp + force = matmul(k, u) + call assert_close(force(2), -b3_12 * u(8), tol_vector, 'NOA-K-BEND3-001 f2') + call assert_close(force(8), b3_12 * u(8), tol_vector, 'NOA-K-BEND3-001 f8') + end subroutine test_analytical_responses + +end program test_kernel_stiffness diff --git a/tests/fortran/uel_3d_euler_beam/test_kernel_transform_modes.f90 b/tests/fortran/uel_3d_euler_beam/test_kernel_transform_modes.f90 new file mode 100644 index 0000000..e2959e2 --- /dev/null +++ b/tests/fortran/uel_3d_euler_beam/test_kernel_transform_modes.f90 @@ -0,0 +1,168 @@ +program test_kernel_transform_modes + use uel_3d_euler_beam_test_support + use uel_3d_euler_beam_kernel, only: & + UEL3DEB_OK, uel3deb_global_stiffness, uel3deb_local_stiffness + implicit none + + call test_identity_and_rotated_transform() + call test_rigid_body_modes() + call test_reversed_node_order() + write(*, '(A)') 'PASS uel_3d_euler_beam_kernel_transform_modes' + +contains + + subroutine valid_props(props) + real(dp), intent(out) :: props(9) + + props = [210.0e9_dp, 80.0e9_dp, 3.0e-3_dp, 4.0e-6_dp, 7.0e-6_dp, & + 2.5e-6_dp, 0.0_dp, 1.0_dp, 0.0_dp] + end subroutine valid_props + + subroutine fill_block_transform(rotation, transform) + real(dp), intent(in) :: rotation(3, 3) + real(dp), intent(out) :: transform(12, 12) + integer :: block + integer :: i + integer :: j + + transform = 0.0_dp + do block = 0, 3 + do i = 1, 3 + do j = 1, 3 + transform(3 * block + i, 3 * block + j) = rotation(i, j) + end do + end do + end do + end subroutine fill_block_transform + + subroutine test_identity_and_rotated_transform() + real(dp) :: props(9) + real(dp) :: coords(3, 2) + real(dp) :: k_local(12, 12) + real(dp) :: k_global(12, 12) + real(dp) :: transform(12, 12) + real(dp) :: expected(12, 12) + real(dp) :: expected_transform(12, 12) + real(dp) :: rotation(3, 3) + integer :: status + + call valid_props(props) + coords(:, 1) = [0.0_dp, 0.0_dp, 0.0_dp] + coords(:, 2) = [2.0_dp, 0.0_dp, 0.0_dp] + + call uel3deb_local_stiffness(2.0_dp, props(1), props(2), props(3), props(4), & + props(5), props(6), k_local, status) + call assert_equal_int(status, UEL3DEB_OK, 'identity local stiffness status') + call uel3deb_global_stiffness(coords, props, k_global, transform, status) + call assert_equal_int(status, UEL3DEB_OK, 'identity global stiffness status') + call set_identity(expected_transform) + call assert_matrix_close(transform, expected_transform, tol_stiff, 'NOA-K-ROT-001 identity T') + call assert_matrix_close(k_global, k_local, tol_stiff, 'NOA-K-ROT-001 identity K') + + props(7:9) = [0.0_dp, 0.0_dp, 1.0_dp] + coords(:, 2) = [0.0_dp, 2.0_dp, 0.0_dp] + call uel3deb_global_stiffness(coords, props, k_global, transform, status) + call assert_equal_int(status, UEL3DEB_OK, 'rotated global stiffness status') + + rotation = 0.0_dp + rotation(1, 2) = 1.0_dp + rotation(2, 3) = 1.0_dp + rotation(3, 1) = 1.0_dp + call fill_block_transform(rotation, expected_transform) + expected = matmul(transpose(expected_transform), matmul(k_local, expected_transform)) + call assert_matrix_close(transform, expected_transform, tol_stiff, 'NOA-K-ROT-001 rotated T') + call assert_matrix_close(k_global, expected, tol_stiff, 'NOA-K-ROT-001 rotated K') + call assert_matrix_symmetric(k_global, tol_symmetry, 'NOA-K-SYM-001 global symmetry') + end subroutine test_identity_and_rotated_transform + + subroutine test_rigid_body_modes() + real(dp) :: props(9) + real(dp) :: coords(3, 2) + real(dp) :: k_global(12, 12) + real(dp) :: transform(12, 12) + real(dp) :: mode(12) + integer :: status + + call valid_props(props) + coords(:, 1) = [0.0_dp, 0.0_dp, 0.0_dp] + coords(:, 2) = [2.0_dp, 0.0_dp, 0.0_dp] + call uel3deb_global_stiffness(coords, props, k_global, transform, status) + call assert_equal_int(status, UEL3DEB_OK, 'rigid mode stiffness status') + + mode = 0.0_dp + mode([1, 7]) = 1.0_dp + call assert_rigid_mode(k_global, mode, 'NOA-K-RBM-001 rigid U1') + + mode = 0.0_dp + mode([2, 8]) = 1.0_dp + call assert_rigid_mode(k_global, mode, 'NOA-K-RBM-001 rigid U2') + + mode = 0.0_dp + mode([3, 9]) = 1.0_dp + call assert_rigid_mode(k_global, mode, 'NOA-K-RBM-001 rigid U3') + + mode = 0.0_dp + mode([4, 10]) = 1.0_dp + call assert_rigid_mode(k_global, mode, 'NOA-K-RBM-001 rigid UR1') + + mode = 0.0_dp + mode(3) = 0.0_dp + mode(5) = 1.0_dp + mode(9) = -2.0_dp + mode(11) = 1.0_dp + call assert_rigid_mode(k_global, mode, 'NOA-K-RBM-001 rigid UR2') + + mode = 0.0_dp + mode(2) = 0.0_dp + mode(6) = 1.0_dp + mode(8) = 2.0_dp + mode(12) = 1.0_dp + call assert_rigid_mode(k_global, mode, 'NOA-K-RBM-001 rigid UR3') + end subroutine test_rigid_body_modes + + subroutine assert_rigid_mode(k_global, mode, message) + real(dp), intent(in) :: k_global(12, 12) + real(dp), intent(in) :: mode(12) + character(len=*), intent(in) :: message + real(dp) :: residual(12) + real(dp) :: limit + + residual = matmul(k_global, mode) + limit = tol_vector * max(1.0_dp, max_abs_matrix(k_global) * max_abs_vector(mode)) + call assert_vector_norm_le(residual, limit, message) + end subroutine assert_rigid_mode + + subroutine test_reversed_node_order() + real(dp) :: props(9) + real(dp) :: coords_forward(3, 2) + real(dp) :: coords_reversed(3, 2) + real(dp) :: k_forward(12, 12) + real(dp) :: k_reversed(12, 12) + real(dp) :: transform(12, 12) + real(dp) :: expected_reversed(12, 12) + integer :: status + integer :: i + integer :: j + integer :: permutation(12) + + call valid_props(props) + coords_forward(:, 1) = [0.0_dp, 0.0_dp, 0.0_dp] + coords_forward(:, 2) = [2.0_dp, 0.0_dp, 0.0_dp] + coords_reversed(:, 1) = coords_forward(:, 2) + coords_reversed(:, 2) = coords_forward(:, 1) + + call uel3deb_global_stiffness(coords_forward, props, k_forward, transform, status) + call assert_equal_int(status, UEL3DEB_OK, 'forward node stiffness status') + call uel3deb_global_stiffness(coords_reversed, props, k_reversed, transform, status) + call assert_equal_int(status, UEL3DEB_OK, 'reversed node stiffness status') + + permutation = [7, 8, 9, 10, 11, 12, 1, 2, 3, 4, 5, 6] + do i = 1, 12 + do j = 1, 12 + expected_reversed(i, j) = k_forward(permutation(i), permutation(j)) + end do + end do + call assert_matrix_close(k_reversed, expected_reversed, tol_stiff, 'NOA-K-REVNODE-001') + end subroutine test_reversed_node_order + +end program test_kernel_transform_modes diff --git a/tests/fortran/uel_3d_euler_beam/test_support.f90 b/tests/fortran/uel_3d_euler_beam/test_support.f90 new file mode 100644 index 0000000..7e79809 --- /dev/null +++ b/tests/fortran/uel_3d_euler_beam/test_support.f90 @@ -0,0 +1,138 @@ +module uel_3d_euler_beam_test_support + use iso_fortran_env, only: real64 + implicit none + + integer, parameter :: dp = real64 + real(dp), parameter :: tol_stiff = 1.0e-10_dp + real(dp), parameter :: tol_vector = 1.0e-10_dp + real(dp), parameter :: tol_symmetry = 1.0e-12_dp + +contains + + subroutine fail_test(message) + character(len=*), intent(in) :: message + + write(*, '(A)') 'FAIL: ' // trim(message) + error stop 1 + end subroutine fail_test + + subroutine assert_true(condition, message) + logical, intent(in) :: condition + character(len=*), intent(in) :: message + + if (.not. condition) call fail_test(message) + end subroutine assert_true + + subroutine assert_equal_int(actual, expected, message) + integer, intent(in) :: actual + integer, intent(in) :: expected + character(len=*), intent(in) :: message + + if (actual /= expected) then + write(*, *) 'actual=', actual, ' expected=', expected + call fail_test(message) + end if + end subroutine assert_equal_int + + subroutine assert_close(actual, expected, tolerance, message) + real(dp), intent(in) :: actual + real(dp), intent(in) :: expected + real(dp), intent(in) :: tolerance + character(len=*), intent(in) :: message + real(dp) :: limit + + limit = tolerance * max(1.0_dp, abs(expected)) + if (abs(actual - expected) > limit) then + write(*, *) 'actual=', actual, ' expected=', expected, ' limit=', limit + call fail_test(message) + end if + end subroutine assert_close + + function max_abs_matrix(values) result(max_abs) + real(dp), intent(in) :: values(:, :) + real(dp) :: max_abs + + max_abs = maxval(abs(values)) + end function max_abs_matrix + + function max_abs_vector(values) result(max_abs) + real(dp), intent(in) :: values(:) + real(dp) :: max_abs + + max_abs = maxval(abs(values)) + end function max_abs_vector + + subroutine assert_matrix_close(actual, expected, tolerance, message) + real(dp), intent(in) :: actual(:, :) + real(dp), intent(in) :: expected(:, :) + real(dp), intent(in) :: tolerance + character(len=*), intent(in) :: message + real(dp) :: diff + real(dp) :: limit + + call assert_true(all(shape(actual) == shape(expected)), trim(message) // ' shape') + diff = maxval(abs(actual - expected)) + limit = tolerance * max(1.0_dp, max_abs_matrix(expected)) + if (diff > limit) then + write(*, *) 'max matrix diff=', diff, ' limit=', limit + call fail_test(message) + end if + end subroutine assert_matrix_close + + subroutine assert_vector_close(actual, expected, tolerance, message) + real(dp), intent(in) :: actual(:) + real(dp), intent(in) :: expected(:) + real(dp), intent(in) :: tolerance + character(len=*), intent(in) :: message + real(dp) :: diff + real(dp) :: limit + + call assert_true(size(actual) == size(expected), trim(message) // ' size') + diff = maxval(abs(actual - expected)) + limit = tolerance * max(1.0_dp, max_abs_vector(expected)) + if (diff > limit) then + write(*, *) 'max vector diff=', diff, ' limit=', limit + call fail_test(message) + end if + end subroutine assert_vector_close + + subroutine assert_vector_norm_le(actual, limit, message) + real(dp), intent(in) :: actual(:) + real(dp), intent(in) :: limit + character(len=*), intent(in) :: message + real(dp) :: norm_inf + + norm_inf = max_abs_vector(actual) + if (norm_inf > limit) then + write(*, *) 'max vector norm=', norm_inf, ' limit=', limit + call fail_test(message) + end if + end subroutine assert_vector_norm_le + + subroutine assert_matrix_symmetric(values, tolerance, message) + real(dp), intent(in) :: values(:, :) + real(dp), intent(in) :: tolerance + character(len=*), intent(in) :: message + real(dp) :: diff + real(dp) :: limit + + call assert_true(size(values, 1) == size(values, 2), trim(message) // ' square') + diff = maxval(abs(values - transpose(values))) + limit = tolerance * max(1.0_dp, max_abs_matrix(values)) + if (diff > limit) then + write(*, *) 'symmetry diff=', diff, ' limit=', limit + call fail_test(message) + end if + end subroutine assert_matrix_symmetric + + subroutine set_identity(values) + real(dp), intent(out) :: values(:, :) + integer :: i + + values = 0.0_dp + do i = 1, min(size(values, 1), size(values, 2)) + values(i, i) = 1.0_dp + end do + end subroutine set_identity + +end module uel_3d_euler_beam_test_support