Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dist/feascript-worker.esm.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/feascript-worker.esm.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/feascript.cjs.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/feascript.cjs.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/feascript.esm.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/feascript.esm.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/feascript.umd.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/feascript.umd.js.map

Large diffs are not rendered by default.

74 changes: 74 additions & 0 deletions src/workers/functionSerialization.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* ════════════════════════════════════════════════════════════════
* FEAScript Core Library
* Finite Element Simulation in JavaScript
* Version: 0.3.0 (RC) | https://feascript.com
* MIT License © 2023–2026 FEAScript
* ════════════════════════════════════════════════════════════════
*/

// Functions cannot be sent to a web worker (structured clone), so they are sent as their source code and
// rebuilt in the worker. A rebuilt function has no access to the variables around its definition, so it
// must be self-contained (e.g. `(x, u) => 0.5 * u ** 2` instead of `(x, u) => Da * u ** 2`)
const functionSourceKey = "__feascriptFunctionSource";

/**
* Function to replace the functions of a value (e.g. the model options) with their source code
* @param {*} value - The value to serialize; plain objects and arrays are serialized recursively
* @returns {*} A copy of the value that can be sent to a web worker
* @throws Will throw an error if a function has no source code (native or bound functions)
*/
export function serializeFunctions(value) {
if (typeof value === "function") {
const source = value.toString();
if (source.includes("[native code]")) {
throw new Error(
`FEAScriptWorker: function "${value.name}" cannot be sent to the worker; use a function defined in your own code, e.g. (x) => Math.sin(x) instead of Math.sin.`,
);
}
return { [functionSourceKey]: source };
}
if (Array.isArray(value)) {
return value.map(serializeFunctions);
}
if (value !== null && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, serializeFunctions(entry)]));
}
return value;
}

/**
* Function to rebuild the functions that serializeFunctions replaced with their source code
* @param {*} value - The serialized value
* @returns {*} A copy of the value with the functions rebuilt
*/
export function deserializeFunctions(value) {
if (Array.isArray(value)) {
return value.map(deserializeFunctions);
}
if (value !== null && typeof value === "object") {
if (typeof value[functionSourceKey] === "string") {
return rebuildFunction(value[functionSourceKey]);
}
return Object.fromEntries(
Object.entries(value).map(([key, entry]) => [key, deserializeFunctions(entry)]),
);
}
return value;
}

/**
* Function to rebuild a function from its source code
* @param {string} source - The source code of an arrow function, function expression or method
* @returns {function} The rebuilt function
*/
function rebuildFunction(source) {
try {
// Arrow functions and function expressions, e.g. "(x) => 1" or "function (x) { return 1; }"
return new Function(`"use strict"; return (${source});`)();
} catch (error) {
if (!(error instanceof SyntaxError)) throw error;
// Methods are given without the function keyword, e.g. "A(x) { return 1; }"
return new Function(`"use strict"; return Object.values({ ${source} })[0];`)();
}
}
14 changes: 10 additions & 4 deletions src/workers/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
// External imports
import * as Comlink from "../vendor/comlink.mjs";

// Internal imports
import { serializeFunctions } from "./functionSerialization.js";

