🇧🇷 Versao em portugues:
docs/para-cientistas.md
Read this first if you used FlowSim before the 2026-07 vectorization campaign and want to keep running your cases without touching the internals.
Nothing about the physics changed. Nothing about your benchmark logic changed.
No Caso* class was renamed, no case number was reassigned, no output format
was altered. This document exists so you can verify that for yourself in
under 10 minutes, and understand the very small handful of things that did
move.
- Update to the latest master (or the
v2.0.1-vectorizedtag):git pull. - Your
Start.datalmost certainly still works, with two possible exceptions (mesh path and output path — see § 3). - Run the same way you always did: open MATLAB,
cd FlowSim/, typemain. - The results should be numerically identical to v1 for TPFA and MPFA-D on the same mesh + same case. There is a test that proves this on M8 + Caso 439 — you can run it with one command (see § 5).
- Nothing was deleted. The old code is still on disk under
legacy/and still on the MATLAB path — the new vectorized modules just get chosen first when both exist.
The performance-critical parts of the code (LPEW2 interpolation weights,
MPFA-D and TPFA matrix assembly) had per-node and per-face for loops in
the original code. Those loops are now batched array operations in a
new subtree called +fs/ (a MATLAB "package"). For an M8 mesh (128
elements), the loops didn't matter much; for larger meshes (Hermeline
192×192 and above), the vectorized version is noticeably faster and
scales better.
How the two versions coexist without conflict: MATLAB's function
resolution honors path order. flowsim_init.m puts the new +fs/ tree
first on the path and the old legacy/ tree last. So when the code asks
for, say, LPEW2 weights, MATLAB finds the fast new version first. If for
some reason +fs/ were removed, the old version underneath would
transparently take over. This is called "path shadowing" and it means:
- You can compare v1 and v2 without switching branches — just call the legacy function by its full ferncodes name (see § 5).
- If you don't trust the new module for a specific case, disable it
with
flowsim_init('legacy', true)and you get pure-v1 behaviour.
The following are byte-identical or behaviourally identical to v1:
- Every
Caso*benchmark class inbenchmarks/(Caso331, Caso341, Caso431, Caso437, Caso439, Caso21p1, Caso346, etc. — the whole registry infactories/createBenchmark.m) - All physics models: Van Genuchten, Gardner, Brooks–Corey, the cubic saturation model, the anisotropic tensors
- All benchmarks that read external data files (Perm_Var*.mat for cases 247/249/250) — the data still loads (see § 6)
- Every
PLUG_*function.mcallback — permeability, boundary conditions, source terms, dispersion, gravity - The time integrators —
hydraulic,hydraulic_RE,IMPES,IMPEC - The output format —
postprocessor.mwrites the same VTK / .mat / plot files with the same field names - The Picard iteration logic and its convergence criteria
- The Anderson acceleration wrapper (
ferncodes_andersonacc*, still used bypmethod='AA') - The L-scheme regularised iteration
- Every
.mshfile — same node coordinates, same connectivity, same boundary tags
Numerically, the golden baseline test (tests/unit/unit_baseline_reproduces.m)
proves that on M8.msh with numcase=439, both TPFA and MPFA-D produce
matrices whose Frobenius diff vs the pre-vectorization run is exactly
0.000e+00 — bit-for-bit identical, not "close enough".
Two lines to check before your first v2 run:
Line 33 — Output folder (>>> EDITE AQUI <<< block for "Pasta onde os
resultados serao gravados"):
C:\Users\flc59\Documents\Benchmark_Cases\BenchHydraulic409
This is your old Windows path. If MATLAB reports "mkdir: Access is denied" or "Cannot open file", either point it at a folder you own, or use a WSL-visible path.
Line 218 — Mesh folder (>>> EDITE AQUI <<< block for "Pasta que
contem os arquivos .msh"):
The mesh files were reorganised into subfolders in v2. Instead of putting
every .msh in one flat directory, they now live under meshes/:
| If you were using... | Point Start.dat at... |
|---|---|
M8.msh, M8_distor1.msh, M8_distor2.msh, M8_distor3.msh |
meshes/kozdon/ (relative to repo root — MATLAB will resolve it) |
HermelineMeshMod*.msh (any resolution) |
meshes/hermeline/ |
malhareal.msh, mesh_randistorted0_6_TriangA_8_8.msh |
meshes/other/ |
Everything else in Start.dat — numcase, pmethod, phasekey,
timestep controls, convergence tolerances, the [AVANCADO] sections
— means what it always meant. Untouched.
In MATLAB (Windows or Linux):
cd C:\path\to\FlowSim % (or /path/to/FlowSim on Linux)
mainmain.m calls flowsim_init for you the first time (via startup.m).
That's it. Same as v1.
Headless from a terminal (WSL / Linux):
cd /path/to/FlowSim
tools/mrun -c $(pwd) main.mThis runs matlab.exe -batch in the background, prints the full log to
your terminal, and exits with code 0 on success. Convenient for scripting
sweeps.
To sweep multiple cases: keep the workflow identical to v1 — a shell
loop or MATLAB script that edits Start.dat between runs.
This is the one thing you probably want to see for yourself. There's a
committed correctness test that captures every intermediate quantity
(mesh counts, assembly matrix Frobenius norm, MPFA-D transmissibilities
Kde/Ded/Kt/Kn, LPEW weights, RHS L2 norm) and diffs it against a
pre-recorded baseline for M8 + Caso 439:
tools/mrun -c $(pwd) tests/unit/unit_baseline_reproduces.mExpected output (last few lines):
[ok] M8-num439-mpfad: M Frobenius reproducible (rel diff 0.000e+00)
[ok] M8-num439-mpfad: I L2-norm reproducible (rel diff 0.000e+00)
[ok] M8-num439-mpfad: premethod.Kde L2 reproducible (rel 0.000e+00)
...
TEST OK unit_baseline_reproduces 35/35 passed
All 35 assertions must show rel diff 0.000e+00. If any of them
diverges, that's a real regression — please open an issue with the log.
For any Caso NNN you care about:
- Get a v1 run:
git checkout v1.0.0-pre-vectorization(in a scratch clone — don't do this in your working copy), run your case, save the output folder somewhere. - Come back to v2:
git checkout master, run the same case, save its output folder. - Diff the two output folders — the
pressure*.vtk,flowrate*.mat, or whichever field you monitor.
For TPFA and MPFA-D methods the diff should be exact zero. For MPFA-H, MPFA-QL, NLFV-PP, NLFV-H, DMP the diff should also be zero because their assembly still calls the legacy code (see § 8).
The cleanest way to run pure v1 code is to check out the pre-vectorization tag in a scratch clone (do NOT do this in your working copy — you'll detach HEAD):
git clone https://github.com/Feraul/FlowSim.git /tmp/flowsim-v1
cd /tmp/flowsim-v1
git checkout v1.0.0-pre-vectorization
# ...then run your case hereThere's also an init flag that skips the legacy/ tree entirely, running
only the new vectorized modules (+fs/) + the OOP layer:
flowsim_init('legacy', false); % skip legacy — vectorized-only modeNote: this flag does the opposite of what its name might suggest — it
controls whether the legacy/ tree gets added to the MATLAB path.
legacy=true (the default) includes it as a lower-precedence fallback;
legacy=false leaves it out. Setting legacy=false is useful for
"can this run purely through the vectorized code path?" experiments, but
you'll lose the scaffolded methods (MPFA-H, MPFA-QL, NLFV-PP, NLFV-H,
DMP) because they still delegate to legacy internally.
To compare v1 vs v2 numerics for a case that is fully vectorized
(TPFA or MPFA-D), the two default runs — one on v1.0.0-pre-vectorization
and one on current master — are what you want.
If you had scripts that reference a file by its full path, here's the map:
| Kind of file | Was at (v1) | Is at (v2) |
|---|---|---|
| Entry point | root: main.m, startup.m |
still root (unchanged) |
| Preprocessor | root: preprocessormod.m, preprocessmethod.m |
runtime/preproc/ |
| Time integrators | root: hydraulic.m, hydraulic_RE.m, IMPES.m, IMPEC.m, IMHEC.m, setmethod.m |
runtime/time/ |
| PLUG callbacks | root: PLUG_bcfunction.m, PLUG_kfunction.m, PLUG_sourcefunction.m, PLUG_dfunction.m, PLUG_Gfunction.m |
runtime/plug/ |
| Solver / helpers | root: solver.m, addsource.m, postprocessor.m, soil_properties.m, thetafunction.m, applyinicialcond.m, ~30 more |
runtime/util/ |
| Meshes | root: M8*.msh, Hermeline*.msh, malhareal.msh, etc. |
meshes/{kozdon,hermeline,other}/ |
| Data files | root: Perm_Var0p1.mat, Perm_Var2.mat, Perm_Var5.mat, Teste_5.xlsx, Teste_6.xlsx, malha_D.geo, figura_case_4_Qian_teste_h.fig |
data/ |
| Ferncodes internals | root: ferncodes_*.m (dozens of files) |
legacy/ferncodes/<method>/ |
| Old preprocessor variants | root: preprocessor.m, preprocessor2.m |
legacy/preprocessor/ |
| Benchmark classes | benchmarks/Caso*.m |
benchmarks/Caso*.m (unchanged) |
| Method classes | solvers/Metodo*.m, Solver*.m |
solvers/Metodo*.m (all under MetodoBase now — see below) |
| Simulation classes | simulacoes/Sim*.m |
simulacoes/Sim*.m (unchanged) |
| Factories | factories/create*.m |
factories/create*.m (unchanged) |
You do not need to update any Caso* file because they don't reference
any of the moved files by absolute path — they reach out through
env.geometry and env.config which are populated by the preprocessor.
The path move is transparent to your physics code.
In v1 the factory tried to instantiate MetodoMPFAH, MetodoMPFAQL,
MetodoNLFVPP but those files didn't exist — only SolverMPFAH and
SolverNLFVPP (inheriting from a missing SolverBase). So dispatching
to any of those pmethod values crashed. In v2, that's fixed:
SolverMPFAH.m→ renamed toMetodoMPFAH.m(now inherits fromMetodoBaselike the others)SolverNLFVPP.m→ renamed toMetodoNLFVPP.m(same)MetodoMPFAQL.m— created from scratch to match what the factory expected
So pmethod = 'mpfah' / 'mpfaql' / 'nlfvpp' now actually works end to
end. That's a bug fix for something that was broken in v1.
Same as always. Copy an existing benchmarks/CasoNNN.m, edit the physics,
add the one line to factories/createBenchmark.m:
case NNN, bench = CasoNNN();No other file needs to change. The abstract methods on SimulacaoBase
tell you what you have to implement (right-click → Go to definition
inside MATLAB, or just look at benchmarks/Caso439.m — it's the most
complete example).
Method (pmethod) |
Vectorized in v2 | Speed vs v1 (M8) | Speed vs v1 (Hermeline 192²) |
|---|---|---|---|
tpfa |
✔ yes | roughly the same | measurably faster |
mpfad |
✔ fully | ~2× faster | large improvement |
mpfah |
scaffold — delegates to legacy | same as v1 | same as v1 |
mpfaql |
scaffold | same | same |
nlfvpp |
scaffold | same | same |
nlfvh |
scaffold | same | same |
dmp |
scaffold | same | same |
"Scaffold" means the entry point exists in the new package tree but
internally calls the legacy ferncodes_* code (correctness is preserved
by construction). The five scaffolded methods will be fully vectorized in
a follow-up campaign (estimated total effort: 30–45 hours, sequenced by
increasing difficulty: NLFV-H → NLFV-PP → MPFA-QL → DMP → MPFA-H).
The LPEW2 interpolation weights — a hot inner loop shared by every MPFA
family method except TPFA — are fully vectorized (+fs/+lpew/+v2/),
so even the scaffolded methods benefit from that piece.
| Symptom | Likely cause | Fix |
|---|---|---|
Cannot open file: M8.msh |
Start.dat's mesh path points at repo root but meshes now live in meshes/kozdon/ |
Update the mesh folder line in Start.dat |
mkdir: Access is denied during preprocessor |
Start.dat's output path is a Windows drive letter MATLAB can't write to | Point it at a folder you own, or use /mnt/c/... under WSL |
Undefined function or variable 'MetodoXYZ' |
You have an old checkout that still has the broken factory | git pull (v2.0.0 fixed this) |
| A test fails with a small non-zero rel diff | You're running a stale golden baseline from a previous experiment | Ignore the specific test (that's a captured artefact) — the pipeline itself is fine if unit_baseline_reproduces passes |
Suddenly the code runs the wrong method for pmethod = 'nlfvpp' |
You edited factories/createMetodo.m in v1 and your edit didn't survive the merge |
Run git log -- factories/createMetodo.m to see what happened; the current dispatch is the canonical one |
- First: run
tools/mrun -c $(pwd) tests/unit/unit_baseline_reproduces.m. If it fails, something is genuinely broken and it needs looking at. - Second: if that passes but your case diverges from a v1 output
you archived, isolate whether it's the mesh (was it moved?), the
physics (did you change PLUG_*?) or the solver (is
pmethodthe same?). - Third: if you're still stuck, open an issue on GitHub with:
- the exact
Start.datyou used - your MATLAB version
- the
unit_baseline_reproducesoutput - a description of the expected vs observed result
- the exact
Reading directly from factories/createBenchmark.m, here are all the
numcase values that will run out of the box. Anything not listed here
was never wired up (or is registered but the Caso* file doesn't exist —
those cases would have failed on v1 too and still fail on v2).
Groundwater / hydraulic head (300–350): 330, 331, 332, 333, 334, 335, 336, 337, 338, 341, 341.1, 342, 343, 347, 248.
Richards (400–500): 431, 432, 433, 434, 435, 436, 437, 438, 439.
Reference / tensor cases (1–100): 21.1, 34.6, 34.7, 35, 36.
Contaminant transport (200–300): 241, 245, 247, 249, 250.
To see the exact class file each numcase dispatches to, open
factories/createBenchmark.m — every entry has a case NNN, bench = CasoNNN(); line with a comment explaining the physics.
Written for the FlowSim team by the AXON code-dev campaign, 2026-07-03.
Feedback welcome — open an issue on GitHub or edit this file directly and
submit a PR. Portuguese translation available on request.