From de6db7aa4599d5af499eae0e628a88fe8b9d865d Mon Sep 17 00:00:00 2001 From: Rob Date: Tue, 27 Jan 2026 15:50:51 +0000 Subject: [PATCH 01/12] create new HDF5 close() method and call at the end of run(). --- src/hsp2/hsp2io/hdf.py | 5 ++++- src/hsp2/hsp2tools/commands.py | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hsp2/hsp2io/hdf.py b/src/hsp2/hsp2io/hdf.py index 201552ee..62bf7582 100644 --- a/src/hsp2/hsp2io/hdf.py +++ b/src/hsp2/hsp2io/hdf.py @@ -13,9 +13,12 @@ def __init__(self, file_path: str) -> None: self._store = pd.HDFStore(file_path) None - def __del__(self): + def close(self): self._store.close() + def __del__(self): + self.close() + def __enter__(self): return self diff --git a/src/hsp2/hsp2tools/commands.py b/src/hsp2/hsp2tools/commands.py index 057670e2..abadb83f 100644 --- a/src/hsp2/hsp2tools/commands.py +++ b/src/hsp2/hsp2tools/commands.py @@ -24,6 +24,7 @@ def run(h5file, saveall=True, compress=True): hdf5_instance = HDF5(h5file) io_manager = IOManager(hdf5_instance) main(io_manager, saveall=saveall, jupyterlab=compress) + hdf5_instance.close() def import_uci(ucifile, h5file): From 8bbedea27c1b7647a006975fed4d88409b309631 Mon Sep 17 00:00:00 2001 From: Burgholzer Date: Tue, 27 Jan 2026 16:52:03 -0500 Subject: [PATCH 02/12] changes to main and utilities to adapt to pandas 3 deprecations --- src/hsp2/hsp2/main.py | 12 +++--- src/hsp2/hsp2/utilities.py | 80 +++++++++++++++++++++----------------- 2 files changed, 50 insertions(+), 42 deletions(-) diff --git a/src/hsp2/hsp2/main.py b/src/hsp2/hsp2/main.py index b7ed561f..5b1aab0c 100644 --- a/src/hsp2/hsp2/main.py +++ b/src/hsp2/hsp2/main.py @@ -704,23 +704,23 @@ def get_flows( t = data_frame[smemn].astype(float64).to_numpy()[0:steps] if MFname in ts and AFname in ts: - t *= ts[MFname][:steps] * ts[AFname][0:steps] + t = t * ts[MFname][:steps] * ts[AFname][0:steps] msg(4, f"MFACTOR modified by timeseries {MFname}") msg(4, f"AFACTR modified by timeseries {AFname}") elif MFname in ts: - t *= afactr * ts[MFname][0:steps] + t = t * afactr * ts[MFname][0:steps] msg(4, f"MFACTOR modified by timeseries {MFname}") elif AFname in ts: - t *= mfactor * ts[AFname][0:steps] + t = t * mfactor * ts[AFname][0:steps] msg(4, f"AFACTR modified by timeseries {AFname}") else: - t *= factor + t = t * factor # if poht to iheat, imprecision in hspf conversion factor requires a slight adjustment if (smemn == "POHT" or smemn == "SOHT") and tmemn == "IHEAT": - t *= 0.998553 + t = t * 0.998553 if (smemn == "PODOXM" or smemn == "SODOXM") and tmemn == "OXIF1": - t *= 1.000565 + t = t * 1.000565 # ??? ISSUE: can fetched data be at different frequency - don't know how to transform. if tmemn in ts: diff --git a/src/hsp2/hsp2/utilities.py b/src/hsp2/hsp2/utilities.py index 4cd17dd2..7c210ccf 100644 --- a/src/hsp2/hsp2/utilities.py +++ b/src/hsp2/hsp2/utilities.py @@ -14,7 +14,7 @@ from numba import types from numba.typed import Dict from numpy import float64, full, tile, zeros -from pandas import Series, date_range +from pandas import Series, date_range, Timedelta from pandas.tseries.offsets import Minute from hsp2.hsp2io.protocols import Category, SupportsReadTS, SupportsWriteTS @@ -213,8 +213,9 @@ def transform(ts, name, how, siminfo): NOTE: these routines work for both regular and sparse timeseries input """ - tsfreq = ts.index.freq - freq = Minute(siminfo["delt"]) + tsfreq = Timedelta("1 " + ts.index.freqstr) + fmins = Minute(siminfo["delt"]) + freq = Timedelta(fmins).to_timedelta64() stop = siminfo["stop"] # append duplicate of last point to force processing last full interval @@ -226,7 +227,7 @@ def transform(ts, name, how, siminfo): elif tsfreq is None: # Sparse time base, frequency not defined ts = ts.reindex(siminfo["tbase"]).ffill().bfill() elif how == "SAME": - ts = ts.resample(freq).ffill() # tsfreq >= freq assumed, or bad user choice + ts = ts.resample(fmins).ffill() # tsfreq >= freq assumed, or bad user choice elif not how: if name in flowtype: if "Y" in str(tsfreq) or "M" in str(tsfreq) or tsfreq > freq: @@ -236,24 +237,24 @@ def transform(ts, name, how, siminfo): ratio = 1.0 / 8766.0 else: ratio = freq / tsfreq - ts = (ratio * ts).resample(freq).ffill() # HSP2 how = div + ts = (ratio * ts).resample(fmins).ffill() # HSP2 how = div else: - ts = ts.resample(freq).sum() + ts = ts.resample(fmins).sum() else: if "Y" in str(tsfreq) or "M" in str(tsfreq) or tsfreq > freq: - ts = ts.resample(freq).ffill() + ts = ts.resample(fmins).ffiio_managerll() else: - ts = ts.resample(freq).mean() + ts = ts.resample(fmins).mean() elif how == "MEAN": - ts = ts.resample(freq).mean() + ts = ts.resample(fmins).mean() elif how == "SUM": - ts = ts.resample(freq).sum() + ts = ts.resample(fmins).sum() elif how == "MAX": - ts = ts.resample(freq).max() + ts = ts.resample(fmins).max() elif how == "MIN": - ts = ts.resample(freq).min() + ts = ts.resample(fmins).min() elif how == "LAST": - ts = ts.resample(freq).ffill() + ts = ts.resample(fmins).ffill() elif how == "DIV": if "Y" in str(tsfreq) or "M" in str(tsfreq): mult = 1 @@ -268,13 +269,13 @@ def transform(ts, name, how, siminfo): ratio = 1.0 / (8766.0 * mult) else: ratio = freq / tsfreq - ts = (ratio * ts).resample(freq).ffill() # HSP2 how = div + ts = (ratio * ts).resample(fmins).ffill() # HSP2 how = div else: - ts = (ts * (freq / ts.index.freq)).resample(freq).ffill() + ts = (ts * (freq / tsfreq)).resample(fmins).ffill() elif how == "ZEROFILL": - ts = ts.resample(freq).fillna(0.0) + ts = ts.resample(fmins).fillna(0.0) elif how == "INTERPOLATE": - ts = ts.resample(freq).interpolate() + ts = ts.resample(fmins).interpolate() else: print(f"UNKNOWN method in TRANS, {how}") return zeros(1) @@ -287,7 +288,8 @@ def hoursval(siminfo, hours24, dofirst=False, lapselike=False): """create hours flags, flag on the hour or lapse table over full simulation""" start = siminfo["start"] stop = siminfo["stop"] - freq = Minute(siminfo["delt"]) + fmins = Minute(siminfo["delt"]) + freq = Timedelta(fmins).to_timedelta64() dr = date_range( start=f"{start.year}-01-01", end=f"{stop.year}-12-31", freq=Minute(60) @@ -297,16 +299,17 @@ def hoursval(siminfo, hours24, dofirst=False, lapselike=False): hours[0] = 1 ts = Series(hours[0 : len(dr)], dr) + tsfreq = Timedelta("1 " + ts.index.freqstr) if lapselike: - if ts.index.freq > freq: # upsample - ts = ts.resample(freq).asfreq().ffill() - elif ts.index.freq < freq: # downsample - ts = ts.resample(freq).mean() + if tsfreq > freq: # upsample + ts = ts.resample(fmins).asfreq().ffill() + elif tsfreq < freq: # downsample + ts = ts.resample(fmins).mean() else: - if ts.index.freq > freq: # upsample - ts = ts.resample(freq).asfreq().fillna(0.0) - elif ts.index.freq < freq: # downsample - ts = ts.resample(freq).max() + if tsfreq > freq: # upsample + ts = ts.resample(fmins).asfreq().fillna(0.0) + elif tsfreq < freq: # downsample + ts = ts.resample(fmins).max() return ts.truncate(start, stop).to_numpy() @@ -321,16 +324,18 @@ def monthval(siminfo, monthly): """returns value at start of month for all times within the month""" start = siminfo["start"] stop = siminfo["stop"] - freq = Minute(siminfo["delt"]) + fmins = Minute(siminfo["delt"]) + freq = Timedelta(fmins).to_timedelta64() months = tile(monthly, stop.year - start.year + 1).astype(float) dr = date_range(start=f"{start.year}-01-01", end=f"{stop.year}-12-31", freq="MS") ts = Series(months, index=dr).resample("D").ffill() + tsfreq = Timedelta("1 " + ts.index.freqstr) - if ts.index.freq > freq: # upsample - ts = ts.resample(freq).asfreq().ffill() - elif ts.index.freq < freq: # downsample - ts = ts.resample(freq).mean() + if tsfreq > freq: # upsample + ts = ts.resample(fmins).asfreq().ffill() + elif tsfreq < freq: # downsample + ts = ts.resample(fmins).mean() return ts.truncate(start, stop).to_numpy() @@ -339,16 +344,19 @@ def dayval(siminfo, monthly): interpolation to day, but constant within day""" start = siminfo["start"] stop = siminfo["stop"] - freq = Minute(siminfo["delt"]) + fmins = Minute(siminfo["delt"]) + freq = Timedelta(fmins).to_timedelta64() months = tile(monthly, stop.year - start.year + 1).astype(float) dr = date_range(start=f"{start.year}-01-01", end=f"{stop.year}-12-31", freq="MS") ts = Series(months, index=dr).resample("D").interpolate("time") + tsfreq = Timedelta("1 " + ts.index.freqstr) - if ts.index.freq > freq: # upsample - ts = ts.resample(freq).ffill() - elif ts.index.freq < freq: # downsample - ts = ts.resample(freq).mean() + + if tsfreq > freq: # upsample + ts = ts.resample(fmins).ffill() + elif tsfreq < freq: # downsample + ts = ts.resample(fmins).mean() return ts.truncate(start, stop).to_numpy() From e7331cc2783d70bbcaf7b4edcb4d4adac9d73d31 Mon Sep 17 00:00:00 2001 From: Rob Date: Thu, 29 Jan 2026 02:03:24 +0000 Subject: [PATCH 03/12] code for cmd prmpt test regression --- tests/cmd_regression.py | 86 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 tests/cmd_regression.py diff --git a/tests/cmd_regression.py b/tests/cmd_regression.py new file mode 100644 index 00000000..5cf0356c --- /dev/null +++ b/tests/cmd_regression.py @@ -0,0 +1,86 @@ +# This is code to be executed in a python shell to run regressions tests +# Note: This code must be run from the tests dir due to importing +# the `convert` directory where the regression_base file lives +# todo: make this convert path passable by argument +import numpy as np +from convert.regression_base import RegressTest + +case = "test10specl" +base_case = "test10" +#case = "testcbp" +tdir = "/opt/model/HSPsquared/tests" +#import_uci(str(hsp2_specl_uci), str(temp_specl_h5file)) +#run(temp_specl_h5file, saveall=True, compress=False) + +############# RegressTest data loader +## TEST CASE +test = RegressTest(case, threads=1) +# test object hydr +rchres_hydr_hsp2_test_table = test.hsp2_data._read_table('RCHRES', '001', 'HYDR') +perlnd_pwat_hsp2_test_table = test.hsp2_data._read_table('PERLND', '001', 'PWATER') +rchres_hydr_hsp2_test = test.hsp2_data.get_time_series('RCHRES', '001', 'RO', 'HYDR') +rchres_hydr_hspf_test = test.get_hspf_time_series( ('RCHRES', 'HYDR', '001', 'RO', 2)) #Note: the order of arguments is wonky in Regressbase +rchres_hydr_hsp2_test_mo = rchres_hydr_hsp2_test.resample('MS').mean() +rchres_hydr_hspf_test_mo = rchres_hydr_hspf_test.resample('MS').mean() +## BASE CASE +base = RegressTest(base_case, threads=1) +# base object hydr +rchres_hydr_hsp2_base_table = base.hsp2_data._read_table('RCHRES', '001', 'HYDR') +perlnd_pwat_hsp2_base_table = base.hsp2_data._read_table('PERLND', '001', 'PWATER') +rchres_hydr_hsp2_base = base.hsp2_data.get_time_series('RCHRES', '001', 'RO', 'HYDR') +rchres_hydr_hspf_base = base.get_hspf_time_series( ('RCHRES', 'HYDR', '001', 'RO', 2)) #Note: the order of arguments is wonky in Regressbase +rchres_hydr_hsp2_base_mo = rchres_hydr_hsp2_base.resample('MS').mean() +rchres_hydr_hspf_base_mo = rchres_hydr_hspf_base.resample('MS').mean() + +# Show quantiles +np.quantile(rchres_hydr_hsp2_test, [0,0.25,0.5,0.75,1.0]) +np.quantile(rchres_hydr_hspf_test, [0,0.25,0.5,0.75,1.0]) +np.quantile(rchres_hydr_hsp2_base, [0,0.25,0.5,0.75,1.0]) +np.quantile(rchres_hydr_hspf_base, [0,0.25,0.5,0.75,1.0]) +# Monthly mean value comparisons +rchres_hydr_hsp2_test_mo +rchres_hydr_hspf_test_mo +rchres_hydr_hsp2_base_mo +rchres_hydr_hspf_base_mo + +# Compare ANY arbitrary timeseries, not just the ones coded into the RegressTest object +# 3rd argument is tolerance to use +tol = 10.0 +test.compare_time_series(rchres_hydr_hsp2_base_table['RO'], rchres_hydr_hsp2_test_table['RO'], tol) +# Example: (True, 8.7855425) + +# Compare inflows and outflows +np.mean(perlnd_pwat_hsp2_test_table['PERO']) * 6000 * 0.0833 +np.mean(rchres_hydr_hsp2_test_table['IVOL']) +np.mean(rchres_hydr_hsp2_test_table['ROVOL']) +np.mean(perlnd_pwat_hsp2_base_table['PERO']) * 6000 * 0.0833 +np.mean(rchres_hydr_hsp2_base_table['IVOL']) +np.mean(rchres_hydr_hsp2_base_table['ROVOL']) + +# now do a comparison +# HYDR diff should be almost nonexistent +test.check_con(params = ('RCHRES', 'HYDR', '001', 'ROVOL', 2)) +# this is very large for PWTGAS +test.check_con(params = ('PERLND', 'PWTGAS', '001', 'POHT', '2'))\ +# Other mismatches in PERLND +test.check_con(params = ('PERLND', 'PWATER', '001', 'AGWS', '2')) +test.check_con(params = ('PERLND', 'PWATER', '001', 'PERO', '2')) +# Now run the full test +results = test.run_test() +found = False +mismatches = [] +for key, results in results.items(): + no_data_hsp2, no_data_hspf, match, diff = results + if any([no_data_hsp2, no_data_hspf]): + continue + if not match: + mismatches.append((case, key, results)) + found = True + +print(mismatches) + +if mismatches: + for case, key, results in mismatches: + diff = results + print(case, key, f"{diff:0.00%}") + From eee69593fd4a25f5f8b11ba2ece213b619a06552 Mon Sep 17 00:00:00 2001 From: Rob Date: Thu, 29 Jan 2026 02:27:49 +0000 Subject: [PATCH 04/12] removed seemingly inactive file that causes compile problems at times --- {src/hsp2/hsp2 => examples}/ACIDPH.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {src/hsp2/hsp2 => examples}/ACIDPH.txt (100%) diff --git a/src/hsp2/hsp2/ACIDPH.txt b/examples/ACIDPH.txt similarity index 100% rename from src/hsp2/hsp2/ACIDPH.txt rename to examples/ACIDPH.txt From acc1e364b2b80cd88f5bcfdb484be3d00c3cf29a Mon Sep 17 00:00:00 2001 From: "Robert W. Burgholzer" Date: Thu, 29 Jan 2026 16:38:01 +0000 Subject: [PATCH 05/12] trigger test bot with pre pandas 3 for testing --- environment.yml | 2 +- environment_dev.yml | 2 +- examples/state_specl_ops/compare_eq_to_specl.py | 1 + pyproject.toml | 2 +- ...FSingle_vs_PythonDouble_precision_test10.png | Bin 0 -> 52018 bytes 5 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 tests/convert/HSPFSingle_vs_PythonDouble_precision_test10.png diff --git a/environment.yml b/environment.yml index 343e179d..d7ea70c3 100644 --- a/environment.yml +++ b/environment.yml @@ -10,7 +10,7 @@ dependencies: # Running HSP2 - scipy # Scipy also installs numpy # Pandas installs most scientific Python modules, such as Numpy, etc. - - pandas + - pandas <3.0 - numba - numpy - hdf5 diff --git a/environment_dev.yml b/environment_dev.yml index 78bfc5a9..78dbb137 100644 --- a/environment_dev.yml +++ b/environment_dev.yml @@ -12,7 +12,7 @@ dependencies: # Running HSP2 - scipy # Scipy also installs numpy # Pandas installs most scientific Python modules, such as Numpy, etc. - - pandas >=2.0 + - pandas >=2.0,<3.0 - numba - numpy - hdf5 diff --git a/examples/state_specl_ops/compare_eq_to_specl.py b/examples/state_specl_ops/compare_eq_to_specl.py index 5e21d9b2..af3b8a34 100644 --- a/examples/state_specl_ops/compare_eq_to_specl.py +++ b/examples/state_specl_ops/compare_eq_to_specl.py @@ -1,4 +1,5 @@ # if testing manually you may need to os.chdir('./tests/test10specl/HSP2results') +# todo: make sure thsi is path independent. import os import pandas as pd import numpy as np diff --git a/pyproject.toml b/pyproject.toml index ea38e3cd..7e8abc8d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ "cltoolbox", "numba", "numpy<2.0", - "pandas", + "pandas<3.0", "tables", "pyparsing" ] diff --git a/tests/convert/HSPFSingle_vs_PythonDouble_precision_test10.png b/tests/convert/HSPFSingle_vs_PythonDouble_precision_test10.png new file mode 100644 index 0000000000000000000000000000000000000000..03bd1e714a37e29385e0cd49810bb1f2965295c4 GIT binary patch literal 52018 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 A%>V!Z literal 0 HcmV?d00001 From 6c9231166c5de0bb01b709eeea8213974e969b85 Mon Sep 17 00:00:00 2001 From: "Robert W. Burgholzer" Date: Thu, 29 Jan 2026 20:55:51 +0000 Subject: [PATCH 06/12] used freq + convert to_timedelta to insure valid comparisons in all types --- src/hsp2/hsp2/utilities.py | 8 ++++---- tests/cmd_regression.py | 16 +++++++++------- tests/convert/regression_base.py | 5 +++-- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/hsp2/hsp2/utilities.py b/src/hsp2/hsp2/utilities.py index 7c210ccf..491004d6 100644 --- a/src/hsp2/hsp2/utilities.py +++ b/src/hsp2/hsp2/utilities.py @@ -213,7 +213,7 @@ def transform(ts, name, how, siminfo): NOTE: these routines work for both regular and sparse timeseries input """ - tsfreq = Timedelta("1 " + ts.index.freqstr) + tsfreq = Timedelta(ts.index.freq).to_timedelta64() fmins = Minute(siminfo["delt"]) freq = Timedelta(fmins).to_timedelta64() stop = siminfo["stop"] @@ -299,7 +299,7 @@ def hoursval(siminfo, hours24, dofirst=False, lapselike=False): hours[0] = 1 ts = Series(hours[0 : len(dr)], dr) - tsfreq = Timedelta("1 " + ts.index.freqstr) + tsfreq = Timedelta(ts.index.freq).to_timedelta64() if lapselike: if tsfreq > freq: # upsample ts = ts.resample(fmins).asfreq().ffill() @@ -330,7 +330,7 @@ def monthval(siminfo, monthly): months = tile(monthly, stop.year - start.year + 1).astype(float) dr = date_range(start=f"{start.year}-01-01", end=f"{stop.year}-12-31", freq="MS") ts = Series(months, index=dr).resample("D").ffill() - tsfreq = Timedelta("1 " + ts.index.freqstr) + tsfreq = Timedelta(ts.index.freq).to_timedelta64() if tsfreq > freq: # upsample ts = ts.resample(fmins).asfreq().ffill() @@ -350,7 +350,7 @@ def dayval(siminfo, monthly): months = tile(monthly, stop.year - start.year + 1).astype(float) dr = date_range(start=f"{start.year}-01-01", end=f"{stop.year}-12-31", freq="MS") ts = Series(months, index=dr).resample("D").interpolate("time") - tsfreq = Timedelta("1 " + ts.index.freqstr) + tsfreq = Timedelta(ts.index.freq).to_timedelta64() if tsfreq > freq: # upsample diff --git a/tests/cmd_regression.py b/tests/cmd_regression.py index 5cf0356c..871b15b4 100644 --- a/tests/cmd_regression.py +++ b/tests/cmd_regression.py @@ -1,4 +1,3 @@ -# This is code to be executed in a python shell to run regressions tests # Note: This code must be run from the tests dir due to importing # the `convert` directory where the regression_base file lives # todo: make this convert path passable by argument @@ -33,10 +32,10 @@ rchres_hydr_hspf_base_mo = rchres_hydr_hspf_base.resample('MS').mean() # Show quantiles -np.quantile(rchres_hydr_hsp2_test, [0,0.25,0.5,0.75,1.0]) -np.quantile(rchres_hydr_hspf_test, [0,0.25,0.5,0.75,1.0]) -np.quantile(rchres_hydr_hsp2_base, [0,0.25,0.5,0.75,1.0]) -np.quantile(rchres_hydr_hspf_base, [0,0.25,0.5,0.75,1.0]) +print("hsp2", case, np.quantile(rchres_hydr_hsp2_test, [0,0.25,0.5,0.75,1.0])) +print("hspf", case, np.quantile(rchres_hydr_hspf_test, [0,0.25,0.5,0.75,1.0])) +print("hsp2", base_case, np.quantile(rchres_hydr_hsp2_base, [0,0.25,0.5,0.75,1.0])) +print("hspf", base_case, np.quantile(rchres_hydr_hspf_base, [0,0.25,0.5,0.75,1.0])) # Monthly mean value comparisons rchres_hydr_hsp2_test_mo rchres_hydr_hspf_test_mo @@ -61,11 +60,13 @@ # HYDR diff should be almost nonexistent test.check_con(params = ('RCHRES', 'HYDR', '001', 'ROVOL', 2)) # this is very large for PWTGAS -test.check_con(params = ('PERLND', 'PWTGAS', '001', 'POHT', '2'))\ +# git: test10specl ('PERLND', 'PWTGAS', '001', 'POHT', '2') 1163640% +test.check_con(params = ('PERLND', 'PWTGAS', '001', 'POHT', '2')) # Other mismatches in PERLND test.check_con(params = ('PERLND', 'PWATER', '001', 'AGWS', '2')) test.check_con(params = ('PERLND', 'PWATER', '001', 'PERO', '2')) # Now run the full test +test.quiet = True # this lets us test without overwhelming the console results = test.run_test() found = False mismatches = [] @@ -83,4 +84,5 @@ for case, key, results in mismatches: diff = results print(case, key, f"{diff:0.00%}") - +else: + print("No mismatches found. Success!") diff --git a/tests/convert/regression_base.py b/tests/convert/regression_base.py index 6cfc4f87..613c4851 100644 --- a/tests/convert/regression_base.py +++ b/tests/convert/regression_base.py @@ -30,7 +30,7 @@ def __init__( self.tcodes = tcodes self.ids = ids self.threads = threads - + self.quiet = False # allows users to set this later self._init_files() def _init_files(self): @@ -185,7 +185,8 @@ def run_test(self) -> Dict[OperationsTuple, ResultsTuple]: def check_con(self, params: OperationsTuple) -> ResultsTuple: """Performs comparision of single constituent""" operation, activity, id, constituent, tcode = params - print(f" {operation}_{id} {activity} {constituent}\n") + if not self.quiet: + print(f" {operation}_{id} {activity} {constituent}\n") ts_hsp2 = self.hsp2_data.get_time_series(operation, id, constituent, activity) ts_hspf = self.get_hspf_time_series(params) From fcacfd4d7707bd2514e49f6a101ad0623270c79a Mon Sep 17 00:00:00 2001 From: "Robert W. Burgholzer" Date: Thu, 29 Jan 2026 21:19:36 +0000 Subject: [PATCH 07/12] check if these changes are robust into pandas 3 --- environment.yml | 2 +- environment_dev.yml | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/environment.yml b/environment.yml index d7ea70c3..343e179d 100644 --- a/environment.yml +++ b/environment.yml @@ -10,7 +10,7 @@ dependencies: # Running HSP2 - scipy # Scipy also installs numpy # Pandas installs most scientific Python modules, such as Numpy, etc. - - pandas <3.0 + - pandas - numba - numpy - hdf5 diff --git a/environment_dev.yml b/environment_dev.yml index 78dbb137..78bfc5a9 100644 --- a/environment_dev.yml +++ b/environment_dev.yml @@ -12,7 +12,7 @@ dependencies: # Running HSP2 - scipy # Scipy also installs numpy # Pandas installs most scientific Python modules, such as Numpy, etc. - - pandas >=2.0,<3.0 + - pandas >=2.0 - numba - numpy - hdf5 diff --git a/pyproject.toml b/pyproject.toml index 7e8abc8d..ea38e3cd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ "cltoolbox", "numba", "numpy<2.0", - "pandas<3.0", + "pandas", "tables", "pyparsing" ] From 193c4bb57e8967b79ad5eafefb18c741e2c3e5a0 Mon Sep 17 00:00:00 2001 From: Burgholzer Date: Thu, 29 Jan 2026 16:44:43 -0500 Subject: [PATCH 08/12] use pandas to_timedelta function to future proof --- examples/state_specl_ops/update_pandas.py | 134 ++++++++++++++++++++++ src/hsp2/hsp2/utilities.py | 10 +- 2 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 examples/state_specl_ops/update_pandas.py diff --git a/examples/state_specl_ops/update_pandas.py b/examples/state_specl_ops/update_pandas.py new file mode 100644 index 00000000..79beb91e --- /dev/null +++ b/examples/state_specl_ops/update_pandas.py @@ -0,0 +1,134 @@ + +from numpy import float64, ones +from pandas import DataFrame, date_range +from pandas.tseries.offsets import Minute +from datetime import datetime as dt +from typing import Union +import os +# must import Category, Modelfirst to prevent circular error with HDF5 +# this does not happen in real runtime... +from hsp2.hsp2.model import Model +from hsp2.hsp2io.protocols import Category +from hsp2.hsp2io.hdf import HDF5 +from hsp2.hsp2.utilities import ( + versions, + get_timeseries, + expand_timeseries_names, + save_timeseries, + get_gener_timeseries, + hoursval +) +from hsp2.hsp2.configuration import activities, noop, expand_masslinks +from hsp2.state.state import ( + init_state_dicts, + state_siminfo_hsp2, + state_load_dynamics_hsp2, + state_init_hsp2, + state_context_hsp2, +) +from hsp2.hsp2.om import ( + om_init_state, + state_om_model_run_prep, + state_load_dynamics_om, + state_om_model_run_finish, +) +from hsp2.hsp2.SPECL import specl_load_state + +from hsp2.hsp2io.io import IOManager, SupportsReadTS, Category + + +fpath = "./tests/test10/HSP2results/test10.h5" +# try also: +# fpath = './tests/testcbp/HSP2results/JL1_6562_6560.h5' +# sometimes when testing you may need to close the file, so try: +# f = h5py.File(fpath,'a') # use mode 'a' which allows read, write, modify +# # f.close() +hdf5_instance = HDF5(fpath) +io_manager = IOManager(hdf5_instance) + +# Begin code from main.py + +# read user control, parameters, states, and flags parameters and map to local variables +parameter_obj = io_manager.read_parameters() +opseq = parameter_obj.opseq +ddlinks = parameter_obj.ddlinks +ddmasslinks = parameter_obj.ddmasslinks +ddext_sources = parameter_obj.ddext_sources +ddgener = parameter_obj.ddgener +model = parameter_obj.model +siminfo = parameter_obj.siminfo +ftables = parameter_obj.ftables +specactions = parameter_obj.specactions +monthdata = parameter_obj.monthdata + +start, stop = siminfo["start"], siminfo["stop"] + +copy_instances = {} +gener_instances = {} + +####################################################################################### +# initialize STATE dicts +####################################################################################### +# Set up Things in state that will be used in all modular activities like SPECL +state = init_state_dicts() +state_siminfo_hsp2(parameter_obj, siminfo, io_manager, state) +# Add support for dynamic functions to operate on STATE +# - Load any dynamic components if present, and store variables on objects +state_load_dynamics_hsp2(state, io_manager, siminfo) +# Iterate through all segments and add crucial paths to state +# before loading dynamic components that may reference them +state_init_hsp2(state, opseq, activities) +# - finally stash specactions in state, not domain (segment) dependent so do it once +state["specactions"] = specactions # stash the specaction dict in state +om_init_state(state) # set up operational model specific state entries +specl_load_state(state, io_manager, siminfo) # traditional special actions +state_load_dynamics_om( + state, io_manager, siminfo +) # operational model for custom python +# finalize all dynamically loaded components and prepare to run the model +state_om_model_run_prep(state, io_manager, siminfo) +####################################################################################### + +# main processing loop +print(1, f"Simulation Start: {start}, Stop: {stop}") + +# Test functions pandas +# this is from PWATER, should be easy? +ts_HRFG = hoursval(siminfo, ones(24), dofirst=True).astype(float) +# do import to allow dev of hoursval3 - temporary +from numpy import float64, full, tile, zeros +ts_HRFG3 = hoursval3(siminfo, ones(24), dofirst=True).astype(float) +from hsp2.hsp2.utilities import LAPSE +ts_LAPSE = hoursval(siminfo, LAPSE, lapselike=True) +ts_LAPSE3 = hoursval3(siminfo, LAPSE, lapselike=True) + +# check these transform() calls inside of get_timeseries() +(operation, segment) = ('PERLND', 'P001') +psrc = ddext_sources[('PERLND', 'P001')] + +ts = get_timeseries( + io_manager, psrc, siminfo +) +prec_orig = ts['PREC'] + +row = psrc[0] # 0 is PRCP +data_frame = io_manager.read_ts( + category=Category.INPUTS, segment=row.SVOLNO +) +# are they the same? +(prec_orig == data_frame).all() +# True - so we don't expect transform to do anything? +precip_pandas2 = transform(data_frame, row.TMEMN, row.TRAN, siminfo) +precip_pandas3 = transform3(data_frame, row.TMEMN, row.TRAN, siminfo) + +# precip was fine, so iterate through them all and check the difference +for row in psrc: + + +# replicate with new code +data_frame = io_manager.read_ts( + category=Category.INPUTS, segment=segment +) +tsfreq = ts.index.freq +freq = Minute(siminfo["delt"]) +stop = siminfo["stop"] \ No newline at end of file diff --git a/src/hsp2/hsp2/utilities.py b/src/hsp2/hsp2/utilities.py index 491004d6..eb03c0f8 100644 --- a/src/hsp2/hsp2/utilities.py +++ b/src/hsp2/hsp2/utilities.py @@ -14,7 +14,7 @@ from numba import types from numba.typed import Dict from numpy import float64, full, tile, zeros -from pandas import Series, date_range, Timedelta +from pandas import Series, date_range, Timedelta, to_timedelta from pandas.tseries.offsets import Minute from hsp2.hsp2io.protocols import Category, SupportsReadTS, SupportsWriteTS @@ -213,7 +213,7 @@ def transform(ts, name, how, siminfo): NOTE: these routines work for both regular and sparse timeseries input """ - tsfreq = Timedelta(ts.index.freq).to_timedelta64() + tsfreq = to_timedelta(ts.index.freq).to_timedelta64() fmins = Minute(siminfo["delt"]) freq = Timedelta(fmins).to_timedelta64() stop = siminfo["stop"] @@ -299,7 +299,7 @@ def hoursval(siminfo, hours24, dofirst=False, lapselike=False): hours[0] = 1 ts = Series(hours[0 : len(dr)], dr) - tsfreq = Timedelta(ts.index.freq).to_timedelta64() + tsfreq = to_timedelta(ts.index.freq).to_timedelta64() if lapselike: if tsfreq > freq: # upsample ts = ts.resample(fmins).asfreq().ffill() @@ -330,7 +330,7 @@ def monthval(siminfo, monthly): months = tile(monthly, stop.year - start.year + 1).astype(float) dr = date_range(start=f"{start.year}-01-01", end=f"{stop.year}-12-31", freq="MS") ts = Series(months, index=dr).resample("D").ffill() - tsfreq = Timedelta(ts.index.freq).to_timedelta64() + tsfreq = to_timedelta(ts.index.freq).to_timedelta64() if tsfreq > freq: # upsample ts = ts.resample(fmins).asfreq().ffill() @@ -350,7 +350,7 @@ def dayval(siminfo, monthly): months = tile(monthly, stop.year - start.year + 1).astype(float) dr = date_range(start=f"{start.year}-01-01", end=f"{stop.year}-12-31", freq="MS") ts = Series(months, index=dr).resample("D").interpolate("time") - tsfreq = Timedelta(ts.index.freq).to_timedelta64() + tsfreq = to_timedelta(ts.index.freq).to_timedelta64() if tsfreq > freq: # upsample From 8df5ad015c53d4c318f0edf8316575ae5a9f2807 Mon Sep 17 00:00:00 2001 From: Burgholzer Date: Thu, 29 Jan 2026 17:26:53 -0500 Subject: [PATCH 09/12] use freq own delta property for properly formatted --- examples/state_specl_ops/update_pandas.py | 51 ++++------------------- src/hsp2/hsp2/utilities.py | 8 ++-- 2 files changed, 12 insertions(+), 47 deletions(-) diff --git a/examples/state_specl_ops/update_pandas.py b/examples/state_specl_ops/update_pandas.py index 79beb91e..97886b8e 100644 --- a/examples/state_specl_ops/update_pandas.py +++ b/examples/state_specl_ops/update_pandas.py @@ -89,46 +89,11 @@ state_om_model_run_prep(state, io_manager, siminfo) ####################################################################################### -# main processing loop -print(1, f"Simulation Start: {start}, Stop: {stop}") - -# Test functions pandas -# this is from PWATER, should be easy? -ts_HRFG = hoursval(siminfo, ones(24), dofirst=True).astype(float) -# do import to allow dev of hoursval3 - temporary -from numpy import float64, full, tile, zeros -ts_HRFG3 = hoursval3(siminfo, ones(24), dofirst=True).astype(float) -from hsp2.hsp2.utilities import LAPSE -ts_LAPSE = hoursval(siminfo, LAPSE, lapselike=True) -ts_LAPSE3 = hoursval3(siminfo, LAPSE, lapselike=True) - -# check these transform() calls inside of get_timeseries() -(operation, segment) = ('PERLND', 'P001') -psrc = ddext_sources[('PERLND', 'P001')] - -ts = get_timeseries( - io_manager, psrc, siminfo -) -prec_orig = ts['PREC'] - -row = psrc[0] # 0 is PRCP -data_frame = io_manager.read_ts( - category=Category.INPUTS, segment=row.SVOLNO -) -# are they the same? -(prec_orig == data_frame).all() -# True - so we don't expect transform to do anything? -precip_pandas2 = transform(data_frame, row.TMEMN, row.TRAN, siminfo) -precip_pandas3 = transform3(data_frame, row.TMEMN, row.TRAN, siminfo) - -# precip was fine, so iterate through them all and check the difference -for row in psrc: - - -# replicate with new code -data_frame = io_manager.read_ts( - category=Category.INPUTS, segment=segment -) -tsfreq = ts.index.freq -freq = Minute(siminfo["delt"]) -stop = siminfo["stop"] \ No newline at end of file +perland_ext = ddext_sources[('PERLND', 'P001')] +perlnd_ts = get_timeseries(io_manager, perland_ext, siminfo) +precip = perlnd_ts['PREC'] +# go another way and get the raw TS to compare to the perlnd_ts which has been converted to simulation +# intervals +precip_df = io_manager.read_ts( + category=Category.INPUTS, segment='TS039' +) \ No newline at end of file diff --git a/src/hsp2/hsp2/utilities.py b/src/hsp2/hsp2/utilities.py index eb03c0f8..d135458a 100644 --- a/src/hsp2/hsp2/utilities.py +++ b/src/hsp2/hsp2/utilities.py @@ -213,7 +213,7 @@ def transform(ts, name, how, siminfo): NOTE: these routines work for both regular and sparse timeseries input """ - tsfreq = to_timedelta(ts.index.freq).to_timedelta64() + tsfreq = ts.index.freq.delta.to_timedelta64() fmins = Minute(siminfo["delt"]) freq = Timedelta(fmins).to_timedelta64() stop = siminfo["stop"] @@ -299,7 +299,7 @@ def hoursval(siminfo, hours24, dofirst=False, lapselike=False): hours[0] = 1 ts = Series(hours[0 : len(dr)], dr) - tsfreq = to_timedelta(ts.index.freq).to_timedelta64() + tsfreq = ts.index.freq.delta.to_timedelta64() if lapselike: if tsfreq > freq: # upsample ts = ts.resample(fmins).asfreq().ffill() @@ -330,7 +330,7 @@ def monthval(siminfo, monthly): months = tile(monthly, stop.year - start.year + 1).astype(float) dr = date_range(start=f"{start.year}-01-01", end=f"{stop.year}-12-31", freq="MS") ts = Series(months, index=dr).resample("D").ffill() - tsfreq = to_timedelta(ts.index.freq).to_timedelta64() + tsfreq = ts.index.freq.delta.to_timedelta64() if tsfreq > freq: # upsample ts = ts.resample(fmins).asfreq().ffill() @@ -350,7 +350,7 @@ def dayval(siminfo, monthly): months = tile(monthly, stop.year - start.year + 1).astype(float) dr = date_range(start=f"{start.year}-01-01", end=f"{stop.year}-12-31", freq="MS") ts = Series(months, index=dr).resample("D").interpolate("time") - tsfreq = to_timedelta(ts.index.freq).to_timedelta64() + tsfreq = ts.index.freq.delta.to_timedelta64() if tsfreq > freq: # upsample From 389a3cbca45485444a859139eb9f01e483177315 Mon Sep 17 00:00:00 2001 From: Burgholzer Date: Thu, 29 Jan 2026 17:31:13 -0500 Subject: [PATCH 10/12] pandas < 3 --- environment.yml | 2 +- environment_dev.yml | 2 +- pyproject.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/environment.yml b/environment.yml index 343e179d..026de50e 100644 --- a/environment.yml +++ b/environment.yml @@ -10,7 +10,7 @@ dependencies: # Running HSP2 - scipy # Scipy also installs numpy # Pandas installs most scientific Python modules, such as Numpy, etc. - - pandas + - pandas <3.0.0 - numba - numpy - hdf5 diff --git a/environment_dev.yml b/environment_dev.yml index 78bfc5a9..b30e9158 100644 --- a/environment_dev.yml +++ b/environment_dev.yml @@ -12,7 +12,7 @@ dependencies: # Running HSP2 - scipy # Scipy also installs numpy # Pandas installs most scientific Python modules, such as Numpy, etc. - - pandas >=2.0 + - pandas >=2.0, <3.0.0 - numba - numpy - hdf5 diff --git a/pyproject.toml b/pyproject.toml index ea38e3cd..85e33a2b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ "cltoolbox", "numba", "numpy<2.0", - "pandas", + "pandas<3.0.0", "tables", "pyparsing" ] From 3b3e56d5c828009f7582f168480e85c3045605ba Mon Sep 17 00:00:00 2001 From: Burgholzer Date: Thu, 29 Jan 2026 18:37:21 -0500 Subject: [PATCH 11/12] pandas 3.0.0 safe code for model execution but NOT RegressTest. QUantile analysis reveals identical results vs hspf but RegressTest fails in pandas 3.0.0 with pandas.errors.IndexingError: Unalignable boolean Series provided as indexer (index of the boolean Series and of the indexed object do not match). --- src/hsp2/hsp2/utilities.py | 46 +++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/hsp2/hsp2/utilities.py b/src/hsp2/hsp2/utilities.py index d135458a..fe4672d6 100644 --- a/src/hsp2/hsp2/utilities.py +++ b/src/hsp2/hsp2/utilities.py @@ -213,35 +213,35 @@ def transform(ts, name, how, siminfo): NOTE: these routines work for both regular and sparse timeseries input """ - tsfreq = ts.index.freq.delta.to_timedelta64() + tsfreq = ts.index.freq fmins = Minute(siminfo["delt"]) - freq = Timedelta(fmins).to_timedelta64() + freq = fmins.nanos stop = siminfo["stop"] # append duplicate of last point to force processing last full interval if ts.index[-1] < stop: ts[stop] = ts.iloc[-1] - if freq == tsfreq: + if freq == tsfreq.nanos: pass elif tsfreq is None: # Sparse time base, frequency not defined ts = ts.reindex(siminfo["tbase"]).ffill().bfill() elif how == "SAME": - ts = ts.resample(fmins).ffill() # tsfreq >= freq assumed, or bad user choice + ts = ts.resample(fmins).ffill() # tsfreq.nanos >= freq assumed, or bad user choice elif not how: if name in flowtype: - if "Y" in str(tsfreq) or "M" in str(tsfreq) or tsfreq > freq: + if "Y" in str(tsfreq) or "M" in str(tsfreq) or tsfreq.nanos > freq: if "M" in str(tsfreq): ratio = 1.0 / 730.5 elif "Y" in str(tsfreq): ratio = 1.0 / 8766.0 else: - ratio = freq / tsfreq + ratio = freq / tsfreq.nanos ts = (ratio * ts).resample(fmins).ffill() # HSP2 how = div else: ts = ts.resample(fmins).sum() else: - if "Y" in str(tsfreq) or "M" in str(tsfreq) or tsfreq > freq: + if "Y" in str(tsfreq) or "M" in str(tsfreq) or tsfreq.nanos > freq: ts = ts.resample(fmins).ffiio_managerll() else: ts = ts.resample(fmins).mean() @@ -268,10 +268,10 @@ def transform(ts, name, how, siminfo): elif "Y" in str(tsfreq): ratio = 1.0 / (8766.0 * mult) else: - ratio = freq / tsfreq + ratio = freq / tsfreq.nanos ts = (ratio * ts).resample(fmins).ffill() # HSP2 how = div else: - ts = (ts * (freq / tsfreq)).resample(fmins).ffill() + ts = (ts * (freq / tsfreq.nanos)).resample(fmins).ffill() elif how == "ZEROFILL": ts = ts.resample(fmins).fillna(0.0) elif how == "INTERPOLATE": @@ -289,7 +289,7 @@ def hoursval(siminfo, hours24, dofirst=False, lapselike=False): start = siminfo["start"] stop = siminfo["stop"] fmins = Minute(siminfo["delt"]) - freq = Timedelta(fmins).to_timedelta64() + freq = fmins.nanos dr = date_range( start=f"{start.year}-01-01", end=f"{stop.year}-12-31", freq=Minute(60) @@ -299,16 +299,16 @@ def hoursval(siminfo, hours24, dofirst=False, lapselike=False): hours[0] = 1 ts = Series(hours[0 : len(dr)], dr) - tsfreq = ts.index.freq.delta.to_timedelta64() + tsfreq = ts.index.freq if lapselike: - if tsfreq > freq: # upsample + if tsfreq.nanos > freq: # upsample ts = ts.resample(fmins).asfreq().ffill() - elif tsfreq < freq: # downsample + elif tsfreq.nanos < freq: # downsample ts = ts.resample(fmins).mean() else: - if tsfreq > freq: # upsample + if tsfreq.nanos > freq: # upsample ts = ts.resample(fmins).asfreq().fillna(0.0) - elif tsfreq < freq: # downsample + elif tsfreq.nanos < freq: # downsample ts = ts.resample(fmins).max() return ts.truncate(start, stop).to_numpy() @@ -325,16 +325,16 @@ def monthval(siminfo, monthly): start = siminfo["start"] stop = siminfo["stop"] fmins = Minute(siminfo["delt"]) - freq = Timedelta(fmins).to_timedelta64() + freq = fmins.nanos months = tile(monthly, stop.year - start.year + 1).astype(float) dr = date_range(start=f"{start.year}-01-01", end=f"{stop.year}-12-31", freq="MS") ts = Series(months, index=dr).resample("D").ffill() - tsfreq = ts.index.freq.delta.to_timedelta64() + tsfreq = ts.index.freq - if tsfreq > freq: # upsample + if tsfreq.nanos > freq: # upsample ts = ts.resample(fmins).asfreq().ffill() - elif tsfreq < freq: # downsample + elif tsfreq.nanos < freq: # downsample ts = ts.resample(fmins).mean() return ts.truncate(start, stop).to_numpy() @@ -345,17 +345,17 @@ def dayval(siminfo, monthly): start = siminfo["start"] stop = siminfo["stop"] fmins = Minute(siminfo["delt"]) - freq = Timedelta(fmins).to_timedelta64() + freq = fmins.nanos months = tile(monthly, stop.year - start.year + 1).astype(float) dr = date_range(start=f"{start.year}-01-01", end=f"{stop.year}-12-31", freq="MS") ts = Series(months, index=dr).resample("D").interpolate("time") - tsfreq = ts.index.freq.delta.to_timedelta64() + tsfreq = ts.index.freq - if tsfreq > freq: # upsample + if tsfreq.nanos > freq: # upsample ts = ts.resample(fmins).ffill() - elif tsfreq < freq: # downsample + elif tsfreq.nanos < freq: # downsample ts = ts.resample(fmins).mean() return ts.truncate(start, stop).to_numpy() From 1e340475fbd694f86642ff152372cd4a7d56c3b4 Mon Sep 17 00:00:00 2001 From: "Robert W. Burgholzer" Date: Fri, 30 Jan 2026 13:32:56 +0000 Subject: [PATCH 12/12] revert from accident --- .../state_specl_ops/compare_eq_to_specl.py | 39 +- examples/state_specl_ops/debug_pyt_est.py | 77 + .../state_specl_ops/demo_specl_initialize.py | 104 +- src/hsp2/hsp2/HYDR.py | 136 +- src/hsp2/hsp2/RQUAL.py | 57 +- src/hsp2/hsp2/RQUAL_Class.py | 62 +- src/hsp2/hsp2/SEDMNT.py | 20 +- src/hsp2/hsp2/SEDTRN.py | 71 +- src/hsp2/hsp2/SPECL.py | 22 +- src/hsp2/hsp2/om.py | 328 ++- src/hsp2/hsp2/om_equation.py | 59 +- src/hsp2/hsp2/om_model_linkage.py | 71 +- src/hsp2/hsp2/om_model_object.py | 185 +- src/hsp2/hsp2/om_sim_timer.py | 95 +- src/hsp2/hsp2/om_special_action.py | 4 +- src/hsp2/hsp2/om_timer.py | 56 + src/hsp2/hsp2tools/readUCI.py | 1 - src/hsp2/state/state.py | 455 ++- src/hsp2/state/state_definitions.py | 31 + src/hsp2/state/state_fn_defaults.py | 7 - tests/convert/regression_base.py | 12 +- tests/test_regression.py | 22 +- tests/testcbp/HSP2results/JL1_6562_6560.uci | 261 ++ .../PL3_5250_0001.json.dynamic_wd.json | 19 + .../HSP2results/PL3_5250_0001.json.manyeq | 2515 +++++++++++++++++ tests/testcbp/HSP2results/PL3_5250_0001.uci | 3 - .../testcbp/HSP2results/PL3_5250_0001eq.json | 24 + .../testcbp/HSP2results/PL3_5250_0001spec.uci | 236 ++ ....json.constant_wd => PL3_5250_0001wd.json} | 4 +- tests/testcbp/HSP2results/PL3_5250_0001wd.uci | 227 ++ .../testcbp/HSP2results/check_endpoint_ts.py | 4 +- tests/testcbp/HSP2results/check_equation.py | 160 +- 32 files changed, 4554 insertions(+), 813 deletions(-) create mode 100644 examples/state_specl_ops/debug_pyt_est.py create mode 100644 src/hsp2/hsp2/om_timer.py create mode 100644 src/hsp2/state/state_definitions.py delete mode 100644 src/hsp2/state/state_fn_defaults.py create mode 100644 tests/testcbp/HSP2results/JL1_6562_6560.uci create mode 100644 tests/testcbp/HSP2results/PL3_5250_0001.json.dynamic_wd.json create mode 100644 tests/testcbp/HSP2results/PL3_5250_0001.json.manyeq create mode 100644 tests/testcbp/HSP2results/PL3_5250_0001eq.json create mode 100644 tests/testcbp/HSP2results/PL3_5250_0001spec.uci rename tests/testcbp/HSP2results/{PL3_5250_0001.json.constant_wd => PL3_5250_0001wd.json} (73%) create mode 100644 tests/testcbp/HSP2results/PL3_5250_0001wd.uci diff --git a/examples/state_specl_ops/compare_eq_to_specl.py b/examples/state_specl_ops/compare_eq_to_specl.py index af3b8a34..fe1caad5 100644 --- a/examples/state_specl_ops/compare_eq_to_specl.py +++ b/examples/state_specl_ops/compare_eq_to_specl.py @@ -11,6 +11,7 @@ from src.hsp2.hsp2tools.commands import import_uci, run from src.hsp2.hsp2tools.HDF5 import HDF5 from src.hsp2.hsp2tools.HBNOutput import HBNOutput +import tabulate def test_h5_file_exists(): assert os.path.exists('test10.h5') @@ -25,7 +26,7 @@ def test_h5_file_exists(): # get RCHRES 5 sedtrn silt ts_silt_hspf = hspf_data.get_time_series('RCHRES', 5, 'RSEDTOTSILT', 'SEDTRN', 'full') total_silt_hspf = ts_silt_hspf.mean() -quantile_silt_hspf = np.quantile(ts_silt_hspf,[0.0,0.10,0.5,0.5,0.75,0.9,0.95,1.0]) +quantile_silt_hspf = np.quantile(ts_silt_hspf,[0.0,0.25,0.5,0.75,1.0]) ############# HSPF NO SPECL hspf_nospecl_root = Path("tests/test10/HSPFresults") @@ -33,7 +34,7 @@ def test_h5_file_exists(): hspf_nospecl_data.read_data() ts_silt_nospecl_hspf = hspf_nospecl_data.get_time_series('RCHRES', 5, 'RSEDTOTSILT', 'SEDTRN', 'full') total_silt_nospecl_hspf = ts_silt_nospecl_hspf.mean() -quantile_silt_nospecl_hspf = np.quantile(ts_silt_nospecl_hspf,[0.0,0.10,0.5,0.5,0.75,0.9,0.95,1.0]) +quantile_silt_nospecl_hspf = np.quantile(ts_silt_nospecl_hspf,[0.0,0.25,0.5,0.75,1.0]) ############# hsp2 SPECL # Run and Analyze hsp2 WITH SPECL actions @@ -55,8 +56,8 @@ def test_h5_file_exists(): hsp2_specl_hydr5 = read_hdf(dstore_specl, '/RESULTS/RCHRES_R005/HYDR') hsp2_specl_sedtrn5 = read_hdf(dstore_specl, '/RESULTS/RCHRES_R005/SEDTRN') hsp2_specl_rsed5 = hsp2_specl_sedtrn5['RSED5'] -quantile_silt_hsp2 = np.quantile(hsp2_specl_rsed5,[0.0,0.10,0.5,0.5,0.75,0.9,0.95,1.0]) -quantile_ro_hsp2 = np.quantile(hsp2_specl_hydr5['RO'],[0.0,0.10,0.5,0.5,0.75,0.9,0.95,1.0]) +quantile_silt_hsp2 = np.quantile(hsp2_specl_rsed5,[0.0,0.25,0.5,0.75,1.0]) +quantile_ro_hsp2 = np.quantile(hsp2_specl_hydr5['RO'],[0.0,0.25,0.5,0.75,1.0]) total_silt_hsp2 = hsp2_specl_rsed5.mean() dstore_specl.close() @@ -64,10 +65,10 @@ def test_h5_file_exists(): # Run and Analyze hsp2 without SPECL actions nospecl_root = Path("tests/test10") nospecl_root.exists() -nospecl_root_hspf = Path(nospecl_root) / "HSPFresults" -hsp2_nospecl_uci = nospecl_root_hspf.resolve() / "test10.uci" +nospecl_root_hsp2 = Path(nospecl_root) / "HSP2results" +hsp2_nospecl_uci = nospecl_root_hsp2.resolve() / "test10.uci" hsp2_nospecl_uci.exists() -temp_nospecl_h5file = nospecl_root_hspf / "nospecl_case.h5" +temp_nospecl_h5file = nospecl_root_hsp2 / "test10.h5" # IF we want to run it from python, do this: #if temp_nospecl_h5file.exists(): @@ -80,8 +81,8 @@ def test_h5_file_exists(): hsp2_nospecl_hydr5 = read_hdf(dstore_nospecl, '/RESULTS/RCHRES_R005/HYDR') hsp2_nospecl_sedtrn5 = read_hdf(dstore_nospecl, '/RESULTS/RCHRES_R005/SEDTRN') hsp2_nospecl_rsed5 = hsp2_nospecl_sedtrn5['RSED5'] -np.quantile(hsp2_nospecl_rsed5,[0.0,0.10,0.5,0.5,0.75,0.9,0.95,1.0]) -np.quantile(hsp2_nospecl_hydr5['RO'],[0.0,0.10,0.5,0.5,0.75,0.9,0.95,1.0]) +quantile_silt_nospecl_hsp2 = np.quantile(hsp2_nospecl_rsed5,[0.0,0.25,0.5,0.75,1.0]) +np.quantile(hsp2_nospecl_hydr5['RO'],[0.0,0.25,0.5,0.75,1.0]) total_silt_nospecl_hsp2 = hsp2_nospecl_rsed5.mean() dstore_nospecl.close() @@ -106,8 +107,8 @@ def test_h5_file_exists(): hsp2_eq_hydr5 = read_hdf(dstore_eq_specl, '/RESULTS/RCHRES_R005/HYDR') hsp2_eq_sedtrn5 = read_hdf(dstore_eq_specl, '/RESULTS/RCHRES_R005/SEDTRN') hsp2_eq_rsed5 = hsp2_eq_sedtrn5['RSED5'] -quantile_silt_eq_hsp2 = np.quantile(hsp2_eq_rsed5,[0.0,0.10,0.5,0.5,0.75,0.9,0.95,1.0]) -quantile_ro_eq_hsp2 = np.quantile(hsp2_eq_hydr5['RO'],[0.0,0.10,0.5,0.5,0.75,0.9,0.95,1.0]) +quantile_silt_eq_hsp2 = np.quantile(hsp2_eq_rsed5,[0.0,0.25,0.5,0.75,1.0]) +quantile_ro_eq_hsp2 = np.quantile(hsp2_eq_hydr5['RO'],[0.0,0.25,0.5,0.75,1.0]) total_silt_eq_hsp2 = hsp2_eq_rsed5.mean() dstore_eq_specl.close() @@ -134,3 +135,19 @@ def test_h5_file_exists(): print("Total SiltHSP2 vs. HSPF, % difference = ", pct_dif_specl, "%") print("No SPECL: Total SiltHSP2 vs. HSPF, % difference = ", pct_dif_nospecl, "%") + +a = pd.DataFrame( + { + 'HSP2 no specl':quantile_silt_nospecl_hsp2, + 'HSPF no specl':quantile_silt_nospecl_hspf, + 'HSPF w/specl':quantile_silt_hspf, + 'HSP2 w/specl':quantile_silt_hsp2, + 'HSP2 w/EQ':quantile_silt_eq_hsp2 + } +) + + +tabulate(a, headers='keys', tablefmt='markdown') +tabulate(a, headers='keys', tablefmt='psql') +# Thi is better, it USES the tabulate lib but has usable format +a.to_markdown(index=False) diff --git a/examples/state_specl_ops/debug_pyt_est.py b/examples/state_specl_ops/debug_pyt_est.py new file mode 100644 index 00000000..5cd13d98 --- /dev/null +++ b/examples/state_specl_ops/debug_pyt_est.py @@ -0,0 +1,77 @@ +from pathlib import Path + +import pytest +from hsp2.hsp2tools.commands import import_uci, run +from hsp2.hsp2tools.HDF5 import HDF5 + +from convert.regression_base import RegressTest as RegressTestBase + + +class RegressTest(RegressTestBase): + def _get_hsp2_data(self, test_root) -> None: + test_root_hspf = Path(test_root) / "HSPFresults" + hspf_uci = test_root_hspf.resolve() / f"{self.compare_case}.uci" + assert hspf_uci.exists() + + temp_h5file = test_root_hspf / f"{self.compare_case}.h5" + if temp_h5file.exists(): + temp_h5file.unlink() + + self.temp_h5file = temp_h5file + + import_uci(str(hspf_uci), str(self.temp_h5file)) + run(self.temp_h5file, saveall=True, compress=False) + self.hsp2_data = HDF5(str(self.temp_h5file)) + + def _init_files(self): + test_dir = Path(__file__).resolve().parent + assert test_dir.name == "tests" + + test_root = test_dir / self.compare_case + assert test_root.exists() + + self._get_hbn_data(str(test_root)) + self._get_hsp2_data(str(test_root)) + + +@pytest.mark.parametrize( + "case", + [ + # "test05", + # "test09", + "test10", + "test10specl", + # "test10b", + ], +) +def test_case(case): + test = RegressTest(case, threads=1) + results = test.run_test() + test.temp_h5file.unlink() + + found = False + mismatches = [] + for key, results in results.items(): + no_data_hsp2, no_data_hspf, match, diff = results + if any([no_data_hsp2, no_data_hspf]): + continue + if not match: + mismatches.append((case, key, results)) + found = True + assert found + + if mismatches: + for case, key, results in mismatches: + _, _, _, diff = results + print(case, key, f"{diff:0.00%}") + raise ValueError("results don't match hspf output") + + +# demo the code in regression_base.py with this: +# creates a shell object namd "self" that can have attributes just added +self = type('', (), {})() +self.compare_case='test10' +self.html_file = os.path.join( + tests_root_dir, f"HSPF_HSP2_{self.compare_case}.html" +) +self.html_file diff --git a/examples/state_specl_ops/demo_specl_initialize.py b/examples/state_specl_ops/demo_specl_initialize.py index 2f6b3fbb..1c79b71c 100644 --- a/examples/state_specl_ops/demo_specl_initialize.py +++ b/examples/state_specl_ops/demo_specl_initialize.py @@ -1,36 +1,90 @@ +# Must be run from the HSPsquared source directory, the h5 file has already been setup with hsp import_uci test10.uci +# bare bones tester - must be run from the HSPsquared source directory -########################################################################################## -# LOAD HSP2 RUNTIME CODE AND UCI FILE -########################################################################################## -import os, numpy +import os +import numpy from hsp2.hsp2.main import * +from hsp2.state.state import * from hsp2.hsp2.om import * -from hsp2.hsp2.state import * +from hsp2.hsp2.SPECL import * from hsp2.hsp2io.hdf import HDF5 from hsp2.hsp2io.io import IOManager -hdf5_instance = HDF5("./tests/test10specl/HSP2results/test10specl.demo.h5") +from hsp2.state.state import * +from hsp2.hsp2.om_timer import timer_class +fpath = "./tests/test10specl/HSP2results/test10specl.h5" +timer = timer_class() +# try also: +# fpath = './tests/testcbp/HSP2results/JL1_6562_6560.h5' -iomanager = IOManager(hdf5_instance) -uci_obj = iomanager.read_uci() -siminfo = uci_obj.siminfo -opseq = uci_obj.opseq -state = init_state_dicts() -state_siminfo_hsp2(uci_obj, siminfo, iomanager, state) +# sometimes when testing you may need to close the file, so try: +# f = h5py.File(fpath,'a') # use mode 'a' which allows read, write, modify +# # f.close() +hdf5_instance = HDF5(fpath) +io_manager = IOManager(hdf5_instance) + +parameter_obj = io_manager.read_parameters() +opseq = parameter_obj.opseq +ddlinks = parameter_obj.ddlinks +ddmasslinks = parameter_obj.ddmasslinks +ddext_sources = parameter_obj.ddext_sources +ddgener = parameter_obj.ddgener +model = parameter_obj.model +siminfo = parameter_obj.siminfo +ftables = parameter_obj.ftables +specactions = parameter_obj.specactions +monthdata = parameter_obj.monthdata -# Add support for dynamic functions to operate on STATE -state_load_dynamics_hsp2(state, iomanager, siminfo) -state_init_hsp2(state, opseq, activities) -state["specactions"] = uci_obj.specactions # stash the specaction dict in state -om_init_state(state) # set up operational model specific state entries -specl_load_state(state, iomanager, siminfo) # traditional special actions -state_load_dynamics_om(state, iomanager, siminfo) -state_om_model_run_prep(state, iomanager, siminfo) -state_context_hsp2(state, "RCHRES", "R005", "SEDTRN") -domain, state_paths, state_ix, dict_ix, ts_ix, op_tokens = state["domain"], state["state_paths"], state["state_ix"], state["dict_ix"], state["ts_ix"], state["op_tokens"] -ep_list = np.asarray(["RSED1", "RSED2", "RSED3", "RSED4", "RSED5", "RSED6"], dtype='U') -model_exec_list = model_domain_dependencies(state, domain, ep_list, True) -get_domain_state(state_paths, state_ix, domain, ep_list) +# read user control, parameters, states, and flags parameters and map to local variables +parameter_obj = io_manager.read_parameters() +opseq = parameter_obj.opseq +ddlinks = parameter_obj.ddlinks +ddmasslinks = parameter_obj.ddmasslinks +ddext_sources = parameter_obj.ddext_sources +ddgener = parameter_obj.ddgener +model = parameter_obj.model +siminfo = parameter_obj.siminfo +ftables = parameter_obj.ftables +specactions = parameter_obj.specactions +monthdata = parameter_obj.monthdata +start, stop = siminfo["start"], siminfo["stop"] +copy_instances = {} +gener_instances = {} +section_timing = {} + +section_timing["io_manager.read_parameters() call and config"] = str(timer.split()) + "seconds" +####################################################################################### +# initialize STATE dicts +####################################################################################### +# Set up Things in state that will be used in all modular activities like SPECL +state = state_class( + state_empty["state_ix"], state_empty["op_tokens"], state_empty["state_paths"], + state_empty["op_exec_lists"], state_empty["model_exec_list"], state_empty["dict_ix"], + state_empty["ts_ix"], state_empty["hsp_segments"] +) +om_operations = om_init_state() # set up operational model specific containers +state_siminfo_hsp2(state, parameter_obj, siminfo, io_manager) +state_om_model_root_object(state, om_operations, siminfo) +# Iterate through all segments and add crucial paths to state +# before loading dynamic components that may reference them +state_init_hsp2(state, opseq, activities, timer) +om_init_hsp2_segments(state, om_operations) +# now initialize all state variables for mutable variables +hsp2_domain_dependencies(state, opseq, activities, om_operations, False) +# Add support for dynamic functions to operate on STATE +# - Load any dynamic components if present, and store variables on objects +state_load_dynamics_hsp2(state, io_manager, siminfo) +# - finally stash specactions in state, not domain (segment) dependent so do it once +specl_load_om(om_operations, specactions) # load traditional special actions +state_load_dynamics_om( + state, io_manager, siminfo, om_operations +) # operational model for custom python +# finalize all dynamically loaded components and prepare to run the model +state_om_model_run_prep(opseq, activities, state, om_operations, siminfo) +section_timing["state om initialization()"] = str(timer.split()) + "seconds" +statenb = state_class_lite(0) +state_copy(state, statenb) +####################################################################################### diff --git a/src/hsp2/hsp2/HYDR.py b/src/hsp2/hsp2/HYDR.py index 8caa0ece..c2f3c50e 100644 --- a/src/hsp2/hsp2/HYDR.py +++ b/src/hsp2/hsp2/HYDR.py @@ -11,16 +11,16 @@ """ -from numpy import zeros, any, full, nan, array, int64, arange, asarray +from numpy import zeros, any, full, nan, array, int64, arange from pandas import DataFrame from math import sqrt, log10 -from numba import njit, types +from numba import njit from numba.typed import List from hsp2.hsp2.utilities import initm, make_numba_dict # the following imports added by rb to handle dynamic code and special actions -from hsp2.state.state import hydr_get_ix, hydr_init_ix, hydr_state_vars -from hsp2.hsp2.om import pre_step_model, step_model, model_domain_dependencies +from hsp2.state.state import hydr_get_ix, get_state_ix +from hsp2.hsp2.om import pre_step_model, step_model from numba.typed import Dict @@ -36,7 +36,7 @@ MAXLOOPS = 100 # newton method exit tolerance -def hydr(io_manager, siminfo, parameters, ts, ftables, state): +def hydr(siminfo, parameters, ts, ftables, state): """find the state of the reach/reservoir at the end of the time interval and the outflows during the interval @@ -48,7 +48,9 @@ def hydr(io_manager, siminfo, parameters, ts, ftables, state): state is a dictionary that contains all dynamic code dictionaries such as: - specactions is a dictionary with all special actions """ - + # TBD: These operations are all preparatory in nature, and will be replaced by code + # in the RCHRES_handler class, which will set properties on RCHRES_class for fast + # and concide run-time execution and memory management. steps = siminfo["steps"] # number of simulation points uunits = siminfo["units"] nexits = int(parameters["PARAMETERS"]["NEXITS"]) @@ -140,41 +142,21 @@ def hydr(io_manager, siminfo, parameters, ts, ftables, state): ####################################################################################### # the following section (1 of 3) added to HYDR by rb to handle dynamic code and special actions ####################################################################################### - # state_info is some generic things about the simulation - # must be numba safe, so we don't just pass the whole state which is not - state_info = Dict.empty(key_type=types.unicode_type, value_type=types.unicode_type) - state_info["operation"], state_info["segment"], state_info["activity"] = ( - state["operation"], - state["segment"], - state["activity"], - ) - state_info["domain"], state_info["state_step_hydr"], state_info["state_step_om"] = ( - state["domain"], - state["state_step_hydr"], - state["state_step_om"], - ) - hsp2_local_py = state["hsp2_local_py"] + hsp2_local_py = state.hsp2_local_py # It appears necessary to load this here, instead of from main.py, otherwise, # _hydr_() does not recognize the function state_step_hydr()? if hsp2_local_py != False: from hsp2_local_py import state_step_hydr else: - from hsp2.state.state_fn_defaults import state_step_hydr - # initialize the hydr paths in case they don't already reside here - hydr_init_ix(state, state["domain"]) - # must split dicts out of state Dict since numba cannot handle mixed-type nested Dicts - state_ix, dict_ix, ts_ix = state["state_ix"], state["dict_ix"], state["ts_ix"] - state_paths = state["state_paths"] - ep_list = ( - hydr_state_vars() - ) # define all eligibile for state integration in state.py - # note: calling dependencies with 4th arg = True grabs only "runnable" types, which can save time - # in long simulations, as iterating through non-runnables like Constants consumes time. - model_exec_list = model_domain_dependencies( - state, state_info["domain"], ep_list, True - ) - model_exec_list = asarray(model_exec_list, dtype="i8") # format for use in numba - op_tokens = state["op_tokens"] + from hsp2.state.state_definitions import state_step_hydr + # note: get executable dynamic operation model components + # TBD: this will be set as a property on each RCHRES object when we move to a class framework + activity_path = state.domain + "/" + 'HYDR' + #print("HYDR activity_path", activity_path) + activity_id = get_state_ix(state.state_paths, activity_path) + model_exec_list = state.op_exec_lists[activity_id] + #print("model_exec_list", model_exec_list) + #print(state.domain, "HYDR called with", state.domain, len(model_exec_list), "op elements.") ####################################################################################### # Do the simulation with _hydr_ (ie run reaches simulation code) @@ -187,14 +169,9 @@ def hydr(io_manager, siminfo, parameters, ts, ftables, state): funct, Olabels, OVOLlabels, - state_info, - state_paths, - state_ix, - dict_ix, - ts_ix, + state, state_step_hydr, - op_tokens, - model_exec_list, + model_exec_list ) if "O" in ts: @@ -206,12 +183,10 @@ def hydr(io_manager, siminfo, parameters, ts, ftables, state): parameters["PARAMETERS"]["ROS"] = ui["ROS"] for i in range(nexits): parameters["PARAMETERS"]["OS" + str(i + 1)] = ui["OS" + str(i + 1)] - # copy back (modified) operational element data - state["state_ix"], state["dict_ix"], state["ts_ix"] = state_ix, dict_ix, ts_ix return errors, ERRMSGS -@njit(cache=True) +#@njit(cache=True) def _hydr_( ui, ts, @@ -221,17 +196,11 @@ def _hydr_( funct, Olabels, OVOLlabels, - state_info, - state_paths, - state_ix, - dict_ix, - ts_ix, + state, state_step_hydr, - op_tokens, - model_exec_list, + model_exec_list ): errors = zeros(int(ui["errlen"])).astype(int64) - steps = int(ui["steps"]) # number of simulation steps delts = ui["delt"] * 60.0 # seconds in simulation interval uunits = ui["uunits"] @@ -368,19 +337,24 @@ def _hydr_( # other initial vars rovol = 0.0 volev = 0.0 - IVOL0 = ts["IVOL"] # the actual inflow in simulation native units ####################################################################################### # the following section (2 of 3) added by rb to HYDR, this one to prepare for dynamic state including special actions ####################################################################################### - hydr_ix = hydr_get_ix(state_ix, state_paths, state_info["domain"]) + hydr_ix = hydr_get_ix(state, state.domain) # these are integer placeholders faster than calling the array look each timestep + # TBD: These will be replaced by class properties in HYDR_class o1_ix, o2_ix, o3_ix, ivol_ix = ( hydr_ix["O1"], hydr_ix["O2"], hydr_ix["O3"], hydr_ix["IVOL"], ) + ovol1_ix, ovol2_ix, ovol3_ix = ( + hydr_ix["OVOL1"], + hydr_ix["OVOL2"], + hydr_ix["OVOL3"], + ) ro_ix, rovol_ix, volev_ix, vol_ix = ( hydr_ix["RO"], hydr_ix["ROVOL"], @@ -389,12 +363,16 @@ def _hydr_( ) # handle varying length outdgt out_ix = arange(nexits) + ovol_ix = arange(nexits) if nexits > 0: out_ix[0] = o1_ix + ovol_ix[0] = ovol1_ix if nexits > 1: out_ix[1] = o2_ix + ovol_ix[1] = ovol2_ix if nexits > 2: out_ix[2] = o3_ix + ovol_ix[2] = ovol3_ix ####################################################################################### # HYDR (except where noted) @@ -408,40 +386,45 @@ def _hydr_( ####################################################################################### # the following section (3 of 3) added by rb to accommodate dynamic code, operations models, and special actions ####################################################################################### - # set state_ix with value of local state variables and/or needed vars - # Note: we pass IVOL0, not IVOL here since IVOL has been converted to different units - state_ix[ro_ix], state_ix[rovol_ix] = ro, rovol + # set state.state_ix with value of local state variables and/or needed vars + state.state_ix[ro_ix], state.state_ix[rovol_ix] = ro, rovol di = 0 for oi in range(nexits): - state_ix[out_ix[oi]] = outdgt[oi] - state_ix[vol_ix], state_ix[ivol_ix] = vol, IVOL0[step] - state_ix[volev_ix] = volev + # Write OVOL for use in equations/specacts. Note: this must be improved! too much code... + state.state_ix[ovol_ix[oi]] = ovol[oi] + + state.state_ix[vol_ix], state.state_ix[ivol_ix] = vol, IVOL[step] + state.state_ix[volev_ix] = volev # - these if statements may be irrelevant if default functions simply return # when no objects are defined. - if state_info["state_step_om"] == "enabled": - pre_step_model(model_exec_list, op_tokens, state_ix, dict_ix, ts_ix, step) - if state_info["state_step_hydr"] == "enabled": + if state.state_step_om == "enabled": + pre_step_model(model_exec_list, state.op_tokens, state.state_ix, state.dict_ix, state.ts_ix, step) + + if state.state_step_hydr == "enabled": state_step_hydr( - state_info, state_paths, state_ix, dict_ix, ts_ix, hydr_ix, step + state, step ) - if state_info["state_step_om"] == "enabled": + + if state.state_step_om == "enabled": # print("trying to execute state_step_om()") # model_exec_list contains the model exec list in dependency order # now these are all executed at once, but we need to make them only for domain end points + #if step < 2: + # print("Calling step_model with", len(model_exec_list), "out of", len(state.op_tokens), "tokens") step_model( - model_exec_list, op_tokens, state_ix, dict_ix, ts_ix, step + model_exec_list, state.op_tokens, state.state_ix, state.dict_ix, state.ts_ix, step ) # traditional 'ACTIONS' done in here - if (state_info["state_step_hydr"] == "enabled") or ( - state_info["state_step_om"] == "enabled" + if (state.state_step_hydr == "enabled") or ( + state.state_step_om == "enabled" ): # Do write-backs for editable STATE variables # OUTDGT is writeable for oi in range(nexits): - outdgt[oi] = state_ix[out_ix[oi]] + outdgt[oi] = state.state_ix[out_ix[oi]] # IVOL is writeable. # Note: we must convert IVOL to the units expected in _hydr_ # maybe routines should do this, and this is not needed (but pass VFACT in state) - IVOL[step] = state_ix[ivol_ix] * VFACT + IVOL[step] = state.state_ix[ivol_ix] # End dynamic code step() ####################################################################################### @@ -866,12 +849,3 @@ def expand_HYDR_masslinks(flags, parameters, dat, recs): rec["SVOL"] = dat.SVOL recs.append(rec) return recs - - -def hydr_load_om(state, io_manager, siminfo): - for i in hydr_state_vars(): - state["model_data"][seg_name][i] = { - "object_class": "ModelVariable", - "name": i, - "value": 0.0, - } diff --git a/src/hsp2/hsp2/RQUAL.py b/src/hsp2/hsp2/RQUAL.py index 81985ec8..3253ad4a 100644 --- a/src/hsp2/hsp2/RQUAL.py +++ b/src/hsp2/hsp2/RQUAL.py @@ -6,14 +6,13 @@ import numpy as np from numba import njit, types from numba.typed import Dict -from numpy import full, zeros, asarray +from numpy import full, zeros from hsp2.hsp2.RQUAL_Class import RQUAL_Class from hsp2.hsp2.utilities import initm, initmd, make_numba_dict # the following imports added to handle special actions -from hsp2.state.state import rqual_init_ix, rqual_state_vars -from hsp2.hsp2.om import model_domain_dependencies +from hsp2.state.state import get_state_ix ERRMSGS_oxrx = ( "OXRX: Warning -- SATDO is less than zero. This usually occurs when water temperature is very high (above ~66 deg. C). This usually indicates an error in input GATMP (or TW, if HTRCH is not being simulated).", @@ -251,33 +250,10 @@ def rqual( ####################################################################################### # the following section (1 of 3) added to RQUAL by pbd to handle special actions ####################################################################################### - # state_info is some generic things about the simulation - # must be numba safe, so we don't just pass the whole state which is not - state_info = Dict.empty(key_type=types.unicode_type, value_type=types.unicode_type) - state_info["operation"], state_info["segment"], state_info["activity"] = ( - state["operation"], - state["segment"], - state["activity"], - ) - state_info["domain"], state_info["state_step_hydr"], state_info["state_step_om"] = ( - state["domain"], - state["state_step_hydr"], - state["state_step_om"], - ) - # must split dicts out of state Dict since numba cannot handle mixed-type nested Dicts - # initialize the rqual paths in case they don't already reside here - rqual_init_ix(state, state["domain"]) - state_ix, dict_ix, ts_ix = state["state_ix"], state["dict_ix"], state["ts_ix"] - state_paths = state["state_paths"] - op_tokens = state["op_tokens"] - # Aggregate the list of all RQUAL end point dependencies - ep_list = ( - rqual_state_vars() - ) # define all eligibile for state integration in state.py - model_exec_list = model_domain_dependencies( - state, state_info["domain"], ep_list, True - ) - model_exec_list = asarray(model_exec_list, dtype="i8") + # Aggregate the list of all SEDTRN end point dependencies + activity_path = state.domain + "/" + 'SEDTRN' + activity_id = get_state_ix(state.state_paths, activity_path) + model_exec_list = state.op_exec_lists[activity_id] ####################################################################################### # --------------------------------------------------------------------- @@ -292,12 +268,7 @@ def rqual( ui_plank, ui_phcarb, ts, - state_info, - state_paths, - state_ix, - dict_ix, - ts_ix, - op_tokens, + state, model_exec_list, ) @@ -364,12 +335,7 @@ def _rqual_run( ui_plank, ui_phcarb, ts, - state_info, - state_paths, - state_ix, - dict_ix, - ts_ix, - op_tokens, + state, model_exec_list, ): nutrx_errors = zeros((0), dtype=np.int64) @@ -382,12 +348,7 @@ def _rqual_run( # run WQ simulation: RQUAL.simulate( ts, - state_info, - state_paths, - state_ix, - dict_ix, - ts_ix, - op_tokens, + state, model_exec_list, ) diff --git a/src/hsp2/hsp2/RQUAL_Class.py b/src/hsp2/hsp2/RQUAL_Class.py index 81b3ae16..4448bcaf 100644 --- a/src/hsp2/hsp2/RQUAL_Class.py +++ b/src/hsp2/hsp2/RQUAL_Class.py @@ -823,18 +823,13 @@ def __init__(self, siminfo, ui, ui_oxrx, ui_nutrx, ui_plank, ui_phcarb, ts): def simulate( self, ts, - state_info, - state_paths, - state_ix, - dict_ix, - ts_ix, - op_tokens, + state, model_exec_list, ): ####################################################################################### # the following section (2 of 3) added by pbd to RQUAL, this one to prepare for special actions ####################################################################################### - rqual_ix = rqual_get_ix(state_ix, state_paths, state_info["domain"]) + rqual_ix = rqual_get_ix(state, state.domain) # these are integer placeholders faster than calling the array look each timestep dox_ix = rqual_ix["DOX"] bod_ix = rqual_ix["BOD"] @@ -854,40 +849,41 @@ def simulate( # the following section (3 of 3) added by pbd to accommodate special actions ####################################################################################### # set state_ix with value of local state variables and/or needed vars - state_ix[dox_ix] = self.OXRX.dox - state_ix[bod_ix] = self.OXRX.bod - state_ix[no3_ix] = self.NUTRX.no3 - state_ix[tam_ix] = self.NUTRX.tam - state_ix[no2_ix] = self.NUTRX.no2 - state_ix[po4_ix] = self.NUTRX.po4 - state_ix[brtam1_ix] = self.NUTRX.brtam[0] - state_ix[brtam2_ix] = self.NUTRX.brtam[1] - state_ix[brpo41_ix] = self.NUTRX.brpo4[0] - state_ix[brpo42_ix] = self.NUTRX.brpo4[1] - state_ix[cforea_ix] = self.OXRX.cforea - if state_info["state_step_om"] == "enabled": + state.state_ix[dox_ix] = self.OXRX.dox + state.state_ix[bod_ix] = self.OXRX.bod + state.state_ix[no3_ix] = self.NUTRX.no3 + state.state_ix[tam_ix] = self.NUTRX.tam + state.state_ix[no2_ix] = self.NUTRX.no2 + state.state_ix[po4_ix] = self.NUTRX.po4 + state.state_ix[brtam1_ix] = self.NUTRX.brtam[0] + state.state_ix[brtam2_ix] = self.NUTRX.brtam[1] + state.state_ix[brpo41_ix] = self.NUTRX.brpo4[0] + state.state_ix[brpo42_ix] = self.NUTRX.brpo4[1] + state.state_ix[cforea_ix] = self.OXRX.cforea + if state.state_step_om == "enabled": pre_step_model( - model_exec_list, op_tokens, state_ix, dict_ix, ts_ix, step=loop + model_exec_list, state.op_tokens, state.state_ix, state.dict_ix, state.ts_ix, step=loop ) # (todo) Insert code hook for dynamic python modification of state - if state_info["state_step_om"] == "enabled": + if state.state_step_om == "enabled": + # (todo) migrate runtime jit code to new state object model step_model( - model_exec_list, op_tokens, state_ix, dict_ix, ts_ix, step=loop + model_exec_list, state.op_tokens, state.state_ix, state.dict_ix, state.ts_ix, step=loop ) # traditional 'ACTIONS' done in here # Do write-backs for editable STATE variables - self.OXRX.dox = state_ix[dox_ix] - self.OXRX.bod = state_ix[bod_ix] - self.NUTRX.no3 = state_ix[no3_ix] - self.NUTRX.tam = state_ix[tam_ix] - self.NUTRX.no2 = state_ix[no2_ix] - self.NUTRX.po4 = state_ix[po4_ix] - self.NUTRX.brtam[0] = state_ix[brtam1_ix] - self.NUTRX.brtam[1] = state_ix[brtam2_ix] - self.NUTRX.brpo4[0] = state_ix[brpo41_ix] - self.NUTRX.brpo4[1] = state_ix[brpo42_ix] - self.OXRX.cforea = state_ix[cforea_ix] + self.OXRX.dox = state.state_ix[dox_ix] + self.OXRX.bod = state.state_ix[bod_ix] + self.NUTRX.no3 = state.state_ix[no3_ix] + self.NUTRX.tam = state.state_ix[tam_ix] + self.NUTRX.no2 = state.state_ix[no2_ix] + self.NUTRX.po4 = state.state_ix[po4_ix] + self.NUTRX.brtam[0] = state.state_ix[brtam1_ix] + self.NUTRX.brtam[1] = state.state_ix[brtam2_ix] + self.NUTRX.brpo4[0] = state.state_ix[brpo41_ix] + self.NUTRX.brpo4[1] = state.state_ix[brpo42_ix] + self.OXRX.cforea = state.state_ix[cforea_ix] ####################################################################################### # ------------------------------------------------------- diff --git a/src/hsp2/hsp2/SEDMNT.py b/src/hsp2/hsp2/SEDMNT.py index 6818d1e5..916665b5 100644 --- a/src/hsp2/hsp2/SEDMNT.py +++ b/src/hsp2/hsp2/SEDMNT.py @@ -59,21 +59,21 @@ def sedmnt(io_manager, siminfo, parameters, ts, state): # must be numba safe, so we don't just pass the whole state which is not state_info = Dict.empty(key_type=types.unicode_type, value_type=types.unicode_type) state_info["operation"], state_info["segment"], state_info["activity"] = ( - state["operation"], - state["segment"], - state["activity"], + state.operation, + state.segment, + state.activity, ) state_info["domain"], state_info["state_step_hydr"], state_info["state_step_om"] = ( - state["domain"], - state["state_step_hydr"], - state["state_step_om"], + state.domain, + state.state_step_hydr, + state.state_step_om, ) # must split dicts out of state Dict since numba cannot handle mixed-type nested Dicts # initialize the sedmnt paths in case they don't already reside here - sedmnt_init_ix(state, state["domain"]) - state_ix, dict_ix, ts_ix = state["state_ix"], state["dict_ix"], state["ts_ix"] - state_paths = state["state_paths"] - op_tokens = state["op_tokens"] + sedmnt_init_ix(state, state.domain) + state_ix, dict_ix, ts_ix = state.state_ix, state.dict_ix, state.ts_ix + state_paths = state.state_paths + op_tokens = state.op_tokens # Aggregate the list of all SEDMNT end point dependencies ep_list = ( sedmnt_state_vars() diff --git a/src/hsp2/hsp2/SEDTRN.py b/src/hsp2/hsp2/SEDTRN.py index c6c5f1d1..99e65287 100644 --- a/src/hsp2/hsp2/SEDTRN.py +++ b/src/hsp2/hsp2/SEDTRN.py @@ -10,8 +10,8 @@ from hsp2.hsp2.utilities import make_numba_dict # the following imports added to handle special actions -from hsp2.state.state import sedtrn_get_ix, sedtrn_init_ix, sedtrn_state_vars -from hsp2.hsp2.om import pre_step_model, step_model, model_domain_dependencies +from hsp2.state.state import sedtrn_get_ix, get_state_ix +from hsp2.hsp2.om import pre_step_model, step_model from numba.typed import Dict ERRMSGS = ( @@ -25,7 +25,7 @@ ) # ERRMSG6 -def sedtrn(io_manager, siminfo, parameters, ts, state): +def sedtrn(siminfo, parameters, ts, state): """Simulate behavior of inorganic sediment""" # simlen = siminfo['steps'] @@ -91,19 +91,6 @@ def sedtrn(io_manager, siminfo, parameters, ts, state): ####################################################################################### # the following section (1 of 3) added to SEDTRN by pbd to handle special actions ####################################################################################### - # state_info is some generic things about the simulation - # must be numba safe, so we don't just pass the whole state which is not - state_info = Dict.empty(key_type=types.unicode_type, value_type=types.unicode_type) - state_info["operation"], state_info["segment"], state_info["activity"] = ( - state["operation"], - state["segment"], - state["activity"], - ) - state_info["domain"], state_info["state_step_hydr"], state_info["state_step_om"] = ( - state["domain"], - state["state_step_hydr"], - state["state_step_om"], - ) # hsp2_local_py = state['hsp2_local_py'] # # It appears necessary to load this here, instead of from main.py, otherwise, # # _hydr_() does not recognize the function state_step_hydr()? @@ -112,31 +99,17 @@ def sedtrn(io_manager, siminfo, parameters, ts, state): # else: # from hsp2.state.state_fn_defaults import state_step_hydr # must split dicts out of state Dict since numba cannot handle mixed-type nested Dicts - # initialize the sedtrn paths in case they don't already reside here - sedtrn_init_ix(state, state["domain"]) - state_ix, dict_ix, ts_ix = state["state_ix"], state["dict_ix"], state["ts_ix"] - state_paths = state["state_paths"] - op_tokens = state["op_tokens"] # Aggregate the list of all SEDTRN end point dependencies - ep_list = ( - sedtrn_state_vars() - ) # define all eligibile for state integration in state.py - model_exec_list = model_domain_dependencies( - state, state_info["domain"], ep_list, True - ) - model_exec_list = asarray(model_exec_list, dtype="i8") # format for use in + activity_path = state.domain + "/" + 'SEDTRN' + activity_id = get_state_ix(state.state_paths, activity_path) + model_exec_list = state.op_exec_lists[activity_id] ####################################################################################### ############################################################################ errors = _sedtrn_( ui, ts, - state_info, - state_paths, - state_ix, - dict_ix, - ts_ix, - op_tokens, + state, model_exec_list, ) # run SEDTRN simulation code ############################################################################ @@ -165,12 +138,7 @@ def sedtrn(io_manager, siminfo, parameters, ts, state): def _sedtrn_( ui, ts, - state_info, - state_paths, - state_ix, - dict_ix, - ts_ix, - op_tokens, + state, model_exec_list, ): """Simulate behavior of inorganic sediment""" @@ -403,7 +371,7 @@ def _sedtrn_( ####################################################################################### # the following section (2 of 3) added by pbd to SEDTRN, this one to prepare for special actions ####################################################################################### - sedtrn_ix = sedtrn_get_ix(state_ix, state_paths, state_info["domain"]) + sedtrn_ix = sedtrn_get_ix(state, state.domain) # these are integer placeholders faster than calling the array look each timestep rsed4_ix, rsed5_ix, rsed6_ix = ( sedtrn_ix["RSED4"], @@ -417,24 +385,25 @@ def _sedtrn_( # the following section (3 of 3) added by pbd to accommodate special actions ####################################################################################### # set state_ix with value of local state variables and/or needed vars - state_ix[rsed4_ix] = sand_wt_rsed4 - state_ix[rsed5_ix] = silt_wt_rsed5 - state_ix[rsed6_ix] = clay_wt_rsed6 - if state_info["state_step_om"] == "enabled": + state.state_ix[rsed4_ix] = sand_wt_rsed4 + state.state_ix[rsed5_ix] = silt_wt_rsed5 + state.state_ix[rsed6_ix] = clay_wt_rsed6 + if state.state_step_om == "enabled": pre_step_model( - model_exec_list, op_tokens, state_ix, dict_ix, ts_ix, step=loop + model_exec_list, state.op_tokens, state.state_ix, state.dict_ix, state.ts_ix, step=loop ) # (todo) Insert code hook for dynamic python modification of state - if state_info["state_step_om"] == "enabled": + if state.state_step_om == "enabled": + # (todo) migrate runtime jit code to new state object model step_model( - model_exec_list, op_tokens, state_ix, dict_ix, ts_ix, step=loop + model_exec_list, state.op_tokens, state.state_ix, state.dict_ix, state.ts_ix, step=loop ) # traditional 'ACTIONS' done in here # Do write-backs for editable STATE variables - sand_wt_rsed4 = state_ix[rsed4_ix] - silt_wt_rsed5 = state_ix[rsed5_ix] - clay_wt_rsed6 = state_ix[rsed6_ix] + sand_wt_rsed4 = state.state_ix[rsed4_ix] + silt_wt_rsed5 = state.state_ix[rsed5_ix] + clay_wt_rsed6 = state.state_ix[rsed6_ix] ####################################################################################### # perform any necessary unit conversions diff --git a/src/hsp2/hsp2/SPECL.py b/src/hsp2/hsp2/SPECL.py index a4f16b42..a187cf1e 100644 --- a/src/hsp2/hsp2/SPECL.py +++ b/src/hsp2/hsp2/SPECL.py @@ -9,33 +9,33 @@ from numba import njit -def specl_load_om(state, io_manager, siminfo): - if "ACTIONS" in state["specactions"]: - dc = state["specactions"]["ACTIONS"] +def specl_load_om(om_operations, specactions): + if "ACTIONS" in specactions: + dc = specactions["ACTIONS"] for ix in dc.index: # add the items to the state['model_data'] dict speca = dc[ix : (ix + 1)] # need to add a name attribute opname = "SPEC" + "ACTION" + str(ix) - state["model_data"][opname] = {} - state["model_data"][opname]["name"] = opname + om_operations["model_data"][opname] = {} + om_operations["model_data"][opname]["name"] = opname for ik in speca.keys(): # print("looking for speca key ", ik) - state["model_data"][opname][ik] = speca.to_dict()[ik][ + om_operations["model_data"][opname][ik] = speca.to_dict()[ik][ ix ] # add subscripts? if ik == "VARI": if len(speca.to_dict()["S1"][ix]) > 0: - state["model_data"][opname][ik] += speca.to_dict()["S1"][ix] + om_operations["model_data"][opname][ik] += speca.to_dict()["S1"][ix] if len(speca.to_dict()["S2"][ix]) > 0: - state["model_data"][opname][ik] += speca.to_dict()["S2"][ix] - state["model_data"][opname]["object_class"] = "SpecialAction" + om_operations["model_data"][opname][ik] += speca.to_dict()["S2"][ix] + om_operations["model_data"][opname]["object_class"] = "SpecialAction" # print("model_data", ix, " = ", state['model_data'][opname]) return -def specl_load_state(state, io_manager, siminfo): - specl_load_om(state, io_manager, siminfo) +def specl_load_state(om_operations, specactions): + specl_load_om(om_operations, specactions) # others defined below, like: # specl_load_uvnames(state, io_manager, siminfo) # ... diff --git a/src/hsp2/hsp2/om.py b/src/hsp2/hsp2/om.py index a85f579e..066090e7 100644 --- a/src/hsp2/hsp2/om.py +++ b/src/hsp2/hsp2/om.py @@ -5,24 +5,21 @@ # defined aove that are called by the object classes import json import os -import pandas as pd -import numpy as np import time -from numpy import zeros + +import numpy as np +from pandas import Series, DataFrame from numba import njit # import the types -from hsp2.state.state import append_state, get_ix_path +from numpy import zeros +from hsp2.hsp2.om_timer import timer_class - -def get_exec_order(model_exec_list, var_ix): - """ - Find the integer key of a variable name in state_ix - """ - model_exec_list = dict(enumerate(model_exec_list.flatten(), 1)) - for exec_order, ix in model_exec_list.items(): - if var_ix == ix: - # we need to add this to the state - return exec_order - return False +from hsp2.state.state import ( + append_state, + hydr_init_ix, + rqual_init_ix, + sedmnt_init_ix, + sedtrn_init_ix, +) def init_op_tokens(op_tokens, tops, eq_ix): @@ -56,19 +53,20 @@ def model_element_paths(mel, state): """ ixn = 1 for ix in mel: - ip = get_ix_path(state["state_paths"], ix) - im = state["model_object_cache"][ip] + ip = state.get_ix_path(ix) + im = om_operations["model_object_cache"][ip] print(ixn, ":", im.name, "->", im.state_path, "=", im.get_state()) ixn = ixn + 1 return # Import Code Classes -from hsp2.hsp2.om_model_object import ModelObject, ModelVariable, pre_step_register -from hsp2.hsp2.om_sim_timer import SimTimer, step_sim_timer from hsp2.hsp2.om_equation import Equation, step_equation from hsp2.hsp2.om_model_linkage import ModelLinkage, step_model_link +from hsp2.hsp2.om_model_object import ModelObject, ModelVariable, pre_step_register +from hsp2.hsp2.om_sim_timer import SimTimer, step_sim_timer from hsp2.hsp2.om_special_action import SpecialAction, step_special_action + # from hsp2.hsp2.om_data_matrix import * # from hsp2.hsp2.om_model_broadcast import * # from hsp2.hsp2.om_simple_channel import * @@ -88,7 +86,7 @@ def init_om_dicts(): return op_tokens, model_object_cache -def state_load_om_json(state, io_manager, siminfo): +def state_load_om_json(state, io_manager, siminfo, om_operations): # - model objects defined in file named '[model h5 base].json -- this will populate an array of object definitions that will # be loadable by "model_loader_recursive()" # JSON file would be in same path as hdf5 @@ -102,16 +100,14 @@ def state_load_om_json(state, io_manager, siminfo): jfile = open(fjson) json_data = json.load(jfile) # dict.update() combines the arg dict with the base - state["model_data"].update(json_data) - # merge in the json siminfo data - if "siminfo" in state["model_data"].keys(): - siminfo.update(state["model_data"]["siminfo"]) - else: - state["model_data"]["siminfo"] = siminfo + om_operations["model_data"].update(json_data) + # merge in the json siminfo data if provided + if "siminfo" in om_operations["model_data"].keys(): + siminfo.update(om_operations["model_data"]["siminfo"]) return -def state_load_om_python(state, io_manager, siminfo): +def state_load_om_python(state, io_manager, siminfo, om_operations): # Look for a [hdf5 file base].py file with specific named functions # - function "om_init_model": This function can be defined in the [model h5 base].py file containing things to be done # early in the model loading, like setting up model objects. This file will already have been loaded by the state module, @@ -123,34 +119,35 @@ def state_load_om_python(state, io_manager, siminfo): (fbase, fext) = os.path.splitext(hdf5_path) # see if there is a code module with custom python # print("Looking for custom om loader in python code ", (fbase + ".py")) - hsp2_local_py = state["hsp2_local_py"] + hsp2_local_py = state.state_step_hydr # Load a function from code if it exists if "om_init_model" in dir(hsp2_local_py): hsp2_local_py.om_init_model( io_manager, siminfo, - state["op_tokens"], - state["state_paths"], - state["state_ix"], - state["dict_ix"], - state["ts_ix"], - state["model_object_cache"], + state.op_tokens, + state.state_paths, + state.state_ix, + state.dict_ix, + state.ts_ix, + om_operations["model_object_cache"], ) -def om_init_state(state): +def om_init_state(): # this function will check to see if any of the multiple paths to loading - was state_initialize_om() # dynamic operational model objects has been supplied for the model. # Grab globals from state for easy handling op_tokens, model_object_cache = init_om_dicts() - state["op_tokens"], state["model_object_cache"], state["model_exec_list"] = ( - op_tokens, - model_object_cache, - [], - ) + om_operations = {} + om_operations["op_tokens"] = op_tokens + om_operations["model_object_cache"] = model_object_cache + om_operations["model_exec_list"] = [] + om_operations["model_data"] = {} + return om_operations -def state_load_dynamics_om(state, io_manager, siminfo): +def state_load_dynamics_om(state, io_manager, siminfo, om_operations): # this function will check to see if any of the multiple paths to loading # dynamic operational model objects has been supplied for the model. # om_init_state(state) must have been called already @@ -160,110 +157,79 @@ def state_load_dynamics_om(state, io_manager, siminfo): # but if things fail post develop-specact-1 pull requests we may investigate here # also, it may be that this should be loaded elsewhere? # comment state_load_om_python() to disable dynamic python - state_load_om_python(state, io_manager, siminfo) - state_load_om_json(state, io_manager, siminfo) + state_load_om_python(state, io_manager, siminfo, om_operations) + state_load_om_json(state, io_manager, siminfo, om_operations) return -def state_om_model_root_object(state, siminfo): +def state_om_model_root_object(state, om_operations, siminfo): # Create the base that everything is added to. this object does nothing except host the rest. - if "model_root_object" not in state.keys(): + if "model_root_object" not in om_operations.keys(): model_root_object = ModelObject( - state["model_root_name"], False, {}, state + state.model_root_name, False, {}, state, om_operations["model_object_cache"] ) # we give this no name so that it does not interfer with child paths like timer, year, etc (i.e. /STATE/year, ...) - state["model_root_object"] = model_root_object + om_operations["model_root_object"] = model_root_object # set up the timer as the first element - model_root_object = state["model_root_object"] - if "/STATE/timer" not in state["state_paths"].keys(): + else: + model_root_object = om_operations["model_root_object"] + #print("model_root_object", timer.split()) + if "/STATE/timer" not in state.state_paths.keys(): timer_props = siminfo timer_props["state_path"] = "/STATE/timer" - timer = SimTimer("timer", model_root_object, timer_props, state) + sim_timer = SimTimer("timer", model_root_object, timer_props) + #print("timer", timer.split()) # add base object for the HSP2 domains and other things already added to state so they can be influenced - for seg_name, seg_path in state["hsp_segments"].items(): - if seg_path not in state["model_object_cache"].keys(): + + +def om_init_hsp2_segments(state, om_operations): + for seg_name, seg_path in state.hsp_segments.items(): + if seg_path not in om_operations["model_object_cache"].keys(): # BUG: need to figure out if this is OK, then how do we add attributes to these River Objects # later when adding from json? # Can we simply check the model_object_cache during load step? # Create an object shell for this - segment = ModelObject(seg_name, model_root_object, {}, state) - state["model_object_cache"][segment.state_path] = segment - - -def state_om_model_run_prep(state, io_manager, siminfo): - # insure model base is set - state_om_model_root_object(state, siminfo) - # now instantiate and link objects - # state['model_data'] has alread been prepopulated from json, .py files, hdf5, etc. - model_root_object = state["model_root_object"] - model_loader_recursive(state["model_data"], model_root_object, state) - # print("Loaded objects & paths: insures all paths are valid, connects models as inputs") - # both state['model_object_cache'] and the model_object_cache property of the ModelObject class def - # will hold a global repo for this data this may be redundant? They DO point to the same datset? - # since this is a function that accepts state as an argument and these were both set in state_load_dynamics_om - # we can assume they are there and functioning - model_object_cache = model_root_object.state["model_object_cache"] - model_path_loader(model_object_cache) + # just get the end of the path, which should be fine since we + # don't use model names for anything, but might be more appropriately made as full path + segment = ModelObject(seg_name, om_operations["model_root_object"], {}) + om_operations["model_object_cache"][segment.state_path] = segment + + +def state_om_model_run_prep(opseq, activities, state, om_operations, siminfo): + # instantiate and link objects + # om_operations['model_data'] has alread been prepopulated from json, .py files, hdf5, etc. + model_loader_recursive( + om_operations["model_data"], om_operations["model_root_object"], state, om_operations["model_object_cache"] + ) + #print("model_loader_recursive", timer.split()) + model_path_loader(om_operations["model_object_cache"]) # len() will be 1 if we only have a simtimer, but > 1 if we have a river being added - model_exec_list = state["model_exec_list"] + model_exec_list = state.model_exec_list # put all objects in token form for fast runtime execution and sort according to dependency order # print("Tokenizing models") if "ops_data_type" in siminfo.keys(): - model_root_object.ops_data_type = siminfo[ + om_operations["model_root_object"].ops_data_type = siminfo[ "ops_data_type" ] # allow override of dat astructure settings - model_root_object.state["op_tokens"] = ModelObject.make_op_tokens( - max(model_root_object.state["state_ix"].keys()) + 1 - ) - model_tokenizer_recursive(model_root_object, model_object_cache, model_exec_list) - op_tokens = model_root_object.state["op_tokens"] - # print("op_tokens afer tokenizing", op_tokens) - # model_exec_list is the ordered list of component operations - # print("model_exec_list(", len(model_exec_list),"items):", model_exec_list) + model_tokenizer_recursive(om_operations["model_root_object"], om_operations["model_object_cache"], model_exec_list) # This is used to stash the model_exec_list in the dict_ix, this might be slow, need to verify. # the resulting set of objects is returned. - state["state_step_om"] = "disabled" - state["model_object_cache"] = model_object_cache - state["model_exec_list"] = np.asarray(model_exec_list, dtype="i8") - if model_root_object.ops_data_type == "ndarray": - state_keyvals = np.asarray( - zeros(max(model_root_object.state["state_ix"].keys()) + 1), dtype="float64" - ) - for ix, val in model_root_object.state["state_ix"].items(): - state_keyvals[ix] = val - state["state_ix"] = state_keyvals - else: - state["state_ix"] = model_root_object.state["state_ix"] - state["op_tokens"] = ( - op_tokens # is this superfluous since the root object got op_tokens from state? - ) - if len(op_tokens) > 0: - state["state_step_om"] = "enabled" - - # print("op_tokens is type", type(op_tokens)) - # print("state_ix is type", type(state['state_ix'])) - # print("state_paths final", state['state_paths']) - # print("op_tokens final", op_tokens) - # Stash a list of runnables - state["runnables"] = ModelObject.runnable_op_list( - state["op_tokens"], list(state["state_paths"].values()) - ) - # print("Operational model status:", state['state_step_om']) - if len(model_exec_list) > 0: - # pass + state.state_step_om = "disabled" + state.model_exec_list = np.asarray(model_exec_list, dtype="int64") + if len(state.op_tokens) > 0: + state.state_step_om = "enabled" + if len(state.op_tokens) > 0: print( "op_tokens has", - len(op_tokens), - "elements, with ", - len(model_exec_list), - "executable elements", + len(state.op_tokens), + "elements", ) - # print("Exec list:", model_exec_list) + # Now make sure that all HSP2 vars that can be affected by state have + hsp2_domain_dependencies(state, opseq, activities, om_operations, False) return - -def state_om_model_run_finish(state, io_manager, siminfo): +def state_om_model_run_finish(state, io_manager, om_operations): # write logs and other post-processing steps (if any) - finish_model(state, io_manager, siminfo) + finish_model(state, io_manager, om_operations) return True @@ -405,7 +371,7 @@ def model_class_translate(model_props, object_class): model_props["object_class"] = "ModelObject" -def model_loader_recursive(model_data, container, state): +def model_loader_recursive(model_data, container, state, model_object_cache): k_list = model_data.keys() object_names = dict.fromkeys(k_list, 1) if type(object_names) is not dict: @@ -445,7 +411,7 @@ def model_loader_recursive(model_data, container, state): if model_props["overwrite"] == True: model_object = False else: - model_object = state["model_object_cache"][model_object_path] + model_object = model_object_cache[model_object_path] if model_object == False: # try to load this object model_object = model_class_loader( @@ -457,7 +423,7 @@ def model_loader_recursive(model_data, container, state): # now for container type objects, go through its properties and handle # print("loaded object", model_object, "with container", container) if type(model_props) is dict: - model_loader_recursive(model_props, model_object, state) + model_loader_recursive(model_props, model_object, state, model_object_cache) def model_path_loader(model_object_cache): @@ -516,7 +482,7 @@ def model_tokenizer_recursive( input_object, model_object_cache, model_exec_list, model_touch_list ) else: - if input_path in model_object.state_paths.keys(): + if input_path in model_object.state.state_paths: # this is a valid state reference without an object # thus, it is likely part of internals that are manually added # which should be fine. tho perhaps we should have an object for these too. @@ -532,11 +498,15 @@ def model_tokenizer_recursive( # now after tokenizing all inputs this should be OK to tokenize model_object.add_op_tokens() if model_object.optype in ModelObject.runnables: - model_exec_list.append(model_object.ix) + model_exec_list = np.append(model_exec_list, model_object.ix) def model_order_recursive( - model_object, model_object_cache, model_exec_list, model_touch_list=None + model_object, + model_object_cache, + model_exec_list, + model_touch_list=None, + debug=False, ): """ Given a root model_object, trace the inputs to load things in order @@ -550,12 +520,28 @@ def model_order_recursive( that are sending to that broadcast? - Or is it better to let it as it is, """ + if debug: + print( + "Handling model object:", + model_object.name, + "with path", + model_object.state_path, + ) if model_touch_list is None: model_touch_list = [] if model_object.ix in model_exec_list: + if debug: + print(model_object.name, "already added to model_exec_list. Returning.") return if model_object.ix in model_touch_list: - # print("Already touched", model_object.name, model_object.ix, model_object.state_path) + if debug: + print( + "Already touched", + model_object.name, + model_object.ix, + model_object.state_path, + ". Returning.", + ) return # record as having been called, and will ultimately return, to prevent recursions model_touch_list.append(model_object.ix) @@ -581,7 +567,7 @@ def model_order_recursive( input_object, model_object_cache, model_exec_list, model_touch_list ) else: - if input_path in model_object.state_paths.keys(): + if input_path in model_object.state.state_paths: # this is a valid state reference without an object # thus, it is likely part of internals that are manually added # which should be fine. tho perhaps we should have an object for these too. @@ -595,32 +581,34 @@ def model_order_recursive( ) return # now after loading input dependencies, add this to list + if debug: + print("Adding", model_object.ix, "to element list") model_exec_list.append(model_object.ix) -def model_input_dependencies(state, exec_list, only_runnable=False): +def model_input_dependencies(state, exec_list, model_object_cache, only_runnable=False): # TODO: is this redundant to model_domain_dependencies? # Cmment in github suggest it is not, and has specific utility # for timeseries values? https://github.com/HARPgroup/HSPsquared/issues/60#issuecomment-2231668979 mello = exec_list mtl = [] mel = [] - for model_element in state["model_object_cache"].values(): + for model_element in model_object_cache.values(): for input_path in model_element.inputs: - input_ix = get_state_ix(state["state_ix"], state["state_paths"], input_path) + input_ix = get_state_ix(state.state_ix, state.state_paths, input_path) if input_ix in exec_list: # do a recursive pull of factors affecting this element - model_order_recursive( - model_element, state["model_object_cache"], mel, mtl - ) + model_order_recursive(model_element, model_object_cache, mel, mtl) mello = mello + mel if only_runnable == True: - mello = ModelObject.runnable_op_list(state["op_tokens"], mello) - mello = pd.Series(mello).drop_duplicates().tolist() + mello = ModelObject.runnable_op_list(state.op_tokens, mello) + mello = Series(mello).drop_duplicates().tolist() return mello -def model_domain_dependencies(state, domain, ep_list, only_runnable=False): +def model_domain_dependencies( + om_operations, state, domain, ep_list, only_runnable=False, debug=False +): """ Given an hdf5 style path to a domain, and a list of variable endpoints in that domain, Find all model elements that influence the endpoints state @@ -630,24 +618,62 @@ def model_domain_dependencies(state, domain, ep_list, only_runnable=False): for ep in ep_list: mel = [] mtl = [] + if debug: + print("Searching for", (domain + "/" + ep), "in state_paths") # if the given element is NOT in model_object_cache, then nothing is acting on it, so we return empty list - if (domain + "/" + ep) in state["state_paths"]: - if (domain + "/" + ep) in state["model_object_cache"].keys(): - endpoint = state["model_object_cache"][domain + "/" + ep] - model_order_recursive(endpoint, state["model_object_cache"], mel, mtl) + if (domain + "/" + ep) in state.state_paths.keys(): + if (domain + "/" + ep) in om_operations["model_object_cache"].keys(): + if debug: + print("Found", (domain + "/" + ep), "in om_operations") + endpoint = om_operations["model_object_cache"][domain + "/" + ep] + model_order_recursive( + endpoint, om_operations["model_object_cache"], mel, mtl + ) mello = mello + mel # TODO: stash the runnable list (mellorun) as a element in dict_ix for cached access during runtime - mellorun = ModelObject.runnable_op_list(state["op_tokens"], mello) + mellorun = ModelObject.runnable_op_list(state.op_tokens, mello) if only_runnable == True: mello = mellorun return mello +def hsp2_domain_dependencies(state, opseq, activities, om_operations, debug=False): + # This sets up the state entries for all state compatible HSP2 model variables + # print("STATE initializing contexts.") + for _, operation, segment, delt in opseq.itertuples(): + if operation != "GENER" and operation != "COPY": + for activity, function in activities[operation].items(): + # set up named paths for model operations + seg_name = operation + "_" + segment + seg_path = "/STATE/" + state.model_root_name + "/" + seg_name + activity_path = seg_path + "/" + activity + activity_id = state.set_state(activity_path, 0.0) + ep_list = DataFrame() + if debug: + print("Getting init_ix for", seg_path, activity) + if activity == "HYDR": + ep_list = hydr_init_ix(state, seg_path) + elif activity == "SEDTRN": + ep_list = sedtrn_init_ix(state, seg_path) + elif activity == "SEDMNT": + ep_list = sedmnt_init_ix(state, seg_path) + elif activity == "RQUAL": + ep_list = rqual_init_ix(state, seg_path) + # Register list of elements to execute if any + op_exec_list = model_domain_dependencies( + om_operations, state, seg_path, ep_list, True, debug + ) + op_exec_list = np.asarray(op_exec_list) + # register the dependencies for each activity so we can load once here + # then just iterate through them at runtime without re-querying + state.set_exec_list(activity_id, op_exec_list) + + def save_object_ts(io_manager, siminfo, op_tokens, ts_ix, ts): # Decide on using from utilities.py: # - save_timeseries(io_manager, ts, savedict, siminfo, saveall, operation, segment, activity, compress=True) # Or, skip the save_timeseries wrapper and call write_ts() directly in io.py: - # write_ts(self, data_frame:pd.DataFrame, save_columns: List[str], category:Category, operation:Union[str,None]=None, segment:Union[str,None]=None, activity:Union[str,None]=None) + # write_ts(self, data_frame:DataFrame, save_columns: List[str], category:Category, operation:Union[str,None]=None, segment:Union[str,None]=None, activity:Union[str,None]=None) # see line 317 in utilities.py for use example of write_ts() x = 0 # dummy return @@ -665,7 +691,7 @@ def iterate_models( return checksum -@njit +@njit(cache=True) def pre_step_model(model_exec_list, op_tokens, state_ix, dict_ix, ts_ix, step): for i in model_exec_list: if op_tokens[i][0] == 12: @@ -676,15 +702,23 @@ def pre_step_model(model_exec_list, op_tokens, state_ix, dict_ix, ts_ix, step): @njit def step_model(model_exec_list, op_tokens, state_ix, dict_ix, ts_ix, step): + n = 0 for i in model_exec_list: + # skip these - we could optimize performance and return assuming + # that the first -1 item is the end of the active components + if op_tokens[i][0] == -1: + return step_one(op_tokens, op_tokens[i], state_ix, dict_ix, ts_ix, step, 0) + n = n + 1 return -def finish_model(state, io_manager, siminfo): - # print("Model object cache list", state["model_object_cache"].keys()) - for i in state["model_exec_list"]: - model_object = state["model_object_cache"][get_ix_path(state["state_paths"], i)] +def finish_model(state, io_manager, om_operations): + # print("Model object cache list", om_operations["model_object_cache"].keys()) + for i in state.model_exec_list: + model_object = om_operations["model_object_cache"][ + state.get_ix_path(i) + ] if "io_manager" in dir(model_object): model_object.io_manager = io_manager model_object.finish() @@ -696,11 +730,11 @@ def step_one(op_tokens, ops, state_ix, dict_ix, ts_ix, step, debug=0): # op_tokens is passed in for ops like matrices that have lookups from other # locations. All others rely only on ops # todo: decide if all step_[class() functions should set value in state_ix instead of returning value? - if debug > 0: + if debug: print("DEBUG: Operator ID", ops[1], "is op type", ops[0]) print("DEBUG: ops: ", ops) if ops[0] == 1: - step_equation(ops, state_ix) + step_equation(ops, state_ix, step) elif ops[0] == 2: # todo: this should be moved into a single function, # with the conforming name step_matrix(op_tokens, ops, state_ix, dict_ix) diff --git a/src/hsp2/hsp2/om_equation.py b/src/hsp2/hsp2/om_equation.py index ade606cf..58bb5d24 100644 --- a/src/hsp2/hsp2/om_equation.py +++ b/src/hsp2/hsp2/om_equation.py @@ -6,26 +6,19 @@ in the state_ix Dict for runtime execution. """ -from hsp2.hsp2.om import is_float_digit -from hsp2.state.state import set_state, get_state_ix -from hsp2.hsp2.om_model_object import ModelObject, ModelConstant from numba import njit -from numpy import array, append +from numpy import append, array -# from hsp2.state.state import set_state, get_state_ix -# from numba.typed import Dict -# from hsp2.hsp2.om import get_exec_order, is_float_digit -# from pandas import Series, DataFrame, concat, HDFStore, set_option, to_numeric -# from pandas import Timestamp, Timedelta, read_hdf, read_csv -# from numpy import pad, asarray, zeros, int32 -# from numba import njit, types +from hsp2.hsp2.om import is_float_digit +from hsp2.hsp2.om_model_object import ModelConstant, ModelObject +from hsp2.state.state import get_state_ix, set_state class Equation(ModelObject): # the following are supplied by the parent class: name, log_path, attribute_path, state_path, inputs def __init__(self, name, container=False, model_props={}, state=None): - super(Equation, self).__init__(name, container, model_props) + super().__init__(name, container, model_props) self.equation = self.handle_prop(model_props, "equation") self.ps = False self.ps_names = [] # Intermediate with constants turned into variable references in state_paths @@ -137,9 +130,7 @@ def tokenize_vars(self): elif is_float_digit(self.var_ops[j]): # must add this to the state array as a constant constant_path = self.state_path + "/_ops/_op" + str(j) - s_ix = set_state( - self.state["state_ix"], - self.state["state_paths"], + s_ix = self.state.set_state( constant_path, float(self.var_ops[j]), ) @@ -147,9 +138,7 @@ def tokenize_vars(self): else: # this is a variable, must find it's data path index var_path = self.find_var_path(self.var_ops[j]) - s_ix = get_state_ix( - self.state["state_ix"], self.state["state_paths"], var_path - ) + s_ix = self.state.get_state_ix(var_path) if s_ix == False: print( "Error: unknown variable ", @@ -159,9 +148,7 @@ def tokenize_vars(self): "index", s_ix, ) - print( - "searched: ", self.state["state_paths"], self.state["state_ix"] - ) + print("searched: ", self.state.state_paths, self.state.state_ix) return else: self.var_ops[j] = s_ix @@ -178,20 +165,21 @@ def tokenize(self): self.ops = self.ops + [self.non_neg, self.min_value_ix] + self.var_ops +import math +import operator + from pyparsing import ( - Literal, - Word, - Group, + CaselessKeyword, Forward, - alphas, - alphanums, + Group, + Literal, Regex, - CaselessKeyword, Suppress, + Word, + alphanums, + alphas, delimitedList, ) -import math -import operator exprStack = [] @@ -263,7 +251,6 @@ def tokenize_ops(ps): bnf = None - def BNF(): """ expop :: '^' @@ -454,7 +441,7 @@ def evaluate_eq_ops(op, val1, val2): @njit -def step_equation(op_token, state_ix): +def step_equation(op_token, state_ix, step): result = 0 s = array([0.0]) s_ix = -1 # pointer to the top of the stack @@ -462,11 +449,10 @@ def step_equation(op_token, state_ix): # handle special equation settings like "non-negative", etc. non_neg = op_token[2] min_ix = op_token[3] - num_ops = op_token[ - 4 - ] # this index is equal to the number of ops common to all classes + 1. See om_model_object for base ops and adjust + # this index is equal to the number of ops common to all classes + 1. + # See om_model_object for base ops and adjust + num_ops = op_token[4] op_loc = 5 # where do the operators and operands start in op_token - # print(num_ops, " operations") # is the below faster since it avoids a brief loop and a couple ifs for 2 op equations? if num_ops == 1: result = evaluate_eq_ops( @@ -494,7 +480,6 @@ def step_equation(op_token, state_ix): s_ix -= 1 else: val2 = state_ix[t2] - # print(s_ix, op, val1, val2) result = evaluate_eq_ops(op, val1, val2) s_ix += 1 if s_ix >= s_len: @@ -504,5 +489,7 @@ def step_equation(op_token, state_ix): result = s[s_ix] if (non_neg == 1) and (result < 0): result = state_ix[min_ix] + #if step < 2: + # print("Eq:", op_token[1], result) state_ix[op_token[1]] = result return True diff --git a/src/hsp2/hsp2/om_model_linkage.py b/src/hsp2/hsp2/om_model_linkage.py index a09c3528..f318556f 100644 --- a/src/hsp2/hsp2/om_model_linkage.py +++ b/src/hsp2/hsp2/om_model_linkage.py @@ -4,7 +4,7 @@ during a model simulation. """ -from hsp2.state.state import state_add_ts, get_state_ix +from hsp2.state.state import state_add_ts from hsp2.hsp2.om import * from hsp2.hsp2.om_model_object import ModelObject from numba import njit @@ -14,7 +14,7 @@ class ModelLinkage(ModelObject): def __init__(self, name, container=False, model_props=None, state=None): if model_props is None: model_props = {} - super(ModelLinkage, self).__init__(name, container, model_props, state=False) + super(ModelLinkage, self).__init__(name, container, model_props, state) # ModelLinkage copies a values from right to left # right_path: is the data source for the link # left_path: is the destination of the link @@ -49,8 +49,8 @@ def __init__(self, name, container=False, model_props=None, state=None): self.left_path = self.state_path if self.link_type == 0: # if this is a simple input we remove the object from the model_object_cache, and pass back to parent as an input - del self.state["model_object_cache"][self.state_path] - del self.state["state_ix"][self.ix] + del self.om_operations["model_object_cache"][self.state_path] + del self.state.state_ix[self.ix] container.add_input(self.name, self.right_path) if self.link_type == 6: # add an entry into time series dataframe @@ -96,19 +96,26 @@ def find_paths(self): # self.insure_path(self, self.right_path) # the left path, if this is type 4 or 5, is a push, so we must require it if (self.link_type == 4) or (self.link_type == 5) or (self.link_type == 6): - self.insure_path(self.left_path) + #print("ModelLinkage", self.name, "insuring register with path", self.left_path) push_pieces = self.left_path.split("/") push_name = push_pieces[len(push_pieces) - 1] - var_register = self.insure_register( - push_name, 0.0, False, self.left_path, False - ) - print( - "Created register", - var_register.name, - "with path", - var_register.state_path, - ) - # add already created objects as inputs + left_object = self.get_object(self.left_path) + if not left_object: + # try to fin the parent and create the register since push is allowed + left_parent_path = '/'.join(push_pieces[0:len(push_pieces) - 1]) + left_parent_object = self.get_object(left_parent_path) + if not left_parent_object: + raise Exception( + "Cannot find variable path: " + + left_parent_path + + " when trying to push to object " + + push_name + ) + var_register = self.insure_register( + push_name, 0.0, left_parent_object, self.left_path, False + ) + #print("Created register", var_register.name, "with path", var_register.state_path) + # add already created objects as inputs var_register.add_object_input(self.name, self, 1) # Now, make sure that all time series paths can be found and loaded if self.link_type == 3: @@ -139,18 +146,16 @@ def read_ts(self, set_ts_ix=True): def write_ts(self, ts=None, ts_cols=None, write_path=None, tindex=None): if ts == None: - tix = get_state_ix( - self.state["state_ix"], self.state["state_paths"], self.left_path - ) + tix = self.state.get_state_ix(self.left_path) # get the ts. Note, we get the ts entry that corresponds to the left_path setting - ts = self.state["ts_ix"][tix] + ts = self.state.ts_ix[tix] if write_path == None: if self.left_path != None: write_path = self.left_path else: return False if tindex == None: - tindex = self.state["model_data"]["siminfo"]["tindex"] + tindex = self.get_tindex() tsdf = self.format_ts(ts, ts_cols, tindex) if self.io_manager == False: # to do: allow object to specify hdf path name and if so, can open and read/write @@ -187,9 +192,7 @@ def tokenize(self): # - execution hierarchy # print("Linkage/link_type ", self.name, self.link_type,"created with params", self.model_props_parsed) if self.link_type in (2, 3): - src_ix = get_state_ix( - self.state["state_ix"], self.state["state_paths"], self.right_path - ) + src_ix = self.state.get_state_ix(self.right_path) if not (src_ix == False): self.ops = self.ops + [src_ix, self.link_type] else: @@ -197,12 +200,8 @@ def tokenize(self): # print(self.name,"tokenize() result", self.ops) if (self.link_type == 4) or (self.link_type == 5) or (self.link_type == 6): # we push to the remote path in this one - left_ix = get_state_ix( - self.state["state_ix"], self.state["state_paths"], self.left_path - ) - right_ix = get_state_ix( - self.state["state_ix"], self.state["state_paths"], self.right_path - ) + left_ix = self.state.get_state_ix(self.left_path) + right_ix = self.state.get_state_ix(self.right_path) if (left_ix != False) and (right_ix != False): self.ops = self.ops + [left_ix, self.link_type, right_ix] else: @@ -226,11 +225,8 @@ def finish(self): # Function for use during model simulations of tokenized objects -@njit +@njit(cache=True) def step_model_link(op_token, state_ix, ts_ix, step): - # if step == 2: - # print("step_model_link() called at step 2 with op_token=", op_token) - # print("step_model_link() called at step 2 with op_token=", op_token) if op_token[3] == 1: return True elif op_token[3] == 2: @@ -250,15 +246,6 @@ def step_model_link(op_token, state_ix, ts_ix, step): return True elif op_token[3] == 6: # set value in a timerseries - if step < 10: - print( - "Writing ", - state_ix[op_token[4]], - "from ix=", - op_token[4], - "to", - op_token[2], - ) ts_ix[op_token[2]][step] = state_ix[op_token[4]] return True diff --git a/src/hsp2/hsp2/om_model_object.py b/src/hsp2/hsp2/om_model_object.py index 30451052..b0c549d1 100644 --- a/src/hsp2/hsp2/om_model_object.py +++ b/src/hsp2/hsp2/om_model_object.py @@ -4,12 +4,13 @@ All runtime exec is done by child classes. """ -from hsp2.state.state import set_state, get_state_ix +from numba import njit, types from numba.typed import Dict -from hsp2.hsp2.om import get_exec_order, is_float_digit +from numpy import asarray, int64, pad, zeros from pandas import HDFStore -from numpy import pad, asarray, zeros, int32 -from numba import njit, types + +from hsp2.hsp2.om import is_float_digit +from hsp2.state.state import nkey_exists class ModelObject: @@ -32,27 +33,44 @@ class ModelObject: ] # runnable components important for optimization ops_data_type = "ndarray" # options are ndarray or Dict - Dict appears slower, but unsure of the cause, so keep as option. - def __init__(self, name, container=False, model_props=None, state=None): + def __init__( + self, + name, + container=False, + model_props=None, + state=None, + model_object_cache=None, + ): self.name = name - self.handle_deprecated_args(name, container, model_props, state) - # END - handle deprecated - if model_props is None: - model_props = {} self.container = container # will be a link to another object - self.state_path = self.handle_prop(model_props, "state_path", False, False) - if type(state) != dict: + if state is None: # we must verify that we have a properly formatted state Dictionary, or that our parent does. if self.container == False: raise Exception( - "Error: State dictionary must be passed to root object. ", - type(state), - "passed instead." - + name - + " cannot be created. See state::init_state_dicts()", + "Error: State object must be passed to root object. ", + +name + " cannot be created. See state::init_state_dicts()", ) else: state = self.container.state + if model_object_cache is None: + # we must verify that we have a properly formatted state Dictionary, or that our parent does. + if self.container == False: + raise Exception( + "Error: model_object_cache object must be available on to root object. " + + name + + " cannot be created. See state::init_state_dicts()" + ) + else: + model_object_cache = self.container.model_object_cache self.state = state # make a copy here. is this efficient? + self.model_object_cache = ( + model_object_cache # make a copy here. is this efficient? + ) + self.handle_deprecated_args(name, container, model_props, state) + # END - handle deprecated + if model_props is None: + model_props = {} + self.state_path = self.handle_prop(model_props, "state_path", False, False) # Local properties self.model_props_parsed = {} # a place to stash parse record for debugging self.log_path = "" # Ex: "/RESULTS/RCHRES_001/SPECL" @@ -109,7 +127,7 @@ def required_properties(): @staticmethod def make_op_tokens(num_ops=5000): if ModelObject.ops_data_type == "ndarray": - op_tokens = int32( + op_tokens = int64( zeros((num_ops, 64)) ) # was Dict.empty(key_type=types.int64, value_type=types.i8[:]) else: @@ -123,6 +141,7 @@ def runnable_op_list(op_tokens, meo, debug=False): run_ops = {} for ops in op_tokens: # the base class defines the type of objects that are runnable (i.e. have a step() method) + #print("Handling ops", ops) if ops[0] in ModelObject.runnables: run_ops[ops[1]] = ops if debug == True: @@ -191,6 +210,8 @@ def handle_prop(self, model_props, prop_name, strict=False, default_value=None): + self.name + " and strict = True. Object creation halted. Path to object with error is " + self.state_path + + "This objects inputs are:", + self.inputs, ) if (prop_val == None) and not (default_value == None): prop_val = default_value @@ -207,20 +228,12 @@ def parse_model_props(self, model_props, strict=False): return True def set_state(self, set_value): - var_ix = set_state( - self.state["state_ix"], - self.state["state_paths"], + var_ix = self.state.set_state( self.state_path, set_value, ) return var_ix - def load_state_dicts(self, op_tokens, state_paths, state_ix, dict_ix): - self.state["op_tokens"] = op_tokens - self.state["state_paths"] = state_paths - self.state["state_ix"] = state_ix - self.state["dict_ix"] = dict_ix - def save_object_hdf(self, hdfname, overwrite=False): # save the object in the full hdf5 path # if overwrite = True replace this and all children, otherwise, just save this. @@ -232,6 +245,7 @@ def make_paths(self, base_path=False): # print("calling make_paths from", self.name, "with base path", base_path) if base_path == False: # we are NOT forcing paths if not (self.container == False): + # print("Using container path as base:", self.container.state_path + "/" + str(self.name)) self.state_path = self.container.state_path + "/" + str(self.name) self.attribute_path = ( self.container.attribute_path + "/" + str(self.name) @@ -250,55 +264,58 @@ def make_paths(self, base_path=False): def get_state(self, var_name=False): if var_name == False: - return self.state["state_ix"][self.ix] + return self.state.state_ix[self.ix] else: var_path = self.find_var_path(var_name) - var_ix = get_state_ix( - self.state["state_ix"], self.state["state_paths"], var_path - ) + # print("Looking for state ix of:", var_path) + var_ix = self.state.get_state_ix(var_path) if var_ix == False: return False - return self.state["state_ix"][var_ix] + return self.state.state_ix[var_ix] - def get_exec_order(self, var_name=False): - if var_name == False: - var_ix = self.ix - else: - var_path = self.find_var_path(var_name) - var_ix = get_state_ix( - self.state["state_ix"], self.state["state_paths"], var_path - ) - exec_order = get_exec_order(self.state["model_exec_list"], var_ix) - return exec_order + def get_tindex(self): + timer = self.get_object("timer") + tindex = self.state.dict_ix[timer.ix] + return tindex def get_object(self, var_name=False): if var_name == False: - return self.state["model_object_cache"][self.state_path] + return self.model_object_cache[self.state_path] else: var_path = self.find_var_path(var_name) - return self.state["model_object_cache"][var_path] + if var_path in self.model_object_cache: + return self.model_object_cache[var_path] + else: + return False def find_var_path(self, var_name, local_only=False): # check local inputs for name + if var_name is None: + print("NULL var searched from", self.name, "child of", self.container.name) if type(var_name) == str: # print("Expanding aliases for", var_name) var_name = self.handle_path_aliases(var_name) # sub out any wildcards # print(self.name, "called", "find_var_path(self, ", var_name, ", local_only = False)") if var_name in self.inputs.keys(): return self.inputs[var_name] + # check for state vars in my path + var_name + if nkey_exists(self.state.state_paths, self.state_path + "/" + var_name): + return self.state_path + "/" + var_name if local_only: + #print("Cannot find var", var_name, "in local scope", self.name) return False # we are limiting the scope, so just return # check parent for name if not (self.container == False): + #print("Searching for var", var_name, "in container scope", self.container.name, self.container.state_path) return self.container.find_var_path(var_name) # check for root state vars STATE + var_name - if ("/STATE/" + var_name) in self.state["state_paths"].keys(): - # return self.state['state_paths'][("/STATE/" + var_name)] + if ("/STATE/" + var_name) in self.state.state_paths: return "/STATE/" + var_name # check for full paths - if var_name in self.state["state_paths"].keys(): + if nkey_exists(self.state.state_paths, var_name): # return self.state['state_paths'][var_name] return var_name + #print("Cannot find var in global scope", self.state_path, "var", var_name) return False def constant_or_path(self, keyname, keyval, trust=False): @@ -318,14 +335,10 @@ def register_path(self): # print("register_path called for", self.name, "with state_path", self.state_path) if self.state_path == "" or self.state_path == False: self.make_paths() - self.ix = set_state( - self.state["state_ix"], - self.state["state_paths"], - self.state_path, - self.default_value, - ) + self.ix = self.state.set_state(self.state_path, self.default_value) # store object in model_object_cache - always, if we have reached this point we need to overwrite - self.state["model_object_cache"][self.state_path] = self + # print("Adding ", self.name, "with state_path", self.state_path, "to model_object_cache") + self.model_object_cache[self.state_path] = self # this should check to see if this object has a parent, and if so, register the name on the parent # default is as a child object. if not (self.container == False): @@ -350,10 +363,10 @@ def add_input(self, var_name, var_path, input_type=1, trust=False): # BUT this only works if both var_name and var_path are month # so add_input('month', 'month', 1, True) works. found_path = self.find_var_path(var_path) - # print("Searched", var_name, "with path", var_path,"found", found_path) - var_ix = get_state_ix( - self.state["state_ix"], self.state["state_paths"], found_path - ) + if found_path == False: + var_ix = False + else: + var_ix = self.state.get_state_ix(found_path) if var_ix == False: if trust == False: raise Exception( @@ -365,6 +378,10 @@ def add_input(self, var_name, var_path, input_type=1, trust=False): + var_name + " ... process terminated. Path to object with error is " + self.state_path + + "This objects inputs are:", + self.inputs, + "State paths=", + self.state.state_paths, ) var_ix = self.insure_path(var_path) else: @@ -408,15 +425,13 @@ def insure_path(self, var_path): # if this path can be found in the hdf5 make sure that it is registered in state # and that it has needed object class to render it at runtime (some are automatic) # RIGHT NOW THIS DOES NOTHING TO CHECK IF THE VAR EXISTS THIS MUST BE FIXED - var_ix = set_state( - self.state["state_ix"], self.state["state_paths"], var_path, 0.0 - ) + var_ix = self.state.set_state(var_path, 0.0) return var_ix def get_dict_state(self, ix=-1): if ix >= 0: - return self.state["dict_ix"][ix] - return self.state["dict_ix"][self.ix] + return self.state.dict_ix[ix] + return self.state.dict_ix[self.ix] def find_paths(self): # Note: every single piece of data used by objects, even constants, are resolved to a PATH in the hdf5 @@ -446,7 +461,7 @@ def insure_register( if register_path == False: register_path = register_container.find_var_path(var_name, True) if (register_path == False) or ( - register_path not in self.state["model_object_cache"].keys() + register_path not in self.model_object_cache.keys() ): # create a register as a placeholder for the data at the hub path # in case there are no senders, or in the case of a timeseries logger, we need to register it so that its path can be set to hold data @@ -466,7 +481,7 @@ def insure_register( var_name, register_container, reg_props, self.state ) else: - var_register = self.state["model_object_cache"][register_path] + var_register = self.model_object_cache[register_path] return var_register def tokenize(self): @@ -495,18 +510,20 @@ def add_op_tokens(self): + self.state_path + "). " ) - self.state["op_tokens"][self.ix] = self.format_ops() + self.state.set_token(self.ix, self.format_ops()) def step(self, step): # this tests the model for a single timestep. # this is not the method that is used for high-speed runs, but can theoretically be used for # easier to understand demonstrations + # this has not been tested since changes to the state from array to object step_one( - self.state["op_tokens"], - self.state["op_tokens"][self.ix], - self.state["state_ix"], - self.state["dict_ix"], - self.state["ts_ix"], + self.state.op_tokens, + self.state.op_tokens[self.ix], + self.state.state_ix, + self.state.dict_ix, + self.state.state_ix, + self.state.ts_ix, step, ) # step_model({self.state['op_tokens'][self.ix]}, self.state['state_ix'], self.state['dict_ix'], self.state['ts_ix'], step) @@ -517,14 +534,10 @@ def finish(self): return True -""" -The class ModelVariable is a base cass for storing numerical values. Used for UVQUAN and misc numerical constants... -""" - - class ModelVariable(ModelObject): + #:The class ModelVariable is a base cass for storing numerical values. Used for UVQUAN and misc numerical constants... def __init__(self, name, container=False, model_props=None, state=None): - super(ModelVariable, self).__init__(name, container, model_props, state) + super().__init__(name, container, model_props, state) # print("ModelVariable named", name, "with path", self.state_path,"beginning") value = self.handle_prop(model_props, "value") self.default_value = float(value) @@ -540,28 +553,20 @@ def required_properties(): return req_props -""" -The class ModelConstant is for storing non-changing values. -""" - - class ModelConstant(ModelVariable): + #: The class ModelConstant is for storing non-changing values. def __init__(self, name, container=False, model_props=None, state=None): - super(ModelConstant, self).__init__(name, container, model_props, state) + super().__init__(name, container, model_props, state) self.optype = 16 # 0 - shell object, 1 - equation, 2 - datamatrix, 3 - input, 4 - broadcastChannel, 5 - SimTimer, 6 - Conditional, 7 - ModelVariable (numeric) # print("ModelVariable named",self.name, "with path", self.state_path,"and ix", self.ix, "value", value) -""" -The class ModelRegister is for storing push values. -Behavior is to zero each timestep. This could be amended later. -Maybe combined with stack behavior? Or accumulator? -""" - - class ModelRegister(ModelVariable): + #: The class ModelRegister is for storing push values. + #: Behavior is to zero each timestep. This could be amended later. + #: Maybe combined with stack behavior? Or accumulator? def __init__(self, name, container=None, model_props=False, state=False): - super(ModelRegister, self).__init__(name, container, model_props, state) + super().__init__(name, container, model_props, state) self.optype = 12 # # self.state['state_ix'][self.ix] = self.default_value diff --git a/src/hsp2/hsp2/om_sim_timer.py b/src/hsp2/hsp2/om_sim_timer.py index 24a93c53..5b4f9e96 100644 --- a/src/hsp2/hsp2/om_sim_timer.py +++ b/src/hsp2/hsp2/om_sim_timer.py @@ -4,20 +4,19 @@ during a model simulation. """ -from hsp2.state.state import set_state from hsp2.hsp2.om import ModelObject from hsp2.hsp2.om_model_object import ModelObject +from hsp2.state.state import set_numba_value from pandas import DataFrame from numba import njit from numpy import int64 - class SimTimer(ModelObject): def __init__(self, name, container, model_props=None, state=None): if model_props is None: model_props = {} # Note: hsp2 siminfo will match model_props here - super(SimTimer, self).__init__(name, container, model_props) + super(SimTimer, self).__init__(name, container, model_props, state) self.state_path = "/STATE/timer" self.time_array = self.dti_to_time_array( model_props @@ -28,80 +27,23 @@ def __init__(self, name, container, model_props=None, state=None): def register_components(self): # initialize the path variable if not already set - self.ix = set_state( - self.state["state_ix"], - self.state["state_paths"], + self.ix = self.state.set_state( self.state_path, - float(self.time_array[0][0]), + float(self.time_array[0][0]) ) # now register all other paths. # register "year", "month" "day", ... - year_ix = set_state( - self.state["state_ix"], - self.state["state_paths"], - "/STATE/year", - float(self.time_array[0][1]), - ) - month_ix = set_state( - self.state["state_ix"], - self.state["state_paths"], - "/STATE/month", - float(self.time_array[0][2]), - ) - day_ix = set_state( - self.state["state_ix"], - self.state["state_paths"], - "/STATE/day", - float(self.time_array[0][3]), - ) - hr_ix = set_state( - self.state["state_ix"], - self.state["state_paths"], - "/STATE/hour", - float(self.time_array[0][4]), - ) - min_ix = set_state( - self.state["state_ix"], - self.state["state_paths"], - "/STATE/minute", - float(self.time_array[0][5]), - ) - sec_ix = set_state( - self.state["state_ix"], - self.state["state_paths"], - "/STATE/second", - float(self.time_array[0][6]), - ) - wd_ix = set_state( - self.state["state_ix"], - self.state["state_paths"], - "/STATE/weekday", - float(self.time_array[0][7]), - ) - dt_ix = set_state( - self.state["state_ix"], - self.state["state_paths"], - "/STATE/dt", - float(self.time_array[0][8]), - ) - jd_ix = set_state( - self.state["state_ix"], - self.state["state_paths"], - "/STATE/jday", - float(self.time_array[0][9]), - ) - md_ix = set_state( - self.state["state_ix"], - self.state["state_paths"], - "/STATE/modays", - float(self.time_array[0][10]), - ) - dts_ix = set_state( - self.state["state_ix"], - self.state["state_paths"], - "/STATE/dts", - float(self.time_array[0][8] * 60.0), - ) + year_ix = self.state.set_state("/STATE/year", float(self.time_array[0][1]) ) + month_ix = self.state.set_state("/STATE/month", float(self.time_array[0][2])) + day_ix = self.state.set_state( "/STATE/day", float(self.time_array[0][3])) + hr_ix = self.state.set_state( "/STATE/hour", float(self.time_array[0][4])) + min_ix = self.state.set_state( "/STATE/minute", float(self.time_array[0][5])) + sec_ix = self.state.set_state( "/STATE/second", float(self.time_array[0][6])) + wd_ix = self.state.set_state( "/STATE/weekday", float(self.time_array[0][7])) + dt_ix = self.state.set_state( "/STATE/dt", float(self.time_array[0][8])) + jd_ix = self.state.set_state( "/STATE/jday", float(self.time_array[0][9])) + md_ix = self.state.set_state( "/STATE/modays", float(self.time_array[0][10])) + dts_ix = self.state.set_state( "/STATE/dts", float(self.time_array[0][8] * 60.0)) self.date_path_ix = [ year_ix, month_ix, @@ -115,7 +57,7 @@ def register_components(self): md_ix, dts_ix, ] - self.state["dict_ix"][self.ix] = self.time_array + self.state.dict_ix = set_numba_value(self.state.dict_ix, self.ix, self.time_array) return self.ix @@ -124,12 +66,12 @@ def tokenize(self): # returns an array of data pointers super().tokenize() # resets ops to common base self.ops = self.ops + self.date_path_ix # adds timer specific items - + def add_op_tokens(self): # this puts the tokens into the global simulation queue # can be customized by subclasses to add multiple lines if needed. super().add_op_tokens() - self.state["dict_ix"][self.ix] = self.time_array + self.state.dict_ix = set_numba_value(self.state.dict_ix, self.ix, self.time_array) def dti_to_time_array(self, siminfo): dateindex = siminfo["tindex"] @@ -175,3 +117,4 @@ def step_sim_timer(op_token, state_ix, dict_ix, ts_ix, step): state_ix[op_token[11]] = dict_ix[op_token[1]][step][10] # modays state_ix[op_token[12]] = dict_ix[op_token[1]][step][11] # dts return + diff --git a/src/hsp2/hsp2/om_special_action.py b/src/hsp2/hsp2/om_special_action.py index e3042fe7..abd1a8d5 100644 --- a/src/hsp2/hsp2/om_special_action.py +++ b/src/hsp2/hsp2/om_special_action.py @@ -78,7 +78,7 @@ def handle_prop(self, model_props, prop_name, strict=False, default_value=None): if prop_name == "when": # when to perform this? timestamp or time-step index prop_val = -1 # prevent a 0 indexed value from triggering return, default means execute every step - si = self.state["model_object_cache"][self.find_var_path("timer")] + si = self.get_object("timer") if len(model_props["YR"]) > 0: # translate date to equivalent model step datestring = ( @@ -168,7 +168,7 @@ def find_paths(self): + self.op_type[0] + str(self.range1).zfill(3) ) - domain = self.state["model_object_cache"][domain_path] + domain = self.model_object_cache[domain_path] var_register = self.insure_register(self.vari, 0.0, domain, False, False) # print("Created register", var_register.name, "with path", var_register.state_path) # add already created objects as inputs diff --git a/src/hsp2/hsp2/om_timer.py b/src/hsp2/hsp2/om_timer.py new file mode 100644 index 00000000..48ddaa3e --- /dev/null +++ b/src/hsp2/hsp2/om_timer.py @@ -0,0 +1,56 @@ +""" +The class timer_class/timer_class_jit is used for benchmarking and peformance inquiry. +""" + +from numba.experimental import jitclass +import ctypes +import time +from numba import njit, types + +# Access the _PyTime_AsSecondsDouble and _PyTime_GetSystemClock functions from pythonapi +get_system_clock = ctypes.pythonapi._PyTime_GetSystemClock +as_seconds_double = ctypes.pythonapi._PyTime_AsSecondsDouble +# Set the argument types and return types of the functions +get_system_clock.argtypes = [] +get_system_clock.restype = ctypes.c_int64 +as_seconds_double.argtypes = [ctypes.c_int64] +as_seconds_double.restype = ctypes.c_double + +timer_spec = [ + ("tstart", types.float64), + ("tend", types.float64), + ("tsplit", types.float64) +] + +@njit +def jitime(): + system_clock = get_system_clock() + current_time = as_seconds_double(system_clock) + return current_time + +@jitclass(timer_spec) +class timer_class_jit(): + def __init__(self): + self.tstart = jitime() + + def split(self): + self.tend = jitime() + self.tsplit = self.tend - self.tstart + self.tstart = jitime() + split = 0 + if (self.tsplit > 0): + split = self.tsplit + return split + +class timer_class(): + def __init__(self): + self.tstart = time.time() + + def split(self): + self.tend = time.time() + self.tsplit = self.tend - self.tstart + self.tstart = time.time() + split = 0 + if (self.tsplit > 0): + split = self.tsplit + return split diff --git a/src/hsp2/hsp2tools/readUCI.py b/src/hsp2/hsp2tools/readUCI.py index 8dd432e1..52d65daa 100644 --- a/src/hsp2/hsp2tools/readUCI.py +++ b/src/hsp2/hsp2tools/readUCI.py @@ -606,7 +606,6 @@ def ftables(info, llines): else: lst.append(parseD(line, parse["FTABLES", "FTABLE"])) - def ext(info, lines): store, parse, path, *_ = info lst = [] diff --git a/src/hsp2/state/state.py b/src/hsp2/state/state.py index d172a5b6..dd8c248e 100644 --- a/src/hsp2/state/state.py +++ b/src/hsp2/state/state.py @@ -1,31 +1,286 @@ """General routines for SPECL""" -import numpy as np -from pandas import date_range -from pandas.tseries.offsets import Minute -from numba.typed import Dict -from numpy import zeros -from numba import njit, types # import the types -import os import importlib.util +import os import sys +import numpy as np +import numba as nb +from numba import njit, types, typeof # import the types supplies int64, float64 +from numba.experimental import jitclass +from numba.typed import Dict as ntdict +from numpy import zeros, float64 as npfloat64, int64 as npint64 +from pandas import date_range, DataFrame +from pandas.tseries.offsets import Minute -def init_state_dicts(): - """ - This contains the base dictionaries used to pass model state amongst modules and custom code plugins - """ - state = {} # shared state Dictionary, contains numba-ready Dicts - state["state_paths"] = Dict.empty( - key_type=types.unicode_type, value_type=types.int64 - ) - state["state_ix"] = Dict.empty(key_type=types.int64, value_type=types.float64) - state["dict_ix"] = Dict.empty(key_type=types.int64, value_type=types.float64[:, :]) - state["ts_ix"] = Dict.empty(key_type=types.int64, value_type=types.float64[:]) - # initialize state for hydr - # add a generic place to stash model_data for dynamic components - state["model_data"] = {} - return state +# Beginning in operation these are likely to be located in model objects when we go fully to that level. +# But for now, they are here to maintain compatiility with the existing code base +# Combine these into a spec to create the class +tindex = date_range("1984-01-01", "2020-12-31", freq=Minute(60)) + +state_spec = [ + # the first entries here are NP arrays, fixed dimenstions, and fast + ("state_ix", typeof(np.asarray(zeros(1), dtype="float64")) ), + ("op_tokens", typeof(types.int64(zeros((1, 64)))) ), + ("op_exec_lists", typeof(types.int64(zeros((1, 1024)))) ), + ("model_exec_list", typeof(np.asarray(zeros(1), dtype="int64")) ), + ("tindex", typeof(tindex.to_numpy()) ), + # dict_ix SHOULD BE an array, this is TBD. Likely defer till OM class runtimes + ("dict_ix", types.DictType(types.int64, types.float64[:, :]) ), + # below here are dictionaries as they are not used in runtime and can be slow + ("state_paths", types.DictType(types.unicode_type, types.int64) ), + ("ts_paths", types.DictType(types.unicode_type, types.float64[:]) ), + ("ts_ix", types.DictType(types.int64, types.float64[:]) ), + ("last_id", types.int64), + ("model_root_name", types.unicode_type), + ("state_step_hydr", types.unicode_type), + ("hsp2_local_py", types.boolean), + ("num_ops", types.int64), + ("op_len", types.int64), + ("operation", types.unicode_type), + ("segment", types.unicode_type), + ("activity", types.unicode_type), + ("domain", types.unicode_type), + ("state_step_om", types.unicode_type), + ("hsp_segments", types.DictType(types.unicode_type, types.unicode_type) ) +] + + +state_lite = [ + ("num_ops", nb.int64), + # the first entries here are NP arrays, fixed dimenstions, and fast + ("state_ix", nb.float64[:]), + ("op_tokens", typeof(types.int64(zeros((1, 64)))) ), + ("op_exec_lists", typeof(types.int64(zeros((1, 1024)))) ), + ("model_exec_list", typeof(np.asarray(zeros(1), dtype="int64")) ), + ("tindex", typeof(tindex.to_numpy()) ), + # dict_ix SHOULD BE an array, this is TBD. Likely defer till OM class runtimes + ("dict_ix", types.DictType(types.int64, types.float64[:, :]) ), + ("ts_ix", types.DictType(types.int64, types.float64[:]) ), + ("state_paths", types.DictType(types.unicode_type, types.int64) ), + ("last_id", types.int64), + ("model_root_name", types.unicode_type), + ("state_step_hydr", types.unicode_type), + ("hsp2_local_py", types.boolean), + ("num_ops", types.int64), + ("operation", types.unicode_type), + ("segment", types.unicode_type), + ("activity", types.unicode_type), + ("domain", types.unicode_type), + ("state_step_om", types.unicode_type), + ("hsp_segments", types.DictType(types.unicode_type, types.unicode_type) ) +] + +@jitclass(state_lite) +class state_class_lite: + def __init__(self, num_ops): + self.num_ops = num_ops + state_ix = zeros(self.num_ops) + self.state_ix = state_ix.astype(npfloat64) + op_tokens = zeros((self.num_ops, 64)) + self.op_tokens = op_tokens.astype(npint64) + # TODO: move to individual objects in OM/RCHRES/PERLND/... + op_exec_lists = zeros((self.num_ops, 1024)) + self.op_exec_lists = op_exec_lists.astype(npint64) + # TODO: is this even needed? Since each domain has it's own exec list? + model_exec_list = zeros(self.num_ops) + self.model_exec_list = model_exec_list.astype(npint64) + self.activity = "" + self.segment = "" + self.operation = "" + self.domain = "" + self.model_root_name = "" + +@njit(cache=True) +def make_state_lite(num_ops): + sc = state_class_lite(num_ops) + return sc + +def state_copy(statesrc, statedest): + # copies from a non-jit to a jit or vice versa + statedest.num_ops = statesrc.num_ops + statedest.state_ix = statesrc.state_ix + statedest.dict_ix = statesrc.dict_ix + statedest.ts_ix = statesrc.ts_ix + statedest.op_tokens = statesrc.op_tokens + statedest.op_exec_lists = statesrc.op_exec_lists + statedest.model_exec_list = statesrc.model_exec_list + statedest.state_paths = statesrc.state_paths + statedest.hsp2_local_py = statesrc.hsp2_local_py + statedest.model_root_name = statesrc.model_root_name + statedest.hsp_segments = statesrc.hsp_segments + statedest.state_step_hydr = statesrc.state_step_hydr + statedest.state_step_om = statesrc.state_step_om + statedest.domain = statesrc.domain + +""" +This function is a simple numba compiled fn to append to a numba dict quickly +This is necessary because there is no way to just cast a normal dictionary to a numba Dict +And appending a numba Dict in regular python is super super slow. +If this does not provide good enough performance when adding a large amount of variables +We may consider using a jitted loop to copy all elements from a dictionary to Dict +""" +@njit(cache=True) +def append_numba_dict(var_dict, var_key, var_val): + var_dict[var_key] = var_val + return var_dict + +@njit(cache=True) +def set_numba_value(var_dict, var_key, var_val): + var_dict[var_key] = var_val + return var_dict + +@njit(cache=True) +def nkey_exists(var_dict, var_key): + return (var_key in var_dict) + +class state_class: + def __init__(self, state_ix, op_tokens, state_paths, op_exec_lists, model_exec_list, dict_ix, ts_ix, hsp_segments): + self.num_ops = 0 + # IMPORTANT these are handled as nparray as numba Dict would be super slow. + # Note: in the type declaration above we are alloweed to use the shortened form op_tokens = int64(zeros((1,64))) + # but in jited class that throws an error and we have to use the form op_tokens.astype(int64) + # to do the type cast + self.state_ix = state_ix.astype(npfloat64) + self.op_tokens = op_tokens.astype(npint64) + # TODO: move to individual objects in OM/RCHRES/PERLND/... + self.op_exec_lists = op_exec_lists.astype(npint64) + # TODO: is this even needed? Since each domain has it's own exec list? + self.model_exec_list = model_exec_list.astype(npint64) + # Done with nparray initializations + # this dict_ix approach is inherently slow, and should be replaced by some other np table type + # on an as-needed basis if possible. Especially for dataMatrix types which are supposed to be fast + # state can still get values via get_state, by grabbing a reference object and then accessing it's storage + self.dict_ix = dict_ix + self.ts_ix = ts_ix + self.state_paths = state_paths + self.hsp_segments = hsp_segments + self.state_step_om = "disabled" + self.state_step_hydr = "disabled" + self.model_root_name = "" + self.operation = "" + self.segment = "" + self.activity = "" + self.domain = "" + self.last_id = 0 + self.hsp2_local_py = False + self.op_len = 64 # how wide is an op list array + return + + @property + def size(self): + return self.state_ix.size + + def append_state(self, var_value): + val_ix = self.size # next ix value= size since ix starts from zero + self.state_ix = np.append(self.state_ix, var_value) + self.last_id = val_ix + self.resize() + return val_ix + + def set_token(self, var_ix, tokens, debug=False): + if var_ix not in range(len(self.state_ix)): + if debug: + print("Undefined index value,", var_ix, ", provided for set_token()") + return False + if var_ix not in range(np.shape(self.op_tokens)[0]): + if debug: + print("set_token called for ix", var_ix, ", need to expand") + self.resize() + # in a perfect world we would insure that the length of tokens is correct + # and if not, we would resize. But this is only called from ModelObject + # and its methods add_op_tokens() and model_format_ops(ops) enforce the + # length limit described by ModelObject.max_token_length (64) which must match + self.op_tokens[var_ix] = tokens + + def resize(self, debug=False): + num_ops = self.size + # print("state_ix has", num_ops, "elements") + ops_needed = num_ops - np.shape(self.op_tokens)[0] + if ops_needed == 0: + # print("resize op_tokens unneccesary, state has", self.size,"indices and op_tokens has", np.shape(self.op_tokens)[0], "elements") + return + if debug: + print("op_tokens needs", ops_needed, "slots") + add_ops = np.full((ops_needed,self.op_len),-1) # fill with -1 + zeros((ops_needed, 64)) + # print("Created add_ops with", ops_needed, "slots") + # we use the 3rd param "axis=1" to prevent flattening of array + if self.op_tokens.size == 0: + if debug: + print("Creating op_tokens") + self.op_tokens = add_ops.astype(npint64) + else: + if debug: + print("Merging op_tokens") + add_ops = np.append(self.op_tokens, add_ops, 0) + self.op_tokens = add_ops.astype(npint64) + ops_needed = num_ops - np.shape(self.op_exec_lists)[0] + el_width = np.shape(self.op_exec_lists)[1] + if debug: + print("op_exec_lists needs", ops_needed, "slots") + if ops_needed == 0: + return + add_ops = zeros((ops_needed, el_width)) + # we use the 3rd param "axis=1" to prevent flattening of array + if self.op_exec_lists.size == 0: + if debug: + print("Creating op_exec_lists") + self.op_exec_lists = add_ops.astype(npint64) + else: + if debug: + print("Merging op_exec_lists") + add_ops = np.append(self.op_exec_lists, add_ops, 0) + self.op_exec_lists = add_ops.astype(npint64) + return + + def set_exec_list(self, ix, op_exec_list): + for i in range(len(op_exec_list)): + self.op_exec_lists[ix][i] = op_exec_list[i] + + def set_state(self, var_path, var_value=0.0, debug=False): + """ + Given an hdf5 style path to a variable, set the value + If the variable does not yet exist, create it. + Returns the integer key of the variable in the state_ix Dict + """ + if not nkey_exists(self.state_paths, var_path): + #if var_path not in self.state_paths: + # we need to add this to the state + var_ix = self.append_state(var_value) + self.state_paths = append_numba_dict(self.state_paths, var_path, var_ix) + else: + var_ix = self.get_state_ix(var_path) + #self.state_ix[var_ix] = var_value + self.state_ix = set_numba_value(self.state_ix, var_ix, var_value) + #append_numba_dict(self.state_ix, var_value, var_ix) + if debug: + print("Setting state_ix[", var_ix, "], to", var_value) + return var_ix + + def get_state_ix(self, var_path): + """ + Find the integer key of a variable name in state_ix + """ + if var_path == False: + # handle a bad path with False + return False + if var_path not in self.state_paths: + # we need to add this to the state + return False # should throw an error + var_ix = self.state_paths[var_path] + return var_ix + + def get_ix_path(self, var_ix): + """ + Find the path of a variable with integer key in state_ix + """ + spath = None + for spath, ix in self.state_paths.items(): + if var_ix == ix: + # we need to add this to the state + return spath + return spath def op_path_name(operation, id): @@ -36,8 +291,8 @@ def op_path_name(operation, id): path_name = f"{operation}_{operation[0]}{tid}" return path_name - -def get_state_ix(state_ix, state_paths, var_path): +@njit(cache=True) +def get_state_ix(state_paths, var_path): """ Find the integer key of a variable name in state_ix """ @@ -48,17 +303,6 @@ def get_state_ix(state_ix, state_paths, var_path): return var_ix -def get_ix_path(state_paths, var_ix): - """ - Find the path of a variable with integer key in state_ix - """ - for spath, ix in state_paths.items(): - if var_ix == ix: - # we need to add this to the state - return spath - return False - - def set_state(state_ix, state_paths, var_path, default_value=0.0, debug=False): """ Given an hdf5 style path to a variable, set the value @@ -81,15 +325,15 @@ def state_add_ts(state, var_path, default_value=0.0, debug=False): If the variable does not yet exist, create it. Returns the integer key of the variable in the state_ix Dict """ - if var_path not in state["state_paths"].keys(): + if var_path not in state.state_paths.keys(): # we need to add this to the state - state["state_paths"][var_path] = append_state(state["state_ix"], default_value) - var_ix = get_state_ix(state["state_ix"], state["state_paths"], var_path) + state.state_paths[var_path] = append_state(state.state_ix, default_value) + var_ix = get_state_ix(state.state_ix, state.state_paths, var_path) if debug == True: print("Setting state_ix[", var_ix, "], to", default_value) # siminfo needs to be in the model_data array of state. Can be populated by HSP2 or standalone by ops model - state["ts_ix"][var_ix] = np.full_like( - zeros(state["model_data"]["siminfo"]["steps"]), default_value + state.ts_ix[var_ix] = np.full_like( + zeros(om_operations["model_data"]["steps"]), default_value ) return var_ix @@ -120,26 +364,7 @@ def append_state(state_ix, var_value): return val_ix -def state_context_hsp2(state, operation, segment, activity): - # this establishes domain info so that a module can know its paths - state["operation"] = operation - state["segment"] = segment # - state["activity"] = activity - # give shortcut to state path for the upcoming function - # insure that there is a model object container - seg_name = operation + "_" + segment - seg_path = "/STATE/" + state["model_root_name"] + "/" + seg_name - if "hsp_segments" not in state.keys(): - state[ - "hsp_segments" - ] = {} # for later use by things that need to know hsp entities and their paths - if seg_name not in state["hsp_segments"].keys(): - state["hsp_segments"][seg_name] = seg_path - - state["domain"] = seg_path # + "/" + activity # may want to comment out activity? - - -def state_siminfo_hsp2(parameter_obj, siminfo, io_manager, state): +def state_siminfo_hsp2(state, parameter_obj, siminfo, io_manager): # Add crucial simulation info for dynamic operation support delt = parameter_obj.opseq.INDELT_minutes[0] # get initial value for STATE objects siminfo["delt"] = delt @@ -147,29 +372,63 @@ def state_siminfo_hsp2(parameter_obj, siminfo, io_manager, state): siminfo["start"], siminfo["stop"], freq=Minute(delt) )[1:] siminfo["steps"] = len(siminfo["tindex"]) + state.tindex = siminfo["tindex"].to_numpy() hdf5_path = io_manager._input.file_path (fbase, fext) = os.path.splitext(hdf5_path) - state["model_root_name"] = os.path.split(fbase)[1] # takes the text before .h5 + state.model_root_name = os.path.split(fbase)[1] # takes the text before .h5 +#@njit(cache=True) +def state_context_hsp2(state, operation, segment, activity): + # this establishes domain info so that a module can know its paths + state.operation = operation + state.segment = segment # + state.activity = activity + # give shortcut to state path for the upcoming function + # insure that there is a model object container + (seg_name, seg_path) = state_segname(state, operation, segment, activity) + #if seg_name not in state.hsp_segments.keys(): + if not nkey_exists(state.hsp_segments, seg_name): # test this for njit + state.hsp_segments = append_numba_dict(state.hsp_segments, seg_name, seg_path) + state.domain = state_domain(state, operation, segment, activity) + +def state_domain(state, operation, segment, activity): + (seg_name, seg_path) = state_segname(state, operation, segment, activity) + domain = seg_path # later we may make his custom depending on the operation/activity + # like + "/" + activity + return domain + +def state_segname(state, operation, segment, activity): + seg_name = operation + "_" + segment + seg_path = "/STATE/" + state.model_root_name + "/" + seg_name + return (seg_name, seg_path) -def state_init_hsp2(state, opseq, activities): +def state_init_hsp2(state, opseq, activities, timer): # This sets up the state entries for all state compatible HSP2 model variables - # print("STATE initializing contexts.") + print("STATE initializing contexts.") for _, operation, segment, delt in opseq.itertuples(): + seg_name = operation + "_" + segment + seg_path = "/STATE/" + state.model_root_name + "/" + seg_name + # set up named paths for model operations + state.set_state(seg_path, 0.0) if operation != "GENER" and operation != "COPY": for activity, function in activities[operation].items(): if activity == "HYDR": state_context_hsp2(state, operation, segment, activity) - hydr_init_ix(state, state["domain"]) elif activity == "SEDTRN": state_context_hsp2(state, operation, segment, activity) - sedtrn_init_ix(state, state["domain"]) elif activity == "SEDMNT": state_context_hsp2(state, operation, segment, activity) - sedmnt_init_ix(state, state["domain"]) elif activity == "RQUAL": state_context_hsp2(state, operation, segment, activity) - rqual_init_ix(state, state["domain"]) + + +def state_load_dynamics_hsp2(state, io_manager, siminfo): + # Load any dynamic components if present, and store variables on objects + # if a local file with state_step_hydr() was found in load_dynamics(), we add it to state + state.hsp2_local_py = load_dynamics( + io_manager, siminfo + ) # Stores the actual function in state + state.state_step_hydr = siminfo["state_step_hydr"] # enabled or disabled def state_load_hdf5_components( @@ -186,14 +445,6 @@ def state_load_hdf5_components( return -def state_load_dynamics_hsp2(state, io_manager, siminfo): - # Load any dynamic components if present, and store variables on objects - hsp2_local_py = load_dynamics(io_manager, siminfo) - # if a local file with state_step_hydr() was found in load_dynamics(), we add it to state - state["state_step_hydr"] = siminfo["state_step_hydr"] # enabled or disabled - state["hsp2_local_py"] = hsp2_local_py # Stores the actual function in state - - @njit def get_domain_state(state_paths, state_ix, domain, varkeys): # get values for a set of variables in a domain @@ -251,14 +502,16 @@ def hydr_state_vars(): ] -def hydr_init_ix(state, domain): +def hydr_init_ix(state, domain, debug = False): # get a list of keys for all hydr state variables hydr_state = hydr_state_vars() - hydr_ix = Dict.empty(key_type=types.unicode_type, value_type=types.int64) + hydr_ix = DataFrame() for i in hydr_state: # var_path = f'{domain}/{i}' var_path = domain + "/" + i - hydr_ix[i] = set_state(state["state_ix"], state["state_paths"], var_path, 0.0) + if debug: + print("initializing", var_path) + hydr_ix[i] = state.set_state(var_path, 0.0) return hydr_ix @@ -270,11 +523,11 @@ def sedtrn_state_vars(): def sedtrn_init_ix(state, domain): # get a list of keys for all sedtrn state variables sedtrn_state = sedtrn_state_vars() - sedtrn_ix = Dict.empty(key_type=types.unicode_type, value_type=types.int64) + sedtrn_ix = DataFrame() for i in sedtrn_state: # var_path = f'{domain}/{i}' var_path = domain + "/" + i - sedtrn_ix[i] = set_state(state["state_ix"], state["state_paths"], var_path, 0.0) + sedtrn_ix[i] = state.set_state(var_path, 0.0) return sedtrn_ix @@ -286,13 +539,14 @@ def sedmnt_state_vars(): def sedmnt_init_ix(state, domain): # get a list of keys for all sedmnt state variables sedmnt_state = sedmnt_state_vars() - sedmnt_ix = Dict.empty(key_type=types.unicode_type, value_type=types.int64) + sedmnt_ix = DataFrame() for i in sedmnt_state: var_path = domain + "/" + i - sedmnt_ix[i] = set_state(state["state_ix"], state["state_paths"], var_path, 0.0) + sedmnt_ix[i] = state.set_state(var_path, 0.0) return sedmnt_ix +@njit(cache=True) def rqual_state_vars(): rqual_state = [ "DOX", @@ -309,19 +563,18 @@ def rqual_state_vars(): ] return rqual_state - def rqual_init_ix(state, domain): # get a list of keys for all rqual state variables rqual_state = rqual_state_vars() - rqual_ix = Dict.empty(key_type=types.unicode_type, value_type=types.int64) + rqual_ix = DataFrame() for i in rqual_state: var_path = domain + "/" + i - rqual_ix[i] = set_state(state["state_ix"], state["state_paths"], var_path, 0.0) + rqual_ix[i] = state.set_state(var_path, 0.0) return rqual_ix -@njit -def hydr_get_ix(state_ix, state_paths, domain): +@njit(cache=True) +def hydr_get_ix(state, domain): # get a list of keys for all hydr state variables hydr_state = [ "DEP", @@ -341,38 +594,40 @@ def hydr_get_ix(state_ix, state_paths, domain): "VOL", "VOLEV", ] - hydr_ix = Dict.empty(key_type=types.unicode_type, value_type=types.int64) + # print(state.state_paths) + hydr_ix = ntdict.empty(key_type=types.unicode_type, value_type=types.int64) for i in hydr_state: # var_path = f'{domain}/{i}' var_path = domain + "/" + i - hydr_ix[i] = state_paths[var_path] + # print("looking for:", var_path) + hydr_ix[i] = get_state_ix(state.state_paths, var_path) return hydr_ix -@njit -def sedtrn_get_ix(state_ix, state_paths, domain): +@njit(cache=True) +def sedtrn_get_ix(state, domain): # get a list of keys for all sedtrn state variables sedtrn_state = ["RSED4", "RSED5", "RSED6"] - sedtrn_ix = Dict.empty(key_type=types.unicode_type, value_type=types.int64) + sedtrn_ix = ntdict.empty(key_type=types.unicode_type, value_type=types.int64) for i in sedtrn_state: var_path = domain + "/" + i - sedtrn_ix[i] = state_paths[var_path] + sedtrn_ix[i] = get_state_ix(state.state_paths, var_path) return sedtrn_ix -@njit -def sedmnt_get_ix(state_ix, state_paths, domain): +@njit(cache=True) +def sedmnt_get_ix(state, domain): # get a list of keys for all sedmnt state variables sedmnt_state = ["DETS"] - sedmnt_ix = Dict.empty(key_type=types.unicode_type, value_type=types.int64) + sedmnt_ix = ntdict.empty(key_type=types.unicode_type, value_type=types.int64) for i in sedmnt_state: var_path = domain + "/" + i - sedmnt_ix[i] = state_paths[var_path] + sedmnt_ix[i] = get_state_ix(state.state_paths, var_path) return sedmnt_ix @njit -def rqual_get_ix(state_ix, state_paths, domain): +def rqual_get_ix(state, domain): # get a list of keys for all sedmnt state variables rqual_state = [ "DOX", @@ -387,10 +642,10 @@ def rqual_get_ix(state_ix, state_paths, domain): "BRPO42", "CFOREA", ] - rqual_ix = Dict.empty(key_type=types.unicode_type, value_type=types.int64) + rqual_ix = ntdict.empty(key_type=types.unicode_type, value_type=types.int64) for i in rqual_state: var_path = domain + "/" + i - rqual_ix[i] = state_paths[var_path] + rqual_ix[i] = get_state_ix(state.state_paths, var_path) return rqual_ix diff --git a/src/hsp2/state/state_definitions.py b/src/hsp2/state/state_definitions.py new file mode 100644 index 00000000..cf563b0b --- /dev/null +++ b/src/hsp2/state/state_definitions.py @@ -0,0 +1,31 @@ +# null function to be loaded when not supplied by user +from numba import njit # import the types +from numba.typed import Dict +from numba import types # import the types +from numpy import zeros + +state_empty = {} # shared state Dictionary, contains numba-ready Dicts +state_empty["state_paths"] = Dict.empty( + key_type=types.unicode_type, value_type=types.int64 +) +state_empty["state_ix"] = types.float64(zeros(0)) +state_empty["dict_ix"] = Dict.empty(key_type=types.int64, value_type=types.float64[:, :]) +state_empty["ts_ix"] = Dict.empty(key_type=types.int64, value_type=types.float64[:]) +state_empty["hsp_segments"] = Dict.empty(key_type=types.unicode_type, value_type=types.unicode_type) +state_empty["op_tokens"] = types.int64(zeros((0, 64))) +state_empty["model_exec_list"] = types.int64(zeros(0)) +state_empty["op_exec_lists"] = types.int64(zeros((0, 1024))) + +# initialize state for hydr +# add a generic place to stash model_data for dynamic components +state_empty["model_data"] = {} + +# variables: these could go into individual files later or in object defs +rqual_state_vars = [ + "DOX", "BOD", "NO3", "TAM", "NO2", "PO4", "BRTAM1", + "BRTAM2", "BRPO41", "BRPO42", "CFOREA" +] + +@njit +def state_step_hydr(state, step): + return diff --git a/src/hsp2/state/state_fn_defaults.py b/src/hsp2/state/state_fn_defaults.py deleted file mode 100644 index 45c7a0c0..00000000 --- a/src/hsp2/state/state_fn_defaults.py +++ /dev/null @@ -1,7 +0,0 @@ -# null function to be loaded when not supplied by user -from numba import njit # import the types - - -@njit -def state_step_hydr(state_info, state_paths, state_ix, dict_ix, ts_ix, hydr_ix, step): - return diff --git a/tests/convert/regression_base.py b/tests/convert/regression_base.py index 613c4851..bd9e43c5 100644 --- a/tests/convert/regression_base.py +++ b/tests/convert/regression_base.py @@ -23,6 +23,7 @@ def __init__( tcodes: List[str] = ["2"], ids: List[str] = [], threads: int = os.cpu_count() - 1, + tests_root_dir = None ) -> None: self.compare_case = compare_case self.operations = operations @@ -34,19 +35,14 @@ def __init__( self._init_files() def _init_files(self): - current_directory = os.path.dirname( - os.path.abspath(inspect.getframeinfo(inspect.currentframe()).filename) - ) - source_root_path = os.path.split(os.path.split(current_directory)[0])[0] - tests_root_dir = os.path.join(source_root_path, "tests") self.html_file = os.path.join( - tests_root_dir, f"HSPF_HSP2_{self.compare_case}.html" + self.tests_root_dir, f"HSPF_HSP2_{self.compare_case}.html" ) - test_dirs = os.listdir(tests_root_dir) + test_dirs = os.listdir(self.tests_root_dir) for test_dir in test_dirs: if test_dir == self.compare_case: - test_root = os.path.join(tests_root_dir, test_dir) + test_root = os.path.join(self.tests_root_dir, test_dir) self._get_hdf5_data(test_root) self._get_hbn_data(test_root) diff --git a/tests/test_regression.py b/tests/test_regression.py index 21051a77..eb8f50a9 100644 --- a/tests/test_regression.py +++ b/tests/test_regression.py @@ -1,13 +1,27 @@ from pathlib import Path +import os import pytest from hsp2.hsp2tools.commands import import_uci, run from hsp2.hsp2tools.HDF5 import HDF5 +from typing import Dict, List, Tuple, Union from .convert.regression_base import RegressTest as RegressTestBase class RegressTest(RegressTestBase): + def __init__(self, compare_case: str, operations: List[str] = [], + activities: List[str] = [], tcodes: List[str] = ["2"], + ids: List[str] = [], threads: int = os.cpu_count() - 1, + tests_root_dir = None + ) -> None: + if tests_root_dir is None: + tests_root_dir = Path(__file__).resolve().parent + super(RegressTest, self).__init__( + compare_case, operations, activities, + tcodes, ids, threads, tests_root_dir + ) + def _get_hsp2_data(self, test_root) -> None: test_root_hspf = Path(test_root) / "HSPFresults" hspf_uci = test_root_hspf.resolve() / f"{self.compare_case}.uci" @@ -24,10 +38,10 @@ def _get_hsp2_data(self, test_root) -> None: self.hsp2_data = HDF5(str(self.temp_h5file)) def _init_files(self): - test_dir = Path(__file__).resolve().parent - assert test_dir.name == "tests" - - test_root = test_dir / self.compare_case + print("Checking tests_root_dir has tests in the name") + assert self.tests_root_dir.name == "tests" + test_root = self.tests_root_dir / self.compare_case + print("Checking case tests_dir exists:", test_root) assert test_root.exists() self._get_hbn_data(str(test_root)) diff --git a/tests/testcbp/HSP2results/JL1_6562_6560.uci b/tests/testcbp/HSP2results/JL1_6562_6560.uci new file mode 100644 index 00000000..a6f3ad8f --- /dev/null +++ b/tests/testcbp/HSP2results/JL1_6562_6560.uci @@ -0,0 +1,261 @@ +RUN + +GLOBAL + JL1_6562_6 riv | P5 | subsheds | Beaver + START 1984/01/01 END 2020/12/31 + RUN INTERP OUTPUT LEVEL 1 1 + RESUME 0 RUN 1 UNIT SYSTEM 1 +END GLOBAL + +FILES + ***<----FILE NAME-------------------------------------------------> +WDM1 21 met_N51003.wdm +WDM2 22 prad_N51003.wdm +WDM3 23 ps_sep_div_ams_subsheds_JL1_6562_6560.wdm +WDM4 24 JL1_6562_6560.wdm +MESSU 25 JL1_6562_6560.ech + 26 JL1_6562_6560.out + 31 JL1_6562_6560.tau +END FILES + +OPN SEQUENCE + INGRP INDELT 01:00 + RCHRES 1 + PLTGEN 1 + END INGRP +END OPN SEQUENCE + +RCHRES + ACTIVITY + # - # HYFG ADFG CNFG HTFG SDFG GQFG OXFG NUFG PKFG PHFG *** + 1 1 1 0 0 0 0 0 0 0 0 + END ACTIVITY + + PRINT-INFO + # - # HYFG ADFG CNFG HTFG SDFG GQFG OXFG NUFG PKFG PHFG PIVL***PY + 1 5 5 0 0 0 0 0 0 0 0 0 12 + END PRINT-INFO + + GEN-INFO + RCHRES<-------Name------->Nexit Unit Systems Printer *** + # - # User t-series Engl Metr LKFG *** + 1 JL1_6562_6560 3 1 1 1 26 0 0 + END GEN-INFO + + HYDR-PARM1 + RCHRES Flags for HYDR section *** + # - # VC A1 A2 A3 ODFVFG for each ODGTFG for each *** FUNCT for each + FG FG FG FG possible exit possible exit *** possible exit + 1 2 3 4 5 1 2 3 4 5 *** 1 2 3 4 5 + VC A1 A2 A3 V1 V2 V3 V4 V5 G1 G2 G3 G4 G5 *** F1 F2 F3 F4 F5 + 1 0 1 1 1 0 0 4 0 0 1 2 0 0 0 0 0 0 0 0 + END HYDR-PARM1 + + HYDR-PARM2 + RCHRES *** + # - # FTABNO LEN DELTH STCOR KS DB50 *** + 1 1. 17.01 249.28 0.5 + END HYDR-PARM2 + + HYDR-INIT + RCHRES Initial conditions for HYDR section *** + # - # VOL Initial value of COLIND *** Initial value of OUTDGT + (ac-ft) for each possible exit *** for each possible exit + VOL CEX1 CEX2 CEX3 CEX4 CEX5 *** DEX1 DEX2 DEX3 DEX4 DEX5 + 1 293.16000 + END HYDR-INIT + + ADCALC-DATA + RCHRES Data for section ADCALC *** + # - # CRRAT VOL *** + 1 1.5 293.16 + END ADCALC-DATA + +END RCHRES + +FTABLES + FTABLE 1 +NOTE: FLOODPLAIN BASE = 5*BANKFULL WIDTH *** + FLOODPLAIN SIDE-SLOPE = SAME AS CHANNEL'S *** + ROWS COLS *** + 19 4 + DEPTH AREA VOLUME DISCH *** + (FT) (ACRES) (AC-FT) (CFS) *** + 0.000 0.000 0.00 0.00 + 0.423 11.894 4.78 23.85 + 0.846 13.061 10.06 77.00 + 1.269 14.227 15.83 154.23 + 1.691 15.393 22.09 254.22 + 2.114 16.560 28.85 376.68 + 2.537 17.726 36.10 521.75 + 2.960 18.893 43.84 689.83 + 3.383 20.059 52.07 881.43 + 3.806 21.225 60.80 1097.15 + 4.806 108.886 168.31 1305.89 + 6.108 112.478 312.43 3577.19 + 7.410 116.069 461.23 6694.41 + 8.712 119.661 614.71 10572.52 + 10.014 123.253 772.86 15161.71 + 11.316 126.845 935.69 20429.73 + 12.618 130.437 1103.20 26354.64 + 13.921 134.028 1275.38 32921.26 + 15.223 137.620 1452.24 40119.05 + END FTABLE 1 +END FTABLES + +EXT SOURCES +<-Volume-> SsysSgap<--Mult-->Tran <-Target vols> <-Grp> <-Member->*** + # # tem strg<-factor->strg # # # #*** +*** METEOROLOGY +WDM1 1000 EVAP ENGLZERO 1.000 SAME RCHRES 1 EXTNL POTEV +WDM1 1001 DEWP ENGLZERO SAME RCHRES 1 EXTNL DEWTMP +WDM1 1002 WNDH ENGLZERO SAME RCHRES 1 EXTNL WIND +WDM1 1003 RADH ENGLZERO SAME RCHRES 1 EXTNL SOLRAD +WDM1 1004 ATMP ENGLZERO SAME RCHRES 1 EXTNL GATMP +WDM1 1005 CLDC ENGLZERO SAME RCHRES 1 EXTNL CLOUD + +*** PRECIPITATION AND ATMOSPHERIC DEPOSITION LOADS +WDM2 2000 HPRC ENGLZERO SAME RCHRES 1 EXTNL PREC +WDM2 2001 NO23 ENGLZERO DIV RCHRES 1 EXTNL NUADFX 1 1 +WDM2 2002 NH4A ENGLZERO DIV RCHRES 1 EXTNL NUADFX 2 1 +WDM2 2003 NO3D ENGLZERO DIV RCHRES 1 EXTNL NUADFX 1 1 +WDM2 2004 NH4D ENGLZERO DIV RCHRES 1 EXTNL NUADFX 2 1 +WDM2 2005 ORGN ENGLZERO DIV RCHRES 1 EXTNL PLADFX 1 1 +WDM2 2006 PO4A ENGLZERO DIV RCHRES 1 EXTNL NUADFX 3 1 +WDM2 2007 ORGP ENGLZERO DIV RCHRES 1 EXTNL PLADFX 2 1 + +*** POINT SOURCE +WDM3 3000 FLOW ENGLZERO DIV RCHRES 1 INFLOW IVOL +WDM3 3001 HEAT ENGLZERO DIV RCHRES 1 INFLOW IHEAT +WDM3 3002 NH3X ENGLZERO DIV RCHRES 1 INFLOW NUIF1 2 +WDM3 3003 NO3X ENGLZERO DIV RCHRES 1 INFLOW NUIF1 1 +WDM3 3004 ORNX ENGLZERO DIV RCHRES 1 INFLOW PKIF 3 +WDM3 3005 PO4X ENGLZERO DIV RCHRES 1 INFLOW NUIF1 4 +WDM3 3006 ORPX ENGLZERO DIV RCHRES 1 INFLOW PKIF 4 +WDM3 3021 BODX ENGLZERO DIV RCHRES 1 INFLOW OXIF 2 +WDM3 3022 TSSX ENGLZERO 0.0005 DIV RCHRES 1 INFLOW ISED 3 +WDM3 3023 DOXX ENGLZERO DIV RCHRES 1 INFLOW OXIF 1 +WDM3 3024 TOCX ENGLZERO DIV RCHRES 1 INFLOW PKIF 5 + +*** RPA LOAD +WDM3 3031 NH3R ENGLZERO 1.0000 DIV RCHRES 1 INFLOW NUIF1 2 +WDM3 3032 NO3R ENGLZERO 1.0000 DIV RCHRES 1 INFLOW NUIF1 1 +WDM3 3033 RONR ENGLZERO 1.0000 DIV RCHRES 1 INFLOW PKIF 3 +WDM3 3034 PO4R ENGLZERO 1.0000 DIV RCHRES 1 INFLOW NUIF1 4 +WDM3 3035 ROPR ENGLZERO 1.0000 DIV RCHRES 1 INFLOW PKIF 4 +WDM3 3036 BODR ENGLZERO 1.0000 DIV RCHRES 1 INFLOW OXIF 2 +WDM3 3037 SNDR ENGLZERO 1.0000 DIV RCHRES 1 INFLOW ISED 1 +WDM3 3038 SLTR ENGLZERO 1.0000 DIV RCHRES 1 INFLOW ISED 2 +WDM3 3039 CLYR ENGLZERO 1.0000 DIV RCHRES 1 INFLOW ISED 3 + +*** DIVERSIONS +WDM3 3007 DIVR ENGLZERO SAME RCHRES 1 EXTNL OUTDGT 1 +WDM3 3008 DIVA ENGLZERO SAME RCHRES 1 EXTNL OUTDGT 2 + +*** SEPTIC +WDM3 3010 SNO3 ENGLZERO 1.0000 DIV RCHRES 1 INFLOW NUIF1 1 + +*** RIB +WDM3 3012 NH3X ENGLZERO DIV RCHRES 1 INFLOW NUIF1 2 +WDM3 3013 NO3X ENGLZERO DIV RCHRES 1 INFLOW NUIF1 1 +WDM3 3014 ORNX ENGLZERO DIV RCHRES 1 INFLOW PKIF 3 +WDM3 3015 PO4X ENGLZERO DIV RCHRES 1 INFLOW NUIF1 4 +WDM3 3016 ORPX ENGLZERO DIV RCHRES 1 INFLOW PKIF 4 +WDM3 3017 BODX ENGLZERO DIV RCHRES 1 INFLOW OXIF 2 + +*** AEOLIAN SEDIMENT +WDM3 3061 SFAS ENGLZERO 7.027e-06DIV RCHRES 1 INFLOW ISED 2 +WDM3 3062 SFAC ENGLZERO 7.027e-06DIV RCHRES 1 INFLOW ISED 3 + +*** UPSTREAM and EOS INPUT *** +WDM4 11 WATR ENGLZERO SAME RCHRES 1 INFLOW IVOL +WDM4 12 HEAT ENGLZERO SAME RCHRES 1 INFLOW IHEAT +WDM4 13 DOXY ENGLZERO SAME RCHRES 1 INFLOW OXIF 1 +WDM4 21 SAND ENGLZERO SAME RCHRES 1 INFLOW ISED 1 +WDM4 22 SILT ENGLZERO SAME RCHRES 1 INFLOW ISED 2 +WDM4 23 CLAY ENGLZERO SAME RCHRES 1 INFLOW ISED 3 +WDM4 31 NO3D ENGLZERO SAME RCHRES 1 INFLOW NUIF1 1 +WDM4 32 NH3D ENGLZERO SAME RCHRES 1 INFLOW NUIF1 2 +WDM4 33 NH3A ENGLZERO SAME RCHRES 1 INFLOW NUIF2 1 1 +WDM4 34 NH3I ENGLZERO SAME RCHRES 1 INFLOW NUIF2 2 1 +WDM4 35 NH3C ENGLZERO SAME RCHRES 1 INFLOW NUIF2 3 1 +WDM4 36 RORN ENGLZERO SAME RCHRES 1 INFLOW PKIF 3 +WDM4 37 LORN ENGLZERO SAME RCHRES 1 INFLOW PKIF 3 +WDM4 41 PO4D ENGLZERO SAME RCHRES 1 INFLOW NUIF1 4 +WDM4 42 PO4A ENGLZERO SAME RCHRES 1 INFLOW NUIF2 1 2 +WDM4 43 PO4I ENGLZERO SAME RCHRES 1 INFLOW NUIF2 2 2 +WDM4 44 PO4C ENGLZERO SAME RCHRES 1 INFLOW NUIF2 3 2 +WDM4 45 RORP ENGLZERO SAME RCHRES 1 INFLOW PKIF 4 +WDM4 47 LORP ENGLZERO SAME RCHRES 1 INFLOW PKIF 4 +WDM4 51 BODA ENGLZERO SAME RCHRES 1 INFLOW OXIF 2 +WDM4 52 TORC ENGLZERO SAME RCHRES 1 INFLOW PKIF 5 +WDM4 53 PHYT ENGLZERO SAME RCHRES 1 INFLOW PKIF 1 +END EXT SOURCES + +EXT TARGETS +<-Volume-> <-Grp> <-Member-><--Mult-->Tran <-Volume-> Tsys Tgap Amd *** + # # #<-factor->strg # # tem strg strg*** +RCHRES 1 OFLOW OVOL 3 SAME WDM4 111 WATR ENGL REPL +RCHRES 1 OFLOW OHEAT 3 SAME WDM4 112 HEAT ENGL REPL +RCHRES 1 OFLOW OXCF2 3 1 SAME WDM4 113 DOXY ENGL REPL +RCHRES 1 OFLOW OSED 3 1 SAME WDM4 121 SAND ENGL REPL +RCHRES 1 OFLOW OSED 3 2 SAME WDM4 122 SILT ENGL REPL +RCHRES 1 OFLOW OSED 3 3 SAME WDM4 123 CLAY ENGL REPL +RCHRES 1 OFLOW NUCF9 3 1 SAME WDM4 131 NO3D ENGL REPL +RCHRES 1 OFLOW NUCF9 3 2 SAME WDM4 132 NH3D ENGL REPL +RCHRES 1 OFLOW OSNH4 3 1 SAME WDM4 133 NH3A ENGL REPL +RCHRES 1 OFLOW OSNH4 3 2 SAME WDM4 134 NH3I ENGL REPL +RCHRES 1 OFLOW OSNH4 3 3 SAME WDM4 135 NH3C ENGL REPL +RCHRES 1 OFLOW PKCF2 3 3 SAME WDM4 136 RORN ENGL REPL +RCHRES 1 OFLOW PKCF2 3 3 0. SAME WDM4 137 LORN ENGL REPL +RCHRES 1 OFLOW NUCF9 3 4 SAME WDM4 141 PO4D ENGL REPL +RCHRES 1 OFLOW OSPO4 3 1 SAME WDM4 142 PO4A ENGL REPL +RCHRES 1 OFLOW OSPO4 3 2 SAME WDM4 143 PO4I ENGL REPL +RCHRES 1 OFLOW OSPO4 3 3 SAME WDM4 144 PO4C ENGL REPL +RCHRES 1 OFLOW PKCF2 3 4 SAME WDM4 145 RORP ENGL REPL +RCHRES 1 OFLOW PKCF2 3 4 0. SAME WDM4 147 LORP ENGL REPL +RCHRES 1 OFLOW OXCF2 3 2 SAME WDM4 151 BODA ENGL REPL +RCHRES 1 OFLOW PKCF2 3 5 SAME WDM4 152 TORC ENGL REPL +RCHRES 1 OFLOW PKCF2 3 1 SAME WDM4 153 PHYT ENGL REPL +RCHRES 1 SEDTRN DEPSCR 4 SAME WDM4 124 SSCR ENGL REPL +END EXT TARGETS + +NETWORK +<-Volume-> <-Grp> <-Member-><--Mult-->Tran <-Target vols> <-Grp> <-Member-> *** + # # #<-factor->strg # # # # *** +RCHRES 1 HYDR TAU AVER PLTGEN 1 INPUT MEAN 1 +END NETWORK + +PLTGEN + PLOTINFO + # - # FILE NPT NMN LABL PYR PIVL *** + 1 31 1 12 24 + END PLOTINFO + + GEN-LABELS + # - #<----------------Title-----------------> *** + 1 JL1_6562_6560 daily_shear_stress_lbsft2 + END GEN-LABELS + + SCALING + #thru# YMIN YMAX IVLIN THRESH *** + 1 99 0. 100000. 20. + END SCALING + + CURV-DATA + <-Curve label--> Line Intg Col Tran *** + # - # type eqv code code *** + 1 daily_shear_stre 1 1 AVER + END CURV-DATA +END PLTGEN + + + +SPEC-ACTIONS +*** test special actions + RCHRES 1 RSED 4 += 2.50E+05 + RCHRES 1 RSED 5 += 6.89E+05 + RCHRES 1 RSED 6 += 4.01E+05 +END SPEC-ACTIONS + +END RUN diff --git a/tests/testcbp/HSP2results/PL3_5250_0001.json.dynamic_wd.json b/tests/testcbp/HSP2results/PL3_5250_0001.json.dynamic_wd.json new file mode 100644 index 00000000..a61c54a4 --- /dev/null +++ b/tests/testcbp/HSP2results/PL3_5250_0001.json.dynamic_wd.json @@ -0,0 +1,19 @@ +{ + "RCHRES_R001": { + "name": "RCHRES_R001", + "object_class": "ModelObject", + "value": "0", + "wd_cfs": { + "name": "wd_cfs", + "object_class": "Equation", + "value": "0.1 * O3" + }, + "O2write": { + "name": "O2write", + "object_class": "ModelLinkage", + "left_path": "/STATE/PL3_5250_0001/RCHRES_R001/O2", + "right_path": "/STATE/PL3_5250_0001/RCHRES_R001/wd_cfs", + "link_type": 5 + } + } +} diff --git a/tests/testcbp/HSP2results/PL3_5250_0001.json.manyeq b/tests/testcbp/HSP2results/PL3_5250_0001.json.manyeq new file mode 100644 index 00000000..4eb8a9a0 --- /dev/null +++ b/tests/testcbp/HSP2results/PL3_5250_0001.json.manyeq @@ -0,0 +1,2515 @@ +{ + "RCHRES_R001": { + "name": "RCHRES_R001", + "object_class": "ModelObject", + "value": "0", + "wd_1": { + "name": "wd_1", + "object_class": "Equation", + "value": "0.02 + 0.0" + }, + "wd_2": { + "name": "wd_2", + "object_class": "Equation", + "value": "0.02 + wd_1" + }, + "wd_3": { + "name": "wd_3", + "object_class": "Equation", + "value": "0.02 + wd_2" + }, + "wd_4": { + "name": "wd_4", + "object_class": "Equation", + "value": "0.02 + wd_3" + }, + "wd_5": { + "name": "wd_5", + "object_class": "Equation", + "value": "0.02 + wd_4" + }, + "wd_6": { + "name": "wd_6", + "object_class": "Equation", + "value": "0.02 + wd_5" + }, + "wd_7": { + "name": "wd_7", + "object_class": "Equation", + "value": "0.02 + wd_6" + }, + "wd_8": { + "name": "wd_8", + "object_class": "Equation", + "value": "0.02 + wd_7" + }, + "wd_9": { + "name": "wd_9", + "object_class": "Equation", + "value": "0.02 + wd_8" + }, + "wd_10": { + "name": "wd_10", + "object_class": "Equation", + "value": "0.02 + wd_9" + }, + "wd_11": { + "name": "wd_11", + "object_class": "Equation", + "value": "0.02 + wd_10" + }, + "wd_12": { + "name": "wd_12", + "object_class": "Equation", + "value": "0.02 + wd_11" + }, + "wd_13": { + "name": "wd_13", + "object_class": "Equation", + "value": "0.02 + wd_12" + }, + "wd_14": { + "name": "wd_14", + "object_class": "Equation", + "value": "0.02 + wd_13" + }, + "wd_15": { + "name": "wd_15", + "object_class": "Equation", + "value": "0.02 + wd_14" + }, + "wd_16": { + "name": "wd_16", + "object_class": "Equation", + "value": "0.02 + wd_15" + }, + "wd_17": { + "name": "wd_17", + "object_class": "Equation", + "value": "0.02 + wd_16" + }, + "wd_18": { + "name": "wd_18", + "object_class": "Equation", + "value": "0.02 + wd_17" + }, + "wd_19": { + "name": "wd_19", + "object_class": "Equation", + "value": "0.02 + wd_18" + }, + "wd_20": { + "name": "wd_20", + "object_class": "Equation", + "value": "0.02 + wd_19" + }, + "wd_21": { + "name": "wd_21", + "object_class": "Equation", + "value": "0.02 + wd_20" + }, + "wd_22": { + "name": "wd_22", + "object_class": "Equation", + "value": "0.02 + wd_21" + }, + "wd_23": { + "name": "wd_23", + "object_class": "Equation", + "value": "0.02 + wd_22" + }, + "wd_24": { + "name": "wd_24", + "object_class": "Equation", + "value": "0.02 + wd_23" + }, + "wd_25": { + "name": "wd_25", + "object_class": "Equation", + "value": "0.02 + wd_24" + }, + "wd_26": { + "name": "wd_26", + "object_class": "Equation", + "value": "0.02 + wd_25" + }, + "wd_27": { + "name": "wd_27", + "object_class": "Equation", + "value": "0.02 + wd_26" + }, + "wd_28": { + "name": "wd_28", + "object_class": "Equation", + "value": "0.02 + wd_27" + }, + "wd_29": { + "name": "wd_29", + "object_class": "Equation", + "value": "0.02 + wd_28" + }, + "wd_30": { + "name": "wd_30", + "object_class": "Equation", + "value": "0.02 + wd_29" + }, + "wd_31": { + "name": "wd_31", + "object_class": "Equation", + "value": "0.02 + wd_30" + }, + "wd_32": { + "name": "wd_32", + "object_class": "Equation", + "value": "0.02 + wd_31" + }, + "wd_33": { + "name": "wd_33", + "object_class": "Equation", + "value": "0.02 + wd_32" + }, + "wd_34": { + "name": "wd_34", + "object_class": "Equation", + "value": "0.02 + wd_33" + }, + "wd_35": { + "name": "wd_35", + "object_class": "Equation", + "value": "0.02 + wd_34" + }, + "wd_36": { + "name": "wd_36", + "object_class": "Equation", + "value": "0.02 + wd_35" + }, + "wd_37": { + "name": "wd_37", + "object_class": "Equation", + "value": "0.02 + wd_36" + }, + "wd_38": { + "name": "wd_38", + "object_class": "Equation", + "value": "0.02 + wd_37" + }, + "wd_39": { + "name": "wd_39", + "object_class": "Equation", + "value": "0.02 + wd_38" + }, + "wd_40": { + "name": "wd_40", + "object_class": "Equation", + "value": "0.02 + wd_39" + }, + "wd_41": { + "name": "wd_41", + "object_class": "Equation", + "value": "0.02 + wd_40" + }, + "wd_42": { + "name": "wd_42", + "object_class": "Equation", + "value": "0.02 + wd_41" + }, + "wd_43": { + "name": "wd_43", + "object_class": "Equation", + "value": "0.02 + wd_42" + }, + "wd_44": { + "name": "wd_44", + "object_class": "Equation", + "value": "0.02 + wd_43" + }, + "wd_45": { + "name": "wd_45", + "object_class": "Equation", + "value": "0.02 + wd_44" + }, + "wd_46": { + "name": "wd_46", + "object_class": "Equation", + "value": "0.02 + wd_45" + }, + "wd_47": { + "name": "wd_47", + "object_class": "Equation", + "value": "0.02 + wd_46" + }, + "wd_48": { + "name": "wd_48", + "object_class": "Equation", + "value": "0.02 + wd_47" + }, + "wd_49": { + "name": "wd_49", + "object_class": "Equation", + "value": "0.02 + wd_48" + }, + "wd_50": { + "name": "wd_50", + "object_class": "Equation", + "value": "0.02 + wd_49" + }, + "wd_51": { + "name": "wd_51", + "object_class": "Equation", + "value": "0.02 + wd_50" + }, + "wd_52": { + "name": "wd_52", + "object_class": "Equation", + "value": "0.02 + wd_51" + }, + "wd_53": { + "name": "wd_53", + "object_class": "Equation", + "value": "0.02 + wd_52" + }, + "wd_54": { + "name": "wd_54", + "object_class": "Equation", + "value": "0.02 + wd_53" + }, + "wd_55": { + "name": "wd_55", + "object_class": "Equation", + "value": "0.02 + wd_54" + }, + "wd_56": { + "name": "wd_56", + "object_class": "Equation", + "value": "0.02 + wd_55" + }, + "wd_57": { + "name": "wd_57", + "object_class": "Equation", + "value": "0.02 + wd_56" + }, + "wd_58": { + "name": "wd_58", + "object_class": "Equation", + "value": "0.02 + wd_57" + }, + "wd_59": { + "name": "wd_59", + "object_class": "Equation", + "value": "0.02 + wd_58" + }, + "wd_60": { + "name": "wd_60", + "object_class": "Equation", + "value": "0.02 + wd_59" + }, + "wd_61": { + "name": "wd_61", + "object_class": "Equation", + "value": "0.02 + wd_60" + }, + "wd_62": { + "name": "wd_62", + "object_class": "Equation", + "value": "0.02 + wd_61" + }, + "wd_63": { + "name": "wd_63", + "object_class": "Equation", + "value": "0.02 + wd_62" + }, + "wd_64": { + "name": "wd_64", + "object_class": "Equation", + "value": "0.02 + wd_63" + }, + "wd_65": { + "name": "wd_65", + "object_class": "Equation", + "value": "0.02 + wd_64" + }, + "wd_66": { + "name": "wd_66", + "object_class": "Equation", + "value": "0.02 + wd_65" + }, + "wd_67": { + "name": "wd_67", + "object_class": "Equation", + "value": "0.02 + wd_66" + }, + "wd_68": { + "name": "wd_68", + "object_class": "Equation", + "value": "0.02 + wd_67" + }, + "wd_69": { + "name": "wd_69", + "object_class": "Equation", + "value": "0.02 + wd_68" + }, + "wd_70": { + "name": "wd_70", + "object_class": "Equation", + "value": "0.02 + wd_69" + }, + "wd_71": { + "name": "wd_71", + "object_class": "Equation", + "value": "0.02 + wd_70" + }, + "wd_72": { + "name": "wd_72", + "object_class": "Equation", + "value": "0.02 + wd_71" + }, + "wd_73": { + "name": "wd_73", + "object_class": "Equation", + "value": "0.02 + wd_72" + }, + "wd_74": { + "name": "wd_74", + "object_class": "Equation", + "value": "0.02 + wd_73" + }, + "wd_75": { + "name": "wd_75", + "object_class": "Equation", + "value": "0.02 + wd_74" + }, + "wd_76": { + "name": "wd_76", + "object_class": "Equation", + "value": "0.02 + wd_75" + }, + "wd_77": { + "name": "wd_77", + "object_class": "Equation", + "value": "0.02 + wd_76" + }, + "wd_78": { + "name": "wd_78", + "object_class": "Equation", + "value": "0.02 + wd_77" + }, + "wd_79": { + "name": "wd_79", + "object_class": "Equation", + "value": "0.02 + wd_78" + }, + "wd_80": { + "name": "wd_80", + "object_class": "Equation", + "value": "0.02 + wd_79" + }, + "wd_81": { + "name": "wd_81", + "object_class": "Equation", + "value": "0.02 + wd_80" + }, + "wd_82": { + "name": "wd_82", + "object_class": "Equation", + "value": "0.02 + wd_81" + }, + "wd_83": { + "name": "wd_83", + "object_class": "Equation", + "value": "0.02 + wd_82" + }, + "wd_84": { + "name": "wd_84", + "object_class": "Equation", + "value": "0.02 + wd_83" + }, + "wd_85": { + "name": "wd_85", + "object_class": "Equation", + "value": "0.02 + wd_84" + }, + "wd_86": { + "name": "wd_86", + "object_class": "Equation", + "value": "0.02 + wd_85" + }, + "wd_87": { + "name": "wd_87", + "object_class": "Equation", + "value": "0.02 + wd_86" + }, + "wd_88": { + "name": "wd_88", + "object_class": "Equation", + "value": "0.02 + wd_87" + }, + "wd_89": { + "name": "wd_89", + "object_class": "Equation", + "value": "0.02 + wd_88" + }, + "wd_90": { + "name": "wd_90", + "object_class": "Equation", + "value": "0.02 + wd_89" + }, + "wd_91": { + "name": "wd_91", + "object_class": "Equation", + "value": "0.02 + wd_90" + }, + "wd_92": { + "name": "wd_92", + "object_class": "Equation", + "value": "0.02 + wd_91" + }, + "wd_93": { + "name": "wd_93", + "object_class": "Equation", + "value": "0.02 + wd_92" + }, + "wd_94": { + "name": "wd_94", + "object_class": "Equation", + "value": "0.02 + wd_93" + }, + "wd_95": { + "name": "wd_95", + "object_class": "Equation", + "value": "0.02 + wd_94" + }, + "wd_96": { + "name": "wd_96", + "object_class": "Equation", + "value": "0.02 + wd_95" + }, + "wd_97": { + "name": "wd_97", + "object_class": "Equation", + "value": "0.02 + wd_96" + }, + "wd_98": { + "name": "wd_98", + "object_class": "Equation", + "value": "0.02 + wd_97" + }, + "wd_99": { + "name": "wd_99", + "object_class": "Equation", + "value": "0.02 + wd_98" + }, + "wd_100": { + "name": "wd_100", + "object_class": "Equation", + "value": "0.02 + wd_99" + }, + "wd_101": { + "name": "wd_101", + "object_class": "Equation", + "value": "0.02 + wd_100" + }, + "wd_102": { + "name": "wd_102", + "object_class": "Equation", + "value": "0.02 + wd_101" + }, + "wd_103": { + "name": "wd_103", + "object_class": "Equation", + "value": "0.02 + wd_102" + }, + "wd_104": { + "name": "wd_104", + "object_class": "Equation", + "value": "0.02 + wd_103" + }, + "wd_105": { + "name": "wd_105", + "object_class": "Equation", + "value": "0.02 + wd_104" + }, + "wd_106": { + "name": "wd_106", + "object_class": "Equation", + "value": "0.02 + wd_105" + }, + "wd_107": { + "name": "wd_107", + "object_class": "Equation", + "value": "0.02 + wd_106" + }, + "wd_108": { + "name": "wd_108", + "object_class": "Equation", + "value": "0.02 + wd_107" + }, + "wd_109": { + "name": "wd_109", + "object_class": "Equation", + "value": "0.02 + wd_108" + }, + "wd_110": { + "name": "wd_110", + "object_class": "Equation", + "value": "0.02 + wd_109" + }, + "wd_111": { + "name": "wd_111", + "object_class": "Equation", + "value": "0.02 + wd_110" + }, + "wd_112": { + "name": "wd_112", + "object_class": "Equation", + "value": "0.02 + wd_111" + }, + "wd_113": { + "name": "wd_113", + "object_class": "Equation", + "value": "0.02 + wd_112" + }, + "wd_114": { + "name": "wd_114", + "object_class": "Equation", + "value": "0.02 + wd_113" + }, + "wd_115": { + "name": "wd_115", + "object_class": "Equation", + "value": "0.02 + wd_114" + }, + "wd_116": { + "name": "wd_116", + "object_class": "Equation", + "value": "0.02 + wd_115" + }, + "wd_117": { + "name": "wd_117", + "object_class": "Equation", + "value": "0.02 + wd_116" + }, + "wd_118": { + "name": "wd_118", + "object_class": "Equation", + "value": "0.02 + wd_117" + }, + "wd_119": { + "name": "wd_119", + "object_class": "Equation", + "value": "0.02 + wd_118" + }, + "wd_120": { + "name": "wd_120", + "object_class": "Equation", + "value": "0.02 + wd_119" + }, + "wd_121": { + "name": "wd_121", + "object_class": "Equation", + "value": "0.02 + wd_120" + }, + "wd_122": { + "name": "wd_122", + "object_class": "Equation", + "value": "0.02 + wd_121" + }, + "wd_123": { + "name": "wd_123", + "object_class": "Equation", + "value": "0.02 + wd_122" + }, + "wd_124": { + "name": "wd_124", + "object_class": "Equation", + "value": "0.02 + wd_123" + }, + "wd_125": { + "name": "wd_125", + "object_class": "Equation", + "value": "0.02 + wd_124" + }, + "wd_126": { + "name": "wd_126", + "object_class": "Equation", + "value": "0.02 + wd_125" + }, + "wd_127": { + "name": "wd_127", + "object_class": "Equation", + "value": "0.02 + wd_126" + }, + "wd_128": { + "name": "wd_128", + "object_class": "Equation", + "value": "0.02 + wd_127" + }, + "wd_129": { + "name": "wd_129", + "object_class": "Equation", + "value": "0.02 + wd_128" + }, + "wd_130": { + "name": "wd_130", + "object_class": "Equation", + "value": "0.02 + wd_129" + }, + "wd_131": { + "name": "wd_131", + "object_class": "Equation", + "value": "0.02 + wd_130" + }, + "wd_132": { + "name": "wd_132", + "object_class": "Equation", + "value": "0.02 + wd_131" + }, + "wd_133": { + "name": "wd_133", + "object_class": "Equation", + "value": "0.02 + wd_132" + }, + "wd_134": { + "name": "wd_134", + "object_class": "Equation", + "value": "0.02 + wd_133" + }, + "wd_135": { + "name": "wd_135", + "object_class": "Equation", + "value": "0.02 + wd_134" + }, + "wd_136": { + "name": "wd_136", + "object_class": "Equation", + "value": "0.02 + wd_135" + }, + "wd_137": { + "name": "wd_137", + "object_class": "Equation", + "value": "0.02 + wd_136" + }, + "wd_138": { + "name": "wd_138", + "object_class": "Equation", + "value": "0.02 + wd_137" + }, + "wd_139": { + "name": "wd_139", + "object_class": "Equation", + "value": "0.02 + wd_138" + }, + "wd_140": { + "name": "wd_140", + "object_class": "Equation", + "value": "0.02 + wd_139" + }, + "wd_141": { + "name": "wd_141", + "object_class": "Equation", + "value": "0.02 + wd_140" + }, + "wd_142": { + "name": "wd_142", + "object_class": "Equation", + "value": "0.02 + wd_141" + }, + "wd_143": { + "name": "wd_143", + "object_class": "Equation", + "value": "0.02 + wd_142" + }, + "wd_144": { + "name": "wd_144", + "object_class": "Equation", + "value": "0.02 + wd_143" + }, + "wd_145": { + "name": "wd_145", + "object_class": "Equation", + "value": "0.02 + wd_144" + }, + "wd_146": { + "name": "wd_146", + "object_class": "Equation", + "value": "0.02 + wd_145" + }, + "wd_147": { + "name": "wd_147", + "object_class": "Equation", + "value": "0.02 + wd_146" + }, + "wd_148": { + "name": "wd_148", + "object_class": "Equation", + "value": "0.02 + wd_147" + }, + "wd_149": { + "name": "wd_149", + "object_class": "Equation", + "value": "0.02 + wd_148" + }, + "wd_150": { + "name": "wd_150", + "object_class": "Equation", + "value": "0.02 + wd_149" + }, + "wd_151": { + "name": "wd_151", + "object_class": "Equation", + "value": "0.02 + wd_150" + }, + "wd_152": { + "name": "wd_152", + "object_class": "Equation", + "value": "0.02 + wd_151" + }, + "wd_153": { + "name": "wd_153", + "object_class": "Equation", + "value": "0.02 + wd_152" + }, + "wd_154": { + "name": "wd_154", + "object_class": "Equation", + "value": "0.02 + wd_153" + }, + "wd_155": { + "name": "wd_155", + "object_class": "Equation", + "value": "0.02 + wd_154" + }, + "wd_156": { + "name": "wd_156", + "object_class": "Equation", + "value": "0.02 + wd_155" + }, + "wd_157": { + "name": "wd_157", + "object_class": "Equation", + "value": "0.02 + wd_156" + }, + "wd_158": { + "name": "wd_158", + "object_class": "Equation", + "value": "0.02 + wd_157" + }, + "wd_159": { + "name": "wd_159", + "object_class": "Equation", + "value": "0.02 + wd_158" + }, + "wd_160": { + "name": "wd_160", + "object_class": "Equation", + "value": "0.02 + wd_159" + }, + "wd_161": { + "name": "wd_161", + "object_class": "Equation", + "value": "0.02 + wd_160" + }, + "wd_162": { + "name": "wd_162", + "object_class": "Equation", + "value": "0.02 + wd_161" + }, + "wd_163": { + "name": "wd_163", + "object_class": "Equation", + "value": "0.02 + wd_162" + }, + "wd_164": { + "name": "wd_164", + "object_class": "Equation", + "value": "0.02 + wd_163" + }, + "wd_165": { + "name": "wd_165", + "object_class": "Equation", + "value": "0.02 + wd_164" + }, + "wd_166": { + "name": "wd_166", + "object_class": "Equation", + "value": "0.02 + wd_165" + }, + "wd_167": { + "name": "wd_167", + "object_class": "Equation", + "value": "0.02 + wd_166" + }, + "wd_168": { + "name": "wd_168", + "object_class": "Equation", + "value": "0.02 + wd_167" + }, + "wd_169": { + "name": "wd_169", + "object_class": "Equation", + "value": "0.02 + wd_168" + }, + "wd_170": { + "name": "wd_170", + "object_class": "Equation", + "value": "0.02 + wd_169" + }, + "wd_171": { + "name": "wd_171", + "object_class": "Equation", + "value": "0.02 + wd_170" + }, + "wd_172": { + "name": "wd_172", + "object_class": "Equation", + "value": "0.02 + wd_171" + }, + "wd_173": { + "name": "wd_173", + "object_class": "Equation", + "value": "0.02 + wd_172" + }, + "wd_174": { + "name": "wd_174", + "object_class": "Equation", + "value": "0.02 + wd_173" + }, + "wd_175": { + "name": "wd_175", + "object_class": "Equation", + "value": "0.02 + wd_174" + }, + "wd_176": { + "name": "wd_176", + "object_class": "Equation", + "value": "0.02 + wd_175" + }, + "wd_177": { + "name": "wd_177", + "object_class": "Equation", + "value": "0.02 + wd_176" + }, + "wd_178": { + "name": "wd_178", + "object_class": "Equation", + "value": "0.02 + wd_177" + }, + "wd_179": { + "name": "wd_179", + "object_class": "Equation", + "value": "0.02 + wd_178" + }, + "wd_180": { + "name": "wd_180", + "object_class": "Equation", + "value": "0.02 + wd_179" + }, + "wd_181": { + "name": "wd_181", + "object_class": "Equation", + "value": "0.02 + wd_180" + }, + "wd_182": { + "name": "wd_182", + "object_class": "Equation", + "value": "0.02 + wd_181" + }, + "wd_183": { + "name": "wd_183", + "object_class": "Equation", + "value": "0.02 + wd_182" + }, + "wd_184": { + "name": "wd_184", + "object_class": "Equation", + "value": "0.02 + wd_183" + }, + "wd_185": { + "name": "wd_185", + "object_class": "Equation", + "value": "0.02 + wd_184" + }, + "wd_186": { + "name": "wd_186", + "object_class": "Equation", + "value": "0.02 + wd_185" + }, + "wd_187": { + "name": "wd_187", + "object_class": "Equation", + "value": "0.02 + wd_186" + }, + "wd_188": { + "name": "wd_188", + "object_class": "Equation", + "value": "0.02 + wd_187" + }, + "wd_189": { + "name": "wd_189", + "object_class": "Equation", + "value": "0.02 + wd_188" + }, + "wd_190": { + "name": "wd_190", + "object_class": "Equation", + "value": "0.02 + wd_189" + }, + "wd_191": { + "name": "wd_191", + "object_class": "Equation", + "value": "0.02 + wd_190" + }, + "wd_192": { + "name": "wd_192", + "object_class": "Equation", + "value": "0.02 + wd_191" + }, + "wd_193": { + "name": "wd_193", + "object_class": "Equation", + "value": "0.02 + wd_192" + }, + "wd_194": { + "name": "wd_194", + "object_class": "Equation", + "value": "0.02 + wd_193" + }, + "wd_195": { + "name": "wd_195", + "object_class": "Equation", + "value": "0.02 + wd_194" + }, + "wd_196": { + "name": "wd_196", + "object_class": "Equation", + "value": "0.02 + wd_195" + }, + "wd_197": { + "name": "wd_197", + "object_class": "Equation", + "value": "0.02 + wd_196" + }, + "wd_198": { + "name": "wd_198", + "object_class": "Equation", + "value": "0.02 + wd_197" + }, + "wd_199": { + "name": "wd_199", + "object_class": "Equation", + "value": "0.02 + wd_198" + }, + "wd_200": { + "name": "wd_200", + "object_class": "Equation", + "value": "0.02 + wd_199" + }, + "wd_201": { + "name": "wd_201", + "object_class": "Equation", + "value": "0.02 + wd_200" + }, + "wd_202": { + "name": "wd_202", + "object_class": "Equation", + "value": "0.02 + wd_201" + }, + "wd_203": { + "name": "wd_203", + "object_class": "Equation", + "value": "0.02 + wd_202" + }, + "wd_204": { + "name": "wd_204", + "object_class": "Equation", + "value": "0.02 + wd_203" + }, + "wd_205": { + "name": "wd_205", + "object_class": "Equation", + "value": "0.02 + wd_204" + }, + "wd_206": { + "name": "wd_206", + "object_class": "Equation", + "value": "0.02 + wd_205" + }, + "wd_207": { + "name": "wd_207", + "object_class": "Equation", + "value": "0.02 + wd_206" + }, + "wd_208": { + "name": "wd_208", + "object_class": "Equation", + "value": "0.02 + wd_207" + }, + "wd_209": { + "name": "wd_209", + "object_class": "Equation", + "value": "0.02 + wd_208" + }, + "wd_210": { + "name": "wd_210", + "object_class": "Equation", + "value": "0.02 + wd_209" + }, + "wd_211": { + "name": "wd_211", + "object_class": "Equation", + "value": "0.02 + wd_210" + }, + "wd_212": { + "name": "wd_212", + "object_class": "Equation", + "value": "0.02 + wd_211" + }, + "wd_213": { + "name": "wd_213", + "object_class": "Equation", + "value": "0.02 + wd_212" + }, + "wd_214": { + "name": "wd_214", + "object_class": "Equation", + "value": "0.02 + wd_213" + }, + "wd_215": { + "name": "wd_215", + "object_class": "Equation", + "value": "0.02 + wd_214" + }, + "wd_216": { + "name": "wd_216", + "object_class": "Equation", + "value": "0.02 + wd_215" + }, + "wd_217": { + "name": "wd_217", + "object_class": "Equation", + "value": "0.02 + wd_216" + }, + "wd_218": { + "name": "wd_218", + "object_class": "Equation", + "value": "0.02 + wd_217" + }, + "wd_219": { + "name": "wd_219", + "object_class": "Equation", + "value": "0.02 + wd_218" + }, + "wd_220": { + "name": "wd_220", + "object_class": "Equation", + "value": "0.02 + wd_219" + }, + "wd_221": { + "name": "wd_221", + "object_class": "Equation", + "value": "0.02 + wd_220" + }, + "wd_222": { + "name": "wd_222", + "object_class": "Equation", + "value": "0.02 + wd_221" + }, + "wd_223": { + "name": "wd_223", + "object_class": "Equation", + "value": "0.02 + wd_222" + }, + "wd_224": { + "name": "wd_224", + "object_class": "Equation", + "value": "0.02 + wd_223" + }, + "wd_225": { + "name": "wd_225", + "object_class": "Equation", + "value": "0.02 + wd_224" + }, + "wd_226": { + "name": "wd_226", + "object_class": "Equation", + "value": "0.02 + wd_225" + }, + "wd_227": { + "name": "wd_227", + "object_class": "Equation", + "value": "0.02 + wd_226" + }, + "wd_228": { + "name": "wd_228", + "object_class": "Equation", + "value": "0.02 + wd_227" + }, + "wd_229": { + "name": "wd_229", + "object_class": "Equation", + "value": "0.02 + wd_228" + }, + "wd_230": { + "name": "wd_230", + "object_class": "Equation", + "value": "0.02 + wd_229" + }, + "wd_231": { + "name": "wd_231", + "object_class": "Equation", + "value": "0.02 + wd_230" + }, + "wd_232": { + "name": "wd_232", + "object_class": "Equation", + "value": "0.02 + wd_231" + }, + "wd_233": { + "name": "wd_233", + "object_class": "Equation", + "value": "0.02 + wd_232" + }, + "wd_234": { + "name": "wd_234", + "object_class": "Equation", + "value": "0.02 + wd_233" + }, + "wd_235": { + "name": "wd_235", + "object_class": "Equation", + "value": "0.02 + wd_234" + }, + "wd_236": { + "name": "wd_236", + "object_class": "Equation", + "value": "0.02 + wd_235" + }, + "wd_237": { + "name": "wd_237", + "object_class": "Equation", + "value": "0.02 + wd_236" + }, + "wd_238": { + "name": "wd_238", + "object_class": "Equation", + "value": "0.02 + wd_237" + }, + "wd_239": { + "name": "wd_239", + "object_class": "Equation", + "value": "0.02 + wd_238" + }, + "wd_240": { + "name": "wd_240", + "object_class": "Equation", + "value": "0.02 + wd_239" + }, + "wd_241": { + "name": "wd_241", + "object_class": "Equation", + "value": "0.02 + wd_240" + }, + "wd_242": { + "name": "wd_242", + "object_class": "Equation", + "value": "0.02 + wd_241" + }, + "wd_243": { + "name": "wd_243", + "object_class": "Equation", + "value": "0.02 + wd_242" + }, + "wd_244": { + "name": "wd_244", + "object_class": "Equation", + "value": "0.02 + wd_243" + }, + "wd_245": { + "name": "wd_245", + "object_class": "Equation", + "value": "0.02 + wd_244" + }, + "wd_246": { + "name": "wd_246", + "object_class": "Equation", + "value": "0.02 + wd_245" + }, + "wd_247": { + "name": "wd_247", + "object_class": "Equation", + "value": "0.02 + wd_246" + }, + "wd_248": { + "name": "wd_248", + "object_class": "Equation", + "value": "0.02 + wd_247" + }, + "wd_249": { + "name": "wd_249", + "object_class": "Equation", + "value": "0.02 + wd_248" + }, + "wd_250": { + "name": "wd_250", + "object_class": "Equation", + "value": "0.02 + wd_249" + }, + "wd_251": { + "name": "wd_251", + "object_class": "Equation", + "value": "0.02 + wd_250" + }, + "wd_252": { + "name": "wd_252", + "object_class": "Equation", + "value": "0.02 + wd_251" + }, + "wd_253": { + "name": "wd_253", + "object_class": "Equation", + "value": "0.02 + wd_252" + }, + "wd_254": { + "name": "wd_254", + "object_class": "Equation", + "value": "0.02 + wd_253" + }, + "wd_255": { + "name": "wd_255", + "object_class": "Equation", + "value": "0.02 + wd_254" + }, + "wd_256": { + "name": "wd_256", + "object_class": "Equation", + "value": "0.02 + wd_255" + }, + "wd_257": { + "name": "wd_257", + "object_class": "Equation", + "value": "0.02 + wd_256" + }, + "wd_258": { + "name": "wd_258", + "object_class": "Equation", + "value": "0.02 + wd_257" + }, + "wd_259": { + "name": "wd_259", + "object_class": "Equation", + "value": "0.02 + wd_258" + }, + "wd_260": { + "name": "wd_260", + "object_class": "Equation", + "value": "0.02 + wd_259" + }, + "wd_261": { + "name": "wd_261", + "object_class": "Equation", + "value": "0.02 + wd_260" + }, + "wd_262": { + "name": "wd_262", + "object_class": "Equation", + "value": "0.02 + wd_261" + }, + "wd_263": { + "name": "wd_263", + "object_class": "Equation", + "value": "0.02 + wd_262" + }, + "wd_264": { + "name": "wd_264", + "object_class": "Equation", + "value": "0.02 + wd_263" + }, + "wd_265": { + "name": "wd_265", + "object_class": "Equation", + "value": "0.02 + wd_264" + }, + "wd_266": { + "name": "wd_266", + "object_class": "Equation", + "value": "0.02 + wd_265" + }, + "wd_267": { + "name": "wd_267", + "object_class": "Equation", + "value": "0.02 + wd_266" + }, + "wd_268": { + "name": "wd_268", + "object_class": "Equation", + "value": "0.02 + wd_267" + }, + "wd_269": { + "name": "wd_269", + "object_class": "Equation", + "value": "0.02 + wd_268" + }, + "wd_270": { + "name": "wd_270", + "object_class": "Equation", + "value": "0.02 + wd_269" + }, + "wd_271": { + "name": "wd_271", + "object_class": "Equation", + "value": "0.02 + wd_270" + }, + "wd_272": { + "name": "wd_272", + "object_class": "Equation", + "value": "0.02 + wd_271" + }, + "wd_273": { + "name": "wd_273", + "object_class": "Equation", + "value": "0.02 + wd_272" + }, + "wd_274": { + "name": "wd_274", + "object_class": "Equation", + "value": "0.02 + wd_273" + }, + "wd_275": { + "name": "wd_275", + "object_class": "Equation", + "value": "0.02 + wd_274" + }, + "wd_276": { + "name": "wd_276", + "object_class": "Equation", + "value": "0.02 + wd_275" + }, + "wd_277": { + "name": "wd_277", + "object_class": "Equation", + "value": "0.02 + wd_276" + }, + "wd_278": { + "name": "wd_278", + "object_class": "Equation", + "value": "0.02 + wd_277" + }, + "wd_279": { + "name": "wd_279", + "object_class": "Equation", + "value": "0.02 + wd_278" + }, + "wd_280": { + "name": "wd_280", + "object_class": "Equation", + "value": "0.02 + wd_279" + }, + "wd_281": { + "name": "wd_281", + "object_class": "Equation", + "value": "0.02 + wd_280" + }, + "wd_282": { + "name": "wd_282", + "object_class": "Equation", + "value": "0.02 + wd_281" + }, + "wd_283": { + "name": "wd_283", + "object_class": "Equation", + "value": "0.02 + wd_282" + }, + "wd_284": { + "name": "wd_284", + "object_class": "Equation", + "value": "0.02 + wd_283" + }, + "wd_285": { + "name": "wd_285", + "object_class": "Equation", + "value": "0.02 + wd_284" + }, + "wd_286": { + "name": "wd_286", + "object_class": "Equation", + "value": "0.02 + wd_285" + }, + "wd_287": { + "name": "wd_287", + "object_class": "Equation", + "value": "0.02 + wd_286" + }, + "wd_288": { + "name": "wd_288", + "object_class": "Equation", + "value": "0.02 + wd_287" + }, + "wd_289": { + "name": "wd_289", + "object_class": "Equation", + "value": "0.02 + wd_288" + }, + "wd_290": { + "name": "wd_290", + "object_class": "Equation", + "value": "0.02 + wd_289" + }, + "wd_291": { + "name": "wd_291", + "object_class": "Equation", + "value": "0.02 + wd_290" + }, + "wd_292": { + "name": "wd_292", + "object_class": "Equation", + "value": "0.02 + wd_291" + }, + "wd_293": { + "name": "wd_293", + "object_class": "Equation", + "value": "0.02 + wd_292" + }, + "wd_294": { + "name": "wd_294", + "object_class": "Equation", + "value": "0.02 + wd_293" + }, + "wd_295": { + "name": "wd_295", + "object_class": "Equation", + "value": "0.02 + wd_294" + }, + "wd_296": { + "name": "wd_296", + "object_class": "Equation", + "value": "0.02 + wd_295" + }, + "wd_297": { + "name": "wd_297", + "object_class": "Equation", + "value": "0.02 + wd_296" + }, + "wd_298": { + "name": "wd_298", + "object_class": "Equation", + "value": "0.02 + wd_297" + }, + "wd_299": { + "name": "wd_299", + "object_class": "Equation", + "value": "0.02 + wd_298" + }, + "wd_300": { + "name": "wd_300", + "object_class": "Equation", + "value": "0.02 + wd_299" + }, + "wd_301": { + "name": "wd_301", + "object_class": "Equation", + "value": "0.02 + wd_300" + }, + "wd_302": { + "name": "wd_302", + "object_class": "Equation", + "value": "0.02 + wd_301" + }, + "wd_303": { + "name": "wd_303", + "object_class": "Equation", + "value": "0.02 + wd_302" + }, + "wd_304": { + "name": "wd_304", + "object_class": "Equation", + "value": "0.02 + wd_303" + }, + "wd_305": { + "name": "wd_305", + "object_class": "Equation", + "value": "0.02 + wd_304" + }, + "wd_306": { + "name": "wd_306", + "object_class": "Equation", + "value": "0.02 + wd_305" + }, + "wd_307": { + "name": "wd_307", + "object_class": "Equation", + "value": "0.02 + wd_306" + }, + "wd_308": { + "name": "wd_308", + "object_class": "Equation", + "value": "0.02 + wd_307" + }, + "wd_309": { + "name": "wd_309", + "object_class": "Equation", + "value": "0.02 + wd_308" + }, + "wd_310": { + "name": "wd_310", + "object_class": "Equation", + "value": "0.02 + wd_309" + }, + "wd_311": { + "name": "wd_311", + "object_class": "Equation", + "value": "0.02 + wd_310" + }, + "wd_312": { + "name": "wd_312", + "object_class": "Equation", + "value": "0.02 + wd_311" + }, + "wd_313": { + "name": "wd_313", + "object_class": "Equation", + "value": "0.02 + wd_312" + }, + "wd_314": { + "name": "wd_314", + "object_class": "Equation", + "value": "0.02 + wd_313" + }, + "wd_315": { + "name": "wd_315", + "object_class": "Equation", + "value": "0.02 + wd_314" + }, + "wd_316": { + "name": "wd_316", + "object_class": "Equation", + "value": "0.02 + wd_315" + }, + "wd_317": { + "name": "wd_317", + "object_class": "Equation", + "value": "0.02 + wd_316" + }, + "wd_318": { + "name": "wd_318", + "object_class": "Equation", + "value": "0.02 + wd_317" + }, + "wd_319": { + "name": "wd_319", + "object_class": "Equation", + "value": "0.02 + wd_318" + }, + "wd_320": { + "name": "wd_320", + "object_class": "Equation", + "value": "0.02 + wd_319" + }, + "wd_321": { + "name": "wd_321", + "object_class": "Equation", + "value": "0.02 + wd_320" + }, + "wd_322": { + "name": "wd_322", + "object_class": "Equation", + "value": "0.02 + wd_321" + }, + "wd_323": { + "name": "wd_323", + "object_class": "Equation", + "value": "0.02 + wd_322" + }, + "wd_324": { + "name": "wd_324", + "object_class": "Equation", + "value": "0.02 + wd_323" + }, + "wd_325": { + "name": "wd_325", + "object_class": "Equation", + "value": "0.02 + wd_324" + }, + "wd_326": { + "name": "wd_326", + "object_class": "Equation", + "value": "0.02 + wd_325" + }, + "wd_327": { + "name": "wd_327", + "object_class": "Equation", + "value": "0.02 + wd_326" + }, + "wd_328": { + "name": "wd_328", + "object_class": "Equation", + "value": "0.02 + wd_327" + }, + "wd_329": { + "name": "wd_329", + "object_class": "Equation", + "value": "0.02 + wd_328" + }, + "wd_330": { + "name": "wd_330", + "object_class": "Equation", + "value": "0.02 + wd_329" + }, + "wd_331": { + "name": "wd_331", + "object_class": "Equation", + "value": "0.02 + wd_330" + }, + "wd_332": { + "name": "wd_332", + "object_class": "Equation", + "value": "0.02 + wd_331" + }, + "wd_333": { + "name": "wd_333", + "object_class": "Equation", + "value": "0.02 + wd_332" + }, + "wd_334": { + "name": "wd_334", + "object_class": "Equation", + "value": "0.02 + wd_333" + }, + "wd_335": { + "name": "wd_335", + "object_class": "Equation", + "value": "0.02 + wd_334" + }, + "wd_336": { + "name": "wd_336", + "object_class": "Equation", + "value": "0.02 + wd_335" + }, + "wd_337": { + "name": "wd_337", + "object_class": "Equation", + "value": "0.02 + wd_336" + }, + "wd_338": { + "name": "wd_338", + "object_class": "Equation", + "value": "0.02 + wd_337" + }, + "wd_339": { + "name": "wd_339", + "object_class": "Equation", + "value": "0.02 + wd_338" + }, + "wd_340": { + "name": "wd_340", + "object_class": "Equation", + "value": "0.02 + wd_339" + }, + "wd_341": { + "name": "wd_341", + "object_class": "Equation", + "value": "0.02 + wd_340" + }, + "wd_342": { + "name": "wd_342", + "object_class": "Equation", + "value": "0.02 + wd_341" + }, + "wd_343": { + "name": "wd_343", + "object_class": "Equation", + "value": "0.02 + wd_342" + }, + "wd_344": { + "name": "wd_344", + "object_class": "Equation", + "value": "0.02 + wd_343" + }, + "wd_345": { + "name": "wd_345", + "object_class": "Equation", + "value": "0.02 + wd_344" + }, + "wd_346": { + "name": "wd_346", + "object_class": "Equation", + "value": "0.02 + wd_345" + }, + "wd_347": { + "name": "wd_347", + "object_class": "Equation", + "value": "0.02 + wd_346" + }, + "wd_348": { + "name": "wd_348", + "object_class": "Equation", + "value": "0.02 + wd_347" + }, + "wd_349": { + "name": "wd_349", + "object_class": "Equation", + "value": "0.02 + wd_348" + }, + "wd_350": { + "name": "wd_350", + "object_class": "Equation", + "value": "0.02 + wd_349" + }, + "wd_351": { + "name": "wd_351", + "object_class": "Equation", + "value": "0.02 + wd_350" + }, + "wd_352": { + "name": "wd_352", + "object_class": "Equation", + "value": "0.02 + wd_351" + }, + "wd_353": { + "name": "wd_353", + "object_class": "Equation", + "value": "0.02 + wd_352" + }, + "wd_354": { + "name": "wd_354", + "object_class": "Equation", + "value": "0.02 + wd_353" + }, + "wd_355": { + "name": "wd_355", + "object_class": "Equation", + "value": "0.02 + wd_354" + }, + "wd_356": { + "name": "wd_356", + "object_class": "Equation", + "value": "0.02 + wd_355" + }, + "wd_357": { + "name": "wd_357", + "object_class": "Equation", + "value": "0.02 + wd_356" + }, + "wd_358": { + "name": "wd_358", + "object_class": "Equation", + "value": "0.02 + wd_357" + }, + "wd_359": { + "name": "wd_359", + "object_class": "Equation", + "value": "0.02 + wd_358" + }, + "wd_360": { + "name": "wd_360", + "object_class": "Equation", + "value": "0.02 + wd_359" + }, + "wd_361": { + "name": "wd_361", + "object_class": "Equation", + "value": "0.02 + wd_360" + }, + "wd_362": { + "name": "wd_362", + "object_class": "Equation", + "value": "0.02 + wd_361" + }, + "wd_363": { + "name": "wd_363", + "object_class": "Equation", + "value": "0.02 + wd_362" + }, + "wd_364": { + "name": "wd_364", + "object_class": "Equation", + "value": "0.02 + wd_363" + }, + "wd_365": { + "name": "wd_365", + "object_class": "Equation", + "value": "0.02 + wd_364" + }, + "wd_366": { + "name": "wd_366", + "object_class": "Equation", + "value": "0.02 + wd_365" + }, + "wd_367": { + "name": "wd_367", + "object_class": "Equation", + "value": "0.02 + wd_366" + }, + "wd_368": { + "name": "wd_368", + "object_class": "Equation", + "value": "0.02 + wd_367" + }, + "wd_369": { + "name": "wd_369", + "object_class": "Equation", + "value": "0.02 + wd_368" + }, + "wd_370": { + "name": "wd_370", + "object_class": "Equation", + "value": "0.02 + wd_369" + }, + "wd_371": { + "name": "wd_371", + "object_class": "Equation", + "value": "0.02 + wd_370" + }, + "wd_372": { + "name": "wd_372", + "object_class": "Equation", + "value": "0.02 + wd_371" + }, + "wd_373": { + "name": "wd_373", + "object_class": "Equation", + "value": "0.02 + wd_372" + }, + "wd_374": { + "name": "wd_374", + "object_class": "Equation", + "value": "0.02 + wd_373" + }, + "wd_375": { + "name": "wd_375", + "object_class": "Equation", + "value": "0.02 + wd_374" + }, + "wd_376": { + "name": "wd_376", + "object_class": "Equation", + "value": "0.02 + wd_375" + }, + "wd_377": { + "name": "wd_377", + "object_class": "Equation", + "value": "0.02 + wd_376" + }, + "wd_378": { + "name": "wd_378", + "object_class": "Equation", + "value": "0.02 + wd_377" + }, + "wd_379": { + "name": "wd_379", + "object_class": "Equation", + "value": "0.02 + wd_378" + }, + "wd_380": { + "name": "wd_380", + "object_class": "Equation", + "value": "0.02 + wd_379" + }, + "wd_381": { + "name": "wd_381", + "object_class": "Equation", + "value": "0.02 + wd_380" + }, + "wd_382": { + "name": "wd_382", + "object_class": "Equation", + "value": "0.02 + wd_381" + }, + "wd_383": { + "name": "wd_383", + "object_class": "Equation", + "value": "0.02 + wd_382" + }, + "wd_384": { + "name": "wd_384", + "object_class": "Equation", + "value": "0.02 + wd_383" + }, + "wd_385": { + "name": "wd_385", + "object_class": "Equation", + "value": "0.02 + wd_384" + }, + "wd_386": { + "name": "wd_386", + "object_class": "Equation", + "value": "0.02 + wd_385" + }, + "wd_387": { + "name": "wd_387", + "object_class": "Equation", + "value": "0.02 + wd_386" + }, + "wd_388": { + "name": "wd_388", + "object_class": "Equation", + "value": "0.02 + wd_387" + }, + "wd_389": { + "name": "wd_389", + "object_class": "Equation", + "value": "0.02 + wd_388" + }, + "wd_390": { + "name": "wd_390", + "object_class": "Equation", + "value": "0.02 + wd_389" + }, + "wd_391": { + "name": "wd_391", + "object_class": "Equation", + "value": "0.02 + wd_390" + }, + "wd_392": { + "name": "wd_392", + "object_class": "Equation", + "value": "0.02 + wd_391" + }, + "wd_393": { + "name": "wd_393", + "object_class": "Equation", + "value": "0.02 + wd_392" + }, + "wd_394": { + "name": "wd_394", + "object_class": "Equation", + "value": "0.02 + wd_393" + }, + "wd_395": { + "name": "wd_395", + "object_class": "Equation", + "value": "0.02 + wd_394" + }, + "wd_396": { + "name": "wd_396", + "object_class": "Equation", + "value": "0.02 + wd_395" + }, + "wd_397": { + "name": "wd_397", + "object_class": "Equation", + "value": "0.02 + wd_396" + }, + "wd_398": { + "name": "wd_398", + "object_class": "Equation", + "value": "0.02 + wd_397" + }, + "wd_399": { + "name": "wd_399", + "object_class": "Equation", + "value": "0.02 + wd_398" + }, + "wd_400": { + "name": "wd_400", + "object_class": "Equation", + "value": "0.02 + wd_399" + }, + "wd_401": { + "name": "wd_401", + "object_class": "Equation", + "value": "0.02 + wd_400" + }, + "wd_402": { + "name": "wd_402", + "object_class": "Equation", + "value": "0.02 + wd_401" + }, + "wd_403": { + "name": "wd_403", + "object_class": "Equation", + "value": "0.02 + wd_402" + }, + "wd_404": { + "name": "wd_404", + "object_class": "Equation", + "value": "0.02 + wd_403" + }, + "wd_405": { + "name": "wd_405", + "object_class": "Equation", + "value": "0.02 + wd_404" + }, + "wd_406": { + "name": "wd_406", + "object_class": "Equation", + "value": "0.02 + wd_405" + }, + "wd_407": { + "name": "wd_407", + "object_class": "Equation", + "value": "0.02 + wd_406" + }, + "wd_408": { + "name": "wd_408", + "object_class": "Equation", + "value": "0.02 + wd_407" + }, + "wd_409": { + "name": "wd_409", + "object_class": "Equation", + "value": "0.02 + wd_408" + }, + "wd_410": { + "name": "wd_410", + "object_class": "Equation", + "value": "0.02 + wd_409" + }, + "wd_411": { + "name": "wd_411", + "object_class": "Equation", + "value": "0.02 + wd_410" + }, + "wd_412": { + "name": "wd_412", + "object_class": "Equation", + "value": "0.02 + wd_411" + }, + "wd_413": { + "name": "wd_413", + "object_class": "Equation", + "value": "0.02 + wd_412" + }, + "wd_414": { + "name": "wd_414", + "object_class": "Equation", + "value": "0.02 + wd_413" + }, + "wd_415": { + "name": "wd_415", + "object_class": "Equation", + "value": "0.02 + wd_414" + }, + "wd_416": { + "name": "wd_416", + "object_class": "Equation", + "value": "0.02 + wd_415" + }, + "wd_417": { + "name": "wd_417", + "object_class": "Equation", + "value": "0.02 + wd_416" + }, + "wd_418": { + "name": "wd_418", + "object_class": "Equation", + "value": "0.02 + wd_417" + }, + "wd_419": { + "name": "wd_419", + "object_class": "Equation", + "value": "0.02 + wd_418" + }, + "wd_420": { + "name": "wd_420", + "object_class": "Equation", + "value": "0.02 + wd_419" + }, + "wd_421": { + "name": "wd_421", + "object_class": "Equation", + "value": "0.02 + wd_420" + }, + "wd_422": { + "name": "wd_422", + "object_class": "Equation", + "value": "0.02 + wd_421" + }, + "wd_423": { + "name": "wd_423", + "object_class": "Equation", + "value": "0.02 + wd_422" + }, + "wd_424": { + "name": "wd_424", + "object_class": "Equation", + "value": "0.02 + wd_423" + }, + "wd_425": { + "name": "wd_425", + "object_class": "Equation", + "value": "0.02 + wd_424" + }, + "wd_426": { + "name": "wd_426", + "object_class": "Equation", + "value": "0.02 + wd_425" + }, + "wd_427": { + "name": "wd_427", + "object_class": "Equation", + "value": "0.02 + wd_426" + }, + "wd_428": { + "name": "wd_428", + "object_class": "Equation", + "value": "0.02 + wd_427" + }, + "wd_429": { + "name": "wd_429", + "object_class": "Equation", + "value": "0.02 + wd_428" + }, + "wd_430": { + "name": "wd_430", + "object_class": "Equation", + "value": "0.02 + wd_429" + }, + "wd_431": { + "name": "wd_431", + "object_class": "Equation", + "value": "0.02 + wd_430" + }, + "wd_432": { + "name": "wd_432", + "object_class": "Equation", + "value": "0.02 + wd_431" + }, + "wd_433": { + "name": "wd_433", + "object_class": "Equation", + "value": "0.02 + wd_432" + }, + "wd_434": { + "name": "wd_434", + "object_class": "Equation", + "value": "0.02 + wd_433" + }, + "wd_435": { + "name": "wd_435", + "object_class": "Equation", + "value": "0.02 + wd_434" + }, + "wd_436": { + "name": "wd_436", + "object_class": "Equation", + "value": "0.02 + wd_435" + }, + "wd_437": { + "name": "wd_437", + "object_class": "Equation", + "value": "0.02 + wd_436" + }, + "wd_438": { + "name": "wd_438", + "object_class": "Equation", + "value": "0.02 + wd_437" + }, + "wd_439": { + "name": "wd_439", + "object_class": "Equation", + "value": "0.02 + wd_438" + }, + "wd_440": { + "name": "wd_440", + "object_class": "Equation", + "value": "0.02 + wd_439" + }, + "wd_441": { + "name": "wd_441", + "object_class": "Equation", + "value": "0.02 + wd_440" + }, + "wd_442": { + "name": "wd_442", + "object_class": "Equation", + "value": "0.02 + wd_441" + }, + "wd_443": { + "name": "wd_443", + "object_class": "Equation", + "value": "0.02 + wd_442" + }, + "wd_444": { + "name": "wd_444", + "object_class": "Equation", + "value": "0.02 + wd_443" + }, + "wd_445": { + "name": "wd_445", + "object_class": "Equation", + "value": "0.02 + wd_444" + }, + "wd_446": { + "name": "wd_446", + "object_class": "Equation", + "value": "0.02 + wd_445" + }, + "wd_447": { + "name": "wd_447", + "object_class": "Equation", + "value": "0.02 + wd_446" + }, + "wd_448": { + "name": "wd_448", + "object_class": "Equation", + "value": "0.02 + wd_447" + }, + "wd_449": { + "name": "wd_449", + "object_class": "Equation", + "value": "0.02 + wd_448" + }, + "wd_450": { + "name": "wd_450", + "object_class": "Equation", + "value": "0.02 + wd_449" + }, + "wd_451": { + "name": "wd_451", + "object_class": "Equation", + "value": "0.02 + wd_450" + }, + "wd_452": { + "name": "wd_452", + "object_class": "Equation", + "value": "0.02 + wd_451" + }, + "wd_453": { + "name": "wd_453", + "object_class": "Equation", + "value": "0.02 + wd_452" + }, + "wd_454": { + "name": "wd_454", + "object_class": "Equation", + "value": "0.02 + wd_453" + }, + "wd_455": { + "name": "wd_455", + "object_class": "Equation", + "value": "0.02 + wd_454" + }, + "wd_456": { + "name": "wd_456", + "object_class": "Equation", + "value": "0.02 + wd_455" + }, + "wd_457": { + "name": "wd_457", + "object_class": "Equation", + "value": "0.02 + wd_456" + }, + "wd_458": { + "name": "wd_458", + "object_class": "Equation", + "value": "0.02 + wd_457" + }, + "wd_459": { + "name": "wd_459", + "object_class": "Equation", + "value": "0.02 + wd_458" + }, + "wd_460": { + "name": "wd_460", + "object_class": "Equation", + "value": "0.02 + wd_459" + }, + "wd_461": { + "name": "wd_461", + "object_class": "Equation", + "value": "0.02 + wd_460" + }, + "wd_462": { + "name": "wd_462", + "object_class": "Equation", + "value": "0.02 + wd_461" + }, + "wd_463": { + "name": "wd_463", + "object_class": "Equation", + "value": "0.02 + wd_462" + }, + "wd_464": { + "name": "wd_464", + "object_class": "Equation", + "value": "0.02 + wd_463" + }, + "wd_465": { + "name": "wd_465", + "object_class": "Equation", + "value": "0.02 + wd_464" + }, + "wd_466": { + "name": "wd_466", + "object_class": "Equation", + "value": "0.02 + wd_465" + }, + "wd_467": { + "name": "wd_467", + "object_class": "Equation", + "value": "0.02 + wd_466" + }, + "wd_468": { + "name": "wd_468", + "object_class": "Equation", + "value": "0.02 + wd_467" + }, + "wd_469": { + "name": "wd_469", + "object_class": "Equation", + "value": "0.02 + wd_468" + }, + "wd_470": { + "name": "wd_470", + "object_class": "Equation", + "value": "0.02 + wd_469" + }, + "wd_471": { + "name": "wd_471", + "object_class": "Equation", + "value": "0.02 + wd_470" + }, + "wd_472": { + "name": "wd_472", + "object_class": "Equation", + "value": "0.02 + wd_471" + }, + "wd_473": { + "name": "wd_473", + "object_class": "Equation", + "value": "0.02 + wd_472" + }, + "wd_474": { + "name": "wd_474", + "object_class": "Equation", + "value": "0.02 + wd_473" + }, + "wd_475": { + "name": "wd_475", + "object_class": "Equation", + "value": "0.02 + wd_474" + }, + "wd_476": { + "name": "wd_476", + "object_class": "Equation", + "value": "0.02 + wd_475" + }, + "wd_477": { + "name": "wd_477", + "object_class": "Equation", + "value": "0.02 + wd_476" + }, + "wd_478": { + "name": "wd_478", + "object_class": "Equation", + "value": "0.02 + wd_477" + }, + "wd_479": { + "name": "wd_479", + "object_class": "Equation", + "value": "0.02 + wd_478" + }, + "wd_480": { + "name": "wd_480", + "object_class": "Equation", + "value": "0.02 + wd_479" + }, + "wd_481": { + "name": "wd_481", + "object_class": "Equation", + "value": "0.02 + wd_480" + }, + "wd_482": { + "name": "wd_482", + "object_class": "Equation", + "value": "0.02 + wd_481" + }, + "wd_483": { + "name": "wd_483", + "object_class": "Equation", + "value": "0.02 + wd_482" + }, + "wd_484": { + "name": "wd_484", + "object_class": "Equation", + "value": "0.02 + wd_483" + }, + "wd_485": { + "name": "wd_485", + "object_class": "Equation", + "value": "0.02 + wd_484" + }, + "wd_486": { + "name": "wd_486", + "object_class": "Equation", + "value": "0.02 + wd_485" + }, + "wd_487": { + "name": "wd_487", + "object_class": "Equation", + "value": "0.02 + wd_486" + }, + "wd_488": { + "name": "wd_488", + "object_class": "Equation", + "value": "0.02 + wd_487" + }, + "wd_489": { + "name": "wd_489", + "object_class": "Equation", + "value": "0.02 + wd_488" + }, + "wd_490": { + "name": "wd_490", + "object_class": "Equation", + "value": "0.02 + wd_489" + }, + "wd_491": { + "name": "wd_491", + "object_class": "Equation", + "value": "0.02 + wd_490" + }, + "wd_492": { + "name": "wd_492", + "object_class": "Equation", + "value": "0.02 + wd_491" + }, + "wd_493": { + "name": "wd_493", + "object_class": "Equation", + "value": "0.02 + wd_492" + }, + "wd_494": { + "name": "wd_494", + "object_class": "Equation", + "value": "0.02 + wd_493" + }, + "wd_495": { + "name": "wd_495", + "object_class": "Equation", + "value": "0.02 + wd_494" + }, + "wd_496": { + "name": "wd_496", + "object_class": "Equation", + "value": "0.02 + wd_495" + }, + "wd_497": { + "name": "wd_497", + "object_class": "Equation", + "value": "0.02 + wd_496" + }, + "wd_498": { + "name": "wd_498", + "object_class": "Equation", + "value": "0.02 + wd_497" + }, + "wd_499": { + "name": "wd_499", + "object_class": "Equation", + "value": "0.02 + wd_498" + }, + "wd_500": { + "name": "wd_500", + "object_class": "Equation", + "value": "0.02 + wd_499" + }, + "O2write": { + "name": "O2write", + "object_class": "ModelLinkage", + "left_path": "/STATE/PL3_5250_0001/RCHRES_R001/O2", + "right_path": "/STATE/PL3_5250_0001/RCHRES_R001/wd_500", + "link_type": 5 + } + } +} + diff --git a/tests/testcbp/HSP2results/PL3_5250_0001.uci b/tests/testcbp/HSP2results/PL3_5250_0001.uci index 0f9de5ad..25d7a004 100644 --- a/tests/testcbp/HSP2results/PL3_5250_0001.uci +++ b/tests/testcbp/HSP2results/PL3_5250_0001.uci @@ -224,7 +224,4 @@ PLTGEN END CURV-DATA END PLTGEN -SPEC-ACTIONS -END SPEC-ACTIONS - END RUN diff --git a/tests/testcbp/HSP2results/PL3_5250_0001eq.json b/tests/testcbp/HSP2results/PL3_5250_0001eq.json new file mode 100644 index 00000000..82aaec29 --- /dev/null +++ b/tests/testcbp/HSP2results/PL3_5250_0001eq.json @@ -0,0 +1,24 @@ +{ + "RCHRES_R001": { + "name": "RCHRES_R001", + "object_class": "ModelObject", + "value": "0", + "wd_cfs": { + "name": "wd_cfs", + "object_class": "Equation", + "value": "0.1 * IVOL" + }, + "Qout": { + "name": "Qout", + "object_class": "Equation", + "value": "OVOL3 * 12.1" + }, + "IVOLwrite": { + "name": "IVOLwrite", + "object_class": "ModelLinkage", + "left_path": "/STATE/PL3_5250_0001eq/RCHRES_R001/O2", + "right_path": "/STATE/PL3_5250_0001eq/RCHRES_R001/wd_cfs", + "link_type": 5 + } + } +} diff --git a/tests/testcbp/HSP2results/PL3_5250_0001spec.uci b/tests/testcbp/HSP2results/PL3_5250_0001spec.uci new file mode 100644 index 00000000..1a4df762 --- /dev/null +++ b/tests/testcbp/HSP2results/PL3_5250_0001spec.uci @@ -0,0 +1,236 @@ +RUN + +GLOBAL + PL3_5250_0 riv | P5 | hsp2_2022 | Occoquan + START 2001/01/01 END 2001/12/31 + RUN INTERP OUTPUT LEVEL 1 1 + RESUME 0 RUN 1 UNIT SYSTEM 1 +END GLOBAL + +FILES + ***<----FILE NAME-------------------------------------------------> +WDM1 21 met_A51059.wdm +WDM2 22 prad_A51059.wdm +WDM3 23 ps_sep_div_ams_hsp2_2022_PL3_5250_0001.wdm +WDM4 24 PL3_5250_0001.wdm +MESSU 25 PL3_5250_0001.ech + 26 PL3_5250_0001.out + 31 PL3_5250_0001.tau +END FILES + +OPN SEQUENCE + INGRP INDELT 01:00 + RCHRES 1 + PLTGEN 1 + END INGRP +END OPN SEQUENCE + +RCHRES + ACTIVITY + # - # HYFG ADFG CNFG HTFG SDFG GQFG OXFG NUFG PKFG PHFG *** + 1 1 1 0 0 0 0 0 0 0 0 + END ACTIVITY + + PRINT-INFO + # - # HYFG ADFG CNFG HTFG SDFG GQFG OXFG NUFG PKFG PHFG PIVL***PY + 1 5 5 0 0 0 0 0 0 0 0 0 12 + END PRINT-INFO + + GEN-INFO + RCHRES<-------Name------->Nexit Unit Systems Printer *** + # - # User t-series Engl Metr LKFG *** + 1 PL3_5250_0001 3 1 1 1 26 0 1 + END GEN-INFO + + HYDR-PARM1 + RCHRES Flags for HYDR section *** + # - # VC A1 A2 A3 ODFVFG for each ODGTFG for each *** FUNCT for each + FG FG FG FG possible exit possible exit *** possible exit + 1 2 3 4 5 1 2 3 4 5 *** 1 2 3 4 5 + VC A1 A2 A3 V1 V2 V3 V4 V5 G1 G2 G3 G4 G5 *** F1 F2 F3 F4 F5 + 1 0 1 1 1 0 0 4 0 0 1 2 0 0 0 0 0 0 0 0 + END HYDR-PARM1 + + HYDR-PARM2 + RCHRES *** + # - # FTABNO LEN DELTH STCOR KS DB50 *** + 1 1. 10. 2. 0.5 + END HYDR-PARM2 + + HYDR-INIT + RCHRES Initial conditions for HYDR section *** + # - # VOL Initial value of COLIND *** Initial value of OUTDGT + (ac-ft) for each possible exit *** for each possible exit + VOL CEX1 CEX2 CEX3 CEX4 CEX5 *** DEX1 DEX2 DEX3 DEX4 DEX5 + 1 12175.000 + END HYDR-INIT + + ADCALC-DATA + RCHRES Data for section ADCALC *** + # - # CRRAT VOL *** + 1 1.5 12175. + END ADCALC-DATA + +END RCHRES + +FTABLES + FTABLE 1 + ROWS COLS *** + 20 4 + DEPTH AREA VOLUME DISCH *** + (FT) (ACRES) (AC-FT) (CFS) *** + 0 0 0 0 + 20 124 1007 0 + 30 240 2781 0 + 40 444 6106 0 + 50 804 12175 0 + 52 909 13886 39 + 54 1024 15819 78 + 56 1155 17999 117 + 57 1226 19227 136 + 58 1296 20456 137 + 60 1413 23180 138 + 62 1524 26140 140 + 63 1586 27745 1922 + 64 1647 29351 5179 + 65 1701 31247 9398 + 66 1755 33143 14393 + 67 1803 34984 20645 + 69 1879 38705 36532 + 70 1908 40585 44603 + 76 2100 54000 103071 + END FTABLE 1 +END FTABLES + +EXT SOURCES +<-Volume-> SsysSgap<--Mult-->Tran <-Target vols> <-Grp> <-Member->*** + # # tem strg<-factor->strg # # # #*** +*** METEOROLOGY +WDM1 1000 EVAP ENGLZERO 1.000 SAME RCHRES 1 EXTNL POTEV +WDM1 1001 DEWP ENGLZERO SAME RCHRES 1 EXTNL DEWTMP +WDM1 1002 WNDH ENGLZERO SAME RCHRES 1 EXTNL WIND +WDM1 1003 RADH ENGLZERO SAME RCHRES 1 EXTNL SOLRAD +WDM1 1004 ATMP ENGLZERO SAME RCHRES 1 EXTNL GATMP +WDM1 1005 CLDC ENGLZERO SAME RCHRES 1 EXTNL CLOUD + +*** PRECIPITATION AND ATMOSPHERIC DEPOSITION LOADS +WDM2 2000 HPRC ENGLZERO SAME RCHRES 1 EXTNL PREC +WDM2 2001 NO23 ENGLZERO DIV RCHRES 1 EXTNL NUADFX 1 1 +WDM2 2002 NH4A ENGLZERO DIV RCHRES 1 EXTNL NUADFX 2 1 +WDM2 2003 NO3D ENGLZERO DIV RCHRES 1 EXTNL NUADFX 1 1 +WDM2 2004 NH4D ENGLZERO DIV RCHRES 1 EXTNL NUADFX 2 1 +WDM2 2005 ORGN ENGLZERO DIV RCHRES 1 EXTNL PLADFX 1 1 +WDM2 2006 PO4A ENGLZERO DIV RCHRES 1 EXTNL NUADFX 3 1 +WDM2 2007 ORGP ENGLZERO DIV RCHRES 1 EXTNL PLADFX 2 1 + +*** POINT SOURCE +WDM3 3000 FLOW ENGLZERO DIV RCHRES 1 INFLOW IVOL +WDM3 3001 HEAT ENGLZERO DIV RCHRES 1 INFLOW IHEAT +WDM3 3002 NH3X ENGLZERO DIV RCHRES 1 INFLOW NUIF1 2 +WDM3 3003 NO3X ENGLZERO DIV RCHRES 1 INFLOW NUIF1 1 +WDM3 3004 ORNX ENGLZERO DIV RCHRES 1 INFLOW PKIF 3 +WDM3 3005 PO4X ENGLZERO DIV RCHRES 1 INFLOW NUIF1 4 +WDM3 3006 ORPX ENGLZERO DIV RCHRES 1 INFLOW PKIF 4 +WDM3 3021 BODX ENGLZERO DIV RCHRES 1 INFLOW OXIF 2 +WDM3 3022 TSSX ENGLZERO 0.0005 DIV RCHRES 1 INFLOW ISED 3 +WDM3 3023 DOXX ENGLZERO DIV RCHRES 1 INFLOW OXIF 1 +WDM3 3024 TOCX ENGLZERO DIV RCHRES 1 INFLOW PKIF 5 + +*** DIVERSIONS +WDM3 3007 DIVR ENGLZERO SAME RCHRES 1 EXTNL OUTDGT 1 +WDM3 3008 DIVA ENGLZERO SAME RCHRES 1 EXTNL OUTDGT 2 + +*** SEPTIC +WDM3 3010 SNO3 ENGLZERO 1.0000 DIV RCHRES 1 INFLOW NUIF1 1 + +*** AEOLIAN SEDIMENT +WDM3 3061 SFAS ENGLZERO 7.027e-06DIV RCHRES 1 INFLOW ISED 2 +WDM3 3062 SFAC ENGLZERO 7.027e-06DIV RCHRES 1 INFLOW ISED 3 + +*** UPSTREAM and EOS INPUT *** +WDM4 11 WATR ENGLZERO SAME RCHRES 1 INFLOW IVOL +WDM4 12 HEAT ENGLZERO SAME RCHRES 1 INFLOW IHEAT +WDM4 13 DOXY ENGLZERO SAME RCHRES 1 INFLOW OXIF 1 +WDM4 21 SAND ENGLZERO SAME RCHRES 1 INFLOW ISED 1 +WDM4 22 SILT ENGLZERO SAME RCHRES 1 INFLOW ISED 2 +WDM4 23 CLAY ENGLZERO SAME RCHRES 1 INFLOW ISED 3 +WDM4 31 NO3D ENGLZERO SAME RCHRES 1 INFLOW NUIF1 1 +WDM4 32 NH3D ENGLZERO SAME RCHRES 1 INFLOW NUIF1 2 +WDM4 33 NH3A ENGLZERO SAME RCHRES 1 INFLOW NUIF2 1 1 +WDM4 34 NH3I ENGLZERO SAME RCHRES 1 INFLOW NUIF2 2 1 +WDM4 35 NH3C ENGLZERO SAME RCHRES 1 INFLOW NUIF2 3 1 +WDM4 36 RORN ENGLZERO SAME RCHRES 1 INFLOW PKIF 3 +WDM4 41 PO4D ENGLZERO SAME RCHRES 1 INFLOW NUIF1 4 +WDM4 42 PO4A ENGLZERO SAME RCHRES 1 INFLOW NUIF2 1 2 +WDM4 43 PO4I ENGLZERO SAME RCHRES 1 INFLOW NUIF2 2 2 +WDM4 44 PO4C ENGLZERO SAME RCHRES 1 INFLOW NUIF2 3 2 +WDM4 45 RORP ENGLZERO SAME RCHRES 1 INFLOW PKIF 4 +WDM4 51 BODA ENGLZERO SAME RCHRES 1 INFLOW OXIF 2 +WDM4 52 TORC ENGLZERO SAME RCHRES 1 INFLOW PKIF 5 +WDM4 53 PHYT ENGLZERO SAME RCHRES 1 INFLOW PKIF 1 +END EXT SOURCES + +EXT TARGETS +<-Volume-> <-Grp> <-Member-><--Mult-->Tran <-Volume-> Tsys Tgap Amd *** + # # #<-factor->strg # # tem strg strg*** +RCHRES 1 OFLOW OVOL 3 SAME WDM4 111 WATR ENGL REPL +RCHRES 1 OFLOW OHEAT 3 SAME WDM4 112 HEAT ENGL REPL +RCHRES 1 OFLOW OXCF2 3 1 SAME WDM4 113 DOXY ENGL REPL +RCHRES 1 OFLOW OSED 3 1 SAME WDM4 121 SAND ENGL REPL +RCHRES 1 OFLOW OSED 3 2 SAME WDM4 122 SILT ENGL REPL +RCHRES 1 OFLOW OSED 3 3 SAME WDM4 123 CLAY ENGL REPL +RCHRES 1 OFLOW NUCF9 3 1 SAME WDM4 131 NO3D ENGL REPL +RCHRES 1 OFLOW NUCF9 3 2 SAME WDM4 132 NH3D ENGL REPL +RCHRES 1 OFLOW OSNH4 3 1 SAME WDM4 133 NH3A ENGL REPL +RCHRES 1 OFLOW OSNH4 3 2 SAME WDM4 134 NH3I ENGL REPL +RCHRES 1 OFLOW OSNH4 3 3 SAME WDM4 135 NH3C ENGL REPL +RCHRES 1 OFLOW PKCF2 3 3 SAME WDM4 136 RORN ENGL REPL +RCHRES 1 OFLOW NUCF9 3 4 SAME WDM4 141 PO4D ENGL REPL +RCHRES 1 OFLOW OSPO4 3 1 SAME WDM4 142 PO4A ENGL REPL +RCHRES 1 OFLOW OSPO4 3 2 SAME WDM4 143 PO4I ENGL REPL +RCHRES 1 OFLOW OSPO4 3 3 SAME WDM4 144 PO4C ENGL REPL +RCHRES 1 OFLOW PKCF2 3 4 SAME WDM4 145 RORP ENGL REPL +RCHRES 1 OFLOW OXCF2 3 2 SAME WDM4 151 BODA ENGL REPL +RCHRES 1 OFLOW PKCF2 3 5 SAME WDM4 152 TORC ENGL REPL +RCHRES 1 OFLOW PKCF2 3 1 SAME WDM4 153 PHYT ENGL REPL +END EXT TARGETS + +NETWORK +<-Volume-> <-Grp> <-Member-><--Mult-->Tran <-Target vols> <-Grp> <-Member-> *** + # # #<-factor->strg # # # # *** +RCHRES 1 HYDR TAU AVER PLTGEN 1 INPUT MEAN 1 +END NETWORK + +PLTGEN + PLOTINFO + # - # FILE NPT NMN LABL PYR PIVL *** + 1 31 1 12 24 + END PLOTINFO + + GEN-LABELS + # - #<----------------Title-----------------> *** + 1 PL3_5250_0001 daily_shear_stress_lbsft2 + END GEN-LABELS + + SCALING + #thru# YMIN YMAX IVLIN THRESH *** + 1 99 0. 100000. 20. + END SCALING + + CURV-DATA + <-Curve label--> Line Intg Col Tran *** + # - # type eqv code code *** + 1 daily_shear_stre 1 1 AVER + END CURV-DATA +END PLTGEN + +SPEC-ACTIONS +*** test special actions +*** I don't think this does anything because CBP is hydro only? +*** They should LOAD into the STATE context but not be executed + RCHRES 1 RSED 4 += 2.50E+05 + RCHRES 1 RSED 5 += 6.89E+05 + RCHRES 1 RSED 6 += 4.01E+05 +END SPEC-ACTIONS + +END RUN diff --git a/tests/testcbp/HSP2results/PL3_5250_0001.json.constant_wd b/tests/testcbp/HSP2results/PL3_5250_0001wd.json similarity index 73% rename from tests/testcbp/HSP2results/PL3_5250_0001.json.constant_wd rename to tests/testcbp/HSP2results/PL3_5250_0001wd.json index 3a4a0439..5c50a667 100644 --- a/tests/testcbp/HSP2results/PL3_5250_0001.json.constant_wd +++ b/tests/testcbp/HSP2results/PL3_5250_0001wd.json @@ -11,8 +11,8 @@ "IVOLwrite": { "name": "IVOLwrite", "object_class": "ModelLinkage", - "left_path": "/STATE/RCHRES_R001/O2", - "right_path": "/STATE/RCHRES_R001/wd_cfs", + "left_path": "/STATE/PL3_5250_0001wd/RCHRES_R001/O2", + "right_path": "/STATE/PL3_5250_0001wd/RCHRES_R001/wd_cfs", "link_type": 5 } } diff --git a/tests/testcbp/HSP2results/PL3_5250_0001wd.uci b/tests/testcbp/HSP2results/PL3_5250_0001wd.uci new file mode 100644 index 00000000..25d7a004 --- /dev/null +++ b/tests/testcbp/HSP2results/PL3_5250_0001wd.uci @@ -0,0 +1,227 @@ +RUN + +GLOBAL + PL3_5250_0 riv | P5 | hsp2_2022 | Occoquan + START 2001/01/01 END 2001/12/31 + RUN INTERP OUTPUT LEVEL 1 1 + RESUME 0 RUN 1 UNIT SYSTEM 1 +END GLOBAL + +FILES + ***<----FILE NAME-------------------------------------------------> +WDM1 21 met_A51059.wdm +WDM2 22 prad_A51059.wdm +WDM3 23 ps_sep_div_ams_hsp2_2022_PL3_5250_0001.wdm +WDM4 24 PL3_5250_0001.wdm +MESSU 25 PL3_5250_0001.ech + 26 PL3_5250_0001.out + 31 PL3_5250_0001.tau +END FILES + +OPN SEQUENCE + INGRP INDELT 01:00 + RCHRES 1 + PLTGEN 1 + END INGRP +END OPN SEQUENCE + +RCHRES + ACTIVITY + # - # HYFG ADFG CNFG HTFG SDFG GQFG OXFG NUFG PKFG PHFG *** + 1 1 1 0 0 0 0 0 0 0 0 + END ACTIVITY + + PRINT-INFO + # - # HYFG ADFG CNFG HTFG SDFG GQFG OXFG NUFG PKFG PHFG PIVL***PY + 1 5 5 0 0 0 0 0 0 0 0 0 12 + END PRINT-INFO + + GEN-INFO + RCHRES<-------Name------->Nexit Unit Systems Printer *** + # - # User t-series Engl Metr LKFG *** + 1 PL3_5250_0001 3 1 1 1 26 0 1 + END GEN-INFO + + HYDR-PARM1 + RCHRES Flags for HYDR section *** + # - # VC A1 A2 A3 ODFVFG for each ODGTFG for each *** FUNCT for each + FG FG FG FG possible exit possible exit *** possible exit + 1 2 3 4 5 1 2 3 4 5 *** 1 2 3 4 5 + VC A1 A2 A3 V1 V2 V3 V4 V5 G1 G2 G3 G4 G5 *** F1 F2 F3 F4 F5 + 1 0 1 1 1 0 0 4 0 0 1 2 0 0 0 0 0 0 0 0 + END HYDR-PARM1 + + HYDR-PARM2 + RCHRES *** + # - # FTABNO LEN DELTH STCOR KS DB50 *** + 1 1. 10. 2. 0.5 + END HYDR-PARM2 + + HYDR-INIT + RCHRES Initial conditions for HYDR section *** + # - # VOL Initial value of COLIND *** Initial value of OUTDGT + (ac-ft) for each possible exit *** for each possible exit + VOL CEX1 CEX2 CEX3 CEX4 CEX5 *** DEX1 DEX2 DEX3 DEX4 DEX5 + 1 12175.000 + END HYDR-INIT + + ADCALC-DATA + RCHRES Data for section ADCALC *** + # - # CRRAT VOL *** + 1 1.5 12175. + END ADCALC-DATA + +END RCHRES + +FTABLES + FTABLE 1 + ROWS COLS *** + 20 4 + DEPTH AREA VOLUME DISCH *** + (FT) (ACRES) (AC-FT) (CFS) *** + 0 0 0 0 + 20 124 1007 0 + 30 240 2781 0 + 40 444 6106 0 + 50 804 12175 0 + 52 909 13886 39 + 54 1024 15819 78 + 56 1155 17999 117 + 57 1226 19227 136 + 58 1296 20456 137 + 60 1413 23180 138 + 62 1524 26140 140 + 63 1586 27745 1922 + 64 1647 29351 5179 + 65 1701 31247 9398 + 66 1755 33143 14393 + 67 1803 34984 20645 + 69 1879 38705 36532 + 70 1908 40585 44603 + 76 2100 54000 103071 + END FTABLE 1 +END FTABLES + +EXT SOURCES +<-Volume-> SsysSgap<--Mult-->Tran <-Target vols> <-Grp> <-Member->*** + # # tem strg<-factor->strg # # # #*** +*** METEOROLOGY +WDM1 1000 EVAP ENGLZERO 1.000 SAME RCHRES 1 EXTNL POTEV +WDM1 1001 DEWP ENGLZERO SAME RCHRES 1 EXTNL DEWTMP +WDM1 1002 WNDH ENGLZERO SAME RCHRES 1 EXTNL WIND +WDM1 1003 RADH ENGLZERO SAME RCHRES 1 EXTNL SOLRAD +WDM1 1004 ATMP ENGLZERO SAME RCHRES 1 EXTNL GATMP +WDM1 1005 CLDC ENGLZERO SAME RCHRES 1 EXTNL CLOUD + +*** PRECIPITATION AND ATMOSPHERIC DEPOSITION LOADS +WDM2 2000 HPRC ENGLZERO SAME RCHRES 1 EXTNL PREC +WDM2 2001 NO23 ENGLZERO DIV RCHRES 1 EXTNL NUADFX 1 1 +WDM2 2002 NH4A ENGLZERO DIV RCHRES 1 EXTNL NUADFX 2 1 +WDM2 2003 NO3D ENGLZERO DIV RCHRES 1 EXTNL NUADFX 1 1 +WDM2 2004 NH4D ENGLZERO DIV RCHRES 1 EXTNL NUADFX 2 1 +WDM2 2005 ORGN ENGLZERO DIV RCHRES 1 EXTNL PLADFX 1 1 +WDM2 2006 PO4A ENGLZERO DIV RCHRES 1 EXTNL NUADFX 3 1 +WDM2 2007 ORGP ENGLZERO DIV RCHRES 1 EXTNL PLADFX 2 1 + +*** POINT SOURCE +WDM3 3000 FLOW ENGLZERO DIV RCHRES 1 INFLOW IVOL +WDM3 3001 HEAT ENGLZERO DIV RCHRES 1 INFLOW IHEAT +WDM3 3002 NH3X ENGLZERO DIV RCHRES 1 INFLOW NUIF1 2 +WDM3 3003 NO3X ENGLZERO DIV RCHRES 1 INFLOW NUIF1 1 +WDM3 3004 ORNX ENGLZERO DIV RCHRES 1 INFLOW PKIF 3 +WDM3 3005 PO4X ENGLZERO DIV RCHRES 1 INFLOW NUIF1 4 +WDM3 3006 ORPX ENGLZERO DIV RCHRES 1 INFLOW PKIF 4 +WDM3 3021 BODX ENGLZERO DIV RCHRES 1 INFLOW OXIF 2 +WDM3 3022 TSSX ENGLZERO 0.0005 DIV RCHRES 1 INFLOW ISED 3 +WDM3 3023 DOXX ENGLZERO DIV RCHRES 1 INFLOW OXIF 1 +WDM3 3024 TOCX ENGLZERO DIV RCHRES 1 INFLOW PKIF 5 + +*** DIVERSIONS +WDM3 3007 DIVR ENGLZERO SAME RCHRES 1 EXTNL OUTDGT 1 +WDM3 3008 DIVA ENGLZERO SAME RCHRES 1 EXTNL OUTDGT 2 + +*** SEPTIC +WDM3 3010 SNO3 ENGLZERO 1.0000 DIV RCHRES 1 INFLOW NUIF1 1 + +*** AEOLIAN SEDIMENT +WDM3 3061 SFAS ENGLZERO 7.027e-06DIV RCHRES 1 INFLOW ISED 2 +WDM3 3062 SFAC ENGLZERO 7.027e-06DIV RCHRES 1 INFLOW ISED 3 + +*** UPSTREAM and EOS INPUT *** +WDM4 11 WATR ENGLZERO SAME RCHRES 1 INFLOW IVOL +WDM4 12 HEAT ENGLZERO SAME RCHRES 1 INFLOW IHEAT +WDM4 13 DOXY ENGLZERO SAME RCHRES 1 INFLOW OXIF 1 +WDM4 21 SAND ENGLZERO SAME RCHRES 1 INFLOW ISED 1 +WDM4 22 SILT ENGLZERO SAME RCHRES 1 INFLOW ISED 2 +WDM4 23 CLAY ENGLZERO SAME RCHRES 1 INFLOW ISED 3 +WDM4 31 NO3D ENGLZERO SAME RCHRES 1 INFLOW NUIF1 1 +WDM4 32 NH3D ENGLZERO SAME RCHRES 1 INFLOW NUIF1 2 +WDM4 33 NH3A ENGLZERO SAME RCHRES 1 INFLOW NUIF2 1 1 +WDM4 34 NH3I ENGLZERO SAME RCHRES 1 INFLOW NUIF2 2 1 +WDM4 35 NH3C ENGLZERO SAME RCHRES 1 INFLOW NUIF2 3 1 +WDM4 36 RORN ENGLZERO SAME RCHRES 1 INFLOW PKIF 3 +WDM4 41 PO4D ENGLZERO SAME RCHRES 1 INFLOW NUIF1 4 +WDM4 42 PO4A ENGLZERO SAME RCHRES 1 INFLOW NUIF2 1 2 +WDM4 43 PO4I ENGLZERO SAME RCHRES 1 INFLOW NUIF2 2 2 +WDM4 44 PO4C ENGLZERO SAME RCHRES 1 INFLOW NUIF2 3 2 +WDM4 45 RORP ENGLZERO SAME RCHRES 1 INFLOW PKIF 4 +WDM4 51 BODA ENGLZERO SAME RCHRES 1 INFLOW OXIF 2 +WDM4 52 TORC ENGLZERO SAME RCHRES 1 INFLOW PKIF 5 +WDM4 53 PHYT ENGLZERO SAME RCHRES 1 INFLOW PKIF 1 +END EXT SOURCES + +EXT TARGETS +<-Volume-> <-Grp> <-Member-><--Mult-->Tran <-Volume-> Tsys Tgap Amd *** + # # #<-factor->strg # # tem strg strg*** +RCHRES 1 OFLOW OVOL 3 SAME WDM4 111 WATR ENGL REPL +RCHRES 1 OFLOW OHEAT 3 SAME WDM4 112 HEAT ENGL REPL +RCHRES 1 OFLOW OXCF2 3 1 SAME WDM4 113 DOXY ENGL REPL +RCHRES 1 OFLOW OSED 3 1 SAME WDM4 121 SAND ENGL REPL +RCHRES 1 OFLOW OSED 3 2 SAME WDM4 122 SILT ENGL REPL +RCHRES 1 OFLOW OSED 3 3 SAME WDM4 123 CLAY ENGL REPL +RCHRES 1 OFLOW NUCF9 3 1 SAME WDM4 131 NO3D ENGL REPL +RCHRES 1 OFLOW NUCF9 3 2 SAME WDM4 132 NH3D ENGL REPL +RCHRES 1 OFLOW OSNH4 3 1 SAME WDM4 133 NH3A ENGL REPL +RCHRES 1 OFLOW OSNH4 3 2 SAME WDM4 134 NH3I ENGL REPL +RCHRES 1 OFLOW OSNH4 3 3 SAME WDM4 135 NH3C ENGL REPL +RCHRES 1 OFLOW PKCF2 3 3 SAME WDM4 136 RORN ENGL REPL +RCHRES 1 OFLOW NUCF9 3 4 SAME WDM4 141 PO4D ENGL REPL +RCHRES 1 OFLOW OSPO4 3 1 SAME WDM4 142 PO4A ENGL REPL +RCHRES 1 OFLOW OSPO4 3 2 SAME WDM4 143 PO4I ENGL REPL +RCHRES 1 OFLOW OSPO4 3 3 SAME WDM4 144 PO4C ENGL REPL +RCHRES 1 OFLOW PKCF2 3 4 SAME WDM4 145 RORP ENGL REPL +RCHRES 1 OFLOW OXCF2 3 2 SAME WDM4 151 BODA ENGL REPL +RCHRES 1 OFLOW PKCF2 3 5 SAME WDM4 152 TORC ENGL REPL +RCHRES 1 OFLOW PKCF2 3 1 SAME WDM4 153 PHYT ENGL REPL +END EXT TARGETS + +NETWORK +<-Volume-> <-Grp> <-Member-><--Mult-->Tran <-Target vols> <-Grp> <-Member-> *** + # # #<-factor->strg # # # # *** +RCHRES 1 HYDR TAU AVER PLTGEN 1 INPUT MEAN 1 +END NETWORK + +PLTGEN + PLOTINFO + # - # FILE NPT NMN LABL PYR PIVL *** + 1 31 1 12 24 + END PLOTINFO + + GEN-LABELS + # - #<----------------Title-----------------> *** + 1 PL3_5250_0001 daily_shear_stress_lbsft2 + END GEN-LABELS + + SCALING + #thru# YMIN YMAX IVLIN THRESH *** + 1 99 0. 100000. 20. + END SCALING + + CURV-DATA + <-Curve label--> Line Intg Col Tran *** + # - # type eqv code code *** + 1 daily_shear_stre 1 1 AVER + END CURV-DATA +END PLTGEN + +END RUN diff --git a/tests/testcbp/HSP2results/check_endpoint_ts.py b/tests/testcbp/HSP2results/check_endpoint_ts.py index f42e1d19..e0d94b58 100644 --- a/tests/testcbp/HSP2results/check_endpoint_ts.py +++ b/tests/testcbp/HSP2results/check_endpoint_ts.py @@ -17,7 +17,7 @@ # # f.close() hdf5_instance = HDF5(fpath) io_manager = IOManager(hdf5_instance) -uci_obj = io_manager.read_uci() +uci_obj = io_manager.read_parameters() siminfo = uci_obj.siminfo opseq = uci_obj.opseq # Note: now that the UCI is read in and hdf5 loaded, you can see things like: @@ -44,7 +44,7 @@ ) # this creates all objects from the UCI and previous loads # state['model_root_object'].find_var_path('RCHRES_R001') # Get the timeseries naked, without an object -rchres1 = state["model_object_cache"]["/STATE/RCHRES_R001"] +rchres1 = om_operations["model_object_cache"]["/STATE/RCHRES_R001"] precip_ts = ModelLinkage( "PRCP", rchres1, diff --git a/tests/testcbp/HSP2results/check_equation.py b/tests/testcbp/HSP2results/check_equation.py index 3289c4ed..0b963a8e 100644 --- a/tests/testcbp/HSP2results/check_equation.py +++ b/tests/testcbp/HSP2results/check_equation.py @@ -1,52 +1,113 @@ # Must be run from the HSPsquared source directory, the h5 file has already been setup with hsp import_uci test10.uci # bare bones tester - must be run from the HSPsquared source directory -import os +import os import numpy from hsp2.hsp2.main import * +from hsp2.state.state import * from hsp2.hsp2.om import * +from hsp2.hsp2.SPECL import * from hsp2.hsp2io.hdf import HDF5 from hsp2.hsp2io.io import IOManager from hsp2.state.state import * +from hsp2.hsp2.om_timer import timer_class -fpath = "./tests/testcbp/HSP2results/JL1_6562_6560.h5" +fbase = "PL3_5250_0001eq" +fpath = "./tests/testcbp/HSP2results/" + fbase + ".h5" # try also: # fpath = './tests/testcbp/HSP2results/JL1_6562_6560.h5' +timer = timer_class() # sometimes when testing you may need to close the file, so try: # f = h5py.File(fpath,'a') # use mode 'a' which allows read, write, modify # # f.close() hdf5_instance = HDF5(fpath) io_manager = IOManager(hdf5_instance) -uci_obj = io_manager.read_uci() -siminfo = uci_obj.siminfo -opseq = uci_obj.opseq -# Note: now that the UCI is read in and hdf5 loaded, you can see things like: -# - hdf5_instance._store.keys() - all the paths in the UCI/hdf5 -# - finally stash specactions in state, not domain (segment) dependent so do it once -# now load state and the special actions -state = init_state_dicts() -state_initialize_om(state) -state["specactions"] = uci_obj.specactions # stash the specaction dict in state -state_siminfo_hsp2(uci_obj, siminfo) + +# read user control, parameters, states, and flags parameters and map to local variables +parameter_obj = io_manager.read_parameters() +opseq = parameter_obj.opseq +ddlinks = parameter_obj.ddlinks +ddmasslinks = parameter_obj.ddmasslinks +ddext_sources = parameter_obj.ddext_sources +ddgener = parameter_obj.ddgener +model = parameter_obj.model +siminfo = parameter_obj.siminfo +ftables = parameter_obj.ftables +specactions = parameter_obj.specactions +monthdata = parameter_obj.monthdata + +start, stop = siminfo["start"], siminfo["stop"] + +copy_instances = {} +gener_instances = {} +section_timing = {} + +section_timing["io_manager.read_parameters() call and config"] = str(timer.split()) + "seconds" +####################################################################################### +# initialize STATE dicts +####################################################################################### +# Set up Things in state that will be used in all modular activities like SPECL +state = state_class( + state_empty["state_ix"], state_empty["op_tokens"], state_empty["state_paths"], + state_empty["op_exec_lists"], state_empty["model_exec_list"], state_empty["dict_ix"], + state_empty["ts_ix"], state_empty["hsp_segments"] +) +om_operations = om_init_state() # set up operational model specific containers +state_siminfo_hsp2(state, parameter_obj, siminfo, io_manager) +state_om_model_root_object(state, om_operations, siminfo) +# Iterate through all segments and add crucial paths to state +# before loading dynamic components that may reference them +state_init_hsp2(state, opseq, activities, timer) +om_init_hsp2_segments(state, om_operations) +# now initialize all state variables for mutable variables +hsp2_domain_dependencies(state, opseq, activities, om_operations, False) # Add support for dynamic functions to operate on STATE # - Load any dynamic components if present, and store variables on objects state_load_dynamics_hsp2(state, io_manager, siminfo) -# Iterate through all segments and add crucial paths to state -# before loading dynamic components that may reference them -state_init_hsp2(state, opseq, activities) -state_load_dynamics_specl(state, io_manager, siminfo) # traditional special actions +# - finally stash specactions in state, not domain (segment) dependent so do it once +specl_load_om(om_operations, specactions) # load traditional special actions state_load_dynamics_om( - state, io_manager, siminfo + state, io_manager, siminfo, om_operations ) # operational model for custom python -state_om_model_run_prep( - state, io_manager, siminfo -) # this creates all objects from the UCI and previous loads +# finalize all dynamically loaded components and prepare to run the model +state_om_model_run_prep(opseq, activities, state, om_operations, siminfo) +section_timing["state om initialization()"] = str(timer.split()) + "seconds" +statenb = state_class_lite(0) +state_copy(state, statenb) +####################################################################################### +####################################################################################### + +# debug loading: +# mtl = [] +# mel = [] +# model_order_recursive(endpoint, om_operations["model_object_cache"], mel, mtl, True) +basepath = "/STATE/" + fbase +RCHRES = om_operations["model_object_cache"][basepath + "/RCHRES_R001"] +O2 = RCHRES.get_object('O2') +O2 = om_operations["model_object_cache"]["/STATE/PL3_5250_0001eq/RCHRES_R001/O2"] +wd_500 = om_operations["model_object_cache"][RCHRES.find_var_path('wd_500')] + +wd_cfs = om_operations["model_object_cache"]["/STATE/PL3_5250_0001eq/RCHRES_R001/wd_cfs"] + +state.get_ix_path(wd_cfs.ops[6]) +state.get_ix_path(wd_cfs.ops[7]) + +wd_cfs.find_var_path("O2") + + +##### Check exec list +ep_list = hydr_init_ix(state, RCHRES.state_path) +op_exec_list = model_domain_dependencies( + om_operations, state, RCHRES.state_path, ep_list, True, False +) +op_exec_list = np.asarray(op_exec_list) + # state['model_root_object'].find_var_path('RCHRES_R001') # Get the timeseries naked, without an object -Rlocal = state["model_object_cache"]["/STATE/RCHRES_R001/Rlocal"] +Rlocal = om_operations["model_object_cache"]["/STATE/RCHRES_R001/Rlocal"] Rlocal_ts = Rlocal.read_ts() -rchres1 = state["model_object_cache"]["/STATE/RCHRES_R001"] +rchres1 = om_operations["model_object_cache"]["/STATE/RCHRES_R001"] Rlocal_check = ModelLinkage( "Rlocal1", rchres1, {"right_path": "/TIMESERIES/TS010", "link_type": 3} ) @@ -78,3 +139,56 @@ end - start, "seconds", ) + + +# try also: +# Must be run from the HSPsquared source directory, the h5 file has already been setup with hsp import_uci test10.uci +# bare bones tester - must be run from the HSPsquared source directory +# sometimes when testing you may need to close the file, so try: +# import h5py;f = h5py.File(fpath,'a') # use mode 'a' which allows read, write, modify +# # f.close() +import os +import numpy +import h5py +from hsp2.hsp2.main import * +from hsp2.state.state import * +from hsp2.hsp2.om import * +from hsp2.hsp2.SPECL import * +from hsp2.hsp2io.hdf import HDF5 +from hsp2.hsp2io.io import IOManager +from hsp2.hsp2tools.readUCI import * +from src.hsp2.hsp2tools.commands import import_uci, run +from pandas import read_hdf + +fpath = "./tests/testcbp/HSP2results/PL3_5250_0001.h5" +# to run use this: +# run(fpath, saveall=True, compress=False) +dstore_hydr = pd.HDFStore(str(fpath), mode='r') +hsp2_hydr = read_hdf(dstore_hydr, '/RESULTS/RCHRES_R001/HYDR') +np.quantile(hsp2_hydr[:]['O2'], [0,0.25,0.5,0.75,1.0]) +# To re-run: +dstore_hydr.close() + +fpath = "./tests/testcbp/HSP2results/PL3_5250_0001wd.h5" +# to run use this: +# run(fpath, saveall=True, compress=False) +dstore_hydr = pd.HDFStore(str(fpath), mode='r') +hsp2_wd_hydr = read_hdf(dstore_hydr, '/RESULTS/RCHRES_R001/HYDR') +dstore_hydr.close() +np.quantile(hsp2_wd_hydr[:]['O2'], [0,0.25,0.5,0.75,1.0]) +# To re-run: + + +fpath = "./tests/testcbp/HSP2results/PL3_5250_0001eq.h5" +# to run use this: +# run(fpath, saveall=True, compress=False) +dstore_hydreq = pd.HDFStore(str(fpath), mode='r') +hsp2_eq_hydr = read_hdf(dstore_hydreq, '/RESULTS/RCHRES_R001/HYDR') +dstore_hydreq.close() +np.quantile(hsp2_eq_hydr[:]['O2'], [0,0.25,0.5,0.75,1.0]) +# To re-run: + +np.mean(hsp2_hydr[:]['IVOL']) +np.mean(hsp2_eq_hydr[:]['IVOL']) +np.mean(hsp2_hydr[:]['OVOL3']) +np.mean(hsp2_eq_hydr[:]['OVOL3'])