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)



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