lagrange / src /app /actions.ts
sameernotes's picture
Upload 56 files
de7129f verified
"use server";
import type { DataPoint } from "@/lib/schemas";
export interface CalculationStep {
termIndex: number;
yValue: number;
basisNumeratorSymbolic: string;
basisDenominatorSymbolic: string;
basisDenominatorValue: number;
basisPolynomialSymbolic: string;
basisNumeratorAtXValues: string;
basisNumeratorAtXProduct: number;
basisPolynomialValueAtX: number;
termSymbolic: string;
termValueAtX: number;
}
interface LagrangeResult {
interpolatedValue: number | null;
polynomialTermsDisplay: string[];
calculationSteps: CalculationStep[];
plotData?: Array<{ x: number; original?: number; interpolated?: number; target?: number }>;
error?: string;
interpolationPoint?: number;
}
function evaluateLagrangePolynomialAtPoint(xEval: number, dataPoints: DataPoint[]): number {
let sum = 0;
const n = dataPoints.length;
if (n === 0) return NaN; // Should not happen with validation
for (let j = 0; j < n; j++) {
let basisProduct = 1;
if (n === 1) { // If only one point, L_0(x) = 1, P(x) = y_0
basisProduct = 1;
} else {
for (let k = 0; k < n; k++) {
if (j === k) continue;
if (dataPoints[j].x - dataPoints[k].x === 0) return NaN; // Avoid division by zero, though schema should prevent duplicate x
basisProduct *= (xEval - dataPoints[k].x) / (dataPoints[j].x - dataPoints[k].x);
}
}
sum += dataPoints[j].y * basisProduct;
}
return sum;
}
export async function calculateLagrangeInterpolation(
dataPoints: DataPoint[],
interpolationX: number
): Promise<LagrangeResult> {
if (dataPoints.length < 1) { // Allow 1 point for P(x)=y0
return { interpolatedValue: null, polynomialTermsDisplay: [], calculationSteps:[], error: "At least one data point is required." };
}
const xValues = dataPoints.map(p => p.x);
if (new Set(xValues).size !== xValues.length) {
return { interpolatedValue: null, polynomialTermsDisplay: [], calculationSteps: [], error: "All X values in data points must be unique." };
}
let lagrangeSum = 0;
const polynomialTermsDisplay: string[] = [];
const calculationSteps: CalculationStep[] = [];
const n = dataPoints.length;
for (let j = 0; j < n; j++) {
const currentStep: Partial<CalculationStep> = { termIndex: j, yValue: dataPoints[j].y };
let basisNumeratorSym = "";
let basisDenominatorSym = "";
let basisDenominatorNum = 1;
let basisNumeratorAtXEvalStrings: string[] = [];
let basisNumeratorAtXProd = 1;
if (n === 1) { // P(x) = y_0, so L_0(x) = 1
basisNumeratorSym = "1";
basisDenominatorSym = "1";
basisDenominatorNum = 1;
basisNumeratorAtXProd = 1;
basisNumeratorAtXEvalStrings.push("1");
} else {
for (let k = 0; k < n; k++) {
if (j === k) continue;
basisNumeratorSym += `(x - ${dataPoints[k].x.toString()})`;
basisDenominatorSym += `(${dataPoints[j].x.toString()} - ${dataPoints[k].x.toString()})`;
basisDenominatorNum *= (dataPoints[j].x - dataPoints[k].x);
basisNumeratorAtXEvalStrings.push(`(${interpolationX.toString()} - ${dataPoints[k].x.toString()})`);
basisNumeratorAtXProd *= (interpolationX - dataPoints[k].x);
}
}
currentStep.basisNumeratorSymbolic = basisNumeratorSym || "1";
currentStep.basisDenominatorSymbolic = basisDenominatorSym || "1"; // Denominator isn't really symbolic in L_j(x) display typically
if (basisDenominatorNum === 0) {
return { interpolatedValue: null, polynomialTermsDisplay: [], calculationSteps: [], error: "Division by zero in basis polynomial. X values might be too close or identical." };
}
currentStep.basisDenominatorValue = basisDenominatorNum;
currentStep.basisPolynomialSymbolic = `(${currentStep.basisNumeratorSymbolic}) / ${currentStep.basisDenominatorValue.toFixed(6)}`;
currentStep.basisNumeratorAtXValues = basisNumeratorAtXEvalStrings.join('*') || "1";
currentStep.basisNumeratorAtXProduct = basisNumeratorAtXProd;
currentStep.basisPolynomialValueAtX = basisNumeratorAtXProd / basisDenominatorNum;
const termCoefficientForDisplay = dataPoints[j].y / (n === 1 ? 1 : basisDenominatorNum);
let singleTermDisplay = `${dataPoints[j].y.toFixed(4)}`;
if (n > 1) { // Only add multiplier if more than one point
if (basisDenominatorNum !== 1 || basisNumeratorSym !== "1") { // avoid " * 1 / 1"
singleTermDisplay = `${termCoefficientForDisplay.toFixed(4)}`;
if (basisNumeratorSym && basisNumeratorSym !== "1") {
singleTermDisplay += ` * ${basisNumeratorSym}`;
}
}
}
polynomialTermsDisplay.push(singleTermDisplay);
currentStep.termSymbolic = `${dataPoints[j].y.toFixed(4)} * ${currentStep.basisPolynomialSymbolic}`;
if (n === 1) currentStep.termSymbolic = `${dataPoints[j].y.toFixed(4)}`; // For y_0
currentStep.termValueAtX = dataPoints[j].y * currentStep.basisPolynomialValueAtX;
calculationSteps.push(currentStep as CalculationStep);
lagrangeSum += currentStep.termValueAtX;
}
if (isNaN(lagrangeSum)) {
return { interpolatedValue: null, polynomialTermsDisplay: [], calculationSteps, error: "Calculation resulted in NaN. Check input values." };
}
const plotData: Array<{ x: number; original?: number; interpolated?: number; target?: number }> = [];
if (dataPoints.length > 0) {
const xS = dataPoints.map(p => p.x);
const minX = Math.min(...xS);
const maxX = Math.max(...xS);
const range = maxX - minX;
const plotPadding = range === 0 ? 1 : range * 0.2; // Ensure some padding for single point or narrow range
const plotStart = minX - plotPadding;
const plotEnd = maxX + plotPadding;
const numPlotPoints = 100;
for (let i = 0; i <= numPlotPoints; i++) {
const currentX = plotStart + (i * (plotEnd - plotStart)) / numPlotPoints;
const interpolatedVal = evaluateLagrangePolynomialAtPoint(currentX, dataPoints);
if (!isNaN(interpolatedVal)) {
plotData.push({ x: currentX, interpolated: interpolatedVal });
}
}
dataPoints.forEach(dp => {
const existingPoint = plotData.find(p => Math.abs(p.x - dp.x) < 1e-9); // Check for float equality
if (existingPoint) {
existingPoint.original = dp.y;
} else {
plotData.push({ x: dp.x, original: dp.y, interpolated: evaluateLagrangePolynomialAtPoint(dp.x, dataPoints) });
}
});
// Ensure the specific interpolationX point is included for the curve and target highlighting
const specificInterpolatedValue = evaluateLagrangePolynomialAtPoint(interpolationX, dataPoints);
if(!isNaN(specificInterpolatedValue)) {
const targetPointEntry = plotData.find(p => Math.abs(p.x - interpolationX) < 1e-9);
if (targetPointEntry) {
targetPointEntry.interpolated = specificInterpolatedValue; // Ensure curve passes through it
targetPointEntry.target = lagrangeSum; // lagrangeSum is P(interpolationX)
} else {
plotData.push({ x: interpolationX, interpolated: specificInterpolatedValue, target: lagrangeSum });
}
}
plotData.sort((a, b) => a.x - b.x);
}
return { interpolatedValue: lagrangeSum, polynomialTermsDisplay, calculationSteps, plotData, interpolationPoint: interpolationX };
}