Repository navigation
ymf mesh tags and provenance, coefficients that name unknowns, domains beyond a box (cekees/ymf#6-#8) - #25
Merged
Merged
Conversation
… cell type
Step A of proteus's ymf mesh campaign (C7.1; decisions 2 and 6), so that
converters from other codes' mesh inputs can write ymf and proteus can
read boundary conditions from it.
- facets: a grid (uniform space or a grid of a spatial collection) may
carry {topology, tags, view}: node tuples of its boundary faces (edges
in 2D), one integer tag each: 0 untagged, >0 a condition. -1 (a rank
interface) is refused in view "global" (the default) and allowed only
in "subdomain"; a tag below -1 is refused. The facet type follows the
cell type (Polyline for Triangle, Tri_6 for Tet_10, ...). Inline tags
are checked by validate_approximation; ymf.mesh.check_facets reads HDF5
tags and checks node range and that each facet is a face of a cell.
- provenance: {tool, version, files: [{name, sha256}], extra}; no list of
tools anywhere. ymf.mesh.file_provenance computes the digests.
- order from the cell type: geometry_order(type), nodes_per_cell(type),
and QUADRATIC_EDGES, the node order of every quadratic type (VTK's,
which is what ParaView draws from an .xmf). The Tet_10 mismatch: one
common generator puts edge (2,3) before (1,3), and proteus's own P2
tetrahedron numbers its edge nodes (0,1)(1,2)(2,3)(0,2)(1,3)(0,3).
ymf.mesh.edge_node_permutation maps any stated convention onto ymf's,
and edge_midpoint_offsets is the check.
Measured: on a straight-sided Tet_10 in either source order, the largest
edge-node offset from its edge's midpoint (relative to the edge length)
is > 0.3 read as ymf's order and < 1e-15 after the permutation.
Tests: 552 passed before, 591 passed after (39 new in test_mesh.py),
5 skipped as before. Proteus's ymf-facing tests (test_ymf_mesh_2dm,
archive, read-path, pro CLI) pass on this branch: 232 passed, 1 skipped.
Documents without the new keys are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A state-dependent conductivity, density or friction law is now a
coefficient whose formula names the unknowns (k: {formula: "1 + u²"}),
resolved with the other coefficients and substituted into the equations,
so the ADR form carries the dependence and its derivatives exactly as if
the expression were written inline. Decided by cekees (2026-11-03) for the
model-family work in place of a separate relations block: two-phase
level-set flow writes χ = heaviside(φ), ρ(χ), μ(χ) this way, shallow water
its Manning friction and canopy drag.
Guards that come with it:
- boundary data, initial conditions, Robin parts and exact solutions that
depend on the unknowns, directly or through a coefficient, are refused
(they are evaluated at points in x, y, z, t); this caught the heat
example's exact solution, which names κ, once κ = κ(T);
- a coefficient may not take an unknown's name, nor (yet) a derivative of
an unknown;
- ymf.units warns when a characteristic scale or dimensionless number is
built on a state-dependent coefficient (it has no value);
coefficient_numbers leaves such coefficients out, as before.
ymf.symbolic.state_coefficients lists which coefficients follow the state.
Measured: examples/nonlinear_diffusion.ymf, -∇·((1 + u²)∇u) = f with a
manufactured u = sin(πx) sin(πy), run by Proteus's scripts/ymf_run
(cekees/main 9fca0b95, serial, superlu, Newton to 1e-10):
cells P1 L2(u) rate P2 L2(u) rate
8 1.828e-02 5.477e-04
16 4.644e-03 1.98 6.873e-05 2.99
32 1.166e-03 1.99 8.600e-06 3.00
and ymf_run --check reproduced all six outputs bitwise. ymf suite: 634
passed, 2 skipped (was 616 passed; 16 new tests in
tests/test_state_coefficients.py, the new example in two parametrized
tests), macOS arm64, petsc-dev env.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
strong_form.equations takes a mapping that names an equation and says which unknown it determines. Bare strings are still ordered like the unknowns; with `for` on every equation the order written stops mattering. Names merge across `extends` like every other named list (replacing momentum's text in a variant is recorded as an override), and they are what a discretization's sub-problems will cite (C42: two-phase flow's momentum / continuity / interface, solved by different sub-problems). Checked on load (references.resolve): a name used twice, `for` naming something that is not an unknown, two equations for one unknown, `for` on some but not all, and, outside an abstract file, an unknown that no equation is for. ymf.equations_in_order is the one reader, used by ymf.symbolic and ymf.units. Measured: plane Poiseuille with its equations named and written in the other order gives the same ADR form as the original, and the same check_units issues; ymf suite 648 passed, 2 skipped (was 634; 14 new in tests/test_named_equations.py), macOS arm64, petsc-dev env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The functions the model families need. Two-phase level-set flow writes χ = heaviside(φ), its initial signed distance with min, max and sqrt, and its viscous stress as ∇·(2μ sym(∇v)); shallow-water friction and drag use norm(q). heaviside, sign, min and max parse, print back through to_notation, and become numpy code; min and max print as nested numpy.minimum / numpy.maximum rather than sympy's functools.reduce, since the code is evaluated with numpy alone. norm(v) = sqrt(v·v) and sym(A) = (A + Aᵀ)/2 expand as they are read. The derivative of heaviside or sign is a Dirac delta, which has no value at a point. to_code now refuses one with a message saying a sharp function of an unknown needs a discretization that regularizes it (it raised a sympy PrintMethodNotImplementedError before); a sharp function of the coordinates (a zone indicator) is ordinary data. Measured: the dam-break column's signed distance from the C42 note, sqrt(max(x − 1.2, 0)² + max(y − 0.6, 0)²) + min(max(x − 1.2, y − 0.6), 0), evaluated from its numpy code, equals damBreakLevelSet.py's phi_init to 1e-15 at six points covering its three branches; ∇·(2μ sym(∇v)) classifies as a diffusion of v_0 in both v_0 and v_1. ymf suite 660 passed, 2 skipped (was 648; 12 new in tests/test_notation_functions.py), macOS arm64, petsc-dev env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A discretization entry may now give `subproblems:`, solved in order each step. Each one solves model equations by name, enforces a constraint the others lose, introduces an unknown of its own (an auxiliary carrier that approximates a model quantity, or a correction of a model unknown), or spins the run up; it says what it lags and when (previous_step, or current_step via an earlier sub-problem), what it writes back, and its own finite element, time integration, stabilization and a solver that refines the entry's (the entry's solver stays required, as the default). Beside them: `binds:` (symbols the discretization supplies and the model may not use: h, ε = 1.5 h), `regularize:` (heaviside or sign made smooth) and `step_control:`. Splitting is not a new `method`: a split entry is still a mesh one, so its studies and output keys work as before, and the split is part of what an output key covers. This is C42's framing of two-phase level-set flow: one model (momentum, continuity, interface transport; ρ and μ coefficients of χ = heaviside(φ)) and proteus's five solves (RANS2P, VOF, NCLS, RDLS, MCorr) as one discretization of it. tests/fixtures/two_phase holds the model and the dam break of test/TwoPhaseFlow/damBreakLevelSet.py split that way. Checked on load (ymf.references): unique sub-problem names; every model equation solved by exactly one sub-problem; every name a sub-problem uses (equation, lagged field, via, introduced, approximated, corrected, written back) exists, and nothing introduced or bound shadows a model name. ymf.discretizations.subproblem_solver merges a sub-problem's solver onto the entry's (petsc_options key by key). Not done: the sub-problems' own equations are carried, not parsed, and adr_problem still classifies the whole model as one system, which refuses this model (v·∇φ). Measured: the dam-break fixture loads and reaches ymf_state discretized with one planned output; changing one sub-problem's solver changes that output's key. ymf suite 680 passed, 2 skipped (was 660; 20 new in tests/test_subproblems.py), macOS arm64, petsc-dev env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The ADR form substitutes a coefficient whose formula names the unknowns, which is all a solver of one system needs. A code generator (C19) or a dispatcher wants the name as well, to emit ρ = ρw (1 − χ) + ρa χ once, as an inline function of the state, rather than its expansion in every slot. So adr_problem now lists each such coefficient under `coefficients`: its numpy code (a list for a vector), `depends_on` per component (linear or nonlinear), and `d`, its derivative in each. heaviside or sign of an unknown has no derivative with a value at a point: it is marked `sharp: true`, without `d`, for the discretization to regularize. A problem with no such coefficient has no `coefficients` key, so its output, and an archive's record of it, are unchanged. Measured, with Proteus's scripts/ymf_run (cekees/main 9fca0b95, serial): plane Poiseuille (8, 16 cells) runs as before; examples/nonlinear_diffusion gives the same output keys and errors as under the first commit of this PR (P1 1.828e-02, 4.644e-03, 1.166e-03; P2 5.477e-04, 6.873e-05, 8.600e-06), every numeric dataset of its six .h5 files is bitwise equal to that run's, and --check reproduces it bitwise. An archive written by the first commit reports "the ADR form differs" under --check, because ymf_run records the whole adr_problem output; only specs with state-dependent coefficients are affected, and those exist only on this unmerged branch. ymf suite 685 passed, 2 skipped (was 680; 5 new), macOS arm64, petsc-dev env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two tests in test_notation_functions.py evaluate the numpy code the new functions print to. The CI leg that installs ymf[symbolic] alone has no numpy, so they failed there (ModuleNotFoundError, run 41714884785); they now skip without it, as other tests do for a missing extra. Measured: a venv with only ymf[symbolic] (Python 3.13, no numpy): 605 passed, 20 skipped; the full env still 685 passed, 2 skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Three keys the classification of each sub-problem needs (next commit): - a sub-problem's `for`: the unknown its own `equation` determines, by default the one it introduces (redistancing introduces nothing and solves for φ); - a sub-problem's `regularize`, which replaces the entry's for it (redistancing smooths its sign over 0.75 h, the rest their Heaviside over 1.5 h, as Proteus's RDLS and the other models do); - a lag's `of`: a copy of a field read under a name of its own (redistancing reads φs, the level set as the level-set solve left it, while it solves for φ). Checked on load: `for` names a model or introduced unknown, a copy's name is new and its `of` exists, a sub-problem regularizes only heaviside or sign. The dam-break fixture now declares every field each sub-problem reads (mass correction reads φ via rdls and χh via vof), and its redistancing gives its equation, sign(φs) (norm(∇φ) − 1) = 0. Measured: ymf suite 685 passed, 2 skipped (unchanged count; one test adjusted, since redistancing now reads φs via ncls), macOS arm64, petsc-dev env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adr_problem reads a model as one ADR system, which two-phase level-set flow is not: v·∇φ is a Hamiltonian in another unknown. Each of its sub-problems is one, once what it reads from the others is data. ymf.symbolic.split. split_problem(doc, discretization) gives that plain data, one ADR form per sub-problem, for a dispatcher to map onto a solver's modules and for C19's kernel generator to read. - A sub-problem's unknowns are those of the model equations it solves (their `for`) and of its own equation. Every other field it reads must be declared in `lags`; it is a known field, a function of the coordinates and time to sympy (Space(..., fields=...)), so it is differentiated as a field; the code printer writes it by name and its gradient grad_<field>_<axis>, and refuses its time derivative. A read that is not declared is an error that says to declare it. - A coefficient lagged via the sub-problem that carries it (χ via vof, which introduces χh approximating χ) is that carrier (the resolver's new `preset`); coefficients may name known fields, and the named-coefficient listing records which they read. Only the coefficients a sub-problem's equations use are listed. - `binds` are substituted; a symbol with only a meaning (h) stays a parameter the solver supplies (Space(..., parameters=...), which classify accepts as a free name). - `regularize` replaces heaviside by Proteus's smoothed Heaviside, gf.H of equivalent_polynomials.h, whose exact derivative is its gf.D, and sign by 2 H_ε − 1, as RDLS does. A sharp function of an unknown left unregularized is still refused. - An introduced unknown's initial condition becomes code in the model unknowns' initial fields; the model's own initial conditions, the domain, the time interval and the bound symbols come with the result. problem._frame is factored out of _posed for the split reader to share. Measured on the two-phase dam break (tests/fixtures/two_phase, split as damBreakLevelSet.py runs it): all five sub-problems classify. flow is variable-density Navier-Stokes, m = v_0 (998.2 − 996.995 χh), diffusion of v_0 in v_0 and v_1 (2μ sym(∇v)); vof is f = χh v; ncls is the Hamiltonian H = grad_φ_0 v_0 + grad_φ_1 v_1; rdls has r = 1 − 2 H_ε(φs) with width 0.75 h and a nonlinear |∇φ| Hamiltonian; mcorr has r = H_ε(φ + φc) − χh, dr = 1/ε at 0, diffusion 10 h. The emitted H_ε and its derivative equal Proteus's compiled ctransportCoefficients .smoothedHeaviside and .smoothedDirac to 1.1e-16 and 1.4e-14 (1203 points, ε = 0.01, 0.0483, 0.3; proteus cekees/main 9fca0b95, env proteus-bc-261025). ymf suite 702 passed, 2 skipped (was 685; 17 new in tests/test_split.py); ymf[symbolic] alone 618 passed, 24 skipped; macOS arm64. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…efficient
The kinds a split discretization's solver needs (C42):
- slip: the normal component of a vector is the value (0 if none is
given) and its tangential traction zero: proteus's free-slip walls, the
SWE integrated-by-parts walls;
- open: a declared open boundary, the natural flux applies (C12's
decision: a boundary term only where a face carries an explicit
condition, and an open boundary is one);
- inflow: a value imposed only where the flow enters (VOF = 1, air, on an
atmosphere boundary);
- characteristic: a state imposed through the incoming characteristics
only (the SWE tailwater stage, which a supercritical outflow ignores).
A condition may name a coefficient of the state as well as an unknown
({variable: χ, type: inflow, value: 1}: what enters is air, whichever
field carries χ).
Conditions are now checked on load (references): the region exists
(outside an abstract file), the variable is an unknown or a coefficient,
slip is on a vector, and a value is given exactly when the kind takes one
(periodic and open take none). ymf.units checks slip, inflow and
characteristic like dirichlet ([g] = [u]). adr_problem still runs only
dirichlet, neumann, robin and periodic on unknowns, and its refusal now
names the solver the others are for.
Measured: ymf suite 713 passed, 2 skipped (was 702; 11 new in
tests/test_boundary_kinds.py), macOS arm64, petsc-dev env.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 25 air-water-vv two-phase cases are written in proteus's boundary-
condition setters (BC_RANS.setFreeSlip, setAtmosphere, ...), each a bundle
of conditions on every model's variables at once. In ymf the bundle is part
of the model, written once over its own unknowns and coefficients
(strong_form.condition_sets: name -> conditions without a region), and a
problem says where each holds (strong_form.boundary_sets: [{region, set}]).
ymf.boundary_conditions(strong_form) is every condition with the sets
expanded, each member carrying its region and `set`. A condition written
out for the same region and variable replaces the set's member (apply
atmosphere, give the pressure its own value); two sets deciding one
region's variable, and a set nobody defines, are errors. adr_problem,
ymf.units (whose locations now name the set) and the load-time checks all
read through it; a set's members are checked where they are defined. The
two-phase fixture now defines free_slip (v slip) and atmosphere (p = 0, v
open, χ inflow 1) and the dam break applies them to its walls and top.
Sets are a key of their own rather than a `set:` entry among
boundary_conditions: strictyaml cannot accept either of two mapping shapes
in one list, and making a condition's keys optional would lose the schema
error that names the file holding an incomplete one.
Measured: the heat example's wall condition written as a set gives the
same Dirichlet data and ADR form as the original. ymf suite 720 passed,
2 skipped (was 713; 7 new in tests/test_condition_sets.py), macOS arm64,
petsc-dev env.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What a run measures as it goes, as part of the problem (model.gauges), in the shape FORCE_HISTORY_GAUGES_PLAN_2026-11-03 step 4 set out for C22: - point, line, line_integral: `fields` (unknowns or coefficients, so a state coefficient such as ρ can be gauged too) sampled at `points` or along `lines`, each line its two ends; - boundary_integral: `quantities` (wetted_area, force_pressure, force_viscous, force, moment about `moment_point`) integrated over each boundary region in `tags`, the gauge that is to replace RANS2P's and RANS3PF's force-history files; - `sample_rate`, `file`, and an optional `name` (the identity extending files merge on). Checked on load: what each kind needs and may not have, that fields and tags exist (outside an abstract file), that a line is two points and a gauge's points share one dimension. The two-phase dam-break fixture now carries damBreakLevelSet.py's three gauge files (four vertical and one horizontal line of φ, three points of p on the right wall). This is the ymf half of step 4; proteus's reader stays with C22. Also: _check's message chooses its article, so a reference error says "an analytical solution" (test_references.py expected the old "a analytical"). Measured: ymf suite 731 passed, 2 skipped (was 720; 11 new in tests/test_gauges.py), macOS arm64, petsc-dev env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each sub-problem now carries the conditions it imposes, in adr_problem's plain-data shape (`where` as boolean code using tol, values as code, per component for a vector except slip's normal component, series, robin's alpha/beta/g, periodic axes), for every kind including slip, open, inflow and characteristic, with condition sets expanded and each entry naming its set. A sub-problem imposes the conditions on the unknowns it solves for, and those on a coefficient its introduced unknown carries (`carried_by`); a condition on a field it only reads is another sub-problem's, so a condition on φ reaches ncls and rdls, which solve for it, not mcorr, which reads it. Measured on the two-phase dam break (free_slip on the walls, atmosphere on the top): flow gets v slip on the walls, p = 0 and an open v on the top; vof gets the inflow of air, χ = 1, through χh; ncls, rdls and mcorr get none, which is what Proteus's BC_RANS.setAtmosphere and setFreeSlip give those models (no level-set condition at an atmosphere boundary). ymf suite 734 passed, 2 skipped (was 731; 3 new); ymf[symbolic] alone passes as well; macOS arm64, petsc-dev env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
a837e69 smoothed every Heaviside in a sub-problem's residuals after they were parsed, so it also smoothed the ones differentiation produces: the derivative of a bed max(0, 0.2 − 0.05 (x − 10)²) is a bounded step in ∇Z, which nobody asked to regularize, and it came out smoothed over one element. The regularization now replaces the functions by name as the expressions are read (Space(..., functions=...), which the notation parser consults): heaviside and sign written in an equation, a coefficient's formula or an initial condition become smooth; what differentiation produces is left as it is. The coefficient resolver takes the same functions, and the bound parameters (he) a smoothed formula keeps free. The `regularized` record now lists what a sub-problem's own texts use, not following a coefficient it reads through a carrier: the dam break's flow reads χ through VOF's χh and reports none (it reported heaviside); vof, rdls and mcorr report as before. Their ADR forms are unchanged. Measured: ymf suite 734 passed, 2 skipped (unchanged count; one assertion added to tests/test_split.py); ymf[symbolic] alone 648 passed, 26 skipped; macOS arm64, petsc-dev env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C42's second family, written as the first was: one model file, one numerics file, one file per case (tests/fixtures/shallow_water). - shallow_water.ymf (abstract): depth h and discharge q; mass and momentum in conservative form with porosity θ, the pressure as a flux ∇(θ g h²/2) − (g h²/2)∇θ and the bed θ g h ∇Z (SW2DCV's well-balanced θ g h ∇(h + Z) is the same strong form); Manning friction τb = g n² |q| q / h^(7/3) and Darcy-Forchheimer drag Fd = (α + β|q|/h) q as coefficients of the state; condition sets `wall` (q slip, SW2DCV's integrated-by-parts walls) and `outflow` (q open). - proteus_numerics.ymf (abstract): SW2DCV as one sub-problem solving both equations, its algebraic scheme (graph viscosity, convex limiting, SSP RK3, cE) in the free-form `discretization` block, the element size bound as `he` (h is the depth), a canopy's edge regularized over one element. - lake_at_rest.ymf: still water over a parabolic bump, exact. - torino.ymf: torino_flume/dswe/torino.py's dam break (H = 0.15 m at the gate, S = 0.03) into the rod canopy (194 rods/m² of 6 mm: porosity and β = C_D min(1, 0.9 Fr^-0.8) m D / 2), free-slip walls, the far end open, cE = 5. Not carried, and said so in the file: the 'age' drag law (it reads each node's wetting history, which no coefficient holds), the gate snapped to a node with half depth (a mesh-dependent initial condition), the 161 × 8 structured mesh (a mesh gives one size, not one per axis), the load-cell forces (a derived-quantity gauge, C34). The family needed no schema beyond rounds 1-3. Measured: both cases load and reach ymf_state discretized; split_problem classifies each as one ADR system (momentum flux 4.905 h² + q_0²/h, Manning friction in the reaction; Torino's mass θ h with θ = 1 − 194π(0.006)²/4 inside the canopy and 1 outside, to 1e-12); the drag coefficient's code equals the deck's Froude law, (α + β|q|/h) q, to 1e-12 at h = 0.1, q = (0.05, 0) in the canopy and 0 outside; the lake at rest substituted into the model's residuals gives exactly 0 in all three (well-balancing of the strong form, the bed's kink included); both pass check_units. ymf suite 741 passed, 2 skipped (was 734; 7 new in tests/test_shallow_water.py); ymf[symbolic] alone 652 passed, 29 skipped; macOS arm64, petsc-dev env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The linux-64 / py3.10 CI leg failed on 7c64ac5 (test_shallow_water's unit check): pint before 0.25 parses "1", the usual way to write "no units", to a plain int, which has no .units, so to_pint_unit raised "could not parse unit string '1'" for the shallow-water porosity θ. A bare number now maps to dimensionless, as None, "" and "dimensionless" already did; only dimensions are compared, so a number's scale is not kept. Measured: with pint 0.24.4 (the version range that leg installs) the ymf suite gives 718 passed, 25 skipped (that venv has no h5py) where the leg failed; with pint 0.25.3, 741 passed, 2 skipped (one assertion added to test_to_pint_unit_none_and_dimensionless); macOS arm64. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Mesh: tagged facets, provenance, and the node order of each quadratic cell type (C7.1 step A)
C42: model families in ymf, rounds 1-3 + SWE family (state coefficients, sub-problems, per-sub-problem classification, boundary kinds and sets, gauges)
C27 Phase 5, decision D1 (cekees, 2026-11-04): a boundary region is named by `where`, a predicate on the point, or by `tags`, the mesh's boundary tags, given as ints or as names the domain's `boundary_tags` resolve; exactly one of the two. For a CAD or Gmsh domain the physical groups are the natural names, and coordinates the wrong tool. adr_problem and split_problem give a tag region's conditions as `flags: [ints]`, for the solver to test against the boundary flag it already passes to getDBC/Neumann/Robin (flag in flags), and with no `where` key: a solver that does not read `flags` then fails on it (KeyError) rather than taking a missing predicate for the whole boundary, which is what `where: None` means. Checked on load (references): where or tags, exactly one; every tag name resolves; a box carries no tags; a periodic region is found by position, so it needs `where`. The schema makes `where` optional to allow this. Measured: on an L-shaped piecewise_linear region with segment flags 1-5 named bottom/right/notch/top/left, regions [bottom, left] and [2, notch, top] give flags [1, 5] and [2, 3, 4] in both readers. ymf suite 789 passed, 2 skipped (was 780 on cekees/main a006429; 9 new in tests/test_tag_regions.py), macOS arm64, petsc-dev env. Proteus's reader (flag in flags) is the companion proteus PR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C27 Phase 5, decision D3 (cekees, 2026-11-04): periodic on a region first,
then file domains, in one round. Proteus's periodic mechanism is a key
function that pairs a node on one face with the node a translation along
the axis puts on the other, so it is geometric and indifferent to whether
the domain is a box; what it needs is a mesh in which every node on one
face has that partner. adr_problem refused a periodic condition on any
region ("periodic faces are a box's"). It now poses one when, on each
periodic axis, the region's boundary segments (2-D) or facets (3-D) lying
in the lower face of its bounding box, moved by the box's length along
the axis, are exactly those lying in the upper face, compared as point
sets (vertex order does not matter) to 1e-8 of the box; otherwise it
refuses, with the count of pieces without a partner and which axis.
periodic_faces_match is shared by adr_problem and split_problem.
The mesher must then add no points to those faces (triangle's -Y,
tetgen's equivalent), and a partner check after meshing catches it if it
does: both are Proteus's, in the companion proteus PR.
test_symbolic's test_periodic_faces_need_a_box asserted the refusal this
lifts; it now asserts that the L-shape's x faces (lengths 2 and 1) are
refused as not matching.
Measured: a notched channel (notch in its top, x faces of one segment
each) poses periodic on axis x; the same with the x = 4 face cut in two is
refused, "3 of 3" pieces without a partner; the unit cube as a PLC poses
periodic on x and y, also with one facet's vertices rotated. ymf suite
794 passed, 2 skipped (was 789; 5 new in tests/test_periodic_regions.py,
one rewritten), macOS arm64, petsc-dev env.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…h (D2)
C27 Phase 5, decisions D2 and D3 (cekees, 2026-11-04). adr_problem refused
a `file` domain; it now carries it whole as the problem's region for the
solver to read (proteus's DomainYMF.from_ymf already builds the Domain).
- bounds: {lower, upper} is optional on a file domain. With it the box is
set as for any other kind. Without it, lower and upper are None and the
solver takes them from the mesh it reads or makes: for this kind alone,
posing follows meshing; everywhere else it stays pure. The dimension
must then be given. Bounds are checked (lengths, upper above lower,
against dimension).
- A mesh file (2dm, 3dm, hex, tetgen, ymf) fixes the mesh, so its
discretization gives mesh: {levels} only, the uniform refinements of the
file's mesh, and its output keys read levels=L; a mesh size there is
reported as what the branch lacks ("already a mesh; give mesh:
{levels: ...} only"). A geometry (gmsh_geo) is meshed by h, as Proteus's
gmsh route does with -clmax. ymf.discretizations (missing, Method.lacks),
ymf.closure.realizations and ymf.state take the domain for this, as an
optional argument: without it a mesh needs a size, as before, so
existing callers (proteus's ymf_run blockers) are unaffected.
- A file domain is not periodic (Gmsh's Periodic constraint is not
carried): refused, saying what can be.
Boundary regions on a file are its boundary tags (D1, dfa38a8).
test_domain's test_a_kind_nothing_solves_yet_says_so posed a file domain
to see it refused; it now checks the decided behaviour (refused without
bounds or dimension, posed with lower/upper None with a dimension, the
box from bounds).
Measured: a tetgen cube with tags bottom/top/front/back/left/right plans
P1 and P2 at levels 1 and 2 (four keys levels=1, levels=2), refuses h as
lacking, gives Dirichlet flags [1, 2] and Neumann [3, 4, 5, 6]; a gmsh_geo
file plans by h. ymf suite 804 passed, 2 skipped (was 794; 10 new in
tests/test_file_domains.py, one rewritten), macOS arm64, petsc-dev env.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…te lands C27 Phase 5, decision D4 (cekees, 2026-11-04), following C7.1's mesh decision 3 (a mesh is a type of domain): a `discrete` domain, which was validated, carried and refused, is posed whole like a file. It may carry the mesh keys #6 defined (tagged `facets`, `provenance`, `attributes`) and an optional `bounds`. Its bounds are its nodes' when the geometry holds them inline; when they are in an HDF5 file the solver takes them (or `bounds` gives them), and the dimension comes from the geometry's type. Its boundary regions select its facet tags (D1), a discretization gives `mesh: {levels}` only (2fae007), and a periodic condition on it is refused, as on a file. Proteus reads it through MeshYmfDomain (cekees/proteus#186, the C7.1 session's); turning an inline discrete block into one is #186's DomainYMF dispatch, so that half waits for it. The design note (branch domains-beyond-box, bed1608) lands with the code as docs/design/domains-beyond-box.md: its status, the four decisions, what each section became (§ 3 rewritten for D4; § 2's "a file's mesh is set by the file" corrected for gmsh_geo, which Proteus meshes with -clmax from h; § 4 with Proteus's Y, the cut faces and the partner check, and their numbers), and § 6's open questions answered where they were measured. docs/README.md indexes it. Measured: the square [0, 2] × [0, 1] as two inline triangles with its four edges tagged poses with bounds [0, 0]-[2, 1] from its nodes and flags [1, 4] / [2, 3]; with the nodes in an HDF5 reference it poses with bounds None and dimension 2, and with `bounds` given, from them; it plans levels=1, levels=2 and refuses cells as "already a mesh"; periodic is refused. ymf suite 809 passed, 2 skipped (was 804; 5 new in tests/test_discrete_domains.py), macOS arm64, petsc-dev env. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
C27 Phase 5 / C42: domains beyond a box (tag regions, periodic regions, file and discrete domains)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Three reviewed PRs from cekees/ymf, merged there in order.
cekees/ymfmain(a44cee1) is a fast-forward of this repository'smain(d466645).Tri_6,Tet_10), including theTet_10ordering fix.ρ = ρw (1 − χ) + ρa χand the drag closures name unknowns; such coefficients are left out of the unit and scale checks. Also: new boundary kinds (slip,open,inflow,characteristic), condition sets, gauges, and boundary conditions per sub-problem.boundary_regionsselect bytagsor bywhere;piecewise_linearregions, with a partner check;filedomains with optionalbounds;discretedomains solved as ymf meshes.proteus CI installs ymf from this repository's
main, so these must merge before the proteus PRs that use them: cekees/proteus#185 (in the next main train), and #186 and #188 (drafts).Merge with "Create a merge commit" so that the fork's
mainstays a fast-forward of this one.🤖 Generated with Claude Code