Skip to content

Commit d541ee2

Browse files
committed
Plot the creeping flow and beam results with the named fields
Use result.fields and the plotSolution field option in the creeping flow (main thread and worker) and cantilever beam tutorials, instead of slicing the solution vector and reading the private _...Metadata properties. The beam tutorial plots the deflection with plotSolution and adds the analytical solution with Plotly.addTraces.
1 parent 5b94c10 commit d541ee2

3 files changed

Lines changed: 38 additions & 131 deletions

File tree

‎tutorials/cantilever-beam-1d.html‎

Lines changed: 18 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,7 @@ <h1>Cantilever Beam Under <span class="accent">a Tip Load Tutorial</span></h1>
270270
&lt;script type="module"&gt;
271271
import {
272272
FEAScriptModel,
273+
plotSolution,
273274
printVersion,
274275
} from "https://core.feascript.com/dist/feascript.esm.js";
275276

@@ -305,9 +306,8 @@ <h1>Cantilever Beam Under <span class="accent">a Tip Load Tutorial</span></h1>
305306
model.setSolverMethod("lusolve");
306307
const result = model.solve();
307308

308-
// 5. Extract the deflection (the solution has two values per node: [w, theta])
309-
const femDeflection = result.solutionVector.filter((_, i) =&gt; i % 2 === 0);
310-
const femNodesX = result.nodesCoordinates.nodesXCoordinates;
309+
// 5. Plot the deflection
310+
plotSolution(model, result, "line", "resultsCanvas");
311311
});
312312
&lt;/script&gt;
313313
&lt;!-- ...rest of body region... --&gt;
@@ -320,8 +320,10 @@ <h1>Cantilever Beam Under <span class="accent">a Tip Load Tutorial</span></h1>
320320
clamped support at node 1 fixes both the deflection and the rotation (\(w=0\), \(\theta=0\)).
321321
</p>
322322
<p>
323-
Since the beam solver packs \(w\) and \(\theta\) together at each node, the deflection field is
324-
extracted from the solution vector by taking every other entry.
323+
The beam solver has two unknowns at each node, the deflection \(w\) and the rotation
324+
\(\theta\). The result of <code>solve()</code> contains both as the named fields
325+
<code>w</code> and <code>theta</code>. <code>plotSolution</code> plots the deflection by default,
326+
and the rotation with the <code>{ field: "theta" }</code> option.
325327
</p>
326328

327329
<div class="eyebrow label" id="results" role="heading" aria-level="2">
@@ -349,9 +351,13 @@ <h1>Cantilever Beam Under <span class="accent">a Tip Load Tutorial</span></h1>
349351

350352
<script type="module">
351353
// Import FEAScript library from GitHub
352-
import { FEAScriptModel, printVersion } from "https://core.feascript.com/dist/feascript.esm.js";
354+
import {
355+
FEAScriptModel,
356+
plotSolution,
357+
printVersion,
358+
} from "https://core.feascript.com/dist/feascript.esm.js";
353359
// Import FEAScript library from a local directory
354-
// import { FEAScriptModel, printVersion } from "../../FEAScript-core/src/index.js";
360+
// import { FEAScriptModel, plotSolution, printVersion } from "../../FEAScript-core/src/index.js";
355361

356362
window.addEventListener("DOMContentLoaded", () => {
357363
// Print FEAScript version
@@ -390,12 +396,8 @@ <h1>Cantilever Beam Under <span class="accent">a Tip Load Tutorial</span></h1>
390396
// Solve the problem and get the solution
391397
const result = model.solve();
392398

393-
// Extract the deflection field from the solution vector
394-
// DOF layout per node: [w, theta]
395-
const totalNodesX = model._eulerBernoulliBeamMetadata.totalNodesX;
396-
const solutionVector = result.solutionVector;
397-
const femDeflection = Array.from({ length: totalNodesX }, (_, i) => solutionVector[2 * i]);
398-
const femNodesX = result.nodesCoordinates.nodesXCoordinates;
399+
// Plot the deflection
400+
plotSolution(model, result, "line", "resultsCanvas");
399401

400402
// Closed-form analytical solution w(x) = P/(6EI) * (3*L*x^2 - x^3)
401403
const numAnalyticalPoints = 100;
@@ -407,36 +409,15 @@ <h1>Cantilever Beam Under <span class="accent">a Tip Load Tutorial</span></h1>
407409
(x) => (tipLoad / (6 * bendingStiffness)) * (3 * beamLength * x ** 2 - x ** 3),
408410
);
409411

410-
// Plot the FEAScript solution against the analytical solution
411-
const femTrace = {
412-
x: femNodesX,
413-
y: femDeflection,
414-
mode: "markers",
415-
type: "scatter",
416-
marker: { color: "rgb(219, 64, 82)", size: 8 },
417-
name: "FEAScript (FEM)",
418-
};
419-
const analyticalTrace = {
412+
// Add the analytical solution to the plot for comparison
413+
Plotly.addTraces("resultsCanvas", {
420414
x: analyticalNodesX,
421415
y: analyticalDeflection,
422416
mode: "lines",
423417
type: "scatter",
424418
line: { color: "rgb(31, 119, 180)", width: 2, dash: "dash" },
425419
name: "Analytical solution",
426-
};
427-
428-
const maxWindowWidth = Math.min(window.innerWidth, 700);
429-
const plotWidth = Math.min(maxWindowWidth, 600);
430-
const layout = {
431-
title: "Cantilever beam deflection",
432-
width: plotWidth,
433-
height: 350,
434-
xaxis: { title: "x (m)" },
435-
yaxis: { title: "Deflection w (m)" },
436-
margin: { l: 70, r: 50, t: 50, b: 50 },
437-
};
438-
439-
Plotly.newPlot("resultsCanvas", [femTrace, analyticalTrace], layout, { responsive: true });
420+
});
440421
});
441422
</script>
442423

