18  Exploring Networks

This section guides you through navigating MIKE IO 1D’s Res1D object. You’ll learn how to access nodes, catchments, reaches, and individual gridpoints, along with their associated data and properties. This exploration is key to understanding the structure and results of your MIKE+ 1D models.

18.1 Nodes

Node data is accessible via the Res1D object, typically named res in our examples. The res.nodes attribute provides access to all nodes in your result file.

res.nodes
<ResultNodes> (119)
Quantities (1)
  • Water level (m)
Derived Quantities (3)
  • NodeFlooding
  • NodeWaterDepth
  • NodeWaterLevelAboveCritical
Tip

Notice that displaying res.nodes shows relevant metadata. This is true for all location objects.

The nodes object behaves like a Python dictionary, where node IDs are the keys and specific node objects are the values. You can access a specific node by its ID.

res.nodes["1"]
<Manhole: 1>
Attributes (8)
  • id: 1
  • type: Manhole
  • xcoord: -687934.6000976562
  • ycoord: -1056500.69921875
  • ground_level: 197.07000732421875
  • bottom_level: 195.0500030517578
  • critical_level: inf
  • diameter: 1.0
Quantities (1)
  • Water level (m)
Derived Quantities (3)
  • NodeFlooding
  • NodeWaterDepth
  • NodeWaterLevelAboveCritical

Each node object contains both dynamic quantities (time series results like water level) and static properties (like invert level).

To access a dynamic quantity:

res.nodes["1"].WaterLevel
<Quantity: Water level (m)>

As shown in the last section, dynamic quantities can be converted to a DataFrame with read(), or plotted with plot():

res.nodes["1"].WaterLevel.plot()

To access a static property:

res.nodes["1"].bottom_level
195.0500030517578

You can iterate through all nodes, similar to a Python dictionary. For example:

for node_id, node in res.nodes.items():
    if node.type == "Outlet":
        display(node)
<Outlet: 120>
Attributes (8)
  • id: 120
  • type: Outlet
  • xcoord: -687917.30078125
  • ycoord: -1056709.4993896484
  • ground_level: 194.0
  • bottom_level: 193.4499969482422
  • critical_level: None
  • diameter: None
Quantities (1)
  • Water level (m)
Derived Quantities (3)
  • NodeFlooding
  • NodeWaterDepth
  • NodeWaterLevelAboveCritical
<Outlet: Weir Outlet:119w1>
Attributes (8)
  • id: Weir Outlet:119w1
  • type: Outlet
  • xcoord: -687941.8002929688
  • ycoord: -1056652.400024414
  • ground_level: 197.1699981689453
  • bottom_level: 183.47999572753906
  • critical_level: None
  • diameter: None
Quantities (1)
  • Water level (m)
Derived Quantities (3)
  • NodeFlooding
  • NodeWaterDepth
  • NodeWaterLevelAboveCritical

18.2 Catchments

You handle catchments similarly to nodes, but they are typically in a separate result file, often from rainfall-runoff simulations.

res_rr.catchments
<ResultCatchments> (31)
Quantities (5)
  • Total Runoff (m^3/s)
  • Actual Rainfall (m/s)
  • Zink, Load, RR (kg/s)
  • Zink, Mass, Accumulated, RR (kg)
  • Zink, RR (mg/l)
