Halite - an analog circuit simulator in ~1k lines of code

DSP, Plugin and Host development discussion.
RELATED
PRODUCTS

Post

stratum wrote:I remember that problem from this paper http://www.simulanalog.org/statevariable.pdf

Oddly, it doesn't reference or mention any MNA related paper. Maybe generic realtime circuit simulation wasn't feasible with the CPU's of the day and they didn't even search for it.
No it certainly was not, and it still does not make much sense today (not without hefty optimizations, that is).

About the paper, the method described therein iirc worked fine when I tested it and can be implemented efficiently. The only flaw in my mind is that the paper sort of suggests that standard analysis techniques will fail in some way. Applying KCL actually works fine, however, and using WDF should work as well. So there is quite a few ways to tackle this circuit and get the same result. Anyway it is a very old paper so we should not apply today's standards here and moan too much about it.

Richard
Synapse Audio Software - www.synapse-audio.com

Post

I have played with it a bit too. Removed one of the diodes, changed the equations to follow triode grid-cathode current consumption instead of the silicon diode equation, added a resistor instead of the removed diode, applied some voltage gain to the result obtained by the newton solver and applied a tanh(x) afterwards as a rudimentary model of plate clipping and while the result was more interesting than a simple wave shaper (it adds some kind of texture to the overdriven chords that is reminiscent of actual tube distortion), it obviously needed a more rigorous approach. Many actual amps have more complex circuits between gain stages. I hope to try modelling a two gain stage tube preamp with this simulator when I find some time. That includes all the basics: the interaction of the grid current with the coupling capacitor between the gain stages is supposed to cause variation in the duty cycle of the clipped waveform, and the grid current should also load the previous gain stage a bit, and eventually that should give the more complex audible 'texture' observed in actual amps. That's my 'theory' anyway.
~stratum~

Post

Richard_Synapse wrote:
stratum wrote:I remember that problem from this paper http://www.simulanalog.org/statevariable.pdf

Oddly, it doesn't reference or mention any MNA related paper. Maybe generic realtime circuit simulation wasn't feasible with the CPU's of the day and they didn't even search for it.
No it certainly was not, and it still does not make much sense today (not without hefty optimizations, that is).

About the paper, the method described therein iirc worked fine when I tested it and can be implemented efficiently. The only flaw in my mind is that the paper sort of suggests that standard analysis techniques will fail in some way. Applying KCL actually works fine, however, and using WDF should work as well. So there is quite a few ways to tackle this circuit and get the same result. Anyway it is a very old paper so we should not apply today's standards here and moan too much about it.
The parent project of Halite can actually generate C-code that almost always beats (typically by a wide margin) anything I could write "manually" in practice and there's still more (easy, although somewhat "minor impact") optimisations I could add. Obviously it won't automatically substitute sinh() for a pair of diodes or do any component model simplification, but using lumped and simplified components directly can fix that kind of stuff.

So... like... honestly I think it's easier to let a generic algorithm figure it out.

Post

So I was looking at the iteration counts and wondering why they were so high and realised that I did something really stupid when I wasn't thinking properly. In the newton() methods for the BJT and the OPA that I posted, one should obviously use & instead of && to combine the return values, so that it actually iterates everything at once without short-circuiting.

Fixing that will allow it to often get the convergence with just 1 iteration as expected.

I'll probably have to update the gist at some point, but .. yeah... sometimes it's hard to think properly. :D

edit: oh right, gist revised. :)

Post

A bit off-topic, Signaldust related questions: is there a chance that Salt will be updated with PNP? And, is that parent project something average plugin user should be excited about?

Post

mystran wrote:The parent project of Halite can actually generate C-code that almost always beats (typically by a wide margin) anything I could write "manually" in practice and there's still more (easy, although somewhat "minor impact") optimisations I could add. Obviously it won't automatically substitute sinh() for a pair of diodes or do any component model simplification, but using lumped and simplified components directly can fix that kind of stuff.

So... like... honestly I think it's easier to let a generic algorithm figure it out.
Sounds cool :)

What kind of speedup does this advanced algorithm give? The Fuzz Face I built with Halite needs about 100% CPU at 44.1 kHz so I'm curious just how low it could be pushed without simplifications.

I'm using this schematic (w/ 9 nodes including ground, and disregarding the pot):
https://www.electrosmash.com/images/tec ... -parts.png

Richard
Synapse Audio Software - www.synapse-audio.com

Post

Richard, is it possible you can share that Halite Fuzz Face with us?

