Example #1
0
        /// <summary>
        /// Destroys the simulation.
        /// </summary>
        protected override void Unsetup()
        {
            // Remove references
            for (var i = 0; i < _transientBehaviors.Count; i++)
            {
                _transientBehaviors[i].Unsetup(this);
            }
            _transientBehaviors = null;
            for (var i = 0; i < _acceptBehaviors.Count; i++)
            {
                _acceptBehaviors[i].Unsetup(this);
            }
            _acceptBehaviors = null;

            // Destroy the integration method
            Method.Unsetup(this);
            Method = null;

            // Destroy the initial conditions
            AfterLoad -= LoadInitialConditions;
            foreach (var ic in _initialConditions)
            {
                ic.Unsetup();
            }
            _initialConditions.Clear();

            base.Unsetup();
        }
Example #2
0
        /// <summary>
        /// Compute expressions for y[t + h] in terms of y[t], the resulting expressions will be implicit for implicit methods.
        /// </summary>
        /// <param name="dy_dt">Equations to solve.</param>
        /// <param name="y">Functions to solve for.</param>
        /// <param name="t">Independent variable.</param>
        /// <param name="h">Step size.</param>
        /// <param name="method">Integration method to use for differential equations.</param>
        /// <returns>Expressions for y[t + h].</returns>
        public static IEnumerable<Arrow> NDIntegrate(this IEnumerable<Arrow> dy_dt, Expression t, Expression h, IntegrationMethod method)
        {
            switch (method)
            {
                // y[t + h] = y[t] + h*f[t, y[t]]
                case IntegrationMethod.Euler:
                    return dy_dt.Select(i => Arrow.New(
                        DOf(i.Left).Evaluate(t, t + h),
                        DOf(i.Left) + h * i.Right));

                // y[t + h] = y[t] + h*f[t + h, y[t + h]]
                case IntegrationMethod.BackwardEuler:
                    return dy_dt.Select(i => Arrow.New(
                        DOf(i.Left).Evaluate(t, t + h),
                        DOf(i.Left) + h * i.Right.Evaluate(t, t + h)));

                // y[t + h] = y[t] + (h/2)*(f[t, y[t]] + f[t + h, y[t + h]])
                case IntegrationMethod.Trapezoid:
                    return dy_dt.Select(i => Arrow.New(
                        DOf(i.Left).Evaluate(t, t + h),
                        DOf(i.Left) + (h / 2) * (i.Right + i.Right.Evaluate(t, t + h))));

                default:
                    throw new NotImplementedException(method.ToString());
            }
        }
Example #3
0
        /// <summary>
        ///   Applies device impact on the circuit equation system. If behavior of the device is nonlinear, this method is
        ///   called once every Newton-Raphson iteration.
        /// </summary>
        /// <param name="context">Context of current simulation.</param>
        public override void ApplyModelValues(ISimulationContext context)
        {
            vt = Parameters.EmissionCoefficient * PhysicalConstants.Boltzmann *
                 PhysicalConstants.CelsiusToKelvin(Parameters.NominalTemperature) /
                 PhysicalConstants.DevicearyCharge;

            gmin = Parameters.MinimalResistance ?? context.SimulationParameters.MinimalResistance;
            smallBiasTreshold   = -5 * vt;
            capacitanceTreshold = Parameters.ForwardBiasDepletionCapacitanceCoefficient * Parameters.JunctionPotential;

            var vd = Voltage - Parameters.SeriesResistance * Current;

            var(id, geq, cd) = GetModelValues(vd);
            var ieq = id - geq * vd;

            // Diode
            stamper.Stamp(geq, -ieq);

            // Capacitance
            var(cieq, cgeq) = IntegrationMethod.GetEquivalents(cd / context.TimeStep);

            if (initialConditionCapacitor)             // initial condition
            {
                capacitorStamper.Stamp(0, 0);
            }
            else
            {
                capacitorStamper.Stamp(cieq, cgeq);
            }

            Current     = id + ic;
            Conductance = geq;
        }
Example #4
0
 /// <summary>
 /// Create states
 /// </summary>
 /// <param name="method"></param>
 public void CreateStates(IntegrationMethod method)
 {
     if (method == null)
     {
         throw new ArgumentNullException(nameof(method));
     }
     QCap = method.CreateDerivative();
 }
        /// <summary>
        /// Create states
        /// </summary>
        /// <param name="method"></param>
        public override void CreateStates(IntegrationMethod method)
        {
            if (method == null)
            {
                throw new ArgumentNullException(nameof(method));
            }

            CapCharge = method.CreateDerivative();
        }
