Accurate analog models with low cpu

To see the generated code, once you simulate a model and you are in Simulation Center you can go to View->Generated Source Code. I have to warn you that it is not easy to take that code and plug it to other simulation backend. It is very customized and the equations are hard to extract without a lot of manual work. I recommend you to see the Equation Browser. You will find it in Tools->Equation Browser. There you can find an easier view of the equation blocks and the order in which they are solved.

This is an example of that view where I’m showing on of the blocks of non-linear equations that need to be solved for one of the semiconductors.

Other important thing is that the models in my blog post are made with the Modelica Standard Library 3.2.1 and they will not work out of the box in 4.0.0. We have a tool to migrate them but I don’t know if it’s released yet.

Thanks, I’ll have a play and see how I go.

For reference my method involves defining the circuit as shown in the linked .cpp file, and then running my solver class to directly generate .h and .hpp template files, so the entire turnaround time between modifying the circuit and having it compiled as a module in VCV Rack is around 1-2 mins, and this goes up to around 5-20 mins if I enable more in-depth simplifications, or even longer if I want, but there isn’t much gain after around 10 mins.

https://cytomic.com/files/forums/cytomic-cf100-ds-circuit.cpp

I write each of the component models, which add entries into the sparse symbolic matrix, and then generate the required setup and non-linear evaluation code, and the solver computes the symbolic matrix solution, and then the optimiser makes it all run fast and spits out the final code.

In the CF100 I’m modelling the OTAs with a Boyle like input only differential npn transistor pair, then have diodes doing the clipping next to the filter capacitors to save on a few pnps, and the output BJT Darlington buffers do add a tiny bit of drive, but I’ve skipped this and just used perfect buffers with some dc offset since I couldn’t hear much difference because the internal of the OTAs did all the clipping / tone.

Ahh, I see, it looks like Modelica is built into the Wolfram System Modeler. @modlfo : So is the System Modeler a nice front end for Modelica? Basically it generates code that calls into Modelica to actually do the computation? I was hoping it would generate the code directly that did the simulation, not call a library to do the heavy lifting.

@brer_rabbit Probably this should be in its own thread to not distract from this great discussion with @andy-cytomic and @modlfo . This stuff is just stunning!! Do you plan to make it as a open source DIY project or a commercial one? Great stuff indeed @brer_rabbit !

@alfonso.santimone - I’ve a thread on some of the dev & some demos. I’ll respond over at Zoxnoxious analog synth VCV Rack demos to not hijack this thread

1 Like

I do not understand exactly what you mean. I’ll try to explain a bit the architecture of the product and maybe it makes it more clear for you.

This is a screenshot from a presentation I made where I show the architecture of the program. The presentation may not be very interesting for this topic but here’s the link if anyone interested on functional programming wants to watch it.

On the bottom you can see the “Kernel” which is a kind of language server. It includes the Modelica compiler and the routines to communicate with the clients shown on top.

Model Center is the program where you can create models graphically and textually. Those models are saved as Modelica models (textual format). The Modelica code stores all the information (equations, components, connections, graphics, simulation settings) what makes it a portable format that can be used in other Modelica tools.

Simulation Center is the program to control the simulations, plot results and perform common analysis.

Mathematica (Wolfram Language) can act as a scripting language for Modelica models. It allows you to create models from Wolfram Language equations, control simulations, perform more advanced analysis and retrieving equations, linearized models etc.

If users have Mathematica only, they can create SystemModeler models (Modelica) from equations. They can also perform simulations of this models and plot the results using the Wolfram Language. If they prefer to create models graphically, System Modeler (Model Center + Simulation Center) is the best option. Some users just need System Modeler and not Mathematica. At the end, a combination of both is what gives all flexibility.

When a user runs a simulation, we process the model and generate C++ code that is compiled and linked to our simulation engine. This results in an executable program that can be called by the clients (Simulation Center and Mathematica) to perform simulations.

1 Like

Very interesting, thanks for sharing this discussion with the rest of us, Andy and Leonardo!

2 Likes

