File size: 7,435 Bytes
de7129f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183

"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 };
}