/// <summary> /// Calculate a new step /// The result is stored in Delta /// Note: This method does not advance time! /// </summary> /// <param name="simulation">Time-based simulation</param> /// <returns>True if the timestep isn't cut</returns> public bool LteControl(TimeSimulation simulation) { // Invoke truncation event TruncationEventArgs args = new TruncationEventArgs(simulation, Delta); Truncate?.Invoke(this, args); double newdelta = args.Delta; if (newdelta > 0.9 * Delta) { if (Order == 1) { Order = 2; // Invoke truncation event args = new TruncationEventArgs(simulation, Delta); Truncate?.Invoke(this, args); newdelta = args.Delta; if (newdelta <= 1.05 * Delta) { Order = 1; } } Delta = newdelta; return(true); } // Truncation too strict, we'll have to recalculate the timepoint Rollback(); Delta = newdelta; return(false); }
/// <summary> /// Do truncation for all devices /// </summary> /// <param name="sender">Sender</param> /// <param name="args">Arguments</param> /// <returns></returns> protected void TruncateDevices(object sender, TruncationEventArgs args) { if (args == null) { throw new ArgumentNullException(nameof(args)); } double timetmp = double.PositiveInfinity; for (int i = 0; i < _transientBehaviors.Count; i++) { timetmp = Math.Min(timetmp, _transientBehaviors[i].Truncate()); } args.Delta = timetmp; }
/// <summary> /// Do truncation for all nodes /// </summary> /// <param name="sender">Sender</param> /// <param name="args">Arguments</param> /// <returns></returns> protected abstract void TruncateNodes(object sender, TruncationEventArgs args);
/// <summary> /// Truncate the timestep /// Uses the Local Truncation Error (LTE) to calculate an approximate timestep. /// The method is slightly different from the original Spice 3f5 version. /// </summary> /// <param name="sender">Sender</param> /// <param name="args">Arguments</param> /// <returns></returns> protected override void TruncateNodes(object sender, TruncationEventArgs args) { if (args == null) { throw new ArgumentNullException(nameof(args)); } // Get the state var simulation = args.Simulation; var state = simulation.RealState; double tol, diff, tmp; double timetemp = Double.PositiveInfinity; var nodes = simulation.Nodes; int index; // In my opinion, the original Spice method is kind of bugged and can be much better... switch (Order) { case 1: for (int i = 0; i < nodes.Count; i++) { var node = nodes[i]; 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])) * BaseParameters.LteRelativeTolerance + BaseParameters.LteAbsoluteTolerance; tmp = DeltaOld[0] * Math.Sqrt(Math.Abs(2.0 * BaseParameters.TruncationTolerance * tol / diff)); timetemp = Math.Min(timetemp, tmp); } } break; case 2: for (int i = 0; i < nodes.Count; i++) { var node = nodes[i]; 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]; double deriv = DeltaOld[1] / DeltaOld[0]; 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])) * BaseParameters.LteRelativeTolerance + BaseParameters.LteAbsoluteTolerance; tmp = DeltaOld[0] * Math.Pow(Math.Abs(12.0 * BaseParameters.TruncationTolerance * 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; }