Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Atmosphere models for h_max to X_max conversion #2000

Merged
merged 54 commits into from
Sep 28, 2022

Conversation

kosack
Copy link
Contributor

@kosack kosack commented Jul 8, 2022

Features

Adds an AtmosphereDensityProfile class hierarchy with three implementations:

  • ExponentialAtmosphereDensityProfile (purely mathematical)
  • TableAtmsophereDensityProfile (cubic interpolation of a tabulated model)
  • FiveLayerAtmosphereDensityProfile (implements the Corsika 5-layer representation where each layer is either an exponential or linear model)

This adds a new atmosphere_density_profile attribute to EventSource, with an implementation in SimTelEventSource (reading them from simtel files) and HDF5EventSource, as well as in DataWriter (propagating them to output files).

By default SimTelEventSource will return a TableAtmosphereDensityProfile if it is found in the data, otherwise it falls back to a FiveLayerAtmosphereDensityProfile, or None if no profile exists. This is user-selectable with the SimTelEventSource.atmosphere_model_choice option (AUTO, TABLE, FIVELAYER)

Example:

from ctapipe.atmosphere import ExponentialAtmosphereDensityProfile, TableAtmosphereDensityProfile

# basic profile, using default parameters
print(density)
print("Density:       ", density(10*u.km))
print("Column Density (0d):", density.line_of_sight_integral(distance=10*u.km, zenith_angle=0*u.deg))
print("Column Density (45d):", density.line_of_sight_integral(distance=10*u.km, zenith_angle=45*u.deg))
ExponentialAtmosphereDensityProfile(h0=<Quantity 8. km>, rho0=<Quantity 0.00125 g / cm3>)
Density:        0.0003581309960752376 g / cm3
Column Density (0d): 286.50479686019 g / cm2
Column Density (45d): 584.3180219705157 g / cm2

Loading one from a simtel or ctapipe file can be done with an EventSource:

# Table profile loaded from a SimTel file
with EventSource("gammas.simtel.zst", atmosphere_profile_choice="TABLE") as source:
     density_tab = source.atmosphere_density_profile
print(density_tab)
TableAtmosphereDensityProfile(meta={'obs_level': <Quantity 215800. cm>, 'atmo_id': 99, 
    'atmo_name': b'atmprof_ecmwf_north_winter_fixed.dat', 'htoa': 12000000.0}, rows=55)

All profiles can also be plotted:

density.peek()
density_tab.peek()

image

@review-notebook-app
Copy link

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

ctapipe/atmosphere/model.py Outdated Show resolved Hide resolved
@codecov
Copy link

codecov bot commented Jul 8, 2022

Codecov Report

Base: 92.50% // Head: 92.51% // Increases project coverage by +0.01% 🎉

Coverage data is based on head (a9c7922) compared to base (370b6b9).
Patch coverage: 93.28% of modified lines in pull request are covered.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #2000      +/-   ##
==========================================
+ Coverage   92.50%   92.51%   +0.01%     
==========================================
  Files         197      199       +2     
  Lines       16311    16561     +250     
==========================================
+ Hits        15089    15322     +233     
- Misses       1222     1239      +17     
Impacted Files Coverage Δ
ctapipe/io/eventsource.py 90.24% <75.00%> (-0.52%) ⬇️
ctapipe/io/datawriter.py 90.03% <87.50%> (-0.07%) ⬇️
ctapipe/io/simteleventsource.py 96.06% <90.47%> (-0.77%) ⬇️
ctapipe/atmosphere.py 91.40% <91.40%> (ø)
ctapipe/instrument/atmosphere.py 92.00% <100.00%> (+1.09%) ⬆️
ctapipe/io/hdf5eventsource.py 90.87% <100.00%> (+0.33%) ⬆️
ctapipe/io/tests/test_simteleventsource.py 100.00% <100.00%> (ø)
ctapipe/tests/test_atmosphere.py 100.00% <100.00%> (ø)
ctapipe/instrument/camera/geometry.py 89.91% <0.00%> (-0.57%) ⬇️
ctapipe/utils/datasets.py 89.71% <0.00%> (+1.86%) ⬆️

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report at Codecov.
📢 Do you have feedback about the report comment? Let us know in this issue.

try_obsconfig_model.ipynb Outdated Show resolved Hide resolved
@kosack kosack force-pushed the feature/atmosphere branch from 232e8be to 14ad684 Compare July 8, 2022 16:30
@kbernloehr
Copy link