Example #6
0
        /// <summary>
        /// Bind the behavior.
        /// </summary>
        /// <param name="simulation">The simulation.</param>
        /// <param name="context">The data provider.</param>
        public override void Bind(Simulation simulation, BindingContext context)
        {
            base.Bind(simulation, context);

            // Get parameters
            _bp = context.GetParameterSet <BaseParameters>();

            // Get behaviors
            _tran = context.GetBehavior <TransientBehavior>();

            _method = ((TimeSimulation)simulation).Method;
        }
Example #7
0
        /// <summary>
        ///   Notifies model class that DC bias for given timepoint is established (i.e after Newton-Raphson iterations
        ///   converged).
        /// </summary>
        /// <param name="context">Context of current simulation.</param>
        public override void OnDcBiasEstablished(ISimulationContext context)
        {
            base.OnDcBiasEstablished(context);
            ic = capacitorStamper.GetCurrent();
            if (Math.Abs(context.TimeStep) < double.Epsilon)             // set initial condition for the capacitor
            {
                ic = Current;
            }
            vc = Voltage;

            IntegrationMethod.SetState(ic, vc);
            initialConditionCapacitor = false;             // capacitor no longer needs initial condition
        }
        /// <summary>
        /// Create states
        /// </summary>
        /// <param name="method"></param>
        public override void CreateStates(IntegrationMethod method)
        {
            if (method == null)
            {
                throw new ArgumentNullException(nameof(method));
            }

            // We just need a history without integration here
            StateChargeBe = method.CreateDerivative();
            StateChargeBc = method.CreateDerivative();
            StateChargeCs = method.CreateDerivative();

            // Spice 3f5 does not include this state for LTE calculations
            StateChargeBx = method.CreateDerivative(false);

            StateExcessPhaseCurrentBc = method.CreateHistory();
        }
Example #9
0
        /// <summary>
        /// Create states
        /// </summary>
        /// <param name="method"></param>
        public override void CreateStates(IntegrationMethod method)
        {
            if (method == null)
            {
                throw new ArgumentNullException(nameof(method));
            }

            VoltageBs = method.CreateHistory();
            VoltageGs = method.CreateHistory();
            VoltageDs = method.CreateHistory();
            CapGs     = method.CreateHistory();
            CapGd     = method.CreateHistory();
            CapGb     = method.CreateHistory();
            ChargeGs  = method.CreateDerivative();
            ChargeGd  = method.CreateDerivative();
            ChargeGb  = method.CreateDerivative();
            ChargeBd  = method.CreateDerivative();
            ChargeBs  = method.CreateDerivative();
        }
Example #10
0
        /// <summary>
        /// Create states
        /// </summary>
        /// <param name="method"></param>
        public override void CreateStates(IntegrationMethod method)
        {
            if (method == null)
            {
                throw new ArgumentNullException(nameof(method));
            }

            _vgs   = method.CreateHistory();
            _vds   = method.CreateHistory();
            _vbs   = method.CreateHistory();
            _capgs = method.CreateHistory();
            _capgd = method.CreateHistory();
            _capgb = method.CreateHistory();
            _qgs   = method.CreateDerivative();
            _qgd   = method.CreateDerivative();
            _qgb   = method.CreateDerivative();
            _qbd   = method.CreateDerivative();
            _qbs   = method.CreateDerivative();
        }
Example #11
0
        /// <summary>
        /// Accept the current time point
        /// </summary>
        /// <param name="ckt"></param>
        public override void Accept(Circuit ckt)
        {
            // Should not be here
            if (ckt.Method == null)
            {
                return;
            }

            IntegrationMethod method = ckt.Method;
            var breaks = method.Breaks;

            // Find the time relative to the first period
            double time     = method.Time - td;
            double basetime = 0.0;

            if (time >= per)
            {
                basetime = per * Math.Floor(time / per);
                time    -= basetime;
            }
            if (basetime == lastbasetime)
            {
                return;
            }
            lastbasetime = basetime;

            // Add all breakpoints for this period
            breaks.SetBreakpoint(basetime + td);
            breaks.SetBreakpoint(basetime + td + tr);
            breaks.SetBreakpoint(basetime + td + tr + pw);
            breaks.SetBreakpoint(basetime + td + tr + pw + tf);
            breaks.SetBreakpoint(basetime + td + per); // Start of the next period

            /*
             * NOTE:
             * Originally Spice only adds a breakpoint when the previous one has been reached.
             * The problem is that if the next breakpoint is too close (< MinBreak), it will
             * not be added which means that any subsequent breakpoints will be lost too.
             *
             * The same problem here will only occur if a whole period is < MinBreak.
             */
        }
