Higher-order integrals are now supported.

This commit is contained in:
That_One_Nerd 2024-03-19 12:34:31 -04:00
parent f5107b7238
commit 9b4905233c
6 changed files with 132 additions and 46 deletions

View File

@ -4,5 +4,5 @@ namespace Graphing.Abstract;
public interface IDerivable public interface IDerivable
{ {
public Equation Derive(); public Graphable Derive();
} }

View File

@ -4,5 +4,5 @@ namespace Graphing.Abstract;
public interface IIntegrable public interface IIntegrable
{ {
public IntegralEquation Integrate(); public Graphable Integrate();
} }

View File

@ -1,5 +1,4 @@
using Graphing.Abstract; using Graphing.Abstract;
using Graphing.Graphables;
using Graphing.Parts; using Graphing.Parts;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;

View File

@ -49,12 +49,12 @@ public class Equation : Graphable, IIntegrable, IDerivable
return lines; return lines;
} }
public Equation Derive() => new(x => public Graphable Derive() => new Equation(x =>
{ {
const double step = 1e-3; const double step = 1e-3;
return (equ(x + step) - equ(x)) / step; return (equ(x + step) - equ(x)) / step;
}); });
public IntegralEquation Integrate() => new(this); public Graphable Integrate() => new IntegralEquation(this);
public EquationDelegate GetDelegate() => equ; public EquationDelegate GetDelegate() => equ;
@ -71,8 +71,6 @@ public class Equation : Graphable, IIntegrable, IDerivable
} }
} }
// Pretty sure this works. Certainly works pretty well with "hard-to-compute"
// equations.
protected (double dist, double y, int index) NearestCachedPoint(double x) protected (double dist, double y, int index) NearestCachedPoint(double x)
{ {
if (cache.Count == 0) return (double.PositiveInfinity, double.NaN, -1); if (cache.Count == 0) return (double.PositiveInfinity, double.NaN, -1);

View File

@ -3,13 +3,18 @@ using Graphing.Forms;
using Graphing.Parts; using Graphing.Parts;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.Design;
namespace Graphing.Graphables; namespace Graphing.Graphables;
public class IntegralEquation : Graphable, IIntegrable, IDerivable public class IntegralEquation : Graphable, IIntegrable, IDerivable
{ {
protected readonly Equation baseEqu; protected readonly Equation? baseEqu;
protected readonly EquationDelegate baseEquDel; protected readonly EquationDelegate? baseEquDel;
protected readonly IntegralEquation? altBaseEqu;
protected readonly bool usingAlt;
public IntegralEquation(Equation baseEquation) public IntegralEquation(Equation baseEquation)
{ {
@ -22,9 +27,27 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable
baseEqu = baseEquation; baseEqu = baseEquation;
baseEquDel = baseEquation.GetDelegate(); baseEquDel = baseEquation.GetDelegate();
altBaseEqu = null;
usingAlt = false;
}
public IntegralEquation(IntegralEquation baseEquation)
{
string oldName = baseEquation.Name, newName;
if (oldName.StartsWith("Integral of ")) newName = "Second Integral of " + oldName[12..];
else if (oldName.StartsWith("Second Integral of ")) newName = "Third Integral of " + oldName[19..];
else newName = "Integral of " + oldName;
Name = newName;
baseEqu = null;
baseEquDel = null;
altBaseEqu = baseEquation;
usingAlt = true;
} }
public override Graphable DeepCopy() => new IntegralEquation(baseEqu); public override Graphable DeepCopy() => new IntegralEquation(this);
public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph) public override IEnumerable<IGraphPart> GetItemsToRender(in GraphForm graph)
{ {
@ -35,45 +58,42 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable
List<IGraphPart> lines = []; List<IGraphPart> lines = [];
Int2 originLocation = graph.GraphSpaceToScreenSpace(new Float2(0, 0)); Int2 originLocation = graph.GraphSpaceToScreenSpace(new Float2(0, 0));
if (originLocation.x < 0) if (originLocation.x < 0)
{ {
// Origin is off the left side of the screen. // Origin is off the left side of the screen.
// Get to the left side from the origin. // Get to the left side from the origin.
double previousY = 0;
double start = graph.MinVisibleGraph.x, end = graph.MaxVisibleGraph.x; double start = graph.MinVisibleGraph.x, end = graph.MaxVisibleGraph.x;
for (double x = 0; x <= start; x += epsilon) previousY += baseEquDel(x) * epsilon; SetInternalStepper(start, epsilon, null);
// Now we can start. // Now we can start.
double previousX = start; double previousX = stepX;
double previousY = stepY;
for (double x = start; x <= end; x += epsilon) for (double x = start; x <= end; x += epsilon)
{ {
double currentX = x, currentY = previousY + baseEquDel(x) * epsilon; MoveInternalStepper(epsilon);
lines.Add(new GraphLine(new Float2(previousX, previousY), new Float2(currentX, currentY))); lines.Add(new GraphLine(new Float2(previousX, previousY),
new Float2(stepX, stepY)));
previousX = currentX; previousX = stepX;
previousY = currentY; previousY = stepY;
} }
} }
else if (originLocation.x > graph.ClientRectangle.Width) else if (originLocation.x > graph.ClientRectangle.Width)
{ {
// Origin is off the right side of the screen. // Origin is off the right side of the screen.
// Get to the right side of the origin. // Get to the right side of the origin.
double previousY = 0;
double start = graph.MaxVisibleGraph.x, end = graph.MinVisibleGraph.x; double start = graph.MaxVisibleGraph.x, end = graph.MinVisibleGraph.x;
for (double x = 0; x >= start; x -= epsilon) previousY -= baseEquDel(x) * epsilon; SetInternalStepper(start, epsilon, null);
// Now we can start. // Now we can start.
double previousX = start; double previousX = stepX;
double previousY = stepY;
for (double x = start; x >= end; x -= epsilon) for (double x = start; x >= end; x -= epsilon)
{ {
double currentX = x, currentY = previousY - baseEquDel(x) * epsilon; MoveInternalStepper(-epsilon);
lines.Add(new GraphLine(new Float2(previousX, previousY), new Float2(currentX, currentY))); lines.Add(new GraphLine(new Float2(previousX, previousY),
new Float2(stepX, stepY)));
previousX = currentX; previousX = stepX;
previousY = currentY; previousY = stepY;
} }
} }
else else
@ -83,45 +103,93 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable
// Start with right. // Start with right.
double start = 0, end = graph.MaxVisibleGraph.x; double start = 0, end = graph.MaxVisibleGraph.x;
double previousX = start; SetInternalStepper(start, epsilon, null);
double previousY = 0;
double previousX = stepX;
double previousY = stepY;
for (double x = start; x <= end; x += epsilon) for (double x = start; x <= end; x += epsilon)
{ {
double currentX = x, currentY = previousY + baseEquDel(x) * epsilon; MoveInternalStepper(epsilon);
lines.Add(new GraphLine(new Float2(previousX, previousY), new Float2(currentX, currentY))); lines.Add(new GraphLine(new Float2(previousX, previousY),
new Float2(stepX, stepY)));
previousX = currentX; previousX = stepX;
previousY = currentY; previousY = stepY;
} }
// Now do left. // Now do left.
start = 0; start = 0;
end = graph.MinVisibleGraph.x; end = graph.MinVisibleGraph.x;
previousX = start; SetInternalStepper(start, epsilon, null);
previousY = 0;
previousX = stepX;
previousY = stepY;
for (double x = start; x >= end; x -= epsilon) for (double x = start; x >= end; x -= epsilon)
{ {
double currentX = x, currentY = previousY - baseEquDel(x) * epsilon; MoveInternalStepper(-epsilon);
lines.Add(new GraphLine(new Float2(previousX, previousY), new Float2(currentX, currentY))); lines.Add(new GraphLine(new Float2(previousX, previousY),
new Float2(stepX, stepY)));
previousX = currentX; previousX = stepX;
previousY = currentY; previousY = stepY;
} }
} }
return lines; return lines;
} }
private double stepX = 0;
private double stepY = 0;
private void SetInternalStepper(double x, double dX, Action<double, double>? stepCallback)
{
stepX = 0;
stepY = 0;
if (usingAlt) altBaseEqu!.SetInternalStepper(0, dX, null);
if (x > 0)
{
while (stepX < x)
{
MoveInternalStepper(dX);
stepCallback?.Invoke(stepX, stepY);
}
}
else if (x < 0)
{
while (x < stepX)
{
MoveInternalStepper(-dX);
stepCallback?.Invoke(stepX, stepY);
}
}
}
private void MoveInternalStepper(double dX)
{
stepX += dX;
if (usingAlt)
{
altBaseEqu!.MoveInternalStepper(dX);
stepY += altBaseEqu!.stepY * dX;
}
else
{
stepY += baseEquDel!(stepX) * dX;
}
}
// Try to avoid using this, as it converts the integral into a
// far less efficient format (uses the `IntegralAtPoint` method).
public Equation AsEquation() => new(IntegralAtPoint) public Equation AsEquation() => new(IntegralAtPoint)
{ {
Name = Name, Name = Name,
Color = Color Color = Color
}; };
public Equation Derive() => (Equation)baseEqu.DeepCopy(); public Graphable Derive()
public IntegralEquation Integrate() => AsEquation().Integrate(); {
if (usingAlt) return altBaseEqu!.DeepCopy();
else return (Equation)baseEqu!.DeepCopy();
}
public Graphable Integrate() => new IntegralEquation(this);
// Standard integral method. // Standard integral method.
// Inefficient for successive calls. // Inefficient for successive calls.
@ -138,4 +206,21 @@ public class IntegralEquation : Graphable, IIntegrable, IDerivable
return sum; return sum;
} }
public override bool ShouldSelectGraphable(in GraphForm graph, Float2 graphMousePos, double factor)
{
Int2 screenMousePos = graph.GraphSpaceToScreenSpace(graphMousePos);
Int2 screenPos = graph.GraphSpaceToScreenSpace(new Float2(graphMousePos.x,
IntegralAtPoint(graphMousePos.x)));
double allowedDist = factor * graph.DpiFloat * 80 / 192;
Int2 dist = new(screenPos.x - screenMousePos.x,
screenPos.y - screenMousePos.y);
double totalDist = Math.Sqrt(dist.x * dist.x + dist.y * dist.y);
return totalDist <= allowedDist;
}
public override Float2 GetSelectedPoint(in GraphForm graph, Float2 graphMousePos) =>
new(graphMousePos.x, IntegralAtPoint(graphMousePos.x));
} }

View File

@ -17,7 +17,9 @@ internal static class Program
GraphForm graph = new("One Of The Graphing Calculators Of All Time"); GraphForm graph = new("One Of The Graphing Calculators Of All Time");
Equation equ = new(Math.Sin); Equation equ = new(Math.Sin);
graph.Graph(equ); SlopeField sf = new(2, (x, y) => Math.Cos(x));
TangentLine tl = new(2, 2, equ);
graph.Graph(equ, sf, tl);
// You can preload graphs in by going Misc > Preload Cache. // You can preload graphs in by going Misc > Preload Cache.
// Keep in mind this uses more memory than usual and can take // Keep in mind this uses more memory than usual and can take
@ -26,6 +28,8 @@ internal static class Program
// Integrating equations is now much smoother and less intensive. // Integrating equations is now much smoother and less intensive.
// Try it out! // Try it out!
// You can click and drag on an equation to select specific points.
Application.Run(graph); Application.Run(graph);
} }
} }