Skip to content

Stop writing a fabricated key into every generated Kratos DEM deck - #53

Merged
alhermann merged 2 commits into
mainfrom
hotfix/kratos-dem-deck
Aug 8, 2026
Merged

Stop writing a fabricated key into every generated Kratos DEM deck#53
alhermann merged 2 commits into
mainfrom
hotfix/kratos-dem-deck

Conversation

@alhermann

Copy link
Copy Markdown
Member

One file. Deliberately minimal so it can land ahead of the full consolidation in #50, like #52 did.

What main does today

Every Kratos DEM deck OASiS generates contains PARTICLE_FRICTION — in the mdpa and in MaterialsDEM.json.

The key does not exist. Measured with grep -r -a -F against the full 28-application Kratos build: zero occurrences. The real keys are STATIC_FRICTION and DYNAMIC_FRICTION, per contact pair, in material_relations — a different place in a different schema, not a rename.

It also emitted the FEM materials schema, plus problem_data / solver_type / time_stepping (DEM reads none of them), DEM_timestep_safety_factor (the real key is DeltaTimeSafetyFactor, itself dead code), and a mesh filename DEM never opens.

The deck had never run, and a green gate said otherwise

catalog_template_executes records kratos::dem::2d_rc=0. But that gate runs the template, which only writes files and exits. It never runs the input.py the template writes. Run that:

RuntimeError: Error: Getting a value that does not exist. entry string : strategy
  kratos/sources/kratos_parameters.cpp:426

So the gate was green on the half that cannot fail. (A general screen for this shape found exactly one affected template across all 255 in the catalog — this one.)

After

kratos:dem:2d   template_rc=0   input.py_rc=0   both run

A 2-D granular settling problem: 64 CylinderParticle2D discs onto a floor between two walls, ~9 s. Physical, not merely exit-0 — all 64 particles survive, the pile comes to rest at max |v_y| = 0.005 m/s, and y_min sits one radius above the floor with a 0.1%-of-radius Hertzian overlap.

The emitted input.py now also refuses to exit 0 when the model has quietly lost its particles. That matters: Kratos DEM deletes escaped particles silently. Measured on this case, dt=2e-4 loses all 16 and dt=1e-4 loses 12 of 16 — both at exit code 0.

Scope

PARTICLE_FRICTION still appears once in the file, in a comment stating it does not exist. The generated deck writes STATIC_FRICTION / DYNAMIC_FRICTION.

The knowledge entries, fixtures and execution ledger for this work (151 rows, 151 pass, 150 discriminate; coverage 73.0% → 80.9%) sit on feature/kratos-generator and are not in this PR.

`main` writes `PARTICLE_FRICTION` into the mdpa and into MaterialsDEM.json on
every DEM deck OASiS generates. The key does not exist. Measured with
`grep -r -a -F` against the full 28-application Kratos build at
/mnt/kratos-tier2/kv: zero occurrences. The real keys are STATIC_FRICTION and
DYNAMIC_FRICTION, per contact PAIR, in `material_relations` — a different place
in a different schema, not a rename.

It also emitted the FEM materials schema, `problem_data`, `solver_type` and
`time_stepping`, none of which DEM reads, plus `DEM_timestep_safety_factor`
(the real key is `DeltaTimeSafetyFactor`, itself dead code) and a mesh filename
DEM never opens.

The deck it produced had therefore never run. `catalog_template_executes`
recorded `kratos::dem::2d_rc=0`, but that gate runs the template — which only
WRITES files and exits — and never the `input.py` it writes. Run that, and it
dies at `entry string : strategy` in kratos_parameters.cpp:426.

Now:

    kratos:dem:2d   template_rc=0   input.py_rc=0   both run

The deck is a 2-D granular settling problem — 64 CylinderParticle2D discs onto
a floor between two walls, ~9 s. Physical, not merely exit-0: all 64 particles
survive, the pile comes to rest at max |v_y| = 0.005 m/s, and y_min sits one
radius above the floor with a 0.1%-of-radius Hertzian overlap.

The emitted input.py also refuses to exit 0 when the model has quietly lost its
particles. That matters because Kratos DEM deletes escaped particles silently:
measured on this case, dt=2e-4 loses all 16 and dt=1e-4 loses 12 of 16, both at
exit code 0.

One file, so it can be reviewed and land ahead of the full consolidation. The
knowledge, fixtures and ledger for this work sit on feature/kratos-generator.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Updates the Kratos DEM 2D generator so it stops emitting the non-existent PARTICLE_FRICTION key and instead writes a runnable DEMApplication deck using the correct DEM schemas (flat ProjectParametersDEM.json, and MaterialsDEM.json with material_relations contact-pair friction).

Changes:

  • Rewrites the DEM deck generator to emit the correct DEMApplication file set and naming conventions (<problem_name>DEM.mdpa, <problem_name>DEM_FEM_boundary.mdpa, MaterialsDEM.json, ProjectParametersDEM.json, input.py).
  • Fixes friction/material modeling by moving friction to contact-pair keys (STATIC_FRICTION / DYNAMIC_FRICTION) under material_relations (removing fabricated PARTICLE_FRICTION from generated decks).
  • Improves execution robustness by generating an input.py that summarizes results and exits non-zero if particles are silently lost.
Suppressed comments (2)

src/backends/kratos/generators/dem.py:97

  • If dt_override is 0/negative (or the Rayleigh estimate produces NaN/inf due to invalid material params), the generated script will crash at T_end / dt with a ZeroDivisionError/ValueError. Add a small validation step so failures are explicit and actionable.
