google-deepmind / torax

TORAX: Tokamak transport simulation in JAX
https://torax.readthedocs.io
Other
295 stars 24 forks source link

Arbitrary heating and current source profiles #212

Open theo-brown opened 1 week ago

theo-brown commented 1 week ago

While future work may focus on developing specific physically realistic profiles for current and heating actuators, in the meantime it would be useful to be able to represent arbitrary source profiles so that scenarios generated in other codes can be run in TORAX.

The most basic of these would be a piecewise linear profile.

theo-brown commented 1 week ago

I would be happy to extend sources/formulas.py and sources/formula_config.py to support this, if a core contributor would give me the go-ahead! Not sure if there is a better option that I'm missing.

jcitrin commented 1 week ago

Fully agree that supporting arbitrary source profiles should be done in the near-term.

At the moment we are working on supporting arbitrary initial and prescribed core profiles, and the infrastructure is already there to a large extent. Te, Ti, ne are now of type TimeInterpolatedArray (see interpolated_param.py) and can be defined in config as nested dicts in a time series {time: value} like {0: {0.0: 20, 0.95: 5, 1.0: 0.2}, 1: {0.0: 20, 0.95: 5, 1.0: 0.2}}, where value is itself a dict representing {rhonorm: value}. Down the road, we plan to generalize TimeInterpolatedArray to accept more general datastructures such as xarray datasets or numpy arrays, as long as they satisfy specific formats. Work has started in this direction, see #197 . This will enable loading these values from output files of previous runs, from IMAS (there are IMAS IDS to xarray converters out there), or more generally from any userdata as long as it's converted to the right format.

After this long preamble :) , I think we should reuse all this infrastructure to enable arbitrary source profiles as well. Can envisage adding a new supported mode (see Mode class in sources/runtime_params.py) like PRESCRIBED, and the source then gets as input the nested dict, or numpy arrays, or an xarray dataset, or a filepath to an .nc file or IMAS h5 file, etc (note that currently only nested dict input is supported). The PRESCRIBED mode could include options like a multiplication factor to the input, or normalizing the input to a desired total integrated source amplitude.

You could indeed also extend sources/formulas.py and sources/formula_config.py to do the same. This is easier since there's less new development to do, but likely down the road we'll move this into a new PRESCRIBED mode. I'd recommend a new "formula" class something like (just a suggestion)

@dataclasses.dataclass
class PiecewiseLinear:
  """Configures a piecewise linear profile."""

  use_total: bool = False
  total: TimeInterpolatedScalar = 1.0
  multiplication_factor: TimeInterpolatedScalar = 1.0
  profile: TimeInterpolatedArray

  ...

Where the user has a choice whether to use the profile as is, or to renormalize to total if use_total=True, and/or to multiply by multiplication_factor for sensitivity tests. The onus is still on the user in a pre-processing step (can be done in the config file itself, above the CONFIG definition) to convert your source data into the nested dict format for input into profile, due to the limited support for other data structures at the moment.

jcitrin commented 1 week ago

and yes, feel free to go ahead and give it a go if you're up for it :)

jcitrin commented 1 week ago

@araju for any further insight here

jcitrin commented 1 week ago

@theo-brown , note #215 in case you get confusing results when playing around with the source config.

theo-brown commented 1 week ago

Adding a PRESCRIBED mode seems much smarter. I'll make a start there, tying in with the TimeInterpolatedArray stuff. Thanks for the tip regarding #215 !

theo-brown commented 1 week ago

Encountering some difficulty with the difference between grids; current density seems to be computed on the hi-resolution grid, but the DynamicRuntimeParamsSlice discretises stuff on the standard grid. Not sure what the best way is for tackling this.

Skipping over the hardcoded Gaussian for the moment, this results in the following obstacle for adding support for PRESCRIBED sources:

  1. build_dynamic_runtime_params_slice is called by the main routine, and all TimeInterpolatedArrays are interpolated onto the standard grid at this timestep.
  2. When it comes to computing the high-resolution currents, we no longer have access to the un-interpolated array; the DynamicRuntimeSlice only contains the interpolated one.

An easy fix could be to re-interpolate the low-resolution interpolation onto the high-resolution grid. My concern is that this is a bit of a sticking-plaster solution, and is avoiding tackling the underlying pitfall.

jcitrin commented 1 week ago

Some deficient/not-yet-implemented elements have been exposed here :) , and good to raise them for prioritization.

First some explanation on the hires grid and what it's used for. It's only used for initializing the psi profile for when the current profile is set to determine the initial psi profile (when CONFIG['runtime_params']['initial_psi_from_j']=True) (see here and what calls it)

Now, in that function, there is a deficiency that the only external current that can be included now is jext. And indeed jext is hard coded to just be a Gaussian formula with convenient physically intuitive names for width, location, and current fraction (which sets its amplitude). This is a kind of proxy current source for our initial testing purposes. The actual physical current sources have placeholders in sources/current_density_sources.py and aren't yet implemented, i.e. not yet included in sources/default_sources.py or have specific implementations of them.

You can see this commit to see what adding a new source entails. There is work coming soon to simplify the process of registering a new source.

jcitrin commented 1 week ago

Which sources are you interested in implementing as PRESCRIBED? From the questions it feels like ECRHCurrentSource?

That is a bit more involved due indeed to the need to extend _get_jtot_hires to include all available current sources. But also not a blocker to get started with a PRESCRIBED ECRHCurrentSource if starting with a prescribed psi from a geometry.

theo-brown commented 1 week ago

Thanks for the explanation about the hires grid! That makes a lot of sense, and I hadn't spotted that it would only be used in the initialisation.

Yes, my main interest is ECRHCurrentSource, but I was basically using it as a way in to learning about the sources generally, as well as the internal workings of the code. I thought that perhaps a PRESCRIBED mode would be a good way of contributing something that would be useful more generally :) I started off just in the base source class, and then worked step-by-step from there to identify and fix problems, with the aim of allowing any source to be prescribed (which I think is a useful goal).

However, as you've highlighted, it's come across some of the surrounding bits that aren't quite there yet and implementing a new mode is wider in scope than I first realised! There's definitely a tradeoff between making a generally-applicable contribution and biting off a manageably-sized problem...

Basically, I'm interested in this part of the code and would be keen to contribute. Let me know if there are any ways I can better dovetail with existing or planned work.

jcitrin commented 5 days ago

I think that you can still consider setting up a PRESCRIBED mode and it will work for jext as well, minimizing the amount of new code needed. We'll probably get to it later this month in any case, if you'd rather wait.

theo-brown commented 2 days ago

PRESCRIBED mode with tests finalised in #221. Note that due to the aforementioned quirks of jext the tests are only for prescribed particle sources at the moment, but should work in future for all sources.