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 5 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,324 changes: 3,324 additions & 0 deletions pysim-nutrients.ipynb
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is a useful addition!

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
Copy link
Collaborator

Choose a reason for hiding this comment

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

I like the greywater and blackwater type. In the future this may be extended to light grey and dark grey, blue, green and yellow water :-)

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
69 changes: 65 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,65 @@ 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():
multiplier = nutrient_data[enduse][nutrient]
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
57 changes: 57 additions & 0 deletions pysimdeum/data/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.

for the Wc, I can imagine you would have different values for a full flush (faeces) and half flush (urine). For the dishwasher and washing machine you may also want to discern between first discharge (dirt and grease), second discharge (soap residu added) and 3rd + 4th discharge (mainly water). This may be too much detail for now, but would be nice to at least have a simple solution in a later version. We may want to discuss this next week

Copy link
Collaborator Author

@val-ismaili val-ismaili Mar 5, 2025

Choose a reason for hiding this comment

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

This makes sense. Wc we think we've got the data to do that quite easily. For WashingMachine and Dishwasher we're looking around. I've set up an issue about this which we'll address next week so we can get this PR closed. #76

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Nutrient statistics per enduse

[Shower]
n = 0.1
p = 0.2
cod = 0.3
bod5 = 0.4
ss = 0.5
amm = 0.6

[Bathtub]
n = 0.1
p = 0.2
cod = 0.3
bod5 = 0.4
ss = 0.5
amm = 0.6

[Wc]
n = 0.1
p = 0.2
cod = 0.3
bod5 = 0.4
ss = 0.5
amm = 0.6

[KitchenTap]
n = 0.1
p = 0.2
cod = 0.3
bod5 = 0.4
ss = 0.5
amm = 0.6

[BathroomTap]
n = 0.1
p = 0.2
cod = 0.3
bod5 = 0.4
ss = 0.5
amm = 0.6

[WashingMachine]
n = 0.1
p = 0.2
cod = 0.3
bod5 = 0.4
ss = 0.5
amm = 0.6

[Dishwasher]
n = 0.1
p = 0.2
cod = 0.3
bod5 = 0.4
ss = 0.5
amm = 0.6