‎tutorials/lid-driven-cavity-2d-creeping-flow-worker.html‎

Lines changed: 7 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -247,18 +247,11 @@ <h1>
247247
await model.setSolverMethod("lusolve");
248248
const result = await model.solve();
249249

250-
// 5. Extract and plot the velocity
251-
// The solution holds [u, v] at every node, followed by the pressure at the corner nodes
252-
const { solutionVector, nodesCoordinates } = result;
253-
const totalNodes = nodesCoordinates.nodesXCoordinates.length;
254-
const uVelocity = solutionVector.slice(0, totalNodes);
255-
const vVelocity = solutionVector.slice(totalNodes, 2 * totalNodes);
256-
const velocityMagnitude = uVelocity.map((u, i) =&gt; Math.hypot(u, vVelocity[i]));
257-
250+
// 5. Plot the results
258251
const modelInfo = await model.getModelInfo();
259-
plotSolution(modelInfo, { solutionVector: velocityMagnitude, nodesCoordinates }, "contour", "velocityMagnitudeCanvas");
260-
plotSolution(modelInfo, { solutionVector: uVelocity, nodesCoordinates }, "contour", "uVelocityCanvas");
261-
plotSolution(modelInfo, { solutionVector: vVelocity, nodesCoordinates }, "contour", "vVelocityCanvas");
252+
plotSolution(modelInfo, result, "contour", "velocityMagnitudeCanvas");
253+
plotSolution(modelInfo, result, "contour", "uVelocityCanvas", { field: "u" });
254+
plotSolution(modelInfo, result, "contour", "vVelocityCanvas", { field: "v" });
262255

263256
// 6. Terminate the worker
264257
model.terminate();
@@ -358,46 +351,16 @@ <h1>
358351
// Solve the problem and get the solution
359352
const result = await model.solve();
360353

361-
// Calculate totalNodesVelocity manually as it is not exposed by worker getModelInfo
362-
const nodesX = 2 * numElementsX + 1;
363-
const nodesY = 2 * numElementsY + 1;
364-
const totalNodesVelocity = nodesX * nodesY;
365-
366-
// Extract velocity and pressure fields from the solution vector
367-
const solutionVector = result.solutionVector;
368-
const uVelocity = solutionVector.slice(0, totalNodesVelocity);
369-
const vVelocity = solutionVector.slice(totalNodesVelocity, 2 * totalNodesVelocity);
370-
const velocityMagnitude = uVelocity.map((u, i) =>
371-
Math.sqrt(u * u + vVelocity[i] * vVelocity[i]),
372-
);
373-
374354
// Get model info for plotting
375355
const modelInfo = await model.getModelInfo();
376356

377357
// Hide loading indicator
378358
document.getElementById("loading").style.display = "none";
379359

380360
// Plot results
381-
plotSolution(
382-
modelInfo,
383-
{ solutionVector: velocityMagnitude, nodesCoordinates: result.nodesCoordinates },
384-
"contour",
385-
"velocityMagnitudeCanvas",
386-
);
387-
388-
plotSolution(
389-
modelInfo,
390-
{ solutionVector: uVelocity, nodesCoordinates: result.nodesCoordinates },
391-
"contour",
392-
"uVelocityCanvas",
393-
);
394-
395-
plotSolution(
396-
modelInfo,
397-
{ solutionVector: vVelocity, nodesCoordinates: result.nodesCoordinates },
398-
"contour",
399-
"vVelocityCanvas",
400-
);
361+
plotSolution(modelInfo, result, "contour", "velocityMagnitudeCanvas");
362+
plotSolution(modelInfo, result, "contour", "uVelocityCanvas", { field: "u" });
363+
plotSolution(modelInfo, result, "contour", "vVelocityCanvas", { field: "v" });
401364

402365
// Terminate the worker
403366
model.terminate();

‎tutorials/lid-driven-cavity-2d-creeping-flow.html‎

Lines changed: 13 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -282,17 +282,10 @@ <h1>Creeping Flow in a <span class="accent">2D Lid-Driven Cavity Tutorial</span>
282282
model.setSolverMethod("lusolve");
283283
const result = model.solve();
284284

