Skip to content

Commit c3a0740

Browse files
committed
SDG-265: implement calc-netcd command for Net Channel Depletion calculation
1 parent 6070b98 commit c3a0740

File tree

4 files changed

+103
-4
lines changed

4 files changed

+103
-4
lines changed

environment.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ dependencies:
2020
- numba
2121
- diskcache
2222
- tabulate
23+
- decorator
2324
# --- viz extras (pyproject.toml [project.optional-dependencies.viz]) ---
2425
- geopandas
2526
- shapely
@@ -29,5 +30,5 @@ dependencies:
2930
# --- conda-only / custom-channel packages ---
3031
- pyhecdss
3132
- vtools3
32-
- pip:
33-
- -e . # installs pydsm itself in editable mode
33+
# - pip:
34+
# - -e . # installs pydsm itself in editable mode

pydsm/cli.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
from pydsm.functions import tsmath
77
from pydsm.analysis import dssutils
88
from pydsm.input import repeating_timeseries
9-
from pydsm.input import create_cd_inp
9+
from pydsm.input import dcd_calcs
10+
from pydsm.input.dcd_calcs import calc_netcd_cmd
1011
from pydsm.input import extend_dss_ts
1112
from pydsm.output.create_gtm_restart import write_gtm_restart
1213
from pydsm.input import channel_orient
@@ -348,7 +349,6 @@ def create_gtm_restart_cmd(tidefile, target_time, outfile, constituent):
348349

349350

350351
# Add the commands to the group repeating
351-
repeating.add_command(create_repeating)
352352
repeating.add_command(extend_repeating)
353353
# adding sub commands to main
354354
main.add_command(repeating)
@@ -365,5 +365,6 @@ def create_gtm_restart_cmd(tidefile, target_time, outfile, constituent):
365365
#
366366
main.add_command(extend_dss_ts.extend_dss_ts)
367367
main.add_command(channel_orient.generate_channel_orientation, "chan-orient")
368+
main.add_command(calc_netcd_cmd)
368369
if __name__ == "__main__":
369370
sys.exit(main())

pydsm/input/dcd_calcs.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import click
12
import pandas as pd
23
import pyhecdss as dss
34

@@ -6,6 +7,25 @@ def get_bpart_pattern():
67
return '[0-9]+|BBID'
78

89

10+
def bparts_to_pattern(bparts):
11+
"""
12+
Convert a list of B-part strings to a regex alternation pattern for DSS path matching.
13+
Each B-part is wrapped in ^ and $ anchors so that e.g. '1' does not match '11' or '100'.
14+
(pyhecdss uses pandas str.match which anchors the start but not the end.)
15+
16+
Parameters
17+
----------
18+
bparts : list of str
19+
List of B-part identifiers, e.g. ['BBID', '12345', '67890']
20+
21+
Returns
22+
-------
23+
str
24+
Regex alternation string with full anchors, e.g. '^1$|^100$|^200$'
25+
"""
26+
return '|'.join('^' + b + '$' for b in bparts)
27+
28+
929
def get_dss_data(file, path_pattern):
1030
"""
1131
returns a data frame of all the time series that match the path_pattern
@@ -71,3 +91,79 @@ def calculate_netcd(div_flows, drain_flows, seep_flows=None):
7191
return sum(div_flows) - sum(drain_flows)
7292
else:
7393
return sum(div_flows) + sum(seep_flows) - sum(drain_flows)
94+
95+
96+
@click.command(name="calc-netcd")
97+
@click.argument("dssfile", type=click.Path(exists=True))
98+
@click.option(
99+
"--bpart",
100+
multiple=True,
101+
help="B-part to include (repeatable, e.g. --bpart BBID --bpart 12345)",
102+
)
103+
@click.option(
104+
"--bpart-file",
105+
type=click.Path(exists=True),
106+
default=None,
107+
help="Text file with one B-part per line",
108+
)
109+
@click.option(
110+
"--no-seepage",
111+
is_flag=True,
112+
default=False,
113+
help="Exclude seepage flows (NetCD = DIV - DRAIN only)",
114+
)
115+
@click.option(
116+
"--epart",
117+
default="1DAY",
118+
show_default=True,
119+
help="Time interval E-part (e.g. 1DAY, 1MON)",
120+
)
121+
@click.option(
122+
"-o",
123+
"--output",
124+
default="netcd.csv",
125+
show_default=True,
126+
help="Output CSV file path",
127+
)
128+
def calc_netcd_cmd(dssfile, bpart, bpart_file, no_seepage, epart, output):
129+
"""Calculate aggregated Net Channel Depletion (NetCD) from a DSS file.
130+
131+
NetCD = DIV-FLOW - DRAIN-FLOW + SEEP-FLOW, summed over all matching B-parts.
132+
133+
B-parts can be supplied via --bpart (repeatable) and/or --bpart-file (one per line).
134+
If neither is given, the default pattern matches all numeric node IDs and BBID.
135+
"""
136+
bparts = list(bpart)
137+
if bpart_file:
138+
with open(bpart_file) as f:
139+
bparts.extend(line.strip() for line in f if line.strip())
140+
141+
if bparts:
142+
bpart_pattern = bparts_to_pattern(bparts)
143+
else:
144+
bpart_pattern = get_bpart_pattern()
145+
146+
try:
147+
div_flows = get_dss_data(dssfile, f"//({bpart_pattern})/DIV-FLOW//{epart}//")
148+
except Exception as e:
149+
raise click.ClickException(f"No DIV-FLOW data found for the specified B-parts: {e}")
150+
151+
try:
152+
drain_flows = get_dss_data(dssfile, f"//({bpart_pattern})/DRAIN-FLOW//{epart}//")
153+
except Exception as e:
154+
raise click.ClickException(f"No DRAIN-FLOW data found for the specified B-parts: {e}")
155+
156+
seep_flows = None
157+
if not no_seepage:
158+
try:
159+
seep_flows = get_dss_data(dssfile, f"//({bpart_pattern})/SEEP-FLOW//{epart}//")
160+
except Exception:
161+
click.echo(
162+
"Warning: No SEEP-FLOW data found for the specified B-parts. "
163+
"Proceeding without seepage (NetCD = DIV - DRAIN).",
164+
err=True,
165+
)
166+
167+
netcd = calculate_netcd(div_flows, drain_flows, seep_flows)
168+
netcd.to_csv(output, header=["NETCD"])
169+
click.echo(f"NetCD written to {output}")

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies = [
2929
"numba",
3030
"diskcache",
3131
"tabulate",
32+
"decorator",
3233
]
3334
dynamic = ["version"]
3435

0 commit comments

Comments
 (0)