diff --git a/web/demo.js b/web/demo.js
deleted file mode 100644
index 1236a8e8..00000000
--- a/web/demo.js
+++ /dev/null
@@ -1,195 +0,0 @@
-"use strict";
-
-const elements = {
- form: document.querySelector("#solve-form"),
- cells: document.querySelector("#cells"),
- cellsValue: document.querySelector("#cells-value"),
- order: document.querySelector("#order"),
- button: document.querySelector("#solve-button"),
- status: document.querySelector("#runtime-status"),
- statusDot: document.querySelector("#status-dot"),
- capabilities: document.querySelector("#capability-list"),
- canvas: document.querySelector("#mesh-canvas"),
- runIndex: document.querySelector("#run-index"),
- dofs: document.querySelector("#metric-dofs"),
- iterations: document.querySelector("#metric-iterations"),
- error: document.querySelector("#metric-error"),
- time: document.querySelector("#metric-time"),
- log: document.querySelector("#runtime-log"),
- footerVersion: document.querySelector("#footer-version"),
-};
-
-let mfemModule;
-let completedRuns = 0;
-let lastCells = Number(elements.cells.value);
-
-function setStatus(message, state = "loading") {
- elements.status.textContent = message;
- elements.statusDot.dataset.state = state;
-}
-
-function appendLog(message) {
- const timestamp = new Date().toLocaleTimeString([], {
- hour: "2-digit",
- minute: "2-digit",
- second: "2-digit",
- });
- const prior = elements.log.textContent.startsWith("Waiting") ? "" : `${elements.log.textContent}\n`;
- elements.log.textContent = `${prior}[${timestamp}] ${message}`;
-}
-
-function drawMesh(cells, solved = false) {
- const canvas = elements.canvas;
- const context = canvas.getContext("2d");
- const ratio = Math.max(1, window.devicePixelRatio || 1);
- const bounds = canvas.getBoundingClientRect();
- const width = Math.max(320, Math.round(bounds.width));
- const height = Math.max(260, Math.round(bounds.height));
- canvas.width = width * ratio;
- canvas.height = height * ratio;
- context.scale(ratio, ratio);
-
- const gradient = context.createRadialGradient(
- width * 0.5,
- height * 0.46,
- 10,
- width * 0.5,
- height * 0.5,
- width * 0.66,
- );
- gradient.addColorStop(0, solved ? "rgba(125, 249, 197, 0.30)" : "rgba(89, 199, 255, 0.16)");
- gradient.addColorStop(0.55, solved ? "rgba(35, 124, 126, 0.20)" : "rgba(27, 72, 104, 0.13)");
- gradient.addColorStop(1, "rgba(8, 14, 24, 0)");
- context.fillStyle = gradient;
- context.fillRect(0, 0, width, height);
-
- const padding = Math.max(30, Math.min(width, height) * 0.11);
- const side = Math.min(width - 2 * padding, height - 2 * padding);
- const left = (width - side) / 2;
- const top = (height - side) / 2;
- const step = side / cells;
-
- context.strokeStyle = solved ? "rgba(151, 255, 215, 0.30)" : "rgba(138, 207, 236, 0.22)";
- context.lineWidth = 1;
- context.beginPath();
- for (let index = 0; index <= cells; index += 1) {
- const offset = Math.round(index * step) + 0.5;
- context.moveTo(left + offset, top);
- context.lineTo(left + offset, top + side);
- context.moveTo(left, top + offset);
- context.lineTo(left + side, top + offset);
- }
- context.stroke();
-
- context.strokeStyle = solved ? "#7df9c5" : "#59c7ff";
- context.lineWidth = 2;
- context.strokeRect(left, top, side, side);
-}
-
-function renderCapabilities(capabilities) {
- const entries = [
- ["Wasm", capabilities.wasm],
- ["MFEM", true],
- ["libCEED", capabilities.ceed],
- ["zlib", capabilities.zlib],
- ["MPI", capabilities.mpi],
- ];
- elements.capabilities.replaceChildren(
- ...entries.map(([name, enabled]) => {
- const badge = document.createElement("span");
- badge.className = enabled ? "capability enabled" : "capability disabled";
- badge.textContent = enabled ? name : `${name} off`;
- return badge;
- }),
- );
- elements.footerVersion.textContent = `MFEM ${capabilities.mfemVersion} · WebAssembly`;
-}
-
-function formatScientific(value) {
- return Number(value).toExponential(3);
-}
-
-function formatTime(seconds) {
- const milliseconds = Number(seconds) * 1000;
- return milliseconds < 1000 ? `${milliseconds.toFixed(1)} ms` : `${Number(seconds).toFixed(2)} s`;
-}
-
-async function runSolve() {
- const cells = Number(elements.cells.value);
- const order = Number(elements.order.value);
- elements.button.disabled = true;
- elements.button.classList.add("working");
- setStatus("Solving finite-element system", "working");
- appendLog(`Starting ${cells}×${cells} mesh at polynomial order ${order}.`);
-
- await new Promise((resolve) => window.setTimeout(resolve, 20));
- try {
- const raw = mfemModule.ccall(
- "mfem_demo_solve",
- "string",
- ["number", "number"],
- [cells, order],
- );
- const result = JSON.parse(raw);
- if (!result.ok) {
- throw new Error(result.error || "The MFEM solve failed.");
- }
-
- completedRuns += 1;
- lastCells = cells;
- elements.runIndex.textContent = `Run ${String(completedRuns).padStart(2, "0")}`;
- elements.dofs.textContent = Number(result.trueDofs).toLocaleString();
- elements.iterations.textContent = Number(result.iterations).toLocaleString();
- elements.error.textContent = formatScientific(result.l2Error);
- elements.time.textContent = formatTime(result.elapsedSeconds);
- drawMesh(cells, true);
- setStatus("Solve complete", "ready");
- appendLog(
- `Converged in ${result.iterations} iterations with residual ${formatScientific(result.finalNorm)}.`,
- );
- } catch (error) {
- setStatus("Solve failed", "error");
- appendLog(error instanceof Error ? error.message : String(error));
- } finally {
- elements.button.disabled = false;
- elements.button.classList.remove("working");
- }
-}
-
-elements.cells.addEventListener("input", () => {
- elements.cellsValue.value = elements.cells.value;
- drawMesh(Number(elements.cells.value), false);
-});
-
-elements.form.addEventListener("submit", (event) => {
- event.preventDefault();
- void runSolve();
-});
-
-window.addEventListener("resize", () => drawMesh(lastCells, completedRuns > 0));
-
-async function initialize() {
- drawMesh(lastCells, false);
- try {
- if (typeof MFEMDemoModule !== "function") {
- throw new Error("The generated MFEM WebAssembly loader was not found.");
- }
- mfemModule = await MFEMDemoModule({
- locateFile: (path) => new URL(path, window.location.href).href,
- print: (message) => appendLog(message),
- printErr: (message) => appendLog(`Runtime: ${message}`),
- });
- const capabilities = JSON.parse(
- mfemModule.ccall("mfem_demo_capabilities", "string", [], []),
- );
- renderCapabilities(capabilities);
- elements.button.disabled = false;
- setStatus("Runtime ready", "ready");
- appendLog(`Loaded MFEM ${capabilities.mfemVersion} from WebAssembly.`);
- } catch (error) {
- setStatus("Runtime failed to load", "error");
- appendLog(error instanceof Error ? error.message : String(error));
- }
-}
-
-void initialize();
diff --git a/web/index.html b/web/index.html
deleted file mode 100644
index c4369950..00000000
--- a/web/index.html
+++ /dev/null
@@ -1,116 +0,0 @@
-
-
-
-
-
-
- MFEM WebAssembly Lab
-
-
-
-
-
-
-
-
-
- Poisson equation · unit square
- Solve a finite-element system in your browser.
-
- MFEM assembles and solves the system locally through WebAssembly.
- Change the mesh or polynomial order and compare the resulting error and workload.
-
-
-
-
-
-
-
-
-
-
-
Computed result
-
Finite-element mesh
-
-
Awaiting run
-
-
-
-
-
- −Δu = f
- u = 0 on ∂Ω
-
-
-
-
-
- True DOFs
- —
-
-
- CG iterations
- —
-
-
- L² error
- —
-
-
- Browser time
- —
-
-
-
-
- Runtime details
- Waiting for WebAssembly initialization…
-
-
-
-
-
- All computation stays in this browser tab.
-
-
-
-
diff --git a/web/meson.build b/web/meson.build
deleted file mode 100644
index e178d44a..00000000
--- a/web/meson.build
+++ /dev/null
@@ -1,34 +0,0 @@
-wasm_browser_demo = []
-
-if is_wasm and get_option('build_examples')
- configure_file(input: 'index.html', output: 'index.html', copy: true)
- configure_file(input: 'styles.css', output: 'styles.css', copy: true)
- configure_file(input: 'demo.js', output: 'demo.js', copy: true)
-
- wasm_browser_demo = executable(
- 'mfem-wasm-demo',
- 'wasm_demo.cpp',
- dependencies: [mfem_dep, template_include_dep],
- override_options: ['cpp_std=' + mfem_consumer_cpp_std],
- link_args: [
- '-sMODULARIZE=1',
- '-sEXPORT_NAME=MFEMDemoModule',
- '-sEXPORTED_FUNCTIONS=_main,_mfem_demo_solve,_mfem_demo_capabilities',
- '-sEXPORTED_RUNTIME_METHODS=ccall',
- '-sNO_EXIT_RUNTIME=1',
- '-sENVIRONMENT=web,node',
- ],
- install: false,
- )
-
- if get_option('build_tests')
- node_program = find_program('node', required: true)
- test(
- 'wasm-browser-api',
- node_program,
- args: [files('smoke.cjs'), wasm_browser_demo],
- depends: wasm_browser_demo,
- timeout: 120,
- )
- endif
-endif
diff --git a/web/smoke.cjs b/web/smoke.cjs
deleted file mode 100644
index 24672c8b..00000000
--- a/web/smoke.cjs
+++ /dev/null
@@ -1,26 +0,0 @@
-"use strict";
-
-const path = require("node:path");
-
-async function main() {
- const modulePath = path.resolve(process.argv[2]);
- const createModule = require(modulePath);
- const module = await createModule({ print() {}, printErr() {} });
- const capabilities = JSON.parse(
- module.ccall("mfem_demo_capabilities", "string", [], []),
- );
- const result = JSON.parse(
- module.ccall("mfem_demo_solve", "string", ["number", "number"], [6, 2]),
- );
- if (!capabilities.wasm || !result.ok || result.trueDofs <= 0 || result.iterations <= 0) {
- throw new Error(`Unexpected MFEM Wasm result: ${JSON.stringify(result)}`);
- }
- process.stdout.write(
- `MFEM ${capabilities.mfemVersion} Wasm solve: dofs=${result.trueDofs} iterations=${result.iterations} l2=${result.l2Error}\n`,
- );
-}
-
-main().catch((error) => {
- console.error(error);
- process.exitCode = 1;
-});
diff --git a/web/styles.css b/web/styles.css
deleted file mode 100644
index cfb429a7..00000000
--- a/web/styles.css
+++ /dev/null
@@ -1,506 +0,0 @@
-:root {
- color-scheme: dark;
- --bg: #071018;
- --surface: #0c1823;
- --surface-raised: #102230;
- --line: rgba(165, 214, 232, 0.16);
- --line-strong: rgba(165, 214, 232, 0.29);
- --text: #eef8fb;
- --muted: #91aab5;
- --cyan: #59c7ff;
- --mint: #7df9c5;
- --amber: #f5c86c;
- --danger: #ff8f8f;
- --radius: 18px;
- font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
-}
-
-* {
- box-sizing: border-box;
-}
-
-body {
- min-width: 320px;
- min-height: 100vh;
- margin: 0;
- color: var(--text);
- background:
- radial-gradient(circle at 18% -10%, rgba(48, 139, 173, 0.20), transparent 34rem),
- linear-gradient(150deg, #071018 0%, #09131d 48%, #060d14 100%);
-}
-
-button,
-input,
-select {
- font: inherit;
-}
-
-.masthead,
-footer {
- width: min(1420px, calc(100% - 48px));
- margin: 0 auto;
-}
-
-.masthead {
- min-height: 92px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- border-bottom: 1px solid var(--line);
-}
-
-.brand {
- display: inline-flex;
- gap: 12px;
- align-items: center;
- color: inherit;
- text-decoration: none;
-}
-
-.brand-mark {
- width: 42px;
- height: 42px;
- display: grid;
- place-items: center;
- color: #061019;
- background: var(--mint);
- border-radius: 11px;
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
- font-weight: 900;
- box-shadow: 0 0 30px rgba(125, 249, 197, 0.20);
-}
-
-.brand strong,
-.brand small {
- display: block;
-}
-
-.brand strong {
- letter-spacing: 0.16em;
- font-size: 0.88rem;
-}
-
-.brand small {
- margin-top: 2px;
- color: var(--muted);
- font-size: 0.76rem;
-}
-
-.runtime-state {
- display: flex;
- align-items: center;
- gap: 9px;
- color: var(--muted);
- font-size: 0.82rem;
-}
-
-.status-dot {
- width: 8px;
- height: 8px;
- border-radius: 50%;
- background: var(--amber);
- box-shadow: 0 0 14px currentColor;
-}
-
-.status-dot[data-state="ready"] {
- background: var(--mint);
-}
-
-.status-dot[data-state="working"] {
- background: var(--cyan);
- animation: pulse 1s ease-in-out infinite;
-}
-
-.status-dot[data-state="error"] {
- background: var(--danger);
-}
-
-.workspace {
- width: min(1420px, calc(100% - 48px));
- min-height: calc(100vh - 168px);
- margin: 0 auto;
- padding: 54px 0 44px;
- display: grid;
- grid-template-columns: minmax(300px, 0.72fr) minmax(560px, 1.28fr);
- gap: clamp(32px, 6vw, 92px);
- align-items: start;
-}
-
-.control-panel {
- padding-top: 12px;
-}
-
-.eyebrow {
- margin-bottom: 14px;
- color: var(--mint);
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
- font-size: 0.72rem;
- letter-spacing: 0.14em;
- text-transform: uppercase;
-}
-
-h1,
-h2,
-p {
- margin-top: 0;
-}
-
-h1 {
- max-width: 12ch;
- margin-bottom: 20px;
- font-size: clamp(2.5rem, 5vw, 4.8rem);
- font-weight: 560;
- letter-spacing: -0.055em;
- line-height: 0.99;
-}
-
-h2 {
- margin-bottom: 0;
- font-size: clamp(1.4rem, 2vw, 2rem);
- font-weight: 560;
- letter-spacing: -0.035em;
-}
-
-.lede {
- max-width: 56ch;
- margin-bottom: 34px;
- color: var(--muted);
- font-size: 1rem;
- line-height: 1.7;
-}
-
-form {
- display: grid;
- grid-template-columns: 1fr 0.72fr;
- gap: 18px;
-}
-
-.field-group {
- display: flex;
- flex-direction: column;
- gap: 11px;
-}
-
-.field-heading {
- display: flex;
- justify-content: space-between;
- align-items: baseline;
-}
-
-label,
-.field-heading output {
- font-size: 0.78rem;
- font-weight: 650;
- letter-spacing: 0.035em;
-}
-
-.field-heading output {
- color: var(--mint);
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
-}
-
-input[type="range"] {
- width: 100%;
- accent-color: var(--mint);
-}
-
-.range-labels {
- display: flex;
- justify-content: space-between;
- margin-top: -7px;
- color: #607b87;
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
- font-size: 0.64rem;
-}
-
-select {
- min-height: 46px;
- padding: 0 42px 0 14px;
- color: var(--text);
- background: var(--surface-raised);
- border: 1px solid var(--line-strong);
- border-radius: 10px;
-}
-
-button {
- grid-column: 1 / -1;
- min-height: 54px;
- margin-top: 5px;
- padding: 0 18px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- color: #061019;
- background: var(--mint);
- border: 0;
- border-radius: 11px;
- cursor: pointer;
- font-weight: 750;
- transition: transform 160ms ease, box-shadow 160ms ease, opacity 160ms ease;
-}
-
-button:hover:not(:disabled) {
- transform: translateY(-2px);
- box-shadow: 0 12px 32px rgba(125, 249, 197, 0.18);
-}
-
-button:focus-visible,
-select:focus-visible,
-input:focus-visible,
-summary:focus-visible {
- outline: 2px solid var(--cyan);
- outline-offset: 3px;
-}
-
-button:disabled {
- cursor: wait;
- opacity: 0.46;
-}
-
-.capabilities {
- margin-top: 24px;
- display: flex;
- align-items: center;
- gap: 12px;
- flex-wrap: wrap;
-}
-
-.capability-label {
- color: var(--muted);
- font-size: 0.7rem;
- text-transform: uppercase;
- letter-spacing: 0.12em;
-}
-
-#capability-list {
- display: flex;
- gap: 7px;
- flex-wrap: wrap;
-}
-
-.capability {
- padding: 5px 8px;
- border: 1px solid var(--line);
- border-radius: 999px;
- color: var(--muted);
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
- font-size: 0.65rem;
-}
-
-.capability.enabled {
- color: var(--mint);
- border-color: rgba(125, 249, 197, 0.27);
- background: rgba(125, 249, 197, 0.06);
-}
-
-.result-panel {
- overflow: hidden;
- background: rgba(12, 24, 35, 0.84);
- border: 1px solid var(--line);
- border-radius: var(--radius);
- box-shadow: 0 32px 90px rgba(0, 0, 0, 0.25);
- backdrop-filter: blur(14px);
-}
-
-.result-heading {
- padding: 24px 26px 21px;
- display: flex;
- align-items: flex-end;
- justify-content: space-between;
- border-bottom: 1px solid var(--line);
-}
-
-.result-heading .eyebrow {
- margin-bottom: 7px;
-}
-
-.run-index {
- color: var(--muted);
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
- font-size: 0.72rem;
-}
-
-.mesh-frame {
- position: relative;
- min-height: 410px;
- background:
- linear-gradient(rgba(255, 255, 255, 0.012) 1px, transparent 1px),
- linear-gradient(90deg, rgba(255, 255, 255, 0.012) 1px, transparent 1px),
- #09131c;
- background-size: 24px 24px;
-}
-
-#mesh-canvas {
- width: 100%;
- height: 410px;
- display: block;
-}
-
-.equation {
- position: absolute;
- top: 18px;
- right: 18px;
- padding: 10px 12px;
- color: var(--text);
- background: rgba(7, 16, 24, 0.78);
- border: 1px solid var(--line);
- border-radius: 9px;
- font-family: ui-serif, Georgia, serif;
- text-align: center;
-}
-
-.equation span,
-.equation small {
- display: block;
-}
-
-.equation small {
- margin-top: 3px;
- color: var(--muted);
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
- font-size: 0.57rem;
-}
-
-.metrics {
- display: grid;
- grid-template-columns: repeat(4, 1fr);
- border-top: 1px solid var(--line);
- border-bottom: 1px solid var(--line);
-}
-
-.metrics article {
- min-width: 0;
- padding: 18px;
- border-right: 1px solid var(--line);
-}
-
-.metrics article:last-child {
- border-right: 0;
-}
-
-.metrics span,
-.metrics strong {
- display: block;
-}
-
-.metrics span {
- margin-bottom: 8px;
- color: var(--muted);
- font-size: 0.68rem;
- letter-spacing: 0.055em;
- text-transform: uppercase;
-}
-
-.metrics strong {
- overflow: hidden;
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
- font-size: clamp(0.92rem, 1.4vw, 1.15rem);
- text-overflow: ellipsis;
-}
-
-.console-panel {
- padding: 16px 24px 20px;
-}
-
-.console-panel summary {
- color: var(--muted);
- cursor: pointer;
- font-size: 0.75rem;
-}
-
-.console-panel pre {
- max-height: 150px;
- overflow: auto;
- margin: 14px 0 0;
- color: #a9c7d2;
- font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
- font-size: 0.68rem;
- line-height: 1.6;
- white-space: pre-wrap;
-}
-
-footer {
- min-height: 76px;
- display: flex;
- align-items: center;
- justify-content: space-between;
- color: #67808b;
- border-top: 1px solid var(--line);
- font-size: 0.7rem;
-}
-
-@keyframes pulse {
- 0%, 100% { opacity: 0.55; transform: scale(0.9); }
- 50% { opacity: 1; transform: scale(1.2); }
-}
-
-@media (max-width: 960px) {
- .workspace {
- grid-template-columns: 1fr;
- gap: 42px;
- }
-
- h1 {
- max-width: 15ch;
- }
-}
-
-@media (max-width: 620px) {
- .masthead,
- .workspace,
- footer {
- width: min(100% - 28px, 1420px);
- }
-
- .masthead {
- min-height: 78px;
- }
-
- .runtime-state {
- max-width: 145px;
- justify-content: flex-end;
- text-align: right;
- }
-
- .workspace {
- padding-top: 32px;
- }
-
- h1 {
- font-size: clamp(2.35rem, 13vw, 3.4rem);
- }
-
- form,
- .metrics {
- grid-template-columns: 1fr 1fr;
- }
-
- .metrics article:nth-child(2) {
- border-right: 0;
- }
-
- .metrics article:nth-child(-n + 2) {
- border-bottom: 1px solid var(--line);
- }
-
- .mesh-frame,
- #mesh-canvas {
- min-height: 320px;
- height: 320px;
- }
-
- footer {
- gap: 18px;
- }
-}
-
-@media (prefers-reduced-motion: reduce) {
- *,
- *::before,
- *::after {
- scroll-behavior: auto !important;
- animation-duration: 0.01ms !important;
- animation-iteration-count: 1 !important;
- transition-duration: 0.01ms !important;
- }
-}
diff --git a/web/wasm_demo.cpp b/web/wasm_demo.cpp
deleted file mode 100644
index 0faecd3b..00000000
--- a/web/wasm_demo.cpp
+++ /dev/null
@@ -1,173 +0,0 @@
-#include
-#include
-
-#include
-
-#include
-#include
-#include
-#include
-#include
-
-namespace
-{
-constexpr double pi = 3.141592653589793238462643383279502884;
-
-double exact_solution(const mfem::Vector &point)
-{
- return std::sin(pi * point[0]) * std::sin(pi * point[1]);
-}
-
-double forcing_function(const mfem::Vector &point)
-{
- return 2.0 * pi * pi * exact_solution(point);
-}
-
-std::string json_escape(const std::string &input)
-{
- std::ostringstream output;
- for (const unsigned char character : input)
- {
- switch (character)
- {
- case '"': output << "\\\""; break;
- case '\\': output << "\\\\"; break;
- case '\b': output << "\\b"; break;
- case '\f': output << "\\f"; break;
- case '\n': output << "\\n"; break;
- case '\r': output << "\\r"; break;
- case '\t': output << "\\t"; break;
- default:
- if (character < 0x20)
- {
- output << "\\u" << std::hex << std::setw(4)
- << std::setfill('0') << static_cast(character)
- << std::dec;
- }
- else
- {
- output << character;
- }
- }
- }
- return output.str();
-}
-
-const char *error_response(const std::string &message)
-{
- static std::string response;
- response = "{\"ok\":false,\"error\":\"" + json_escape(message) + "\"}";
- return response.c_str();
-}
-} // namespace
-
-extern "C" EMSCRIPTEN_KEEPALIVE const char *mfem_demo_capabilities()
-{
- static const std::string capabilities = []() {
- std::ostringstream output;
- output << "{\"mfemVersion\":\"" << json_escape(MFEM_VERSION_STRING)
- << "\",\"mpi\":" << (MESON_MFEM_HAS_MPI ? "true" : "false")
- << ",\"hypre\":" << (MESON_MFEM_HAS_HYPRE ? "true" : "false")
- << ",\"zlib\":" << (MESON_MFEM_HAS_ZLIB ? "true" : "false")
- << ",\"ceed\":" << (MESON_MFEM_HAS_CEED ? "true" : "false")
- << ",\"fms\":" << (MESON_MFEM_HAS_FMS ? "true" : "false")
- << ",\"wasm\":true}";
- return output.str();
- }();
- return capabilities.c_str();
-}
-
-extern "C" EMSCRIPTEN_KEEPALIVE const char *mfem_demo_solve(int cells, int order)
-{
- static std::string response;
- try
- {
- if (cells < 2 || cells > 64)
- {
- throw std::invalid_argument("cells must be between 2 and 64");
- }
- if (order < 1 || order > 4)
- {
- throw std::invalid_argument("polynomial order must be between 1 and 4");
- }
-
- static mfem::Device device("cpu");
- (void)device;
- mfem::StopWatch timer;
- timer.Start();
-
- mfem::Mesh mesh = mfem::Mesh::MakeCartesian2D(
- cells, cells, mfem::Element::QUADRILATERAL, true, 1.0, 1.0);
- mfem::H1_FECollection elements(order, mesh.Dimension());
- mfem::FiniteElementSpace space(&mesh, &elements);
-
- mfem::Array essential_boundary(mesh.bdr_attributes.Max());
- essential_boundary = 1;
- mfem::Array essential_dofs;
- space.GetEssentialTrueDofs(essential_boundary, essential_dofs);
-
- mfem::FunctionCoefficient exact(exact_solution);
- mfem::FunctionCoefficient forcing(forcing_function);
- mfem::LinearForm rhs(&space);
- rhs.AddDomainIntegrator(new mfem::DomainLFIntegrator(forcing));
- rhs.Assemble();
-
- mfem::GridFunction solution(&space);
- solution = 0.0;
- solution.ProjectBdrCoefficient(exact, essential_boundary);
-
- mfem::BilinearForm diffusion(&space);
- mfem::ConstantCoefficient one(1.0);
- diffusion.AddDomainIntegrator(new mfem::DiffusionIntegrator(one));
- diffusion.Assemble();
-
- mfem::OperatorPtr matrix;
- mfem::Vector linear_rhs;
- mfem::Vector linear_solution;
- diffusion.FormLinearSystem(
- essential_dofs, solution, rhs, matrix, linear_solution, linear_rhs);
-
- mfem::GSSmoother smoother(static_cast(*matrix));
- mfem::CGSolver solver;
- solver.SetOperator(*matrix);
- solver.SetPreconditioner(smoother);
- solver.SetRelTol(1e-10);
- solver.SetAbsTol(0.0);
- solver.SetMaxIter(400);
- solver.SetPrintLevel(0);
- solver.Mult(linear_rhs, linear_solution);
- diffusion.RecoverFEMSolution(linear_solution, rhs, solution);
-
- timer.Stop();
- const double l2_error = solution.ComputeL2Error(exact);
- if (!solver.GetConverged() || !std::isfinite(l2_error))
- {
- throw std::runtime_error("MFEM conjugate-gradient solve did not converge");
- }
-
- std::ostringstream output;
- output << std::setprecision(10)
- << "{\"ok\":true"
- << ",\"mfemVersion\":\"" << json_escape(MFEM_VERSION_STRING) << '"'
- << ",\"cells\":" << cells
- << ",\"elements\":" << mesh.GetNE()
- << ",\"order\":" << order
- << ",\"trueDofs\":" << space.GetTrueVSize()
- << ",\"iterations\":" << solver.GetNumIterations()
- << ",\"finalNorm\":" << solver.GetFinalNorm()
- << ",\"l2Error\":" << l2_error
- << ",\"elapsedSeconds\":" << timer.RealTime()
- << '}';
- response = output.str();
- return response.c_str();
- }
- catch (const std::exception &error)
- {
- return error_response(error.what());
- }
-}
-
-int main()
-{
- return 0;
-}