servertools¶
This module facilitates using HydPy as an HTTP server application.
HydPy is designed to be used interactively or by executing individual Python scripts.
Consider the typical steps of calibrating model parameters. Usually, one first
prepares an instance of class HydPy
, then changes some parameter values and performs
a simulation, and finally inspects whether the new simulation results are better than
the ones of the original parameterisation or not. One can perform these steps
manually (in a Python console) or apply optimisation tools like those provided by
scipy
(usually in a Python script).
Performing or implementing such procedures is relatively simple, as long as all tools
are written in Python or come with a Python interface, which is not the case for some
relevant optimisation tools. One example is OpenDA, being written in Java, which
was the original reason for adding module servertools
to the HydPy framework.
Module servertools
solves such integration problems by running HydPy within an
HTTP server. After starting such a server, one can use any HTTP client (e.g. curl)
to perform the above steps.
The server’s API is relatively simple, allowing performing a “normal” calibration using only a few server methods. However, it is also more restrictive than controlling HydPy within a Python process. Within a Python process, you are free to do anything. Using the HydPy server, you are much more restricted to what was anticipated by the framework developers.
Commonly but not mandatory, one configures the initial state of a HydPy server with
an XML file. As an example, we prepare the HydPy-H-Lahn project by calling function
prepare_full_example_1()
, which contains the XML configuration file
multiple_runs_alpha.xml:
>>> from hydpy.core.testtools import prepare_full_example_1
>>> prepare_full_example_1()
To start the server in a new process, open a command-line tool and insert the following
command (see module hyd
for general information on how to use HydPy via the command
line):
>>> command = "hyd.py start_server 8080 HydPy-H-Lahn multiple_runs_alpha.xml"
>>> from hydpy import run_subprocess, TestIO
>>> with TestIO():
... process = run_subprocess(command, blocking=False, verbose=False)
... result = run_subprocess("hyd.py await_server 8080 10", verbose=False)
The HydPy server should now be running on port 8080. You can use any HTTP client to check it is working. For example, you can type the following URLs in your web browser to get information on the types and initial values of the exchange items defined in multiple_runs_alpha.xml (in a format required by the HydPy-OpenDA-Black-Box-Model-Wrapper):
>>> from urllib import request
>>> url = "http://127.0.0.1:8080/query_itemtypes"
>>> print(str(request.urlopen(url).read(), encoding="utf-8"))
alpha = Double0D
dill_assl_nodes_sim_series = TimeSeries0D
>>> url = "http://127.0.0.1:8080/query_initialitemvalues"
>>> print(str(request.urlopen(url).read(), encoding="utf-8"))
alpha = 2.0
dill_assl_nodes_sim_series = [nan, nan, nan, nan, nan]
It is generally possible to control the HydPy server via invoking each method with a
separate HTTP request. However, alternatively, one can use methods
GET_execute()
and POST_execute()
to execute many methods with
only one HTTP request. We now define three such metafunctions. The first one changes
the value of the parameter Alpha
The second one runs a simulation.
The third one prints the newly calculated discharge at the outlet of the headwater
catchment Dill. All of this is very similar to what the
HydPy-OpenDA-Black-Box-Model-Wrapper does.
Function set_itemvalues wraps the POST methods
POST_register_simulationdates()
,
POST_register_parameteritemvalues()
, and
POST_register_conditionitemvalues()
. The HydPy server will execute
these methods in the given order. The arguments firstdate_sim, lastdate_sim,
and alpha allow changing the start and end date of the simulation period and the
value of parameter Alpha
later:
>>> def set_itemvalues(id_, firstdate, lastdate, alpha):
... content = (f"firstdate_sim = {firstdate}\n"
... f"lastdate_sim = {lastdate}\n"
... f"alpha = {alpha}").encode("utf-8")
... methods = ",".join(("POST_register_simulationdates",
... "POST_register_parameteritemvalues",
... "POST_register_conditionitemvalues"))
... url = f"http://127.0.0.1:8080/execute?id={id_}&methods={methods}"
... request.urlopen(url, data=content)
Function simulate wraps only GET methods and triggers the next simulation run. As for all GET and POST methods, one should pass the query parameter id, used by the HydPy server for internal bookmarking:
>>> def simulate(id_):
... methods = ",".join(("GET_activate_simulationdates",
... "GET_activate_parameteritemvalues",
... "GET_load_internalconditions",
... "GET_activate_conditionitemvalues",
... "GET_simulate",
... "GET_save_internalconditions",
... "GET_update_conditionitemvalues",
... "GET_update_getitemvalues"))
... url = f"http://127.0.0.1:8080/execute?id={id_}&methods={methods}"
... request.urlopen(url)
Function print_itemvalues also wraps only GET methods and prints the current value of
parameter Alpha
as well as the lastly simulated discharge values
corresponding to the given id value:
>>> from hydpy import print_vector
>>> def print_itemvalues(id_):
... methods = ",".join(("GET_query_simulationdates",
... "GET_query_parameteritemvalues",
... "GET_query_conditionitemvalues",
... "GET_query_getitemvalues"))
... url = f"http://127.0.0.1:8080/execute?id={id_}&methods={methods}"
... data = str(request.urlopen(url).read(), encoding="utf-8")
... for line in data.split("\n"):
... if line.startswith("alpha"):
... alpha = line.split("=")[1].strip()
... if line.startswith("dill_assl"):
... discharge = eval(line.split("=")[1])
... print(f"{alpha}: ", end="")
... print_vector(discharge)
For the sake of brevity, we also define do_everything for calling the other functions at once:
>>> def do_everything(id_, firstdate, lastdate, alpha):
... set_itemvalues(id_, firstdate, lastdate, alpha)
... simulate(id_)
... print_itemvalues(id_)
In the simplest example, we perform a simulation throughout five days for an
Alpha
value of 2:
>>> do_everything("1a", "1996-01-01", "1996-01-06", 2.0)
2.0: 35.494358, 7.730125, 5.01782, 4.508775, 4.244626
The following example shows interlocked simulation runs. The first call only triggers a simulation run for the first initialised day:
>>> do_everything("1b", "1996-01-01", "1996-01-02", 2.0)
2.0: 35.494358
The second call repeats the first one with a different id value:
>>> do_everything("2", "1996-01-01", "1996-01-02", 2.0)
2.0: 35.494358
The third call covers the first three initialisation days:
>>> do_everything("3", "1996-01-01", "1996-01-04", 2.0)
2.0: 35.494358, 7.730125, 5.01782
The fourth call continues the simulation of the first call, covering the last four initialised days:
>>> do_everything("1b", "1996-01-02", "1996-01-06", 2.0)
2.0: 7.730125, 5.01782, 4.508775, 4.244626
The results of the very first call of function do_everything (with`id=1`) are identical with the pulled-together discharge values of the calls with id=1b, made possible by the internal bookmarking feature of the HydPy server. Here we use numbers, but any other strings are valid id values.
This example extends the last one by applying different parameter values:
>>> do_everything("4", "1996-01-01", "1996-01-04", 2.0)
2.0: 35.494358, 7.730125, 5.01782
>>> do_everything("5", "1996-01-01", "1996-01-04", 1.0)
1.0: 11.757526, 8.865079, 7.101815
>>> do_everything("4", "1996-01-04", "1996-01-06", 2.0)
2.0: 4.508775, 4.244626
>>> do_everything("5", "1996-01-04", "1996-01-06", 1.0)
1.0: 5.994195, 5.301584
>>> do_everything("5", "1996-01-01", "1996-01-06", 1.0)
1.0: 11.757526, 8.865079, 7.101815, 5.994195, 5.301584
The order in which function do_everything calls its subfunctions seems quite natural, but some tools might require do deviate from it. For example, OpenDA offers ensemble-based algorithms triggering the simulation of all memberse before starting to query any simulation results. The final example shows that the underlying atomic methods support such an execution sequence:
>>> set_itemvalues("6", "1996-01-01", "1996-01-03", 2.0)
>>> simulate("6")
>>> set_itemvalues("7", "1996-01-01", "1996-01-03", 1.0)
>>> simulate("7")
>>> print_itemvalues("6")
2.0: 35.494358, 7.730125
>>> print_itemvalues("7")
1.0: 11.757526, 8.865079
When working in parallel mode, OpenDA might not always call the functions set_itemvalues and simulate for the same id directly one after another, which also causes no problem:
>>> set_itemvalues("6", "1996-01-03", "1996-01-06", 2.0)
>>> set_itemvalues("7", "1996-01-03", "1996-01-06", 1.0)
>>> simulate("6")
>>> simulate("7")
>>> print_itemvalues("6")
2.0: 5.01782, 4.508775, 4.244626
>>> print_itemvalues("7")
1.0: 7.101815, 5.994195, 5.301584
Finally, we close the server and kill its process (just closing your command-line tool works likewise):
>>> _ = request.urlopen("http://127.0.0.1:8080/close_server")
>>> process.kill()
>>> _ = process.communicate()
The above description focussed on coupling HydPy to OpenDA. However, the applied
atomic submethods of class HydPyServer
also allow coupling HydPy with other
software products. See the documentation on class HydPyServer
for further information.
Module servertools
implements the following members:
ID
Type for strings that identify “artificial” HydPy instances (from a client’s point of view).
ServerState
Singleton class handling states like the currentHydPy
instance exchange items.
HydPyServer
The API of the HydPy server.
start_server()
Start the HydPy server using the given socket.
await_server()
Block the current process until either the HydPy server is responding on the given port or the given number of seconds elapsed.
- class hydpy.exe.servertools.ServerState(projectname: str, xmlfile: str, load_conditions: bool = True, load_series: bool = True)[source]¶
Bases:
object
Singleton class handling states like the current
HydPy
instance exchange items.The instance of class
ServerState
is available as the member state of classHydPyServer
after calling the functionstart_server()
. You could create other instances (like we do in the following examples), but you most likely shouldn’t. The primary purpose of this instance is to store information between successive initialisations of classHydPyServer
.We use the HydPy-H-Lahn project and its (complicated) XML configuration file multiple_runs.xml as an example (module
xmltools
provides information on interpreting this file):>>> from hydpy.core.testtools import prepare_full_example_1 >>> prepare_full_example_1() >>> from hydpy import print_vector, TestIO >>> from hydpy.exe.servertools import ServerState >>> with TestIO(): ... state = ServerState("HydPy-H-Lahn", "multiple_runs.xml") Start HydPy project `HydPy-H-Lahn` (...). Read configuration file `multiple_runs.xml` (...). Interpret the defined options (...). Interpret the defined period (...). Read all network files (...). Activate the selected network (...). Read the required control files (...). Read the required condition files (...). Read the required time series files (...).
After initialisation, all defined exchange items are available:
>>> for item in state.parameteritems: ... print(item) SetItem(name="alpha", master="hland_96", target="control.alpha", level="global") SetItem(name="beta", master="hland_96", target="control.beta", level="global") SetItem(name="lag", master="musk_classic", target="control.nmbsegments", keyword="lag", level="global") SetItem(name="damp", master="musk_classic", target="control.coefficients", keyword="damp", level="global") AddItem(name="sfcf_1", master="hland_96", target="control.sfcf", base="control.rfcf", level="global") AddItem(name="sfcf_2", master="hland_96", target="control.sfcf", base="control.rfcf", level="global") AddItem(name="sfcf_3", master="hland_96", target="control.sfcf", base="control.rfcf", level="subunit") MultiplyItem(name="k4", master="hland_96", target="control.k4", base="control.k", level="global") >>> for item in state.conditionitems: ... print(item) SetItem(name="ic_lahn_leun", master="hland_96", target="states.ic", level="device") SetItem(name="ic_lahn_marb", master="hland_96", target="states.ic", level="subunit") SetItem(name="sm_lahn_leun", master="hland_96", target="states.sm", level="device") SetItem(name="sm_lahn_marb", master="hland_96", target="states.sm", level="subunit") SetItem(name="quh", master="rconc_uh", target="logs.quh", level="device") >>> for item in state.getitems: ... print(item) GetItem(name="?", master="hland_96", target="factors.contriarea") GetItem(name="current_discharge", master="hland_96", target="fluxes.qt") GetItem(name="entire_discharge_series", master="hland_96", target="fluxes.qt.series") GetItem(name="?", master="hland_96", target="states.sm") GetItem(name="?", master="hland_96", target="states.sm.series") GetItem(name="?", master="nodes", target="nodes.sim.series")
The initialisation also memorises the initial conditions of all elements:
>>> for element in state.init_conditions: ... print(element) land_dill_assl land_lahn_kalk land_lahn_leun land_lahn_marb stream_dill_assl_lahn_leun stream_lahn_leun_lahn_kalk stream_lahn_marb_lahn_leun
The initialisation also prepares all selected series arrays and reads the required input data:
>>> print_vector(state.hp.elements.land_dill_assl.model.sequences.inputs.t.series) 0.0, -0.5, -2.4, -6.8, -7.8 >>> state.hp.nodes.dill_assl.sequences.sim.series InfoArray([nan, nan, nan, nan, nan])
- interface: XMLInterface¶
- parameteritems: list[ChangeItem]¶
- class hydpy.exe.servertools.HydPyServer(request, client_address, server)[source]¶
Bases:
BaseHTTPRequestHandler
The API of the HydPy server.
Technically and strictly speaking,
HydPyServer
is, only the HTTP request handler for the real HTTP server class (from the standard library).After initialising the HydPy server, each communication via a GET or POST request is handled by a new instance of
HydPyServer
. This handling occurs in a unified way using either methoddo_GET()
or [HydPyServer.do_POST|, which select and apply the actual GET or POST method. All methods provided by classHydPyServer
starting with “GET” or “POST” are accessible via HTTP.In the main documentation on module
servertools
, we use the multiple_runs_alpha.xml file of the HydPy-H-Lahn project as an example. However, now we select the more complex XML configuration file multiple_runs.xml, covering a higher number of cases:>>> from hydpy.core.testtools import prepare_full_example_1 >>> prepare_full_example_1() >>> from hydpy import run_subprocess, TestIO >>> with TestIO(): ... process = run_subprocess( ... "hyd.py start_server 8080 HydPy-H-Lahn multiple_runs.xml " ... "debugging=enable", ... blocking=False, ... verbose=False, ... ) ... result = run_subprocess("hyd.py await_server 8080 10", verbose=False)
We define a test function that simplifies sending the following requests and offers two optional arguments. When passing a value to id_, test adds this value as the query parameter id to the URL. When passing a string to data, test sends a POST request containing the given data; otherwise, a GET request without additional data:
>>> from urllib import request >>> def test(name, id_=None, data=None, return_result=False): ... url = f"http://127.0.0.1:8080/{name}" ... if id_: ... url = f"{url}?id={id_}" ... if data: ... data = bytes(data, encoding="utf-8") ... response = request.urlopen(url, data=data) ... result = str(response.read(), encoding="utf-8") ... print(result) ... return result if return_result else None
Asking for its status tells us that the server is ready (which may take a while, depending on the project’s size):
>>> test("status") status = ready
You can query the current version number of the HydPy installation used to start the server:
>>> result = test("version", return_result=True) version = ... >>> hydpy.__version__ in result True
HydPyServer
returns the error code 400 if it realises the URL to be wrong:>>> test("missing") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 400: RuntimeError: No method `GET_missing` available.
The error code is 500 in all other cases of error:
>>> test("register_parameteritemvalues", id_="0", data="alpha = []") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: RuntimeError: While trying to execute method `POST_register_parameteritemvalues`, the following error occurred: A value for parameter item `beta` is missing.
Some methods require identity information, passed as query parameter id, used for internal bookmarking:
>>> test("query_parameteritemvalues") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: RuntimeError: While trying to execute method `GET_query_parameteritemvalues`, the following error occurred: For the GET method `query_parameteritemvalues` no query parameter `id` is given.
POST methods always expect an arbitrary number of lines, each one assigning some values to some variable (in most cases, numbers to exchange items):
>>> test("parameteritemvalues", ... id_="a", ... data=("x = y\n" ... " \n" ... "x == y\n" ... "x = y")) Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 400: RuntimeError: The POST method `parameteritemvalues` received a wrongly formated data body. The following line has been extracted but cannot be further processed: `x == y`.
Before explaining the more offical methods, we introduce the method
POST_evaluate()
, which evaluates arbitrary valid Python code within the server process. Its most likely use-case is to access the (sub)attributes of the single instance of classServerState
, available as a member of classHydPyServer
. This method can help when being puzzled about the state of the HydPy server. Use it, for example, to find out whichNode
objects are available and to see which one is the outlet node of theElement
object land_dill_assl:>>> test("evaluate", ... data=("nodes = HydPyServer.state.hp.nodes\n" ... "elements = HydPyServer.state.hp.elements.land_dill_assl")) nodes = Nodes("dill_assl", "lahn_kalk", "lahn_leun", "lahn_marb") elements = Element("land_dill_assl", outlets="dill_assl", keywords="catchment")
Method
GET_query_itemtypes()
, already described in the main documentation of moduleservertools
, returns all available exchange item types at once. However, it is also possible to query those that are related to setting parameter values (GET_query_parameteritemtypes()
), setting condition values (GET_query_conditionitemtypes()
), setting input time series (GET_query_inputitemtypes()
), getting values or series of factors or fluxes in the “setitem style” (GET_query_outputitemtypes()
) and getting different kinds of values or series in the “getitem style” (GET_query_getitemtypes()
) separately:>>> test("query_parameteritemtypes") alpha = Double0D beta = Double0D lag = Double0D damp = Double0D sfcf_1 = Double0D sfcf_2 = Double0D sfcf_3 = Double1D k4 = Double0D >>> test("query_conditionitemtypes") ic_lahn_leun = Double1D ic_lahn_marb = Double1D sm_lahn_leun = Double1D sm_lahn_marb = Double1D quh = Double1D >>> test("query_inputitemtypes") t_headwaters = TimeSeries1D >>> test("query_outputitemtypes") swe_headwaters = TimeSeries1D >>> test("query_getitemtypes") land_dill_assl_factors_contriarea = Double0D land_dill_assl_fluxes_qt = Double0D land_dill_assl_fluxes_qt_series = TimeSeries0D land_dill_assl_states_sm = Double1D land_lahn_kalk_states_sm = Double1D land_lahn_leun_states_sm = Double1D land_lahn_marb_states_sm = Double1D land_lahn_kalk_states_sm_series = TimeSeries1D dill_assl_nodes_sim_series = TimeSeries0D
The same holds for the initial values of the exchange items. Method
GET_query_initialitemvalues()
returns them all at once, while the methodsGET_query_initialparameteritemvalues()
,GET_query_initialconditionitemvalues()
,GET_query_initialinputitemvalues()
,GET_query_initialoutputitemvalues()
, and (GET_query_initialgetitemvalues()
return the relevant subgroup only. Note that for the exchange items related to state sequenceSM
(sm_lahn_marb and sm_lahn_leun), the initial values stem from the XML file. For the items related to state sequenceIc
and input sequenceT
, the XML file does not provide such information. Thus, the initial values of ic_lahn_marb, ic_lahn_leun, and t_headwaters stem from the corresponding sequences themselves (and thus, indirectly, from the respective condition and time series files):>>> test("query_initialparameteritemvalues") alpha = 2.0 beta = 1.0 lag = 5.0 damp = 0.5 sfcf_1 = 0.3 sfcf_2 = 0.2 sfcf_3 = [0.1, 0.2, 0.1, 0.2, 0.1, 0.2, 0.1, 0.2, 0.1, 0.2, 0.1, 0.2, 0.2, 0.2] k4 = 10.0 >>> test("query_initialconditionitemvalues") ic_lahn_leun = [1.184948] ic_lahn_marb = [0.96404, 1.36332, 0.96458, 1.46458, 0.96512, 1.46512, 0.96565, 1.46569, 0.96617, 1.46617, 0.96668, 1.46668, 1.46719] sm_lahn_leun = [123.0] sm_lahn_marb = [110.0, 120.0, 130.0, 140.0, 150.0, 160.0, 170.0, 180.0, 190.0, 200.0, 210.0, 220.0, 230.0] quh = [10.0] >>> test("query_initialinputitemvalues") t_headwaters = [[0.0, -0.5, -2.4, -6.8, -7.8], [-0.7, -1.5, -4.6, -8.2, -8.7]] >>> test("query_initialoutputitemvalues") swe_headwaters = [[nan, nan, nan, nan, nan], [nan, nan, nan, nan, nan]] >>> test("query_initialgetitemvalues") land_dill_assl_factors_contriarea = nan land_dill_assl_fluxes_qt = nan land_dill_assl_fluxes_qt_series = [nan, nan, nan, nan, nan] land_dill_assl_states_sm = [185.13164...] land_lahn_kalk_states_sm = [101.31248...] land_lahn_leun_states_sm = [138.31396...] land_lahn_marb_states_sm = [99.27505...] land_lahn_kalk_states_sm_series = [[nan, ...], [nan, ...], ..., [nan, ...]] dill_assl_nodes_sim_series = [nan, nan, nan, nan, nan]
Some external tools require ways to identify specific sub-values of different exchange items. For example, they need to map those sub-values to location data available in a separate database. Method
GET_query_itemsubnames()
provides artificial sub names suitable for such a mapping. See propertysubnames
of classChangeItem
and methodyield_name2subnames()
of classGetItem
for the specification of the sub names. Here, note the special handling for change items addressing the global level, for which we cannot define a meaningful sub name. MethodGET_query_itemsubnames()
returns the string *global* in such cases:>>> test("query_itemsubnames") alpha = *global* beta = *global* lag = *global* damp = *global* sfcf_1 = *global* sfcf_2 = *global* sfcf_3 = [land_lahn_kalk_0, ..., land_lahn_kalk_13] k4 = *global* t_headwaters = [land_dill_assl, land_lahn_marb] ic_lahn_leun = [land_lahn_leun] ic_lahn_marb = [land_lahn_marb_0, ..., land_lahn_marb_12] sm_lahn_leun = [land_lahn_leun] sm_lahn_marb = [land_lahn_marb_0, ..., land_lahn_marb_12] quh = [land_lahn_leun] swe_headwaters = [land_dill_assl, land_lahn_marb] land_dill_assl_factors_contriarea = land_dill_assl land_dill_assl_fluxes_qt = land_dill_assl land_dill_assl_fluxes_qt_series = land_dill_assl land_dill_assl_states_sm = ('land_dill_assl_0', ..., 'land_dill_assl_11') land_lahn_kalk_states_sm = ('land_lahn_kalk_0', ..., 'land_lahn_kalk_13') land_lahn_leun_states_sm = ('land_lahn_leun_0', ..., 'land_lahn_leun_9') land_lahn_marb_states_sm = ('land_lahn_marb_0', ..., 'land_lahn_marb_12') land_lahn_kalk_states_sm_series = ('land_lahn_kalk_0', ..., 'land_lahn_kalk_13') dill_assl_nodes_sim_series = dill_assl
The
init
time grid is immutable once the server is ready. MethodGET_query_initialisationtimegrid()
returns the fixed first date, last date, and stepsize of the whole initialised period:>>> test("query_initialisationtimegrid") firstdate_init = 1996-01-01T00:00:00+01:00 lastdate_init = 1996-01-06T00:00:00+01:00 stepsize = 1d
The dates of the
sim
time grid, on the other hand, are mutable and can vary for different id query parameters. This flexibility makes things a little more complicated, as theTimegrids
object of the globalpub
module handles only onesim
object at once. Hence, we differentiate between registered simulation dates of the respective id values and the current simulation dates of theTimegrids
object.Method
GET_query_simulationdates()
asks for registered simulation dates and thus fails at first:>>> test("query_simulationdates", id_="0") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: RuntimeError: While trying to execute method `GET_query_simulationdates`, the following error occurred: Nothing registered under the id `0`. There is nothing registered, so far.
After logging new simulation dates via the POST method
POST_register_simulationdates()
, methodGET_query_simulationdates()
returns them correctly:>>> test("register_simulationdates", id_="0", ... data=("firstdate_sim = 1996-01-01\n" ... "lastdate_sim = 1996-01-02")) >>> test("query_simulationdates", id_="0") firstdate_sim = 1996-01-01T00:00:00+01:00 lastdate_sim = 1996-01-02T00:00:00+01:00
Our initial call to the POST method
POST_register_simulationdates()
did not affect the currently active simulation dates. We need to do this manually by calling methodGET_activate_simulationdates()
:>>> test("evaluate", data="lastdate = hydpy.pub.timegrids.sim.lastdate") lastdate = Date("1996-01-06T00:00:00") >>> test("activate_simulationdates", id_="0") >>> test("evaluate", data="lastdate = hydpy.pub.timegrids.sim.lastdate") lastdate = Date("1996-01-02 00:00:00")
Generally, passing a missing id while others are available results in error messages like the following:
>>> test("activate_simulationdates", id_="1") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: RuntimeError: While trying to execute method `GET_activate_simulationdates`, the following error occurred: Nothing registered under the id `1`. The available ids are: 0.
The logic of the parameter-related GET and POST methods is very similar to the one of the simulation date-related methods discussed above. Method
POST_register_parameteritemvalues()
registers new values of the exchange items, and methodGET_activate_parameteritemvalues()
activates them (assigns them to the relevant parameters):>>> test("register_parameteritemvalues", id_="0", ... data=("alpha = 3.0\n" ... "beta = 2.0\n" ... "lag = 1.0\n" ... "damp = 0.5\n" ... "sfcf_1 = 0.3\n" ... "sfcf_2 = 0.2\n" ... "sfcf_3 = 0.1\n" ... "k4 = 10.0\n")) >>> control = ("HydPyServer.state.hp.elements.land_dill_assl.model.parameters." ... "control") >>> test("evaluate", ... data=(f"alpha = {control}.alpha\n" ... f"sfcf = {control}.sfcf")) alpha = alpha(1.0) sfcf = sfcf(1.1)
>>> test("activate_parameteritemvalues", id_="0") >>> test("evaluate", ... data=(f"alpha = {control}.alpha\n" ... f"sfcf = {control}.sfcf")) alpha = alpha(3.0) sfcf = sfcf(1.34283)
The list of exchange items must be complete:
>>> test("register_parameteritemvalues", id_="0", ... data=("alpha = 3.0\n" ... "beta = 2.0")) Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: RuntimeError: While trying to execute method `POST_register_parameteritemvalues`, the following error occurred: A value for parameter item `lag` is missing.
Note that the related query method (
GET_query_parameteritemvalues()
) returns the logged values of theChangeItem
objects instead of the (eventually modified) values of theParameter
objects:>>> test("query_parameteritemvalues", id_="0") alpha = 3.0 beta = 2.0 lag = 1.0 damp = 0.5 sfcf_1 = 0.3 sfcf_2 = 0.2 sfcf_3 = 0.1 k4 = 10.0
The condition-related methods
POST_register_conditionitemvalues()
,GET_activate_conditionitemvalues()
, andGET_query_conditionitemvalues()
work like the parameter-related methods described above:>>> test("register_conditionitemvalues", id_="0", ... data=("sm_lahn_leun = 246.0\n" ... "sm_lahn_marb = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]\n" ... "ic_lahn_leun = 642.0\n" ... "ic_lahn_marb = [13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]\n" ... "quh = 1.0\n")) >>> test("query_conditionitemvalues", id_="0") ic_lahn_leun = 642.0 ic_lahn_marb = [13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1] sm_lahn_leun = 246.0 sm_lahn_marb = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13] quh = 1.0
Note the trimming of the too-high value for the state sequence
SM
to its highest possible value defined by control parameterFC
):>>> for element in ("land_lahn_marb", "land_lahn_leun"): ... path_element = f"HydPyServer.state.hp.elements.{element}" ... path_sequences_model = f"{path_element}.model.sequences" ... path_sequences_submodel = f"{path_element}.model.rconcmodel.sequences" ... test("evaluate", ... data=(f"sm = {path_sequences_model}.states.sm \n" ... f"quh = {path_sequences_submodel}.logs.quh")) sm = sm(99.27505, ..., 142.84148) quh = quh(0.0) sm = sm(138.31396, ..., 164.63255) quh = quh(0.7, 0.0) >>> test("activate_conditionitemvalues", id_="0") >>> for element in ("land_lahn_marb", "land_lahn_leun"): ... path_element = f"HydPyServer.state.hp.elements.{element}" ... path_sequences_model = f"{path_element}.model.sequences" ... path_sequences_submodel = f"{path_element}.model.rconcmodel.sequences" ... test("evaluate", ... data=(f"sm = {path_sequences_model}.states.sm \n" ... f"quh = {path_sequences_submodel}.logs.quh")) sm = sm(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0) quh = quh(0.0) sm = sm(197.0, 197.0, 197.0, 197.0, 197.0, 197.0, 197.0, 197.0, 197.0, 197.0) quh = quh(1.0, 0.0)
The methods
POST_register_inputitemvalues()
,GET_activate_inputitemvalues()
, andGET_query_inputitemvalues()
always focus on the currently relevant simulation time grid:>>> test("update_inputitemvalues", id_="0") >>> test("query_inputitemvalues", id_="0") t_headwaters = [[0.0], [-0.7]] >>> t = "HydPyServer.state.hp.elements.land_lahn_marb.model.sequences.inputs.t" >>> test("evaluate", data=(f"t_series = {t}.series\n" ... f"t_simseries = {t}.simseries\n")) t_series = InfoArray([-0.7, -1.5, -4.6, -8.2, -8.7]) t_simseries = InfoArray([-0.7])
>>> test("register_inputitemvalues", id_="0", ... data="t_headwaters = [[1.0], [2.0]]\n") >>> test("activate_inputitemvalues", id_="0") >>> test("evaluate", data=(f"t_series = {t}.series\n" ... f"t_simseries = {t}.simseries\n")) t_series = InfoArray([ 2. , -1.5, -4.6, -8.2, -8.7]) t_simseries = InfoArray([2.])
The “official” way to gain information on modified parameters or conditions is to use the method
GET_query_getitemvalues()
:>>> test("query_getitemvalues", id_="0") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: RuntimeError: While trying to execute method `GET_query_getitemvalues`, the following error occurred: Nothing registered under the id `0`. There is nothing registered, so far.
As the error message explains, we first need to fill the registry for the given id parameter. Unlike the examples above, we do not do this by sending external data via a POST request but by retrieving the server’s currently active data. We accomplish this task by calling the GET method
GET_update_getitemvalues()
:>>> test("update_getitemvalues", id_="0") >>> test("query_getitemvalues", id_="0") land_dill_assl_factors_contriarea = nan land_dill_assl_fluxes_qt = nan land_dill_assl_fluxes_qt_series = [nan] land_dill_assl_states_sm = [185.13164, ...] land_lahn_kalk_states_sm = [101.31248, ...] land_lahn_leun_states_sm = [197.0, ..., 197.0] land_lahn_marb_states_sm = [1.0, 2.0, ..., 12.0, 13.0] land_lahn_kalk_states_sm_series = [[nan, ..., nan]] dill_assl_nodes_sim_series = [nan]
Besides the “official” way for retrieving information (which we sometimes call the “getitem style”), some sequences types (namely those derived from
FactorSequence
andFluxSequence
) also allow retrieving information in the so-called “setitem style” via the methodsGET_update_outputitemvalues()
andGET_query_outputitemvalues()
:>>> test("query_outputitemvalues", id_="0") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: RuntimeError: While trying to execute method `GET_query_outputitemvalues`, the following error occurred: Nothing registered under the id `0`. There is nothing registered, so far. >>> test("update_outputitemvalues", id_="0") >>> test("query_outputitemvalues", id_="0") swe_headwaters = [[nan], [nan]]
We now modify the parameter, condition, and input time series values again, but this time in one step through calling
POST_register_changeitemvalues()
andGET_activate_changeitemvalues()
:>>> test("register_changeitemvalues", id_="0", ... data=("alpha = 1.0\n" ... "beta = 1.0\n" ... "lag = 0.0\n" ... "damp = 0.0\n" ... "sfcf_1 = 0.0\n" ... "sfcf_2 = 0.0\n" ... "sfcf_3 = 0.0\n" ... "k4 = 5.0\n" ... "ic_lahn_marb = 1.0\n" ... "ic_lahn_leun = 2.0\n" ... "sm_lahn_marb = 50.0\n" ... "sm_lahn_leun = 100.0\n" ... "quh = 0.0\n" ... "t_headwaters = [[-0.29884643], [-0.70539496]]\n")) >>> test("activate_changeitemvalues", id_="0") >>> test("query_changeitemvalues", id_="0") alpha = 1.0 beta = 1.0 lag = 0.0 damp = 0.0 sfcf_1 = 0.0 sfcf_2 = 0.0 sfcf_3 = 0.0 k4 = 5.0 t_headwaters = [[-0.29884...], [-0.70539...]] ic_lahn_leun = 2.0 ic_lahn_marb = 1.0 sm_lahn_leun = 100.0 sm_lahn_marb = 50.0 quh = 0.0
Next, we trigger a simulation run by calling the GET method
GET_simulate()
:>>> test("simulate", id_="0")
Calling methods
GET_update_getitemvalues()
andGET_query_getitemvalues()
as well as methodsGET_update_outputitemvalues()
andGET_query_outputitemvalues()
reveals how the simulation results:>>> test("update_getitemvalues", id_="0") >>> test("query_getitemvalues", id_="0") land_dill_assl_factors_contriarea = 0.759579 land_dill_assl_fluxes_qt = 5.508952 ... land_lahn_leun_states_sm = [100.341052, ..., 100.0] ... dill_assl_nodes_sim_series = [5.508952] >>> test("update_outputitemvalues", id_="0") >>> test("query_outputitemvalues", id_="0") swe_headwaters = [[0.074231], [0.0]]
So far, we have explained how the HydPy server memorises different exchange item values for different values of query parameter id. Complicating matters, memorising condition values must also consider the relevant time point. You load conditions for the simulation period’s current start date with method
GET_load_internalconditions()
and save them for the current end date with methodGET_save_internalconditions()
. For example, we first save the states calculated for the end time of the last simulation run (January 2):>>> test("query_simulationdates", id_="0") firstdate_sim = 1996-01-01T00:00:00+01:00 lastdate_sim = 1996-01-02T00:00:00+01:00 >>> test("evaluate", ... data=f"sm_lahn2 = {path_sequences_model}.states.sm") sm_lahn2 = sm(100.341052, ..., 100.0) >>> test("save_internalconditions", id_="0")
Calling method
GET_load_internalconditions()
without changing the simulation dates reloads the initial conditions for January 1, originally read from disk:>>> test("load_internalconditions", id_="0") >>> test("evaluate", ... data=f"sm_lahn2 = {path_sequences_model}.states.sm") sm_lahn2 = sm(138.31396, ..., 164.63255)
If we set the first date of the simulation period to January 2, method
GET_load_internalconditions()
loads the conditions we saved for January 2 previously:>>> test("register_simulationdates", id_="0", ... data=("firstdate_sim = 1996-01-02\n" ... "lastdate_sim = 1996-01-03")) >>> test("activate_simulationdates", id_="0") >>> test("load_internalconditions", id_="0") >>> test("evaluate", ... data=f"sm_lahn2 = {path_sequences_model}.states.sm") sm_lahn2 = sm(100.341052, ..., 100.0)
Loading condition values for a specific time point requires saving them before:
>>> test("register_simulationdates", id_="0", ... data=("firstdate_sim = 1996-01-03\n" ... "lastdate_sim = 1996-01-05")) >>> test("activate_simulationdates", id_="0") >>> test("load_internalconditions", id_="0") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: RuntimeError: While trying to execute method `GET_load_internalconditions`, the following error occurred: Conditions for ID `0` and time point `1996-01-03 00:00:00` are required, but have not been calculated so far.
For example, when restarting data assimilation subsequent forecasting periods, you might need to get and set all internal conditions from the client side. Then, you have two options. The more efficient way relies on methods
GET_query_internalconditions()
andPOST_register_internalconditions()
. MethodGET_query_internalconditions()
returns the information registered for the end of the current simulation period. All data is within a single nesteddict
object (created by theconditions
property of classHydPy
):>>> test("register_simulationdates", id_="0", ... data=("firstdate_sim = 1996-01-01\n" ... "lastdate_sim = 1996-01-02")) >>> test("activate_simulationdates", id_="0") >>> conditions = test("query_internalconditions", id_="0", ... return_result=True)[13:] conditions = {'land_dill_assl': {'model': {'states': {'ic': array([0.73040403, 1.23040403, 0.73046025...
Due to the steps above, the returned dictionary agrees with the current state of the
HydPy
instance:>>> sequences = f"HydPyServer.state.hp.elements.land_dill_assl.model.sequences" >>> test("evaluate", ... data=f"ic_dill_assl = {sequences}.states.ic") ic_dill_assl = ic(0.730404, 1.230404, 0.73046,...
To show that registering new internal conditions also works, we first convert the string representation of the data to actual Python objects by using Python’s
eval()
function. Therefore, we need to clarify that “array” means the array creation functionarray()
ofnumpy
:>>> import numpy >>> conditions = eval(conditions, {"array": numpy.array})
Next, we modify an arbitrary state and convert the dictionary back to a single-line string:
>>> conditions["land_dill_assl"]["model"]["states"]["ic"][:2] = 0.5, 2.0 >>> conditions = str(conditions).replace("\n", " ")
Now we can send the modified data back to the server by using the
POST_register_internalconditions()
method, which stores it for the start of the simulation period:>>> test("register_internalconditions", id_="0", data=f"conditions = {conditions}") >>> ic_dill_assl = ("self.state.conditions['0'][0]['land_dill_assl']['model']" ... "['states']['ic']") >>> test("evaluate", ... data=f"ic_dill_assl = {ic_dill_assl}") ic_dill_assl = array([0.5 , 2. , 0.73046025...
After calling method
GET_load_internalconditions()
, the freshly registered states are ready to be used by the next simulation run:>>> test("load_internalconditions", id_="0") >>> test("evaluate", ... data=f"ic_dill_assl = {sequences}.states.ic") ic_dill_assl = ic(0.5, 2.0, 0.73046,...
Keeping the internal conditions for multiple time points can use plenty of RAM. Use the GET method
GET_deregister_internalconditions()
to remove all conditions data available under the given id to avoid that:>>> test("query_internalconditions", id_="0") conditions = {'land_dill_assl': {'model': {'states': {'ic': array([0.7304... >>> test("deregister_internalconditions", id_="0") >>> test("query_internalconditions", id_="0") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: RuntimeError: While trying to execute method `GET_query_internalconditions`, the following error occurred: No internal conditions registered under the id `0` for `1996-01-02 00:00:00`.
Some algorithms provide new information about initial conditions and require information on how they evolve during a simulation. For such purposes, you can use method
GET_update_conditionitemvalues()
to store the current conditions under an arbitrary id and use methodGET_query_conditionitemvalues()
to query them later. Note that this approach so far only works when usingSetItem
objects that modify their target sequence on the device or subunit level (please tell us if you encounter other relevant use-cases):>>> test("update_conditionitemvalues", id_="0") >>> test("query_conditionitemvalues", id_="0") ic_lahn_leun = [0.955701] ic_lahn_marb = [0.7421...] sm_lahn_leun = [100.1983...] sm_lahn_marb = [49.9304...] quh = [0.000395]
The second option for handling multiple “simultaneous” initial conditions is telling the HydPy server to read them from and write them to disk, which is easier but often less efficient due to higher IO activity. Use methods
GET_load_conditions()
andGET_save_conditions()
for this purpose. Reading from or writing to different directories than those defined in multiple_runs.xml requires registering them beforehand. If we, for example, create a new empty directory with methodPOST_register_inputconditiondir()
, loading conditions from it must fail:>>> test("register_inputconditiondir", id_="0", data="inputconditiondir = new") >>> test("load_conditions", id_="0") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: FileNotFoundError: While trying to execute method `GET_load_conditions`, the following error occurred: While trying to load the initial conditions of element `land_dill_assl`, the following error occurred: [Errno 2] No such file or directory: ...land_dill_assl.py'
>>> test("register_outputconditiondir", id_="0", data="outputconditiondir = new") >>> test("save_conditions", id_="0")
Hence, we better first write suitable conditions into the new directory:
>>> lz_dill_assl = "self.state.hp.elements.land_dill_assl.model.sequences.states.lz" >>> test("evaluate", data=f"lz_dill_assl = {lz_dill_assl}") lz_dill_assl = lz(9.493...)
To prove reading and writing conditions works, we first set the current value of sequence
LZ
of catchment “Dill” to zero:>>> test("evaluate", data=f"nothing = {lz_dill_assl}(0.0)") nothing = None >>> test("evaluate", data=f"lz_dill_assl = {lz_dill_assl}") lz_dill_assl = lz(0.0)
As expected, applying
GET_load_conditions()
on the previously written data resets the value ofLZ
:>>> test("load_conditions", id_="0") >>> test("evaluate", data=f"lz_dill_assl = {lz_dill_assl}") lz_dill_assl = lz(9.493...)
Use the GET methods
GET_query_inputconditiondir()
andGET_deregister_inputconditiondir()
to query or remove the currently registered input condition directory:>>> test("query_inputconditiondir", id_="0") inputconditiondir = new >>> test("deregister_inputconditiondir", id_="0") >>> test("query_inputconditiondir", id_="0") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: RuntimeError: While trying to execute method `GET_query_inputconditiondir`, the following error occurred: Nothing registered under the id `0`. There is nothing registered, so far.
Use the GET methods
GET_query_outputconditiondir()
andGET_deregister_outputconditiondir()
to query or remove the currently registered output condition directory:>>> test("query_outputconditiondir", id_="0") outputconditiondir = new >>> test("deregister_outputconditiondir", id_="0") >>> test("query_outputconditiondir", id_="0") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: RuntimeError: While trying to execute method `GET_query_outputconditiondir`, the following error occurred: Nothing registered under the id `0`. There is nothing registered, so far.
Above, we explained the recommended way to query the initial values of all or a subgroup of the available exchange items. Alternatively, you can first register the initial values and query them later, which is a workaround for retrieving initial and intermediate values with the same HTTP request (an OpenDA requirement):
>>> test("register_initialitemvalues", id_="1") >>> test("query_itemvalues", id_="1") alpha = 2.0 beta = 1.0 lag = 5.0 damp = 0.5 sfcf_1 = 0.3 sfcf_2 = 0.2 sfcf_3 = [0.1, 0.2, 0.1, 0.2, 0.1, 0.2, 0.1, 0.2, 0.1, 0.2, 0.1, 0.2, 0.2, 0.2] k4 = 10.0 t_headwaters = [[0.0, -0.5, -2.4, -6.8, -7.8], [-0.7, -1.5, -4.6, -8.2, -8.7]] ic_lahn_leun = [1.18494...] ic_lahn_marb = [0.96404...] sm_lahn_leun = [123.0] sm_lahn_marb = [110.0, 120.0, 130.0, 140.0, 150.0, 160.0, 170.0, 180.0, 190.0, 200.0, 210.0, 220.0, 230.0] quh = [10.0] swe_headwaters = [[nan, nan, nan, nan, nan], [nan, nan, nan, nan, nan]] land_dill_assl_factors_contriarea = nan land_dill_assl_fluxes_qt = nan land_dill_assl_fluxes_qt_series = [nan, nan, nan, nan, nan] land_dill_assl_states_sm = [185.13164...] land_lahn_kalk_states_sm = [101.31248...] land_lahn_leun_states_sm = [138.31396...] land_lahn_marb_states_sm = [99.27505...] land_lahn_kalk_states_sm_series = [[nan, ...], [nan, ...], ..., [nan, ...]] dill_assl_nodes_sim_series = [nan, nan, nan, nan, nan]
In contrast to running a single simulation via method
run_simulation()
, the HydPy server does (usually) not write calculated time series automatically. Instead, one must manually call methodGET_save_allseries()
:>>> test("save_allseries", id_="0")
According to the fixed configuration of multiple_runs.xml,
GET_save_allseries()
wrote averaged soil moisture values into the directory mean_sm:>>> import netCDF4 >>> from hydpy import print_vector >>> filepath = "HydPy-H-Lahn/series/mean_sm/hland_96_state_sm_mean.nc" >>> with TestIO(), netCDF4.Dataset(filepath) as ncfile: ... print_vector(ncfile["hland_96_state_sm_mean"][:, 0]) 211.467386, 0.0, 0.0, 0.0, 0.0
To save the results of subsequent simulations without overwriting the previous ones, change the current series writer directory by the GET method
POST_register_serieswriterdir()
:>>> test("register_serieswriterdir", id_="0", data="serieswriterdir = sm_averaged") >>> test("save_allseries", id_="0") >>> filepath = "HydPy-H-Lahn/series/sm_averaged/hland_96_state_sm_mean.nc" >>> with TestIO(), netCDF4.Dataset(filepath) as ncfile: ... print_vector(ncfile["hland_96_state_sm_mean"][:, 0]) 211.467386, 0.0, 0.0, 0.0, 0.0
GET_deregister_serieswriterdir()
removes the currently set directory from the registry so that the HydPy server falls back to the configuration of multiple_runs.xml:>>> test("query_serieswriterdir", id_="0") serieswriterdir = sm_averaged >>> test("deregister_serieswriterdir", id_="0") >>> test("query_serieswriterdir", id_="0") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: RuntimeError: While trying to execute method `GET_query_serieswriterdir`, the following error occurred: Nothing registered under the id `0`. There is nothing registered, so far.
The same holds for time series to be written “just in time” during simulation runs. The temperature writer in multiple_runs.xml select the jit mode. This setting triggered that the HydPy server wrote the time series of sequences
T
andNormalAirTemperature
to the directory temperature during the last simulation:>>> filepath = "HydPy-H-Lahn/series/temperature/hland_96_input_t.nc" >>> with TestIO(), netCDF4.Dataset(filepath) as ncfile: ... print_vector(ncfile["hland_96_input_t"][:, 0]) -0.298846, 0.0, 0.0, 0.0, 0.0
The input sequences
P
andNormalEvapotranspiration
are instead reading their time series “just in time” (reading and writing data for the sameIOSequence
object is not supported). We query the last read value ofNormalEvapotranspiration
for the Dill catchment:>>> submodel = ("HydPyServer.state.hp.elements.land_dill_assl.model.aetmodel." ... "petmodel") >>> net = f"{submodel}.sequences.inputs.normalevapotranspiration" >>> test("evaluate", data=f"net_dill_assl = {net}") net_dill_assl = normalevapotranspiration(0.3)
We can change the series writer directory before starting another simulation run to write the time series of
T
andNormalAirTemperature
to another directory:>>> test("register_serieswriterdir", id_="0", data="serieswriterdir = temp") >>> test("simulate", id_="0") >>> filepath = "HydPy-H-Lahn/series/temp/hland_96_input_t.nc" >>> with TestIO(), netCDF4.Dataset(filepath) as ncfile: ... print_vector(ncfile["hland_96_input_t"][:, 0]) -0.298846, 0.0, 0.0, 0.0, 0.0
The “just in time” reading of the series of
P
andNormalEvapotranspiration
still worked, showing the registered series directory “temp” only applied for writing data:>>> test("evaluate", data=f"net_dill_assl = {net}") net_dill_assl = normalevapotranspiration(0.3)
Changing the series reader directory works as explained for the series writer directory. After setting it to an empty folder,
GET_load_allseries()
andGET_simulate()
cannot find suitable files and report this problem:>>> test("register_seriesreaderdir", id_="0", data="seriesreaderdir = no_data") >>> test("query_seriesreaderdir", id_="0") seriesreaderdir = no_data
>>> test("load_allseries", id_="0") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: FileNotFoundError: While trying to execute method `GET_load_allseries`, the following error occurred: While trying to load the time series data of sequence `t` of element `land_dill_assl`, the following error occurred: [Errno 2] No such file or directory: ...land_dill_assl_hland_96_input_t.asc'
>>> test("simulate", id_="0") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: FileNotFoundError: While trying to execute method `GET_simulate`, the following error occurred: While trying to prepare NetCDF files for reading or writing data "just in time" during the current simulation run, the following error occurred: No file `...hland_96_input_p.nc` available for reading.
After deregistering the “no_data” directory, both methods work again:
>>> test("deregister_seriesreaderdir", id_="0") >>> test("query_seriesreaderdir", id_="0") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: RuntimeError: While trying to execute method `GET_query_seriesreaderdir`, the following error occurred: Nothing registered under the id `0`. There is nothing registered, so far.
>>> test("load_allseries", id_="0") >>> test("simulate", id_="0")
As described for time series, one must explicitly pass (comparable) requests to the HydPy Server to let it write parameter control files. The control files reflect the current parameter values of all model instances:
>>> test("register_outputcontroldir", id_="0", data="outputcontroldir = calibrated") >>> test("query_outputcontroldir", id_="0") outputcontroldir = calibrated
>>> test("save_controls", id_="0") >>> with TestIO(), open("HydPy-H-Lahn/control/calibrated/" ... "land_dill_assl.py") as file_: ... print(file_.read()) # -*- coding: utf-8 -*- from hydpy.models.hland_96 import * from hydpy.models import evap_aet_hbv96 from hydpy.models import evap_pet_hbv96 from hydpy.models import rconc_uh simulationstep("1d") parameterstep("1d") ... beta(1.0) ...
>>> parameterstep = "hydpy.pub.options.parameterstep" >>> simulationstep = "hydpy.pub.options.simulationstep" >>> beta = ("HydPyServer.state.hp.elements.land_dill_assl.model.parameters." ... "control.beta") >>> test("evaluate", data=(f"simulationstep = {simulationstep}\n" ... f"parameterstep = {parameterstep}\n" ... f"beta = {beta}")) simulationstep = Period("1d") parameterstep = Period("1d") beta = beta(1.0)
>>> test("deregister_outputcontroldir", id_="0") >>> test("query_outputcontroldir", id_="0") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: RuntimeError: While trying to execute method `GET_query_outputcontroldir`, the following error occurred: Nothing registered under the id `0`. There is nothing registered, so far.
To close the HydPy server, call
GET_close_server()
:>>> test("close_server") >>> process.kill() >>> _ = process.communicate()
- server: _HTTPServerBase¶
- state: ClassVar[ServerState]¶
- GET_execute() None [source]¶
Execute an arbitrary number of GET methods.
The method names must be passed as query parameters, as explained in the main documentation on module
servertools
.
- POST_execute() None [source]¶
Execute an arbitrary number of POST and GET methods.
The method names must be passed as query parameters, as explained in the main documentation on module
servertools
.
- POST_evaluate() None [source]¶
Evaluate any valid Python expression with the HydPy server process and get its result.
Method
POST_evaluate()
serves to test and debug, primarily. The main documentation on classHydPyServer
explains its usage.For safety purposes, method
POST_evaluate()
only works if you start the HydPy Server in debug mode by writing “debugging=enable”, as we do in the examples of the main documentation on classHydPyServer
. When not working in debug mode, invoking this method results in the following error message:>>> from hydpy.core.testtools import prepare_full_example_1 >>> prepare_full_example_1() >>> from hydpy import run_subprocess, TestIO >>> with TestIO(): ... process = run_subprocess( ... "hyd.py start_server 8080 HydPy-H-Lahn multiple_runs_alpha.xml", ... blocking=False, verbose=False) ... _ = run_subprocess("hyd.py await_server 8080 10", verbose=False) >>> from urllib import request >>> request.urlopen("http://127.0.0.1:8080/evaluate", data=b"") Traceback (most recent call last): ... urllib.error.HTTPError: HTTP Error 500: RuntimeError: While trying to execute method `POST_evaluate`, the following error occurred: You can only use the POST method `evaluate` if you have started the `HydPy Server` in debugging mode.
>>> _ = request.urlopen("http://127.0.0.1:8080/close_server") >>> process.kill() >>> _ = process.communicate()
- GET_query_changeitemtypes() None [source]¶
Get the types of all current exchange items supposed to change the values of
Parameter
,StateSequence
, orLogSequence
objects.
- GET_query_parameteritemtypes() None [source]¶
Get the types of all current exchange items supposed to change the values of
Parameter
objects.
- GET_query_inputitemtypes() None [source]¶
Get the types of all current exchange items supposed to change the series of
InputSequence
objects.
- GET_query_conditionitemtypes() None [source]¶
Get the types of all current exchange items supposed to change the values of
StateSequence
orLogSequence
objects.
- GET_query_outputitemtypes() None [source]¶
Get the types of all current exchange items supposed to return the values or series of
FactorSequence
orFluxSequence
objects in the “setitem style”.
- GET_query_getitemtypes() None [source]¶
Get the types of all current exchange items supposed to return the values of
Parameter
orSequence_
objects or the time series ofIOSequence
objects in the “getitem style”.
- GET_query_itemsubnames() None [source]¶
Get names (suitable as IDs) describing the individual values of all current exchange objects.
- GET_query_changeitemsubnames() None [source]¶
Get names (suitable as IDs) describing the individual values of all current exchange objects supposed to change the values of
Parameter
,StateSequence
, orLogSequence
objects.
- GET_query_parameteritemnames() None [source]¶
Get names (suitable as IDs) describing the individual values of all current exchange objects supposed to change the values of
Parameter
objects.
- GET_query_inputitemnames() None [source]¶
Get names (suitable as IDs) describing the individual values of all current exchange objects supposed to change the values of
InputSequence
objects.
- GET_query_conditionitemnames() None [source]¶
Get names (suitable as IDs) describing the individual values of all current exchange objects supposed to change the values of
StateSequence
orLogSequence
objects.
- GET_query_outputitemnames() None [source]¶
Get names (suitable as IDs) describing the individual values of all current exchange objects supposed to return the values of or series of
FactorSequence
orFluxSequence
objects in the “setitem style”.
- GET_query_getitemsubnames() None [source]¶
Get names (suitable as IDs) describing the individual values of all current exchange objects supposed to return the values of
Parameter
orSequence_
objects or the time series ofIOSequence
objects in the “getitem style”.
- GET_register_initialitemvalues() None [source]¶
Register the initial values of all current exchange items under the given id.
Implemented as a workaround to support OpenDA. Better use method
GET_query_initialitemvalues()
.
- GET_query_initialchangeitemvalues() None [source]¶
Get the initial values of all current exchange items supposed to change the values of
Parameter
,InputSequence
,StateSequence
, orLogSequence
objects.
- GET_register_initialchangeitemvalues() None [source]¶
Register the initial values of all current exchange items supposed to change the values of
Parameter
,InputSequence
,StateSequence
, orLogSequence
objects under the given id.Implemented as a workaround to support OpenDA. Better use method
GET_query_initialchangeitemvalues()
.
- GET_query_initialparameteritemvalues() None [source]¶
Get the initial values of all current exchange items supposed to change the values of
Parameter
objects.
- GET_register_initialparameteritemvalues() None [source]¶
Register the initial values of all current exchange items supposed to change the values of
Parameter
objects under the given id.Implemented as a workaround to support OpenDA. Better use method
GET_query_initialparameteritemvalues()
.
- GET_query_initialinputitemvalues() None [source]¶
Get the initial values of all current exchange items supposed to change the series of
InputSequence
objects.
- GET_register_initialinputitemvalues() None [source]¶
Register the initial series of all current exchange items supposed to change the values of
InputSequence
objects under the given id.Implemented as a workaround to support OpenDA. Better use method
GET_query_initialinputitemvalues()
.
- GET_query_initialconditionitemvalues() None [source]¶
Get the initial values of all current exchange items supposed to change the values of
StateSequence
orLogSequence
objects.
- GET_register_initialconditionitemvalues() None [source]¶
Register the initial values of all current exchange items supposed to change the values of
StateSequence
orLogSequence
objects under the given id.Implemented as a workaround to support OpenDA. Better use method
GET_query_initialconditionitemvalues()
.
- GET_query_initialoutputitemvalues() None [source]¶
Get the initial values of all current exchange items supposed to return the values or sequences of
FactorSequence
orFluxSequence
objects in the “setitem style”.
- GET_register_initialoutputitemvalues() None [source]¶
Register the initial values of all current exchange items supposed to return the values or sequences of
FactorSequence
orFluxSequence
objects in the “setitem style” under the given id.Implemented as a workaround to support OpenDA. Better use method
GET_query_initialoutputitemvalues()
.
- GET_query_initialgetitemvalues() None [source]¶
Get the initial values of all current exchange items supposed to return the values of
Parameter
orSequence_
objects or the time series ofIOSequence
objects in the “getitems style”.
- GET_register_initialgetitemvalues() None [source]¶
Register the initial values of all current exchange items supposed to return the values of
Parameter
orSequence_
objects or the time series ofIOSequence
objects in the “getitem style” under the given id.Implemented as a workaround to support OpenDA. Better use method
GET_query_initialgetitemvalues()
.
- POST_register_simulationdates() None [source]¶
Register the send simulation dates under the given id.
- GET_activate_simulationdates() None [source]¶
Activate the simulation dates registered under the given id.
- GET_query_simulationdates() None [source]¶
Return the simulation dates registered under the given id.
- GET_query_itemvalues() None [source]¶
Get the values of all
ExchangeItem
objects registered under the given id.
- POST_register_changeitemvalues() None [source]¶
Register the send values of all
ChangeItem
objects under the given id.
- GET_activate_changeitemvalues() None [source]¶
Activate the values of the
ChangeItem
objects registered under the given id.
- GET_query_changeitemvalues() None [source]¶
Get the values of all
ChangeItem
objects registered under the given id.
- POST_register_parameteritemvalues() None [source]¶
Register the send parameter values under the given id.
- GET_activate_parameteritemvalues() None [source]¶
Activate the parameter values registered under the given id.
- GET_query_parameteritemvalues() None [source]¶
Return the parameter values registered under the given id.
- POST_register_inputitemvalues() None [source]¶
Register the send input item values under the given id.
- GET_activate_inputitemvalues() None [source]¶
Apply the input item values registered under the given id to modify the current
InputSequence
values.
- GET_update_inputitemvalues() None [source]¶
Convert the current
InputSequence
values to input item values (when necessary) and register them under the given id.
- GET_query_inputitemvalues() None [source]¶
Return the input item values registered under the given id.
- POST_register_conditionitemvalues() None [source]¶
Register the send condition item values under the given id.
- GET_activate_conditionitemvalues() None [source]¶
Apply the condition item values registered under the given id to modify the current
StateSequence
andLogSequence
values.
- GET_update_conditionitemvalues() None [source]¶
Convert the current
StateSequence
andLogSequence
values to condition item values (when necessary) and register them under the given id.
- GET_query_conditionitemvalues() None [source]¶
Return the condition item values registered under the given id.
- GET_update_outputitemvalues() None [source]¶
Convert the current
FactorSequence
andFluxSequence
values or series to output item values (when necessary) and register them under the given id.
- GET_query_outputitemvalues() None [source]¶
Return the output item values registered under the given id.
- GET_save_internalconditions() None [source]¶
Register the
StateSequence
andLogSequence
values of theHydPy
instance for the current simulation endpoint under the given id.
- GET_load_internalconditions() None [source]¶
Activate the
StateSequence
orLogSequence
values registered for the current simulation start point under the given id.When the simulation start point is identical with the initialisation time point, and you do not register alternative conditions manually, method
GET_load_internalconditions()
uses the “original” initial conditions of the current process (usually those of the conditions files of the respective HydPy project).
- POST_register_internalconditions() None [source]¶
Register the send internal conditions under the given id.
- GET_deregister_internalconditions() None [source]¶
Remove all internal condition directories registered under the given id.
- GET_query_internalconditions() None [source]¶
Get the internal conditions registered under the given id.
- POST_register_inputconditiondir() None [source]¶
Register the send input condition directory under the given id.
- GET_deregister_inputconditiondir() None [source]¶
Remove the input condition directory registered under the id.
- GET_query_inputconditiondir() None [source]¶
Return the input condition directory registered under the id.
- POST_register_outputconditiondir() None [source]¶
Register the send output condition directory under the given id.
- GET_deregister_outputconditiondir() None [source]¶
Remove the output condition directory registered under the id.
- GET_query_outputconditiondir() None [source]¶
Return the output condition directory registered under the id.
- GET_update_getitemvalues() None [source]¶
Register the current
GetItem
values under the given id.For
GetItem
objects observing time series, methodGET_update_getitemvalues()
registers only the values within the current simulation period.
- POST_register_seriesreaderdir() None [source]¶
Register the send series reader directory under the given id.
- GET_deregister_seriesreaderdir() None [source]¶
Remove the series reader directory registered under the id.
- GET_query_seriesreaderdir() None [source]¶
Return the series reader directory registered under the id.
- POST_register_serieswriterdir() None [source]¶
Register the send series writer directory under the given id.
- GET_deregister_serieswriterdir() None [source]¶
Remove the series writer directory registered under the id.
- GET_query_serieswriterdir() None [source]¶
Return the series writer directory registered under the id.
- GET_load_allseries() None [source]¶
Load the time series of all sequences selected for (non-jit) reading.
- GET_save_allseries() None [source]¶
Save the time series of all sequences selected for (non-jit) writing.
- POST_register_outputcontroldir() None [source]¶
Register the send output control directory under the given id.
- GET_deregister_outputcontroldir() None [source]¶
Remove the output control directory registered under the id.
- hydpy.exe.servertools.start_server(socket: int | str, projectname: str, xmlfilename: str, *, load_conditions: bool | str = True, load_series: bool | str = True, maxrequests: int | str = 5, debugging: Literal['enable', 'disable'] = 'disable') None [source]¶
Start the HydPy server using the given socket.
The folder with the given projectname must be available within the current working directory. The XML configuration file must be placed within the project folder unless xmlfilename is an absolute file path. The XML configuration file must be valid concerning the schema file HydPyConfigMultipleRuns.xsd (see class
ServerState
for further information).The
HydPyServer
allows for five still unhandled requests before refusing new connections by default. Use the optional maxrequests argument to increase this number (which might be necessary when parallelising optimisation or data assimilation):>>> from hydpy.core.testtools import prepare_full_example_1 >>> prepare_full_example_1() >>> command = ( ... "hyd.py start_server 8080 HydPy-H-Lahn multiple_runs_alpha.xml " ... "debugging=enable maxrequests=100") >>> from hydpy import run_subprocess, TestIO >>> with TestIO(): ... process = run_subprocess(command, blocking=False, verbose=False) ... result = run_subprocess("hyd.py await_server 8080 10", verbose=False)
>>> from urllib import request >>> command = "maxrequests = self.server.request_queue_size" >>> response = request.urlopen("http://127.0.0.1:8080/evaluate", ... data=bytes(command, encoding="utf-8")) >>> print(str(response.read(), encoding="utf-8")) maxrequests = 100
>>> _ = request.urlopen("http://127.0.0.1:8080/close_server") >>> process.kill() >>> _ = process.communicate()
Please see the documentation on method
POST_evaluate()
that explains the “debugging” argument.Note that function
start_server()
tries to read the “mime types” from a dictionary stored in the file mimetypes.txt available in subpackage conf and passes it as attribute extension_map to classHydPyServer
. The reason is to avoid the long computation time of functioninit()
of modulemimetypes
, usually called when defining class BaseHTTPRequestHandler of module http.server. If file mimetypes.txt does not exist or does not work for ,start_server()
callsinit()
as usual, (over)writes mimetypes.txt and tries to proceed as expected.
- hydpy.exe.servertools.await_server(port: int | str, seconds: float | str) None [source]¶
Block the current process until either the HydPy server is responding on the given port or the given number of seconds elapsed.
>>> from hydpy import run_subprocess, TestIO >>> with TestIO(): ... result = run_subprocess("hyd.py await_server 8080 0.1") Invoking hyd.py with arguments `await_server, 8080, 0.1` resulted in the following error: <urlopen error Waited for 0.1 seconds without response on port 8080.> ...
>>> from hydpy.core.testtools import prepare_full_example_1 >>> prepare_full_example_1() >>> with TestIO(): ... process = run_subprocess( ... "hyd.py start_server 8080 HydPy-H-Lahn multiple_runs.xml", ... blocking=False, verbose=False) ... result = run_subprocess("hyd.py await_server 8080 10", verbose=False)
>>> from urllib import request >>> _ = request.urlopen("http://127.0.0.1:8080/close_server") >>> process.kill() >>> _ = process.communicate()