forked from 1b-agent/fieldcraft-upstream
tests: add the NAFEMS LE5 Z-section oracle case
The Z-section cantilever under an end torque as five hex20 blocks (two flanges, two corner blocks, the web; two elements through every wall): axial stress at A, on the mid-surface of the lower flange at its free edge, -109.45 against -108 (2%). The solid model converges to about -109.7, 1.6% beyond the thin-walled reference, as ESRD's converged p-version solid (-109.5) does. The README also records the MITC4 mid-surface shell model of the same problem, which converges to about -111.3 (3.1%), and why the case is the solid. Part of #97.
This commit is contained in:
1 parent
68aff646b2
commit
65066b8451
5 files changed
+145
-1
No files matched your search
@@ -0,0 +1,100 @@
|
||||
# NAFEMS LE5 Z-section cantilever (hex20)
|
||||
|
||||
Our model of the published problem: a cantilever 10 m long (x) whose cross-section is a Z of
|
||||
0.1 m thick walls, E = 210 GPa, ν = 0.3 (m, MN, MPa). On the mid-surface the web is 2 m wide, in
|
||||
the plane z = 0 from y = −1 to y = 1, and the two flanges are 1 m high: the upper one at y = 1
|
||||
rises to z = 1, the lower one at y = −1 drops to z = −1. Every displacement is zero at x = 0. A
|
||||
torque of 1.2 MN·m acts at x = 10 as two shear forces of 0.6 MN, one along each flange's end
|
||||
(+z on the upper flange, −z on the lower), 2 m apart. The target is the axial stress σ_xx at A,
|
||||
x = 2.5 on the mid-surface of the lower flange at its free edge (2.5, −1, −1), where the restrained
|
||||
warping of the section puts its largest axial stress.
|
||||
|
||||
## The mesh
|
||||
|
||||
The walls are solid hex20 blocks (`mesh.block`), the route LE1 and LE10 take: the issue allows
|
||||
quad4 shells or hex20, and the shell model reaches a different limit (below). Five blocks,
|
||||
2 elements through every wall: the two flanges (0.95 m outside the web, k = 4 elements), the two
|
||||
corner blocks where a flange meets the web (2 × 2), and the web between them (8 elements); 32
|
||||
elements along the length. The blocks share their faces, so `mesh.block` joins them. The shear
|
||||
force on each flange is a total over the flange block's end face (z from ±0.05 to ±1), split by
|
||||
area. A is a node: the mid-plane of the lower flange (y = −1) is a node plane of its 2 elements
|
||||
through the thickness.
|
||||
|
||||
The recipe was generated with this script (`python3 le5.py 4 > recipe.json`):
|
||||
|
||||
```python
|
||||
import json, sys
|
||||
|
||||
def le5_solid(k):
|
||||
"""NAFEMS LE5 Z-section as hex20 solid blocks (m, MN, MPa): mid-surface web 2 wide in z = 0,
|
||||
flanges at y = ±1 reaching z = ±1, all walls 0.1 thick, length 10 along x; k sets the density
|
||||
(8k cells along the length, k per 0.95 m across the walls, 2 through the thickness)."""
|
||||
h = 0.05
|
||||
blocks = { # name: (y0, y1, z0, z1, ny, nz)
|
||||
"lower_flange": (-1 - h, -1 + h, -1.0, -h, 2, k),
|
||||
"lower_corner": (-1 - h, -1 + h, -h, h, 2, 2),
|
||||
"web": (-1 + h, 1 - h, -h, h, 2 * k, 2),
|
||||
"upper_corner": (1 - h, 1 + h, -h, h, 2, 2),
|
||||
"upper_flange": (1 - h, 1 + h, h, 1.0, 2, k),
|
||||
}
|
||||
commands = []
|
||||
for name, (y0, y1, z0, z1, ny, nz) in blocks.items():
|
||||
base = [[0.0, y0, z0], [0.0, y1, z0], [0.0, y1, z1], [0.0, y0, z1]]
|
||||
corners = base + [[10.0, y, z] for _, y, z in base]
|
||||
commands.append({"command": "mesh.block", "params": {"body": name, "kind": "hex20", "corners": corners, "divisions": [ny, nz, 8 * k], "faces": {"1": "root", "2": "tip"}}})
|
||||
return commands + [
|
||||
{"command": "model.set.create", "params": {"name": "all", "geometry": [{"body": b} for b in blocks]}},
|
||||
{"command": "model.set.create", "params": {"name": "root", "geometry": [{"body": b, "face": "root"} for b in blocks]}},
|
||||
{"command": "model.set.create", "params": {"name": "lower_tip", "geometry": [{"body": "lower_flange", "face": "tip"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "upper_tip", "geometry": [{"body": "upper_flange", "face": "tip"}]}},
|
||||
{"command": "model.material.create", "params": {"name": "Steel", "elastic": {"type": "isotropic", "young": 210000.0, "poisson": 0.3}}},
|
||||
{"command": "model.section.create", "params": {"name": "z_section", "set": "all", "material": "Steel", "kind": {"type": "solid"}}},
|
||||
{"command": "model.boundary_condition.create", "params": {"name": "root", "type": "fixed", "target": {"set": "root"}}},
|
||||
# the 1.2 MN·m torque: 0.6 MN along each flange's end face, 2 m apart
|
||||
{"command": "model.load.create", "params": {"name": "lower_shear", "type": "force", "target": {"set": "lower_tip"}, "vector": [0, 0, -0.6]}},
|
||||
{"command": "model.load.create", "params": {"name": "upper_shear", "type": "force", "target": {"set": "upper_tip"}, "vector": [0, 0, 0.6]}},
|
||||
{"command": "model.step.create", "params": {"name": "Torque", "procedure": {"type": "static"}, "boundary_conditions": ["root"], "loads": ["lower_shear", "upper_shear"]}},
|
||||
{"command": "solve.static", "params": {}, "id": "solve"},
|
||||
]
|
||||
|
||||
k = int(sys.argv[1]) if len(sys.argv) > 1 else 4
|
||||
print("[\n " + ",\n ".join(json.dumps(c) for c in le5_solid(k)) + "\n]")
|
||||
```
|
||||
|
||||
## Refinement
|
||||
|
||||
Every row is the listing above with the row's k, solved and probed at A as the case does; the
|
||||
case mesh is k = 4:
|
||||
|
||||
| k | elements | σ_xx at A | error |
|
||||
|---|---|---|---|
|
||||
| 1 | 128 | −108.93 | +0.86% |
|
||||
| 2 | 384 | −109.27 | +1.17% |
|
||||
| 4 (this case) | 1280 | −109.46 | +1.35% |
|
||||
| 8 | 4608 | −109.60 | +1.48% |
|
||||
|
||||
The result converges to about −109.7, 1.6% beyond the target. The published value comes from a
|
||||
thin-walled idealisation of the section; a solid model of the same section differs from it by
|
||||
about that much (ESRD's guide reports −109.5 for its converged p-version solid model on two
|
||||
meshes, p. 16). The next refinement of the case mesh moves the value by 0.13%, under a tenth of
|
||||
the tolerance.
|
||||
|
||||
A model of quad4 MITC4 shells on the mid-surface (walls meeting at the junction lines, the edge
|
||||
shears as consistent nodal forces) converges further away, to about −111.3 (320 elements along the
|
||||
length and 16 per metre across the walls: −111.65 and −111.06 at A from the two elements that meet
|
||||
there), 3.1% beyond the target (the Abaqus Benchmarks Guide, Table 4.2.5–1, reports −110.38 for its
|
||||
S4 shell on the refined mesh); we have not separated how much of the difference is the shell
|
||||
idealisation of the junctions. That is why this case is the solid model.
|
||||
|
||||
## Tolerance
|
||||
|
||||
2%: the target is published to three figures, and the solid model's converged value differs from
|
||||
the thin-walled reference by about 1.5% (above). `fixture.json` holds the k = 4 value: the mesh is
|
||||
built by `mesh.block` from the recipe's corners, so it is the same on every platform.
|
||||
|
||||
## Running it
|
||||
|
||||
```sh
|
||||
cargo xtask oracle --case nafems-le5
|
||||
cargo xtask oracle --bless nafems-le5 # only after the case passes
|
||||
```
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"title": "NAFEMS LE5 Z-section cantilever (hex20)",
|
||||
"kind": "benchmark",
|
||||
"source": "NAFEMS, The Standard NAFEMS Benchmarks, TNSB Rev. 3, October 1990, test LE5 (target: axial stress −108 MPa at A, mid-surface); the problem and target as reproduced in the Abaqus 2016 Benchmarks Guide, section 4.2.5 'LE5: Z-section cantilever', and in the ESRD StressCheck Benchmarks Guide 'The Standard NAFEMS Benchmarks: Linear Elastic Tests' (2018), p. 15",
|
||||
"quantities": [
|
||||
{
|
||||
"id": "sxx_A",
|
||||
"quantity": "σ_xx at A (2.5, −1, −1), mid-surface of the lower flange at its free edge",
|
||||
"measure": {"command": "solve.probe", "params": {"step": "Torque", "at": [[2.5, -1, -1]]}, "pointer": "/nodes/0/stress/0"},
|
||||
"value": -108.0,
|
||||
"tolerance": {"relative": 0.02},
|
||||
"reason": "the target is published to three figures and comes from a thin-walled idealisation; a 3D solid model of the section converges about 1.5% beyond it (ours: −109.46 here, −109.60 on the next mesh; ESRD's p-version solid: −109.5, see README)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"note": "FieldCraft's accepted results for this case, written by `cargo xtask oracle --bless`. A result outside its band fails CI.",
|
||||
"values": {
|
||||
"sxx_A": {
|
||||
"band": 1.0945472240551884e-7,
|
||||
"value": -109.45472240551884
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{"command": "mesh.block", "params": {"body": "lower_flange", "kind": "hex20", "corners": [[0.0, -1.05, -1.0], [0.0, -0.95, -1.0], [0.0, -0.95, -0.05], [0.0, -1.05, -0.05], [10.0, -1.05, -1.0], [10.0, -0.95, -1.0], [10.0, -0.95, -0.05], [10.0, -1.05, -0.05]], "divisions": [2, 4, 32], "faces": {"1": "root", "2": "tip"}}},
|
||||
{"command": "mesh.block", "params": {"body": "lower_corner", "kind": "hex20", "corners": [[0.0, -1.05, -0.05], [0.0, -0.95, -0.05], [0.0, -0.95, 0.05], [0.0, -1.05, 0.05], [10.0, -1.05, -0.05], [10.0, -0.95, -0.05], [10.0, -0.95, 0.05], [10.0, -1.05, 0.05]], "divisions": [2, 2, 32], "faces": {"1": "root", "2": "tip"}}},
|
||||
{"command": "mesh.block", "params": {"body": "web", "kind": "hex20", "corners": [[0.0, -0.95, -0.05], [0.0, 0.95, -0.05], [0.0, 0.95, 0.05], [0.0, -0.95, 0.05], [10.0, -0.95, -0.05], [10.0, 0.95, -0.05], [10.0, 0.95, 0.05], [10.0, -0.95, 0.05]], "divisions": [8, 2, 32], "faces": {"1": "root", "2": "tip"}}},
|
||||
{"command": "mesh.block", "params": {"body": "upper_corner", "kind": "hex20", "corners": [[0.0, 0.95, -0.05], [0.0, 1.05, -0.05], [0.0, 1.05, 0.05], [0.0, 0.95, 0.05], [10.0, 0.95, -0.05], [10.0, 1.05, -0.05], [10.0, 1.05, 0.05], [10.0, 0.95, 0.05]], "divisions": [2, 2, 32], "faces": {"1": "root", "2": "tip"}}},
|
||||
{"command": "mesh.block", "params": {"body": "upper_flange", "kind": "hex20", "corners": [[0.0, 0.95, 0.05], [0.0, 1.05, 0.05], [0.0, 1.05, 1.0], [0.0, 0.95, 1.0], [10.0, 0.95, 0.05], [10.0, 1.05, 0.05], [10.0, 1.05, 1.0], [10.0, 0.95, 1.0]], "divisions": [2, 4, 32], "faces": {"1": "root", "2": "tip"}}},
|
||||
{"command": "model.set.create", "params": {"name": "all", "geometry": [{"body": "lower_flange"}, {"body": "lower_corner"}, {"body": "web"}, {"body": "upper_corner"}, {"body": "upper_flange"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "root", "geometry": [{"body": "lower_flange", "face": "root"}, {"body": "lower_corner", "face": "root"}, {"body": "web", "face": "root"}, {"body": "upper_corner", "face": "root"}, {"body": "upper_flange", "face": "root"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "lower_tip", "geometry": [{"body": "lower_flange", "face": "tip"}]}},
|
||||
{"command": "model.set.create", "params": {"name": "upper_tip", "geometry": [{"body": "upper_flange", "face": "tip"}]}},
|
||||
{"command": "model.material.create", "params": {"name": "Steel", "elastic": {"type": "isotropic", "young": 210000.0, "poisson": 0.3}}},
|
||||
{"command": "model.section.create", "params": {"name": "z_section", "set": "all", "material": "Steel", "kind": {"type": "solid"}}},
|
||||
{"command": "model.boundary_condition.create", "params": {"name": "root", "type": "fixed", "target": {"set": "root"}}},
|
||||
{"command": "model.load.create", "params": {"name": "lower_shear", "type": "force", "target": {"set": "lower_tip"}, "vector": [0, 0, -0.6]}},
|
||||
{"command": "model.load.create", "params": {"name": "upper_shear", "type": "force", "target": {"set": "upper_tip"}, "vector": [0, 0, 0.6]}},
|
||||
{"command": "model.step.create", "params": {"name": "Torque", "procedure": {"type": "static"}, "boundary_conditions": ["root"], "loads": ["lower_shear", "upper_shear"]}},
|
||||
{"command": "solve.static", "params": {}, "id": "solve"}
|
||||
]
|
||||
Reference in new issue
Block a user