feat: implement 3d euler beam fortran kernel
This commit is contained in:
@@ -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: `step7` Fortran implementation
|
||||
- First pending step: `step8` validation readiness
|
||||
|
||||
## Planned Steps
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
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 - completed
|
||||
8. GREEN Fortran implementation - next
|
||||
9. Validation, physics sanity, and readiness audit
|
||||
8. GREEN Fortran implementation - completed
|
||||
9. Validation, physics sanity, and readiness audit - next
|
||||
|
||||
## Success Criteria
|
||||
|
||||
|
||||
+18
-13
@@ -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/step6.md` RED no-Abaqus test creation step
|
||||
- Next action: execute `phases/uel-3d-euler-beam/step7.md` Fortran implementation step
|
||||
- Current status: completed `phases/uel-3d-euler-beam/step7.md` Fortran implementation step
|
||||
- Next action: execute `phases/uel-3d-euler-beam/step8.md` validation-readiness step
|
||||
|
||||
## Completed
|
||||
|
||||
@@ -40,6 +40,15 @@
|
||||
- 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`.
|
||||
- Completed step 7 Fortran implementation for `uel-3d-euler-beam`.
|
||||
- Created `src/fortran/uel_3d_euler_beam_kernel.f90`.
|
||||
- Created `src/fortran/uel_3d_euler_beam_abi_adapter.f90`.
|
||||
- Added source-smoke no-Abaqus tests under `tests/fortran/`.
|
||||
- Updated `tests/fortran/manifest.json` with the source-smoke tests.
|
||||
- Fixed `scripts/validate_fortran.py` to create per-test build directories.
|
||||
- Fixed `scripts/fortran_toolchain.py` to use `ComSpec`/absolute `cmd.exe` and a minimal Windows PATH for oneAPI env setup.
|
||||
- Created `docs/build-test-reports/uel-3d-euler-beam-green.md`.
|
||||
- Updated `phases/uel-3d-euler-beam/index.json` step 7 to `completed`.
|
||||
|
||||
## In Progress
|
||||
|
||||
@@ -51,28 +60,24 @@
|
||||
|
||||
## Last Verification
|
||||
|
||||
Latest verification after completing step 6 RED no-Abaqus test creation:
|
||||
Latest verification after completing step 7 Fortran implementation:
|
||||
|
||||
```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_reference_artifacts.py
|
||||
python scripts/validate_workspace.py
|
||||
$env:HARNESS_FORTRAN_VALIDATION='detect'; python scripts/validate_workspace.py
|
||||
```
|
||||
|
||||
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.
|
||||
Result: all passed.
|
||||
- `python -m unittest discover -s scripts -p "test_*.py"`: 57 tests passed.
|
||||
- `python scripts/validate_fortran.py`: six Fortran manifest executables compiled and passed with Intel `ifx` through oneAPI env script.
|
||||
- `python scripts/validate_reference_artifacts.py`: reference artifact metadata validation succeeded.
|
||||
- `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.
|
||||
- `python scripts/validate_workspace.py`: succeeded.
|
||||
|
||||
## 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/step7.md`.
|
||||
- Start `phases/uel-3d-euler-beam/step8.md`.
|
||||
- Update this file when step status changes or before handing off.
|
||||
|
||||
@@ -12,3 +12,4 @@
|
||||
- `scripts/execute.py`는 phase step을 Codex subprocess로 실행하고, 각 step이 `phases/<phase>/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가 발생했다.
|
||||
- Step 7에서 위 PATH 문제는 `scripts/fortran_toolchain.py`가 `ComSpec`/절대 `cmd.exe`와 최소 Windows PATH를 사용하도록 수정해 해결했다. 또한 `scripts/validate_fortran.py`가 per-test build directory를 만들지 않아 `LNK1104`가 발생했으므로 `build_dir.mkdir(parents=True, exist_ok=True)`를 추가했다.
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# 3D Euler-Bernoulli Beam UEL GREEN Test Report
|
||||
|
||||
## Metadata
|
||||
- feature_id: uel-3d-euler-beam
|
||||
- source_red_report: `docs/build-test-reports/uel-3d-euler-beam-red.md`
|
||||
- source_reference_models: `docs/reference-models/uel-3d-euler-beam.md`
|
||||
- status: pass-for-workspace-validation
|
||||
- owner_agent: implementation-agent
|
||||
- date: 2026-06-11
|
||||
|
||||
## Implementation Scope
|
||||
|
||||
Production source added in this step:
|
||||
|
||||
- `src/fortran/uel_3d_euler_beam_kernel.f90`
|
||||
- `src/fortran/uel_3d_euler_beam_abi_adapter.f90`
|
||||
|
||||
The implementation covers the approved no-Abaqus scope:
|
||||
|
||||
- 12-by-12 Euler-Bernoulli local stiffness matrix.
|
||||
- Local frame construction from node coordinates and `PROPS(7:9)`.
|
||||
- `K_global = T^T*k_local*T`.
|
||||
- Static residual `RHS(1:12,1) = -K_global*U(1:12)`.
|
||||
- `AMATRX` and `RHS` selection for `LFLAGS(3)=1`, `2`, and `5`.
|
||||
- Shape, property, geometry, orientation, and unsupported-request diagnostics.
|
||||
- Deterministic zeroing of `RHS`, `AMATRX`, and `ENERGY`; `PNEWDT` unchanged for valid calls.
|
||||
|
||||
The fixed-form Abaqus `UEL` wrapper remains deferred because the approved step 7 no-Abaqus tests target the kernel and ABI adapter only.
|
||||
|
||||
## Harness Support Changes
|
||||
|
||||
Two validation harness fixes were required after the step 6 RED manifest became executable:
|
||||
|
||||
- `scripts/validate_fortran.py` now creates each `build/fortran-tests/<test-name>/` directory before invoking Intel Fortran.
|
||||
- `scripts/fortran_toolchain.py` now invokes the oneAPI environment script through `ComSpec` or discovered `cmd.exe` and sets a minimal Windows PATH for the child command shell.
|
||||
|
||||
These changes are covered by updated Python harness tests in:
|
||||
|
||||
- `scripts/test_validate_fortran.py`
|
||||
- `scripts/test_fortran_toolchain.py`
|
||||
|
||||
## Command Log Summary
|
||||
|
||||
| order | command | exit_code | result | evidence |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| 1 | `python scripts/test_validate_fortran.py -k build_commands_create` | 1 then 0 | RED then GREEN | failed before build-dir creation fix; passed after `build_dir.mkdir(...)` |
|
||||
| 2 | `python scripts/test_fortran_toolchain.py -k wrap_command` | 1 then 0 | RED then GREEN | failed before absolute `cmd.exe` and minimal PATH wrapper; passed after wrapper update |
|
||||
| 3 | `python -m unittest discover -s scripts -p "test_*.py"` | 0 | pass | 57 Python harness tests passed |
|
||||
| 4 | `python scripts/validate_reference_artifacts.py` | 0 | pass | reference artifact metadata validation succeeded |
|
||||
| 5 | `python scripts/validate_fortran.py` | 0 | pass | all six no-Abaqus Fortran manifest executables compiled and passed with Intel `ifx` through oneAPI env script |
|
||||
| 6 | `python scripts/validate_workspace.py` | 0 | pass | reference validation and Fortran validation both succeeded |
|
||||
|
||||
## No-Abaqus Fortran Tests Passed
|
||||
|
||||
| manifest_test | result |
|
||||
| --- | --- |
|
||||
| `uel_3d_euler_beam_kernel_stiffness` | pass |
|
||||
| `uel_3d_euler_beam_kernel_transform_modes` | pass |
|
||||
| `uel_3d_euler_beam_abi_static` | pass |
|
||||
| `uel_3d_euler_beam_invalid_inputs` | pass |
|
||||
| `uel_3d_euler_beam_kernel_source_smoke` | pass |
|
||||
| `uel_3d_euler_beam_abi_adapter_source_smoke` | pass |
|
||||
|
||||
## Handoff to Step 8
|
||||
|
||||
Step 8 can proceed to validation readiness using the passing no-Abaqus evidence above. External Abaqus reference artifacts remain absent and must still be user-generated before solver-result comparison.
|
||||
@@ -47,7 +47,8 @@
|
||||
{
|
||||
"step": 7,
|
||||
"name": "fortran-implementation",
|
||||
"status": "pending"
|
||||
"status": "completed",
|
||||
"summary": "Implemented the approved no-Abaqus Fortran kernel and ABI adapter source layout, added narrow source smoke manifest tests, fixed Fortran validation harness build-dir and cmd PATH handling, and recorded GREEN evidence in docs/build-test-reports/uel-3d-euler-beam-green.md. Python harness, Fortran manifest, reference artifact validation, and workspace validation all passed."
|
||||
},
|
||||
{
|
||||
"step": 8,
|
||||
|
||||
@@ -72,10 +72,23 @@ def quote_path(path: str | Path) -> str:
|
||||
return shell_join([path])
|
||||
|
||||
|
||||
def cmd_executable(env: dict[str, str] | None = None) -> str:
|
||||
env = env or os.environ
|
||||
return env.get("ComSpec") or shutil.which("cmd.exe") or shutil.which("cmd") or "cmd"
|
||||
|
||||
|
||||
def cmd_minimal_path(env: dict[str, str] | None = None) -> str:
|
||||
env = env or os.environ
|
||||
system_root = env.get("SystemRoot") or r"C:\Windows"
|
||||
return f"{system_root}\\System32;{system_root}"
|
||||
|
||||
|
||||
def wrap_command(toolchain: FortranToolchain, args: list[str | Path]) -> str:
|
||||
command = shell_join(args)
|
||||
if toolchain.env_script is None:
|
||||
return command
|
||||
|
||||
env_script = quote_path(toolchain.env_script)
|
||||
return f'cmd /d /s /c "call {env_script} intel64 >nul && {command}"'
|
||||
cmd = quote_path(cmd_executable())
|
||||
path = cmd_minimal_path()
|
||||
return f'{cmd} /d /s /c "set "PATH={path}" && call {env_script} intel64 >nul && {command}"'
|
||||
|
||||
@@ -65,9 +65,15 @@ class FortranToolchainTests(unittest.TestCase):
|
||||
env_script=Path(r"C:\Program Files (x86)\Intel\oneAPI\setvars.bat"),
|
||||
)
|
||||
|
||||
command = fortran_toolchain.wrap_command(toolchain, ["ifx", "/nologo", "test.f90"])
|
||||
with patch.dict(
|
||||
os.environ,
|
||||
{"ComSpec": r"C:\Windows\System32\cmd.exe", "SystemRoot": r"C:\Windows"},
|
||||
clear=True,
|
||||
):
|
||||
command = fortran_toolchain.wrap_command(toolchain, ["ifx", "/nologo", "test.f90"])
|
||||
|
||||
self.assertIn("cmd /d /s /c", command)
|
||||
self.assertIn(r"C:\Windows\System32\cmd.exe /d /s /c", command)
|
||||
self.assertIn('set "PATH=C:\\Windows\\System32;C:\\Windows"', command)
|
||||
self.assertIn('call "C:\\Program Files (x86)\\Intel\\oneAPI\\setvars.bat" intel64', command)
|
||||
self.assertIn("ifx /nologo test.f90", command)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import json
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
from unittest import mock
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@@ -19,7 +20,7 @@ class ValidateFortranTests(unittest.TestCase):
|
||||
validate_fortran = load_validate_fortran()
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
with unittest.mock.patch.dict(os.environ, {}, clear=True):
|
||||
with mock.patch.dict(os.environ, {}, clear=True):
|
||||
commands = validate_fortran.discover_commands(root)
|
||||
|
||||
self.assertEqual(commands, [])
|
||||
@@ -31,7 +32,7 @@ class ValidateFortranTests(unittest.TestCase):
|
||||
manifest = root / "tests" / "fortran" / "manifest.json"
|
||||
manifest.parent.mkdir(parents=True)
|
||||
manifest.write_text(json.dumps({"tests": []}), encoding="utf-8")
|
||||
with unittest.mock.patch.dict(os.environ, {"HARNESS_FORTRAN_VALIDATION": "off"}, clear=True):
|
||||
with mock.patch.dict(os.environ, {"HARNESS_FORTRAN_VALIDATION": "off"}, clear=True):
|
||||
commands = validate_fortran.discover_commands(root)
|
||||
|
||||
self.assertEqual(commands, [])
|
||||
@@ -43,8 +44,8 @@ class ValidateFortranTests(unittest.TestCase):
|
||||
manifest = root / "tests" / "fortran" / "manifest.json"
|
||||
manifest.parent.mkdir(parents=True)
|
||||
manifest.write_text(json.dumps({"tests": [{"name": "case", "sources": ["a.f90"]}]}), encoding="utf-8")
|
||||
with unittest.mock.patch.dict(os.environ, {}, clear=True):
|
||||
with unittest.mock.patch.object(validate_fortran, "resolve_toolchain", return_value=None):
|
||||
with mock.patch.dict(os.environ, {}, clear=True):
|
||||
with mock.patch.object(validate_fortran, "resolve_toolchain", return_value=None):
|
||||
with self.assertRaises(validate_fortran.FortranValidationError):
|
||||
validate_fortran.discover_commands(root)
|
||||
|
||||
@@ -71,8 +72,8 @@ class ValidateFortranTests(unittest.TestCase):
|
||||
encoding="utf-8",
|
||||
)
|
||||
toolchain = validate_fortran.FortranToolchain(name="ifx", executable="ifx", env_script=None)
|
||||
with unittest.mock.patch.dict(os.environ, {}, clear=True):
|
||||
with unittest.mock.patch.object(validate_fortran, "resolve_toolchain", return_value=toolchain):
|
||||
with mock.patch.dict(os.environ, {}, clear=True):
|
||||
with mock.patch.object(validate_fortran, "resolve_toolchain", return_value=toolchain):
|
||||
commands = validate_fortran.discover_commands(root)
|
||||
|
||||
self.assertEqual(len(commands), 2)
|
||||
@@ -81,6 +82,36 @@ class ValidateFortranTests(unittest.TestCase):
|
||||
self.assertIn("/exe:", commands[0])
|
||||
self.assertTrue(commands[1].endswith("umat_linear_elastic_kernel.exe"))
|
||||
|
||||
def test_manifest_build_commands_create_test_build_directories(self):
|
||||
validate_fortran = load_validate_fortran()
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
root = Path(tmp)
|
||||
manifest = root / "tests" / "fortran" / "manifest.json"
|
||||
manifest.parent.mkdir(parents=True)
|
||||
manifest.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"tests": [
|
||||
{
|
||||
"name": "uel_3d_euler_beam_kernel_stiffness",
|
||||
"sources": [
|
||||
"src/fortran/uel_3d_euler_beam_kernel.f90",
|
||||
"tests/fortran/test_uel_3d_euler_beam_kernel.f90",
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
),
|
||||
encoding="utf-8",
|
||||
)
|
||||
toolchain = validate_fortran.FortranToolchain(name="ifx", executable="ifx", env_script=None)
|
||||
with mock.patch.dict(os.environ, {}, clear=True):
|
||||
with mock.patch.object(validate_fortran, "resolve_toolchain", return_value=toolchain):
|
||||
validate_fortran.discover_commands(root)
|
||||
|
||||
build_dir = root / "build" / "fortran-tests" / "uel_3d_euler_beam_kernel_stiffness"
|
||||
self.assertTrue(build_dir.is_dir())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -62,6 +62,7 @@ def build_test_commands(root: Path, manifest: dict, toolchain: FortranToolchain)
|
||||
for record in manifest.get("tests", []):
|
||||
name, sources = _validate_test_record(record)
|
||||
build_dir = root / BUILD_ROOT / name
|
||||
build_dir.mkdir(parents=True, exist_ok=True)
|
||||
exe_path = build_dir / f"{name}.exe"
|
||||
source_paths = [root / source for source in sources]
|
||||
compile_args: list[str | Path] = [
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
module uel_3d_euler_beam_abi_adapter
|
||||
use iso_fortran_env, only: real64
|
||||
use ieee_arithmetic, only: ieee_is_finite
|
||||
use uel_3d_euler_beam_kernel, only: &
|
||||
UEL3DEB_OK, 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_E014_LFLAGS2, &
|
||||
UEL3DEB_E015_LFLAGS3, UEL3DEB_E016_NDLOAD, &
|
||||
uel3deb_validate_kernel_inputs, uel3deb_global_stiffness
|
||||
implicit none
|
||||
|
||||
private
|
||||
|
||||
integer, parameter :: dp = real64
|
||||
|
||||
public :: uel3deb_abi_static
|
||||
|
||||
contains
|
||||
|
||||
subroutine uel3deb_abi_static(rhs, amatrx, energy, ndofel, nrhs, nsvars, &
|
||||
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
|
||||
integer, intent(out) :: status
|
||||
real(dp) :: coords3(3, 2)
|
||||
real(dp) :: props9(9)
|
||||
real(dp) :: u12(12)
|
||||
real(dp) :: k_global(12, 12)
|
||||
real(dp) :: transform(12, 12)
|
||||
real(dp) :: residual(12)
|
||||
|
||||
status = UEL3DEB_OK
|
||||
rhs = 0.0_dp
|
||||
amatrx = 0.0_dp
|
||||
energy = 0.0_dp
|
||||
|
||||
if (ndofel /= 12) then
|
||||
status = UEL3DEB_E001_NDOFEL
|
||||
return
|
||||
end if
|
||||
if (nnode /= 2) then
|
||||
status = UEL3DEB_E002_NNODE
|
||||
return
|
||||
end if
|
||||
if (mcrd < 3) then
|
||||
status = UEL3DEB_E003_MCRD
|
||||
return
|
||||
end if
|
||||
if (nprops /= 9) then
|
||||
status = UEL3DEB_E004_NPROPS
|
||||
return
|
||||
end if
|
||||
if (njprop /= 0) then
|
||||
status = UEL3DEB_E005_NJPROP
|
||||
return
|
||||
end if
|
||||
if (nsvars /= 0) then
|
||||
status = UEL3DEB_E006_NSVARS
|
||||
return
|
||||
end if
|
||||
if (nrhs /= 1) then
|
||||
status = UEL3DEB_E007_NRHS
|
||||
return
|
||||
end if
|
||||
if (mlvarx < 12) then
|
||||
status = UEL3DEB_E008_MLVARX
|
||||
return
|
||||
end if
|
||||
if (lflags(2) /= 0) then
|
||||
status = UEL3DEB_E014_LFLAGS2
|
||||
return
|
||||
end if
|
||||
if (.not. any(lflags(3) == [1, 2, 5])) then
|
||||
status = UEL3DEB_E015_LFLAGS3
|
||||
return
|
||||
end if
|
||||
if (ndload /= 0) then
|
||||
status = UEL3DEB_E016_NDLOAD
|
||||
return
|
||||
end if
|
||||
|
||||
props9 = props(1:9)
|
||||
coords3 = coords(1:3, 1:2)
|
||||
u12 = u(1:12)
|
||||
|
||||
if (.not. all(ieee_is_finite(u12))) then
|
||||
status = UEL3DEB_E009_NONFINITE
|
||||
return
|
||||
end if
|
||||
|
||||
call uel3deb_validate_kernel_inputs(coords3, props9, status)
|
||||
if (status /= UEL3DEB_OK) return
|
||||
|
||||
call uel3deb_global_stiffness(coords3, props9, k_global, transform, status)
|
||||
if (status /= UEL3DEB_OK) return
|
||||
|
||||
residual = -matmul(k_global, u12)
|
||||
if (lflags(3) == 1 .or. lflags(3) == 2) amatrx(1:12, 1:12) = k_global
|
||||
if (lflags(3) == 1 .or. lflags(3) == 5) rhs(1:12, 1) = residual
|
||||
end subroutine uel3deb_abi_static
|
||||
|
||||
end module uel_3d_euler_beam_abi_adapter
|
||||
@@ -0,0 +1,274 @@
|
||||
module uel_3d_euler_beam_kernel
|
||||
use iso_fortran_env, only: real64
|
||||
use ieee_arithmetic, only: ieee_is_finite
|
||||
implicit none
|
||||
|
||||
private
|
||||
|
||||
integer, parameter :: dp = real64
|
||||
|
||||
integer, parameter, public :: UEL3DEB_OK = 0
|
||||
integer, parameter, public :: UEL3DEB_E001_NDOFEL = 1
|
||||
integer, parameter, public :: UEL3DEB_E002_NNODE = 2
|
||||
integer, parameter, public :: UEL3DEB_E003_MCRD = 3
|
||||
integer, parameter, public :: UEL3DEB_E004_NPROPS = 4
|
||||
integer, parameter, public :: UEL3DEB_E005_NJPROP = 5
|
||||
integer, parameter, public :: UEL3DEB_E006_NSVARS = 6
|
||||
integer, parameter, public :: UEL3DEB_E007_NRHS = 7
|
||||
integer, parameter, public :: UEL3DEB_E008_MLVARX = 8
|
||||
integer, parameter, public :: UEL3DEB_E009_NONFINITE = 9
|
||||
integer, parameter, public :: UEL3DEB_E010_NONPOSITIVE_PROPERTY = 10
|
||||
integer, parameter, public :: UEL3DEB_E011_ZERO_LENGTH = 11
|
||||
integer, parameter, public :: UEL3DEB_E012_ZERO_ORIENTATION = 12
|
||||
integer, parameter, public :: UEL3DEB_E013_PARALLEL_ORIENTATION = 13
|
||||
integer, parameter, public :: UEL3DEB_E014_LFLAGS2 = 14
|
||||
integer, parameter, public :: UEL3DEB_E015_LFLAGS3 = 15
|
||||
integer, parameter, public :: UEL3DEB_E016_NDLOAD = 16
|
||||
|
||||
public :: uel3deb_validate_kernel_inputs
|
||||
public :: uel3deb_local_stiffness
|
||||
public :: uel3deb_global_stiffness
|
||||
|
||||
contains
|
||||
|
||||
subroutine uel3deb_validate_kernel_inputs(coords, props, status)
|
||||
real(dp), intent(in) :: coords(3, 2)
|
||||
real(dp), intent(in) :: props(9)
|
||||
integer, intent(out) :: status
|
||||
real(dp) :: axis(3)
|
||||
real(dp) :: a_ref(3)
|
||||
real(dp) :: a_perp(3)
|
||||
real(dp) :: length
|
||||
real(dp) :: coord_scale
|
||||
real(dp) :: ref_norm
|
||||
real(dp) :: perp_norm
|
||||
|
||||
status = UEL3DEB_OK
|
||||
|
||||
if (.not. all(ieee_is_finite(coords))) then
|
||||
status = UEL3DEB_E009_NONFINITE
|
||||
return
|
||||
end if
|
||||
if (.not. all(ieee_is_finite(props))) then
|
||||
status = UEL3DEB_E009_NONFINITE
|
||||
return
|
||||
end if
|
||||
if (any(props(1:6) <= 0.0_dp)) then
|
||||
status = UEL3DEB_E010_NONPOSITIVE_PROPERTY
|
||||
return
|
||||
end if
|
||||
|
||||
axis = coords(:, 2) - coords(:, 1)
|
||||
length = norm3(axis)
|
||||
coord_scale = max(1.0_dp, norm3(coords(:, 1)), norm3(coords(:, 2)))
|
||||
if (length <= 1.0e-12_dp * coord_scale) then
|
||||
status = UEL3DEB_E011_ZERO_LENGTH
|
||||
return
|
||||
end if
|
||||
|
||||
a_ref = props(7:9)
|
||||
ref_norm = norm3(a_ref)
|
||||
if (ref_norm <= 1.0e-12_dp) then
|
||||
status = UEL3DEB_E012_ZERO_ORIENTATION
|
||||
return
|
||||
end if
|
||||
|
||||
axis = axis / length
|
||||
a_perp = a_ref - dot_product(a_ref, axis) * axis
|
||||
perp_norm = norm3(a_perp)
|
||||
if (perp_norm / ref_norm <= 1.0e-8_dp) then
|
||||
status = UEL3DEB_E013_PARALLEL_ORIENTATION
|
||||
return
|
||||
end if
|
||||
end subroutine uel3deb_validate_kernel_inputs
|
||||
|
||||
subroutine uel3deb_local_stiffness(length, young, shear, area, iy, iz, &
|
||||
torsion_j, k_local, status)
|
||||
real(dp), intent(in) :: length
|
||||
real(dp), intent(in) :: young
|
||||
real(dp), intent(in) :: shear
|
||||
real(dp), intent(in) :: area
|
||||
real(dp), intent(in) :: iy
|
||||
real(dp), intent(in) :: iz
|
||||
real(dp), intent(in) :: torsion_j
|
||||
real(dp), intent(out) :: k_local(12, 12)
|
||||
integer, intent(out) :: status
|
||||
real(dp) :: axial
|
||||
real(dp) :: torsion
|
||||
real(dp) :: b2_12
|
||||
real(dp) :: b2_6
|
||||
real(dp) :: b2_4
|
||||
real(dp) :: b2_2
|
||||
real(dp) :: b3_12
|
||||
real(dp) :: b3_6
|
||||
real(dp) :: b3_4
|
||||
real(dp) :: b3_2
|
||||
|
||||
k_local = 0.0_dp
|
||||
status = UEL3DEB_OK
|
||||
|
||||
if (.not. ieee_is_finite(length) .or. .not. ieee_is_finite(young) .or. &
|
||||
.not. ieee_is_finite(shear) .or. .not. ieee_is_finite(area) .or. &
|
||||
.not. ieee_is_finite(iy) .or. .not. ieee_is_finite(iz) .or. &
|
||||
.not. ieee_is_finite(torsion_j)) then
|
||||
status = UEL3DEB_E009_NONFINITE
|
||||
return
|
||||
end if
|
||||
if (young <= 0.0_dp .or. shear <= 0.0_dp .or. area <= 0.0_dp .or. &
|
||||
iy <= 0.0_dp .or. iz <= 0.0_dp .or. torsion_j <= 0.0_dp) then
|
||||
status = UEL3DEB_E010_NONPOSITIVE_PROPERTY
|
||||
return
|
||||
end if
|
||||
if (length <= 0.0_dp) then
|
||||
status = UEL3DEB_E011_ZERO_LENGTH
|
||||
return
|
||||
end if
|
||||
|
||||
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
|
||||
|
||||
k_local(1, 1) = axial
|
||||
k_local(1, 7) = -axial
|
||||
k_local(7, 1) = -axial
|
||||
k_local(7, 7) = axial
|
||||
|
||||
k_local(4, 4) = torsion
|
||||
k_local(4, 10) = -torsion
|
||||
k_local(10, 4) = -torsion
|
||||
k_local(10, 10) = torsion
|
||||
|
||||
k_local(3, 3) = b2_12
|
||||
k_local(3, 5) = -b2_6
|
||||
k_local(3, 9) = -b2_12
|
||||
k_local(3, 11) = -b2_6
|
||||
k_local(5, 3) = -b2_6
|
||||
k_local(5, 5) = b2_4
|
||||
k_local(5, 9) = b2_6
|
||||
k_local(5, 11) = b2_2
|
||||
k_local(9, 3) = -b2_12
|
||||
k_local(9, 5) = b2_6
|
||||
k_local(9, 9) = b2_12
|
||||
k_local(9, 11) = b2_6
|
||||
k_local(11, 3) = -b2_6
|
||||
k_local(11, 5) = b2_2
|
||||
k_local(11, 9) = b2_6
|
||||
k_local(11, 11) = b2_4
|
||||
|
||||
k_local(2, 2) = b3_12
|
||||
k_local(2, 6) = b3_6
|
||||
k_local(2, 8) = -b3_12
|
||||
k_local(2, 12) = b3_6
|
||||
k_local(6, 2) = b3_6
|
||||
k_local(6, 6) = b3_4
|
||||
k_local(6, 8) = -b3_6
|
||||
k_local(6, 12) = b3_2
|
||||
k_local(8, 2) = -b3_12
|
||||
k_local(8, 6) = -b3_6
|
||||
k_local(8, 8) = b3_12
|
||||
k_local(8, 12) = -b3_6
|
||||
k_local(12, 2) = b3_6
|
||||
k_local(12, 6) = b3_2
|
||||
k_local(12, 8) = -b3_6
|
||||
k_local(12, 12) = b3_4
|
||||
end subroutine uel3deb_local_stiffness
|
||||
|
||||
subroutine uel3deb_global_stiffness(coords, props, k_global, transform, status)
|
||||
real(dp), intent(in) :: coords(3, 2)
|
||||
real(dp), intent(in) :: props(9)
|
||||
real(dp), intent(out) :: k_global(12, 12)
|
||||
real(dp), intent(out) :: transform(12, 12)
|
||||
integer, intent(out) :: status
|
||||
real(dp) :: k_local(12, 12)
|
||||
real(dp) :: rotation(3, 3)
|
||||
real(dp) :: length
|
||||
|
||||
k_global = 0.0_dp
|
||||
transform = 0.0_dp
|
||||
|
||||
call build_frame(coords, props, rotation, length, status)
|
||||
if (status /= UEL3DEB_OK) return
|
||||
|
||||
call uel3deb_local_stiffness(length, props(1), props(2), props(3), &
|
||||
props(4), props(5), props(6), k_local, &
|
||||
status)
|
||||
if (status /= UEL3DEB_OK) return
|
||||
|
||||
call fill_transform(rotation, transform)
|
||||
k_global = matmul(transpose(transform), matmul(k_local, transform))
|
||||
end subroutine uel3deb_global_stiffness
|
||||
|
||||
subroutine build_frame(coords, props, rotation, length, status)
|
||||
real(dp), intent(in) :: coords(3, 2)
|
||||
real(dp), intent(in) :: props(9)
|
||||
real(dp), intent(out) :: rotation(3, 3)
|
||||
real(dp), intent(out) :: length
|
||||
integer, intent(out) :: status
|
||||
real(dp) :: e1(3)
|
||||
real(dp) :: e2(3)
|
||||
real(dp) :: e3(3)
|
||||
real(dp) :: a_ref(3)
|
||||
real(dp) :: a_perp(3)
|
||||
|
||||
rotation = 0.0_dp
|
||||
length = 0.0_dp
|
||||
|
||||
call uel3deb_validate_kernel_inputs(coords, props, status)
|
||||
if (status /= UEL3DEB_OK) return
|
||||
|
||||
e1 = coords(:, 2) - coords(:, 1)
|
||||
length = norm3(e1)
|
||||
e1 = e1 / length
|
||||
|
||||
a_ref = props(7:9)
|
||||
a_perp = a_ref - dot_product(a_ref, e1) * e1
|
||||
e2 = a_perp / norm3(a_perp)
|
||||
e3 = cross3(e1, e2)
|
||||
|
||||
rotation(1, :) = e1
|
||||
rotation(2, :) = e2
|
||||
rotation(3, :) = e3
|
||||
end subroutine build_frame
|
||||
|
||||
subroutine fill_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_transform
|
||||
|
||||
pure function norm3(values) result(length)
|
||||
real(dp), intent(in) :: values(3)
|
||||
real(dp) :: length
|
||||
|
||||
length = sqrt(dot_product(values, values))
|
||||
end function norm3
|
||||
|
||||
pure function cross3(a, b) result(c)
|
||||
real(dp), intent(in) :: a(3)
|
||||
real(dp), intent(in) :: b(3)
|
||||
real(dp) :: c(3)
|
||||
|
||||
c(1) = a(2) * b(3) - a(3) * b(2)
|
||||
c(2) = a(3) * b(1) - a(1) * b(3)
|
||||
c(3) = a(1) * b(2) - a(2) * b(1)
|
||||
end function cross3
|
||||
|
||||
end module uel_3d_euler_beam_kernel
|
||||
@@ -33,6 +33,23 @@
|
||||
"src/fortran/uel_3d_euler_beam_abi_adapter.f90",
|
||||
"tests/fortran/uel_3d_euler_beam/test_invalid_inputs.f90"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "uel_3d_euler_beam_kernel_source_smoke",
|
||||
"sources": [
|
||||
"tests/fortran/uel_3d_euler_beam/test_support.f90",
|
||||
"src/fortran/uel_3d_euler_beam_kernel.f90",
|
||||
"tests/fortran/test_uel_3d_euler_beam_kernel.f90"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "uel_3d_euler_beam_abi_adapter_source_smoke",
|
||||
"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/test_uel_3d_euler_beam_abi_adapter.f90"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
program test_uel_3d_euler_beam_abi_adapter
|
||||
use uel_3d_euler_beam_test_support
|
||||
use uel_3d_euler_beam_kernel, only: UEL3DEB_OK
|
||||
use uel_3d_euler_beam_abi_adapter, only: uel3deb_abi_static
|
||||
implicit none
|
||||
|
||||
real(dp) :: rhs(12, 1)
|
||||
real(dp) :: amatrx(12, 12)
|
||||
real(dp) :: energy(8)
|
||||
real(dp) :: props(9)
|
||||
real(dp) :: coords(3, 2)
|
||||
real(dp) :: u(12)
|
||||
real(dp) :: pnewdt
|
||||
integer :: lflags(5)
|
||||
integer :: jprops(1)
|
||||
integer :: status
|
||||
|
||||
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 = 0.0_dp
|
||||
pnewdt = 1.0_dp
|
||||
lflags = 0
|
||||
lflags(3) = 2
|
||||
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)
|
||||
call assert_equal_int(status, UEL3DEB_OK, 'abi adapter source smoke status')
|
||||
call assert_matrix_symmetric(amatrx, tol_symmetry, 'abi adapter source smoke K')
|
||||
write(*, '(A)') 'PASS uel_3d_euler_beam_abi_adapter_source_smoke'
|
||||
end program test_uel_3d_euler_beam_abi_adapter
|
||||
@@ -0,0 +1,19 @@
|
||||
program test_uel_3d_euler_beam_kernel
|
||||
use uel_3d_euler_beam_test_support
|
||||
use uel_3d_euler_beam_kernel, only: &
|
||||
UEL3DEB_OK, uel3deb_validate_kernel_inputs
|
||||
implicit none
|
||||
|
||||
real(dp) :: coords(3, 2)
|
||||
real(dp) :: props(9)
|
||||
integer :: status
|
||||
|
||||
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]
|
||||
|
||||
call uel3deb_validate_kernel_inputs(coords, props, status)
|
||||
call assert_equal_int(status, UEL3DEB_OK, 'kernel source smoke status')
|
||||
write(*, '(A)') 'PASS uel_3d_euler_beam_kernel_source_smoke'
|
||||
end program test_uel_3d_euler_beam_kernel
|
||||
Reference in New Issue
Block a user