3fd4fb7b8c56f93c1a38ee270f4c5870e4affc5e
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4da70faa1e |
rtx-fea: QM6 as an opt-in bending path; cantilever pinned to Euler-Bernoulli directly
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Format Check (push) Canceled after 0s
CI / Clippy Check (push) Canceled after 0s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Build (ubuntu-latest) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (push) Canceled after 0s
CI / Python Bindings (maturin) (macos-latest) (push) Canceled after 0s
CI / Python Bindings (maturin) (ubuntu-latest) (push) Canceled after 0s
CI / WASM Build + Size Check (push) Canceled after 0s
CI / Distributed Training Tests (push) Canceled after 0s
CI / CI Success (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
The QM6 incompatible-modes stiffness existed and was verified
(compute_stiffness_matrix_incompatible) but nothing could reach it: the
assembler always routed Quad4 through the compatible element. AssemblyOptions
gains use_incompatible_modes (default false — every existing matrix is
byte-identical, which the manufactured-solution verification depends on),
threaded through GlobalAssembler into StandardFiniteElement; element types
QM6 does not apply to keep their standard stiffness either way.
What it buys, measured on the cantilever first bending mode against
Euler-Bernoulli's 40.3848 Hz:
mesh QM6 (error) compatible (error)
8x2 40.4020 (+0.04%) 81.8102 (+102.6%)
16x4 40.3402 (-0.11%) 53.8022 (+33.2%)
32x8 40.3242 (-0.15%) 44.0796 (+9.2%)
The frequency is now asserted against the closed form directly (0.5% band)
instead of as convergence-from-above, plus the condensation theorem — QM6
can only soften, so its frequency must sit at or below the compatible one on
every mesh. The slight undershoot on finer meshes is physical: the 2-D solid
carries the transverse shear flexibility the beam theory neglects.
552 rtx-fea tests, 0 failing.
Co-Authored-By: Claude Fable 5 <[email protected]>
|
||
|
|
87cf392556 |
rtx-fea: re-enable the remaining CPU test modules; fix three real defects they caught
CI / Format Check (push) Canceled after 0s
CI / Clippy Check (push) Canceled after 0s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Build (ubuntu-latest) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (push) Canceled after 0s
CI / Python Bindings (maturin) (macos-latest) (push) Canceled after 0s
CI / Python Bindings (maturin) (ubuntu-latest) (push) Canceled after 0s
CI / WASM Build + Size Check (push) Canceled after 0s
CI / Distributed Training Tests (push) Canceled after 0s
CI / CI Success (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
All 33 remaining #[cfg(disabled)] test modules outside the GPU cluster are now enabled: assembly (dof_mapping, constraints, global assembly), boundary (mod + dirichlet/neumann/robin/thermal/contact), analysis (mod + static), materials (mod, linear_elastic, hyperelastic, plasticity), elements (mod, element_matrices, isoparametric, jacobian, quadrature), mesh (element_types, connectivity, topology, topology_repair), solvers (mod, direct, iterative, nonlinear) and lib.rs. Lib tests 117 -> 335, stable across repeated runs. Only gpu_solver_tests and the GpuMeshData fixture stay disabled — they need CUDA hardware and belong to the GPU tranche. Three real defects found by the newly-compiling tests, each fixed: - Direct solvers reused factorizations keyed on matrix SIZE alone. In a Newton loop the Jacobian changes every iteration but never its dimension, so LuDirect/CholeskyDirect/LdltDirect silently solved with the first iteration's factorization forever — Newton on x^2-4 crawled to x=1.955 in 1000 iterations instead of converging in 5. Invisible in single-solve linear analysis, which is why every green test passed over it. solve() now factorizes the matrix it is given. - AdaptiveQuadrature's refinement re-integrated the WHOLE domain once per subdomain, so each level multiplied the estimate by the subdomain count: integrating e^x over [-1,1] at tolerance 1e-10 returned ~75 instead of 2.35. The recursion now descends into each sub-box with its share of the error budget. - compute_skewness read Jacobian columns as coordinate-line tangents, but the trait's jacobian() stores tangents in ROWS: on a sheared parallelogram whose tangents meet at 14 degrees it reported skewness 0.43 instead of 0.84 — measuring per-component gradients, not mesh skew. Fixtures corrected rather than the code where the fixture was wrong: sigma_yy ~ 0 asserted uniaxial-stress physics on a uniaxial-strain state (exact Lame values now asserted); an "unstable" orthotropic parameter set that satisfies the determinant stability condition (delta = 0.187 > 0); a unit-cube hex Jacobian of 1.0 that assumed a unit reference element (it is 0.125 from [-1,1]^3); a "distorted" quad whose centre Jacobian is exactly orthogonal, asserted as skewed (flattening and shearing now tested separately); a quality score below the implementation's own calibration; Rayleigh damping fed the scalar-field mass (now expanded via the Kronecker identity, with C = alpha*M + beta*K asserted entry-wise); an element factory required to construct Point/Line types that have no implementation; and DOF counts that encoded the repaired 3-DOFs-per-node-on-2-D defect. MaterialDatabase::add_material call sites updated to the (id, material, name) signature; ConnectivityInfo::build takes elements only; TopologyRepair::triangle_quality (normalized 4*sqrt(3)*A/sum(a^2)) added for the repair tests; create_subdomain_rule_* widened to pub(super) for the quadrature tests. Co-Authored-By: Claude Fable 5 <[email protected]> |
||
|
|
698c844926 |
solvers: near-wall momentum, Newmark dynamics, QM6, and MMS across elements
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (push) Canceled after 0s
CI / Python Bindings (maturin) (macos-latest) (push) Canceled after 0s
CI / Python Bindings (maturin) (ubuntu-latest) (push) Canceled after 0s
CI / WASM Build + Size Check (push) Canceled after 0s
CI / Distributed Training Tests (push) Canceled after 0s
CI / CI Success (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Format Check (push) Canceled after 0s
CI / Clippy Check (push) Canceled after 0s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Build (ubuntu-latest) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
Four parallel work items plus two defects found while integrating them.
561 -> 592 tests, 0 failing, verified stable over repeated runs.
## rtx-cfd: solve the near-wall velocity lines
Every u row sits at y = (j+0.5) dy and every v column at x = (i+0.5) dx --
strictly interior. The sweeps froze rows 0 and ny-1 and columns 0 and
nx-1 and treated whatever was stored there as a boundary condition, which
imposed wall values half a cell inside the domain. They are now unknowns,
with the wall entering through the control volume's half-cell conductance
(mu dx / (dy/2)), zero convective flux through the wall, and the wall's
tangential velocity in the source.
That in turn makes continuity enforceable on every cell, with a neighbour
coefficient zero only for a genuine boundary face. Extending continuity
had been tried before and broke convergence; it works now because the
near-wall lines are no longer frozen. Order matters here.
Manufactured solutions, which is how any of this is known:
n L2 velocity order max |p - p_exact|
16 3.516212e-2 - 9.245576e-2
32 1.953751e-2 0.85 5.225739e-2
64 1.037523e-2 0.91 2.796415e-2
Velocity error is 7.4x smaller at n=16, and the observed order rises from
0.48 toward 1. The pressure error was 0.408 -> 0.624 -> 0.756, *growing*
with refinement; it now falls. Divergence on the outer ring of cells goes
from 1.0e1 to 2.5e-10.
A separate defect found on the way: u_source_term was computed and never
called, so the x-momentum equation carried no body force at all while the
y-momentum one did. That is exactly the u-versus-v asymmetry the earlier
diagnosis had flagged as an unexplained clue.
Cavity at 65^2, against Ghia's u_min = -0.2109 at y = 0.4531:
-0.1792 at 0.3906 before, -0.1932 at 0.5000 after, in 733 iterations
rather than 971.
The cavity test now sets FreeSlipWall on all four sides plus the lid
through the new set_wall_velocity hook. That is not a weakened benchmark:
on a staggered grid the only velocity component living *on* a boundary is
the normal one, which is what FreeSlipWall prescribes, and the tangential
no-slip arrives through the half-cell wall term with wall velocity zero on
the three stationary walls. Prescribing whole u rows and v columns, as
before, pins lines half a cell inside the domain and over-determines the
cells beside them once every cell has a continuity equation.
## rtx-fea: DynamicAnalysis, previously a stub returning zeros
Newmark-beta in acceleration form -- the displacement form divides by
beta dt^2, singular at beta = 0 -- with Rayleigh damping, the effective
matrix Cholesky-factorised once and reused. Initial acceleration is solved
from M a0 = F0 - C v0 - K u0 rather than assumed zero, which would destroy
the second-order rate.
Verified two ways that cannot both be faked: against the closed-form
single-degree-of-freedom response, undamped and damped, with the measured
order of accuracy; and against the free-vibration period of the same bar
whose modal frequencies are already validated. Time domain and frequency
domain come from different code paths.
## rtx-fea: QM6 incompatible modes
Wilson's Q6 with Taylor's correction, added alongside compute_stiffness_
matrix rather than replacing it -- the existing method is byte-identical,
which matters because the manufactured-solution verification depends on
it. Internal modes statically condensed; the incompatible strain block
evaluated at the element centre, which is what makes the patch test pass
on distorted elements.
## rtx-fea: manufactured solutions across the element library
Quad4 order 2.00 Tri3 order 1.98
Quad8 order 3.00 Hex8 order 1.96 (new 3-D solution)
Each element asserts its own theoretical rate.
## Two defects found while integrating
Reverse Cuthill-McKee node ordering was nondeterministic. All three of its
orderings -- seed selection, neighbour ordering, and the trailing sweep --
were decided by HashMap/HashSet iteration order, which std randomises per
process. On a rectangular mesh every corner ties at minimum degree, so two
calls to displacement_only on the same mesh in the same process returned
different DOF indices for the same node, agreeing in only 5 of 20 measured
runs. Ties now break by node id. This surfaced as a coin-flip test failure
-- 12 in 25 runs -- and would have been dismissed as flaky rather than
diagnosed had the integration pass not re-run it.
Quadrature: triangle(3) weights summed to 0.25 against a reference area of
0.5, and tetrahedron(3) to 1/36 against a volume of 1/6. Both divided
weights that were already tabulated for the reference measure by that
measure again, so both rules integrated everything to a fraction of its
value -- invisibly, since a scaled quadrature leaves the stiffness matrix
symmetric, the mass matrix positive definite and the rigid-body modes
exact. New test asserts every rule integrates 1 to its reference measure,
across every family and order, plus Gauss-Legendre exactness to degree
2n-1.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
||
|
|
8615fc5783 |
rtx-fea: verify elastostatics by manufactured solution — second order confirmed
Performance Benchmarks / Run Benchmarks (push) Canceled after 0s
CI / Format Check (push) Canceled after 0s
CI / Clippy Check (push) Canceled after 0s
CI / Build (macos-latest) (push) Canceled after 0s
CI / Build (ubuntu-latest) (push) Canceled after 0s
CI / Test (macos-latest) (push) Canceled after 0s
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Build CPU-Only (Explicit) (push) Canceled after 0s
CI / Python Bindings (maturin) (macos-latest) (push) Canceled after 0s
CI / Python Bindings (maturin) (ubuntu-latest) (push) Canceled after 0s
CI / WASM Build + Size Check (push) Canceled after 0s
CI / Distributed Training Tests (push) Canceled after 0s
CI / CI Success (push) Canceled after 0s
Documentation / Build API Documentation (push) Canceled after 0s
Documentation / Build User Guide (push) Canceled after 0s
Adds the Method of Manufactured Solutions to this crate, and it immediately
paid for itself by finding a bug that every existing test missed.
MMS asserts something stronger than "close enough to a value someone
believed": that the discretisation converges to the exact solution at the
rate the theory predicts. Choose a smooth field, substitute it into the
governing equations, and whatever they fail to balance is the body force
that makes it exact. Solve, refine, read off log2(e_h / e_h/2).
Observed order for Quad4 displacement in L2:
n = 8 L2 error = 7.816681e-3 order -
n = 16 L2 error = 1.962845e-3 order 1.99
n = 32 L2 error = 4.912786e-4 order 2.00
(n = 64 reads 2.00 as well, at ten times the cost)
That verifies the whole chain at once -- element matrices, quadrature,
Jacobian, assembly, DOF numbering, constraints and the linear solver --
against a solution none of them can represent exactly. It is the check
that none of the sixteen defects fixed in this crate would have survived.
The manufactured field is u = sin(pi x) sin(pi y), v = x^2(1-x) y(1-y):
smooth, not in the bilinear element space, with the two components
different in form and a non-zero shear strain, so the shear block of the
constitutive matrix is exercised rather than skipped.
Found by it: `compute_shape_functions` inferred how many parametric
coordinates to pass from the coordinate *values* --
match coords.eta() {
0.0 if coords.zeta() == 0.0 => vec![coords.xi()], // 1 component
...
-- so any evaluation on an axis was handed a one-component slice, which
every 2-D and 3-D element rejects. That includes the element centre and
the middle point of every odd-order Gauss rule. It survived only because
the default 2-point rule never samples zero; asking for a 3-point rule to
integrate the error was enough to trip it. Dimensionality now comes from
the element, which is where it belongs.
Two prerequisites, both real functional gaps rather than test scaffolding:
- Consistent body-force integration. `BodyForceBC` distributed load as
force * volume / num_nodes, which is exact only for a constant force
on a symmetric element and otherwise first-order -- enough to cap the
measured order of the whole solver at 1 regardless of the element.
`ElementMatrixComputer::compute_body_force_vector` now integrates
the consistent form, taking the force as a closure so a spatially
varying load can be expressed at all.
- Non-homogeneous Dirichlet conditions did not exist.
`StaticLinearAnalysis` read the prescribed value out of the boundary
condition and discarded it, and `extract_free_system` built the
reduced right-hand side without the K_fc u_c coupling, so every
Dirichlet condition behaved as zero whatever the caller asked for.
`GlobalSystem::set_prescribed_value` and the coupling term close
that, reusing `extract_submatrix` and `multiply_vector` rather than
a dof-by-dof loop.
560 tests across the three crates, 0 failing.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
||
|
|
4c2cea36aa |
rtx-fea: make the analysis stack produce physics, validated against closed form
The census found rtx-fea could not produce a non-zero answer for any
analysis type. Six defects sat between a correctly specified mesh and a
natural frequency, each of which alone was fatal. Every one was found by
writing the closed-form test first and confirming red.
1. Element matrices were a stub. StandardFiniteElement::
compute_element_matrices returned DMatrix::zeros for stiffness, force
and mass -- and it is what GlobalAssembler calls for every element, so
every global matrix in the crate was zero. Real quadrature-based
stiffness and mass already existed in ElementMatrixComputer; nothing
called them. Now wired, with the scalar mass matrix expanded by a
Kronecker product with the spatial identity to match the interleaved
per-node DOF layout its stiffness uses.
2. Quadrature returned no points. quadrature_rule built
QuadratureRule::new(vec![], ..). Every integration loop iterates over
rule.points, so an empty rule does not fail -- it skips the loop and
yields a zero matrix. Real Gauss rules for line, triangle, quad, tet
and hex existed unused; now dispatched by element type, with wedges as
the triangle-line tensor product and pyramids an explicit error rather
than an empty rule.
3. transform_derivatives computed J^-T * dN where dN is
(num_nodes x param_dim). By the chain rule it is dN * J^-1. The two
agree only when both are square and symmetric; for any element with
more nodes than parametric directions -- every element -- the old form
was a dimension mismatch that panicked inside BLAS.
4. MaterialDatabase::clone silently dropped every material, cloning
names only, because Box<dyn Material> is not Clone. GlobalAssembler is
constructed with materials.clone(), so every assembler ever built got
an empty database and every analysis failed MaterialNotFound on a
correctly specified mesh. Materials are immutable once registered, so
the map now holds Arc and cloning shares them.
5. displacement_only numbered three displacement components on a 2-D
mesh. Elements supply two, so assembly rejected every contribution.
6. to_dof_numbering pushed each node's DOFs in HashMap iteration order.
When that came out [v, u] the assembler wrote the element's u row into
the global v row. The result was still symmetric, still had the right
rigid-body null space and still summed to the right total mass -- it
simply described a structure with its axes transposed per node, and
get_dof(node, DisplacementX) then pointed at the wrong row so
constraints were applied to the wrong direction too. DofComponent now
carries a canonical_index and the DOFs are sorted by it.
ModalAnalysis is wired to real assembly and the repaired eigensolver, and
takes boundary conditions, which it previously had no way to accept. The
eigensolver now rejects a singular stiffness explicitly: try_inverse does
not fail on a matrix singular only to working precision, so an
unconstrained structure used to return rigid-body noise dressed up as
low-frequency modes.
Validation, 18 tests:
- Element matrices: rigid translation stores no energy, exactly 3
rigid-body modes in 2-D and 6 in 3-D, consistent mass integrates to
rho*V, mass positive definite, and K and M each scale only with the
property they depend on. A zero matrix passes symmetry and
does-not-crash checks, so these are chosen to be ones it fails.
- Modal, end to end: longitudinal modes of a fixed-free bar against
f_n = (2n-1)/(4L) sqrt(E/rho), within 1% on the first three, and
second-order convergence under refinement. Axial rather than
cantilever bending on purpose: Quad4 shear-locks, so a bending
tolerance would fail for a reason unrelated to correctness. Bending
is asserted as convergence from above instead, which is the honest
claim for a locking element.
Two fixtures corrected rather than tolerances loosened: integration_tests
expected 27 DOFs for a 9-node planar mesh (3 components per node), which
encoded defect 5 and contradicted comprehensive_tdd_tests asserting
num_nodes * 2 for the same situation.
rtx-fsi stays 26/26. No new failures; the rtx-cfd quarantine is
unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
|
||
|
|
02d382d5f6 |
style: apply rustfmt across all crates and demos
Consistent formatting pass: line wrapping, import sorting, trailing whitespace removal, let-chain indentation, merged derive attributes, and unsafe block reformatting. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> |
||
|
|
4d88dc0584 | Initial commit |