Skip to content

Commit b2c62cc

Browse files
committed
added end_date_print parameter
1 parent ced01bc commit b2c62cc

File tree

4 files changed

+122
-41
lines changed

4 files changed

+122
-41
lines changed

pySWATPlus/sensitivity_analyzer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ def simulation_by_sobol_sample(
308308
simulation_data = {
309309
'channel_sd_mon.txt': {
310310
'has_units': True,
311-
'start_date': '01-Jun-2014',
311+
'begin_date': '01-Jun-2014',
312312
'end_date': '01-Oct-2016',
313313
'apply_filter': {'gis_id': [561], 'yr': [2015, 2016]},
314314
'usecols': ['gis_id', 'flo_out']

pySWATPlus/txtinout_reader.py

Lines changed: 51 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -463,33 +463,46 @@ def set_print_interval(
463463

464464
return None
465465

466-
def set_start_date_print(
466+
def set_print_period(
467467
self,
468-
start_date: str,
468+
begin_date: str,
469+
end_date: str,
469470
) -> None:
470471
'''
471-
Set the start date in the `print.prt` file to define when output files begin recording simulation results.
472+
Set the start and end date in the `print.prt` file to define when output files begin recording simulation results.
472473
473474
Args:
474-
start_date (str): Start date in `DD-Mon-YYYY` format (e.g., 01-Jun-2010).
475+
begin_date (str): Start date in `DD-Mon-YYYY` format (e.g., 01-Jun-2010).
476+
end_date (str): End date in `DD-Mon-YYYY` format (e.g., 31-Dec-2020).
475477
'''
476478

477479
# Check input variables type
478480
validators._variable_origin_static_type(
479481
vars_types=typing.get_type_hints(
480-
obj=self.set_start_date_print
482+
obj=self.set_print_period
481483
),
482484
vars_values=locals()
483485
)
484486

485487
# Convert date string to datetime.date object
486-
start_dt = utils._date_str_to_object(
487-
date_str=start_date
488+
begin_dt = utils._date_str_to_object(
489+
date_str=begin_date
490+
)
491+
end_dt = utils._date_str_to_object(
492+
date_str=end_date
493+
)
494+
495+
# Check begin date is earlier than end date
496+
validators._date_begin_earlier_end(
497+
begin_date=begin_dt,
498+
end_date=end_dt
488499
)
489500

490501
# Extract years and Julian days
491-
start_day = start_dt.timetuple().tm_yday
492-
year_year = start_dt.year
502+
start_day = begin_dt.timetuple().tm_yday
503+
start_year = begin_dt.year
504+
end_day = end_dt.timetuple().tm_yday
505+
end_year = end_dt.year
493506

494507
# File path of print.prt
495508
print_prt_path = self.root_folder / 'print.prt'
@@ -500,7 +513,7 @@ def set_start_date_print(
500513

501514
nth_line = 3
502515
columns = lines[nth_line - 1].split()
503-
lines[nth_line - 1] = f"{columns[0]:<12}{start_day:<11}{year_year:<11}{columns[3]:<10}{columns[4]:<10}{columns[5]}\n"
516+
lines[nth_line - 1] = f"{columns[0]:<12}{start_day:<11}{start_year:<11}{end_day:<10}{end_year:<10}{columns[5]}\n"
504517

505518
# Modify print.prt file
506519
with open(print_prt_path, 'w') as file:
@@ -722,40 +735,44 @@ def _apply_swat_configuration(
722735
simulation_timestep: typing.Optional[int] = None,
723736
warmup: typing.Optional[int] = None,
724737
print_prt_control: typing.Optional[dict[str, dict[str, bool]]] = None,
725-
start_date_print: typing.Optional[str] = None,
738+
begin_date_print: typing.Optional[str] = None,
739+
end_date_print: typing.Optional[str] = None,
726740
print_interval: typing.Optional[int] = None
727741
) -> None:
728742
'''
729743
Set begin and end year for the simulation, the warm-up period, and toggles the elements in print.prt file
730744
'''
731745

732-
# Ensure both begin and end dates are provided together
733-
if (begin_date is None) != (end_date is None):
734-
raise ValueError(
735-
"Both 'begin_date' and 'end_date' must be provided together, "
736-
f"got begin_date={begin_date}, end_date={end_date}"
737-
)
746+
validators._ensure_together(begin_date=begin_date, end_date=end_date)
747+
validators._ensure_together(begin_date_print=begin_date_print, end_date_print=end_date_print)
738748

739-
if start_date_print is not None and (begin_date is None or end_date is None):
749+
# Validate dependencies between simulation and print periods
750+
if (begin_date_print or end_date_print) and not (begin_date and end_date):
740751
raise ValueError(
741-
"'start_date_print' cannot be set unless both 'begin_date' and 'end_date' are also provided. "
742-
f"got start_date_print={start_date_print}, begin_date={begin_date}, end_date={end_date}"
752+
"'begin_date_print'/'end_date_print' cannot be set unless "
753+
"'begin_date' and 'end_date' are also provided."
743754
)
744755

745756
# Validate date relationships
746-
if start_date_print is not None and begin_date is not None and end_date is not None:
757+
if begin_date_print and end_date_print and begin_date and end_date:
747758
begin_dt = utils._date_str_to_object(begin_date)
748759
end_dt = utils._date_str_to_object(end_date)
749-
start_print_dt = utils._date_str_to_object(start_date_print)
760+
start_print_dt = utils._date_str_to_object(begin_date_print)
761+
end_print_dt = utils._date_str_to_object(end_date_print)
750762

751763
validators._date_within_range(
752764
date_to_check=start_print_dt,
753765
begin_date=begin_dt,
754766
end_date=end_dt
755767
)
768+
validators._date_within_range(
769+
date_to_check=end_print_dt,
770+
begin_date=begin_dt,
771+
end_date=end_dt
772+
)
756773

757774
# Set simulation range time
758-
if begin_date is not None and end_date is not None:
775+
if begin_date and end_date:
759776
self.set_simulation_period(
760777
begin_date=begin_date,
761778
end_date=end_date
@@ -809,9 +826,10 @@ def _apply_swat_configuration(
809826
**key_dict
810827
)
811828

812-
if start_date_print is not None:
813-
self.set_start_date_print(
814-
start_date=start_date_print
829+
if begin_date_print and end_date_print:
830+
self.set_print_period(
831+
begin_date=begin_date_print,
832+
end_date=end_date_print
815833
)
816834

817835
if print_interval is not None:
@@ -871,7 +889,8 @@ def run_swat(
871889
simulation_timestep: typing.Optional[int] = None,
872890
warmup: typing.Optional[int] = None,
873891
print_prt_control: typing.Optional[dict[str, dict[str, bool]]] = None,
874-
start_date_print: typing.Optional[str] = None,
892+
begin_date_print: typing.Optional[str] = None,
893+
end_date_print: typing.Optional[str] = None,
875894
print_interval: typing.Optional[int] = None,
876895
skip_validation: bool = False
877896
) -> pathlib.Path:
@@ -947,7 +966,9 @@ def run_swat(
947966
}
948967
```
949968
950-
start_date_print (str): Number of years at the beginning of the simulation to not print output
969+
begin_date_print (str): The start date for printing the output
970+
971+
end_date_print (str): The end date for printing the output
951972
952973
print_interval (int): Print interval within the period. For example, if interval = 2, output will be printed for every other day.
953974
@@ -993,7 +1014,8 @@ def run_swat(
9931014
simulation_timestep=simulation_timestep,
9941015
warmup=warmup,
9951016
print_prt_control=print_prt_control,
996-
start_date_print=start_date_print,
1017+
begin_date_print=begin_date_print,
1018+
end_date_print=end_date_print,
9971019
print_interval=print_interval
9981020
)
9991021

pySWATPlus/validators.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,3 +322,23 @@ def _json_extension(
322322
)
323323

324324
return None
325+
326+
327+
def _ensure_together(**kwargs: typing.Any) -> None:
328+
"""
329+
Ensure that either all or none of the given arguments are provided (not None).
330+
331+
Example:
332+
_ensure_together(begin_date=begin, end_date=end)
333+
"""
334+
total = len(kwargs)
335+
provided = [name for name, value in kwargs.items() if value is not None]
336+
337+
# If some (but not all) values are provided → inconsistent input
338+
if 0 < len(provided) < total:
339+
missing = [name for name in kwargs if name not in provided]
340+
all_args = ", ".join(kwargs.keys())
341+
raise ValueError(
342+
f"Arguments [{all_args}] must be provided together. "
343+
f"Missing: {missing}, Provided: {provided}"
344+
)

tests/test_txtinout_reader.py

Lines changed: 50 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ def test_run_swat(
7979
'channel_sd': {'daily': False},
8080
'basin_wb': {}
8181
},
82-
start_date_print='01-Feb-2010',
82+
begin_date_print='01-Feb-2010',
83+
end_date_print='31-Dec-2011',
8384
print_interval=1
8485
)
8586
assert os.path.samefile(target_dir, tmp2_dir)
@@ -263,7 +264,7 @@ def test_set_simulation_timestep(
263264
assert 'Received invalid step: 7' in exc_info.value.args[0]
264265

265266

266-
def test_set_start_date_print(
267+
def test_set_print_period(
267268
txtinout_reader
268269
):
269270

@@ -280,19 +281,31 @@ def test_set_start_date_print(
280281
parts = original_lines[2].split()
281282
parts[1] = str(74)
282283
parts[2] = str(2010)
284+
parts[3] = str(365)
285+
parts[4] = str(2021)
286+
283287
expected_line = f"{parts[0]:<12}{parts[1]:<11}{parts[2]:<11}{parts[3]:<10}{parts[4]:<10}{parts[5]}\n"
284288

285289
target_reader = pySWATPlus.TxtinoutReader(target_dir)
286290

287-
target_reader.set_start_date_print(
288-
start_date='15-Mar-2010'
291+
target_reader.set_print_period(
292+
begin_date='15-Mar-2010',
293+
end_date='31-Dec-2021'
289294
)
290295

291296
# Read the line in print.prt again
292297
with open(target_dir / 'print.prt', 'r') as f:
293298
lines = f.readlines()
294299
assert lines[2] == expected_line, f'Expected:\n{expected_line}\nGot:\n{lines[2]}'
295300

301+
# Error: begin date earlier than end date
302+
with pytest.raises(ValueError) as exc_info:
303+
target_reader.set_print_period(
304+
begin_date='01-Jan-2016',
305+
end_date='01-Jan-2012'
306+
)
307+
assert exc_info.value.args[0] == 'begin_date 01-Jan-2016 must be earlier than end_date 01-Jan-2012'
308+
296309

297310
def test_set_print_interval(
298311
txtinout_reader
@@ -334,28 +347,54 @@ def test_error_run_swat(
334347
txtinout_reader.run_swat(
335348
begin_date='01-Jan-2010'
336349
)
337-
assert "Both 'begin_date' and 'end_date' must be provided together" in exc_info.value.args[0]
350+
assert "must be provided together" in exc_info.value.args[0]
338351

339352
# Error: end_date set but no begin_date
340353
with pytest.raises(ValueError) as exc_info:
341354
txtinout_reader.run_swat(
342355
end_date='31-Dec-2013'
343356
)
344-
assert "Both 'begin_date' and 'end_date' must be provided together" in exc_info.value.args[0]
357+
assert "must be provided together" in exc_info.value.args[0]
358+
359+
# Error: begin_date_print set but no end_date_print
360+
with pytest.raises(ValueError) as exc_info:
361+
txtinout_reader.run_swat(
362+
begin_date_print='01-Jan-2010'
363+
)
364+
assert "must be provided together" in exc_info.value.args[0]
345365

346-
# Error: start_date_print set without begin_date and end_date
366+
# Error: end_date_print set but no begin_date_print
347367
with pytest.raises(ValueError) as exc_info:
348368
txtinout_reader.run_swat(
349-
start_date_print='01-Jan-2010'
369+
end_date_print='31-Dec-2013'
350370
)
351-
assert "'start_date_print' cannot be set unless both 'begin_date' and 'end_date'" in exc_info.value.args[0]
371+
assert "must be provided together" in exc_info.value.args[0]
372+
373+
# Error: begin_date_print and end_date_print set without begin_date and end_date
374+
with pytest.raises(ValueError) as exc_info:
375+
txtinout_reader.run_swat(
376+
begin_date_print='01-Jan-2010',
377+
end_date_print='01-Jan-2011'
378+
)
379+
assert "'begin_date_print'/'end_date_print' cannot be set unless 'begin_date' and 'end_date' are also provided." == exc_info.value.args[0]
380+
381+
# Error: begin_date_print out of range
382+
with pytest.raises(ValueError) as exc_info:
383+
txtinout_reader.run_swat(
384+
begin_date='01-Jan-2010',
385+
end_date='31-Dec-2010',
386+
begin_date_print='31-Dec-2011',
387+
end_date_print='31-Dec-2012'
388+
)
389+
assert "must be between" in exc_info.value.args[0]
352390

353-
# Error: start_date_print out of range
391+
# Error: end_date_print out of range
354392
with pytest.raises(ValueError) as exc_info:
355393
txtinout_reader.run_swat(
356394
begin_date='01-Jan-2010',
357395
end_date='31-Dec-2010',
358-
start_date_print='31-Dec-2011'
396+
begin_date_print='15-Jan-2010',
397+
end_date_print='31-Dec-2012'
359398
)
360399
assert "must be between" in exc_info.value.args[0]
361400

0 commit comments

Comments
 (0)