feat(justfile): added justfile with catalog of builds

This commit is contained in:
2026-09-05 07:46:36 -04:00
parent 6a54f2625e
commit 7282b2e134
8 changed files with 1167 additions and 0 deletions

195
web/demo.js Normal file
View File

@@ -0,0 +1,195 @@
"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();

116
web/index.html Normal file
View File

@@ -0,0 +1,116 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Run a finite-element Poisson solve with MFEM compiled to WebAssembly.">
<title>MFEM WebAssembly Lab</title>
<link rel="stylesheet" href="./styles.css">
<script src="./mfem-wasm-demo.js" defer></script>
<script src="./demo.js" defer></script>
</head>
<body>
<header class="masthead">
<a class="brand" href="#workspace" aria-label="MFEM WebAssembly Lab home">
<span class="brand-mark" aria-hidden="true">M</span>
<span>
<strong>MFEM</strong>
<small>WebAssembly Lab</small>
</span>
</a>
<div class="runtime-state" role="status" aria-live="polite">
<span class="status-dot" id="status-dot"></span>
<span id="runtime-status">Loading numerical runtime</span>
</div>
</header>
<main id="workspace" class="workspace">
<section class="control-panel" aria-labelledby="problem-title">
<div class="eyebrow">Poisson equation · unit square</div>
<h1 id="problem-title">Solve a finite-element system in your browser.</h1>
<p class="lede">
MFEM assembles and solves the system locally through WebAssembly.
Change the mesh or polynomial order and compare the resulting error and workload.
</p>
<form id="solve-form">
<div class="field-group">
<div class="field-heading">
<label for="cells">Cells per axis</label>
<output id="cells-value" for="cells">10</output>
</div>
<input id="cells" name="cells" type="range" min="4" max="40" step="2" value="10">
<div class="range-labels" aria-hidden="true"><span>4</span><span>40</span></div>
</div>
<div class="field-group">
<label for="order">Polynomial order</label>
<select id="order" name="order">
<option value="1">1 · Linear</option>
<option value="2" selected>2 · Quadratic</option>
<option value="3">3 · Cubic</option>
<option value="4">4 · Quartic</option>
</select>
</div>
<button id="solve-button" type="submit" disabled>
<span>Run MFEM solve</span>
<span aria-hidden="true"></span>
</button>
</form>
<div class="capabilities" aria-label="Compiled capabilities">
<span class="capability-label">Runtime</span>
<div id="capability-list"></div>
</div>
</section>
<section class="result-panel" aria-labelledby="result-title">
<div class="result-heading">
<div>
<div class="eyebrow">Computed result</div>
<h2 id="result-title">Finite-element mesh</h2>
</div>
<span class="run-index" id="run-index">Awaiting run</span>
</div>
<div class="mesh-frame">
<canvas id="mesh-canvas" width="720" height="500" aria-label="Structured finite-element mesh preview"></canvas>
<div class="equation" aria-label="Negative Laplacian of u equals f">
<span>Δu = f</span>
<small>u = 0 on ∂Ω</small>
</div>
</div>
<div class="metrics" aria-live="polite">
<article>
<span>True DOFs</span>
<strong id="metric-dofs"></strong>
</article>
<article>
<span>CG iterations</span>
<strong id="metric-iterations"></strong>
</article>
<article>
<span>L² error</span>
<strong id="metric-error"></strong>
</article>
<article>
<span>Browser time</span>
<strong id="metric-time"></strong>
</article>
</div>
<details class="console-panel">
<summary>Runtime details</summary>
<pre id="runtime-log">Waiting for WebAssembly initialization…</pre>
</details>
</section>
</main>
<footer>
<span>All computation stays in this browser tab.</span>
<span id="footer-version">MFEM WebAssembly</span>
</footer>
</body>
</html>

34
web/meson.build Normal file
View File

@@ -0,0 +1,34 @@
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

26
web/smoke.cjs Normal file
View File

@@ -0,0 +1,26 @@
"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;
});

506
web/styles.css Normal file
View File

@@ -0,0 +1,506 @@
: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;
}
}

173
web/wasm_demo.cpp Normal file
View File

@@ -0,0 +1,173 @@
#include <meson_mfem_template/config.hpp>
#include <mfem.hpp>
#include <emscripten/emscripten.h>
#include <cmath>
#include <iomanip>
#include <sstream>
#include <stdexcept>
#include <string>
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<int>(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<int> essential_boundary(mesh.bdr_attributes.Max());
essential_boundary = 1;
mfem::Array<int> 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<mfem::SparseMatrix &>(*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;
}