Example #12
0
        public PhysicsEngine(Game1 g, IntegrationMethod integrationMethod)
            : base(g)
        {
            game = g;
            physicsObjects = new List<IPhysicsObject>();
            t = 0.0f;

            switch (integrationMethod)
            {
                case IntegrationMethod.Euler:
                    this.integrator = new EulerIntegrator();
                    break;
                case IntegrationMethod.RungeKutta4:
                    this.integrator = new RK4Integrator();
                    break;
                default:
                    this.integrator = new EulerIntegrator();
                    break;
            }
        }
        /// <summary>
        /// Setup behavior
        /// </summary>
        /// <param name="simulation">Simulation</param>
        /// <param name="provider">Data provider</param>
        public override void Setup(Simulation simulation, SetupDataProvider provider)
        {
            if (provider == null)
            {
                throw new ArgumentNullException(nameof(provider));
            }

            // Get parameters
            _bp  = provider.GetParameterSet <BaseParameters>();
            _mbp = provider.GetParameterSet <ModelBaseParameters>("model");

            // Get behaviors
            _temp      = provider.GetBehavior <TemperatureBehavior>();
            _load      = provider.GetBehavior <LoadBehavior>();
            _modeltemp = provider.GetBehavior <ModelTemperatureBehavior>("model");

            if (simulation is TimeSimulation ts)
            {
                _method = ts.Method;
            }
        }
Example #14
0
        /// <summary>
        /// Set up the simulation.
        /// </summary>
        /// <param name="circuit">The circuit that will be used.</param>
        /// <exception cref="ArgumentNullException">circuit</exception>
        /// <exception cref="SpiceSharp.CircuitException">
        /// {0}: No time configuration".FormatString(Name)
        /// or
        /// {0}: No integration method specified".FormatString(Name)
        /// </exception>
        protected override void Setup(Circuit circuit)
        {
            if (circuit == null)
            {
                throw new ArgumentNullException(nameof(circuit));
            }

            // Get base behaviors
            base.Setup(circuit);

            // Get behaviors and configurations
            var config = Configurations.Get <TimeConfiguration>() ?? throw new CircuitException("{0}: No time configuration".FormatString(Name));

            _useIc = config.UseIc;
            Method = config.Method ?? throw new CircuitException("{0}: No integration method specified".FormatString(Name));
            _transientBehaviors = SetupBehaviors <BaseTransientBehavior>(circuit.Entities);

            // Allow all transient behaviors to allocate equation elements and create states
            for (var i = 0; i < _transientBehaviors.Count; i++)
            {
                _transientBehaviors[i].GetEquationPointers(RealState.Solver);
                _transientBehaviors[i].CreateStates(Method);
            }
            Method.Setup(this);

            // TODO: Compatibility - initial conditions from nodes instead of configuration should be removed eventually
            if (config.InitialConditions.Count == 0)
            {
                foreach (var ns in Variables.InitialConditions)
                {
                    _initialConditions.Add(new ConvergenceAid(ns.Key, ns.Value));
                }
            }

            // Set up initial conditions
            foreach (var ic in config.InitialConditions)
            {
                _initialConditions.Add(new ConvergenceAid(ic.Key, ic.Value));
            }
        }
        /// <summary>
        /// Численное итегрирование.
        /// </summary>
        /// <param name="func">Функция, для которой нужно вычислить интеграл.</param>
        /// <param name="boundaries">Отрезок, на котором выполняется интегрирование.</param>
        /// <param name="n">Количество разбиений отрезка.</param>
        /// <param name="integrationMethod">Метод интегрирования.</param>
        /// <returns>Результат интегрирования.</returns>
        public static double Integrate(Func <double, object[], double> func, object[] args,
                                       Boundaries boundaries, IntegrationMethod integrationMethod)
        {
            double result;

            switch (integrationMethod)
            {
            case IntegrationMethod.Rectangles:
                result = RectangleIntegration(func, args, boundaries);
                break;

            case IntegrationMethod.Trapezium:
                result = TrapeziumIntegration(func, args, boundaries);
                break;

            default:
                result = double.MinValue;
                break;
            }

            return(result);
        }