kbernloehr commented Jul 11, 2022 via email

@kosack
Copy link
Contributor Author

kosack commented Jul 12, 2022

Thanks. As it's not intended for simulation, the use case for this package probably doesn't require extreme precision in the atmosphere profile or it's interpolation as other systematics dominate, but in any case I'll switch to a log interpolation and use cubic splines. I'll also add a disclaimer in the documentation!

@kbernloehr
Copy link

kbernloehr commented Jul 12, 2022 via email

@kosack kosack force-pushed the feature/atmosphere branch from b066b92 to 3cb4d29 Compare July 13, 2022 09:19
@maxnoe
Copy link
Member

maxnoe commented Jul 13, 2022

means (switch to 5-layer description).

Do you want to add a class for that description as well, @kosack ? As a fallback if the tabulated version is not available?

@kosack kosack added physics science-performance related issues new functionality labels Jul 13, 2022
@kosack
Copy link
Contributor Author

kosack commented Jul 13, 2022

Interpolation in the wiggly areas looks like this now (with cubic splines used):

image

@kosack
Copy link
Contributor Author

kosack commented Jul 13, 2022

means (switch to 5-layer description).

Do you want to add a class for that description as well, @kosack ? As a fallback if the tabulated version is not available?

Is that documented somewhere and do we have a file that has that format atmosphere? If so, I can add it, or we could always add a subclass in a later version

@kbernloehr
Copy link

kbernloehr commented Jul 13, 2022 via email

@maxnoe
Copy link
Member

maxnoe commented Jul 13, 2022

Is that documented somewhere and do we have a file that has that format atmosphere?

All the files should have it, the data array is next to the tabulated values in five_layer_atmosphere:

In [2]: s = SimTelFile('./gamma_20deg_0deg_run5___cta-prod5-paranal_desert-2147m-Paranal-dark_cone10-1000e
   ...: vts.simtel.zst')

In [3]: s.atmospheric_profiles
Out[3]: 
[{'id': 99,
  'name': b'atmprof_ecmwf_south_winter_fixed.dat',
  'obslevel': 214700.0,
  'altitude_km': array([  0.,   1.,   2.,   3.,   4.,   5.,   6.,   7.,   8.,   9.,  10.,
          11.,  12.,  13.,  14.,  15.,  16.,  17.,  18.,  19.,  20.,  21.,
          22.,  23.,  24.,  25.,  26.,  27.,  28.,  29.,  30.,  32.,  34.,
          36.,  38.,  40.,  42.,  44.,  46.,  48.,  50.,  55.,  60.,  65.,
          70.,  75.,  80.,  85.,  90.,  95., 100., 105., 110., 115., 120.]),
  'rho': array([1.18699e-03, 1.07756e-03, 9.70186e-04, 8.77072e-04, 7.93791e-04,
         7.18080e-04, 6.48844e-04, 5.85278e-04, 5.26715e-04, 4.72504e-04,
         4.22121e-04, 3.75369e-04, 3.31981e-04, 2.91583e-04, 2.53862e-04,
         2.19443e-04, 1.88802e-04, 1.60996e-04, 1.35710e-04, 1.13534e-04,
         9.47734e-05, 7.93632e-05, 6.68271e-05, 5.65359e-05, 4.79687e-05,
         4.07332e-05, 3.46217e-05, 2.94582e-05, 2.50907e-05, 2.14037e-05,
         1.82914e-05, 1.34008e-05, 9.84255e-06, 7.27288e-06, 5.40049e-06,
         4.03328e-06, 3.02738e-06, 2.29511e-06, 1.75935e-06, 1.34678e-06,
         8.51962e-07, 4.66369e-07, 2.52771e-07, 1.32269e-07, 6.57119e-08,
         3.09563e-08, 1.40756e-08, 6.31771e-09, 2.83254e-09, 1.24950e-09,
         5.18266e-10, 2.04233e-10, 8.16552e-11, 3.47007e-11, 2.10471e-11]),
  'thickness': array([1.03754e+03, 9.24333e+02, 8.22018e+02, 7.29769e+02, 6.46292e+02,
         5.70757e+02, 5.02461e+02, 4.40799e+02, 3.85239e+02, 3.35311e+02,
         2.90611e+02, 2.50766e+02, 2.15425e+02, 1.84269e+02, 1.57021e+02,
         1.33387e+02, 1.13003e+02, 9.55337e+01, 8.07217e+01, 6.82873e+01,
         5.79008e+01, 4.92203e+01, 4.19320e+01, 3.57802e+01, 3.05675e+01,
         2.61425e+01, 2.23833e+01, 1.91865e+01, 1.64652e+01, 1.41457e+01,
         1.21653e+01, 9.02211e+00, 6.71687e+00, 5.01908e+00, 3.76159e+00,
         2.82520e+00, 2.12448e+00, 1.59575e+00, 1.19405e+00, 8.81689e-01,
         6.64492e-01, 3.60590e-01, 1.83457e-01, 9.04211e-02, 4.25828e-02,
         1.94123e-02, 8.68112e-03, 3.83736e-03, 1.66475e-03, 6.94448e-04,
         2.76618e-04, 1.07666e-04, 4.10884e-05, 1.36535e-05, 0.00000e+00]),
  'refractive_index_minus_1': array([2.74714e-04, 2.49149e-04, 2.24083e-04, 2.02469e-04, 1.83215e-04,
         1.65729e-04, 1.49739e-04, 1.35062e-04, 1.21542e-04, 1.09030e-04,
         9.74023e-05, 8.66137e-05, 7.66020e-05, 6.72812e-05, 5.85785e-05,
         5.06375e-05, 4.35681e-05, 3.71527e-05, 3.13184e-05, 2.62017e-05,
         2.18726e-05, 1.83165e-05, 1.54235e-05, 1.30485e-05, 1.10713e-05,
         9.40138e-06, 7.99087e-06, 6.79914e-06, 5.79113e-06, 4.94015e-06,
         4.22183e-06, 3.09304e-06, 2.27177e-06, 1.67867e-06, 1.24650e-06,
         9.30931e-07, 6.98757e-07, 5.29740e-07, 4.06083e-07, 3.10853e-07,
         1.96643e-07, 1.07644e-07, 5.83428e-08, 3.05295e-08, 1.51671e-08,
         7.14511e-09, 3.24882e-09, 1.45821e-09, 6.53786e-10, 2.88402e-10,
         1.19622e-10, 4.71396e-11, 1.88470e-11, 8.00936e-12, 4.85794e-12]),
  'five_layer_atmosphere': array([[ 0.00000000e+00, -1.40507760e+02,  1.17804776e+03,
           9.94186380e+05,  1.00584762e-06],
         [ 9.75000000e+05, -1.84376971e+01,  1.26508431e+03,
           7.08915051e+05,  1.41060625e-06],
         [ 1.90000000e+06,  2.17564847e-01,  1.34922059e+03,
           6.36143040e+05,  1.57197350e-06],
         [ 4.60000000e+06, -2.01796296e-04,  7.03744586e+02,
           7.21127964e+05,  1.38671644e-06],
         [ 1.06000000e+07,  7.63128384e-04,  1.00000000e+00,
           1.57247460e+10,  6.35940320e-11]]),
  'htoa': 12000000.0}]

@kosack
Copy link
Contributor Author

kosack commented Jul 18, 2022

I added a FiveLayer profile. Looks reasonable. The default is to use the table one, but if no table exists, the eventsource looks for the five-layer one and returns that.

image

@kosack
Copy link
Contributor Author

kosack commented Jul 19, 2022

The last item to implement here is the i/o. Since there could in principle be more than one version of the atmosphere profile in the file (one from the simulation, and one from real measurements for observation data), I think the ones loaded by the SimTelEventSource will go in /simulation/service/atmosphere_profile, which then allows one to add a /dl2/service/atmosphere_profile later for the frequently updated/measured one.

@maxnoe
Copy link
Member

maxnoe commented Jul 19, 2022

Since there could in principle be more than one version of the atmosphere profile in the file (one from the simulation, and one from real measurements for observation data), I think the ones loaded by the SimTelEventSource will go in /simulation/service/atmosphere_profile, which then allows one to add a /dl2/service/atmosphere_profile later for the frequently updated/measured one.

I don't understand why this split is necessary. Simulated files only have the simulated atmosphere, observed files only have a measured one. Why can we not use the same location? And wouldn't the atmosphere profile be monitoring not service information?

@maxnoe
Copy link
Member

maxnoe commented Jul 19, 2022

Looks reasonable.

There seems to be a discontinuity in the first plot, reading the CORSIKA manual, this should not happen?

@kbernloehr
Copy link

Hi Max,

A jump in density looks ugly but could even kind of happen in a
real atmosphere, for example an inversion layer. Related to
a jump in temperature.
What should never have a jump is the atmospheric depth;
that one must strictly monotonically fall with increasing height.
Pressure, if we would care about that, would be similar.

The 5-layer thickness should never be far from the tabulated
thickness, in g/cm^2 terms, although the relative difference
can be large in the upper regions of the atmosphere, which
spans a too large region to be reasonably covered by the fourth
of the five layers (the fifth only settling the thickness down to zero
at the top).

Since the jump looked too big for my gut feeling, I look into
the original values.
The CORSIKA log file (run*.log) contains a table with both sets of
values side by side to see where the tension is biggest.
I usually have a look at that whenever I use a profile the first time.
I looked into a file where this particular atmospheric profile
was used and could not see that much difference between
table and fit, some five percent perhaps in this region:

Atmospheric profile 99 to be read from file 'atmprof_ecmwf_south_winter_fixed.dat'.

Atmospheric profile 99 with 55 levels read from file atmprof_ecmwf_south_winter_fixed.dat

Initialize atmosphere ranging from 0.000 to 120.000 km a.s.l.

Results of the atmosphere fit:
Layer 1: 0.00 km < h < 9.75 km: a = -140.508, b = 1178.05, c = 994186
Layer 2: 9.75 km < h < 19.00 km: a = -18.4377, b = 1265.08, c = 708915
Layer 3: 19.00 km < h < 46.00 km: a = 0.217565, b = 1349.22, c = 636143
Layer 4: 46.00 km < h < 106.00 km: a = -0.000201796, b = 703.745, c = 721128
Layer 5: 106.00 km < h < 120.00 km: a = 0.000763128, b = 1, c = 1.57247e+10
Sum of residuals squared: 0.173609 (compared to 1.26597 with starting layers)

Altitude [km] rho(table) rho(fit) thick(table) thick(fit)
0.0 1.18699e-03 1.18494e-03 1.03754e+03 1.03754e+03
1.0 1.07756e-03 1.07155e-03 9.24333e+02 9.24811e+02
2.0 9.70186e-04 9.69010e-04 8.22018e+02 8.22869e+02
3.0 8.77072e-04 8.76284e-04 7.29769e+02 7.30682e+02
4.0 7.93791e-04 7.92431e-04 6.46292e+02 6.47316e+02
5.0 7.18080e-04 7.16602e-04 5.70757e+02 5.71928e+02
6.0 6.48844e-04 6.48029e-04 5.02461e+02 5.03754e+02
7.0 5.85278e-04 5.86018e-04 4.40799e+02 4.42104e+02
8.0 5.26715e-04 5.29941e-04 3.85239e+02 3.86353e+02
9.0 4.72504e-04 4.79230e-04 3.35311e+02 3.35937e+02
10.0 4.22121e-04 4.35418e-04 2.90611e+02 2.90237e+02
11.0 3.75369e-04 3.78133e-04 2.50766e+02 2.49627e+02
12.0 3.31981e-04 3.28385e-04 2.15425e+02 2.14359e+02
13.0 2.91583e-04 2.85181e-04 1.84269e+02 1.83732e+02
14.0 2.53862e-04 2.47662e-04 1.57021e+02 1.57134e+02
15.0 2.19443e-04 2.15079e-04 1.33387e+02 1.34035e+02
16.0 1.88802e-04 1.86782e-04 1.13003e+02 1.13975e+02
17.0 1.60996e-04 1.62209e-04 9.55337e+01 9.65544e+01
18.0 1.35710e-04 1.40868e-04 8.07217e+01 8.14257e+01
19.0 1.13534e-04 1.07004e-04 6.82873e+01 6.82873e+01
20.0 9.47734e-05 9.14386e-05 5.79008e+01 5.83856e+01
21.0 7.93632e-05 7.81375e-05 4.92203e+01 4.99242e+01
22.0 6.68271e-05 6.67712e-05 4.19320e+01 4.26936e+01
23.0 5.65359e-05 5.70584e-05 3.57802e+01 3.65149e+01
24.0 4.79687e-05 4.87584e-05 3.05675e+01 3.12349e+01
25.0 4.07332e-05 4.16658e-05 2.61425e+01 2.67230e+01
26.0 3.46217e-05 3.56049e-05 2.23833e+01 2.28674e+01
27.0 2.94582e-05 3.04256e-05 1.91865e+01 1.95726e+01
28.0 2.50907e-05 2.59998e-05 1.64652e+01 1.67571e+01
29.0 2.14037e-05 2.22177e-05 1.41457e+01 1.43512e+01
30.0 1.82914e-05 1.89858e-05 1.21653e+01 1.22953e+01
32.0 1.34008e-05 1.38640e-05 9.02211e+00 9.03708e+00
34.0 9.84255e-06 1.01240e-05 6.71687e+00 6.65785e+00
36.0 7.27288e-06 7.39282e-06 5.01908e+00 4.92046e+00
38.0 5.40049e-06 5.39847e-06 3.76159e+00 3.65176e+00
...

The border boundaries are identical to what your earlier post showed,
with the second layer going from 9.75 to 19.00 km but the jump is
at about 25 km. Seems to me that the plot did not respect the layer ranges
given in the data.

@kosack
Copy link
Contributor Author

kosack commented Jul 20, 2022

The border boundaries are identical to what your earlier post showed,
with the second layer going from 9.75 to 19.00 km but the jump is
at about 25 km. Seems to me that the plot did not respect the layer ranges
given in the data.

I only had the bare array from the simtel file, so I just guessed at what it meant (following the Corsika manual), however I think it looks ok, but I could have a index-1 bug somewhere. The thickness profile looks fine (very little difference from a table profile), and the jumps in the density are just because I simply used the same parameters for thickness but took their derivative to get density, so those are just cases where the derivative wasn't continuous between bins I guess (or perhaps a bug - i'll check).