We experience something similar. When processing a model, we perform a bunch of optimizations but we avoid the most complex ones because often times it does not worth the effort. To give you an example, on my current machine processing a mechanical model with around 1000 equations takes 12 seconds. Simulating the model takes 0.2 seconds. If we perform more aggressive optimization the compilation time will go above 12 seconds and the gain in simulation time would be minimal. In total, the spent time will be with normal optimization 12.2 seconds and with aggressive optimization most certainly more than 12.2 seconds.

Since System Modeler mainly a modeling tool it is often more important to our users being able of performing fast iterations to their models e.g. modifying the model and quickly see results. One thing we have not explored much is to do less optimzation to what we haven now, that way we could get results like 7 seconds to compile the model and 1 second to simulate (these numbers are made up).

You have a very nice representation of the circuit embedded in C++.

Your solver is more like a nodal (SPICE) solver then? or do you do symbolic processing to try to reduce the matrix size?

Thanks for the breakdown. I had a look at the generated code and it looks a bit slow is all, a bunch of if statements, and things split a lot between different functions. I’ll have a more in depth look over the trial period of the System Modeler. The graphical schematic editor looks gorgeous, and it’s great having such easy plotting, so being able to slot my own code generation somehow into this system would be really cool.

Thanks! I’ll abstract everything a little more to a factory method to create everything, and also support text file input, so hopefully I can use something like LTSpice to create the netlist, and add custom comments / spice directives for all the additional parts I need to help specify the solver parameters that way hopefully I can just use LTSpice directly to generate the input text to my solver / code generator.

Yes, my main code generation in current products is based on nodal analysis, but the matrix can be anything solved to the regular form A.x = z as a sparse symbolic matrix. I also have substitutions I perform on the entries for terms that can tend towards zero, so I can evaluate them with this apriori information to not pivot on something close to zero.

The matrix size doesn’t matter too much since it’s sparse. I currently solve the matrix using either Gaussian Elimination or LU decomp symbolically, and then the steps are optimised directly. I am able to manually set the order terms are solved to manipulate the location of non-linear terms, but I haven’t thus far spent too much time trying to do this automatically or reduce the matrix size before solving, but that is definitely an interesting idea, and something I’m sure Mathematica / System Solver can help with. I also solve multiple matricies, the regular Nodal “tick”, a special “warmup” version where call caps are set to zero, and DK / PDK versions which generate shearings and allow for pre-computation of the solutions, and interpolating multi-dimensional tables for “single step” direct solution, or a small number of steps for global iteration type solutions.

The other part that is important to me is pre-calculation of as many values as possible. I pull out there invarients to help the compiler as much as possible:

  • randomisable component value constants (eg resistor / cap values)
  • sample rate dependant constants / other values derived from constants (eg capacitor conductance based on the numerical integration used which is changes based on sample rate)
  • parmeter “set” dependant constants (eg setting the filter cutoff, resonance etc)
  • per sample constants
  • inner solver loop constants

This might be a good point to tease a project I’ve been working on for the past year. First my background is in computational physics, but not at all in the electronic engineering area so this domain is totally new to me. My idea was to teach myself circuit simulation and build/breadboard hardware circuits as I go along. To do this I’ve been following Mortiz Klein’s excellent series which is brilliant for making things as simple as possible whilst still being actually usable and useful, and he does a great job of building intuition.

On the simulation side, I’ve been following @andy-cytomic’s ADC talk, mystran’s halite code and a few other resources. I started by implementing a symbolic MNA solver using sympy (in python) that autogenerates C++ code. I’ve managed to simulate the mixer (with diode clipping), envelope generator, and all VCF variants (starting from simple RC, though to the full diode ladder filter) with this approach. I’m really pleased with what I’ve managed so far and the diode filter actually sounds pretty nice to my ears. But it still has some pops and clicks in extreme use. I’ve been writing it all up as a series of blog posts comparing to the breadboarded circuits, which I will eventually make public. I’m now exploring state space methods to compare and contrast, and eventually hope to cover the full series of modules.

The aims are almost orthogonal to Andy’s though, in that this project will probably be:

  • amateurish / entry-level / unsophisticated / high-ish CPU
  • free / open source
  • pedagogical

Once I’m happy enough with the quality of the code / blog, I’ll publish it and start a thread here. I’ll certainly have a few questions for the experts!

9 Likes

Great, work @hemmer! Thanks for letting us know what you’re up to. Computational physics sounds a lot more general than circuit simulation, but there will be plenty of overlap. I try to teach a bit as well (pedagogical), so hopefully that part isn’t too orthogonal.

The little pops and clicks are definitely a thing, that’s usually stuff not converging, and it’s generally non-trivial to come up with stuff that does converge quickly with a fixed number of iterations at a fixed and low sample rate. Most of the time a bunch of hand tuned hacks / tricks are needed to make everything behave nicely to minimise the crud like that. After so many years knowing where things are likely to go wrong I’m pretty much a professional digital crud finder, it doesn’t take me long to get a lot of different algorithms break! I’m pretty unkind on my own stuff to try and pick this up, but sometimes a little bit still slips through.

It’s good to see you’re using Julia. I really wanted to use it for my code generator, I was thinking of using Julia’s internal “macro / expression re-write tree code injection” type stuff to do some of the work, but then realised that I really needed to use with a Mathematica like n-ary tree structure for the expressions to make simplification / substitution easier, so just wrote it all in c++. I do like Julia though, and hope I can use it more in the future.

1 Like

I’ve watched and enjoyed lots of their videos as well. Watch out for his “diode ladder” filter, it’s not a diode ladder, it’s a Steiner-Parker partly differential Sallen Key filter. Diode ladder filters are the ones like in the TB-303 and EMS, which have a “rung” of capacitors hooked up between the a parallel series of diodes (or transistors).

For sure, I’ve found that ADC talk / papers very helpful! And in case it’s misinterpreted, the point on open source / free isn’t a dig, if I had invested 10+ years of R&D work to produce something of real commerical value, I’m certain I’d keep the source private too!

Basically solving ODEs/PDEs for fluid dynamics type of problems, so there is some common ground.

Yeah - so far I’ve only really used a vanilla Newton solve step, but it’d be good to see if e.g. damped methods work.

As for Julia, I’ve actually only really being playing around with the library GitHub - HSU-ANT/ACME.jl: ACME.jl - Analog Circuit Modeling and Emulation for Julia as I read the associated papers. My main implementation is still just in python → C++. Super neat and compact language though!

Sorry, you did clearly say you used Python. Python is a great language as well :slight_smile:

@modlfo : do you have an idea of what are the best terms are to search for on the topic of manipulating a matrix to put all the non-linear elements in most efficient form to solve? I’d like to read some literature on the various methods, and knowing maths people there will be a lot of them, most likely invented a long time ago!

You’ve piqued my interest enough to chuck up a not-ready-for-public version of the blog (usual disclaimers). I am also interested in this topic and came up short. Basically I could “by hand” re-order the MNA matrix to have elements that change in the top left and fixed in the rest, and therefore only newton iterate the top left. This only works by hand for a very simple example of course:

For a more complex example I struggled to get anywhere:

I also asked on KVRAudio but didn’t get any bites (and that thread went off the rails…)

EDIT: I should add, in practice I don’t explicitly solve any Ax = b problems each iteration, I symbolically solve these at precomputation stage but the same logic applies.

1 Like

This looks great, thanks for sharing! Another key point to consider is that there is an additional partitioning for the top left corner, where you only need so solve a sub-set of the remaining voltages - those that are used to evaluate the non-linearities. The last voltages, which will appear in any linearly dependant only sub-parts of the circuit, can be evaluated once you have converged.

I also pre-compute all solutions symbolically. I see the trouble, but I’m not sure what can be done about it though. Can you please share the netlist of circuit you’re having trouble with so I can have a look as well?

(also I think the heading “Optimised Approach” is meant to be above the 2nd code snippet)

@hemmer how about construct your A matrix by ordering the x terms to include in it first entries all the voltages that the non-linearities require as inputs to evaluate, and all the voltages they output as well. That’s how I do it and it seems to work fine.

1 Like