---
title: "Gallery"
listing:
id: model-listing
contents: pages
type: grid
grid-columns: 3
sort: "categories"
sort-ui: [title, date, date-modified]
filter-ui: true
categories: cloud
fields: [catalog-id, title, description, categories, date, date-modified]
field-display-names:
catalog-id: "ID"
date: "Published"
date-modified: "Updated"
date-format: "YYYY-MM-DD"
page-size: 1000
feed: true
page-layout: full
toc: false
---
Every page shows a published model, the experimental data it was validated
against, and a `pymrm` reproduction with a quantitative agreement metric.
**Filtering.** Every page carries tags on five axes at once. The **Categories**
panel groups them by axis — click a tag to filter, click *Categories* to clear.
Selecting a tag prints its code and meaning above the list. The
[Taxonomy](taxonomy.qmd) page defines every axis and code in full.
| Axis | Meaning | Example |
|---|---|---|
| **Section** | Catalog section — the physical domain | `A` transport closures, `D` fixed bed |
| **Structure** | Mathematical structure — *the axis to filter on if you want to reuse code* | `S8` nested reactor–particle coupling |
| **Tier** | Standing in the literature | `T0` canonical |
| **Data** | Provenance tier of the dataset | `tier1` open repository, `tier6` reference solution |
| **Phase** | Phase system | `gas`, `gas-solid` |
Two pages sharing a structure code are solved by nearly the same pymrm code even
when their physics is unrelated — that is the intended route to reuse. Hovering a
tag in the panel shows what it means; the [Taxonomy](taxonomy.qmd) page lists them
all.
::: {#gallery-category-title}
:::
::: {#model-listing}
:::
```{=html}
<script>
// Regroup Quarto's category cloud into one block per classification axis.
//
// The tags are prefixed (sec:, struct:, tier:, data:, phase:) so that a single
// flat list can carry five axes. Left alone, Quarto sorts them alphabetically
// and scales each one's font size by how many pages use it -- which interleaves
// the axes, repeats the prefix on every chip, puts struct:S10 before struct:S2,
// and makes a rare tag look less important than a common one when the tags are
// a filter, not a measure of popularity.
//
// The existing elements are MOVED, never cloned or rebuilt, so the click
// handlers Quarto attached to them keep working, and
// `.quarto-listing-category .category[data-category=...]` still matches for
// activation from the URL hash.
(function () {
var AXES = [
{ prefix: "sec", label: "Section" },
{ prefix: "struct", label: "Structure" },
{ prefix: "tier", label: "Tier" },
{ prefix: "data", label: "Data" },
{ prefix: "phase", label: "Phase" },
];
// A bare "A" or "S8" on a chip says nothing on its own, so the meaning goes
// in the tooltip. Kept in step with docs/taxonomy.md, Axes 1 and 2.
var MEANING = {
"sec:A": "Transport & closure relations",
"sec:B": "Catalyst particle & intraparticle transport",
"sec:C": "Reaction kinetics",
"sec:D": "Fixed / packed bed reactors",
"sec:E": "Fluidised beds",
"sec:F": "Gas–liquid & slurry reactors",
"sec:G": "Three-phase packed beds",
"sec:H": "Membrane reactors & separations",
"sec:I": "Structured & intensified reactors",
"sec:J": "Adjacent unit operations",
"struct:S1": "ODE-IVP in time (batch, CSTR dynamics)",
"struct:S2": "ODE-IVP in space (ideal PFR marching)",
"struct:S3": "1D steady BVP (particle, film)",
"struct:S4": "1D transient PDE (axial dispersion, breakthrough)",
"struct:S5": "1D convection-dominated (sharp fronts)",
"struct:S6": "2D PDE (radial dispersion, Graetz, monolith)",
"struct:S7": "Multi-domain / multi-phase coupling",
"struct:S8": "Nested-scale coupling (reactor ↔ particle)",
"struct:S9": "Implicit multicomponent flux (Maxwell–Stefan, dusty gas)",
"struct:S10": "DAE / constrained (pressure–velocity, electroneutrality)",
"struct:S11": "Population balance (internal coordinate)",
"struct:S12": "Moving boundary / shrinking core",
"struct:S13": "Non-standard geometry",
"tier:T0": "Canonical — taught everywhere, still the reference formulation",
"tier:T1": "Field standard — the default model in its subfield",
"tier:T2": "Established alternative — competes with the field standard",
"tier:T3": "Specialised or emerging",
"data:tier1": "Open repository",
"data:tier2": "Table printed in the paper",
"data:tier3": "Supplementary material",
"data:tier4": "Digitised from a figure",
"data:tier5": "Obtained from the authors",
"data:tier6": "Reference or exact solution, not experiment",
};
// "S10" must sort after "S2", so compare any trailing digits numerically.
function naturalKey(s) {
var m = /^(\D*)(\d*)(.*)$/.exec(s);
return [m[1], m[2] === "" ? -1 : parseInt(m[2], 10), m[3]];
}
function naturalCmp(a, b) {
var ka = naturalKey(a), kb = naturalKey(b);
return (
ka[0].localeCompare(kb[0]) || ka[1] - kb[1] || ka[2].localeCompare(kb[2])
);
}
function group() {
var cloud = document.querySelector(
".quarto-listing-category.category-cloud"
);
if (!cloud || cloud.dataset.grouped === "1") return;
var items = Array.prototype.slice
.call(cloud.querySelectorAll(":scope > .category"))
.map(function (el) {
var full = (el.textContent || "").trim();
var i = full.indexOf(":");
return {
el: el,
full: full,
prefix: i > 0 ? full.slice(0, i) : "",
rest: i > 0 ? full.slice(i + 1) : full,
};
});
if (!items.length) return;
var buckets = {}, order = [];
items.forEach(function (it) {
var key = it.prefix || "_other";
if (!buckets[key]) {
buckets[key] = [];
order.push(key);
}
buckets[key].push(it);
});
var known = AXES.map(function (a) { return a.prefix; });
var sequence = AXES.filter(function (a) { return buckets[a.prefix]; }).concat(
order
.filter(function (k) { return known.indexOf(k) === -1; })
.map(function (k) {
return { prefix: k, label: k === "_other" ? "Other" : k };
})
);
var frag = document.createDocumentFragment();
sequence.forEach(function (axis) {
var bucket = buckets[axis.prefix];
bucket.sort(function (a, b) { return naturalCmp(a.rest, b.rest); });
var group = document.createElement("div");
group.className = "gallery-cat-group";
var label = document.createElement("span");
label.className = "gallery-cat-group-label";
label.textContent = axis.label;
group.appendChild(label);
var chips = document.createElement("div");
chips.className = "gallery-cat-chips";
bucket.forEach(function (it) {
// the full prefixed tag stays reachable, and where the short label is
// not self-explanatory the tooltip says what it means
var meaning = MEANING[it.full];
it.el.setAttribute("title", meaning ? it.full + " — " + meaning : it.full);
var span = it.el.querySelector(".quarto-category-count") || it.el;
if (it.prefix) span.textContent = it.rest;
chips.appendChild(it.el); // move, preserving Quarto's onclick
});
group.appendChild(chips);
frag.appendChild(group);
});
cloud.appendChild(frag);
cloud.dataset.grouped = "1";
var title = document.querySelector(".quarto-listing-category-title");
if (title && !document.querySelector(".gallery-category-hint")) {
var hint = document.createElement("span");
hint.className = "gallery-category-hint";
hint.textContent = "Click a tag to filter; click Categories to clear.";
title.parentNode.insertBefore(hint, title.nextSibling);
}
}
// When a tag is active, print a heading above the list that spells out the
// code and what it stands for -- e.g. "Section A -- Transport & closure
// relations" -- so the short chip label is not the only clue to what is being
// shown. The active category is read from Quarto's own `.category.active`
// element, so this stays in step with whatever the click handlers and the URL
// agree on.
//
// Quarto stores the category in `data-category`, base64-encoded from its
// URL-encoded form (e.g. "sec:A" -> "c2VjJTNBQQ=="). Decode it, but only trust
// the decoded value if it looks like one of our prefixed tags, so a future
// Quarto that stored the value verbatim still works.
function decodeCat(raw) {
if (!raw) return null;
try {
var d = decodeURIComponent(atob(raw));
if (d.indexOf(":") >= 0 || MEANING[d]) return d;
} catch (e) {}
return raw;
}
function describe(full) {
var i = full.indexOf(":");
var prefix = i > 0 ? full.slice(0, i) : "";
var rest = i > 0 ? full.slice(i + 1) : full;
var axis = AXES.filter(function (a) { return a.prefix === prefix; })[0];
var axisLabel = axis ? axis.label : prefix;
var meaning = MEANING[full];
return (
(axisLabel ? axisLabel + " " : "") +
rest +
(meaning ? " — " + meaning : "")
);
}
function updateTitle() {
var host = document.getElementById("gallery-category-title");
if (!host) return;
var active = document.querySelector(
".quarto-listing-category .category.active"
);
var full = active ? decodeCat(active.getAttribute("data-category")) : null;
if (!full) {
host.textContent = "";
host.style.display = "none";
return;
}
host.textContent = describe(full);
host.style.display = "block";
}
function wireTitle() {
if (document.body.dataset.catTitleWired !== "1") {
// Any click in the category sidebar can change the active filter (a chip,
// or the "Categories" title that clears it); read the settled state a tick
// later. Delegated on document so the clear-title click is covered too.
document.addEventListener("click", function (e) {
if (
e.target.closest &&
e.target.closest(
".quarto-listing-category, .quarto-listing-category-title"
)
) {
setTimeout(updateTitle, 0);
}
});
document.body.dataset.catTitleWired = "1";
}
updateTitle();
}
function init() {
group();
wireTitle();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}
window.addEventListener("load", init); // in case the listing renders late
window.addEventListener("hashchange", updateTitle); // filter set from the URL
})();
</script>
```