dt = float(dt_override) if dt_override is not None else dt_safety * dt_rayleigh
n_steps = max(1, int(round(T_end / dt)))

src/backends/kratos/generators/dem.py:104

  • The y-fit is validated, but x-fit is not: for small domain_x (or large radius), x0 can be < radius and the initial particle centers will start inside/through the left wall. Since this is a deck generator, it's better to fail fast with a clear message when the particle block cannot fit in x within [radius, domain_x - radius].
spacing = 2.2 * radius
cols = max(1, int(math.floor((domain_x - 2.0 * spacing) / spacing)))
cols = min(cols, max(1, int(math.ceil(math.sqrt(n_particles)))))
rows = int(math.ceil(n_particles / cols))
block_w = (cols - 1) * spacing

Comment thread src/backends/kratos/generators/dem.py Outdated
dt = params.get("dt", None)
dt_safety = float(params.get("dt_safety_factor", 0.02))
problem_name = str(params.get("problem_name", "granular"))
threads = int(params.get("omp_threads", 2))
Flagged in review of #53. `omp_threads=0` reached the deck verbatim, and
OMP_NUM_THREADS=0 is not "let OpenMP decide" — it is undefined behaviour, with
some runtimes aborting and others silently serialising.

A user passing 0 to mean "use the default" would get a deck that fails for a
reason unrelated to their simulation. Worse in this particular case: a DEM run
reports particle loss silently, so a hard crash is the kinder of the two
outcomes and a silent one would be the dangerous one.

Verified: omp_threads=0 and -1 both emit 1; 2 passes through unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@alhermann
alhermann merged commit 7c184d5 into main Aug 8, 2026
2 checks passed
@alhermann
alhermann deleted the hotfix/kratos-dem-deck branch August 8, 2026 23:48
alhermann added a commit that referenced this pull request Aug 9, 2026
…ot measure

Seven ledgers were produced before the mutation arm was fixed, so their
discrimination counts came from a tool that never applied recipe-declared
controls. Re-run sequentially, each waiting for load below 12 (a campaign at
100-156 previously produced wall-clock false failures in BOTH directions):

    backend    rows  pass  disc  prove-nothing  no-verdict
    fourc       395   393   377        0            18
    fenics      197   193   192        0             5
    skfem       144   144   144        0             0
    kratos      139   133   132        1             6
    dealii      118   105   103        1            14
    ngsolve     115   115   115        0             0
    dune         41    38    36        2             3
    sparta       50    50    50        0             0
    febio        76    25    24        1            51
    coupling     26    26     0        0            26
    TOTAL      1301  1222  1173        5           123

skfem and ngsolve had never had a ledger at all.

TWO ROWS DO NOT MEAN WHAT THEY LOOK LIKE, AND BOTH ARE ENVIRONMENT, NOT CORPUS:

febio's 51 no-verdicts are a missing solver. The installed binary has no MKL and
no pardiso — 0 linked libraries, 0 symbols — so its only linear solver is
skyline, which is symmetric-only and cannot do the biphasic and FSI analyses
those fixtures exercise. One of them is named
`biphasic_fsi_needs_nonsymmetric_solver`: the corpus correctly documenting the
limitation that now blocks it. The committed 74/76 was measured against a
different, more capable build, and should not be quoted as current.

coupling's 26 no-verdicts are `--no-mutation`: its controls are recipe-declared
and the run that would exercise them needs a quiet tree, which it has not had.

Also worth stating: my first attempt at this sweep had TWO bugs of my own.
`run_one dune DUNE_PYTHON=...` passed the assignment as a command, so dune,
kratos and fenics never launched. And febio ran without FEBIO_BINARY, so every
fixture printed FAIL: because it could not find the solver — an environment
failure wearing the costume of a fixture failure, which is the third time in
this campaign an instrument's blindness has been reported in the vocabulary of
a finding.

The five that genuinely prove nothing are named and left alone rather than
tuned: dune/point_indicator_selects_no_facet (its assertion selects a different
phenomenon than its name claims), kratos/element_name_missing_node_count, one
each in dealii and febio.

Note kratos measures 132/139 here and 150/151 on feature/kratos-generator. The
difference is the DEM generator fix, which is not on this tree. The second
number describes the code once PR #53 lands; neither is wrong, and they measure
different trees.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
alhermann added a commit that referenced this pull request Aug 9, 2026
Brings the two published hotfixes (#52, #53) onto the consolidation line
so the release branch is a superset of what is public.

Two conflicts, resolved deliberately:

src/backends/kratos/generators/dem.py — took main's side. It carries the
OMP_NUM_THREADS clamp added in review of PR #53: omp_threads=0 is not
"let OpenMP decide", it is undefined behaviour, and a DEM run reports
particle loss silently, so failing early is the kinder outcome.

src/backends/fourc/backend.py — took the consolidation side, which
deletes a 149-line block of prose "needs/pitfalls" dictionaries that main
still carries. This is not a loss: consolidation replaced all six physics
entries with input decks that actually run —
plasticity_{linear_2d,nonlinear_3d}, porous_media_{terzaghi_2d,
consolidation_3d}, particle_pd_impact and particle_sph_dambreak — each
checked present before the block was dropped.

Verified after resolution: no conflict markers survive; the thread clamp
is present; and none of SOUNDSPEED, SMOOTHING_LENGTH, PARTICLE_FRICTION
or DEM_timestep_safety_factor appears anywhere as a positive assertion.
Every remaining mention is a warning that the key does not exist, which
is the knowledge the hotfixes were written to add.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants