Ejemplo n.º 1
0
        public void OdeLorenz()
        {
            double sigma = 10.0;
            double beta  = 8.0 / 3.0;
            double rho   = 28.0;

            Func <double, IReadOnlyList <double>, IReadOnlyList <double> > rhs = (double t, IReadOnlyList <double> u) =>
                                                                                 new ColumnVector(
                sigma * (u[1] - u[0]),
                u[0] * (rho - u[2]) - u[1],
                u[0] * u[1] - beta * u[2]
                );

            MultiOdeSettings settings = new MultiOdeSettings()
            {
                RelativePrecision = 1.0E-8,
                EvaluationBudget  = 10000
            };

            ColumnVector u0 = new ColumnVector(1.0, 1.0, 1.0);

            MultiOdeResult result = MultiFunctionMath.IntegrateOde(rhs, 0.0, u0, 10.0, settings);

            Console.WriteLine(result.EvaluationCount);

            // Is there anything we can assert? There is no analytic solution or conserved quantity.
        }
Ejemplo n.º 2
0
        public void OdeLotkaVolterra()
        {
            // Lotka/Volterra equations are a non-linear predator-prey model.
            //   \dot{x} = A x + B x y
            //   \dot{y} = -C y + D x y
            // See http://mathworld.wolfram.com/Lotka-VolterraEquations.html

            // It can be shown solutions are always periodic (but not sinusoidal, and
            // often with phase shift between x and y).

            // Equilibria are x, y = C/D, A /B (and 0, 0).
            // If started positive, x and y never go negative.
            // A conserved quantity is: - D x + C log x - B y + A log y
            // Period of equations linearized around equilibrium is 2 \pi / \sqrt{AC}.

            // Can also be used to model chemical reaction rates.

            double A = 1.5; // Prey growth rate
            double B = 1.0;
            double C = 3.0; // Predator death rate
            double D = 1.0;

            // Try A = 20, B = 1, C = 30, D = 1, X = 8, Y = 12, T = 1
            // Try A = 1.5, B = 1 C = 3, D = 1, X = 10, Y = 5, T = 10

            Func <double, IReadOnlyList <double>, IReadOnlyList <double> > rhs = (double t, IReadOnlyList <double> p) =>
                                                                                 new double[] {
                A *p[0] - B * p[0] * p[1],
                -C *p[1] + D * p[0] * p[1]
            };

            Func <IList <double>, double> conservedQuantity = (IList <double> p) =>
                                                              - D * p[0] + C * Math.Log(p[0]) - B * p[1] + A * Math.Log(p[1]);

            ColumnVector p0 = new ColumnVector(10.0, 5.0);

            double L0 = conservedQuantity(p0);

            // Set up a handler that verifies conservation and positivity
            MultiOdeSettings settings = new MultiOdeSettings()
            {
                Listener = (MultiOdeResult rr) => {
                    double L = conservedQuantity(rr.Y);
                    Assert.IsTrue(rr.Y[0] > 0.0);
                    Assert.IsTrue(rr.Y[1] > 0.0);
                    Assert.IsTrue(TestUtilities.IsNearlyEqual(L, L0, rr.Settings));
                }
            };

            // Estimate period
            double T = 2.0 * Math.PI / Math.Sqrt(A * C);

            // Integrate over a few estimated periods
            MultiOdeResult result = MultiFunctionMath.IntegrateOde(rhs, 0.0, p0, 3.0 * T, settings);

            Console.WriteLine(result.EvaluationCount);
        }
Ejemplo n.º 3
0
        public static void IntegrateOde()
        {
            Func <double, double, double> rhs = (x, y) => - x * y;
            OdeResult sln = FunctionMath.IntegrateOde(rhs, 0.0, 1.0, 2.0);

            Console.WriteLine($"Numeric solution y({sln.X}) = {sln.Y}.");
            Console.WriteLine($"Required {sln.EvaluationCount} evaluations.");
            Console.WriteLine($"Analytic solution y({sln.X}) = {Math.Exp(-MoreMath.Sqr(sln.X) / 2.0)}");

            // Lotka-Volterra equations
            double A = 0.1;
            double B = 0.02;
            double C = 0.4;
            double D = 0.02;
            Func <double, IReadOnlyList <double>, IReadOnlyList <double> > lkRhs = (t, y) => {
                return(new double[] {
                    A *y[0] - B * y[0] * y[1], D *y[0] * y[1] - C * y[1]
                });
            };
            MultiOdeSettings lkSettings = new MultiOdeSettings()
            {
                Listener = r => { Console.WriteLine($"t={r.X} rabbits={r.Y[0]}, foxes={r.Y[1]}"); }
            };

            MultiFunctionMath.IntegrateOde(lkRhs, 0.0, new double[] { 20.0, 10.0 }, 50.0, lkSettings);

            Func <double, IReadOnlyList <double>, IReadOnlyList <double> > rhs1 = (x, u) => {
                return(new double[] { u[1], -u[0] });
            };
            MultiOdeSettings settings1 = new MultiOdeSettings()
            {
                EvaluationBudget = 100000
            };
            MultiOdeResult result1 = MultiFunctionMath.IntegrateOde(
                rhs1, 0.0, new double[] { 0.0, 1.0 }, 500.0, settings1
                );
            double s1 = MoreMath.Sqr(result1.Y[0]) + MoreMath.Sqr(result1.Y[1]);

            Console.WriteLine($"y({result1.X}) = {result1.Y[0]}, (y)^2 + (y')^2 = {s1}");
            Console.WriteLine($"Required {result1.EvaluationCount} evaluations.");

            Func <double, double, double> rhs2 = (x, y) => - y;
            OdeSettings settings2 = new OdeSettings()
            {
                EvaluationBudget = 100000
            };
            OdeResult result2 = FunctionMath.IntegrateConservativeOde(
                rhs2, 0.0, 0.0, 1.0, 500.0, settings2
                );
            double s2 = MoreMath.Sqr(result2.Y) + MoreMath.Sqr(result2.YPrime);

            Console.WriteLine($"y({result2.X}) = {result2.Y}, (y)^2 + (y')^2 = {s2}");
            Console.WriteLine($"Required {result2.EvaluationCount} evaluations");

            Console.WriteLine(MoreMath.Sin(500.0));
        }
Ejemplo n.º 4
0
        public void OdeOrbitKepler()
        {
            // This is a simple Keplerian orbit.
            // Hull (1972) constructed initial conditions that guarantee a given orbital eccentricity
            // and a period of 2 \pi with unit masses and unit gravitational constant.

            Func <double, IReadOnlyList <double>, IReadOnlyList <double> > rhs = (double t, IReadOnlyList <double> r) => {
                double d  = MoreMath.Hypot(r[0], r[1]);
                double d3 = MoreMath.Pow(d, 3);
                return(new double[] { -r[0] / d3, -r[1] / d3 });
            };

            foreach (double e in new double[] { 0.0, 0.1, 0.3, 0.5, 0.7, 0.9 })
            {
                ColumnVector r0    = new ColumnVector(1.0 - e, 0.0);
                ColumnVector rDot0 = new ColumnVector(0.0, Math.Sqrt((1.0 + e) / (1.0 - e)));

                double E = OrbitEnergy(r0, rDot0);
                double L = OrbitAngularMomentum(r0, rDot0);

                MultiOdeSettings settings = new MultiOdeSettings()
                {
                    Listener = (MultiOdeResult b) => {
                        Assert.IsTrue(TestUtilities.IsNearlyEqual(OrbitAngularMomentum(b.Y, b.YPrime), L, b.Settings));
                        EvaluationSettings relaxed = new EvaluationSettings()
                        {
                            RelativePrecision = 2.0 * b.Settings.RelativePrecision, AbsolutePrecision = 2.0 * b.Settings.AbsolutePrecision
                        };
                        Assert.IsTrue(TestUtilities.IsNearlyEqual(OrbitEnergy(b.Y, b.YPrime), E, relaxed));
                    }
                };


                MultiOdeResult result = MultiFunctionMath.IntegrateConservativeOde(rhs, 0.0, r0, rDot0, 2.0 * Math.PI, settings);
                ColumnVector   r1     = result.Y;

                Console.WriteLine(result.EvaluationCount);

                Assert.IsTrue(TestUtilities.IsNearlyEqual(r0, r1, new EvaluationSettings()
                {
                    RelativePrecision = 512 * result.Settings.RelativePrecision
                }));

                // For large eccentricities, we loose precision. This is apparently typical behavior.
                // Would be nice if we could quantify expected loss, or correct to do better.
            }
        }