Example #16
0
 /// <summary>
 /// Creates all necessary states for the transient behavior.
 /// </summary>
 /// <param name="method">The integration method.</param>
 public void CreateStates(IntegrationMethod method)
 {
 }
Example #17
0
 /// <summary>
 /// Creates all necessary states for the transient behavior.
 /// </summary>
 /// <param name="method">The integration method.</param>
 /// <exception cref="ArgumentNullException">method</exception>
 public virtual void CreateStates(IntegrationMethod method)
 {
     // Do nothing (for now)
 }
Example #18
0
 /// <summary>
 /// Creates all necessary states for the transient behavior.
 /// </summary>
 /// <param name="method">The integration method.</param>
 public void CreateStates(IntegrationMethod method)
 {
     Signal = new DelayedSignal(1, BaseParameters.Delay);
 }
Example #19
0
 /// <summary>
 /// Create states
 /// </summary>
 /// <param name="method"></param>
 public void CreateStates(IntegrationMethod method)
 {
     method.ThrowIfNull(nameof(method));
     QCap = method.CreateDerivative();
 }
Example #20
0
        /// <summary>
        /// Compute expressions for y[t] in terms of y[t - h], the resulting expressions will be implicit for implicit methods.
        /// </summary>
        /// <param name="dy_dt">Equations to solve.</param>
        /// <param name="y">Functions to solve for.</param>
        /// <param name="t">Independent variable.</param>
        /// <param name="h">Step size.</param>
        /// <param name="method">Integration method to use for differential equations.</param>
        /// <returns>Expressions for y[t].</returns>
        public static IEnumerable <Arrow> NDIntegrate(this IEnumerable <Arrow> dy_dt, Expression t, Expression h, IntegrationMethod method)
        {
            switch (method)
            {
            // y[t] = y[t - h] + h*f[t - h, y[t - h]]
            case IntegrationMethod.Euler:
                return(dy_dt.Select(i => Arrow.New(
                                        DOf(i.Left),
                                        DOf(i.Left).Substitute(t, t - h) + h * i.Right.Substitute(t, t - h))));

            // y[t] = y[t - h] + h*f[t, y[t]]
            case IntegrationMethod.BackwardEuler:
                return(dy_dt.Select(i => Arrow.New(
                                        DOf(i.Left),
                                        DOf(i.Left).Substitute(t, t - h) + h * i.Right)));

            // y[t] = y[t - h] + (h/2)*(f[t - h, y[t - h]] + f[t, y[t]])
            case IntegrationMethod.Trapezoid:
                return(dy_dt.Select(i => Arrow.New(
                                        DOf(i.Left),
                                        DOf(i.Left).Substitute(t, t - h) + (h / 2) * (i.Right.Substitute(t, t - h) + i.Right))));

            // y[t] = (4/3)*y[t - h] - (1/3)y[t - 2h] + (2h/3)*f[t, y[t]]
            case IntegrationMethod.BackwardDifferenceFormula2:
                return(dy_dt.Select(i => Arrow.New(
                                        DOf(i.Left),
                                        4 * DOf(i.Left).Substitute(t, t - h) / 3 -
                                        DOf(i.Left).Substitute(t, t - 2 * h) / 3 +
                                        2 * h * i.Right / 3)));

            // y[t] = (2/11)*y[t - 3h] - (9/11)*y[t - 2h] + (18/11)*y[t - h] + (6h/11)*f[t, y[t]]
            case IntegrationMethod.BackwardDifferenceFormula3:
                return(dy_dt.Select(i => Arrow.New(
                                        DOf(i.Left),
                                        2 * DOf(i.Left).Substitute(t, t - 3 * h) / 11 -
                                        9 * DOf(i.Left).Substitute(t, t - 2 * h) / 11 +
                                        18 * DOf(i.Left).Substitute(t, t - h) / 11 +
                                        6 * h * i.Right / 11)));

            default:
                throw new NotImplementedException(method.ToString());
            }
        }
