L3_Ferroelectric — context contract#
Public interface: mod_mupro_ferroelectric, exporting
type_mupro_ferroelectricContext (=> type_ferroelectricContext),
type_mupro_inhomoFerroelectricContext, and
mupro_ferroelectric_read_electrostrictive (interface.f90:1-6).
Audience: a developer writing a new application against this solver, like
apps/muFerro. Line references are against the current tree (2026-08-06).
1. What the solver computes#
Given a polarization field op, this module computes the material-dependent
pieces of a ferroelectric phase-field model: the Landau–Devonshire bulk
free-energy density and its driving force from a 2nd–8th order polynomial
(ferroelectric.f90:671-729); the Ginzburg gradient energy and gradient
driving force in the Hlinka–Marton form (ferroelectric.f90:751-752); the
electrostrictive eigenstrain ε⁰ = Q·PP (ferroelectric.f90:509-567); the
elastic driving force −∂f_elast/∂P through the coupling matrix A = C·Q
built at setup (ferroelectric.f90:336-343, :355-418); and the
polarization bound charge −∇·P, evaluated spectrally
(ferroelectric.f90:480-497). It does not step time and does not solve
mechanical or electrostatic equilibrium — those are L2_TDGL, L2_Elastic and
L2_Electric, which the application composes around this module
(apps/muFerro/src/solve.f90:17-64).
2. The context contract — type_mupro_ferroelectricContext#
The type extends the abstract type_ferroelectricBase
(ferroelectric.f90:78-100, extension at :133-163). Every component carries
a default written 2026-08-06 (PR-054): before that, none of the 106 components
across the four solver context types had an initializer, and anything the
application did not assign was read straight off the stack — hidden on the
platform of record by Intel’s -init=zero (TASK.md:1422). muFerro assigns
29 of the 30 components; the one it cannot assign is the private Amatrix
(ferroelectric.f90:73-77, :150).
Field shapes are not checked by the library (only association is,
ferroelectric.f90:936-958). The shapes below are what the real caller
allocates (apps/muFerro/src/setup.f90:37-62): Rn3, Rn2, Rn1 are the
local-slab extents of the FFT decomposition (L0_FFT/fft.f90:98-99).
“Required” means: for setup — refused by validate_setup; for solve —
refused by validate_solve; blank — never validated, read as-is.
Material scalars and tensors (ferroelectric.f90:79-80, :140-147)#
Name |
Kind / shape |
Required |
Default and meaning |
Read by |
|---|---|---|---|---|
|
|
setup |
|
|
|
|
setup |
|
|
|
|
setup |
|
|
|
|
no |
|
|
|
|
no |
|
|
|
|
no |
|
|
|
|
no |
|
|
|
|
no |
|
|
|
|
no |
|
|
|
|
no |
|
|
|
|
no |
|
|
|
|
no |
|
|
|
|
no |
|
|
|
|
no |
|
|
|
|
no |
|
|
|
|
no |
|
|
|
|
finite only ( |
|
|
|
|
finite only ( |
|
|
|
|
no |
|
|
Field bindings (ferroelectric.f90:81-90) — all pointers, all => null()#
null() is the sentinel in every row: validate_solve refuses each unbound
pointer by component name (:936-958, message shape at :963-972).
Name |
Kind / shape |
Required |
Default and meaning |
Read / written by |
|---|---|---|---|---|
|
|
solve |
|
read by all six compute routines ( |
|
|
solve |
|
written by |
|
|
solve |
|
read by |
|
|
solve |
|
written by |
|
|
solve ( |
|
not read by any built routine in this module; the application consumes it — muFerro binds |
|
|
solve |
|
written by |
|
|
solve |
|
written by |
|
|
solve |
|
written by |
|
|
solve |
|
written by |
|
|
solve |
|
written by |
Internal (not settable)#
Name |
Kind / shape |
Meaning |
|---|---|---|
|
|
Built by |
Global state the solve path reads (not on the context)#
FFT extents
Rn1..Cn3and wave vectorsmqk1_3/mqk2_3/mqk3_3(L0_FFT/fft.f90:98,:63), set bymupro_fft_setup; used by the bound-charge path (ferroelectric.f90:481-489). muFerro’s order:mupro_mpi_setup→mupro_size_setup→mupro_fft_setup(apps/muFerro/src/setup.f90:90-102).Slab loop bounds
k1, k2(L0_Base/size.f90:27), used by the elastic driving force loop (ferroelectric.f90:372-374).Optional rotation state
rotation_A/rotation_B(L1_Transform/rotate.f90:5,rotate_inhomo.f90:4):landau_routinerotates into and out of the local frame iff one of them is allocated (ferroelectric.f90:642-650,:704-712). Unallocated means identity.
The inhomogeneous variant#
type_mupro_inhomoFerroelectricContext (ferroelectric_inhomo.f90:18-26)
replaces the material scalars with per-cell pointer fields. Its pointer
components carry no => null() defaults and it has no validator pair — it
is exactly the PR-054 defect class (TASK.md:1422) not yet closed there. Do
not extrapolate this contract to it.
3. The validator contract#
Both validators are type-bound (ferroelectric.f90:161-162), report through
type_mupro_Status (L0_Base/error.f90:15-22) and never terminate.
setup itself does not call them — validation is the caller’s step. The only
way setup aborts is a failed license check in a non-MUPRODEV build
(ferroelectric.f90:269-276).
validate_setup checks the material and nothing else (:883-916):
a1,a11,a12not all zero (:889-894);a1,a11,a12finite (:897-903);electrostrictivefinite (:905-909);stiffnessfinite (:911-915).
validate_solve = validate_setup + grid + bindings (:923-959): it
re-runs the material checks (:927), then requires Cn1/Cn2/Cn3 > 0
(:930-934), then association of op, eigenstrain, strain, elast,
elec, lan, grad, landau_energy, grad_energy, boundCharge in that
order (:936-958). First failure wins; you fix one refusal at a time.
Why the split is where it is (:862-877, TASK.md:1503-1505):
ferroelectric_setup is the one solver setup in the library that reads no
global state at all — it consumes the two tensors on the context and derives
Amatrix, nothing more (:281-343). The grid and the bindings are read by
the seven driving-force and energy routines, so that is where they are
demanded. A first draft required FFT dimensions and every binding in
validate_setup; unit.l3_ferroelectric sets up no grid, FFT or MPI
precisely because setup needs none, and it went red immediately
(tests/unit/test_l3_ferroelectric.f90:8-13, TASK.md:1503).
Exact refusal messages and status codes (constants in
L0_Base/error.f90:6-12):
Check |
|
|
|---|---|---|
all of |
|
|
non-finite |
|
|
non-finite |
|
|
non-finite |
|
|
|
|
|
any unbound pointer |
|
|
One non-zero coefficient among a1/a11/a12 passes: it means somebody
chose a material (tests/unit/test_l3_ferroelectric.f90:120-126).
4. Minimal working example#
Fills the context and passes validate_setup. Values and structure follow the
unit test’s fill_context helper and its validation case
(tests/unit/test_l3_ferroelectric.f90:32-50, :103-109) — deliberately
no grid, FFT or MPI setup, which is legal for setup (not for solve).
program minimal_ferroelectric
use mod_mupro_base, only: rdp, type_mupro_Status, mupro_status_success
use mod_mupro_ferroelectric, only: type_mupro_ferroelectricContext
implicit none
type(type_mupro_ferroelectricContext) :: context
type(type_mupro_Status) :: status
integer :: i
! Material only. Everything not assigned keeps its documented default.
do i = 1, 6
context%stiffness(i, i) = 1.0_rdp
context%electrostrictive(i, i) = 2.0_rdp
end do
context%a1 = -1.0_rdp ! required: all three zero is refused
context%a11 = 2.0_rdp ! required
context%a12 = 0.5_rdp ! required
context%a111 = 4.0_rdp ! optional: zero would mean "term absent"
context%a112 = 5.0_rdp
context%a123 = 6.0_rdp
call context%validate_setup(status)
if (status%code /= mupro_status_success) then
print *, trim(status%message)
stop 1
end if
call context%setup() ! license-gated in non-MUPRODEV builds
end program
Before calling any get_* routine, additionally initialize the runtime
(mupro_mpi_setup, mupro_size_setup, mupro_fft_setup —
apps/muFerro/src/setup.f90:90-102), bind all ten pointers to arrays of the
shapes in §2 (complete binding sequence:
apps/muFerro/src/setup/ferroelectric.f90:7-36), and gate on
validate_solve.
5. Failure modes with evidence#
Real defects this module’s history produced, each with its source:
Unassigned components were undefined, not zero (fixed PR-054, 2026-08-06). No component of this type had a default initializer and no pointer had
=> null(); anything an application did not assign was read off the stack. Intel’s-init=arrays -init=zerohid it completely; gfortran and nvfortran do not, and nvfortran is the production GPU path (TASK.md:1422). Adding the defaults changed no number on the platform of record (TASK.md:1426). muFerro assigns 29 of 30 components, which is why the defaults matter for the next application (ferroelectric.f90:73-77).The TOML reader let
a1/a11/a12go unread (fixed PR-046b, 2026-08-02). They had no default, so a document missing one left the context component untouched — undefined — and the Landau energy was computed from it (TASK.md:1335,ferroelectric.f90:178-181). They aremupro_toml_required now (:202-204). Thea2=a1-style cascade is cubic symmetry, kept but announced in the log (:206-218).Same PR, on the material path feeding this context:
read_tensor_rank2read its ownintent(out)argument (out_1d = reshape(out, [9])before anything wroteout), so every component the point-group symmetry left out kept stack garbage (TASK.md:1332). The owner-approved fix isout_1d = 0.0_rdp(L1_MaterialGenerator/tensor.f90:162,TASK.md:1333).The license verdict was computed and discarded (fixed PR-028).
ferroelectric_setupcalledcheck_validityand threw the result away, so release builds ran the module unlicensed (TASK.md:977). The gate now terminates on failure (ferroelectric.f90:269-276).Overbroad validation was attempted twice and caught by the unit test both times. A runtime-initialization guard was applied to
ferroelectric_setupduring the principle-6 work andunit.l3_ferroelectricwent red — setup reads no global state (TASK.md:1270). The first draft of PR-061’svalidate_setuprepeated the mistake with FFT dimensions and bindings (TASK.md:1503-1505). The test now asserts the split directly: a material-only context passesvalidate_setupand is refused byvalidate_solve(tests/unit/test_l3_ferroelectric.f90:128-137).Most of this directory is not in the build.
SRCis onlyferroelectric.f90,ferroelectric_inhomo.f90,interface.f90(CMakeLists.txt:3-6).evolution.f90does not compile (it cannot seetype_msolve) and contains three sites that write through never-associated pointers (msolveContext%sysArray = p1_sys) (TASK.md:1310,TASK.md:1302);LNO.f90references the non-existentmod_fftw_mpi(CLAUDE.md, dead-code list). Do not take contract semantics from those files.