From 238ba018b0f3a4834f82286f9a379ac6db520dfb Mon Sep 17 00:00:00 2001 From: Dillon Date: Mon, 2 Dec 2024 20:14:49 -0800 Subject: [PATCH 01/12] Revert "Admitting defeat on solving circuits with pots as variables" This reverts commit b9f03cab6475207496ada502aa58fda407467e51. --- Circuit/Analysis.cs | 89 ++++++++++++++++++++ Circuit/Component.cs | 4 +- Circuit/Components/Potentiometer.cs | 2 +- Circuit/Components/VariableResistor.cs | 2 +- Circuit/Simulation/Simulation.cs | 34 ++++++-- Circuit/Simulation/TransientSolution.cs | 2 + LiveSPICE/LiveSimulation.xaml.cs | 107 ++++++++++++++---------- Tests/Test.cs | 13 ++- 8 files changed, 197 insertions(+), 56 deletions(-) diff --git a/Circuit/Analysis.cs b/Circuit/Analysis.cs index ba49a030..07a6d939 100644 --- a/Circuit/Analysis.cs +++ b/Circuit/Analysis.cs @@ -253,6 +253,95 @@ public Expression AddUnknownEqualTo(string Name, Expression Eq) /// public string AnonymousName() { return context.AnonymousName(); } + /// + /// Describes a parameter for the circuit. + /// + public abstract class Parameter + { + private Component of; + /// + /// Component the parameter affects. + /// + public Component Of { get { return of; } } + + private Expression expr; + /// + /// Expression for the parameter in the system of equations for this analysis. + /// + public Expression Expression { get { return expr; } } + + private string name; + /// + /// Name of this parameter. + /// + public string Name { get { return name; } } + + /// + /// Expression describing the default value. + /// + public abstract Expression Default { get; } + + public Parameter(Component Of, string Name, Expression Expression) + { + of = Of; + name = Name; + expr = Expression; + } + } + + /// + /// Describes a ranged parameter for the circuit. + /// + public class RangeParameter : Parameter + { + private double def, min, max; + /// + /// Default value of the parameter. + /// + public override Expression Default { get { return def; } } + /// + /// Minimum value for the parameter. + /// + public double Minimum { get { return min; } } + /// + /// Maximum value for the parameter. + /// + public double Maximum { get { return max; } } + + private SweepType sweep; + /// + /// Sweep type of the parameter. + /// + public SweepType Sweep { get { return sweep; } } + + public RangeParameter(Component Of, string Name, Expression Expression, double Default, double Minimum, double Maximum, SweepType Sweep) + : base(Of, Name, Expression) + { + def = Default; + min = Minimum; + max = Maximum; + sweep = Sweep; + } + } + + private List parameters = new List(); + /// + /// Enumerates the parameters in this analysis. + /// + public IEnumerable Parameters { get { return parameters; } } + + /// + /// Create an expression for a parameter in the current context. + /// + /// + /// + public Expression AddParameter(Component Of, string Name, double Default, double Min, double Max, SweepType Sweep) + { + Expression expr = context.Prefix + Name; + parameters.Add(new RangeParameter(Of, Name, expr, Default, Min, Max, Sweep)); + return expr; + } + private void AddKcl(Dictionary kcl, Expression V, Expression i) { if (kcl.TryGetValue(V, out var sumi)) diff --git a/Circuit/Component.cs b/Circuit/Component.cs index 87c028b5..f05f90e6 100644 --- a/Circuit/Component.cs +++ b/Circuit/Component.cs @@ -21,7 +21,7 @@ public interface IGroupableComponent /// /// Interface for components that expose a pot controlled value. /// - public interface IPotControl: IGroupableComponent + public interface IPotControl : IGroupableComponent { /// /// The value of the pot. @@ -32,7 +32,7 @@ public interface IPotControl: IGroupableComponent /// /// Interface for components that expose a button controlled value. /// - public interface IButtonControl: IGroupableComponent + public interface IButtonControl : IGroupableComponent { void Click(); int Position { get; set; } diff --git a/Circuit/Components/Potentiometer.cs b/Circuit/Components/Potentiometer.cs index 278ab974..4ddd3caa 100644 --- a/Circuit/Components/Potentiometer.cs +++ b/Circuit/Components/Potentiometer.cs @@ -63,7 +63,7 @@ public void ConnectTo(Node A, Node C, Node W) public override void Analyze(Analysis Mna) { - Expression P = VariableResistor.AdjustWipe(wipe, sweep); + Expression P = Mna.AddParameter(this, Name, Wipe, 1e-6, 1.0 - 1e-6, Sweep); Expression R1 = Resistance * P; Expression R2 = Resistance * (1 - P); diff --git a/Circuit/Components/VariableResistor.cs b/Circuit/Components/VariableResistor.cs index 13f5abf3..6e7e4059 100644 --- a/Circuit/Components/VariableResistor.cs +++ b/Circuit/Components/VariableResistor.cs @@ -80,7 +80,7 @@ public class VariableResistor : TwoTerminal, IPotControl public override void Analyze(Analysis Mna) { - Expression P = AdjustWipe(wipe, sweep); + Expression P = Mna.AddParameter(this, Name, Wipe, 1e-6, 1.0 - 1e-6, Sweep); Resistor.Analyze(Mna, Name, Anode, Cathode, (Expression)Resistance * P); } diff --git a/Circuit/Simulation/Simulation.cs b/Circuit/Simulation/Simulation.cs index 165f2335..7d833acb 100644 --- a/Circuit/Simulation/Simulation.cs +++ b/Circuit/Simulation/Simulation.cs @@ -6,6 +6,7 @@ using System.Numerics; using System.Reflection; using Util; +using static Circuit.Analysis; using LinqExpr = System.Linq.Expressions.Expression; using ParamExpr = System.Linq.Expressions.ParameterExpression; @@ -98,6 +99,12 @@ public TransientSolution Solution /// public IEnumerable Output { get { return output; } set { output = value.ToArray(); InvalidateProcess(); } } + private Expression[] arguments = new Expression[] { }; + /// + /// Expressions for parameters. + /// + public IEnumerable Arguments { get { return arguments; } set { arguments = value.ToArray(); InvalidateProcess(); } } + // Stores any global state in the simulation (previous state values, mostly). private Dictionary> globals = new Dictionary>(); // Add a new global and set it to 0 if it didn't already exist. @@ -153,7 +160,8 @@ public Simulation(TransientSolution Solution) /// Number of samples to process. /// Buffers that describe the input samples. /// Buffers to receive output samples. - public void Run(int N, IEnumerable Input, IEnumerable Output) + /// Scalar values for the arguments. + public void Run(int N, IEnumerable Input, IEnumerable Output, IEnumerable Parameters) { if (_process == null) _process = DefineProcess(); @@ -162,7 +170,7 @@ public void Run(int N, IEnumerable Input, IEnumerable Output { try { - _process(N, n*TimeStep, Input.AsArray(), Output.AsArray()); + _process(N, n*TimeStep, Input.AsArray(), Output.AsArray(), Parameters.AsArray()); n += N; } catch (TargetInvocationException Ex) @@ -175,11 +183,11 @@ public void Run(int N, IEnumerable Input, IEnumerable Output throw new SimulationDiverged("Simulation diverged near t = " + Quantity.ToString(Time, Units.s) + " + " + Ex.At, n + Ex.At); } } - public void Run(int N, IEnumerable Output) { Run(N, new double[][] { }, Output); } - public void Run(double[] Input, IEnumerable Output) { Run(Input.Length, new[] { Input }, Output); } - public void Run(double[] Input, double[] Output) { Run(Input.Length, new[] { Input }, new[] { Output }); } + public void Run(int N, IEnumerable Output, IEnumerable Parameters) { Run(N, new double[][] { }, Output, Parameters); } + public void Run(double[] Input, IEnumerable Output, IEnumerable Parameters) { Run(Input.Length, new[] { Input }, Output, Parameters); } + public void Run(double[] Input, double[] Output, IEnumerable Parameters) { Run(Input.Length, new[] { Input }, new[] { Output }, Parameters); } - private Action _process; + private Action _process; // Force rebuilding of the process function. private void InvalidateProcess() { @@ -189,11 +197,12 @@ private void InvalidateProcess() // The resulting lambda processes N samples, using buffers provided for Input and Output: // void Process(int N, double t0, double T, double[] Input0 ..., double[] Output0 ...) // { ... } - private Action DefineProcess() + private Action DefineProcess() { // Map expressions to identifiers in the syntax tree. var inputs = new List>(); var outputs = new List>(); + var parameters = new List>(); // Lambda code generator. CodeGen code = new CodeGen(); @@ -203,6 +212,7 @@ private Action DefineProcess() ParamExpr t = code.Decl(Scope.Parameter, Simulation.t); var ins = code.Decl(Scope.Parameter, "ins"); var outs = code.Decl(Scope.Parameter, "outs"); + var parms = code.Decl(Scope.Parameter, "params"); // Create buffer parameters for each input... for (int i = 0; i < input.Length; i++) @@ -216,6 +226,11 @@ private Action DefineProcess() outputs.Add(new KeyValuePair(output[i], LinqExpr.ArrayAccess(outs, LinqExpr.Constant(i)))); } + for (int i = 0; i < arguments.Length; i++) + { + parameters.Add(new KeyValuePair(arguments[i], LinqExpr.ArrayAccess(parms, LinqExpr.Constant(i)))); + } + Arrow t_t1 = Arrow.New(Simulation.t, Simulation.t - Solution.TimeStep); // Create globals to store previous values of inputs. @@ -240,6 +255,9 @@ private Action DefineProcess() foreach (KeyValuePair i in inputs) code.DeclInit(i.Key, code[i.Key.Evaluate(t_t1)]); + foreach (KeyValuePair i in parameters) + code.DeclInit(i.Key, i.Value); + // Create arrays for linear systems. int M = Solution.Solutions.OfType().Max(i => i.Equations.Count(), 0); int N = Solution.Solutions.OfType().Max(i => i.UnknownDeltas.Count(), 0); @@ -409,7 +427,7 @@ private Action DefineProcess() foreach (KeyValuePair> i in globals) code.Add(LinqExpr.Assign(i.Value, code[i.Key])); - var lambda = code.Build>(); + var lambda = code.Build>(); return lambda.Compile(); } diff --git a/Circuit/Simulation/TransientSolution.cs b/Circuit/Simulation/TransientSolution.cs index 5ab3cab3..fd87da08 100644 --- a/Circuit/Simulation/TransientSolution.cs +++ b/Circuit/Simulation/TransientSolution.cs @@ -101,6 +101,8 @@ public static TransientSolution Solve(Analysis Analysis, Expression TimeStep, IE .Substitute(dy_dt.Select(i => Arrow.New(i, 0)).Append(Arrow.New(t, 0), Arrow.New(T, 0), SinglePoleSwitch.IncludeOpen)) // Use the initial conditions from analysis. .Substitute(Analysis.InitialConditions) + // Use the default parameter values. + .Substitute(Analysis.Parameters.Select(i => Arrow.New(i.Expression, i.Default))) // Evaluate variables at t=0. .OfType(), y.Select(j => j.Substitute(t, 0))); diff --git a/LiveSPICE/LiveSimulation.xaml.cs b/LiveSPICE/LiveSimulation.xaml.cs index c2231fb1..a4745138 100644 --- a/LiveSPICE/LiveSimulation.xaml.cs +++ b/LiveSPICE/LiveSimulation.xaml.cs @@ -65,6 +65,7 @@ public int Iterations protected Simulation simulation = null; private List probes = new List(); + protected Dictionary namedArguments = new Dictionary(); private object sync = new object(); @@ -96,6 +97,62 @@ public LiveSimulation(Schematic Simulate, Audio.Device Device, Audio.Channel[] I // Build the circuit from the schematic. circuit = Schematic.Schematic.Build(Log); + Circuit.Analysis analysis = circuit.Analyze(); + + foreach (Circuit.Analysis.Parameter P in analysis.Parameters) + { + namedArguments[P.Expression] = (double)P.Default; + + Circuit.Symbol S = P.Of.Tag as Circuit.Symbol; + if (S == null) + continue; + + SymbolControl tag = (SymbolControl)S.Tag; + + if (tag != null) + { + if (P is Circuit.Analysis.RangeParameter RP) + { + IPotControl pot = (IPotControl)P.Of; + var potControl = new PotControl() + { + Width = 80, + Height = 80, + Opacity = 0.5, + FontSize = 15, + FontWeight = FontWeights.Bold, + Value = (double)P.Default, + }; + Schematic.Overlays.Children.Add(potControl); + Canvas.SetLeft(potControl, Canvas.GetLeft(tag) - potControl.Width / 2 + tag.Width / 2); + Canvas.SetTop(potControl, Canvas.GetTop(tag) - potControl.Height / 2 + tag.Height / 2); + + var binding = new Binding + { + Source = pot, + Path = new PropertyPath("(0)", typeof(IPotControl).GetProperty(nameof(IPotControl.PotValue))), + Mode = BindingMode.TwoWay, + NotifyOnSourceUpdated = true + }; + + potControl.SetBinding(PotControl.ValueProperty, binding); + + potControl.AddHandler(Binding.SourceUpdatedEvent, new RoutedEventHandler((o, args) => + { + double x = potControl.Value; + lock (namedArguments) namedArguments[P.Expression] = x * (RP.Maximum - RP.Minimum) + RP.Minimum; //AdjustWipe(x * (RP.Maximum - RP.Minimum) + RP.Minimum, RP.Sweep); + })); + + potControl.MouseEnter += (o, e) => potControl.Opacity = 0.95; + potControl.MouseLeave += (o, e) => potControl.Opacity = 0.4; + } + } + else + { + // The component does not have a symbol, just use the default parameter value. + namedArguments[P.Expression] = (double)P.Default; + } + } // Create the input and output controls. IEnumerable components = circuit.Components; @@ -116,47 +173,6 @@ public LiveSimulation(Schematic Simulate, Audio.Device Device, Audio.Channel[] I if (tag == null) continue; - // Create potentiometers. - if (i is IPotControl potentiometer) - { - var potControl = new PotControl() - { - Width = 80, - Height = 80, - Opacity = 0.5, - FontSize = 15, - FontWeight = FontWeights.Bold, - }; - Schematic.Overlays.Children.Add(potControl); - Canvas.SetLeft(potControl, Canvas.GetLeft(tag) - potControl.Width / 2 + tag.Width / 2); - Canvas.SetTop(potControl, Canvas.GetTop(tag) - potControl.Height / 2 + tag.Height / 2); - - var binding = new Binding - { - Source = potentiometer, - Path = new PropertyPath("(0)", typeof(IPotControl).GetProperty(nameof(IPotControl.PotValue))), - Mode = BindingMode.TwoWay, - NotifyOnSourceUpdated = true - }; - - potControl.SetBinding(PotControl.ValueProperty, binding); - - potControl.AddHandler(Binding.SourceUpdatedEvent, new RoutedEventHandler((o, args) => - { - if (!string.IsNullOrEmpty(potentiometer.Group)) - { - foreach (var p in components.OfType().Where(p => p != potentiometer && p.Group == potentiometer.Group)) - { - p.PotValue = (o as PotControl).Value; - } - } - UpdateSimulation(false); - })); - - potControl.MouseEnter += (o, e) => potControl.Opacity = 0.95; - potControl.MouseLeave += (o, e) => potControl.Opacity = 0.5; - } - // Create Buttons. if (i is IButtonControl b) { @@ -303,6 +319,7 @@ private void RebuildSolution() Log = Log, Input = inputs.Keys.ToArray(), Output = probes.Select(i => i.V).Concat(OutputChannels.Select(i => i.Signal)).ToArray(), + Arguments = namedArguments.Select(i => i.Key).ToArray(), Oversample = Oversample, Iterations = Iterations, }; @@ -336,6 +353,7 @@ private void UpdateSimulation(bool Rebuild) Log = Log, Input = inputs.Keys.ToArray(), Output = probes.Select(i => i.V).Concat(OutputChannels.Select(i => i.Signal)).ToArray(), + Arguments = namedArguments.Select(i => i.Key).ToArray(), Oversample = Oversample, Iterations = Iterations, }; @@ -388,6 +406,7 @@ private void ProcessSamples(int Count, Audio.SampleBuffer[] In, Audio.SampleBuff // These lists only ever grow, but they should never contain more than 10s of items. readonly List inputBuffers = new List(); readonly List outputBuffers = new List(); + readonly List arguments = new List(); private void RunSimulation(int Count, Audio.SampleBuffer[] In, Audio.SampleBuffer[] Out, double Rate) { try @@ -415,8 +434,12 @@ private void RunSimulation(int Count, Audio.SampleBuffer[] In, Audio.SampleBuffe for (int i = 0; i < Out.Length; ++i) outputBuffers.Add(Out[i].Samples); + arguments.Clear(); + foreach (double i in namedArguments.Select(i => i.Value)) + arguments.Add(i); + // Process the samples! - simulation.Run(Count, inputBuffers, outputBuffers); + simulation.Run(Count, inputBuffers, outputBuffers, arguments); // Show the samples on the oscilloscope. long clock = Scope.Signals.Clock; diff --git a/Tests/Test.cs b/Tests/Test.cs index f66ad353..e3122af5 100644 --- a/Tests/Test.cs +++ b/Tests/Test.cs @@ -45,6 +45,8 @@ public Dictionary> Run( { Analysis analysis = C.Analyze(); TransientSolution TS = TransientSolution.Solve(analysis, (Real)1 / (SampleRate * Oversample)); + + List> arguments = analysis.Parameters.Select(i => new KeyValuePair(i.Expression, (double)i.Default)).ToList(); // By default, pass Vin to each input of the circuit. if (Input == null) @@ -65,11 +67,14 @@ public Dictionary> Run( Iterations = Iterations, Input = new[] { Input }, Output = Outputs, + Arguments = arguments.Select(i => i.Key), }; Dictionary> outputs = S.Output.ToDictionary(i => i, i => new List(Samples)); + List parameters = arguments.Select(i => i.Value).ToList(); + double T = S.TimeStep; double t = 0; Random rng = new Random(); @@ -83,7 +88,7 @@ public Dictionary> Run( for (int n = 0; n < N; ++n, t += T) inputBuffer[n] = Vin(t); - S.Run(inputBuffer, outputBuffers); + S.Run(inputBuffer, outputBuffers, parameters); for (int i = 0; i < S.Output.Count(); ++i) outputs[S.Output.ElementAt(i)].AddRange(outputBuffers[i]); @@ -115,6 +120,8 @@ public double[] Benchmark( TransientSolution? TS = null; double solveTime = Benchmark(1, () => TS = TransientSolution.Solve(analysis, (Real)1 / (SampleRate * Oversample), log)); + List> arguments = analysis.Parameters.Select(i => new KeyValuePair(i.Expression, (double)i.Default)).ToList(); + // By default, pass Vin to each input of the circuit. if (Input == null) Input = FindInput(C); @@ -134,12 +141,14 @@ public double[] Benchmark( Iterations = Iterations, Input = new[] { Input }, Output = Outputs, + Arguments = arguments.Select(i => i.Key), }; int N = 1000; double[] inputBuffer = new double[N]; List outputBuffers = Outputs.Select(i => new double[N]).ToList(); + List parameters = arguments.Select(i => i.Value).ToList(); double T = 1.0 / SampleRate; double t = 0; double runTime = Benchmark(3, () => @@ -148,7 +157,7 @@ public double[] Benchmark( for (int n = 0; n < N; ++n, t += T) inputBuffer[n] = Vin(t); - S.Run(inputBuffer, outputBuffers); + S.Run(inputBuffer, outputBuffers, parameters); }); double rate = N / runTime; return new double[] { analyzeTime, solveTime, rate }; From 5398a639515c7ab47b4ffc28ec625eef4278f806 Mon Sep 17 00:00:00 2001 From: Dillon Date: Mon, 2 Dec 2024 20:15:41 -0800 Subject: [PATCH 02/12] Add a variable to the system for capacitor currents. This seemingly small change means that we don't need to solve for differentials before discretization, which may enable dynamic parameters to work! Partially addressing #94 --- Circuit/Components/Capacitor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Circuit/Components/Capacitor.cs b/Circuit/Components/Capacitor.cs index fe77ba6c..c8d98f66 100644 --- a/Circuit/Components/Capacitor.cs +++ b/Circuit/Components/Capacitor.cs @@ -25,7 +25,7 @@ public static Expression Analyze(Analysis Mna, string Name, Node Anode, Node Cat V = Mna.AddUnknownEqualTo("V" + Name, V); // i = C*dV/dt Expression i = C * D(V, t); - //i = Mna.AddUnknownEqualTo("i" + Name, i); + i = Mna.AddUnknownEqualTo("i" + Name, i); Mna.AddPassiveComponent(Anode, Cathode, i); return i; } From c271540a7e376e1d28bb7647a40b92fb35d0d877 Mon Sep 17 00:00:00 2001 From: Dillon Date: Mon, 2 Dec 2024 20:27:13 -0800 Subject: [PATCH 03/12] Don't factor expressions in solve --- Circuit/Simulation/TransientSolution.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/Circuit/Simulation/TransientSolution.cs b/Circuit/Simulation/TransientSolution.cs index fd87da08..f3d84788 100644 --- a/Circuit/Simulation/TransientSolution.cs +++ b/Circuit/Simulation/TransientSolution.cs @@ -154,7 +154,6 @@ public static TransientSolution Solve(Analysis Analysis, Expression TimeStep, IE IEnumerable linear = F.Solve(); if (linear.Any()) { - linear = Factor(linear); solutions.Add(new LinearSolutions(linear)); LogExpressions(Log, MessageType.Verbose, "Linear solutions:", linear); } @@ -177,15 +176,12 @@ public static TransientSolution Solve(Analysis Analysis, Expression TimeStep, IE // Find linear solutions for dy. nonlinear.RowReduce(ly); IEnumerable solved = nonlinear.Solve(ly); - solved = Factor(solved); // Initial guess for y[t] = y[t - h]. IEnumerable guess = F.Unknowns.Select(i => Arrow.New(i, i.Substitute(t, t - h))).ToList(); - guess = Factor(guess); // Newton system equations. IEnumerable equations = nonlinear.Equations.Buffer(); - equations = Factor(equations); solutions.Add(new NewtonIteration(solved, equations, nonlinear.Unknowns, guess)); LogExpressions(Log, MessageType.Verbose, String.Format("Non-linear Newton's method updates ({0}):", String.Join(", ", nonlinear.Unknowns)), equations.Select(i => Equal.New(i, 0))); @@ -205,9 +201,6 @@ public static TransientSolution Solve(Analysis Analysis, Expression TimeStep, IE public static TransientSolution Solve(Analysis Analysis, Expression TimeStep, ILog Log) { return Solve(Analysis, TimeStep, new Arrow[] { }, Log); } public static TransientSolution Solve(Analysis Analysis, Expression TimeStep) { return Solve(Analysis, TimeStep, new Arrow[] { }, new NullLog()); } - private static IEnumerable Factor(IEnumerable x) { return x.Select(i => Arrow.New(i.Left, i.Right.Factor())).Buffer(); } - private static IEnumerable Factor(IEnumerable x) { return x.Select(i => LinearCombination.New(i.Select(j => new KeyValuePair(j.Key, j.Value.Factor())))).Buffer(); } - // Shorthand for df/dx. protected static Expression D(Expression f, Expression x) { return Call.D(f, x); } From fc2c9d00b873223873c632f6ce928811f5899887 Mon Sep 17 00:00:00 2001 From: Dillon Date: Mon, 2 Dec 2024 21:23:14 -0800 Subject: [PATCH 04/12] Don't try to parse parameter names as expressions --- Circuit/Analysis.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Circuit/Analysis.cs b/Circuit/Analysis.cs index 07a6d939..b4e901ea 100644 --- a/Circuit/Analysis.cs +++ b/Circuit/Analysis.cs @@ -337,7 +337,7 @@ public RangeParameter(Component Of, string Name, Expression Expression, double D /// public Expression AddParameter(Component Of, string Name, double Default, double Min, double Max, SweepType Sweep) { - Expression expr = context.Prefix + Name; + Expression expr = ComputerAlgebra.Variable.New(context.Prefix + Name); parameters.Add(new RangeParameter(Of, Name, expr, Default, Min, Max, Sweep)); return expr; } From b187c770a37a441db1274bc96da2f318bf42b045 Mon Sep 17 00:00:00 2001 From: Dillon Date: Mon, 2 Dec 2024 21:23:44 -0800 Subject: [PATCH 05/12] Refactor sweep min/max for easier experimenting --- Circuit/Components/Potentiometer.cs | 2 +- Circuit/Components/VariableResistor.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/Circuit/Components/Potentiometer.cs b/Circuit/Components/Potentiometer.cs index 4ddd3caa..21285359 100644 --- a/Circuit/Components/Potentiometer.cs +++ b/Circuit/Components/Potentiometer.cs @@ -63,7 +63,7 @@ public void ConnectTo(Node A, Node C, Node W) public override void Analyze(Analysis Mna) { - Expression P = Mna.AddParameter(this, Name, Wipe, 1e-6, 1.0 - 1e-6, Sweep); + Expression P = Mna.AddParameter(this, Name, Wipe, VariableResistor.SweepMin, VariableResistor.SweepMax, Sweep); Expression R1 = Resistance * P; Expression R2 = Resistance * (1 - P); diff --git a/Circuit/Components/VariableResistor.cs b/Circuit/Components/VariableResistor.cs index 6e7e4059..4a8a62a7 100644 --- a/Circuit/Components/VariableResistor.cs +++ b/Circuit/Components/VariableResistor.cs @@ -80,11 +80,14 @@ public class VariableResistor : TwoTerminal, IPotControl public override void Analyze(Analysis Mna) { - Expression P = Mna.AddParameter(this, Name, Wipe, 1e-6, 1.0 - 1e-6, Sweep); + Expression P = Mna.AddParameter(this, Name, Wipe, SweepMin, SweepMax, Sweep); Resistor.Analyze(Mna, Name, Anode, Cathode, (Expression)Resistance * P); } + public static readonly double SweepMin = 1e-6; + public static readonly double SweepMax = 1.0 - SweepMin; + public static double AdjustWipe(double x, SweepType Sweep) { x = Math.Min(Math.Max(x, 1e-3), 1.0 - 1e-3); From 61afff4f614526894a21686c96f2607b534fcad4 Mon Sep 17 00:00:00 2001 From: Dillon Date: Mon, 2 Dec 2024 23:36:58 -0800 Subject: [PATCH 06/12] Remove unused `Name` --- Circuit/Analysis.cs | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/Circuit/Analysis.cs b/Circuit/Analysis.cs index b4e901ea..6799fb30 100644 --- a/Circuit/Analysis.cs +++ b/Circuit/Analysis.cs @@ -270,21 +270,14 @@ public abstract class Parameter /// public Expression Expression { get { return expr; } } - private string name; - /// - /// Name of this parameter. - /// - public string Name { get { return name; } } - /// /// Expression describing the default value. /// public abstract Expression Default { get; } - public Parameter(Component Of, string Name, Expression Expression) + public Parameter(Component Of, Expression Expression) { of = Of; - name = Name; expr = Expression; } } @@ -314,8 +307,8 @@ public class RangeParameter : Parameter /// public SweepType Sweep { get { return sweep; } } - public RangeParameter(Component Of, string Name, Expression Expression, double Default, double Minimum, double Maximum, SweepType Sweep) - : base(Of, Name, Expression) + public RangeParameter(Component Of, Expression Expression, double Default, double Minimum, double Maximum, SweepType Sweep) + : base(Of, Expression) { def = Default; min = Minimum; @@ -338,7 +331,7 @@ public RangeParameter(Component Of, string Name, Expression Expression, double D public Expression AddParameter(Component Of, string Name, double Default, double Min, double Max, SweepType Sweep) { Expression expr = ComputerAlgebra.Variable.New(context.Prefix + Name); - parameters.Add(new RangeParameter(Of, Name, expr, Default, Min, Max, Sweep)); + parameters.Add(new RangeParameter(Of, expr, Default, Min, Max, Sweep)); return expr; } From 06af6201ddb88e2fd00403b847217294ccbfffca Mon Sep 17 00:00:00 2001 From: Dillon Date: Mon, 2 Dec 2024 23:42:20 -0800 Subject: [PATCH 07/12] Minor refactor --- LiveSPICE/LiveSimulation.xaml.cs | 35 +++++++++++++++----------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/LiveSPICE/LiveSimulation.xaml.cs b/LiveSPICE/LiveSimulation.xaml.cs index a4745138..63412e77 100644 --- a/LiveSPICE/LiveSimulation.xaml.cs +++ b/LiveSPICE/LiveSimulation.xaml.cs @@ -5,6 +5,7 @@ using System.Diagnostics; using System.Linq; using System.Linq.Expressions; +using System.Printing; using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -302,6 +303,19 @@ public LiveSimulation(Schematic Simulate, Audio.Device Device, Audio.Channel[] I } } + private Simulation MakeSimulation(TransientSolution solution) + { + return new Simulation(solution) + { + Log = Log, + Input = inputs.Keys.ToArray(), + Output = probes.Select(i => i.V).Concat(OutputChannels.Select(i => i.Signal)).ToArray(), + Arguments = namedArguments.Select(i => i.Key).ToArray(), + Oversample = Oversample, + Iterations = Iterations, + }; + } + private void RebuildSolution() { lock (sync) @@ -313,16 +327,7 @@ private void RebuildSolution() { ComputerAlgebra.Expression h = (ComputerAlgebra.Expression)1 / (stream.SampleRate * Oversample); TransientSolution solution = Circuit.TransientSolution.Solve(circuit.Analyze(), h, Log); - - simulation = new Simulation(solution) - { - Log = Log, - Input = inputs.Keys.ToArray(), - Output = probes.Select(i => i.V).Concat(OutputChannels.Select(i => i.Signal)).ToArray(), - Arguments = namedArguments.Select(i => i.Key).ToArray(), - Oversample = Oversample, - Iterations = Iterations, - }; + simulation = MakeSimulation(solution); } catch (Exception Ex) { @@ -348,15 +353,7 @@ private void UpdateSimulation(bool Rebuild) { if (Rebuild) { - simulation = new Simulation(s) - { - Log = Log, - Input = inputs.Keys.ToArray(), - Output = probes.Select(i => i.V).Concat(OutputChannels.Select(i => i.Signal)).ToArray(), - Arguments = namedArguments.Select(i => i.Key).ToArray(), - Oversample = Oversample, - Iterations = Iterations, - }; + simulation = MakeSimulation(s); } else { From ec3c0e3c01649c44d05b1e84cc298fbea56d1389 Mon Sep 17 00:00:00 2001 From: Dillon Date: Tue, 3 Dec 2024 00:19:33 -0800 Subject: [PATCH 08/12] Parameter values don't need to be expressions --- Circuit/Analysis.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Circuit/Analysis.cs b/Circuit/Analysis.cs index 6799fb30..b211145e 100644 --- a/Circuit/Analysis.cs +++ b/Circuit/Analysis.cs @@ -273,7 +273,7 @@ public abstract class Parameter /// /// Expression describing the default value. /// - public abstract Expression Default { get; } + public abstract double Default { get; } public Parameter(Component Of, Expression Expression) { @@ -291,7 +291,7 @@ public class RangeParameter : Parameter /// /// Default value of the parameter. /// - public override Expression Default { get { return def; } } + public override double Default { get { return def; } } /// /// Minimum value for the parameter. /// From c01596e6bf34e3fa53916610d30af0e7850eddc9 Mon Sep 17 00:00:00 2001 From: Dillon Date: Tue, 3 Dec 2024 10:36:07 -0800 Subject: [PATCH 09/12] Respect Sweep type of Potentiometer/VariableResistor --- Circuit/Analysis.cs | 51 ++++-------------- Circuit/Components/Potentiometer.cs | 2 +- Circuit/Components/VariableResistor.cs | 7 +-- Circuit/Simulation/TransientSolution.cs | 2 +- LiveSPICE/LiveSimulation.xaml.cs | 72 +++++++++++-------------- Tests/Test.cs | 16 +++--- 6 files changed, 50 insertions(+), 100 deletions(-) diff --git a/Circuit/Analysis.cs b/Circuit/Analysis.cs index b211145e..7566d9b9 100644 --- a/Circuit/Analysis.cs +++ b/Circuit/Analysis.cs @@ -256,7 +256,7 @@ public Expression AddUnknownEqualTo(string Name, Expression Eq) /// /// Describes a parameter for the circuit. /// - public abstract class Parameter + public class Parameter { private Component of; /// @@ -270,50 +270,17 @@ public abstract class Parameter /// public Expression Expression { get { return expr; } } + Func getter; /// - /// Expression describing the default value. + /// Current value of the parameter. /// - public abstract double Default { get; } + public double Value { get { return getter(); } } - public Parameter(Component Of, Expression Expression) + public Parameter(Component Of, Expression Expression, Func Getter) { of = Of; expr = Expression; - } - } - - /// - /// Describes a ranged parameter for the circuit. - /// - public class RangeParameter : Parameter - { - private double def, min, max; - /// - /// Default value of the parameter. - /// - public override double Default { get { return def; } } - /// - /// Minimum value for the parameter. - /// - public double Minimum { get { return min; } } - /// - /// Maximum value for the parameter. - /// - public double Maximum { get { return max; } } - - private SweepType sweep; - /// - /// Sweep type of the parameter. - /// - public SweepType Sweep { get { return sweep; } } - - public RangeParameter(Component Of, Expression Expression, double Default, double Minimum, double Maximum, SweepType Sweep) - : base(Of, Expression) - { - def = Default; - min = Minimum; - max = Maximum; - sweep = Sweep; + getter = Getter; } } @@ -328,10 +295,10 @@ public RangeParameter(Component Of, Expression Expression, double Default, doubl /// /// /// - public Expression AddParameter(Component Of, string Name, double Default, double Min, double Max, SweepType Sweep) + public Expression AddParameter(Component Of, string Name, Func Getter) { - Expression expr = ComputerAlgebra.Variable.New(context.Prefix + Name); - parameters.Add(new RangeParameter(Of, expr, Default, Min, Max, Sweep)); + Expression expr = Variable.New(context.Prefix + Name); + parameters.Add(new Parameter(Of, expr, Getter)); return expr; } diff --git a/Circuit/Components/Potentiometer.cs b/Circuit/Components/Potentiometer.cs index 21285359..c028e368 100644 --- a/Circuit/Components/Potentiometer.cs +++ b/Circuit/Components/Potentiometer.cs @@ -63,7 +63,7 @@ public void ConnectTo(Node A, Node C, Node W) public override void Analyze(Analysis Mna) { - Expression P = Mna.AddParameter(this, Name, Wipe, VariableResistor.SweepMin, VariableResistor.SweepMax, Sweep); + Expression P = Mna.AddParameter(this, Name, () => VariableResistor.AdjustWipe(Wipe, Sweep)); Expression R1 = Resistance * P; Expression R2 = Resistance * (1 - P); diff --git a/Circuit/Components/VariableResistor.cs b/Circuit/Components/VariableResistor.cs index 4a8a62a7..73f2d476 100644 --- a/Circuit/Components/VariableResistor.cs +++ b/Circuit/Components/VariableResistor.cs @@ -64,7 +64,7 @@ public class VariableResistor : TwoTerminal, IPotControl protected double wipe = 0.5; [Serialize, Description("Position of the wiper on this variable resistor, between 0 and 1.")] - public double Wipe { get { return wipe; } set { wipe = value; NotifyChanged(nameof(Wipe)); } } + public double Wipe { get { return wipe; } set { wipe = value; NotifyChanged(nameof(Wipe)); NotifyChanged(nameof(IPotControl.PotValue)); } } // IPotControl double IPotControl.PotValue { get { return Wipe; } set { Wipe = value; } } @@ -80,14 +80,11 @@ public class VariableResistor : TwoTerminal, IPotControl public override void Analyze(Analysis Mna) { - Expression P = Mna.AddParameter(this, Name, Wipe, SweepMin, SweepMax, Sweep); + Expression P = Mna.AddParameter(this, Name, () => AdjustWipe(Wipe, Sweep)); Resistor.Analyze(Mna, Name, Anode, Cathode, (Expression)Resistance * P); } - public static readonly double SweepMin = 1e-6; - public static readonly double SweepMax = 1.0 - SweepMin; - public static double AdjustWipe(double x, SweepType Sweep) { x = Math.Min(Math.Max(x, 1e-3), 1.0 - 1e-3); diff --git a/Circuit/Simulation/TransientSolution.cs b/Circuit/Simulation/TransientSolution.cs index f3d84788..1aa9251d 100644 --- a/Circuit/Simulation/TransientSolution.cs +++ b/Circuit/Simulation/TransientSolution.cs @@ -102,7 +102,7 @@ public static TransientSolution Solve(Analysis Analysis, Expression TimeStep, IE // Use the initial conditions from analysis. .Substitute(Analysis.InitialConditions) // Use the default parameter values. - .Substitute(Analysis.Parameters.Select(i => Arrow.New(i.Expression, i.Default))) + .Substitute(Analysis.Parameters.Select(i => Arrow.New(i.Expression, i.Value))) // Evaluate variables at t=0. .OfType(), y.Select(j => j.Substitute(t, 0))); diff --git a/LiveSPICE/LiveSimulation.xaml.cs b/LiveSPICE/LiveSimulation.xaml.cs index 63412e77..00697085 100644 --- a/LiveSPICE/LiveSimulation.xaml.cs +++ b/LiveSPICE/LiveSimulation.xaml.cs @@ -102,57 +102,47 @@ public LiveSimulation(Schematic Simulate, Audio.Device Device, Audio.Channel[] I foreach (Circuit.Analysis.Parameter P in analysis.Parameters) { - namedArguments[P.Expression] = (double)P.Default; + namedArguments[P.Expression] = P.Value; Circuit.Symbol S = P.Of.Tag as Circuit.Symbol; if (S == null) continue; - SymbolControl tag = (SymbolControl)S.Tag; + SymbolControl tag = S.Tag as SymbolControl; + if (tag == null) + continue; - if (tag != null) + IPotControl pot = (IPotControl)P.Of; + var potControl = new PotControl() { - if (P is Circuit.Analysis.RangeParameter RP) - { - IPotControl pot = (IPotControl)P.Of; - var potControl = new PotControl() - { - Width = 80, - Height = 80, - Opacity = 0.5, - FontSize = 15, - FontWeight = FontWeights.Bold, - Value = (double)P.Default, - }; - Schematic.Overlays.Children.Add(potControl); - Canvas.SetLeft(potControl, Canvas.GetLeft(tag) - potControl.Width / 2 + tag.Width / 2); - Canvas.SetTop(potControl, Canvas.GetTop(tag) - potControl.Height / 2 + tag.Height / 2); - - var binding = new Binding - { - Source = pot, - Path = new PropertyPath("(0)", typeof(IPotControl).GetProperty(nameof(IPotControl.PotValue))), - Mode = BindingMode.TwoWay, - NotifyOnSourceUpdated = true - }; - - potControl.SetBinding(PotControl.ValueProperty, binding); + Width = 80, + Height = 80, + Opacity = 0.5, + FontSize = 15, + FontWeight = FontWeights.Bold, + Value = pot.PotValue, + }; + Schematic.Overlays.Children.Add(potControl); + Canvas.SetLeft(potControl, Canvas.GetLeft(tag) - potControl.Width / 2 + tag.Width / 2); + Canvas.SetTop(potControl, Canvas.GetTop(tag) - potControl.Height / 2 + tag.Height / 2); + + var binding = new Binding + { + Source = pot, + Path = new PropertyPath("(0)", typeof(IPotControl).GetProperty(nameof(IPotControl.PotValue))), + Mode = BindingMode.TwoWay, + NotifyOnSourceUpdated = true + }; - potControl.AddHandler(Binding.SourceUpdatedEvent, new RoutedEventHandler((o, args) => - { - double x = potControl.Value; - lock (namedArguments) namedArguments[P.Expression] = x * (RP.Maximum - RP.Minimum) + RP.Minimum; //AdjustWipe(x * (RP.Maximum - RP.Minimum) + RP.Minimum, RP.Sweep); - })); + potControl.SetBinding(PotControl.ValueProperty, binding); - potControl.MouseEnter += (o, e) => potControl.Opacity = 0.95; - potControl.MouseLeave += (o, e) => potControl.Opacity = 0.4; - } - } - else + potControl.AddHandler(Binding.SourceUpdatedEvent, new RoutedEventHandler((o, args) => { - // The component does not have a symbol, just use the default parameter value. - namedArguments[P.Expression] = (double)P.Default; - } + lock (namedArguments) namedArguments[P.Expression] = P.Value; + })); + + potControl.MouseEnter += (o, e) => potControl.Opacity = 0.95; + potControl.MouseLeave += (o, e) => potControl.Opacity = 0.4; } // Create the input and output controls. diff --git a/Tests/Test.cs b/Tests/Test.cs index e3122af5..c375b633 100644 --- a/Tests/Test.cs +++ b/Tests/Test.cs @@ -45,8 +45,6 @@ public Dictionary> Run( { Analysis analysis = C.Analyze(); TransientSolution TS = TransientSolution.Solve(analysis, (Real)1 / (SampleRate * Oversample)); - - List> arguments = analysis.Parameters.Select(i => new KeyValuePair(i.Expression, (double)i.Default)).ToList(); // By default, pass Vin to each input of the circuit. if (Input == null) @@ -67,13 +65,13 @@ public Dictionary> Run( Iterations = Iterations, Input = new[] { Input }, Output = Outputs, - Arguments = arguments.Select(i => i.Key), + Arguments = analysis.Parameters.Select(i => i.Expression), }; Dictionary> outputs = S.Output.ToDictionary(i => i, i => new List(Samples)); - List parameters = arguments.Select(i => i.Value).ToList(); + double[] parameters = analysis.Parameters.Select(i => i.Value).ToArray(); double T = S.TimeStep; double t = 0; @@ -84,7 +82,7 @@ public Dictionary> Run( // Using a varying number of samples on each call to S.Run int N = Math.Min(remaining, rng.Next(1000, 10000)); double[] inputBuffer = new double[N]; - List outputBuffers = S.Output.Select(i => new double[N]).ToList(); + double[][] outputBuffers = S.Output.Select(i => new double[N]).ToArray(); for (int n = 0; n < N; ++n, t += T) inputBuffer[n] = Vin(t); @@ -120,8 +118,6 @@ public double[] Benchmark( TransientSolution? TS = null; double solveTime = Benchmark(1, () => TS = TransientSolution.Solve(analysis, (Real)1 / (SampleRate * Oversample), log)); - List> arguments = analysis.Parameters.Select(i => new KeyValuePair(i.Expression, (double)i.Default)).ToList(); - // By default, pass Vin to each input of the circuit. if (Input == null) Input = FindInput(C); @@ -141,14 +137,14 @@ public double[] Benchmark( Iterations = Iterations, Input = new[] { Input }, Output = Outputs, - Arguments = arguments.Select(i => i.Key), + Arguments = analysis.Parameters.Select(i => i.Expression), }; int N = 1000; double[] inputBuffer = new double[N]; - List outputBuffers = Outputs.Select(i => new double[N]).ToList(); + double[][] outputBuffers = Outputs.Select(i => new double[N]).ToArray(); - List parameters = arguments.Select(i => i.Value).ToList(); + double[] parameters = analysis.Parameters.Select(i => i.Value).ToArray(); double T = 1.0 / SampleRate; double t = 0; double runTime = Benchmark(3, () => From 5aa0347c67562398908433c9fbd9d51c700c80fb Mon Sep 17 00:00:00 2001 From: Dillon Date: Tue, 3 Dec 2024 17:31:23 -0800 Subject: [PATCH 10/12] Don't add unknowns for anonymous constants or other simple expressions --- Circuit/Analysis.cs | 6 +++++- Circuit/Components/VacuumTubes/Triode.cs | 6 ++++-- Circuit/Components/VoltageSource.cs | 8 +++++++- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/Circuit/Analysis.cs b/Circuit/Analysis.cs index 7566d9b9..9dc3c7b3 100644 --- a/Circuit/Analysis.cs +++ b/Circuit/Analysis.cs @@ -238,7 +238,11 @@ public Expression AddUnknownEqualTo(string Name, Expression Eq) /// /// /// - public Expression AddUnknownEqualTo(Expression Eq) { return AddUnknownEqualTo(AnonymousName(), Eq); } + public Expression AddUnknownEqualTo(Expression x) { + if (x is Constant) return x; + if (x is Call c && c.Target is UnknownFunction) return x; + return AddUnknownEqualTo(AnonymousName(), x); + } /// /// Add initial conditions to the system. diff --git a/Circuit/Components/VacuumTubes/Triode.cs b/Circuit/Components/VacuumTubes/Triode.cs index 8fcec460..f2928405 100644 --- a/Circuit/Components/VacuumTubes/Triode.cs +++ b/Circuit/Components/VacuumTubes/Triode.cs @@ -146,7 +146,8 @@ public override void Analyze(Analysis Mna) break; case TriodeModel.Koren: Expression E1 = Ln1Exp(Kp * (1.0 / Mu + Vgk * Binary.Power(Kvb + Vpk * Vpk, -0.5))) * Vpk / Kp; - ip = Mna.AddUnknownEqualTo(Call.If(E1 > 0, 2d * (E1 ^ Ex) / Kg, 0)); + ip = Call.If(E1 > 0, 2d * (E1 ^ Ex) / Kg, 0); + ip = Mna.AddUnknownEqualTo("Ip", ip); var vg = (Real)Vg; var knee = (Real)Kn; @@ -156,7 +157,8 @@ public override void Analyze(Analysis Mna) var b = (knee - vg) / (2 * knee * rg1); var c = (-a * Binary.Power(vg - knee, 2)) - (b * (vg - knee)); - ig = Mna.AddUnknownEqualTo(Call.If(Vgk < vg - knee, 0, Call.If(Vgk > vg + knee, (Vgk - vg) / rg1, a * Vgk * Vgk + b * Vgk + c))); + ig = Call.If(Vgk < vg - knee, 0, Call.If(Vgk > vg + knee, (Vgk - vg) / rg1, a * Vgk * Vgk + b * Vgk + c)); + ig = Mna.AddUnknownEqualTo("Ig", ig); ik = -(ip + ig); break; case TriodeModel.DempwolfZolzer: diff --git a/Circuit/Components/VoltageSource.cs b/Circuit/Components/VoltageSource.cs index d8be7ef9..686fcb5b 100644 --- a/Circuit/Components/VoltageSource.cs +++ b/Circuit/Components/VoltageSource.cs @@ -26,6 +26,9 @@ public static void Analyze(Analysis Mna, string Name, Node Anode, Node Cathode, { // Unknown current. Mna.AddPassiveComponent(Anode, Cathode, Mna.AddUnknown("i" + Name)); + + V = Mna.AddUnknownEqualTo(V); + // Set the voltage. Mna.AddEquation(Anode.V - Cathode.V, V); } @@ -35,7 +38,10 @@ public static void Analyze(Analysis Mna, string Name, Node Anode, Node Cathode, Mna.AddInitialConditions(InitialConditions); } public static void Analyze(Analysis Mna, Node Anode, Node Cathode, Expression V) { Analyze(Mna, Mna.AnonymousName(), Anode, Cathode, V); } - public static void Analyze(Analysis Mna, Node Anode, Node Cathode, Expression V, Arrow InitialConditions) { Analyze(Mna, Mna.AnonymousName(), Anode, Cathode, V, InitialConditions); } + public static void Analyze(Analysis Mna, Node Anode, Node Cathode, Expression V, Arrow InitialConditions) + { + Analyze(Mna, Mna.AnonymousName(), Anode, Cathode, V, InitialConditions); + } public override void Analyze(Analysis Mna) { Analyze(Mna, Name, Anode, Cathode, Voltage); } From 4c8765a0f270779d4df3b5d8778c89a7e9610b23 Mon Sep 17 00:00:00 2001 From: Dillon Date: Fri, 6 Dec 2024 00:51:23 -0800 Subject: [PATCH 11/12] Pull solutions off both ends of the system --- Circuit/Simulation/TransientSolution.cs | 20 ++++++++++++++------ ComputerAlgebra | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Circuit/Simulation/TransientSolution.cs b/Circuit/Simulation/TransientSolution.cs index 7b57092d..7ea5d924 100644 --- a/Circuit/Simulation/TransientSolution.cs +++ b/Circuit/Simulation/TransientSolution.cs @@ -79,10 +79,10 @@ public static TransientSolution Solve(Analysis Analysis, Expression TimeStep, IE // Define T = step size. DynamicNamespace globals = new DynamicNamespace(); globals.Add("T", h); - // Define d[t] = delta function. + // Define dq = delta function. // TODO: This should probably be centered around 0, and also have an integral of 1 (i.e. a height of 1 / h). globals.Add(ExprFunction.New("d", Call.If((0 <= t) & (t < h), 1, 0), t)); - // Define u[t] = step function. + // Define uq = step function. globals.Add(ExprFunction.New("u", Call.If(t >= 0, 1, 0), t)); mna = mna.Resolve(Analysis).Resolve(globals).OfType().ToList(); @@ -151,11 +151,13 @@ public static TransientSolution Solve(Analysis Analysis, Expression TimeStep, IE Log.WriteLine(MessageType.Verbose, "Partition unknowns: {0}", String.Join(", ", F.Unknowns)); // Find linear solutions for y. Linear systems should be completely solved here. F.RowReduce(); - IEnumerable linear = F.Solve(); - if (linear.Any()) + List linearFwd = new List(); + List linearBack = new List(); + F.PartialSolve(linearFwd, linearBack); + if (linearFwd.Any()) { - solutions.Add(new LinearSolutions(linear)); - LogExpressions(Log, MessageType.Verbose, "Linear solutions:", linear); + solutions.Add(new LinearSolutions(linearFwd)); + LogExpressions(Log, MessageType.Verbose, "Linear solutions:", linearFwd); } // If there are any variables left, there are some non-linear equations requiring numerical techniques to solve. @@ -187,6 +189,12 @@ public static TransientSolution Solve(Analysis Analysis, Expression TimeStep, IE LogExpressions(Log, MessageType.Verbose, String.Format("Non-linear Newton's method updates ({0}):", String.Join(", ", nonlinear.Unknowns)), equations.Select(i => Equal.New(i, 0))); LogExpressions(Log, MessageType.Verbose, "Linear Newton's method updates:", solved); } + + if (linearBack.Any()) + { + solutions.Add(new LinearSolutions(linearBack)); + LogExpressions(Log, MessageType.Verbose, "Linear solutions:", linearBack); + } } Log.WriteLine(MessageType.Info, "System solved, {0} solution sets for {1} unknowns.", diff --git a/ComputerAlgebra b/ComputerAlgebra index 64c51e89..b90b04b2 160000 --- a/ComputerAlgebra +++ b/ComputerAlgebra @@ -1 +1 @@ -Subproject commit 64c51e893d6c2dc497eb4dc4f87d278529d93009 +Subproject commit b90b04b2e1fb1f4458c965a8286304d2a45061dc From 97dcd853fdb34238f71d00dd3dfb1b05e6f7a1c5 Mon Sep 17 00:00:00 2001 From: Dillon Date: Mon, 9 Dec 2024 23:43:58 -0800 Subject: [PATCH 12/12] Simplify code that supported background parameter updates --- LiveSPICE/LiveSimulation.xaml.cs | 32 +++++++------------------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/LiveSPICE/LiveSimulation.xaml.cs b/LiveSPICE/LiveSimulation.xaml.cs index 00697085..5cc856eb 100644 --- a/LiveSPICE/LiveSimulation.xaml.cs +++ b/LiveSPICE/LiveSimulation.xaml.cs @@ -187,7 +187,7 @@ public LiveSimulation(Schematic Simulate, Audio.Device Device, Audio.Channel[] I foreach (var j in components.OfType().Where(x => x != b && x.Group == b.Group)) j.Click(); } - UpdateSimulation(true); + UpdateSimulation(); }; button.MouseEnter += (o, e) => button.Opacity = 0.95; @@ -327,32 +327,14 @@ private void RebuildSolution() } } - private int clock = -1; - private int update = 0; - private TaskScheduler scheduler = new RedundantTaskScheduler(1); - private void UpdateSimulation(bool Rebuild) + private void UpdateSimulation() { - int id = Interlocked.Increment(ref update); - new Task(() => + ComputerAlgebra.Expression h = (ComputerAlgebra.Expression)1 / (stream.SampleRate * Oversample); + TransientSolution s = Circuit.TransientSolution.Solve(circuit.Analyze(), h, (ILog)Log); + lock (sync) { - ComputerAlgebra.Expression h = (ComputerAlgebra.Expression)1 / (stream.SampleRate * Oversample); - TransientSolution s = Circuit.TransientSolution.Solve(circuit.Analyze(), h, Rebuild ? (ILog)Log : new NullLog()); - lock (sync) - { - if (id > clock) - { - if (Rebuild) - { - simulation = MakeSimulation(s); - } - else - { - simulation.Solution = s; - clock = id; - } - } - } - }).Start(scheduler); + simulation = MakeSimulation(s); + } } private void ProcessSamples(int Count, Audio.SampleBuffer[] In, Audio.SampleBuffer[] Out, double Rate)