Example #21
0
        /// <summary>
        /// Partially solve a linear system of differential equations for y[t] in terms of y[t - h]. See SolveExtensions.PartialSolve for more information.
        /// </summary>
        /// <param name="f">Equations to solve.</param>
        /// <param name="y">Functions to solve for.</param>
        /// <param name="t">Independent variable.</param>
        /// <param name="h">Step size.</param>
        /// <param name="method">Integration method to use for differential equations.</param>
        /// <returns>Expressions for y[t].</returns>
        public static List <Arrow> NDPartialSolve(this IEnumerable <Equal> f, IEnumerable <Expression> y, Expression t, Expression h, IntegrationMethod method)
        {
            // Find y' in terms of y.
            List <Arrow> dy_dt = f.Solve(y.Select(i => D(i, t)));

            // If dy/dt appears on the right side of the system, the differential equation is not linear. Can't handle these.
            if (dy_dt.Any(i => i.Right.DependsOn(dy_dt.Select(j => j.Left))))
            {
                throw new ArgumentException("Differential equation is singular or not linear.");
            }

            return(NDIntegrate(dy_dt, t, h, method)
                   .Select(i => Equal.New(i.Left, i.Right))
                   .PartialSolve(y));
        }
Example #22
0
        /// <summary>
        /// Accept the current time point
        /// </summary>
        /// <param name="ckt"></param>
        public override void Accept(Circuit ckt)
        {
            // Should not be here
            if (ckt.Method == null)
            {
                return;
            }

            // Are we at a breakpoint?
            IntegrationMethod method = ckt.Method;
            var breaks = method.Breaks;

            if (!method.Break)
            {
                return;
            }

            // Find the time relative to the first period
            double time     = method.Time - td;
            double basetime = 0.0;

            if (time >= per)
            {
                basetime = per * Math.Floor(time / per);
                time    -= basetime;
            }
            double tol = 1e-7 * pw;

            // Are we at the start of a breakpoint?
            if (time <= 0 || time >= tr + pw + tf)
            {
                if (Math.Abs(time - 0) <= tol)
                {
                    breaks.SetBreakpoint(basetime + tr + td);
                }
                else if (Math.Abs(tr + pw + tf - time) <= tol)
                {
                    breaks.SetBreakpoint(basetime + per + td);
                }
                else if ((time == -td))
                {
                    breaks.SetBreakpoint(basetime + td);
                }
                else if (Math.Abs(per - time) <= tol)
                {
                    breaks.SetBreakpoint(basetime + td + tr + per);
                }
            }
            else if (time >= tr && time <= tr + pw)
            {
                if (Math.Abs(time - tr) <= tol)
                {
                    breaks.SetBreakpoint(basetime + td + tr + pw);
                }
                else if (Math.Abs(tr + pw - time) <= tol)
                {
                    breaks.SetBreakpoint(basetime + td + tr + pw + tf);
                }
            }
            else if (time > 0 && time < tr)
            {
                if (Math.Abs(time - 0) <= tol)
                {
                    breaks.SetBreakpoint(basetime + td + tr);
                }
                else if (Math.Abs(time - tr) <= tol)
                {
                    breaks.SetBreakpoint(basetime + td + tr + pw);
                }
            }
            else
            {
                if (Math.Abs(tr + pw - time) <= tol)
                {
                    breaks.SetBreakpoint(basetime + td + tr + pw + tf);
                }
                else if (Math.Abs(tr + pw + tf - time) <= tol)
                {
                    breaks.SetBreakpoint(basetime + td + per);
                }
            }
        }
Example #23
0
        /// <summary>
        /// Compute expressions for y[t + h] in terms of y[t], the resulting expressions will be implicit for implicit methods.
        /// </summary>
        /// <param name="dy_dt">Equations to solve.</param>
        /// <param name="y">Functions to solve for.</param>
        /// <param name="t">Independent variable.</param>
        /// <param name="h">Step size.</param>
        /// <param name="method">Integration method to use for differential equations.</param>
        /// <returns>Expressions for y[t + h].</returns>
        public static IEnumerable <Arrow> NDIntegrate(this IEnumerable <Arrow> dy_dt, Expression t, Expression h, IntegrationMethod method)
        {
            switch (method)
            {
            // y[t + h] = y[t] + h*f[t, y[t]]
            case IntegrationMethod.Euler:
                return(dy_dt.Select(i => Arrow.New(
                                        DOf(i.Left).Evaluate(t, t + h),
                                        DOf(i.Left) + h * i.Right)));

            // y[t + h] = y[t] + h*f[t + h, y[t + h]]
            case IntegrationMethod.BackwardEuler:
                return(dy_dt.Select(i => Arrow.New(
                                        DOf(i.Left).Evaluate(t, t + h),
                                        DOf(i.Left) + h * i.Right.Evaluate(t, t + h))));

            // y[t + h] = y[t] + (h/2)*(f[t, y[t]] + f[t + h, y[t + h]])
            case IntegrationMethod.Trapezoid:
                return(dy_dt.Select(i => Arrow.New(
                                        DOf(i.Left).Evaluate(t, t + h),
                                        DOf(i.Left) + (h / 2) * (i.Right + i.Right.Evaluate(t, t + h)))));

            default:
                throw new NotImplementedException(method.ToString());
            }
        }
 /// <summary>
 /// Creates all necessary states for the transient behavior.
 /// </summary>
 /// <param name="method">The integration method.</param>
 public void CreateStates(IntegrationMethod method)
 {
     Qgs = method.CreateDerivative();
     Qgd = method.CreateDerivative();
 }
