Skip to content

Commit 83f2917

Browse files
authored
Pass model options to FEAScriptWorker (#107)
FEAScriptWorker.setModelConfig now takes the same options as FEAScriptModel (coefficientFunctions, nonlinear, maxIterations, tolerance), so the general form PDE and Euler–Bernoulli beam models can run in a worker and heat conduction uses the given k and Q. Functions cannot be sent to a web worker, so each coefficient function is sent as its source code and rebuilt in the worker; it must therefore be self-contained. solve() now also forwards maxIterations and tolerance. Tests: - Add worker options tests (functions are rebuilt and every model gives the same solution as without the worker) - Merge the unit tests into solvers.test.js (Jacobi, sparse matrix) and model.test.js (boundary conditions, solution format, worker options) - Rename the regression test folders to camelCase, as the verification and example folders are
1 parent 058b089 commit 83f2917

34 files changed

Lines changed: 860 additions & 674 deletions

‎dist/feascript-worker.esm.js‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/feascript-worker.esm.js.map‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/feascript.cjs.js‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/feascript.cjs.js.map‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/feascript.esm.js‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/feascript.esm.js.map‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/feascript.umd.js‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎dist/feascript.umd.js.map‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/**
2+
* ════════════════════════════════════════════════════════════════
3+
* FEAScript Core Library
4+
* Finite Element Simulation in JavaScript
5+
* Version: 0.3.0 (RC) | https://feascript.com
6+
* MIT License © 2023–2026 FEAScript
7+
* ════════════════════════════════════════════════════════════════
8+
*/
9+
10+
// Functions cannot be sent to a web worker (structured clone), so they are sent as their source code and
11+
// rebuilt in the worker. A rebuilt function has no access to the variables around its definition, so it
12+
// must be self-contained (e.g. `(x, u) => 0.5 * u ** 2` instead of `(x, u) => Da * u ** 2`)
13+
const functionSourceKey = "__feascriptFunctionSource";
14+
15+
/**
16+
* Function to replace the functions of a value (e.g. the model options) with their source code
17+
* @param {*} value - The value to serialize; plain objects and arrays are serialized recursively
18+
* @returns {*} A copy of the value that can be sent to a web worker
19+
* @throws Will throw an error if a function has no source code (native or bound functions)
20+
*/
21+
export function serializeFunctions(value) {
22+
if (typeof value === "function") {
23+
const source = value.toString();
24+
if (source.includes("[native code]")) {
25+
throw new Error(
26+
`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.`,
27+
);
28+
}
29+
return { [functionSourceKey]: source };
30+
}
31+
if (Array.isArray(value)) {
32+
return value.map(serializeFunctions);
33+
}
34+
if (value !== null && typeof value === "object" && Object.getPrototypeOf(value) === Object.prototype) {
35+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, serializeFunctions(entry)]));
36+
}
37+
return value;
38+
}
39+
40+
/**
41+
* Function to rebuild the functions that serializeFunctions replaced with their source code
42+
* @param {*} value - The serialized value
43+
* @returns {*} A copy of the value with the functions rebuilt
44+
*/
45+
export function deserializeFunctions(value) {
46+
if (Array.isArray(value)) {
47+
return value.map(deserializeFunctions);
48+
}
49+
if (value !== null && typeof value === "object") {
50+
if (typeof value[functionSourceKey] === "string") {
51+
return rebuildFunction(value[functionSourceKey]);
52+
}
53+
return Object.fromEntries(
54+
Object.entries(value).map(([key, entry]) => [key, deserializeFunctions(entry)]),
55+
);
56+
}
57+
return value;
58+
}
59+
60+
/**
61+
* Function to rebuild a function from its source code
62+
* @param {string} source - The source code of an arrow function, function expression or method
63+
* @returns {function} The rebuilt function
64+
*/
65+
function rebuildFunction(source) {
66+
try {
67+
// Arrow functions and function expressions, e.g. "(x) => 1" or "function (x) { return 1; }"
68+
return new Function(`"use strict"; return (${source});`)();
69+
} catch (error) {
70+
if (!(error instanceof SyntaxError)) throw error;
71+
// Methods are given without the function keyword, e.g. "A(x) { return 1; }"
72+
return new Function(`"use strict"; return Object.values({ ${source} })[0];`)();
73+
}
74+
}

‎src/workers/worker.js‎

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
// External imports
1111
import * as Comlink from "../vendor/comlink.mjs";
1212

13+
// Internal imports
14+
import { serializeFunctions } from "./functionSerialization.js";
15+
1316
/**
1417
* Class to facilitate communication with web workers for FEAScript operations
1518
*/
@@ -93,11 +96,13 @@ export class FEAScriptWorker {
9396
/**
9497
* Function to set the model configuration in the worker
9598
* @param {string} modelConfig - The model configuration to set
99+
* @param {object} [options] - Optional additional configuration, as in FEAScriptModel.setModelConfig. The
100+
* coefficient functions are sent to the worker as source code, so they must be self-contained (no outer variables)
96101
* @returns {Promise<boolean>} Resolves when the configuration is set
97102
*/
98-
async setModelConfig(modelConfig) {
103+
async setModelConfig(modelConfig, options = {}) {
99104
await this._ensureReady();
100-
return this.feaWorker.setModelConfig(modelConfig);
105+
return this.feaWorker.setModelConfig(modelConfig, serializeFunctions(options));
101106
}
102107

103108
/**
@@ -133,11 +138,12 @@ export class FEAScriptWorker {
133138

134139
/**
135140
* Function to request the worker to solve the problem
141+
* @param {object} [options] - Additional parameters for the solver, such as `maxIterations` and `tolerance`
136142
* @returns {Promise<object>} Resolves with the solution result
137143
*/
138-
async solve() {
144+
async solve(options = {}) {
139145
await this._ensureReady();
140-
return this.feaWorker.solve();
146+
return this.feaWorker.solve(options);
141147
}
142148

143149
/**

0 commit comments

Comments
 (0)