Note

Last update 04/05/2021

Interfacing SuperflexPy with other frameworks

SuperflexPy does not integrate tools for calibration or uncertainty analysis. In this page we show an example on how a model built using SuperflexPy can be interfaced with other tools to perform this task.

SuperflexPy + SPOTPY

Note

This example is for illustration purposes only, and as such does not represent a specific recommendation of SPOTPY or of any specific calibration algorithm.

SPOTPY is a Python framework for calibration, uncertainty, and sensitivity analysis.

A model can be interfaced with SPOTPY by defining a class that wraps the model and implements the following methods:

  • __init__: initializes the class, defining some attributes;
  • parameters: returns the parameters considered in the analysis (note that they may not be all the parameters used by the SuperflexPy model but only the ones that we want to vary in the analysis);
  • simulation: returns the output of the simulation;
  • evaluation: returns the observed output;
  • objectivefunction: defines the objective function to use to evaluate the simulation results.

Method __init__

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import spotpy

class spotpy_model(object):

    def __init__(self, model, inputs, dt, observations, parameters, parameter_names, output_index):

        self._model = model
        self._model.set_input(inputs)
        self._model.set_timestep(dt)

        self._parameters = parameters
        self._parameter_names = parameter_names
        self._observarions = observations
        self._output_index = output_index

The class spotpy_model is initialized defining the SuperflexPy model that is used. The model, which can be any SuperflexPy component (from element to network), must be defined before; the spotpy_model class sets only the inputs and the dt.

Other variables necessary to initialize the class spotpy_model are:

  • parameters and parameters_names, which define the parameters considered in the calibration. The first variable is a list of spotpy.parameter objects, the second variable is a list of the names of the SuperflexPy parameters;
  • observations, which is an array of observed output values;
  • output_index, which is the index of the output flux to be considered when evaluating the SuperflexPy simulation. This specification is necessary in the case of multiple output fluxes.

Method parameters

1
2
    def parameters(self):
        return spotpy.parameter.generate(self._parameters)

The method parameters generates a new parameter set using the SPOTPY functionalities.

Method simulation

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    def simulation(self, parameters):

        named_parameters = {}
        for p_name, p in zip(self._parameter_names, parameters):
            named_parameters[p_name] = p

        self._model.set_parameters(named_parameters)
        self._model.reset_states()
        output = self._model.get_output()

        return output[self._output_index]

The method simulation sets the parameters (lines 3-7), resets the states to their initial value (line 8), runs the SuperflexPy model (line 9), and returns the output flux for the evaluation of the objective function (line 11).

Method evaluation

1
2
    def evaluation(self):
        return self._observarions

The method evaluation returns the observed flux, used for the evaluation of the objective function.

Method objectivefunction

1
2
3
4
5
6
    def objectivefunction(self, simulation, evaluation):

        obj_fun = spotpy.objectivefunctions.nashsutcliffe(evaluation=evaluation,
                                                          simulation=simulation)

        return obj_fun

The method objectivefunction defines the objective function used to measure the model fit to the observed data. In this case, the Nash-Sutcliffe efficiency is used.

Example of use

We now show how to employ the implementation above to calibrate a lumped model composed of 2 reservoirs.

First, we initialize the SuperflexPy model, as follows (see How to build a model with SuperflexPy for more details on how to set-up a model).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
from superflexpy.implementation.numerical_approximators.implicit_euler import ImplicitEulerPython
from superflexpy.implementation.root_finders.pegasus import PegasusPython
from superflexpy.implementation.elements.hbv import PowerReservoir
from superflexpy.framework.unit import Unit

root_finder = PegasusPython()
num_app = ImplicitEulerPython(root_finder=root_finder)

reservoir_1 = PowerReservoir(parameters={'k': 0.1, 'alpha': 2.0},
                            states={'S0': 10.0},
                            approximation=num_app,
                            id='FR1')
reservoir_2 = PowerReservoir(parameters={'k': 0.5, 'alpha': 1.0},
                            states={'S0': 10.0},
                            approximation=num_app,
                            id='FR2')

hyd_mod = Unit(layers=[[reservoir_1],
                       [reservoir_2]],
               id='model')

Then, we initialize an instance of the spotpy_model class

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
spotpy_hyd_mod = spotpy_model(
    model=hyd_mod,
    inputs=[P],
    dt=1.0,
    observations=Q_obs,
    parameters=[
        spotpy.parameter.Uniform('model_FR1_k', 1e-4, 1e-1),
        spotpy.parameter.Uniform('model_FR2_k', 1e-3, 1.0),
    ],
    parameter_names=['model_FR1_k', 'model_FR2_k'],
    output_index=0
)

The arrays P and Q_obs in lines 3 and 5 contain time series of precipitation (input) and observed streamflow (output). In this example, lines 6-10 indicate the two parameters that we calibrate (model_FR1_k and model_FR2_k) together with their range of variability.

We can now call the SPOTPY method to calibrate the model. Here, the SCE algorithm option is used.

1
2
sampler = spotpy.algorithms.sceua(spotpy_hyd_mod, dbname='calibration', dbformat='csv')
sampler.sample(repetitions=5000)