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
31 changes: 31 additions & 0 deletions app/(workspace)/methods/bvp/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import type { Metadata } from 'next';
import { BvpFiniteDifferenceMethod } from '@/components/bvp-finite-difference-method';
import { MethodJsonLd } from '@/components/seo/json-ld';
import { siteConfig } from '@/lib/config';

export const metadata: Metadata = {
title: 'Boundary Value Problem Solver | Finite Difference & Thomas Algorithm',
description: 'Solve second-order linear boundary value problems step by step using finite differences and the Thomas tridiagonal algorithm.',
keywords: ['boundary value problem solver', 'finite difference method', 'Thomas algorithm', 'two-point boundary conditions', 'ODE BVP solver'],
alternates: { canonical: '/methods/bvp' },
openGraph: {
title: 'Finite Difference BVP Solver | Thomas Algorithm',
description: 'Discretize and solve second-order linear boundary value problems, inspect Thomas coefficients, residuals, and the solution graph.',
url: `${siteConfig.url}/methods/bvp`,
},
};

export default function BvpPage() {
return (
<>
<MethodJsonLd
name="Finite Difference Boundary Value Problem Solver"
alternateName="Thomas algorithm BVP solver"
description="Step-by-step finite difference solution of second-order linear two-point boundary value problems."
path="/methods/bvp"
formula="y'' + P(x)y' + Q(x)y = f(x)"
/>
<BvpFiniteDifferenceMethod />
</>
);
}
14 changes: 11 additions & 3 deletions components/app-shell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export function AppShell({
},
];