I interpreted the data as a table as follows:
image
(where ? is actually 1/c)

@kosack
Copy link
Contributor Author

kosack commented Jul 20, 2022

The comparison looks like this:
image
image

@kosack
Copy link
Contributor Author

kosack commented Jul 20, 2022

I don't understand why this split is necessary. Simulated files only have the simulated atmosphere, observed files only have a measured one. Why can we not use the same location? And wouldn't the atmosphere profile be monitoring not service information?

It's just a bookkeeping thing... one might want to eventually have both the simulated and measured profiles in the same file, for comparison reasons, so having them in different places of the data model could help that. But for now, I'll just concentrate on the simulated version, so I'll add it to /simulation/service. It is Service data as it does not change during an observation (not a time-series).

@kosack
Copy link
Contributor Author

kosack commented Jul 20, 2022

It looks like my implementation is identical to @kbernloehr :

fit_konrad = np.array(
    [
        [0.00*100000, -140.508, 1178.05, 994186,0],
        [9.75*100000, -18.4377, 1265.08, 708915,0],
        [19.00*100000, 0.217565, 1349.22, 636143,0],
        [46.00*100000, -0.000201796, 703.745, 721128,0],
        [106.00*100000, 0.000763128, 1, 1.57247e10,0],
    ]
)

k5 = FiveLayerAtmosphereDensityProfile.from_array(fit_konrad)