/**
* Class to facilitate communication with web workers for FEAScript operations
*/
Expand Down Expand Up @@ -93,11 +96,13 @@ export class FEAScriptWorker {
/**
* Function to set the model configuration in the worker
* @param {string} modelConfig - The model configuration to set
* @param {object} [options] - Optional additional configuration, as in FEAScriptModel.setModelConfig. The
* coefficient functions are sent to the worker as source code, so they must be self-contained (no outer variables)
* @returns {Promise<boolean>} Resolves when the configuration is set
*/
async setModelConfig(modelConfig) {
async setModelConfig(modelConfig, options = {}) {
await this._ensureReady();
return this.feaWorker.setModelConfig(modelConfig);
return this.feaWorker.setModelConfig(modelConfig, serializeFunctions(options));
}

/**
Expand Down Expand Up @@ -133,11 +138,12 @@ export class FEAScriptWorker {

/**
* Function to request the worker to solve the problem
* @param {object} [options] - Additional parameters for the solver, such as `maxIterations` and `tolerance`
* @returns {Promise<object>} Resolves with the solution result
*/
async solve() {
async solve(options = {}) {
await this._ensureReady();
return this.feaWorker.solve();
return this.feaWorker.solve(options);
}

/**
Expand Down
11 changes: 7 additions & 4 deletions src/workers/wrapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import * as Comlink from "../vendor/comlink.mjs";

// Internal imports
import { FEAScriptModel } from "../FEAScript.js";
import { deserializeFunctions } from "./functionSerialization.js";

/**
* Class to wrap the FEAScriptModel for use in a web worker context
Expand All @@ -34,12 +35,13 @@ class workerWrapper {
/**
* Function to set the model configuration in the FEAScriptModel
* @param {string} modelConfig - The model configuration to set
* @param {object} [options] - Optional additional configuration, with the functions serialized by serializeFunctions
* @returns {boolean} Returns true if the configuration is set successfully
* @throws Will throw an error if the configuration fails to set
*/
setModelConfig(modelConfig) {
setModelConfig(modelConfig, options = {}) {
try {
this.model.setModelConfig(modelConfig);
this.model.setModelConfig(modelConfig, deserializeFunctions(options));
return true;
} catch (error) {
console.error("FEA Worker: Error in setModelConfig", error);
Expand Down Expand Up @@ -98,12 +100,13 @@ class workerWrapper {

/**
* Function to solve the problem using the FEAScriptModel
* @param {object} [options] - Additional parameters for the solver, such as `maxIterations` and `tolerance`
* @returns {object} Returns the solution result, including the solution vector, node coordinates, solver configuration, and mesh dimension
* @throws Will throw an error if the solve operation fails
*/
solve() {
solve(options = {}) {
try {
const result = this.model.solve();
const result = this.model.solve(options);

return {
solutionVector: result.solutionVector,
Expand Down
6 changes: 3 additions & 3 deletions tests/meshes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ like the sides of FEAScript structured meshes.

## Used by

- [`regression/HeatConduction2DFin`](../regression/HeatConduction2DFin/README.md)
- [`regression/SolidificationFront2D`](../regression/SolidificationFront2D/README.md)
- [`regression/LidDrivenCavity2DCreepingFlow`](../regression/LidDrivenCavity2DCreepingFlow/README.md)
- [`regression/heatConduction2DFin`](../regression/heatConduction2DFin/README.md)
- [`regression/solidificationFront2D`](../regression/solidificationFront2D/README.md)
- [`regression/lidDrivenCavity2DCreepingFlow`](../regression/lidDrivenCavity2DCreepingFlow/README.md)
- [`verification/analytical/creepingFlow2DCouette`](../verification/analytical/creepingFlow2DCouette/README.md)
- [`verification/analytical/frontPropagation2DPlanarFront`](../verification/analytical/frontPropagation2DPlanarFront/README.md)
- [`unit/plotInterpolation.test.js`](../unit/plotInterpolation.test.js)
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Every solver method must give the same values. Tolerance used in the assertions:
From the repository root:

```bash
node tests/regression/AdvectionDiffusion1D/regression.test.js
node tests/regression/advectionDiffusion1D/regression.test.js
```

The `test` script in `package.json` also runs this file, so `npm test` works too.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* and every applicable solver method (lusolve, jacobi, frontal), and asserts the solution at x=0.5 and x=1
* against known good values
*
* Run: node tests/regression/AdvectionDiffusion1D/regression.test.js (or npm test)
* Run: node tests/regression/advectionDiffusion1D/regression.test.js (or npm test)
*/

import { FEAScriptModel } from "../../../src/FEAScript.js";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Tolerance used for the equilibrium assertions: `1e-6`.
From the repository root:

```bash
node tests/regression/EulerBernoulliBeam/regression.test.js
node tests/regression/eulerBernoulliBeam/regression.test.js
```

The `test` script in `package.json` also runs this file, so `npm test` works too.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
* holds regardless of the specific numeric baseline and independently
* confirms the assembled system is physically consistent
*
* Run: node tests/regression/EulerBernoulliBeam/regression.test.js (or npm test)
* Run: node tests/regression/eulerBernoulliBeam/regression.test.js (or npm test)
*/

import { FEAScriptModel } from "../../../src/FEAScript.js";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ Tolerance used in the assertions: `1e-4`.
From the repository root:

```bash
node tests/regression/HeatConduction1DWall/regression.test.js
node tests/regression/heatConduction1DWall/regression.test.js
```

The `test` script in `package.json` also runs this file, so `npm test` works too.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* and every applicable solver method (lusolve, frontal, jacobi), and asserts that the temperature
* at x=0 remains 7.60870 and at x=0.075 remains 6.30435
*
* Run: node tests/regression/HeatConduction1DWall/regression.test.js (or npm test)
* Run: node tests/regression/heatConduction1DWall/regression.test.js (or npm test)
*/

import { FEAScriptModel } from "../../../src/FEAScript.js";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ used by the convection boundary condition.
From the repository root:

```bash
node tests/regression/HeatConduction2DFin/regression.test.js
node tests/regression/heatConduction2DFin/regression.test.js
```

The `test` script in `package.json` also runs this file, so `npm test` works too.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
* - Patch test on the Gmsh meshes: 1D conduction with convection, reproduced exactly (T = 200 - 60y)
* by the direct solvers
*
* Run: node tests/regression/HeatConduction2DFin/regression.test.js (or npm test)
* Run: node tests/regression/heatConduction2DFin/regression.test.js (or npm test)
*/

import fs from "fs";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ Tolerance used in the assertions: `1e-4`.
From the repository root:

```bash
node tests/regression/LidDrivenCavity2DCreepingFlow/regression.test.js
node tests/regression/lidDrivenCavity2DCreepingFlow/regression.test.js
```

The `test` script in `package.json` also runs this file, so `npm test` works too.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* flow (minimum x-velocity), the nodal mean x-velocity and y-velocity and the number of pressure
* nodes against known good values
*
* Run: node tests/regression/LidDrivenCavity2DCreepingFlow/regression.test.js (or npm test)
* Run: node tests/regression/lidDrivenCavity2DCreepingFlow/regression.test.js (or npm test)
*/

import fs from "fs";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Tolerance used in the assertions: `1e-4`.
From the repository root:

```bash
node tests/regression/NonlinearReactionDiffusion1D/regression.test.js
node tests/regression/nonlinearReactionDiffusion1D/regression.test.js
```

The `test` script in `package.json` also runs this file, so `npm test` works too.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
* for linear and quadratic elements, and asserts the solution at x=1, x=5 and x=10 against known
* good values
*
* Run: node tests/regression/NonlinearReactionDiffusion1D/regression.test.js (or npm test)
* Run: node tests/regression/nonlinearReactionDiffusion1D/regression.test.js (or npm test)
*/

import { FEAScriptModel } from "../../../src/FEAScript.js";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ exact value. Every solver method must give the same values. Tolerance used in th
From the repository root:

```bash
node tests/regression/SolidificationFront2D/regression.test.js
node tests/regression/solidificationFront2D/regression.test.js
```

The `test` script in `package.json` also runs this file, so `npm test` works too.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* solver method (lusolve, frontal), and asserts the value at (x=2, y=2) and the nodal mean value
* against known good values
*
* Run: node tests/regression/SolidificationFront2D/regression.test.js (or npm test)
* Run: node tests/regression/solidificationFront2D/regression.test.js (or npm test)
*/

import fs from "fs";
Expand Down
16 changes: 7 additions & 9 deletions tests/unit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,13 @@ alt="FEAScript general form PDE logo"
This folder contains unit tests for individual FEAScript modules, including solvers, assemblers,
and utilities.

| Test file | Covers |
| ---------------------------- | ---------------------------------------------------------------------------------------------------- |
| `jacobiMethod.test.js` | Jacobi solver convergence and non-convergence |
| `sparseMatrix.test.js` | Sparse matrix entry access, CSR/CSC/dense conversion, and sparse input to the LU and Jacobi solvers |
| `elements2D.test.js` | Basis functions and quadrature rules of linear and quadratic quadrilaterals and triangles |
| `plotInterpolation.test.js` | Point location and solution interpolation of the contour plot, for all 2D element types |
| `solutionFormat.test.js` | Solution vector is a Float64Array for every model and solver method, with the same values and plots |
| `gmshReader.test.js` | Gmsh reader, including meshes with non-contiguous node tags (same nodes, connectivity, solution) |
| `boundaryConditions.test.js` | Unknown condition types and malformed conditions are reported; plain strings are wrapped in an array |
| Test file | Covers |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `solvers.test.js` | Jacobi solver convergence; sparse matrix assembly and CSR/CSC/dense conversion; sparse solver input |
| `model.test.js` | Boundary condition input checks; solution format for every model and solver method; `FEAScriptWorker` model options |
| `elements2D.test.js` | Basis functions and quadrature rules of linear and quadratic quadrilaterals and triangles |
| `plotInterpolation.test.js` | Point location and solution interpolation of the contour plot, for all 2D element types |
| `gmshReader.test.js` | Gmsh reader, including meshes with non-contiguous node tags (same nodes, connectivity, solution) |

Run all unit, regression, and verification tests from the repository root with:

Expand Down
Loading
Loading