/// <summary> /// Execute the transient simulation /// </summary> protected override void Execute() { // First do temperature-dependent calculations and IC base.Execute(); var exportargs = new ExportDataEventArgs(this); var state = RealState; var baseConfig = BaseConfiguration; var timeConfig = TimeConfiguration; double delta = Math.Min(timeConfig.FinalTime / 50.0, timeConfig.Step) / 10.0; // Initialize before starting the simulation state.UseIc = timeConfig.UseIc; state.Domain = RealState.DomainType.Time; state.Gmin = baseConfig.Gmin; // Use node initial conditions if device initial conditions are not used if (!timeConfig.UseIc) { OnLoad += LoadInitialConditions; } // Calculate the operating point Op(baseConfig.DcMaxIterations); Statistics.TimePoints++; Method.DeltaOld.Clear(timeConfig.MaxStep); Method.Delta = delta; Method.SaveDelta = timeConfig.FinalTime / 50.0; // Stop calculating a DC solution state.UseIc = false; state.UseDc = false; for (int i = 0; i < TransientBehaviors.Count; i++) { TransientBehaviors[i].GetDcState(this); } StatePool.ClearDc(); OnLoad -= LoadInitialConditions; // Start our statistics Statistics.TransientTime.Start(); int startIters = Statistics.Iterations; var startselapsed = Statistics.SolveTime.Elapsed; try { while (true) { // nextTime: // Accept the current timepoint (CKTaccept()) for (int i = 0; i < AcceptBehaviors.Count; i++) { AcceptBehaviors[i].Accept(this); } Method.SaveSolution(state.Solution); // end of CKTaccept() // Check if current breakpoint is outdated; if so, clear Method.UpdateBreakpoints(); Statistics.Accepted++; // Export the current timepoint if (Method.Time >= timeConfig.InitTime) { Export(exportargs); } // Detect the end of the simulation if (Method.Time >= timeConfig.FinalTime) { // Keep our statistics Statistics.TransientTime.Stop(); Statistics.TransientIterations += Statistics.Iterations - startIters; Statistics.TransientSolveTime += Statistics.SolveTime.Elapsed - startselapsed; // Finished! OnLoad -= LoadInitialConditions; return; } // Pause test - pausing not supported // resume: Method.Delta = Math.Min(Method.Delta, timeConfig.MaxStep); Method.Resume(); StatePool.History.Cycle(); // Calculate a new solution while (true) { Method.TryDelta(); // Compute coefficients and predict a solution and reset states to our previous solution Method.ComputeCoefficients(this); Method.Predict(this); // Try to solve the new point if (Method.SavedTime.Equals(0.0)) { state.Init = RealState.InitializationStates.InitTransient; } bool converged = TimeIterate(timeConfig.TranMaxIterations); 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 Method.Rollback(); Statistics.Rejected++; Method.Delta /= 8.0; Method.CutOrder(); var data = new TimestepCutEventArgs(Method.Delta / 8.0, TimestepCutEventArgs.TimestepCutReason.Convergence); TimestepCut?.Invoke(this, data); } else { // Do not check the first time point if (Method.SavedTime.Equals(0.0) || Method.LteControl(this)) { // goto nextTime; break; } Statistics.Rejected++; var data = new TimestepCutEventArgs(Method.Delta, TimestepCutEventArgs.TimestepCutReason.Truncation); TimestepCut?.Invoke(this, data); } if (Method.Delta <= timeConfig.DeltaMin) { if (Method.OldDelta > timeConfig.DeltaMin) { Method.Delta = timeConfig.DeltaMin; } else { throw new CircuitException("Timestep too small at t={0:e}: {1:e}".FormatString(Method.SavedTime, Method.Delta)); } } } } } catch (CircuitException ex) { // Keep our statistics Statistics.TransientTime.Stop(); Statistics.TransientIterations += Statistics.Iterations - startIters; Statistics.TransientSolveTime += Statistics.SolveTime.Elapsed - startselapsed; throw new CircuitException("{0}: transient terminated".FormatString(Name), ex); } }
/// <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; } }