lagrange / src /components /lagrange-solver.tsx
sameernotes's picture
Upload 56 files
de7129f verified
"use client";
import * as React from "react";
import { useForm, useFieldArray } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { LagrangeFormSchema, type LagrangeFormValues, type DataPoint } from "@/lib/schemas";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import { PlusCircle, MinusCircle, Calculator, Sigma, Target, AlertTriangle, ListChecks, FunctionSquare, Spline } from "lucide-react";
import { calculateLagrangeInterpolation, type CalculationStep } from "@/app/actions";
import { useToast } from "@/hooks/use-toast";
import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion";
import {
ChartContainer,
ChartTooltip,
ChartTooltipContent,
ChartLegend,
ChartLegendContent,
} from "@/components/ui/chart";
import { LineChart as RechartsLineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip as RechartsTooltip, Legend as RechartsLegend, Scatter, ReferenceDot, Label as RechartsLabel, ReferenceLine } from "recharts";
import { cn } from "@/lib/utils";
// Helper component to render fractions
const SymbolicFraction: React.FC<{ numerator: string | React.ReactNode; denominator: string | React.ReactNode; className?: string }> = ({ numerator, denominator, className }) => {
return (
<div className={cn("inline-flex flex-col items-center text-center align-middle mx-1 leading-tight", className)}>
<span className="px-1 pb-0.5">{numerator}</span>
<hr className="w-full border-foreground my-0.5" />
<span className="px-1 pt-0.5">{denominator}</span>
</div>
);
};
export function LagrangeSolver() {
const { toast } = useToast();
const [isLoading, setIsLoading] = React.useState(false);
const [result, setResult] = React.useState<{
interpolatedValue: number | null;
polynomialTermsDisplay: string[];
calculationSteps: CalculationStep[];
plotData?: Array<{ x: number; original?: number; interpolated?: number; target?: number }>;
interpolationPoint?: number;
} | null>(null);
const [error, setError] = React.useState<string | null>(null);
const form = useForm<LagrangeFormValues>({
resolver: zodResolver(LagrangeFormSchema),
defaultValues: {
dataPoints: [{ x: "", y: "" }, { x: "", y: "" }],
interpolationX: "",
},
});
const { fields, append, remove } = useFieldArray({
control: form.control,
name: "dataPoints",
});
const onSubmit = async (values: LagrangeFormValues) => {
setIsLoading(true);
setResult(null);
setError(null);
const numericDataPoints: DataPoint[] = values.dataPoints.map(dp => ({
x: parseFloat(dp.x),
y: parseFloat(dp.y),
}));
const numericInterpolationX = parseFloat(values.interpolationX);
try {
const response = await calculateLagrangeInterpolation(numericDataPoints, numericInterpolationX);
if (response.error) {
setError(response.error);
toast({
title: "Calculation Error",
description: response.error,
variant: "destructive",
});
} else {
setResult(response);
}
} catch (e) {
const errorMessage = e instanceof Error ? e.message : "An unknown error occurred.";
setError(errorMessage);
toast({
title: "Unhandled Error",
description: errorMessage,
variant: "destructive",
});
} finally {
setIsLoading(false);
}
};
const chartConfig = {
original: { label: "Original Data", color: "hsl(var(--primary))" },
interpolated: { label: "Lagrange Polynomial", color: "hsl(var(--accent))" },
target: { label: `Interpolated L(${result?.interpolationPoint})`, color: "hsl(var(--destructive))" },
};
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 font-headline">
<ListChecks className="h-6 w-6 text-primary" />
Data Points (x, y)
</CardTitle>
<CardDescription>Enter the known data points. At least one point is required. X values must be unique.</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{fields.map((field, index) => (
<div key={field.id} className="flex items-start gap-4">
<FormField
control={form.control}
name={`dataPoints.${index}.x`}
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>X<sub>{index}</sub></FormLabel>
<FormControl>
<Input type="text" placeholder={`X value for point ${index + 1}`} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`dataPoints.${index}.y`}
render={({ field }) => (
<FormItem className="flex-1">
<FormLabel>Y<sub>{index}</sub></FormLabel>
<FormControl>
<Input type="text" placeholder={`Y value for point ${index + 1}`} {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{fields.length > 1 && (
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => remove(index)}
className="mt-8 text-destructive hover:text-destructive hover:bg-destructive/10"
aria-label="Remove point"
>
<MinusCircle className="h-5 w-5" />
</Button>
)}
</div>
))}
{form.formState.errors.dataPoints && typeof form.formState.errors.dataPoints.message === 'string' && (
<p className="text-sm font-medium text-destructive">{form.formState.errors.dataPoints.message}</p>
)}
{form.formState.errors.dataPoints?.root?.message && (
<p className="text-sm font-medium text-destructive">{form.formState.errors.dataPoints.root.message}</p>
)}
</CardContent>
<CardFooter>
<Button
type="button"
variant="outline"
onClick={() => append({ x: "", y: "" })}
className="text-primary border-primary hover:bg-primary/10 hover:text-primary"
>
<PlusCircle className="mr-2 h-4 w-4" />
Add Point
</Button>
</CardFooter>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 font-headline">
<Target className="h-6 w-6 text-primary" />
Interpolation Point (X)
</CardTitle>
<CardDescription>Specify the X value at which to estimate the function.</CardDescription>
</CardHeader>
<CardContent>
<FormField
control={form.control}
name="interpolationX"
render={({ field }) => (
<FormItem>
<FormLabel>X value for interpolation</FormLabel>
<FormControl>
<Input type="text" placeholder="Enter X value" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</CardContent>
</Card>
<Button type="submit" className="w-full" disabled={isLoading}>
<Calculator className="mr-2 h-4 w-4" />
{isLoading ? "Calculating..." : "Calculate Interpolated Value"}
</Button>
{isLoading && (
<div className="text-center p-4 text-muted-foreground">
<p>Calculating, please wait...</p>
</div>
)}
{error && !isLoading && (
<Alert variant="destructive">
<AlertTriangle className="h-4 w-4" />
<AlertTitle>Error</AlertTitle>
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{result && !isLoading && !error && (
<>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 font-headline">
<FunctionSquare className="h-6 w-6 text-primary" />
Results Summary
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<h3 className="font-semibold">Interpolated Value:</h3>
<p className="text-2xl">
L({result.interpolationPoint?.toString()}) =&nbsp;
<span className="font-bold text-accent">{result.interpolatedValue?.toFixed(6)}</span>
</p>
</div>
<div>
<h3 className="font-semibold">Lagrange Polynomial L(x):</h3>
<p className="text-sm text-muted-foreground">
The polynomial is formed by summing these terms:
</p>
<div className="mt-2 p-3 bg-muted rounded-md overflow-x-auto">
<pre className="text-sm"><code className="font-code">
{result.polynomialTermsDisplay.map((term, index) => (
<React.Fragment key={index}>
{term}
{index < result.polynomialTermsDisplay.length - 1 ? " + " : ""}
</React.Fragment>
))}
</code></pre>
</div>
</div>
</CardContent>
</Card>
{result.calculationSteps && result.calculationSteps.length > 0 && (
<Card className="mt-6">
<CardHeader>
<CardTitle className="flex items-center gap-2 font-headline">
<Sigma className="h-6 w-6 text-primary" />
Step-by-Step Calculation
</CardTitle>
</CardHeader>
<CardContent>
<Accordion type="single" collapsible className="w-full">
{result.calculationSteps.map((step, index) => (
<AccordionItem value={`item-${index}`} key={index}>
<AccordionTrigger>
Term {index + 1}: Contribution of y<sub>{step.termIndex}</sub> = {step.yValue.toFixed(4)}
</AccordionTrigger>
<AccordionContent className="space-y-4 text-sm p-4 bg-background rounded-md border">
{/* Section 1: Basis Polynomial Definition */}
<div>
<p className="font-semibold">Basis Polynomial L<sub>{step.termIndex}</sub>(x)</p>
<p className="text-muted-foreground text-xs mt-0.5">
General form: L<sub>{step.termIndex}</sub>(x) = &prod;<sub>k&ne;{step.termIndex}</sub>
<span className="inline-block align-middle text-center mx-1">
(x - x<sub>k</sub>) / (x<sub>{step.termIndex}</sub> - x<sub>k</sub>)
</span>
</p>
<div className="mt-2 flex items-center flex-wrap">
Actual L<sub>{step.termIndex}</sub>(x) =&nbsp;
<SymbolicFraction
numerator={step.basisNumeratorSymbolic || "1"}
denominator={`(${step.basisDenominatorSymbolic || "1"})`}
/>
</div>
<div className="mt-2 flex items-center flex-wrap">
=&nbsp;
<SymbolicFraction
numerator={step.basisNumeratorSymbolic || "1"}
denominator={step.basisDenominatorValue.toFixed(6)}
/>
</div>
</div>
{/* Section 2: Evaluation */}
<div>
<p className="font-semibold mt-3">Evaluating L<sub>{step.termIndex}</sub>(x) at x = {result.interpolationPoint?.toString()}</p>
<div className="mt-2 flex items-center flex-wrap">
L<sub>{step.termIndex}</sub>({result.interpolationPoint?.toString()}) =&nbsp;
<SymbolicFraction
numerator={step.basisNumeratorAtXValues || "1"}
denominator={step.basisDenominatorValue.toFixed(6)}
/>
</div>
<div className="mt-2 flex items-center flex-wrap">
=&nbsp;
<SymbolicFraction
numerator={step.basisNumeratorAtXProduct.toFixed(6)}
denominator={step.basisDenominatorValue.toFixed(6)}
/>
&nbsp;=&nbsp;<span className="font-bold">{step.basisPolynomialValueAtX.toFixed(6)}</span>
</div>
</div>
{/* Section 3: Term Contribution */}
<div>
<p className="font-semibold mt-3">Term Contribution: y<sub>{step.termIndex}</sub> &middot; L<sub>{step.termIndex}</sub>({result.interpolationPoint?.toString()})</p>
{ form.getValues("dataPoints").length > 1 && (step.basisNumeratorSymbolic !== "1" || step.basisDenominatorSymbolic !== "1" || step.basisDenominatorValue !== 1.0) &&
<div className="mt-2 flex items-center flex-wrap">
Symbolic Term<sub>{step.termIndex}</sub> =&nbsp;
<SymbolicFraction
numerator={
<span className="whitespace-nowrap">{step.yValue.toFixed(4)} &middot; ({step.basisNumeratorSymbolic || "1"})</span>
}
denominator={step.basisDenominatorValue.toFixed(6)}
/>
</div>
}
{ (form.getValues("dataPoints").length === 1 || (step.basisNumeratorSymbolic === "1" && step.basisDenominatorSymbolic ==="1" && step.basisDenominatorValue === 1.0)) &&
<p className="mt-1">
Symbolic Term<sub>{step.termIndex}</sub> = {step.yValue.toFixed(4)}
</p>
}
<p className="mt-2">
Numerical Term<sub>{step.termIndex}</sub> = {step.yValue.toFixed(4)} &middot; {step.basisPolynomialValueAtX.toFixed(6)}
</p>
<p className="mt-1">
=&nbsp;<span className="font-bold text-primary">{step.termValueAtX.toFixed(6)}</span>
</p>
</div>
</AccordionContent>
</AccordionItem>
))}
</Accordion>
<div className="mt-6 p-4 bg-muted rounded-md">
<h3 className="font-semibold text-md">Sum of Term Contributions (Final Interpolated Value):</h3>
<p className="break-all text-sm mt-1"> {/* Removed font-code */}
L({result.interpolationPoint?.toString()}) = {result.calculationSteps.map(s => s.termValueAtX.toFixed(6)).join(" + ")} = <span className="font-bold text-accent text-lg">{result.interpolatedValue?.toFixed(6)}</span>
</p>
</div>
</CardContent>
</Card>
)}
{result.plotData && result.plotData.length > 0 && (
<Card className="mt-6">
<CardHeader>
<CardTitle className="flex items-center gap-2 font-headline">
<Spline className="h-6 w-6 text-primary" />
Interpolation Plot
</CardTitle>
</CardHeader>
<CardContent className="h-[450px] w-full pt-6">
<ChartContainer
config={chartConfig}
className="h-full w-full"
>
<RechartsLineChart
data={result.plotData}
margin={{ top: 20, right: 30, left: 0, bottom: 20 }}
>
<CartesianGrid strokeDasharray="3 3" stroke="hsl(var(--border))" />
<XAxis
type="number"
dataKey="x"
name="X"
allowDuplicatedCategory={false}
domain={['dataMin - 1', 'dataMax + 1']}
tickFormatter={(tick) => typeof tick === 'number' ? tick.toFixed(2) : tick}
stroke="hsl(var(--muted-foreground))"
/>
<YAxis
type="number"
name="Y"
domain={['auto', 'auto']}
tickFormatter={(tick) => typeof tick === 'number' ? tick.toFixed(2) : tick}
stroke="hsl(var(--muted-foreground))"
/>
<RechartsTooltip
content={<ChartTooltipContent
hideLabel
formatter={(value, name, props) => {
if (props.payload?.target !== undefined && name === 'target') return [`L(${props.payload.x.toFixed(2)}) = ${props.payload.target.toFixed(4)}`, null];
if (name === 'original') return [value.toFixed(4), 'Original Point'];
if (name === 'interpolated') return [value.toFixed(4), 'Polynomial P(x)'];
return [value, name];
}}
/>}
cursor={{ stroke: "hsl(var(--accent))", strokeDasharray: '3 3' }}
wrapperStyle={{ outline: 'none', border: '1px solid hsl(var(--border))', borderRadius: 'var(--radius)', boxShadow: 'var(--shadow-md)'}}
/>
<RechartsLegend content={<ChartLegendContent />} wrapperStyle={{paddingTop: '20px'}} />
<Line
type="monotone"
dataKey="interpolated"
stroke="var(--color-interpolated)"
strokeWidth={2.5}
dot={false}
name={chartConfig.interpolated.label}
animationDuration={500}
/>
<Scatter
dataKey="original"
fill="var(--color-original)"
name={chartConfig.original.label}
shape="circle"
r={5}
/>
{result.interpolationPoint !== undefined && result.interpolatedValue !== null && (
<ReferenceDot
x={result.interpolationPoint}
y={result.interpolatedValue}
r={7}
fill="var(--color-target)"
stroke="hsl(var(--background))"
strokeWidth={2}
ifOverflow="extendDomain"
isFront={true}
>
<RechartsLabel
value={`L(${result.interpolationPoint.toFixed(2)}) = ${result.interpolatedValue.toFixed(4)}`}
position="top"
offset={10}
fill="var(--color-target)"
fontSize="0.8rem"
fontWeight="bold"
/>
</ReferenceDot>
)}
{result.interpolationPoint !== undefined && result.interpolatedValue !== null && (
<>
<ReferenceLine x={result.interpolationPoint} stroke="var(--color-target)" strokeDasharray="4 4" strokeOpacity={0.7}>
<RechartsLabel value={`x=${result.interpolationPoint.toFixed(2)}`} position="insideTopRight" fill="var(--color-target)" fontSize="0.75rem"/>
</ReferenceLine>
<ReferenceLine y={result.interpolatedValue} stroke="var(--color-target)" strokeDasharray="4 4" strokeOpacity={0.7}>
<RechartsLabel value={`y=${result.interpolatedValue.toFixed(2)}`} position="insideTopRight" fill="var(--color-target)" fontSize="0.75rem"/>
</ReferenceLine>
</>
)}
</RechartsLineChart>
</ChartContainer>
</CardContent>
</Card>
)}
</>
)}
</form>
</Form>
);
}