Ejemplo n.º 5
0
        public void OdeSolarSystem()
        {
            Planet[] planets = new Planet[] {
                new Planet()
                {
                    Name     = "Sun",
                    Mass     = 1.3271244004193938E11 * 2.22972472E-15,
                    Position = new ColumnVector(3.700509269632818E-03, 2.827000367199164E-03, -1.623212133858169E-04),
                    Velocity = new ColumnVector(-1.181051079944745E-06, 7.011580463060376E-06, 1.791336618633265E-08),
                    Year     = 0.0
                },
                new Planet()
                {
                    Name     = "Earth",
                    Mass     = 398600.440 * 2.22972472E-15,
                    Position = new ColumnVector(5.170309635282939E-01, -8.738395510520275E-01, -1.323433043109283E-04),
                    Velocity = new ColumnVector(1.456221287512929E-02, 8.629625079574064E-03, 2.922661879104068E-07),
                    Year     = 365.25636
                },
                new Planet()
                {
                    Name     = "Jupiter",
                    Mass     = 126686511 * 2.22972472E-15,
                    Position = new ColumnVector(-5.438893557878444E+00, 1.497713688978628E-01, 1.210124167423688E-01),
                    Velocity = new ColumnVector(-2.955588275385831E-04, -7.186425047188191E-03, 3.648630400553893E-05),
                    Year     = 4332.59
                },
                new Planet()
                {
                    Name     = "Saturn",
                    Mass     = 37931207.8 * 2.22972472E-15,
                    Position = new ColumnVector(-2.695377264613252E+00, -9.657410990313211E+00, 2.751886819002198E-01),
                    Velocity = new ColumnVector(5.067197776417300E-03, -1.516587142748002E-03, -1.754902991309407E-04),
                    Year     = 10759.22
                }
            };

            Func <double, IReadOnlyList <double>, IReadOnlyList <double> > rhs = (double t, IReadOnlyList <double> p) => {
                ColumnVector a = new ColumnVector(3 * planets.Length);

                for (int i = 0; i < planets.Length; i++)
                {
                    for (int j = 0; j < planets.Length; j++)
                    {
                        if (i == j)
                        {
                            continue;
                        }

                        double x  = p[3 * i] - p[3 * j];
                        double y  = p[3 * i + 1] - p[3 * j + 1];
                        double z  = p[3 * i + 2] - p[3 * j + 2];
                        double d3 = Math.Pow(x * x + y * y + z * z, 3.0 / 2.0);

                        a[3 * i]     -= planets[j].Mass * x / d3;
                        a[3 * i + 1] -= planets[j].Mass * y / d3;
                        a[3 * i + 2] -= planets[j].Mass * z / d3;
                    }
                }

                return(a);
            };

            ColumnVector p0 = new ColumnVector(3 * planets.Length);

            for (int i = 0; i < planets.Length; i++)
            {
                p0[3 * i]     = planets[i].Position[0];
                p0[3 * i + 1] = planets[i].Position[1];
                p0[3 * i + 2] = planets[i].Position[2];
            }

            ColumnVector q0 = new ColumnVector(3 * planets.Length);

            for (int i = 0; i < planets.Length; i++)
            {
                q0[3 * i]     = planets[i].Velocity[0];
                q0[3 * i + 1] = planets[i].Velocity[1];
                q0[3 * i + 2] = planets[i].Velocity[2];
            }

            MultiOdeSettings settings = new MultiOdeSettings()
            {
                RelativePrecision = 1.0E-8,
                AbsolutePrecision = 1.0E-16
            };

            MultiOdeResult result = MultiFunctionMath.IntegrateConservativeOde(rhs, 0.0, p0, q0, 4332.59, settings);
        }
Ejemplo n.º 6
0
        public void OdeOrbitTriangle()
        {
            // Lagrange described three-body systems where each body sits at the vertex
            // of an equilateral triangle. Each body executes an elliptical orbit
            // and the triangle remains equilateral, although its size can change.

            // The case of equal masses on a circle is easily analytically tractable.
            // In this case the orbits are all along the same circle and the
            // the triangle doesn't change size, it just rotates.

            Func <double, IReadOnlyList <double>, IReadOnlyList <double> > rhs = (double t, IReadOnlyList <double> r) => {
                double x01 = r[0] - r[1];
                double x02 = r[0] - r[2];
                double x12 = r[1] - r[2];

                double y01 = r[3] - r[4];
                double y02 = r[3] - r[5];
                double y12 = r[4] - r[5];

                double r01 = MoreMath.Pow(MoreMath.Hypot(x01, y01), 3);
                double r02 = MoreMath.Pow(MoreMath.Hypot(x02, y02), 3);
                double r12 = MoreMath.Pow(MoreMath.Hypot(x12, y12), 3);

                return(new double[] {
                    -x01 / r01 - x02 / r02,
                    x01 / r01 - x12 / r12,
                    x02 / r02 + x12 / r12,
                    -y01 / r01 - y02 / r02,
                    y01 / r01 - y12 / r12,
                    y02 / r02 + y12 / r12
                });
            };

            double L = 1.0;
            double R = L / Math.Sqrt(3.0);
            double v = Math.Sqrt(1.0 / L);
            double T = 2.0 * Math.PI * Math.Sqrt(L * L * L / 3.0);

            double c = 1.0 / 2.0;
            double s = Math.Sqrt(3.0) / 2.0;

            ColumnVector r0 = new ColumnVector(R, -c * R, -c * R, 0.0, s * R, -s * R);

            double[] rp0 = new double[] { 0.0, -s * v, s *v, v, -c * v, -c * v };

            int count = 0;
            MultiOdeSettings settings = new MultiOdeSettings()
            {
                RelativePrecision = 1.0E-12,
                AbsolutePrecision = 1.0E-12,
                Listener          = (MultiOdeResult mer) => {
                    count++;
                    ColumnVector r = mer.Y;

                    double x01 = r[0] - r[1];
                    double x02 = r[0] - r[2];
                    double x12 = r[1] - r[2];

                    double y01 = r[3] - r[4];
                    double y02 = r[3] - r[5];
                    double y12 = r[4] - r[5];

                    Assert.IsTrue(TestUtilities.IsNearlyEqual(MoreMath.Hypot(x01, y01), L, mer.Settings));
                    Assert.IsTrue(TestUtilities.IsNearlyEqual(MoreMath.Hypot(x02, y02), L, mer.Settings));
                    Assert.IsTrue(TestUtilities.IsNearlyEqual(MoreMath.Hypot(x12, y12), L, mer.Settings));
                }
            };

            MultiOdeResult result = MultiFunctionMath.IntegrateConservativeOde(rhs, 0.0, r0, rp0, T, settings);

            // Test that one period brought us back to the same place.
            Assert.IsTrue(TestUtilities.IsNearlyEqual(r0, result.Y, settings));

            Assert.IsTrue(count > 0);
        }