Example #25
0
        /// <summary>
        /// Accept the current time point
        /// </summary>
        /// <param name="simulation">Time-based simulation</param>
        public override void Accept(TimeSimulation simulation)
        {
            if (simulation == null)
            {
                throw new ArgumentNullException(nameof(simulation));
            }

            // Should not be here
            if (simulation.Method == null)
            {
                return;
            }

            // Are we at a breakpoint?
            IntegrationMethod method = simulation.Method;
            var breaks = method.Breaks;

            if (!method.Break)
            {
                return;
            }

            // Find the time relative to the first period
            double time     = method.Time - _td;
            double basetime = 0.0;

            if (time >= _per)
            {
                basetime = _per * Math.Floor(time / _per);
                time    -= basetime;
            }
            double tol = 1e-7 * _pw;

            // Are we at the start of a breakpoint?
            if (time <= 0 || time >= _tr + _pw + _tf)
            {
                if (Math.Abs(time - 0) <= tol)
                {
                    breaks.SetBreakpoint(basetime + _tr + _td);
                }
                else if (Math.Abs(_tr + _pw + _tf - time) <= tol)
                {
                    breaks.SetBreakpoint(basetime + _per + _td);
                }
                else if (time <= -_td)
                {
                    breaks.SetBreakpoint(basetime + _td);
                }
                else if (Math.Abs(_per - time) <= tol)
                {
                    breaks.SetBreakpoint(basetime + _td + _tr + _per);
                }
            }
            else if (time >= _tr && time <= _tr + _pw)
            {
                if (Math.Abs(time - _tr) <= tol)
                {
                    breaks.SetBreakpoint(basetime + _td + _tr + _pw);
                }
                else if (Math.Abs(_tr + _pw - time) <= tol)
                {
                    breaks.SetBreakpoint(basetime + _td + _tr + _pw + _tf);
                }
            }
            else if (time > 0 && time < _tr)
            {
                if (Math.Abs(time - 0) <= tol)
                {
                    breaks.SetBreakpoint(basetime + _td + _tr);
                }
                else if (Math.Abs(time - _tr) <= tol)
                {
                    breaks.SetBreakpoint(basetime + _td + _tr + _pw);
                }
            }
            else
            {
                if (Math.Abs(_tr + _pw - time) <= tol)
                {
                    breaks.SetBreakpoint(basetime + _td + _tr + _pw + _tf);
                }
                else if (Math.Abs(_tr + _pw + _tf - time) <= tol)
                {
                    breaks.SetBreakpoint(basetime + _td + _per);
                }
            }
        }
Example #26
0
        /// <summary>
        /// Solve a linear system of differential equations for y[t + h] in terms of y[t].
        /// </summary>
        /// <param name="f">Equations to solve.</param>
        /// <param name="y">Functions to solve for.</param>
        /// <param name="t">Independent variable.</param>
        /// <param name="h">Step size.</param>
        /// <param name="method">Integration method to use for differential equations.</param>
        /// <returns>Expressions for y[t + h].</returns>
        public static List<Arrow> NDSolve(this IEnumerable<Equal> f, IEnumerable<Expression> y, Expression t, Expression h, IntegrationMethod method)
        {
            // Find y' in terms of y.
            List<Arrow> dy_dt = f.Solve(y.Select(i => D(i, t)));

            // If dy/dt appears on the right side of the system, the differential equation is not linear. Can't handle these.
            if (dy_dt.Any(i => i.Right.DependsOn(dy_dt.Select(j => j.Left))))
                throw new ArgumentException("Differential equation is singular or not linear.");

            return NDIntegrate(dy_dt, t, h, method)
                .Select(i => Equal.New(i.Left, i.Right))
                .Solve(y.Evaluate(t, t + h));
        }