image

image

So I thikn the implementation is ok.

[Edit: I had incorrect binning in h in my previous version of this plot]

@kosack kosack requested a review from maxnoe September 5, 2022 12:15
@kosack kosack added this to the v0.17.0 milestone Sep 23, 2022
ctapipe/atmosphere.py Outdated Show resolved Hide resolved
ctapipe/atmosphere.py Outdated Show resolved Hide resolved
Copy link
Member

@maxnoe maxnoe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's important to also have a table versioning here.

ctapipe/atmosphere.py Show resolved Hide resolved
@maxnoe maxnoe modified the milestones: v0.17.0, v0.18.0 Sep 23, 2022
ctapipe/atmosphere.py Outdated Show resolved Hide resolved
ctapipe/atmosphere.py Outdated Show resolved Hide resolved
and change supported table versions to a set
ctapipe/io/datawriter.py Outdated Show resolved Hide resolved
maxnoe
maxnoe previously approved these changes Sep 26, 2022
Copy link
Member

@maxnoe maxnoe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, didn't see that the FiveLayerAtmosphere also has .table.

ctapipe/io/simteleventsource.py Outdated Show resolved Hide resolved
@@ -1,3 +1,5 @@
""" tests of SimTelEventSource """
# pylint: disable=import-outside-toplevel
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For another PR: can we disable import-outside-toplevel for all test files?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't found a way to filter by filename, but it would be nice! We could just disable it globally since it's used sparingly and correctly for the most part.

ctapipe/io/tests/test_simteleventsource.py Outdated Show resolved Hide resolved
@maxnoe maxnoe merged commit 89add5c into cta-observatory:master Sep 28, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
new functionality physics science-performance related issues
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants