Deltares / Ribasim

Water resources modeling
https://ribasim.org/
MIT License
39 stars 5 forks source link

BMI User Demand: priorities are unknown to coupler #1296

Closed Huite closed 6 months ago

Huite commented 7 months ago

The BMI will offer a demand array which is sized n_priority * n_user, with multiple demands for a single water user.

For a coupled model, we would like MetaSWAP to set the irrigation demand. However, the coupler has no way of setting this in the right column (Python) / row (Julia) of the demand array, since it does not know which priority the irrigation has.

For now, I suggest the coupler just sets the irrigation demand with first priority. But we should think about whether either the priority should also be communicated via BMI, or whether a single external demand is offered instead, which is then distributed along the priorities in Ribasim internally.

Huite commented 7 months ago

Another note: the current implementation only takes values from the demand array in case of static forcing. This means the test model should set a UserDemand / static table, not a UserDemand / time table.

See: https://github.com/Deltares/Ribasim/blob/503443d5f7b36de73cd1c4f6fe6b4cf265af62a2/core/src/allocation_optim.jl#L116

SouthEndMusic commented 7 months ago

I think exposing the priorities array via BMI is the easiest and most generic solution.

Huite commented 7 months ago

But a single user demand node might have multiple priorities, right? Do we also return a n_user * n_priority sized array for priorities, with fill values for the non-used priorities. And then assert that only a single priority is set for MetaSWAP users in the coupler pre-processing?

SouthEndMusic commented 7 months ago

But a single user demand node might have multiple priorities, right? Do we also return a n_user * n_priority sized array for priorities, with fill values for the non-used priorities. And then assert that only a single priority is set for MetaSWAP users in the coupler pre-processing?

I don't understand what you mean, so let me just explain some things. The priorities vector is global, i.e. it gives all priorities that appear somewhere in the model input. So if MetaSWAP knows at which priority it wants to set a demand, it just has to do a searchsorted in the priorities vector to know the index in the demand and allocated matrices in the priority dimension. For the other dimension it also has to know all UserDemand IDs.

Huite commented 7 months ago

Coming from the talk we just had.

The current setup only works with a static user demand for the coupled nodes. Proposal is to let Ribasim determine the priority. So a single demand with a single priority is defined per coupled user demand node. Then the coupler infers the priority to set from this initial value.

Something along these lines:

self.user_demand =  self.ribasim.get_value_ptr("user.demand").reshape((n_user, n_priority))
self.user_demand_flag = self.user_demand > 0
not_coupled = ~np.isin(np.arange(n_user), coupled_user_demand_indices)
self.user_demand_flag[not_coupled, :] = False

multiple_priorities = np.flatnonzero(self.user_demand_flag.sum(axis=1) > 1)
if multiple_priorities:
     raise ValueError(f"Multiple priorities detected for coupled UserDemand nodes at indices: {multiple_priorities}")

...

# when setting demand
self.user_demand[self.user_demand_flag] = svat_demand

In the pre-processing, we should also check for multiple priorities. In fact, only a single row should be given in general:

df = ribasim_model.user_demand.static.df
coupled_df = df.loc[df["node_id"].isin(coupled_user_demand_node_ids)]
duplicated_ids = coupled_df["node_id"].loc[coupled_df["node_id"].duplicated()]
if duplicated_ids:
    raise ValueError(
        f"Found UserDemand to couple with multiple demands defined: {duplicated_ids}\n"
        "Coupled UserDemand nodes may only have a single demand and priority value."
)