Add spectral measurement capability to `BolometerFoil` class or to a new class in `observers` to enable simulation of AXUV diodes
I started working with AXUV diode measurements on ASDEX-Upgrade which have a notable spectral dependency in their response function (and with plasma operation, this response function also degrades). Still, these diodes are similar to bolometers in the sense that they measure radiated power. Therefore, I suggest adding spectral pipelines to either observers.BolometerFoil or adding a very similar, separate class for AXUV diodes. Then the user can apply a custom spectral response function and even spectral filters (e. g. on KSTAR) while post-processing the synthetic signals.
I have some specific suggestions, but do note that I am new to the code and I am not too familiar with the inner workings of it just yet. With that said, here are my suggestions for observers.bolometry.py:
In BolometerFoil
- In the
units()method at@units.setterI suggest adding
elif units == "SpectralPower":
pipeline = SpectralPowerPipeline0D(accumulate=self.accumulate)
elif units == "SpectralRadiance":
pipeline = SpectralRadiancePipeline0D(accumulate=self.accumulate)
else:
raise ValueError("The units property of BolometerFoil must be one of 'Power', 'Radiance', 'SpectralPower' or 'SpectralRadiance'.")
- In the
as_sightline()method, similarly:
elif self.units == "SpectralPower":
pipeline = SpectralPowerPipeline0D(accumulate=self.accumulate)
elif self.units == "SpectralRadiance":
pipeline = SpectralRadiancePipeline0D(accumulate=self.accumulate)
else:
raise ValueError("The units argument of BolometerFoil must be one of 'Power', 'Radiance', 'SpectralPower' or 'SpectralRadiance'.")
Also in the same method where the SightLine object gets initialized, some changes might be needed. The user should be able to set for example the value of pixel_samples.
In BolometerCamera
The observe() method also needs to change, and I suggest adding type checking of the pipeline and appending at least samples.mean and samples.variance to observations:
def observe(self):
...
observations = []
for i, foil_detector in enumerate(self._foil_detectors):
foil_detector.observe()
if type(foil_detector.pipelines[0]) in [PowerPipeline0D, RadiancePipeline0D]:
observations.append(foil_detector.pipelines[0].value.mean)
elif type(foil_detector.pipelines[0]) in [SpectralPowerPipeline0D, SpectralRadiancePipeline0D]:
spectral_data = [foil_detector.pipelines[0].samples.mean,
foil_detector.pipelines[0].samples.variance,
foil_detector.pipelines[0].wavelengths]
observations.append(spectral_data)
return observations
Other
- I noticed that in the initialization of the
BolometerFoilthere is asuper().__init__()call which once again contains apixel_samplesvalue for theTargetedPixelbase class. This might interfere with externally settingpixel_samples. - The 0D spectral pipelines are already present in the
BolometerFoil.calculate_sensitivity()method, so potentially that could be modified instead of the units setter and theobserve()method? In that case, one could externally loop through the foils in theBolometerCameraobject(s). - As I mentioned in the beginning, adding a different class might be another solution, maybe the cleanest one.
I would happily contribute with some guidance from people who are more familiar with the bolometry part of Cherab.
Neither bolometer foils nor AXUV diodes actually provide spectrally-resolved measurements. I would avoid adding spectral pipelines to the Cherab bolometers and this will result in physically-unrealisable measurements.
I do however think that accounting for the spectral sensitivity of these observers is a worthwhile addition. But I would go about this by customising the filter property of the PowerPipeline0D or RadiancePipeline0D. See for example the Cornell Box demo in Raysect where custom filter responses are used to mimic separate red/green/blue filters: https://www.raysect.org/demonstrations/observers/cornell_box.html.
Indeed you can already do this with a BolometerFoil instance:
from cherab.tools.observers import BolometerFoil
from raysect.optical.pipeline import PowerPipeline0D
from raysect.optical import InterpolatedSF
foil = BolometerFoil(<options>)
spectral_response = InterpolatedSF(<wavelengths>, <absorption(wavelength)>)
foil.pipelines[0] = PowerPipeline0D(foil.accumulate, filter=spectral_response)
foil.observe()
This will result in the observer only measuring light which passes through the filter (i.e. is absorbed by the detector).
It may be worthwhile modifying BolometerFoil's __init__ method to allow specifying the custom filter function which will then add the correct pipeline for you, or adding a property (like units) for the spectral response, but the functionality does already exist.
Thank you for the response @jacklovell! I am somewhat familiar with that filter option, but I would like a spectral pipeline because as I mentioned these diodes can and do degrade over time. Their degradation can depend on their location, the radiation that affects them, etc. and the resulting degraded spectral response function cannot be measured on the whole spectral range (see in the AUG AXUV article by Matthias Bernert for example). Therefore if one wants to calculate total measured power with multiple different spectral responsivity functions one would need to run Cherab multiple times for the same plasma just with different filters each time. This I believe is extremely inefficient and that is why I would like to do this in post-processing. For that, I would need some spectral pipeline implemented. It doesn't have to be in bolometry.py, but I thought that those classes could serve as a nice basis.
Ah, I see. In that case I think you can just replace BolometerFoil.pipelines with a list which includes a SpectralPowerPipeline0D instead of PowerPipeline0D. The pipelines property is user-writable for all observers, and it's a common Raysect paradigm to change this from default in end user scripts. This would be fine for your specific application.
I think this sort of analysis would actually make quite a nice demo script, showing how to use the existing machinery to perform this calibration. But it doesn't necessarily need modifications to the core library.
Okay, thank you, I will try this then. Indeed, this would be much easier than to change core files I was just not aware of the possibility. So if I do something like this:
from raysect.optical.observer import SpectralPowerPipeline0D
diode = BolometerFoil(detector_id=detector_id, centre_point=ORIGIN.transform(diode_transform),
basis_x=XAXIS.transform(diode_transform), dx=SENSOR_X_SIZE,
basis_y=YAXIS.transform(diode_transform), dy=SENSOR_Y_SIZE,
slit=slit, parent=diode_camera, accumulate=False, curvature_radius=0)
diode.pipelines = [SpectralPowerPipeline()]
diode.spectral_rays = 1
diode.spectral_bins = 2500
diode.ray_extinction_prob = 0.01
diode.min_wavelength = 0.25
diode.max_wavelength = 1240
diode.pixel_samples = 3e4
Will the pixel_samples correctly overwrite the defaults?
Yes. Setting any of the parameters after creating the BolometerFoil object will override the default values. This is the encouraged way to configure observers in Cherab.
You may wish to use diode.pipelines = [SpectralPowerPipeline0D(accumulate=False)] to ensure each observation is independent. Otherwise if you call observe() twice, the results may add (I can't remember the default value of accumulate for the pipelines off the top of my head, but it's best to be explicit).
Thank you for your help, I will close this issue. I tested this approach and it works.