285-
// 5. Extract and plot the velocity
286-
// The solution holds [u, v] at every node, followed by the pressure at the corner nodes
287-
const { solutionVector, nodesCoordinates } = result;
288-
const totalNodes = nodesCoordinates.nodesXCoordinates.length;
289-
const uVelocity = solutionVector.slice(0, totalNodes);
290-
const vVelocity = solutionVector.slice(totalNodes, 2 * totalNodes);
291-
const velocityMagnitude = uVelocity.map((u, i) =&gt; Math.hypot(u, vVelocity[i]));
292-
293-
plotSolution(model, { solutionVector: velocityMagnitude, nodesCoordinates }, "contour", "velocityMagnitudeCanvas");
294-
plotSolution(model, { solutionVector: uVelocity, nodesCoordinates }, "contour", "uVelocityCanvas");
295-
plotSolution(model, { solutionVector: vVelocity, nodesCoordinates }, "contour", "vVelocityCanvas");
285+
// 5. Plot the results
286+
plotSolution(model, result, "contour", "velocityMagnitudeCanvas");
287+
plotSolution(model, result, "contour", "uVelocityCanvas", { field: "u" });
288+
plotSolution(model, result, "contour", "vVelocityCanvas", { field: "v" });
296289
});
297290
&lt;/script&gt;
298291
&lt;!-- ...rest of body region... --&gt;
@@ -306,12 +299,11 @@ <h1>Creeping Flow in a <span class="accent">2D Lid-Driven Cavity Tutorial</span>
306299
the boundary nodes (the second and third arguments respectively).
307300
</p>
308301
<p>
309-
Since the Stokes solver uses a mixed formulation, the solution vector contains all DOFs packed
310-
sequentially: \([u_0, \ldots, u_{N_2-1}, \, v_0, \ldots, v_{N_2-1}, \, p_0, \ldots,
311-
p_{N_1-1}]\), where \(N_2\) is the number of velocity nodes (Q2) and \(N_1\) is the number of
312-
pressure nodes (Q1). The velocity components are extracted by slicing the solution vector and
313-
each field can then be plotted individually using the existing
314-
<code>plotSolution</code> function.
302+
Since the Stokes solver uses a mixed formulation, the result of <code>solve()</code> also
303+
contains the named fields <code>velocityMagnitude</code>, <code>u</code>, <code>v</code> and
304+
<code>p</code>, each with one value per mesh node (the pressure, solved at the corner nodes, is
305+
interpolated linearly to the remaining nodes). <code>plotSolution</code> plots the velocity
306+
magnitude by default, and any other field with the <code>field</code> option.
315307
</p>
316308
<p>
317309
After solving the case, the results are shown in 2D contour plots. To visualize them, include
@@ -400,39 +392,10 @@ <h1>Creeping Flow in a <span class="accent">2D Lid-Driven Cavity Tutorial</span>
400392
// Solve the problem and get the solution
401393
const result = model.solve();
402394

403-
// Extract velocity and pressure fields from the solution vector
404-
// DOF layout: [u₀…u_{N₂−1}, v₀…v_{N₂−1}, p₀…p_{N₁−1}]
405-
const totalNodesVelocity = model._creepingFlowMetadata.totalNodesVelocity;
406-
const solutionVector = result.solutionVector;
407-
const uVelocity = solutionVector.slice(0, totalNodesVelocity);
408-
const vVelocity = solutionVector.slice(totalNodesVelocity, 2 * totalNodesVelocity);
409-
const velocityMagnitude = uVelocity.map((u, i) =>
410-
Math.sqrt(u * u + vVelocity[i] * vVelocity[i]),
411-
);
412-
413-
// Plot velocity magnitude as a 2D contour plot
414-
plotSolution(
415-
model,
416-
{ solutionVector: velocityMagnitude, nodesCoordinates: result.nodesCoordinates },
417-
"contour",
418-
"velocityMagnitudeCanvas",
419-
);
420-
421-
// Plot u-velocity component as a 2D contour plot
422-
plotSolution(
423-
model,
424-
{ solutionVector: uVelocity, nodesCoordinates: result.nodesCoordinates },
425-
"contour",
426-
"uVelocityCanvas",
427-
);
428-
429-
// Plot v-velocity component as a 2D contour plot
430-
plotSolution(
431-
model,
432-
{ solutionVector: vVelocity, nodesCoordinates: result.nodesCoordinates },
433-
"contour",
434-
"vVelocityCanvas",
435-
);
395+
// Plot the velocity magnitude and the velocity components as 2D contour plots
396+
plotSolution(model, result, "contour", "velocityMagnitudeCanvas");
397+
plotSolution(model, result, "contour", "uVelocityCanvas", { field: "u" });
398+
plotSolution(model, result, "contour", "vVelocityCanvas", { field: "v" });
436399
});
437400
</script>
438401
</div>

0 commit comments

Comments
 (0)