ukaea/PROCESS

Burn time consistency loop

Closed this issue · 4 comments

Discussed in #3048

In Power balance output and tburn consistency #17 I added an extra extra loop a long time ago in fcnvm1 (evaluators.py) to ensure that tburn has reached a converged value. This will run all the physics and engineering functions up to 10 times, so it could be contributing to slow run times.

        # Evaluate machine parameters at xv
        self.caller.call_models(xv)

        # Convergence loop to ensure burn time consistency
        if sv.istell == 0:
            for _ in range(10):
                if abs((tv.tburn - tv.tburn0) / max(tv.tburn, 0.01)) <= 0.001:
                    break

                self.caller.call_models(xv)
                if gv.verbose == 1:
                    print("Internal tburn consistency check: ", tv.tburn, tv.tburn0)

See comments below.
Conclusion: no change is required now.
However, if and when PROCESS is changed so that all the function evaluations are repeated several times, this loop should be removed.
@jonmaddock @timothy-nunn

It seems that the problem arises because the calculation of loop voltage vburn is duplicated and the whole thing is oddly circular.
Pseudocode:

        physics.physics()
        def physics()
              tburn0 = tburn
              tpulse = ... + tburn + ...
              # Calculate Volt-second requirements
              ..., vsstt = vscalc(...tburn...)
             def vscalc():
                 # Volt-second requirements
                 # Loop voltage during flat-top
                 vburn = plascur * rplas * facoh * csawth
                 vsbrn = vburn * (t_fusion_ramp + tburn)
                 vsstt = vsstt + vsbrn

        pfcoil.run()
             pfcoil.py
            def run():
            # Volt-second capability of PF coil set
            self.vsec()
           def vsec():
                   # Calculation of volt-second capability of PF system.
                   # vsbn = total flux swing available for burn (Wb)
                   pfv.vsbn = .....
        
        pulse.run()
        pulse.py
        run()
           burn()
               # Routine to calculate the burn time for a pulsed reactor
               if pulse_variables.lpulse != 1:
                     return
               vsmax = -pfcoil_variables.vsbn
              # Loop voltage during flat-top 
              vburn = plascur * rplas * facoh * csawth
              tburn = vsmax / vburn - t_fusion_ramp

In principle I would like to eliminate the switch lpulse, since any tokamak burn is limited in duration, and can therefore be considered pulsed. However, the STEP input file uses the "steady-state" option (lpulse = 0), so it's not wise to fix something that's working and needed.

Name Type Datatype Default Value Description
lpulse Output integer - Switch for reactor model: =0 continuous operation =1 pulsed operation
tburn Input real 1000.0 burn time (s) (calculated if lpulse=1)

It turns out that the consistency loop above is not used often.

In the large-tokamak scenario, the total number of function evaluations is ft.numerics.ncalls = 1364, while the loop is entered only 13 times. The vast majority of calls to call_models are in the gradient function evaluator fcnvmc2, since a gradient is evaluated for every iteration variable, requiring two calls each.

I have tried replacing the line that ensures tburn is positive (times_variables.tburn = max(0.0e0, tb)) by a smooth function that is always positive:
image

Original code:

times_variables.tburn = max(0.0e0, tb)

Smooth function:

        # if calculated burn time is less than 10 seconds use a smooth positive function.
        if(tb < 10):
            times_variables.tburn = 100 / ( 20 - tb )        
        else:
            times_variables.tburn = tb

Allow negative tburn:

times_variables.tburn = tb

(I have used error tolerance for VMCON epsvmc = 1e-4 to save time.)

I tried this out in a version of large-tokamak in which the thickness of the CS was gradually reduced:

nsweep = 63 * ohcth : CS thickness (m) 
isweep = 5
sweep = 0.42, 0.4, 0.39, 0.38, 037

All three options give exactly the same values for nviter and the cumulative number of function calls ncalls.

I have also tried removing the consistency loop just for fun. The scan above crashes on the first scan point.

Changing the scan:

sweep = 0.55, 0.4, 0.39, 0.38, 037

gives:
No loop:

  • nviter = 29, 11, 5, 5
  • ncalls = 5017, 6866, 7659, 8452, crash.

With loop:

  • nviter = 22, 10, 5, 5, crash
  • ncalls = 3817, 5499, 6293, 7087, crash

The loop seems to help a little in this one example.