Ejemplo n.º 7
0
        public void OdeEuler()
        {
            // Euler's equations for rigid body motion (without external forces) are
            //   I_1 \dot{\omega}_1  = (I_2 - I_3) \omega_2 \omega_3
            //   I_2 \dot{\omega}_2  = (I_3 - I_1) \omega_3 \omega_1
            //   I_3 \dot{\omega}_3  = (I_1 - I_2) \omega_1 \omega_2
            // where \omega's are rotations about the principal axes and I's are the moments
            // of inertia about those axes. The rotational kinetic energy
            //   I_1 \omega_1^2 + I_2 \omega_2^2 + I_3 \omega_3^2 = 2 E
            // and angular momentum
            //   I_1^2 \omega_1^2 + I_2^2 \omega_2^2 + I_3^2 \omega_3^2 = L^2
            // are conserved quantities.

            // Landau & Lifshitz, Mechanics (3rd edition) solves these equations.

            // Note
            //   (\cn u)' = - (\sn u)(\dn u)
            //   (\sn u)' = (\dn u)(\cn u)
            //   (\dn u)' = -k^2 (\cn u)(\sn u)
            // So a solution with \omega_1 ~ \cn, \omega_2 ~ \sn, \omega_3 ~ \dn
            // would seem to work, if we can re-scale variables to eliminate factors.
            // In fact, this turns out to be the case.

            // Label axis so that I_3 > I_2 > I_1. If L^2 > 2 E I_2, define
            //   v^2 = \frac{(I_3 - I_2)(L^2 - 2E I_1)}{I_1 I_2 I_3}
            //   k^2 = \frac{(I_2 - I_1)(2E I_3 - L^2)}{(I_3 - I_2)(L^2 - 2E I_1)}
            //   A_1^2 = \frac{2E I_3 - L^2}{I_1 (I_3 - I_1)}
            //   A_2^2 = \frac{2E I_3 - L^2}{I_2 (I_3 - I_2)}
            //   A_3^2 = \frac{L^2 - 2E I_1}{I_3 (I_3 - I_1)}
            // (If L^2 < 2 E I_2, just switch subscripts 1 <-> 3). Then
            //   \omega_1 = A_1 \cn (c t, k)
            //   \omega_2 = A_2 \sn (c t, k)
            //   \omega_3 = A_3 \dn (c t, k)
            // Period is complete when v T = 4 K.

            ColumnVector I = new ColumnVector(3.0, 4.0, 5.0);

            Func <double, IReadOnlyList <double>, IReadOnlyList <double> > rhs = (double t, IReadOnlyList <double> w) => {
                return(new ColumnVector((I[1] - I[2]) * w[1] * w[2] / I[0], (I[2] - I[0]) * w[2] * w[0] / I[1], (I[0] - I[1]) * w[0] * w[1] / I[2]));
            };

            ColumnVector w0 = new ColumnVector(6.0, 0.0, 1.0);

            // Determine L^2 and 2 E
            double L2 = EulerAngularMomentum(I, w0);
            double E2 = EulerKineticEnergy(I, w0);

            // As Landau points out, these inequalities should be ensured by the definitions of E and L.
            Debug.Assert(E2 * I[0] < L2);
            Debug.Assert(L2 < E2 * I[2]);

            double v, k;
            Func <double, ColumnVector> sln;

            if (L2 > E2 * I[1])
            {
                v = Math.Sqrt((I[2] - I[1]) * (L2 - E2 * I[0]) / I[0] / I[1] / I[2]);
                k = Math.Sqrt((I[1] - I[0]) * (E2 * I[2] - L2) / (I[2] - I[1]) / (L2 - E2 * I[0]));
                ColumnVector A = new ColumnVector(
                    Math.Sqrt((E2 * I[2] - L2) / I[0] / (I[2] - I[0])),
                    Math.Sqrt((E2 * I[2] - L2) / I[1] / (I[2] - I[1])),
                    Math.Sqrt((L2 - E2 * I[0]) / I[2] / (I[2] - I[0]))
                    );
                sln = t => new ColumnVector(
                    A[0] * AdvancedMath.JacobiCn(v * t, k),
                    A[1] * AdvancedMath.JacobiSn(v * t, k),
                    A[2] * AdvancedMath.JacobiDn(v * t, k)
                    );
            }
            else
            {
                v = Math.Sqrt((I[0] - I[1]) * (L2 - E2 * I[2]) / I[0] / I[1] / I[2]);
                k = Math.Sqrt((I[1] - I[2]) * (E2 * I[0] - L2) / (I[0] - I[1]) / (L2 - E2 * I[2]));
                ColumnVector A = new ColumnVector(
                    Math.Sqrt((L2 - E2 * I[2]) / I[0] / (I[0] - I[2])),
                    Math.Sqrt((E2 * I[0] - L2) / I[1] / (I[0] - I[1])),
                    Math.Sqrt((E2 * I[0] - L2) / I[2] / (I[0] - I[2]))
                    );
                sln = t => new ColumnVector(
                    A[0] * AdvancedMath.JacobiDn(v * t, k),
                    A[1] * AdvancedMath.JacobiSn(v * t, k),
                    A[2] * AdvancedMath.JacobiCn(v * t, k)
                    );
            }

            Debug.Assert(k < 1.0);

            double T = 4.0 * AdvancedMath.EllipticK(k) / v;

            int listenerCount         = 0;
            MultiOdeSettings settings = new MultiOdeSettings()
            {
                Listener = (MultiOdeResult q) => {
                    listenerCount++;

                    // Verify that energy and angular momentum conservation is respected
                    Assert.IsTrue(TestUtilities.IsNearlyEqual(EulerKineticEnergy(I, q.Y), E2, q.Settings));
                    Assert.IsTrue(TestUtilities.IsNearlyEqual(EulerAngularMomentum(I, q.Y), L2, q.Settings));

                    // Verify that the result agrees with the analytic solution
                    //ColumnVector YP = new ColumnVector(
                    //    A[0] * AdvancedMath.JacobiCn(v * q.X, k),
                    //    A[1] * AdvancedMath.JacobiSn(v * q.X, k),
                    //    A[2] * AdvancedMath.JacobiDn(v * q.X, k)
                    //);
                    Assert.IsTrue(TestUtilities.IsNearlyEqual(q.Y, sln(q.X), q.Settings));
                }
            };
            MultiOdeResult result = MultiFunctionMath.IntegrateOde(rhs, 0.0, w0, T, settings);

            Console.WriteLine(result.EvaluationCount);
            //MultiOdeResult r2 = NewMethods.IntegrateOde(rhs, 0.0, w0, T, settings);

            // Verify that one period of evolution has brought us back to our initial state
            Assert.IsTrue(TestUtilities.IsNearlyEqual(result.X, T));
            Assert.IsTrue(TestUtilities.IsNearlyEqual(result.Y, w0, settings));
            Assert.IsTrue(listenerCount > 0);
        }