/// <summary> /// Truncates the timestep based on the states. /// </summary> /// <param name="sender">The sender (integration method).</param> /// <param name="args">The <see cref="TruncateEvaluateEventArgs"/> instance containing the event data.</param> protected virtual void TruncateStates(object sender, TruncateEvaluateEventArgs args) { args.ThrowIfNull(nameof(args)); // Don't truncate the first step if (BaseTime.Equals(0.0)) { return; } // Truncate! var newDelta = args.Delta; foreach (var state in TruncatableStates) { newDelta = Math.Min(newDelta, state.Truncate()); } if (newDelta > 0.9 * IntegrationStates[0].Delta) { if (Order < MaxOrder) { // Try increasing the order Order++; args.Order = Order; // Try truncation again newDelta = args.Delta; foreach (var state in TruncatableStates) { newDelta = Math.Min(newDelta, state.Truncate()); } // Increasing the order doesn't make a significant difference if (newDelta <= 1.05 * IntegrationStates[0].Delta) { Order--; args.Order = Order; } } } else { args.Accepted = false; } args.Delta = newDelta; }
/// <summary> /// Truncates the timestep using nodes. /// </summary> /// <param name="sender">The sender (integration method).</param> /// <param name="args">The <see cref="T:SpiceSharp.IntegrationMethods.TruncateEvaluateEventArgs" /> instance containing the event data.</param> protected override void TruncateNodes(object sender, TruncateEvaluateEventArgs args) { args.ThrowIfNull(nameof(args)); // Get the state var state = args.Simulation.RealState; var timetmp = double.PositiveInfinity; var nodes = args.Simulation.Variables; var delsum = 0.0; for (var i = 0; i <= Order; i++) { delsum += IntegrationStates[i].Delta; } for (var i = 0; i < nodes.Count; i++) { var node = nodes[i]; var index = node.Index; var tol = Math.Max(Math.Abs(state.Solution[index]), Math.Abs(Prediction[index])) * RelTol + AbsTol; var diff = state.Solution[index] - Prediction[i]; if (!diff.Equals(0.0)) { var tmp = tol * TrTol * delsum / (diff * IntegrationStates[0].Delta); tmp = Math.Abs(tmp); switch (Order) { case 0: break; case 1: tmp = Math.Sqrt(tmp); break; default: tmp = Math.Exp(Math.Log(tmp) / (Order + 1)); break; } tmp *= IntegrationStates[0].Delta; timetmp = Math.Min(timetmp, tmp); } args.Delta = timetmp; } }
/// <summary> /// Truncates the timestep using nodes. /// </summary> /// <param name="sender">The sender (integration method).</param> /// <param name="args">The <see cref="TruncateEvaluateEventArgs" /> instance containing the event data.</param> protected override void TruncateNodes(object sender, TruncateEvaluateEventArgs args) { args.ThrowIfNull(nameof(args)); // Get the state var state = args.Simulation.RealState; double tol, diff, tmp; var timetemp = double.PositiveInfinity; var nodes = args.Simulation.Variables; int index; // In my opinion, the original Spice method is kind of bugged and can be much better... switch (Order) { case 1: foreach (var node in nodes) { if (node.UnknownType != VariableType.Voltage) { continue; } index = node.Index; // Milne's estimate for the second-order derivative using a Forward Euler predictor and Backward Euler corrector diff = state.Solution[index] - Prediction[index]; // Avoid division by zero if (!diff.Equals(0.0)) { tol = Math.Max(Math.Abs(state.Solution[index]), Math.Abs(Prediction[index])) * LteRelTol + LteAbsTol; tmp = IntegrationStates[0].Delta * Math.Sqrt(Math.Abs(2.0 * TrTol * tol / diff)); timetemp = Math.Min(timetemp, tmp); } } break; case 2: foreach (var node in nodes) { if (node.UnknownType != VariableType.Voltage) { continue; } index = node.Index; // Milne's estimate for the third-order derivative using an Adams-Bashforth predictor and Trapezoidal corrector diff = state.Solution[index] - Prediction[index]; var deriv = IntegrationStates[1].Delta / IntegrationStates[0].Delta; deriv = diff * 4.0 / (1 + deriv * deriv); // Avoid division by zero if (!deriv.Equals(0.0)) { tol = Math.Max(Math.Abs(state.Solution[index]), Math.Abs(Prediction[index])) * LteRelTol + LteAbsTol; tmp = IntegrationStates[0].Delta * Math.Pow(Math.Abs(12.0 * TrTol * tol / deriv), 1.0 / 3.0); timetemp = Math.Min(timetemp, tmp); } } break; default: throw new CircuitException("Invalid order"); } // Get the minimum timestep args.Delta = timetemp; }