Installing the client
The NoM client code is hosted on the ESA Space Environment and Effects Git repository:
https://space-env-repo.estec.esa.int/network-of-models/code/nom-client.git
Registration is required to access this software repository.
The NoM client can also be installed using the python package manager pip:
""" New install """
pip install git+HTTPS://USERNAME:PASSWORD@space-env-repo.estec.esa.int/network-of-models/code/nom-client.git
""" Update installation """
pip install --upgrade git+HTTPS://USERNAME:PASSWORD@space-env-repo.estec.esa.int/network-of-models/code/nom-client.git
Suggested installation using python virtual environment:
# Create a nom working directory and make it your current directory
mkdir nom
cd nom
# Create a python virtual environment
# python –m venv [PATH_TO_NOM_VENV]
# The PATH_TO_NOM_VENV can be anywhere accessible on your filesystem
python –m venv [PATH_TO_NOM_VENV]/nom-venv
# Activate the virtual environment
source [PATH_TO_NOM_VENV]/nom-venv/bin/activate
# Install the NoM Client
(nom-venv) pip install git+HTTPS://[USERNAME]@space-env-repo.estec.esa.int/network-of-models/code/nom-client.git
Common issues:
- If there is an error creating the python virtual environment check that you are running the correct version of python, (user python –version)
- Issues relating to errors importing nom libraries in python scripts are usually related to using the python binary from your system, not your nom virtual environment.
NoM Client setup
The NoM client is initiated with the following:
- project name (optional, defaults to 'default')
- the client configuration
- the default server to use
You can pass the client configuration in several ways:
- directly as a python dictionary
- passed directly as arguments
- location of a JSON configuration file
Passed as a python dictionary
from nom_client.nom_client import NoMClient
client_conf_dict = {
"servers": {
"ext_rest_server": {
"type": "rest",
"server_addr": "https://nom.esa.int",
"api_version": "api",
"api_key": "123456789"
}
}
}
nom_client = NoMClient(project_name="My project", client_configuration=client_conf_dict, default_server_id="ext_rest_server")
"""
Optionally you can set the default server to use, although if only 1 is passed then its set by default
"""
nom_client.set_default_server("ext_rest_server")
Passed as arguments
nom_client = NoMClient(project_name="My project",
server_id="ext_rest_server",
server_url="https://nom.esa.int",
server_api_key="123456789")
Passed as a file location
nom_client = NoMClient(project_name="My project", client_configuration="path to file")
"""
The file should look like:
{
"servers": {
"ext_rest_server": {
"type": "rest",
"server_addr": "https://nom.esa.int",
"api_version": "api",
"api_key": "123456789"
}
}
}
"""
"""
Optionally you can set the default server to use, although if only 1 is passed then its set by default
"""
nom_client.set_default_server("ext_rest_server")
Configuring models
Configuring models can be done in several ways depending upon the type of parameters being set. There are two main types of inputs a model can have:
Model inputs
Model inputs are inputs intrinsic to the model and have a specific role or meaning. Examples of such inputs are the shield thickness in a radiation transport model etc.
Model inputs are further split up into two kinds, singular and array-like.
Singular inputs (multiplicity: 'one')
These are the most common model inputs. They represent single-value, specific inputs such as 'kp' or 'shield thickness'.
Array-like inputs (multiplicity: 'many')
Array-like inputs are groups of one or more similar parameters. An example would be the devices specified in a single event effects models
External inputs
External inputs are inputs or values, typicaly produced by other models, that feed into the current model. Examples of these imputs include the trajectory for radiation environment models. More details on external inputs can be found below.
You can list the input group names (and types):
newupseto_model = nom_client.get_model("newupseto")
newupseto_model.get_input_group_meta()
>>> {'let': {'type': 'one'}, 'device': {'multiplicity': 'many', 'added_groups': 'None'}}
# or after adding setting group parameters:
newupseto_model.get_input_group_meta()
>>> {'let': {'type': 'one'}, 'device': {'multiplicity': 'many', 'added_groups': 'mydevice1, mydevice2'}}
You set singular type inputs using the 'set_params' method of the NoM client. This method takes input_name=value arguments. The input_name and valid values can be found on the specific model page.
ap8_model = nom_client.get_model("newupseto")
"""
Configuring singular inputs (multiplicity 'one')
"""
upset_model.set_params(shieldThickness=1,
shieldThicknessUnit=3,
numberDevices=len(devices),
includeSEPSpectrum=1,
includeTrappedSpectrum=0,
includeGCRSpectrum=1)
# or
input_params = {
"shieldThickness": 1,
"shieldThicknessUnit": 3,
"numberDevices": len(devices),
"includeSEPSpectrum"=1,
"includeTrappedSpectrum": 0,
"includeGCRSpectrum": 1
}
upset_model.set_params(params=input_params)
Array-like model input groups
You set array-like inputs using the 'new_input_group' method of the NoM client. This method takes the name of the input group and JSON-style, input_name:value pairs. The input_name and valid values can be found on the specific model page.
ap8_model = nom_client.get_model("newupseto")
"""
Configuring array-like inputs (multiplicity 'many')
"""
device_params = {
"name": "my_device",
"index": 1,
"directIonisationMethod": -1,
"weibull_w_direct": 53.5, "weibull_let_th_direct": 16.8, "weibull_s_direct": 1.45, "weibull_sigma_direct": 2.2e-7,
"protonInducedMethod": 5,
"weibull_w_proton": 0, "weibull_energy_th_proton": 0, "weibull_s_proton": 0, "weibull_sigma_proton": 0
}
upset_model.new_input_group(input_group_name="device", unique_group_name="mydevice1", params=device_params)
Each array-type input group added will have a unique, incremented index. You can use this index, along with the name of the nput group to set parameters within that arry-type input group.
"""
This new array-type input will have an index 0. To modify these parameters:
"""
new_device_params = {
"name": "my_modified_device",
"weibull_w_direct": 63.5,
}
upset_model.set_params(input_group_name="device", unique_group_name="mydevice1", params=new_device_params)
External model inputs
It is common that a model requires or depends upon some data that it cannot compute itself. An example is an effects model that requires a representative orbit to operate on.
It makes little sense for this effects model to compute the trajectory itself as there are dedicated models to do so. In addition, it may be useful to run several effects models on the same trajectory. CLearly its more efficient to generate the trajectory and then pass it as an external input to another model.
When a model requires inputs from external sources, they are defined as external inputs within the model specification / inputs section.
Currently, external inputs are passed as ModelRun or ModelResult objects. What a particular model expects
can be specified
in a number of ways depending upon how specific the input needs to be.
For example, specifying:
Input parameter | Comment |
---|---|
Quantity | The external input needs to be of a particular quantity (see taxonomy). Example: "trajectory", "flux_spectrum" |
Model result name | The external input needs to be a particular named result. Example: "electron_spectrum_over_trajectory" |
Model name | This is the most specific contraint. Example: "sapre", "ap-8" |
nom_client = NoMClient("supplying-external-inputs-example")
nom_client.set_default_server("ext_rest_server")
ap8_model = nom_client.get_model('ap-8')
"""
Get a list of external inputs for the AP-8 model
"""
ap8_model.get_external_inputs()
>>> {'trajectory': {'input_type': 'external',
'model_name': 'sapre',
'model_result_name': 'trajectory',
'quantity': 'trajectory'}}
"""
So we need to run the 'sapre' model and pass the trajectory result.
"""
sapre_model = nom_client.get_model('sapre')
sapre_result = nom_client.run_model(sapre_model)
"""
Pass the whole sapre run result object.
It will locate the model_result_name=trajectory and quantity=trajectory.
"""
ap8_model.set_external_input(external_input_name="trajectory", external_input=sapre_result)
"""
Pass only the trajectory result from the sapre run.
"""
sapre_trajectory = nom_client.get_model_run_results(model_run_name='trajectory',
model_result_name='trajectory')
ap8_model.set_external_input(external_input_name="trajectory", external_input=sapre_trajectory)
ap8_run_result = nom_client.run_model(ap8_model)
Note: If any of the external inputs are not satisfied, the NoM Client will search the persistance layer for any results with the attributes specified in the model specification with the same project name and tag.
If the external input is still not satisfied and error will be raised.
Running models
You can run a model using the default server and default tag:
model_run = nom_client.run_model(sapre_model)
Models can also be run models using tags as namespaces:
The results for each of these model runs will be in separate namespaces. This is useful when you want to control which models are used in different model pipelines.
geo_orbit = nom_client.run_model(sapre_model, tag="geo_orbit")
leo_orbit = nom_client.run_model(sapre_model, tag="leo_orbit")
geo_ap8_results = nom_client.run_model(ap8_model, tag="geo_orbit")
leo_ap8_results = nom_client.run_model(ap8_model, tag="leo_orbit")
Accessing the results/data with the NoMClient
sapphire_model = nom_client.get_model('sapphire-1-in-N-peak-flux')
sapphire_run = nom_client.run_model(sapphire_model)
When you run a model, it returns a dictionary of ModelRun objects. The dictionary keys are the names of the models that created the results.
It is useful to list the labels of the ModelResult objects within the ModelRun object 'sapphire_run'. This method retuns a simple array of strings. These strings correspond to the entries in the Model Outputs table.
model_result_names = nom_client.get_result_names(model_run_name='sapphire-1-in-N-peak-flux')
>>> ['sep_proton_spectrum', 'full-ion-spectrum']
To get the ModelResult object with label 'full-ion-spectrum' you can use the 'get_model_run_results' method. This method actually returns the ModelResult object itself. If you print this object it will show you a string representation of the meta data within the ModelResult object.
sapphire_model_result = nom_client.get_model_run_results(model_run_name='sapphire-1-in-N-peak-flux',
model_result_name='full-ion-spectrum')
ModelResult objects contain several (potentially N-dimentional) variables.
To get a simple string array of the variable names:
print(sapphire_model_result.get_variable_names())
>>> ['energy', 'species', 'fpio', 'fpdo']
To get the actual data for the variable 'fpio' within the ModelResult object, you can get it by name. This will return a simple array of data. According to the Model ouutputs table for this model, this variable has 2-dimensions or axes, 'energy' and 'species'.
To get the full multi-dimentional data:
data = results.get_result_data("fpio")
>>>[[...
...
...]]
To explore the 'fpio' variable closer we can access its meta-data:
fpio_variable = sapphire_model_result.get_variable_by_name("fpio")
print(fpio_variable.get_metadata())
>>>{'axes': {1: 'energy', 2: 'species'},
'description': '',
'dim': 1,
'name': 'fpio',
'qualifiers': ['omni-directional', 'integral'],
'quantity': 'numberflux',
'row_varying': True,
'units': 'cm^-2 s^-1',
'valid_max': None,
'valid_min': None
}
To get a particular axis of this data you can specify the axis name. This will return the 'fpio' data for just the species 'H', i.e. a vector of data.
data = results.get_result_data("fpio", species="H")
>>>[[...
...
...]]
To get a particular point within this data you can specify both axes. This will return a single data point.
data = results.get_result_data("fpio", energy=..., species="H")
>>>...
Model specifications
Model specifications represent the single authoratative source of information about a model.
They describe:
- Model meta data such as model developer etc
- Model inputs
- Model outputs
Model inputs
Key | Comment | Required |
---|---|---|
name | Name of the input | Always |
input_type | The type of input, external or input_group | Always |
quantity | The input quantity. | External inputs only |
model_name | The name(s) of the model(s) that would satisfy this external input. | External inputs only (optional) |
model_result_name | The name of the model result that would satisfy this external input. | External inputs only (optional) |
required | If there is no suitable default, then the input will be set to required. | Always |
model_mapping | Optional mapping from the name of the input to the input name expected by the underlying model. | Optional |
multiplicity | Is the model input a single value ir an array-like input. | For input_groups |
default | A suitable default if the input is not given. | Always |
default-value-if | The default value depends upon another input. | Optional |
label | Label description of the input. | Always |
choice | If the input is from a limited selection. Value/label pairs. | Optional |
constant | This value is constant and cannot be input by the user. | Optional |
units | Units of the input. | Optional |