sharpie7 / circuitjs1

Electronic Circuit Simulator in the Browser
GNU General Public License v2.0
2.29k stars 633 forks source link

Disambiguating getIterCount() and it's use in runCircuit() #763

Open Mervill opened 2 years ago

Mervill commented 2 years ago

With respect to time and timing in the circuit, there are two major things to be aware of (as I understand it):

  1. The time step is the value set by "Time step size" in the options menu and displayed below the total time in the lower right section of the screen.
  2. The simulation speed is controlled by the "Simulation Speed" slider upper right hand section of the screen.

Inside runCircuit() there are two loops, the outer iteration loop and the inner subiteration loop. The outer iteration loop represents one step of time in the circuit, i.e. each run of the iteration loop advances the total elapsed time by the time step. We don't need to worry about the behavior of the subiteration loop right now (it's based on element convergence).

How the outer iteration loop exits is the focus of this issue. The simulation speed is used to help determine if runCircuit() can run the iteration loop more then once per fame, or if it should exit.

// This is the main way that the iteration loop exits.
// Here 'steprate' is the value ultimately derived from the value of the 'Simulation Speed' slider.
long steprate = (long)(160 * getIterCount());
if ((timeStepCount - timeStepCountAtFrameStart) * 1000 >= steprate * (tm - lastIterTime) || (tm - lastFrameTime > 500))
    break;

For clarity's sake, we can discard the or clause on the end (just checking to see if the delta-time is greater then 50ms) and break out the sections:

long steprate = (long)(160 * getIterCount());
int deltaSteps = (timeStepCount - timeStepCountAtFrameStart);
long deltaTime = (tm - lastIterTime);

if (deltaSteps * 1000 >= steprate * deltaTime)
    break;

Exactly what's going on in this statement isn't clear at all to me. Multiplying steprate by the current deltaTime would give a value that is an estimate of how long it would take (in ms) to run the iteration loop steprate times. I think? The left hand side also isn't clear to me either.

Then we come to getIterCount()

double getIterCount() {
    if (speedBar.getValue() == 0)
        return 0;

    // An explination of what is going on here would be extremely helpful.
    // What units are we dealing with? Iterations per frame? Per second?
    // Why these constants?
    // Why is the speedBar range 0 to 260?
    // Why is the result multiplied by the constant 160?
    // Logarithms are scary and intimidating, I am not good at math :p
    return 0.1 * Math.exp((speedBar.getValue() - 61) / 24.0);
}
// Create a scrollbar that can select the values 0 ... 260 (inclusive)
verticalPanel.add(speedBar = new Scrollbar(Scrollbar.HORIZONTAL, 3, 1, 0, 260));
Ultimately `steprate` is determined by:
steprate = 160 * (0.1 * Math.exp(([0 ... 260] - 61) / 24.0));

My goal with these questions is to disambiguate how the iteration loop decides how many times it will run in a given frame. Once a good explanation has been hammered out I'll summarize it and add comments to the code and update the INTERNALS doc appropriately. In the long term I think this will help with achieving measurable gains in performance.

pfalstad commented 2 years ago

steprate determines how many iterations we will try to do per second. deltaTime is in milliseconds. so steprate*deltaTime/1000 is the number of steps we should be doing in deltaTime. If deltaSteps is more than that, we should stop.

getIterCount() originally determined the number of iterations per frame, but then I changed it so that the simulator attempts to run at a constant number of iterations per second. I scaled it by 160. (so an iter count of 1 (iters per frame) becomes a step rate of 160 iters/sec) A lot of these numbers are determined by trial and error.

getIterCount() just gives a number between 0 and 399. When multiplied by 160, it gives you iterations per second, I think.

We use exp() because it lets you increase the iterations per second to a large number at the top of the scale, but still gives you fine control at the low end.