// 2. Numerical methods (methods with isNew: true automatically pin to top of methods section)
// 2. Numerical methods (new methods appear at the end of the section)
const methodItems: NavItem[] = [
{
href: '/methods/bisection',
Expand Down Expand Up @@ -130,11 +130,17 @@ export function AppShell({
icon: Activity,
isNew: true,
},
{
href: '/methods/bvp',
label: t('nav.bvp'),
icon: Activity,
isNew: true,
},
];

const sortedMethods = [...methodItems].sort((a, b) => {
if (Boolean(a.isNew) !== Boolean(b.isNew)) {
return a.isNew ? -1 : 1;
return a.isNew ? 1 : -1;
}
return 0;
});
Expand Down Expand Up @@ -175,7 +181,9 @@ export function AppShell({
? t('nav.newtonInterpolation')
: pathname.startsWith('/methods/newton')
? t('nav.newton')
: pathname.startsWith('/methods/sweep')
: pathname.startsWith('/methods/bvp')
? t('nav.bvp')
: pathname.startsWith('/methods/sweep')
? t('nav.sweep')
: pathname.startsWith('/methods/lagrange')
? t('nav.lagrange')
Expand Down
410 changes: 410 additions & 0 deletions components/bvp-finite-difference-method.tsx

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions components/dashboard/methods-hub.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@ const METHODS: MethodDef[] = [
icon: ArrowRightLeft,
accentClass: 'text-violet-600 dark:text-violet-400 bg-violet-500/10 border-violet-500/25',
},
{
id: 'bvp',
code: '12 / ODE',
category: 'odes',
categoryKey: 'dashboard.cat_odes',
titleKey: 'nav.bvp',
descKey: 'dashboard.desc_bvp',
href: '/methods/bvp',
formulaTex: "y'' + P(x)y' + Q(x)y = f(x)",
orderTex: 'O(n)',
icon: Activity,
accentClass: 'text-amber-600 dark:text-amber-400 bg-amber-500/10 border-amber-500/25',
},
{
id: 'lagrange',
code: '04 / INT',
Expand Down
80 changes: 80 additions & 0 deletions lib/bvp-finite-difference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { compileMath } from '@/lib/math';

export interface BvpInput {
p: string; q: string; f: string; a: string; b: string; h: string; intervals: string;
m0: string; m1: string; m2: string; g0: string; g1: string; g2: string;
}
export interface BvpRow {
index: number; x: number; a: number; b: number; c: number; d: number;
pivot: number; u: number; v: number; y: number; residual: number;
}
export interface BvpResult {
h: number; rows: BvpRow[];
backward: Array<{ index: number; nextY: number | null; y: number }>;
maxResidual: number;
}

function number(value: string, label: string): number {
const parsed = Number(value);
if (!value.trim() || !Number.isFinite(parsed)) throw new Error(`${label}: finite number required`);
return parsed;
}

export function solveBvp(input: BvpInput): BvpResult {
const left = number(input.a, 'a');
const right = number(input.b, 'b');
const h = number(input.h, 'h');
const intervals = number(input.intervals, 'n');
const m0 = number(input.m0, 'm₀'); const m1 = number(input.m1, 'm₁'); const m2 = number(input.m2, 'm₂');
const g0 = number(input.g0, 'g₀'); const g1 = number(input.g1, 'g₁'); const g2 = number(input.g2, 'g₂');
if (right <= left) throw new Error('b must be greater than a');
if (!Number.isInteger(intervals) || intervals < 2 || intervals > 80) throw new Error('n must be an integer from 2 to 80');
if (h <= 0 || Math.abs((right - left) / intervals - h) > Math.max(1, Math.abs(h)) * 1e-8) {
throw new Error('h must divide the interval into an integer number of steps (2–80).');
}
if (m0 === 0 && m1 === 0) throw new Error('Left boundary condition cannot be zero');
if (g0 === 0 && g1 === 0) throw new Error('Right boundary condition cannot be zero');
const functions = [input.p, input.q, input.f].map((expression) => {
const compiled = compileMath(expression);
if (!compiled) throw new Error(`Invalid expression: ${expression}`);
return (x: number) => {
const value = Number(compiled.evaluate({ x }));
if (!Number.isFinite(value)) throw new Error(`Expression is not finite at x=${x}`);
return value;
};
});
const [P, Q, F] = functions;
const coeffs = Array.from({ length: intervals + 1 }, (_, i) => {
if (i === 0) return { a: 0, b: m0 - m1 / h, c: m1 / h, d: m2 };
if (i === intervals) return { a: -g1 / h, b: g0 + g1 / h, c: 0, d: g2 };
const x = left + i * h; const p = P(x);
return { a: 1 - h * p / 2, b: -2 + h * h * Q(x), c: 1 + h * p / 2, d: h * h * F(x) };
});
const tracedRows: BvpRow[] = [];
coeffs.forEach((row, i) => {
const previous = tracedRows[i - 1];
const pivot = previous ? row.a * previous.u + row.b : row.b;
const scale = previous ? Math.abs(row.a * previous.u) + Math.abs(row.b) : Math.abs(row.b);
if (!Number.isFinite(pivot) || Math.abs(pivot) <= Math.max(scale, 1) * Number.EPSILON * 64) {
throw new Error(`Thomas pivot is zero or unstable at row ${i + 1}`);
}
const u = -row.c / pivot;
const v = (row.d - (previous ? row.a * previous.v : 0)) / pivot;
if (![u, v].every(Number.isFinite)) throw new Error(`Non-finite Thomas coefficient at row ${i + 1}`);
tracedRows.push({ ...row, index: i + 1, x: left + i * h, pivot, u, v, y: 0, residual: 0 });
});
const ys = new Array<number>(tracedRows.length);
ys[ys.length - 1] = tracedRows[tracedRows.length - 1].v;
const backward: BvpResult['backward'] = [{ index: ys.length, nextY: null, y: ys[ys.length - 1] }];
for (let i = ys.length - 2; i >= 0; i -= 1) {
ys[i] = tracedRows[i].u * ys[i + 1] + tracedRows[i].v;
if (!Number.isFinite(ys[i])) throw new Error(`Non-finite solution at node ${i + 1}`);
backward.push({ index: i + 1, nextY: ys[i + 1], y: ys[i] });
}
const rows = tracedRows.map((row, i) => ({
...row,
y: ys[i],
residual: row.d - (row.a * (ys[i - 1] ?? 0) + row.b * ys[i] + row.c * (ys[i + 1] ?? 0)),
}));
return { h, rows, backward, maxResidual: Math.max(...rows.map((row) => Math.abs(row.residual))) };
}
68 changes: 66 additions & 2 deletions lib/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"rungeKutta": "Runge–Kutta 4th Order",
"learn": "Uzbek learning guides",
"newtonInterpolation": "Newton Interpolation",
"adamsPredictor": "Adams Predictor Method"
"adamsPredictor": "Adams Predictor Method",
"bvp": "Finite Difference BVP"
},
"about": {
"strip": "DOC-002 / ABOUT",
Expand Down Expand Up @@ -113,7 +114,8 @@
"cat_linear": "Linear systems",
"desc_rungeKutta": "Solve a first-order initial value problem with the classical fourth-order Runge–Kutta method.",
"desc_newtonInterpolation": "Build the Newton polynomial from divided differences, compare finite differences, and evaluate P(x*) step by step.",
"desc_adamsPredictor": "Solve an initial value problem with RK4 startup steps and the third-order Adams predictor formula."
"desc_adamsPredictor": "Solve an initial value problem with RK4 startup steps and the third-order Adams predictor formula.",
"desc_bvp": "Solve second-order linear boundary value problems with finite differences and a step-by-step Thomas algorithm trace."
},
"actions": {
"tools_badge": "EXPORT / TOOLS",
Expand Down Expand Up @@ -846,5 +848,67 @@
"notebook_collapse": "Collapse (key steps)",
"notebook_final_ans": "Final Answer",
"notebook_ans_desc": "Summary of solution across the interval"
},
"bvp": {
"eyebrow": "Boundary value problems / 012",
"title": "Finite Difference",
"title_highlight": "BVP Solver",
"lead": "Solve a second-order linear boundary value problem and inspect its discretization, Thomas sweep, residuals, and numerical curve.",
"boundaries": "m₀y(a) + m₁y′(a) = m₂; g₀y(b) + g₁y′(b) = g₂",
"map_eyebrow": "Solution workflow",
"map_title": "Finite differences + Thomas algorithm",
"map_1": "Discretize",
"map_1_desc": "Build the tridiagonal equations at boundary and interior nodes.",
"map_2": "Forward sweep",
"map_2_desc": "Compute the Thomas coefficients uₖ and vₖ row by row.",
"map_3": "Back substitute",
"map_3_desc": "Recover y values, then inspect the residual and graph.",
"input_eyebrow": "Inputs / Boundary conditions",
"input_title": "Problem parameters",
"expression_hint": "Enter Math.js expressions for P(x), Q(x), and f(x). With n=4 intervals, h=(b−a)/4 is calculated automatically.",
"left": "Left boundary",
"right": "Right boundary",
"calculate": "Solve BVP",
"error": "Unable to solve the boundary value problem.",
"note": "Central differences are used for interior derivatives; one-sided differences approximate endpoint derivatives.",
"matrix_eyebrow": "01 / DISCRETIZATION",
"matrix_title": "Tridiagonal system and Thomas coefficients",
"empty": "Set the equation and boundary data, then select Solve BVP.",
"backward_eyebrow": "02 / BACK SUBSTITUTION",
"backward_title": "Recover the solution step by step",
"result_eyebrow": "03 / SOLUTION",
"result_title": "Numerical solution and plot",
"max_residual": "Maximum equation residual",
"example_button": "Load example: y″ − 2(1 + x²)y = 0, y(0)=0, y(0.8)=3.5",
"full_problem_label": "Enter the complete problem",
"full_problem_hint": "Enter the differential equation and each boundary condition in its own field. For n=4, h is calculated automatically from the endpoints.",
"full_problem_placeholder": "h = 0.1\ny″ - x y′ + 2x y = 0.8\ny(1.2) - 0.5 y′(1.2) = 1\ny′(1.5) = 2",
"step_label": "Step size h",
"equation_label": "Differential equation",
"left_boundary_label": "Left boundary condition",
"right_boundary_label": "Right boundary condition",
"parse_button": "Read problem & fill fields",
"parsed_summary": "Parsed: h={h}, interval [{a}, {b}], number of steps n={n}. Review the values below.",
"parse_error": "Could not read the problem text.",
"steps_derived": "n={count} equal intervals; automatically calculated h={h}.",
"derivation_eyebrow": "NOTEBOOK / STEP BY STEP",
"notebook_title": "Full derivation of the finite-difference system",
"notebook_intro": "Follow how the differential equation becomes discrete equations, how the boundary rows are formed, and how the tridiagonal system is assembled.",
"notebook_grid": "Grid and step size",
"notebook_equation": "Step 1. Given differential equation",
"notebook_differences": "Step 2. Replace derivatives with finite differences",
"notebook_substitution": "Substitute the differences into the equation and multiply by h²:",
"notebook_boundary": "Step 3. Add the boundary conditions to the system",
"notebook_left_after": "After substituting into the left boundary row",
"notebook_right_after": "After substituting into the right boundary row",
"notebook_system": "Step 4. Resulting tridiagonal system",
"notebook_system_hint": "All node equations, including boundary conditions",
"notebook_system_reduced_hint": "Boundary values y₀ and yₙ substituted; unknowns are y₁…yₙ₋₁",
"notebook_sweep": "Step 5. Forward sweep and back substitution",
"notebook_sweep_hint": "The pivot, uₖ, and vₖ columns in the table are calculated with these formulas.",
"notebook_values": "Calculated values at the grid nodes",
"copy_button": "Copy for notebook",
"copy_done": "Copied",
"copy_error": "Could not copy the text. Select the equations and copy them manually."
}
}
Loading
Loading