Sensitivity analysis
Goal: run sensitivity analysis to show the impact of a given parameter on the SMRT output
Learning:
Intuitively running many simulations can be done with a loop and many calls to the SMRT functions. However, this is not the recommended way. SMRT is able to iterate on several parameters of the sensor or the snowpack, and return a unique result with new coordinates. This is more convenient to work with, and enables parallel computations.
A sensor with several frequencies, angles or polarizations is automatically understood by SMRT and the
Result object contains all simulation results as array (i.e. internally as xarray).
The result methods (e.g. TbV()) can return all the values with xarray.Dataset result.TbV() and
xarray.Dataset result.TbH() or can be filtered by frequency, angle or polarization. For instance, to get the
brightness temperature at vertical polarisation for 37 GHz, simply call result.TbV(frequency=37e9)`.
The same applies when a list of snowpacks is given to the run method. The result contains all simulation results
as an xarray with a dimension called snowpack, or a customn name if provided.
Using pandas provides an extra leval of convenience for sensitivity analysis. A pandas DataFrame with a snowpack column
can be given to run and the result is a dataframe with the same column plus the simulation results. This is the most
advanced and powerful way to conduct sensitivity analysis.
Sensitivity with a list of snowpacks
First import the necessary libraries and prepare the sensor and model configuration:
import time
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from smrt import make_model, make_snowpack, sensor_list
The key idea is to build a list of snowpacks. Let’s test the sensitivity of TB to the radius by first building a list of snowpack with different radius.
# prepare the snowpack
density = 300.0
radius = np.arange(0.05, 0.5, 0.01) * 1e-3 # from 0.05 mm to 0.5 mm
# the NAIVE APPROACH:
snowpack = list()
for x in radius:
sp = make_snowpack([1000.0], "sticky_hard_spheres",
density=density, temperature=265,
radius=x, stickiness=0.15)
snowpack.append(sp)
In simple cases, it is easier to use “list comprehension”, a nice python feature to create list.
# a BETTER APPROACH with list comprehension
snowpack = [make_snowpack([1000.0], "sticky_hard_spheres",
density=density, temperature=265,
radius=x, stickiness=0.15) for x in radius]
# prepare the sensor and model
model = make_model("iba", "dort")
sensor = sensor_list.passive(37e9, 55)
#run!
With this list of snowpacks, you can call the model for each snowpack. Here, results is a list of Results objects and a loop is again necessary to extract the TB for each snowpack and plot it. This works but this approach is not recommended.
# a NAIVE APPROACH
# call many times 'run' and get a list of results
results = [model.run(sensor, sp) for sp in snowpack]
tbv = [res.TbV() for res in results]
plt.figure()
plt.plot(radius, tbv)
Instead, the run function can directly take a list of snowpacks and returns a unique result with a new coordinate named snowpack.
# a BETTER APPROACH
# snowpack is a list of Snowpack objects
results = model.run(sensor, snowpack)
# results is a Result object not a list
plt.figure()
plt.plot(radius, results.TbV())
It is possible to give a custom name and values to the new dimension with snowpack_dimension argument.
results = model.run(sensor, snowpack, snowpack_dimension=('radius', radius))
plt.figure()
plt.plot(results.radius, results.TbV())
The simulations are run in parallel by default, so the computation time is much shorter than the naive approach. It is possible to disable parallel computation by setting parallel_computation=False. It is sometimes easier when debugging, the error messages are clearer without parallel computation.
results = model.run(sensor, snowpack, snowpack_dimension=('radius', radius), parallel_computation=False)
It is also possible to save the simulation results to disk:
results.save("radius-sensitivity.nc")
And later read the results, and get a Result object as if the simulations were just run:
from smrt import open_result
results = open_result("radius-sensitivity.nc")
Sensitivity with pandas.DataFrame
Instead of providing a list of snowpack and the dimension name and values, a more concise approach is using pandas.DataFrame:
# create and load a DataFrame with snowpack information:
sp = pd.DataFrame({'radius' : np.arange(0.05, 0.5, 0.01) * 1e-3})
# add a column named "snowpack" in the DataFrame:
sp['snowpack'] = [make_snowpack([1000.0], "sticky_hard_spheres",
density=density, temperature=265,
radius=row['radius'], stickiness=0.15) for i, row in sp.iterrows()]
# use model.run with the DataFrame as input for the snowpack.
results = model.run(sensor, sp)
results
The key step is to add a column named “snowpack” in the DataFrame that contains the Snowpack objects. While pandas.DataFrame is mainly used with numerical values, it is possible to add any kind of object into the columns. SMRT will automatically detect the snowpack column and use it as if it was a list of snowpack.
This approach is particularly useful when using pandas to read a database of sites, and build the snowpacks directly from it.
# you can even convert the results object to a dataframe
results = model.run(sensor, sp).to_dataframe()
# that's it
results
The to_dataframe() method converts the Result` object into a dataframe.
For this, it is recommended to use a named sensor (e.g. amsre, smos, …) defined in smrt.sensor.list. The sensors define a channel_map that allows elegant conversion into DataFrame. In this case the columns of the DataFrame are the channels of the sensor, as expected. This is the most convenient way to run multiple simulations and use the results for plotting or computing statistics.
Recap:
The two recommended ways to run sensitivity analysis are:
# with List
snowpack = [make_snowpack([1000.0], "sticky_hard_spheres",
density=density, temperature=265, radius=x, stickiness=0.15) for x in radius]
model = make_model("iba", "dort")
sensor = sensor_list.amsre(['19', '37'])
results = model.run(sensor, snowpack, snowpack_dimension=('radius', radius))
plt.figure()
plt.plot(results.radius, results.TbV(frequency=19e9), label="19 GHz")
plt.plot(results.radius, results.TbV(frequency=37e9), label="37 GHz")
plt.legend()
# with DataFrame
sp = pd.DataFrame({'radius' : np.arange(0.05, 0.5, 0.01) * 1e-3})
sp['snowpack'] = [make_snowpack([1000.0], "sticky_hard_spheres",
density=density, temperature=265, radius=row['radius'],
stickiness=0.15) for i, row in sp.iterrows()]
model = make_model("iba", "dort")
sensor = sensor_list.amsre(['19', '37'])
results = model.run(sensor, sp, parallel_computation=True).to_dataframe()
plt.figure()
plt.plot(results['radius'], results['19V'], label="19 GHz")
plt.plot(results['radius'], results['37V'], label="37 GHz")
plt.legend()