Derived Quantities (0)

    Recall that all quantities in a network result file share the same time axis. Hydraulic simulations require finer time steps than hydrologic simulations, and thus a different time index. This is the main reason hydrologic and hydraulic results are stored in separate files.

    The catchments object also acts like a dictionary, with catchment IDs as keys.

    res_rr.catchments["100_16_16"]
    <Catchment: 100_16_16>
    Attributes (3)
    • id: 100_16_16
    • area: 22800.0
    • type: Kinematic Wave
    Quantities (5)
    • Total Runoff (m^3/s)
    • Actual Rainfall (m/s)
    • Zink, Load, RR (kg/s)
    • Zink, Mass, Accumulated, RR (kg)
    • Zink, RR (mg/l)
    Derived Quantities (0)

      Catchment objects also have dynamic quantities and static properties. For instance, TotalRunOff is a dynamic quantity, and area is a static property.

      res_rr.catchments["100_16_16"].TotalRunOff.plot()

      res_rr.catchments["100_16_16"].area
      22800.0

      Just like nodes, you can iterate through catchments like a Python dictionary. For example:

      for catchment_id, catchment in res_rr.catchments.items():
          if "28" in catchment_id:
              display(catchment)
      <Catchment: 28_6_6>
      Attributes (3)
      • id: 28_6_6
      • area: 3500.0
      • type: Kinematic Wave
      Quantities (5)
      • Total Runoff (m^3/s)
      • Actual Rainfall (m/s)
      • Zink, Load, RR (kg/s)
      • Zink, Mass, Accumulated, RR (kg)
      • Zink, RR (mg/l)
      Derived Quantities (0)
        <Catchment: 90_28_28>
        Attributes (3)
        • id: 90_28_28
        • area: 48000.0
        • type: Kinematic Wave
        Quantities (5)
        • Total Runoff (m^3/s)
        • Actual Rainfall (m/s)
        • Zink, Load, RR (kg/s)
        • Zink, Mass, Accumulated, RR (kg)
        • Zink, RR (mg/l)
        Derived Quantities (0)

          18.3 Reaches

          Reaches are accessed similarly to nodes and catchments. A key difference is that individual reach objects also contain gridpoints.

          res.reaches
          <ResultReaches> (118)
          Quantities (2)
          • Water level (m)
          • Discharge (m^3/s)
          Derived Quantities (6)
          • ReachAbsoluteDischarge
          • ReachFilling
          • ReachFlooding
          • ReachQQManning
          • ReachWaterDepth
          • ReachWaterLevelAboveCritical

          The reaches object is dictionary-like, with reach names as keys and specific reach objects as values.

          res.reaches["101l1"]
          <Reach: 101l1>
          Attributes (9)
          • name: 101l1
          • length: 66.4360966980845
          • start_chainage: 0.0
          • end_chainage: 66.4360966980845
          • n_gridpoints: 3
          • start_node: 101
          • end_node: 100
          • height: 0.30000001192092896
          • full_flow_discharge: 0.0809713208695954
          Quantities (2)
          • Water level (m)
          • Discharge (m^3/s)
          Derived Quantities (6)
          • ReachAbsoluteDischarge
          • ReachFilling
          • ReachFlooding
          • ReachQQManning
          • ReachWaterDepth
          • ReachWaterLevelAboveCritical

          Each reach object contains dynamic quantities and static properties.

          res.reaches["101l1"].WaterLevel.plot()

          Tip

          Notice that two water levels are plotted above, representing the start and end H-points of the reach.

          res.reaches["101l1"].n_gridpoints
          3

          Just like before, you can iterate through reaches like a Python dictionary. For example:

          # This may print multiple lines
          for reach_name, reach in res.reaches.items():
              if reach.start_node == "1":
                  display(reach)
          <Reach: 1l1>
          Attributes (9)
          • name: 1l1
          • length: 12.0784172521484
          • start_chainage: 0.0
          • end_chainage: 12.0784172521484
          • n_gridpoints: 3
          • start_node: 1
          • end_node: 16
          • height: 0.6000000238418579
          • full_flow_discharge: 0.7808684423624622
          Quantities (2)
          • Water level (m)
          • Discharge (m^3/s)
          Derived Quantities (6)
          • ReachAbsoluteDischarge
          • ReachFilling
          • ReachFlooding
          • ReachQQManning
          • ReachWaterDepth
          • ReachWaterLevelAboveCritical

          18.4 Gridpoints

          Specific reach objects are also both list- and dictionary-like, mapping to their constituent gridpoint objects. Gridpoints are the locations along a reach where results are calculated.

          Note

          Unlike Python dictionaries, reaches also support sequential indexing (e.g., [0] for the first gridpoint) for convenient gridpoint access.

          Access the first gridpoint of reach “101l1”:

          res.reaches["101l1"][0]
          <ResultGridPoint>
          Attributes (5)
          • reach_name: 101l1
          • chainage: 0.0
          • xcoord: -687859.5004882812
          • ycoord: -1056308.700012207
          • bottom_level: 195.92999267578125
          Quantities (1)
          • Water level (m)
          Derived Quantities (0)

            Access the last gridpoint of reach “101l1”:

            res.reaches["101l1"][-1]
            <ResultGridPoint>
            Attributes (5)
            • reach_name: 101l1
            • chainage: 66.4360966980845
            • xcoord: -687887.6008911133
            • ycoord: -1056368.9006958008
            • bottom_level: 195.44000244140625
            Quantities (1)
            • Water level (m)
            Derived Quantities (0)

              You can also access gridpoints by their chainage value. Chainage can be a float or a string.

              res.reaches["100l1"]['23.841']
              <ResultGridPoint>
              Attributes (5)
              • reach_name: 100l1
              • chainage: 23.8413574216414
              • xcoord: -687897.8000488281
              • ycoord: -1056390.4503479004
              • bottom_level: 195.0500030517578
              Quantities (1)
              • Discharge (m^3/s)
              Derived Quantities (0)

                Notice that each gridpoint has its own dynamic quantities and static properties.

                Access WaterLevel at the first gridpoint:

                res.reaches["100l1"][0].WaterLevel.plot()

                Access Discharge at the second gridpoint:

                res.reaches["100l1"][1].Discharge.plot()

                Note

                Gridpoint quantities can vary along a reach. For example, WaterLevel is typically available at H-points (calculation points), while Discharge is available at Q-points (flow points, often at gridpoint centers or structures). Refer to the MIKE+ documentation for details on H-points and Q-points.

                Access the chainage static property of a gridpoint:

                res.reaches["100l1"][-1].chainage
                47.6827148432828

                Just like before, you can iterate through reaches like a Python dictionary. The keys are the gridpoint chainage along the reach. For example:

                for chainage, gridpoint in res.reaches["100l1"].items():
                    print(f"Bottom level at chainage {chainage} is {gridpoint.bottom_level}")
                Bottom level at chainage 0.000 is 195.44000244140625
                Bottom level at chainage 23.841 is 195.0500030517578
                Bottom level at chainage 47.683 is 194.66000366210938

                18.5 Example - Dynamic Autocompletion

                Dynamic autocompletion in environments like Jupyter or VS Code significantly aids in exploring these objects. Watch this video to see it in action.