Deltares Software Days 2022: A simple hydrolib-core demo¶
Hydrolib-core is a pip-installable python package. It consists of wrappers around the D-HYDRO model files.
Classes in Hydrolib-core are hierarchically organized by file type, and can be accessed via hydrolib.core.io.<filetype>.models.
File objects in Hydrolib-core reflect the same structure as the file contents.
In this demo, we will load a Flow FM model, make some changes to the model and save it again.
💡 Tips for working in the Jupyter Notebook:
- Tab: Auto-complete on code
- Ctrl+Enter: Run cell
- Shift+Enter: Run cell and move to next cell
- Ctrl+Shift+P: Open command palette
Step 0) Importing the modules and setting the path variables¶
from hydrolib.core.dflowfm import (
FMModel,
Lateral,
Weir, FlowDirection
)
from pathlib import Path
root = Path.cwd()
demo_data_folder = root / "data"
work_folder = root / "work"
Step 1) Loading a Flow FM model¶
mdu_file_path = demo_data_folder / "FlowFM.mdu"
model = FMModel(filepath=mdu_file_path)
print(f"Loaded the model from {model.filepath}")
Loaded the model from C:\gdrive\algorithms\deltares\HYDROLIB-core\docs\tutorials\dsd_2022_demo\data\FlowFM.mdu
Step 2) Inspecting the model¶
We can see the hierarchy tree of the model by calling the following function:
model.show_tree()
C:\gdrive\algorithms\deltares\HYDROLIB-core\docs\tutorials\dsd_2022_demo\data\FlowFM.mdu
Geometry
∟ FlowFM_net.nc
∟ structures.ini
∟ initialFields.ini
∟ roughness-Channels.ini
∟ roughness-Main.ini
∟ roughness-Sewer.ini
∟ roughness-FloodPlain1.ini
∟ roughness-FloodPlain2.ini
∟ crsdef.ini
∟ crsloc.ini
∟ nodeFile.ini
ExternalForcing
∟ FlowFM_bnd.ext
∟ Lateral
∟ FlowFM_lateral_sources.bc
∟ Lateral
∟ FlowFM_lateral_sources.bc
∟ Lateral
∟ Lateral
∟ Lateral
∟ FlowFM_lateral_sources.bc
∟ Lateral
∟ FlowFM_lateral_sources.bc
∟ Lateral
∟ FlowFM_lateral_sources.bc
Output
∟ obsFile1D_obs.ini
We can also inspect sub-parts of the model, like the structures.
Note that a model can have multiple structure files, but this model only has one, so we will get the first and only one.
assert model.geometry.structurefile is not None
structure_file = model.geometry.structurefile[0]
# Show the list of all the structures
print(f"Number of structures: {len(structure_file.structure)}")
print(structure_file.structure)
Number of structures: 6 [Pump(comments=Comments(id=None, name=None, polylinefile='*.pli; Polyline geometry definition for 2D structure.', branchid=None, chainage=None, numcoordinates='Number of values in xCoordinates and yCoordinates', xcoordinates='x-coordinates of the location of the structure. (number of values = numCoordinates)', ycoordinates='y-coordinates of the location of the structure. (number of values = numCoordinates)', orientation=None, startleveldeliveryside=None, stopleveldeliveryside=None, startlevelsuctionside=None, head=None, controlside=None, capacity=None, stoplevelsuctionside=None, reductionfactor=None, numstages=None, numreductionlevels=None, type=None), id='636', name='De Poale 34 m3/min', type='pump', polylinefile=None, branchid='164', chainage=610.198737, numcoordinates=None, xcoordinates=None, ycoordinates=None, orientation='positive', controlside='suctionSide', numstages=1, capacity=0.567, startlevelsuctionside=[-0.1], stoplevelsuctionside=[-0.2], startleveldeliveryside=[0.0], stopleveldeliveryside=[0.0], numreductionlevels=1, head=[0.0], reductionfactor=[1.0]), Orifice(comments=Comments(id=None, name='Given name in the user interface.', polylinefile='*.pli; Polyline geometry definition for 2D structure.', branchid=None, chainage=None, numcoordinates='Number of values in xCoordinates and yCoordinates', xcoordinates='x-coordinates of the location of the structure. (number of values = numCoordinates)', ycoordinates='y-coordinates of the location of the structure. (number of values = numCoordinates)', allowedflowdir=None, uselimitflowneg=None, crestwidth=None, usevelocityheight=None, corrcoeff=None, uselimitflowpos=None, crestlevel=None, type=None, gateloweredgelevel=None), id='8_1476', name='id', type='orifice', polylinefile=None, branchid='457', chainage=211.274378, numcoordinates=None, xcoordinates=None, ycoordinates=None, allowedflowdir='positive', crestlevel=-1.0, crestwidth=1.15, gateloweredgelevel=3.0, corrcoeff=0.63, usevelocityheight=True, uselimitflowpos=False, limitflowpos=None, uselimitflowneg=False, limitflowneg=None), Pump(comments=Comments(id=None, name='Given name in the user interface.', polylinefile='*.pli; Polyline geometry definition for 2D structure.', branchid=None, chainage=None, numcoordinates='Number of values in xCoordinates and yCoordinates', xcoordinates='x-coordinates of the location of the structure. (number of values = numCoordinates)', ycoordinates='y-coordinates of the location of the structure. (number of values = numCoordinates)', orientation=None, startleveldeliveryside=None, stopleveldeliveryside=None, startlevelsuctionside=None, head=None, controlside=None, capacity=None, stoplevelsuctionside=None, reductionfactor=None, numstages=None, numreductionlevels=None, type=None), id='DV_8_216', name='id', type='pump', polylinefile=None, branchid='459', chainage=174.381882, numcoordinates=None, xcoordinates=None, ycoordinates=None, orientation='positive', controlside='suctionSide', numstages=1, capacity=0.05, startlevelsuctionside=[0.4], stoplevelsuctionside=[0.0], startleveldeliveryside=[0.0], stopleveldeliveryside=[0.0], numreductionlevels=1, head=[0.0], reductionfactor=[1.0]), Compound(comments=Comments(id=None, name=None, polylinefile='*.pli; Polyline geometry definition for 2D structure.', branchid='Branch on which the structure is located.', chainage='Chainage on the branch (m).', numcoordinates='Number of values in xCoordinates and yCoordinates', xcoordinates='x-coordinates of the location of the structure. (number of values = numCoordinates)', ycoordinates='y-coordinates of the location of the structure. (number of values = numCoordinates)', numstructures=None, structureids=None, type=None), id='636 [compound]', name='De Poale 34 m3/min', type='compound', polylinefile=None, branchid=None, chainage=None, numcoordinates=None, xcoordinates=None, ycoordinates=None, numstructures=1, structureids=['636']), Compound(comments=Comments(id=None, name='Given name in the user interface.', polylinefile='*.pli; Polyline geometry definition for 2D structure.', branchid='Branch on which the structure is located.', chainage='Chainage on the branch (m).', numcoordinates='Number of values in xCoordinates and yCoordinates', xcoordinates='x-coordinates of the location of the structure. (number of values = numCoordinates)', ycoordinates='y-coordinates of the location of the structure. (number of values = numCoordinates)', numstructures=None, structureids=None, type=None), id='8_1476 [compound]', name='id', type='compound', polylinefile=None, branchid=None, chainage=None, numcoordinates=None, xcoordinates=None, ycoordinates=None, numstructures=1, structureids=['8_1476']), Compound(comments=Comments(id=None, name='Given name in the user interface.', polylinefile='*.pli; Polyline geometry definition for 2D structure.', branchid='Branch on which the structure is located.', chainage='Chainage on the branch (m).', numcoordinates='Number of values in xCoordinates and yCoordinates', xcoordinates='x-coordinates of the location of the structure. (number of values = numCoordinates)', ycoordinates='y-coordinates of the location of the structure. (number of values = numCoordinates)', numstructures=None, structureids=None, type=None), id='DV_8_216 [compound]', name='id', type='compound', polylinefile=None, branchid=None, chainage=None, numcoordinates=None, xcoordinates=None, ycoordinates=None, numstructures=1, structureids=['DV_8_216'])]
Step 3) Adjusting the model¶
a) Creating a new weir and adding it to the structures¶
# Create a new weir.
weir = Weir(
id="DV_1471",
branchId="458",
chainage=106.277467,
allowedFlowDir=FlowDirection.both,
crestLevel=0.400,
crestWidth=40.000,
corrCoeff=1.000,
useVelocityHeight=True
)
# It is also possible to create objects with a dictionary.
weir_data = {
"id": "DV_1471",
"branchId": "458",
"chainage": 106.277467,
"allowedFlowDir": FlowDirection.both,
"crestLevel": 0.400,
"crestWidth": 40.000,
"corrCoeff": 1.000,
"useVelocityHeight": True
}
weir = Weir(**weir_data)
# Add the weir to the model
structure_file.structure.append(weir)
print(f"Number of structures: {len(structure_file.structure)}")
Number of structures: 7
b) Adjusting some parameters of the model¶
model.physics.backgroundsalinity = 30 # [ppt]
model.physics.backgroundwatertemperature = 6 # [°C]
model.time.dtuser = 900 # [s]
Hydrolib-core offers instant validation of objects that are created, either by loading them from file or by manually creating them. This makes it easier for users to create valid models.
A lateral, like many other objects, should have a valid location specification, specified by:
- A node id,
- A branch id with a chainage, or
- Coordinates
Let's create an invalid Lateral. We "forget" to specify a chainage.
try:
lateral = Lateral(
id="lateral_1",
name="lateral_1",
branchid="branch_a",
discharge="realtime"
)
except ValueError as error:
print(error)
1 validation error for Lateral lateral_1 -> __root__ nodeId or branchId and chainage or xCoordinates, yCoordinates and numCoordinates should be provided (type=value_error)
Now let's create a valid lateral.
lateral = Lateral(
id="lateral_1",
name="lateral_1",
branchid="branch_a",
chainage=100,
discharge="realtime"
)
print("Lateral with branch id and chainage is correct!")
Lateral with branch id and chainage is correct!
Step 5) Saving the model¶
If we save the model now, it will overwrite the current model files. So let's save it in a different location.
print(f"The original model is located at {model.filepath}")
save_mdu_file_path = work_folder / "save_model" / "FlowFM.mdu"
model.filepath = save_mdu_file_path
# Set recurse to True. If it is False, only the MDU file will be saved.
model.save(recurse=True)
print(f"The saved model is located at {model.filepath}")
The original model is located at C:\gdrive\algorithms\deltares\HYDROLIB-core\docs\tutorials\dsd_2022_demo\data\FlowFM.mdu The saved model is located at C:\gdrive\algorithms\deltares\HYDROLIB-core\docs\tutorials\dsd_2022_demo\work\save_model\FlowFM.mdu
Saving only a sub-part of the model¶
It is also possible to save individual child model files, such as the cross section definition file.
crossdef_file = model.geometry.crossdeffile
assert crossdef_file is not None
crossdef_file.filepath = work_folder / "save_crossdef" / "crsdef.ini"
crossdef_file.save()
print(f"The saved cross section definition model is located at {crossdef_file.filepath}")
The saved cross section definition model is located at C:\gdrive\algorithms\deltares\HYDROLIB-core\docs\tutorials\dsd_2022_demo\work\save_crossdef\crsdef.ini