/// <summary> /// Execute the transient simulation /// </summary> /// <param name="ckt">The circuit</param> public override void Execute(Circuit ckt) { var state = ckt.State; var rstate = state.Real; var config = CurrentConfig ?? throw new CircuitException("No configuration"); var method = config.Method ?? throw new CircuitException("No integration method"); double delta = Math.Min(FinalTime / 50.0, Step) / 10.0; // Setup breakpoints method.Breaks.Clear(); method.Breaks.SetBreakpoint(0.0); method.Breaks.SetBreakpoint(FinalTime); method.Breaks.MinBreak = MaxStep * 5e-5; // Initialize before starting the simulation state.UseIC = config.UseIC; state.UseDC = true; state.UseSmallSignal = false; state.Domain = CircuitState.DomainTypes.Time; state.Gmin = config.Gmin; // Setup breakpoints method.Initialize(); state.ReinitStates(method); // Call events for initializing the simulation Initialize(ckt); // Calculate the operating point Op(config, ckt, config.DcMaxIterations); ckt.Statistics.TimePoints++; for (int i = 0; i < method.DeltaOld.Length; i++) { method.DeltaOld[i] = MaxStep; } method.Delta = delta; method.SaveDelta = FinalTime / 50.0; // Initialize the method ckt.Method = method; // Stop calculating a DC solution state.UseIC = false; state.UseDC = false; state.States[0].CopyTo(state.States[1]); // Start our statistics ckt.Statistics.TransientTime.Start(); int startIters = ckt.Statistics.NumIter; var startselapsed = ckt.Statistics.SolveTime.Elapsed; try { nextTime: // Accept the current timepoint (CKTaccept()) foreach (var c in ckt.Objects) { c.Accept(ckt); } method.SaveSolution(rstate.Solution); // end of CKTaccept() // Check if current breakpoint is outdated; if so, clear method.UpdateBreakpoints(); ckt.Statistics.Accepted++; // Export the current timepoint if (method.Time >= InitTime) { Export(ckt); } // Detect the end of the simulation if (method.Time >= FinalTime) { // Keep our statistics ckt.Statistics.TransientTime.Stop(); ckt.Statistics.TranIter += ckt.Statistics.NumIter - startIters; ckt.Statistics.TransientSolveTime += ckt.Statistics.SolveTime.Elapsed - startselapsed; // Finished! Finalize(ckt); return; } // Pause test - pausing not supported // resume: method.Delta = Math.Min(method.Delta, MaxStep); method.Resume(); state.ShiftStates(); // Calculate a new solution while (true) { method.TryDelta(); // Compute coefficients and predict a solution and reset states to our previous solution method.ComputeCoefficients(ckt); method.Predict(ckt); // Try to solve the new point bool converged = Iterate(config, ckt, config.TranMaxIterations); ckt.Statistics.TimePoints++; if (method.SavedTime == 0.0) { state.States[1].CopyTo(state.States[2]); state.States[1].CopyTo(state.States[3]); } // Spice copies the states the first time, we're not // I believe this is because Spice treats the first timepoint after the OP as special (MODEINITTRAN) // We don't treat it special (we just assume it started from a circuit in rest) if (!converged) { // Failed to converge, let's try again with a smaller timestep method.Rollback(); ckt.Statistics.Rejected++; method.Delta /= 8.0; method.CutOrder(); var data = new TimestepCutData(ckt, method.Delta / 8.0, TimestepCutData.TimestepCutReason.Convergence); TimestepCut?.Invoke(this, data); } else { // Do not check the first time point if (method.SavedTime == 0.0) { goto nextTime; } if (method.LteControl(ckt)) { goto nextTime; } else { ckt.Statistics.Rejected++; var data = new TimestepCutData(ckt, method.Delta, TimestepCutData.TimestepCutReason.Truncation); TimestepCut?.Invoke(this, data); } } if (method.Delta <= DeltaMin) { if (method.OldDelta > DeltaMin) { method.Delta = DeltaMin; } else { throw new CircuitException($"Timestep too small at t={method.SavedTime.ToString("g")}: {method.Delta.ToString("g")}"); } } } } catch (CircuitException) { // Keep our statistics ckt.Statistics.TransientTime.Stop(); ckt.Statistics.TranIter += ckt.Statistics.NumIter - startIters; ckt.Statistics.TransientSolveTime += ckt.Statistics.SolveTime.Elapsed - startselapsed; throw; } }
/// <summary> /// Execute the transient simulation /// The timesteps are always too small... I can't find what it is, must have checked almost 10 times now. /// </summary> /// <param name="ckt">The circuit</param> public override void Execute(Circuit ckt) { var state = ckt.State; var rstate = state.Real; var method = MyConfig.Method; // Initialize state.UseIC = MyConfig.UseIC; state.UseDC = true; state.UseSmallSignal = false; state.Domain = CircuitState.DomainTypes.Time; // Setup breakpoints method.Breaks.SetBreakpoint(MyConfig.InitTime); method.Breaks.SetBreakpoint(MyConfig.FinalTime); if (method.Breaks.MinBreak == 0.0) { method.Breaks.MinBreak = 5e-5 * MyConfig.MaxStep; } // Initialize the method ckt.Method = method; method.Initialize(); method.DeltaMin = MyConfig.DeltaMin; method.FillOldDeltas(MyConfig.MaxStep); // Calculate the operating point this.Op(ckt, MyConfig.DcMaxIterations); ckt.Statistics.TimePoints++; // Stop calculating a DC solution state.UseIC = false; state.UseDC = false; for (int i = 1; i < state.States.Length; i++) { state.States[0].CopyTo(state.States[i]); } // Start our statistics ckt.Statistics.TransientTime.Start(); int startIters = ckt.Statistics.NumIter; var startselapsed = ckt.Statistics.SolveTime.Elapsed; try { while (true) { // Accept the current timepoint foreach (var c in ckt.Components) { c.Accept(ckt); } method.SaveSolution(rstate.Solution); method.UpdateBreakpoints(); ckt.Statistics.Accepted++; // Export the current timepoint if (method.Time >= MyConfig.InitTime) { Export(ckt); } // Detect the end of the simulation if (method.Time >= MyConfig.FinalTime) { // Keep our statistics ckt.Statistics.TransientTime.Stop(); ckt.Statistics.TranIter += ckt.Statistics.NumIter - startIters; ckt.Statistics.TransientSolveTime += ckt.Statistics.SolveTime.Elapsed - startselapsed; // Finished! break; } // Advance time double delta = method.Time > 0.0 ? method.Delta : Math.Min(MyConfig.FinalTime / 50, MyConfig.Step) / 10.0; method.Advance(Math.Min(delta, MyConfig.MaxStep)); state.ShiftStates(); // Calculate a new solution while (true) { double olddelta = method.Delta; // Check validity of the delta if (double.IsNaN(olddelta)) { throw new CircuitException("Invalid timestep"); } // Compute coefficients and predict a solution and reset states to our previous solution method.ComputeCoefficients(ckt); method.Predict(ckt); state.States[1].CopyTo(state.States[0]); // Try to solve the new point bool converged = this.Iterate(ckt, MyConfig.TranMaxIterations); ckt.Statistics.TimePoints++; // Spice copies the states the first time, we're not // I believe this is because Spice treats the first timepoint after the OP as special (MODEINITTRAN) // We don't treat it special (we just assume it started from a circuit in rest) if (!converged) { // Failed to converge, let's try again with a smaller timestep ckt.Statistics.Rejected++; var data = new TimestepCutData(ckt, method.Delta / 8.0, TimestepCutData.TimestepCutReason.Convergence); TimestepCut?.Invoke(this, data); method.Retry(method.Delta / 8.0); } else { // Spice does not truncate the first timepoint (it deliberately makes it small) // We just check the first timepoint just like any other, and assume the circuit // has always been at that voltage. // Calculate a new value based on the local truncation error if (!method.NewDelta(ckt)) { // Reject the timepoint if the calculated timestep shrinks too fast ckt.Statistics.Rejected++; var data = new TimestepCutData(ckt, method.Delta, TimestepCutData.TimestepCutReason.Truncation); TimestepCut?.Invoke(this, data); } else { break; } } // Stop simulation if timesteps are consistently too small if (method.Delta <= MyConfig.DeltaMin) { if (olddelta <= MyConfig.DeltaMin) { throw new CircuitException($"Timestep too small: {method.Delta}"); } } } } } catch (CircuitException) { // Keep our statistics ckt.Statistics.TransientTime.Stop(); ckt.Statistics.TranIter += ckt.Statistics.NumIter - startIters; ckt.Statistics.TransientSolveTime += ckt.Statistics.SolveTime.Elapsed - startselapsed; throw; } }