It seems like a dream in terms of educational value for us newbies - a schematics along Halite code...

Post

Sure :)

Code: Select all

// Halite Fuzz Face
NetList * net = new NetList(9);

net->addComponent(new InputSignal(1, 0));
net->addComponent(new Capacitor(2.2e-6, 2, 1));

net->addComponent(new Resistor(100000, 2, 3));
net->addComponent(new Resistor(1000, 0, 3));

// Voltage Source
net->addComponent(new Voltage(-9.0, 8, 0));

net->addComponent(new Resistor(470, 8, 6));
net->addComponent(new Resistor(33000, 8, 4));

net->addComponent(new Resistor(8200, 6, 5));

// Output node
net->addComponent(new Capacitor(0.01e-6, 6, 7));
net->addComponent(new Resistor(500000, 0, 7));

// PNP transistors
net->addComponent(new BJT(2,4,0, true)); // base, collector, emitter
net->addComponent(new BJT(4,5,3, true)); // base, collector, emitter

net->buildSystem();
The result seems to match the SPICE output exactly, so this model should work. Haven't tested a wide range of inputs though.

Not shown is the "InputSignal" class which is simply a voltage source which inserts a 44.1 kHz sample into the circuit (if you want a sine wave at an arbitrary rate, use the function generator instead). I grabbed the output signal directly from node 7, alternatively just insert a Probe.

The general approach is very simple. Print out any schematic you want to model, then label the nodes arbitrarily using consecutive integers (1,2,3,4,5,...). The ground node is 0. Now build the network using the addComponent method, by simply using the numbers you chose above. So if a resistor sits between ground and node 2, you write net->addComponent(new Resistor(100000, 2, 0), etc. See attached image below for the node numbers corresponding to the Fuzz Face.

Hope this helps as a quick start guide!

Richard
You do not have the required permissions to view the files attached to this post.
Synapse Audio Software - www.synapse-audio.com

Post

urosh wrote:A bit off-topic, Signaldust related questions: is there a chance that Salt will be updated with PNP? And, is that parent project something average plugin user should be excited about?
Right, so in case someone didn't get the pun 'cos the word isn't familiar, "halite" is a mineral composed of NCl which is commonly known simply as "salt." In the mineral form it's a bit hard to use it to "spice" up your food though: it needs further refinement. ;)

The code of the old "Salt prototype" has various issues, mostly due to the fact that when I started writing the project I had only a vague idea of what would be needed. As a result the code is a huge mess, because all the abstractions chosen are in the wrong places and the "solver" not only does ten things at the same time, but also has to do some ridiculous stuff to try to apply some of the optimisations (and is known to fail rather consistently, but finding the problem has proven "challenging" to put it lightly). So it's hard to add anything into it, because you never really know if it's working or not (because it just fails randomly on various stuff anyway).

So rather than try to meditate until the old code starts working, I figured I could just as well take what I learned and fix things from the ground up: write a nice and clean "regular" circuit simulator that can easily apply the important optimisations on algorithmic level, then let it record the solution process so that we can generate code if desired. This way it's much easier to actually figure out what's going on (to either improve the results or fix problems).

Right now it can do such a recording and write it out as C-code and in many cases the code is already slightly better than what the old prototype could do. It doesn't have a schematic editor yet, but that's the next thing on the list. Eventually I'll probably try to add a JIT again at which point it could be put "back" into a plugin.

All this might take a while, so just don't get too exited.

Post

Richard_Synapse wrote: What kind of speedup does this advanced algorithm give? The Fuzz Face I built with Halite needs about 100% CPU at 44.1 kHz so I'm curious just how low it could be pushed without simplifications.
There's basically three main things that can speed it up a lot:

1. we start from scratch every iteration, even though most of the matrix is constants so it could be pre-calculated just once; this is done for simplicity, but it's really inefficient

2. related to the previous point, we solve the matrix in the order it was specified (if you ignore the partial pivoting) even though we could re-ordering it to further maximise the amount of stuff that can be pre-calculated and minimise the number of nodes we have to solve if we just want a partial solution (ie. outputs, new capacitor voltages, etc)

3. looping through an actual matrix with generic code is just very slow in general; once you have a pivoting order that works (which is practically always good enough to use forever), you can make it a lot faster by simply compiling the actual math into straight-line machine code

I might try to get an actual figure at some point, but with the above optimisations (and a bit of help from a C-compiler) the bulk of the CPU is going go into the Newton-evaluations and you might be looking at a speedup factor of 100 or so (which is really just an educated guess at this point, but you probably get the idea).

The "parent project" that applies optimisations 1 and 2, but otherwise uses a similar generic solver currently solves the original BJT test circuit (all nodes) with about 250k steps per second on my MBP (i5-5257U CPU @ 2.70GHz, currently running on battery), with the original function generator as the input and the cost of the Fuzz-face circuit would like be similar (unless it has to iterate a lot more). My educated guess would be a speedup of up to 10 or so once you compile to native (which I can try once I fix a few minor things with the C-code backend).

Post

Oh right.. got the remaining bugs fixed in my C-generator so I could try how fast the code runs in practice.

Solving the BJT test with the same device models as in halite (after the Newton fix, which is on gist.github), compiled with clang (-Ofast) just for the probe output and using the same test-function generator as input, takes ~11ms on this MBP to process 1 second just looping simulateTick() and the function generator at samplerate 44.1kHz (and processing 100 seconds takes just under a second). In practice shorter blocks would take a bit more (thanks to caches) and oversampled input would take a bit less (since the sharp edges from the function generator cause some extra iteration: it says 1.62 average over the 100 second loop).

So that's about 1% load on this computer for that circuit without oversampling (although it'll take some more if the input is totally ridiculous).

edit: urgh, testing the Fuzzface I get WAY more iteration in native code, so something's not right :(

edit2: not true, they give identical, test setup was broken :D

Post

Pushed another update: looks like the diode Newton routine was using the wrong node (probably because it used to have an extra node for the series resistor at first and apparently I never fixed it when I got rid of that). So if you tried to do something with diodes and they were broken and you didn't find the problem yourself, it's fixed now. :D

Post

mystran wrote:Pushed another update: looks like the diode Newton routine was using the wrong node (probably because it used to have an extra node for the series resistor at first and apparently I never fixed it when I got rid of that). So if you tried to do something with diodes and they were broken and you didn't find the problem yourself, it's fixed now. :D
Thanks for the update! A minor note, the stampConductor() method seems to be missing (when including that OPA class from your other post).

Richard
Synapse Audio Software - www.synapse-audio.com

Post

Hi!

Thanks for this one!

But i wonder if it's possible to convert this result to plugin or source code?

I'm electric engineer and using Spice programs.I can convert my Spice designs to netlist to use with "Halite".But i would like to convert your design's results to source code...

For examples here is my Fairchild Summing Mixer netlist.

Can you convert it to Halite,please?I just want to understand how to convert netlists.

(C5 is output node)

Code: Select all

Q1 N005 N011 N016 0 2N2222
Q2 N006 N005 N015 0 2N2222
R1 N015 N011 3.3meg
R2 N016 0 150
R3 N001 N005 180k
R4 N015 0 560
C1 N015 0 330µ
R5 N001 N004 3.9k
D1 N004 N012 1N4148
D2 N012 N006 1N4148
C2 N006 N016 470p
Q3 N001 N004 N007 0 2N2222
Q4 0 N006 N013 0 BC327-40
R6 N008 N007 4.7
R7 N008 N013 4.7
C3 N008 N014 100µ V=63 Irms=450m Rser=0.3 Lser=0
C4 N017 N008 100µ
R8 N017 N016 1
C5 N014 0 750p
V1 N003 0 24
R9 N002 N001 47
V2 N009 0 SINE(0 0.775 1k) AC 1
C6 N011 N010 2.2µ V=50 Irms=54m Rser=3 Lser=0 mfg="Nichicon" pn="UPL1H2R2MAH" type="Al electrolytic"
R10 N010 N009 150k
D3 N003 N002 BAT54
R11 N009 0 680
.model D D
http://analogobsession.com/ VST, AU, AAX for WIN & MAC

Post

tunca wrote:Hi!

Thanks for this one!

But i wonder if it's possible to convert this result to plugin or source code?

I'm electric engineer and using Spice programs.I can convert my Spice designs to netlist to use with "Halite".But i would like to convert your design's results to source code...
Halite is source code. So yes you could use it in a plug-in (probably rather for experimental purposes though, due to its performance).
tunca wrote:Can you convert it to Halite,please?I just want to understand how to convert netlists.
See my post above, and have a look at the image. You label the nodes using integers starting from 0 (ground) that's it :)

Richard
Synapse Audio Software - www.synapse-audio.com

Post Reply

Return to “DSP and Plugin Development”