Skip to content

Commit d606061

Browse files
authored
feature 1D-PDE-Solver for FEAScript-core (#42)
This commit merges the PR introducing a general-purpose solver for 1D linear PDEs, supporting equations like d/dx(A du/dx) + B du/dx + C u = D with user-defined coefficients and boundary conditions. The implementation is standalone and functional as a prototype. Further integration is needed to align with FEAScript's existing utilities.
1 parent 558b835 commit d606061

File tree

3 files changed

+256
-0
lines changed

3 files changed

+256
-0
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// ______ ______ _____ _ _ //
2+
// | ____| ____| /\ / ____| (_) | | //
3+
// | |__ | |__ / \ | (___ ___ ____ _ ____ | |_ //
4+
// | __| | __| / /\ \ \___ \ / __| __| | _ \| __| //
5+
// | | | |____ / ____ \ ____) | (__| | | | |_) | | //
6+
// |_| |______/_/ \_\_____/ \___|_| |_| __/| | //
7+
// | | | | //
8+
// |_| | |_ //
9+
// Website: https://feascript.com/ \__| //
10+
11+
// Example usage of generalFormPDESolver with a simple mesh and coefficients
12+
import { generalFormPDESolver } from '../../src/solvers/generalFormPDEScript.js';
13+
14+
// Generate a uniform mesh from 0 to 1 with 20 nodes
15+
const nNodes = 20;
16+
const mesh = Array.from({ length: nNodes }, (_, i) => i / (nNodes - 1));
17+
18+
// Coefficient functions for Given equation: d²u/dx² + 10 du/dx = -10 * exp(-200 * (x - 0.5)²), for 0 < x < 1
19+
const A = x => 1; // Diffusion coefficient
20+
const B = x => 10; // first derivative term
21+
const C = x => 0; // No reaction term
22+
const D = x => -10 * Math.exp(-200 * Math.pow(x - 0.5, 2)); // Source function D(x)
23+
24+
// Dirichlet left boundary conditions: u(0) = 1, and Neumann right boundry conditions: u(1) = 0
25+
const boundary = {
26+
left: { type: 'dirichlet', value: 1 },
27+
right: { type: 'neumann', value: 0 }
28+
};
29+
30+
//Solve
31+
const u = generalFormPDESolver({ A, B, C, D, mesh, boundary });
32+
console.log('Mesh nodes:', mesh);
33+
console.log('Solution u:', u);
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Example: 1D General PDE Solver
2+
3+
This example demonstrates how to use the FEAScript 1D general PDE solver for equations of the form:
4+
5+
$$
6+
\frac{d}{dx}\left(A(x)\frac{du}{dx}\right) + B(x)\frac{du}{dx} + C(x)u = D(x)
7+
$$
8+
9+
where $u$ is the unknown, $x$ is the position, and $A$, $B$, $C$, $D$ are user-provided coefficient functions.
10+
11+
## How to Run
12+
13+
1. **Set up the mesh:**
14+
- Here, we use a uniform mesh from 0 to 1 with 20 nodes.
15+
2. **Define coefficient functions:**
16+
- For the Given equation $-\frac{d^2u}{dx^2} = 1$, set $A(x) = 1$, $B(x) = 10$,
17+
$C(x) = 0$, $D(x) = -10 * exp(-200 * (x - 0.5)²)$, for 0 < x < 1.
18+
3. **Set boundary conditions:**
19+
- Dirichlet boundaries: $u(0) = 1$, $u(1) = 0$.
20+
4. **Solve and print results:**
21+
- The solution vector $u$ will be printed for each mesh node.
22+
23+
## Example Code
24+
25+
```js
26+
const { generalFormPDESolver } = require("../../src/solvers/generalFormPDEScript");
27+
28+
// Generate a uniform mesh from 0 to 1 with 20 nodes
29+
const nNodes = 20;
30+
const mesh = Array.from({ length: nNodes }, (_, i) => i / (nNodes - 1));
31+
32+
// Coefficient functions for Given equation: d²u/dx² + 10 du/dx = -10 * exp(-200 * (x - 0.5)²), for 0 < x < 1
33+
const A = x => 1; // Diffusion coefficient
34+
const B = x => 0; // No first derivative term
35+
const C = x => 0; // No reaction term
36+
const D = x => 1; // Source function D(X)
37+
38+
39+
// Dirichlet left boundary conditions: u(0) = 1, and Neumann right boundry conditions: u(1) = 0
40+
const boundary = {
41+
left: { type: 'dirichlet', value: 1 },
42+
right: { type: 'neumann', value: 0 }
43+
};
44+
45+
// Solve
46+
const u = generalFormPDESolver({ A, B, C, D, mesh, boundary });
47+
48+
console.log('Mesh nodes:', mesh);
49+
console.log('Solution u:', u);
50+
51+
52+
## Output
53+
54+
The script prints the mesh nodes and the solution $u$ at each node. You can modify the coefficient functions and boundary conditions to solve other problems.
55+
56+
---
57+
58+
For more details, see the main project README or documentation.
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// ______ ______ _____ _ _ //
2+
// | ____| ____| /\ / ____| (_) | | //
3+
// | |__ | |__ / \ | (___ ___ ____ _ ____ | |_ //
4+
// | __| | __| / /\ \ \___ \ / __| __| | _ \| __| //
5+
// | | | |____ / ____ \ ____) | (__| | | | |_) | | //
6+
// |_| |______/_/ \_\_____/ \___|_| |_| __/| | //
7+
// | | | | //
8+
// |_| | |_ //
9+
// Website: https://feascript.com/ \__| //
10+
11+
// generalFormPDEScript.js
12+
// Solver for general 1D linear differential equations in weak form
13+
// User provides coefficients A(x), B(x), C(x), D(x) and boundary conditions
14+
15+
/**
16+
* General 1D PDE Solver (Weak Form)
17+
* Solves equations of the form:
18+
* --> d/dx(A(x) du/dx) + B(x) du/dx + C(x) u = D(x)
19+
* using the finite element method (FEM) and user-supplied coefficients.
20+
* @param {Object} options - Solver options
21+
* @param {function} options.A - Coefficient function A(x) (diffusion)
22+
* @param {function} options.B - Coefficient function B(x) (advection)
23+
* @param {function} options.C - Coefficient function C(x) (reaction)
24+
* @param {function} options.D - Source function D(x)
25+
* @param {Array<number>} options.mesh - Mesh nodes (1D array)
26+
* @param {Object} options.boundary - Boundary conditions
27+
* { left: {type: 'dirichlet'|'neumann'|'robin', value}, right: {type, value} }
28+
* - Dirichlet: value is the prescribed u
29+
* - Neumann: value is the prescribed flux (A du/dx)
30+
* - Robin: value is { a, b, c } for a*u + b*du/dx = c
31+
* @returns {Array<number>} Solution vector u at mesh nodes
32+
*/
33+
export function generalFormPDESolver({ A, B, C, D, mesh, boundary }) {
34+
// Number of nodes and elements
35+
const nNodes = mesh.length;
36+
const nElements = nNodes - 1;
37+
38+
// Initialize global stiffness matrix and load vector
39+
const K = Array.from({ length: nNodes }, () => Array(nNodes).fill(0));
40+
const F = Array(nNodes).fill(0);
41+
42+
// Linear basis functions for 1D elements
43+
// For each element [x0, x1]
44+
for (let e = 0; e < nElements; e++) {
45+
const x0 = mesh[e];
46+
const x1 = mesh[e + 1];
47+
const h = x1 - x0;
48+
// Local stiffness matrix and load vector
49+
let Ke = [ [0, 0], [0, 0] ];
50+
let Fe = [0, 0];
51+
// 2-point Gauss quadrature for integration
52+
const gaussPoints = [ -1/Math.sqrt(3), 1/Math.sqrt(3) ];
53+
const gaussWeights = [ 1, 1 ];
54+
for (let gp = 0; gp < 2; gp++) {
55+
// Map reference [-1,1] to [x0,x1]
56+
const xi = gaussPoints[gp];
57+
const w = gaussWeights[gp];
58+
const x = ((1 - xi) * x0 + (1 + xi) * x1) / 2;
59+
const dx_dxi = h / 2;
60+
// Basis functions and derivatives
61+
const N = [ (1 - xi) / 2, (1 + xi) / 2 ];
62+
const dN_dxi = [ -0.5, 0.5 ];
63+
const dN_dx = dN_dxi.map(d => d / dx_dxi);
64+
// Coefficients at integration point
65+
const a = A(x);
66+
const b = B(x);
67+
const c = C(x);
68+
const d = D(x);
69+
// Element matrix assembly (weak form)
70+
for (let i = 0; i < 2; i++) {
71+
for (let j = 0; j < 2; j++) {
72+
Ke[i][j] += (
73+
a * dN_dx[i] * dN_dx[j] +
74+
b * dN_dx[j] * N[i] -
75+
c * N[i] * N[j]
76+
) * w * dx_dxi;
77+
}
78+
Fe[i] += d * N[i] * w * dx_dxi;
79+
}
80+
}
81+
// Assemble into global matrix/vector
82+
K[e][e] += Ke[0][0];
83+
K[e][e+1] += Ke[0][1];
84+
K[e+1][e] += Ke[1][0];
85+
K[e+1][e+1] += Ke[1][1];
86+
F[e] += Fe[0];
87+
F[e+1] += Fe[1];
88+
}
89+
90+
// Apply boundary conditions (Dirichlet, Neumann, Robin)
91+
// Left boundary
92+
if (boundary.left) {
93+
if (boundary.left.type === 'dirichlet') {
94+
for (let j = 0; j < nNodes; j++) {
95+
K[0][j] = 0;
96+
}
97+
K[0][0] = 1;
98+
F[0] = boundary.left.value;
99+
} else if (boundary.left.type === 'neumann') {
100+
// Add Neumann flux to load vector
101+
F[0] += boundary.left.value;
102+
} else if (boundary.left.type === 'robin') {
103+
// Robin: a*u + b*du/dx = c
104+
const { a, b, c } = boundary.left.value;
105+
K[0][0] += a;
106+
F[0] += c;
107+
K[0][0] += b;
108+
}
109+
}
110+
// Right boundary
111+
if (boundary.right) {
112+
if (boundary.right.type === 'dirichlet') {
113+
for (let j = 0; j < nNodes; j++) {
114+
K[nNodes-1][j] = 0;
115+
}
116+
K[nNodes-1][nNodes-1] = 1;
117+
F[nNodes-1] = boundary.right.value;
118+
} else if (boundary.right.type === 'neumann') {
119+
// Add Neumann flux to load vector
120+
F[nNodes-1] += boundary.right.value;
121+
} else if (boundary.right.type === 'robin') {
122+
// Robin: a*u + b*du/dx = c
123+
const { a, b, c } = boundary.right.value;
124+
K[nNodes-1][nNodes-1] += a;
125+
F[nNodes-1] += c;
126+
K[nNodes-1][nNodes-1] += b;
127+
}
128+
}
129+
130+
// Solve linear system Ku = F (simple Gauss elimination for small systems)
131+
function solveLinearSystem(A, b) {
132+
// Naive Gauss elimination (for small systems)
133+
const n = b.length;
134+
const x = Array(n).fill(0);
135+
const M = A.map(row => row.slice());
136+
const f = b.slice();
137+
// Forward elimination
138+
for (let i = 0; i < n; i++) {
139+
let maxRow = i;
140+
for (let k = i+1; k < n; k++) {
141+
if (Math.abs(M[k][i]) > Math.abs(M[maxRow][i])) maxRow = k;
142+
}
143+
[M[i], M[maxRow]] = [M[maxRow], M[i]];
144+
[f[i], f[maxRow]] = [f[maxRow], f[i]];
145+
for (let k = i+1; k < n; k++) {
146+
const c = M[k][i] / M[i][i];
147+
for (let j = i; j < n; j++) {
148+
M[k][j] -= c * M[i][j];
149+
}
150+
f[k] -= c * f[i];
151+
}
152+
}
153+
// Back substitution
154+
for (let i = n-1; i >= 0; i--) {
155+
let sum = 0;
156+
for (let j = i+1; j < n; j++) {
157+
sum += M[i][j] * x[j];
158+
}
159+
x[i] = (f[i] - sum) / M[i][i];
160+
}
161+
return x;
162+
}
163+
const u = solveLinearSystem(K, F);
164+
return u;
165+
}

0 commit comments

Comments
 (0)