Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nutrient concentrations #72

Merged
merged 8 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3,036 changes: 3,036 additions & 0 deletions examples/pysim-nutrients.ipynb

Large diffs are not rendered by default.

24 changes: 18 additions & 6 deletions pysimdeum/core/end_use.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ def __post_init__(self):
**kwargs: keyword arguments for super classes.
"""
self.name = "Bathtub"
self.wastewater_type = "greywater"

def fct_frequency(self, age=None):
"""Random function computing the frequency of use for the Bathtub end-use class.
Expand Down Expand Up @@ -214,6 +215,7 @@ class BathroomTap(EndUse):

def __post_init__(self):
self.name = "BathroomTap"
self.wastewater_type = "greywater"

def fct_frequency(self):

Expand Down Expand Up @@ -302,6 +304,7 @@ class Dishwasher(EndUse):

def __post_init__(self):
self.name = "Dishwasher"
self.wastewater_type = "blackwater"

def fct_frequency(self, numusers=None):

Expand All @@ -326,7 +329,7 @@ def calculate_discharge(self, discharge, start, j, ind_enduse, pattern_num, day_
elif ((day_num + 1) == total_days) and (discharge_time > end_of_day):
pass
else:
discharge[discharge_time, j, ind_enduse, pattern_num, 0] = discharge_pattern[time]
discharge[discharge_time, j, ind_enduse, pattern_num, 1] = discharge_pattern[time]

return discharge

Expand Down Expand Up @@ -381,6 +384,7 @@ class KitchenTap(EndUse):

def __post_init__(self):
self.name = "KitchenTap"
self.wastewater_type = "blackwater"

def fct_frequency(self, numusers=None):

Expand Down Expand Up @@ -451,10 +455,9 @@ def calculate_discharge(self, discharge, start, duration, intensity, temperature
end = int(start + discharge_duration)
# check if subtype = consumption (drinking), if so the discharge flow rate is set to 0
if self.subtype == 'consumption':
discharge[start:end, j, ind_enduse, pattern_num, 0] = 0
discharge[start:end, j, ind_enduse, pattern_num, 1] = 0
else:
discharge[start:end, j, ind_enduse, pattern_num, 0] = discharge_flow_rate
#discharge[start:end, j, ind_enduse, pattern_num, 0] = discharge_flow_rate
discharge[start:end, j, ind_enduse, pattern_num, 1] = discharge_flow_rate
remaining_water -= discharge_flow_rate * discharge_duration
start = end

Expand Down Expand Up @@ -570,6 +573,7 @@ class Shower(EndUse):

def __post_init__(self):
self.name = "Shower"
self.wastewater_type = "greywater"

def fct_frequency(self, age=None):

Expand Down Expand Up @@ -655,17 +659,20 @@ class NormalShower(Shower):

def __post_init__(self):
self.name = "NormalShower"
self.wastewater_type = "greywater"

class FancyShower(Shower):

def __post_init__(self):
self.name = "FancyShower"
self.wastewater_type = "greywater"


class WashingMachine(EndUse):

def __post_init__(self):
self.name = "WashingMachine"
self.wastewater_type = "blackwater"

def fct_frequency(self, numusers=None):

Expand All @@ -691,7 +698,7 @@ def calculate_discharge(self, discharge, start, j, ind_enduse, pattern_num, day_
elif ((day_num + 1) == total_days) and (discharge_time > end_of_day):
pass
else:
discharge[discharge_time, j, ind_enduse, pattern_num, 0] = discharge_pattern[time]
discharge[discharge_time, j, ind_enduse, pattern_num, 1] = discharge_pattern[time]

return discharge

Expand Down Expand Up @@ -748,6 +755,7 @@ class Wc(EndUse):

def __post_init__(self):
self.name = "Wc"
self.wastewater_type = "blackwater"


def fct_frequency(self, age=None, gender=None):
Expand Down Expand Up @@ -790,7 +798,7 @@ def calculate_discharge(self, discharge, start, duration, intensity, temperature
while incoming_water > 0:
discharge_duration = incoming_water / discharge_flow_rate
start = int(end - discharge_duration)
discharge[start:end, j, ind_enduse, pattern_num, 0] = discharge_flow_rate
discharge[start:end, j, ind_enduse, pattern_num, 1] = discharge_flow_rate
incoming_water -= discharge_flow_rate * discharge_duration
end = start

Expand Down Expand Up @@ -831,21 +839,25 @@ class WcNormal(Wc):

def __post_init__(self):
self.name = 'WcNormal'
self.wastewater_type = "blackwater"

@dataclass
class WcNormalSave(Wc):

def __post_init__(self):
self.name = "WcNormalSave"
self.wastewater_type = "blackwater"

@dataclass
class WcNew(Wc):

def __post_init__(self):
self.name = "WcNew"
self.wastewater_type = "blackwater"

@dataclass
class WcNewSave(Wc):

def __post_init__(self):
self.name = "WcNewSave"
self.wastewater_type = "blackwater"
2 changes: 1 addition & 1 deletion pysimdeum/core/house.py
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ def simulate(self, date=None, duration='1 day', num_patterns=1, simulate_dischar
number_of_days = int(timedelta/pd.to_timedelta('1 day'))

if simulate_discharge:
dischargetype = ['discharge']
dischargetype = ['greywater', 'blackwater']
discharge = np.zeros((len(time), len(users), len(enduse), num_patterns, len(dischargetype)))
else:
discharge = None
Expand Down
71 changes: 67 additions & 4 deletions pysimdeum/core/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
import numpy as np
from dataclasses import dataclass
from typing import Union
import toml
import xarray as xr
import os
from pysimdeum.data import DATA_DIR


def chooser(data: Union[pd.Series, pd.DataFrame], myproperty: str=''):
Expand Down Expand Up @@ -195,12 +199,10 @@ def handle_discharge_spillover(discharge, discharge_pattern, time, discharge_tim
"""
if discharge_time >= (total_days * 24 * 60 * 60):
spillover_time = discharge_time - end_of_day
discharge[spillover_time, j, ind_enduse, pattern_num, 0] = discharge_pattern[time]
discharge[spillover_time, j, ind_enduse, pattern_num, 1] = discharge_pattern[time]
else:
# Continue to the next day
discharge[discharge_time, j, ind_enduse, pattern_num, 0] = discharge_pattern[time]

#discharge[spillover_time, j, ind_enduse, pattern_num, 0] = discharge_pattern[time]
discharge[discharge_time, j, ind_enduse, pattern_num, 1] = discharge_pattern[time]

return discharge

Expand Down Expand Up @@ -354,6 +356,67 @@ def complex_discharge_pattern(config, enduse_pattern, resolution='1s'):
return discharge_pattern


def process_discharge_nutrients(discharge):
"""Process discharge data and add nutrient concentrations based on values from a .toml file.

This function reads nutrient multipliers from a TOML file, converts the discharge data from an
xarray.DataArray to a pandas DataFrame, calculates the nutrient concentrations based on the
multipliers, and adds the nutrient data back to an xarray.Dataset.

Args:
discharge (xr.DataArray): The discharge data.

Returns:
xr.Dataset: The updated xarray.Dataset containing the discharge data and the nutrient concentrations.
"""

toml_file_path = os.path.join(DATA_DIR, 'NL', 'ww_nutrients.toml')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hard coded 'NL' will need an update with the roll out of PR 73, as well as a new config file

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, we'll address that in your PR when pulling from master #77


# Read the .toml file
nutrient_data = toml.load(toml_file_path)

# Convert a xarray.DataArray to a pd.DataFrame
df = discharge.to_dataframe(name='flow').reset_index()

# list of nutrient types
nutrients = ['n', 'p', 'cod', 'bod5', 'ss', 'amm']

# Add new columns for each nutrient initialised to zero
for nutrient in nutrients:
df[nutrient] = 0.0

# Set the values for each nutrient based on the multipliers from the TOML file
for nutrient in nutrients:
for enduse in nutrient_data.keys():
low = nutrient_data[enduse][nutrient]['low']
high = nutrient_data[enduse][nutrient]['high']
multiplier = np.random.uniform(low, high) # sample from uniform distribution
df.loc[df['enduse'] == enduse, nutrient] = df['flow'] * multiplier

# Create an xarray.Dataset and add the discharge DataArray to it
ds = xr.Dataset({'discharge': discharge})

# Add the pandas DataFrame to the xarray.Dataset as a new variable
ds['df'] = (('index', 'columns'), df.values)
ds['df_index'] = ('index', df.index)
ds['df_columns'] = ('columns', df.columns)

return ds


def dataset_to_df(ds):
"""Convert an xarray.Dataset to a pd.DataFrame.

Args:
ds (xr.Dataset): The input xarray.Dataset containing the 'df', 'df_index', and 'df_columns' variables.

Returns:
pd.DataFrame: The converted pandas DataFrame.
"""
df_from_ds = pd.DataFrame(data=ds['df'].values, index=ds['df_index'].values, columns=ds['df_columns'].values)

return df_from_ds

@dataclass
class Base:
"""Base class of pysimdeum for generating objects.
Expand Down
141 changes: 141 additions & 0 deletions pysimdeum/data/NL/ww_nutrients.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# Nutrient statistics per enduse

[Shower]
[Shower.n]
low = 0.01
high = 0.017
[Shower.p]
low = 0.002
high = 0.002
[Shower.cod]
low = 0.35
high = 0.424
[Shower.bod5]
low = 0.17
high = 0.25
[Shower.ss]
low = 0.076
high = 0.12
[Shower.amm]
low = 0.0015
high = 0.002

[Bathtub]
[Bathtub.n]
low = 0.006
high = 0.017
[Bathtub.p]
low = 0.002
high = 0.002
[Bathtub.cod]
low = 0.282
high = 0.424
[Bathtub.bod5]
low = 0.17
high = 0.25
[Bathtub.ss]
low = 0.076
high = 0.12
[Bathtub.amm]
low = 0.0013
high = 0.002

[Wc]
[Wc.n]
low = 0.3
high = 0.3
[Wc.p]
low = 0.0361
high = 0.0361
[Wc.cod]
low = 0.751
high = 0.751
[Wc.bod5]
low = 0.4
high = 0.4
[Wc.ss]
low = 0.391
high = 0.391
[Wc.amm]
low = 0.04
high = 0.04

[KitchenTap]
[KitchenTap.n]
low = 0.04
high = 0.068
[KitchenTap.p]
low = 0.013
high = 0.074
[KitchenTap.cod]
low = 0.936
high = 1.38
[KitchenTap.bod5]
low = 0.536
high = 0.8
[KitchenTap.ss]
low = 0.72
high = 0.72
[KitchenTap.amm]
low = 0.0042
high = 0.006

[BathroomTap]
[BathroomTap.n]
low = 0.012
high = 0.012
[BathroomTap.p]
low = 0.028
high = 0.049
[BathroomTap.cod]
low = 0.383
high = 0.433
[BathroomTap.bod5]
low = 0.148
high = 0.252
[BathroomTap.ss]
low = 0.04
high = 0.15
[BathroomTap.amm]
low = 0.00016
high = 0.00115

[WashingMachine]
[WashingMachine.n]
low = 0.013
high = 0.02
[WashingMachine.p]
low = 0.032
high = 0.171
[WashingMachine.cod]
low = 0.725
high = 0.725
[WashingMachine.bod5]
low = 0.11
high = 0.662
[WashingMachine.ss]
low = 0.09
high = 0.19
[WashingMachine.amm]
low = 0.013
high = 0.02

[Dishwasher]
[Dishwasher.n]
low = 0.04
high = 0.05
[Dishwasher.p]
low = 0.068
high = 0.068
[Dishwasher.cod]
low = 1
high = 1
[Dishwasher.bod5]
low = 0.11
high = 1.04
[Dishwasher.ss]
low = 0.09
high = 0.44
[Dishwasher.amm]
low = 0.0045
high = 0.05