Skip to content

Parameters guide

SWMManywhere is a deliberately highly parameterised workflow, with the goal of enabling users to create a diverse range of UDMs. This guide is to explain the logic of the implemented parameters and how to customise them, as what each parameter does is highly specific to the graphfcn that uses it. Instead, to understand specific parameter purposes, you can view all available parameters at the API.

Using parameters

Let's look at a parameter group, which is a group of parameters related to identifying outfall locations.

Source code in swmmanywhere/parameters.py
@register_parameter_group(name="outfall_derivation")
class OutfallDerivation(BaseModel):
    """Parameters for outfall derivation."""

    method: str = Field(
        default="separate",
        unit="-",
        description="""Method to derive outfall locations, 
            can be 'separate' or 'withtopo'.""",
    )

    river_buffer_distance: float = Field(
        default=150.0,
        ge=10.0,
        le=500.0,
        unit="m",
        description="Buffer distance to link rivers to streets.",
    )

    outfall_length: float = Field(
        default=40.0,
        ge=0.0,
        le=600.0,
        unit="-",
        description="Weight to discourage street drainage into river buffers.",
    )

We can see here three related parameters and relevant metadata, grouped together in a pydantic.BaseModel object. Parameters in SWMManywhere are grouped together because graphfcns that need one of them tend to need the others. Let's look at identify_outfalls, which needs these parameters.

Source code in swmmanywhere/graphfcns/outfall_graphfcns.py
@register_graphfcn
class identify_outfalls(
    BaseGraphFunction,
    required_edge_attributes=["length", "edge_type"],
    required_node_attributes=["x", "y", "surface_elevation"],
):
    """identify_outfalls class."""

    def __call__(
        self, G: nx.Graph, outfall_derivation: parameters.OutfallDerivation, **kwargs
    ) -> nx.Graph:
        """Identify outfalls in a combined river-street graph.

        This function identifies outfalls in a combined river-street graph. An
        outfall is a node that is connected to a river and a street. Each street
        node is paired with the nearest river node provided that it is within
        a distance of outfall_derivation.river_buffer_distance - this provides a
        large set of plausible outfalls. If there are no plausible outfalls for an
        entire subgraph, then a dummy river node is created and the lowest
        elevation node is paired with it. Any street->river/outfall link is given
        a `weight` and `length` of outfall_derivation.outfall_length, this is to
        ensure some penalty on the total number of outfalls selected.

        Two methods are available for determining which plausible outfalls to
        retain:

        - `withtopo`: all plausible outfalls are retained, connected to a single
        'waste' node and assumed to be later identified as part of the
        `derive_topology` graphfcn.

        - `separate`: the retained outfalls are those that are selected as part
        of the minimum spanning tree (MST) of the combined street-river graph.
        This method can be temporamental because the MST is undirected, because
        rivers are inherently directed unusual outfall locations may be retained.

        Args:
            G (nx.Graph): A graph
            outfall_derivation (parameters.OutfallDerivation): An OutfallDerivation
                parameter object
            **kwargs: Additional keyword arguments are ignored.

        Returns:
            G (nx.Graph): A graph
        """
        G = G.copy()

        river_points, street_points = _get_points(G)

        G_ = _pair_rivers(
            G,
            river_points,
            street_points,
            outfall_derivation.river_buffer_distance,
            outfall_derivation.outfall_length,
        )

        # Set the length of the river edges to 0 - from a design perspective
        # once water is in the river we don't care about the length - since it
        # costs nothing
        for _, _, d in G_.edges(data=True):
            if d["edge_type"] == "river":
                d["length"] = 0
                d["weight"] = 0

        # Add edges from the river nodes to a waste node
        G_ = _root_nodes(G_)

        if outfall_derivation.method == "withtopo":
            # The outfalls can be derived as part of the shortest path calculations
            return G_
        elif outfall_derivation.method == "separate":
            return _connect_mst_outfalls(G_, G)
        else:
            raise ValueError(f"Unknown method {outfall_derivation.method}")

When calling iterate_graphfcns, for more information see here, SWMManywhere will automatically provide any parameters that have been registered to any graphfcn.

Registering parameters

When you create a new parameter, it will need to belong to an existing or new parameter group.

Creating a new parameter group(s)

You create a new module(s) that can contain multiple parameter groups. See below as a template of such amodule.

from __future__ import annotations

from swmmanywhere import parameters


@parameters.register_parameter_group(name="new_params")
class new_params(parameters.BaseModel):
    """New parameters."""

    new_param: int = parameters.Field(
        default=1,
        ge=0,
        le=10,
        unit="-",
        description="A new parameter.",
    )

Adjust config file

We will add the required lines to the minimum viable config template.

base_dir: /path/to/base/directory
project: my_first_swmm
bbox: [1.52740,42.50524,1.54273,42.51259]
custom_parameter_modules: 
  - /path/to/custom_parameters.py

Now when we run our config file, these parameters will be registered and any custom graphfcns will have access to them.

Changing existing parameter groups

There may be cases where you want to change existing parameter groups, such as introducing new weights to the calculate_weights step so that they are minimized during the shortest path optimization. In this example, we want the TopologyDerivation group to include some new parameters. We can do this in a similar way to above, but being mindful to inherit from TopologyDerivation rather than BaseModel:

from swmmanywhere.parameters import register_parameter_group, TopologyDerivation, Field

@register_parameter_group("topology_derivation")
class NewTopologyDerivation(TopologyDerivation):
    new_weight_scaling: float = Field(
        default=1,
        le=1,
        ge=0,
    )
    new_weight_exponent: float = Field(
        default=1,
        le=2,
        ge=0,
    )

Now the calculate_weights function will have access to these new weighting parameters, as well as existing ones.

Note, in this specific example of adding custom weights, you will also have to:

  • Update the weights parameter in your config file, for example:
parameter_overrides:
  topology_derviation:
    weights:
      - new_weight
      - length