diff --git a/Circuit/Simulation/Simulation.cs b/Circuit/Simulation/Simulation.cs index 165f2335..a36c85cd 100644 --- a/Circuit/Simulation/Simulation.cs +++ b/Circuit/Simulation/Simulation.cs @@ -51,7 +51,7 @@ public class Simulation /// /// Get the timestep for the simulation. /// - public double TimeStep { get { return (double)(Solution.TimeStep * oversample); } } + public double TimeStep { get { return (double)Solution.TimeStep; } } private ILog log = new NullLog(); /// @@ -69,22 +69,10 @@ public TransientSolution Solution set { solution = value; InvalidateProcess(); } } - private int oversample = 8; - /// - /// Oversampling factor for this simulation. - /// - public int Oversample { get { return oversample; } set { oversample = value; InvalidateProcess(); } } - - private int iterations = 8; - /// - /// Maximum number of iterations allowed for the simulation to converge. - /// - public int Iterations { get { return iterations; } set { iterations = value; InvalidateProcess(); } } - /// /// The sampling rate of this simulation, the sampling rate of the transient solution divided by the oversampling factor. /// - public Expression SampleRate { get { return 1 / (Solution.TimeStep * oversample); } } + public Expression SampleRate { get { return 1 / Solution.TimeStep; } } private Expression[] input = new Expression[] { }; /// @@ -228,10 +216,7 @@ private Action DefineProcess() LinqExpr Zero = LinqExpr.Constant(0); // double h = T / Oversample - LinqExpr h = LinqExpr.Constant(TimeStep / (double)Oversample); - - // double invOversample = 1 / Oversample - LinqExpr invOversample = LinqExpr.Constant(1.0 / (double)Oversample); + LinqExpr h = LinqExpr.Constant(TimeStep); // Load the globals to local variables and add them to the map. foreach (KeyValuePair> i in globals) @@ -261,141 +246,121 @@ private Action DefineProcess() () => code.Add(LinqExpr.PreIncrementAssign(n)), () => { - // Prepare input samples for oversampling interpolation. - Dictionary dVi = new Dictionary(); + // t += h + code.Add(LinqExpr.AddAssign(t, h)); + + // Interpolate the input samples. foreach (Expression i in Input.Distinct()) { - LinqExpr Va = code[i]; // Sum all inputs with this key. IEnumerable Vbs = inputs.Where(j => j.Key.Equals(i)).Select(j => j.Value); - LinqExpr Vb = LinqExpr.ArrayAccess(Vbs.First(), n); + LinqExpr v = LinqExpr.ArrayAccess(Vbs.First(), n); foreach (LinqExpr j in Vbs.Skip(1)) - Vb = LinqExpr.Add(Vb, LinqExpr.ArrayAccess(j, n)); - - // dVi = (Vb - Va) / Oversample - code.Add(LinqExpr.Assign( - Decl(code, dVi, i, "d" + i.ToString().Replace("[t]", "")), - LinqExpr.Multiply(LinqExpr.Subtract(Vb, Va), invOversample))); + v = LinqExpr.Add(v, LinqExpr.ArrayAccess(j, n)); + code.Add(LinqExpr.Assign(code[i], v)); } - // Prepare output sample accumulators for low pass filtering. - Dictionary Vo = new Dictionary(); - foreach (Expression i in Output.Distinct()) - code.Add(LinqExpr.Assign( - Decl(code, Vo, i, i.ToString().Replace("[t]", "")), - LinqExpr.Constant(0.0))); - - // int ov = Oversample; - // do { -- ov; } while(ov > 0) - ParamExpr ov = code.Decl("ov"); - code.Add(LinqExpr.Assign(ov, LinqExpr.Constant(Oversample))); - code.DoWhile(() => + // Compile all of the SolutionSets in the solution. + foreach (SolutionSet ss in Solution.Solutions) { - // t += h - code.Add(LinqExpr.AddAssign(t, h)); - - // Interpolate the input samples. - foreach (Expression i in Input.Distinct()) - code.Add(LinqExpr.AddAssign(code[i], dVi[i])); - - // Compile all of the SolutionSets in the solution. - foreach (SolutionSet ss in Solution.Solutions) + if (ss is LinearSolutions) { - if (ss is LinearSolutions) - { - // Linear solutions are easy. - LinearSolutions S = (LinearSolutions)ss; - foreach (Arrow i in S.Solutions) - code.DeclInit(i.Left, i.Right); - } - else if (ss is NewtonIteration) + // Linear solutions are easy. + LinearSolutions S = (LinearSolutions)ss; + foreach (Arrow i in S.Solutions) + code.DeclInit(i.Left, i.Right); + } + else if (ss is NewtonIteration) + { + NewtonIteration S = (NewtonIteration)ss; + + // Start with the initial guesses from the solution. + foreach (Arrow i in S.Guesses) + code.DeclInit(i.Left, i.Right); + + // int it = MaxIterations + // This used to be a parameter, defaulted to 8. Experiments seem to show that + // it basically never makes sense to stop iterating at a limit. This sort of + // makes sense, because stopping iteration early suggests that the initial guess + // for the next sample will be worse. So, we're just going to use a very large + // number of iterations just to avoid getting stuck forever. + LinqExpr it = code.ReDeclInit("it", 128); + // do { ... --it } while(it > 0) + code.DoWhile((Break) => { - NewtonIteration S = (NewtonIteration)ss; + // Solve the un-solved system. + Solve(code, JxF, S.Equations, S.UnknownDeltas); - // Start with the initial guesses from the solution. - foreach (Arrow i in S.Guesses) - code.DeclInit(i.Left, i.Right); + // Compile the pre-solved solutions. + if (S.KnownDeltas != null) + foreach (Arrow i in S.KnownDeltas) + code.DeclInit(i.Left, i.Right); - // int it = iterations - LinqExpr it = code.ReDeclInit("it", Iterations); - // do { ... --it } while(it > 0) - code.DoWhile((Break) => + // bool done = true + LinqExpr done = code.ReDeclInit("done", true); + foreach (Expression i in S.Unknowns) { - // Solve the un-solved system. - Solve(code, JxF, S.Equations, S.UnknownDeltas); - - // Compile the pre-solved solutions. - if (S.KnownDeltas != null) - foreach (Arrow i in S.KnownDeltas) - code.DeclInit(i.Left, i.Right); - - // bool done = true - LinqExpr done = code.ReDeclInit("done", true); - foreach (Expression i in S.Unknowns) - { - LinqExpr v = code[i]; - LinqExpr dv = code[NewtonIteration.Delta(i)]; - - // done &= (|dv| < |v|*epsilon) - code.Add(LinqExpr.AndAssign(done, LinqExpr.LessThan(Abs(dv), MultiplyAdd(Abs(v), LinqExpr.Constant(1e-4), LinqExpr.Constant(1e-6))))); - // v += dv - code.Add(LinqExpr.AddAssign(v, dv)); - } - // if (done) break - code.Add(LinqExpr.IfThen(done, Break)); - - // --it; - code.Add(LinqExpr.PreDecrementAssign(it)); - }, LinqExpr.GreaterThan(it, Zero)); - - //// bool failed = false - //LinqExpr failed = Decl(code, code, "failed", LinqExpr.Constant(false)); - //for (int i = 0; i < eqs.Length; ++i) - // // failed |= |JxFi| > epsilon - // code.Add(LinqExpr.OrAssign(failed, LinqExpr.GreaterThan(Abs(eqs[i].ToExpression().Compile(map)), LinqExpr.Constant(1e-3)))); - - //code.Add(LinqExpr.IfThen(failed, ThrowSimulationDiverged(n))); - } + LinqExpr v = code[i]; + LinqExpr dv = code[NewtonIteration.Delta(i)]; + + // done &= (|dv| < |v|*epsilon) + code.Add(LinqExpr.AndAssign(done, LinqExpr.LessThan(Abs(dv), MultiplyAdd(Abs(v), LinqExpr.Constant(1e-4), LinqExpr.Constant(1e-6))))); + // v += dv + code.Add(LinqExpr.AddAssign(v, dv)); + } + // if (done) break + code.Add(LinqExpr.IfThen(done, Break)); + + // --it; + code.Add(LinqExpr.PreDecrementAssign(it)); + }, LinqExpr.GreaterThan(it, Zero)); + + //// bool failed = false + //LinqExpr failed = Decl(code, code, "failed", LinqExpr.Constant(false)); + //for (int i = 0; i < eqs.Length; ++i) + // // failed |= |JxFi| > epsilon + // code.Add(LinqExpr.OrAssign(failed, LinqExpr.GreaterThan(Abs(eqs[i].ToExpression().Compile(map)), LinqExpr.Constant(1e-3)))); + + //code.Add(LinqExpr.IfThen(failed, ThrowSimulationDiverged(n))); } + } - // Update the previous timestep variables. - foreach (SolutionSet S in Solution.Solutions) + // Update the previous timestep variables. + foreach (SolutionSet S in Solution.Solutions) + { + for (int m = MaxDelay; m < 0; m++) { - for (int m = MaxDelay; m < 0; m++) - { - Arrow t_tm = Arrow.New(Simulation.t, Simulation.t + m * Solution.TimeStep); - Arrow t_tm1 = Arrow.New(Simulation.t, Simulation.t + (m + 1) * Solution.TimeStep); - foreach (Expression i in S.Unknowns.Where(i => globals.Keys.Contains(i.Evaluate(t_tm)))) - code.Add(LinqExpr.Assign(code[i.Evaluate(t_tm)], code[i.Evaluate(t_tm1)])); - } + Arrow t_tm = Arrow.New(Simulation.t, Simulation.t + m * Solution.TimeStep); + Arrow t_tm1 = Arrow.New(Simulation.t, Simulation.t + (m + 1) * Solution.TimeStep); + foreach (Expression i in S.Unknowns.Where(i => globals.Keys.Contains(i.Evaluate(t_tm)))) + code.Add(LinqExpr.Assign(code[i.Evaluate(t_tm)], code[i.Evaluate(t_tm1)])); } + } - // Vo += i - foreach (Expression i in Output.Distinct()) + Dictionary Vo = new Dictionary(); + + // Vo = i + foreach (Expression i in Output.Distinct()) + { + try { - LinqExpr Voi = LinqExpr.Constant(0.0); - try - { - Voi = code.Compile(i); - } - catch (Exception Ex) - { - Log.WriteLine(MessageType.Warning, Ex.Message); - } - code.Add(LinqExpr.AddAssign(Vo[i], Voi)); + code.Add(LinqExpr.Assign( + Decl(code, Vo, i, i.ToString().Replace("[t]", "")), + code.Compile(i))); } + catch (Exception Ex) + { + Log.WriteLine(MessageType.Warning, Ex.Message); + } + } - // Vi_t0 = Vi - foreach (Expression i in Input.Distinct()) - code.Add(LinqExpr.Assign(code[i.Evaluate(t_t1)], code[i])); - - // --ov; - code.Add(LinqExpr.PreDecrementAssign(ov)); - }, LinqExpr.GreaterThan(ov, Zero)); + // Vi_t0 = V + foreach (Expression i in Input.Distinct()) + code.Add(LinqExpr.Assign(code[i.Evaluate(t_t1)], code[i])); - // Output[i][n] = Vo / Oversample + // Output[i][n] = Vo foreach (KeyValuePair i in outputs) - code.Add(LinqExpr.Assign(LinqExpr.ArrayAccess(i.Value, n), LinqExpr.Multiply(Vo[i.Key], invOversample))); + code.Add(LinqExpr.Assign(LinqExpr.ArrayAccess(i.Value, n), Vo[i.Key])); // Every 256 samples, check for divergence. if (Vo.Any()) diff --git a/LiveSPICE/LiveSimulation.xaml b/LiveSPICE/LiveSimulation.xaml index 066334a0..7b37470b 100644 --- a/LiveSPICE/LiveSimulation.xaml +++ b/LiveSPICE/LiveSimulation.xaml @@ -78,24 +78,7 @@ - - - - 1 - 2 - 3 - 4 - 6 - 8 - 12 - 16 - - - - - - - + diff --git a/LiveSPICE/LiveSimulation.xaml.cs b/LiveSPICE/LiveSimulation.xaml.cs index c2231fb1..642eb783 100644 --- a/LiveSPICE/LiveSimulation.xaml.cs +++ b/LiveSPICE/LiveSimulation.xaml.cs @@ -27,26 +27,6 @@ partial class LiveSimulation : Window, INotifyPropertyChanged public Log Log { get { return (Log)log.Content; } } public Scope Scope { get { return (Scope)scope.Content; } } - protected int oversample = 8; - /// - /// Simulation oversampling rate. - /// - public int Oversample - { - get { return oversample; } - set { oversample = Math.Max(1, value); RebuildSolution(); NotifyChanged(nameof(Oversample)); } - } - - protected int iterations = 8; - /// - /// Max iterations for numerical algorithms. - /// - public int Iterations - { - get { return iterations; } - set { iterations = Math.Max(1, value); RebuildSolution(); NotifyChanged(nameof(Iterations)); } - } - private double inputGain = 1.0; /// /// Overall input gain. @@ -295,7 +275,7 @@ private void RebuildSolution() { try { - ComputerAlgebra.Expression h = (ComputerAlgebra.Expression)1 / (stream.SampleRate * Oversample); + ComputerAlgebra.Expression h = (ComputerAlgebra.Expression)1 / stream.SampleRate; TransientSolution solution = Circuit.TransientSolution.Solve(circuit.Analyze(), h, Log); simulation = new Simulation(solution) @@ -303,8 +283,6 @@ private void RebuildSolution() Log = Log, Input = inputs.Keys.ToArray(), Output = probes.Select(i => i.V).Concat(OutputChannels.Select(i => i.Signal)).ToArray(), - Oversample = Oversample, - Iterations = Iterations, }; } catch (Exception Ex) @@ -323,7 +301,7 @@ private void UpdateSimulation(bool Rebuild) int id = Interlocked.Increment(ref update); new Task(() => { - ComputerAlgebra.Expression h = (ComputerAlgebra.Expression)1 / (stream.SampleRate * Oversample); + ComputerAlgebra.Expression h = (ComputerAlgebra.Expression)1 / stream.SampleRate; TransientSolution s = Circuit.TransientSolution.Solve(circuit.Analyze(), h, Rebuild ? (ILog)Log : new NullLog()); lock (sync) { @@ -336,8 +314,6 @@ private void UpdateSimulation(bool Rebuild) Log = Log, Input = inputs.Keys.ToArray(), Output = probes.Select(i => i.V).Concat(OutputChannels.Select(i => i.Signal)).ToArray(), - Oversample = Oversample, - Iterations = Iterations, }; } else diff --git a/LiveSPICEVst/EditorView.xaml b/LiveSPICEVst/EditorView.xaml index 05defd33..38669929 100644 --- a/LiveSPICEVst/EditorView.xaml +++ b/LiveSPICEVst/EditorView.xaml @@ -35,23 +35,6 @@ - Oversample: - - 1 - 2 - 4 - 8 - - Iterations: - - 1 - 2 - 4 - 8 - 16 - 32 - 64 - diff --git a/LiveSPICEVst/EditorView.xaml.cs b/LiveSPICEVst/EditorView.xaml.cs index 705b6876..ed5e8aa5 100644 --- a/LiveSPICEVst/EditorView.xaml.cs +++ b/LiveSPICEVst/EditorView.xaml.cs @@ -35,40 +35,6 @@ public void UpdateSchematic() (LoadCircuitButton.Content as TextBlock).Text = (Plugin.SimulationProcessor.Schematic != null) ? Plugin.SimulationProcessor.SchematicName : "Load Schematic"; schematicWindow = null; - - for (int i = 0; i < OversampleComboBox.Items.Count; i++) - { - if (int.Parse((OversampleComboBox.Items[i] as ComboBoxItem).Content as string) == Plugin.SimulationProcessor.Oversample) - { - OversampleComboBox.SelectedIndex = i; - - break; - } - } - - for (int i = 0; i < IterationsComboBox.Items.Count; i++) - { - if (int.Parse((IterationsComboBox.Items[i] as ComboBoxItem).Content as string) == Plugin.SimulationProcessor.Iterations) - { - IterationsComboBox.SelectedIndex = i; - - break; - } - } - } - - private void OversampleComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - ComboBox combo = sender as ComboBox; - - Plugin.SimulationProcessor.Oversample = int.Parse((combo.SelectedItem as ComboBoxItem).Content as string); - } - - private void IterationsComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - ComboBox combo = sender as ComboBox; - - Plugin.SimulationProcessor.Iterations = int.Parse((combo.SelectedItem as ComboBoxItem).Content as string); } private void LoadCircuitButton_Click(object sender, RoutedEventArgs e) diff --git a/LiveSPICEVst/LiveSPICEPlugin.cs b/LiveSPICEVst/LiveSPICEPlugin.cs index 6c32491e..6df1c164 100644 --- a/LiveSPICEVst/LiveSPICEPlugin.cs +++ b/LiveSPICEVst/LiveSPICEPlugin.cs @@ -82,8 +82,6 @@ public override byte[] SaveState() VstProgramParameters programParameters = new VstProgramParameters { SchematicPath = SimulationProcessor.SchematicPath, - OverSample = SimulationProcessor.Oversample, - Iterations = SimulationProcessor.Iterations }; foreach (var wrapper in SimulationProcessor.InteractiveComponents) @@ -141,9 +139,6 @@ public override void RestoreState(byte[] stateData) LoadSchematic(programParameters.SchematicPath); } - SimulationProcessor.Oversample = programParameters.OverSample; - SimulationProcessor.Iterations = programParameters.Iterations; - foreach (VSTProgramControlParameter controlParameter in programParameters.ControlParameters) { var wrapper = SimulationProcessor.InteractiveComponents.Where(i => i.Name == controlParameter.Name).SingleOrDefault(); diff --git a/LiveSPICEVst/SimulationProcessor.cs b/LiveSPICEVst/SimulationProcessor.cs index 03d56442..3c8a18e5 100644 --- a/LiveSPICEVst/SimulationProcessor.cs +++ b/LiveSPICEVst/SimulationProcessor.cs @@ -39,37 +39,7 @@ public double SampleRate } } - public int Oversample - { - get { return oversample; } - set - { - if (oversample != value) - { - oversample = value; - - needRebuild = true; - } - } - } - - public int Iterations - { - get { return iterations; } - set - { - if (iterations != value) - { - iterations = value; - - needRebuild = true; - } - } - } - double sampleRate; - int oversample = 2; - int iterations = 8; Circuit.Circuit circuit = null; Simulation simulation = null; @@ -273,7 +243,7 @@ void UpdateSimulation(bool rebuild) try { Analysis analysis = circuit.Analyze(); - TransientSolution ts = TransientSolution.Solve(analysis, (Real)1 / (sampleRate * oversample)); + TransientSolution ts = TransientSolution.Solve(analysis, (Real)1 / sampleRate); lock (sync) { @@ -307,8 +277,6 @@ void UpdateSimulation(bool rebuild) { simulation = new Simulation(ts) { - Oversample = oversample, - Iterations = iterations, Input = new[] { inputExpression }, Output = new[] { outputExpression } }; diff --git a/LiveSPICEVst/VstProgramParameters.cs b/LiveSPICEVst/VstProgramParameters.cs index f6633176..89404772 100644 --- a/LiveSPICEVst/VstProgramParameters.cs +++ b/LiveSPICEVst/VstProgramParameters.cs @@ -8,15 +8,14 @@ namespace LiveSPICEVst public class VstProgramParameters { public string SchematicPath { get; set; } + public List ControlParameters { get; set; } + + // Deprecated, but kept to avoid breaking serialization of state...? public int OverSample { get; set; } public int Iterations { get; set; } - public List ControlParameters { get; set; } public VstProgramParameters() { - OverSample = 2; - Iterations = 8; - ControlParameters = new List(); } } diff --git a/Tests/Program.cs b/Tests/Program.cs index 78fe1daf..94c26266 100644 --- a/Tests/Program.cs +++ b/Tests/Program.cs @@ -18,25 +18,23 @@ static async Task Main(string[] args) .WithArgument("pattern", "Glob pattern for files to test") .WithOption(new[] { "--plot" }, "Plot results") .WithOption(new[] { "--samples" }, () => 4800, "Samples") - .WithHandler(CommandHandler.Create(Test))) + .WithHandler(CommandHandler.Create(Test))) .WithCommand("benchmark", "Run benchmarks", c => c .WithArgument("pattern", "Glob pattern for files to benchmark") - .WithHandler(CommandHandler.Create(Benchmark))) - .WithGlobalOption(new Option("--sampleRate", () => 48000, "Sample Rate")) - .WithGlobalOption(new Option("--oversample", () => 8, "Oversample")) - .WithGlobalOption(new Option("--iterations", () => 8, "Iterations")); + .WithHandler(CommandHandler.Create(Benchmark))) + .WithGlobalOption(new Option("--sampleRate", () => 48000, "Sample Rate")); return await rootCommand.InvokeAsync(args); } - public static void Test(string pattern, bool plot, int sampleRate, int samples, int oversample, int iterations) + public static void Test(string pattern, bool plot, int sampleRate, int samples) { var log = new ConsoleLog() { Verbosity = MessageType.Info }; var tester = new Test(); foreach (var circuit in GetCircuits(pattern, log)) { - var outputs = tester.Run(circuit, t => Harmonics(t, 0.5, 82, 2), sampleRate, samples, oversample, iterations); + var outputs = tester.Run(circuit, t => Harmonics(t, 0.5, 82, 2), sampleRate, samples); if (plot) { tester.PlotAll(circuit.Name, outputs); @@ -44,7 +42,7 @@ public static void Test(string pattern, bool plot, int sampleRate, int samples, } } - public static void Benchmark(string pattern, int sampleRate, int oversample, int iterations) + public static void Benchmark(string pattern, int sampleRate) { var log = new ConsoleLog() { Verbosity = MessageType.Error }; var tester = new Test(); @@ -52,7 +50,7 @@ public static void Benchmark(string pattern, int sampleRate, int oversample, int System.Console.WriteLine(fmt, "Circuit", "Analysis (ms)", "Solve (ms)", "Sim (kHz)", "Realtime x"); foreach (var circuit in GetCircuits(pattern, log)) { - double[] result = tester.Benchmark(circuit, t => Harmonics(t, 0.5, 82, 2), sampleRate, oversample, iterations, log: log); + double[] result = tester.Benchmark(circuit, t => Harmonics(t, 0.5, 82, 2), sampleRate, log: log); double analyzeTime = result[0]; double solveTime = result[1]; double simRate = result[2]; diff --git a/Tests/Test.cs b/Tests/Test.cs index f66ad353..cc4225d4 100644 --- a/Tests/Test.cs +++ b/Tests/Test.cs @@ -38,13 +38,11 @@ public Dictionary> Run( Func Vin, int SampleRate, int Samples, - int Oversample, - int Iterations, Expression? Input = null, IEnumerable? Outputs = null) { Analysis analysis = C.Analyze(); - TransientSolution TS = TransientSolution.Solve(analysis, (Real)1 / (SampleRate * Oversample)); + TransientSolution TS = TransientSolution.Solve(analysis, (Real)1 / SampleRate); // By default, pass Vin to each input of the circuit. if (Input == null) @@ -61,8 +59,6 @@ public Dictionary> Run( Simulation S = new Simulation(TS) { - Oversample = Oversample, - Iterations = Iterations, Input = new[] { Input }, Output = Outputs, }; @@ -103,8 +99,6 @@ public double[] Benchmark( Circuit.Circuit C, Func Vin, int SampleRate, - int Oversample, - int Iterations, Expression? Input = null, IEnumerable? Outputs = null, ILog? log = null) @@ -113,7 +107,7 @@ public double[] Benchmark( double analyzeTime = Benchmark(1, () => analysis = C.Analyze()); TransientSolution? TS = null; - double solveTime = Benchmark(1, () => TS = TransientSolution.Solve(analysis, (Real)1 / (SampleRate * Oversample), log)); + double solveTime = Benchmark(1, () => TS = TransientSolution.Solve(analysis, (Real)1 / SampleRate, log)); // By default, pass Vin to each input of the circuit. if (Input == null) @@ -130,8 +124,6 @@ public double[] Benchmark( Simulation S = new Simulation(TS) { - Oversample = Oversample, - Iterations = Iterations, Input = new[] { Input }, Output = Outputs, };