hydrodl2 1.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- hydrodl2/__init__.py +122 -0
- hydrodl2/_version.py +34 -0
- hydrodl2/api/__init__.py +3 -0
- hydrodl2/api/methods.py +144 -0
- hydrodl2/core/calc/__init__.py +11 -0
- hydrodl2/core/calc/batch_jacobian.pye +501 -0
- hydrodl2/core/calc/fdj.py +92 -0
- hydrodl2/core/calc/uh_routing.py +105 -0
- hydrodl2/core/calc/utils.py +59 -0
- hydrodl2/core/utils/__init__.py +7 -0
- hydrodl2/core/utils/clean_temp.sh +8 -0
- hydrodl2/core/utils/utils.py +63 -0
- hydrodl2/models/hbv/hbv.py +596 -0
- hydrodl2/models/hbv/hbv_1_1p.py +608 -0
- hydrodl2/models/hbv/hbv_2.py +670 -0
- hydrodl2/models/hbv/hbv_2_hourly.py +897 -0
- hydrodl2/models/hbv/hbv_2_mts.py +377 -0
- hydrodl2/models/hbv/hbv_adj.py +712 -0
- hydrodl2/modules/__init__.py +2 -0
- hydrodl2/modules/data_assimilation/variational_prcp_da.py +1 -0
- hydrodl2-1.3.0.dist-info/METADATA +184 -0
- hydrodl2-1.3.0.dist-info/RECORD +24 -0
- hydrodl2-1.3.0.dist-info/WHEEL +4 -0
- hydrodl2-1.3.0.dist-info/licenses/LICENSE +31 -0
hydrodl2/__init__.py
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# src/hydrodl2/__init__.py
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from importlib.resources import files
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from platformdirs import user_config_dir
|
|
10
|
+
|
|
11
|
+
from hydrodl2._version import __version__
|
|
12
|
+
from hydrodl2.api import available_models, available_modules, load_model, load_module
|
|
13
|
+
|
|
14
|
+
log = logging.getLogger('hydrodl2')
|
|
15
|
+
|
|
16
|
+
# In case setuptools scm says version is 0.0.0
|
|
17
|
+
assert not __version__.startswith('0.0.0')
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
'__version__',
|
|
21
|
+
'available_models',
|
|
22
|
+
'available_modules',
|
|
23
|
+
'load_model',
|
|
24
|
+
'load_module',
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _check_license_agreement():
|
|
29
|
+
"""Checks if user has agreed to package license and prompts if not."""
|
|
30
|
+
package_name = 'hydrodl2'
|
|
31
|
+
|
|
32
|
+
config_dir = Path(user_config_dir(package_name))
|
|
33
|
+
agreement_file = config_dir / '.license_status'
|
|
34
|
+
|
|
35
|
+
model_classes = available_models()
|
|
36
|
+
|
|
37
|
+
if not agreement_file.exists():
|
|
38
|
+
print(f"\n[----- {package_name} LICENSE AGREEMENT -----]")
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
# Find and read LICENSE file
|
|
42
|
+
license_path = files(package_name).parent.parent.joinpath("LICENSE")
|
|
43
|
+
license = license_path.read_text(encoding="utf-8")
|
|
44
|
+
print(license)
|
|
45
|
+
except FileNotFoundError:
|
|
46
|
+
# Fallback in case the LICENSE file wasn't packaged correctly
|
|
47
|
+
print(
|
|
48
|
+
"\n|> Error locating License. Showing summary <|\n"
|
|
49
|
+
"By using this software, you agree to the terms specified \n"
|
|
50
|
+
"in the Non-Commercial Software License Agreement: \n"
|
|
51
|
+
"\nhttps://github.com/mhpi/hydrodl2/blob/master/LICENSE \n"
|
|
52
|
+
"\n'hydrodl2' models are free for non-commercial use. \n"
|
|
53
|
+
"Prior authorization must be obtained for commercial \n"
|
|
54
|
+
"use. For further details, please contact the Pennsylvania \n"
|
|
55
|
+
"State University Office of Technology Management at \n"
|
|
56
|
+
"814.865.6277 or otminfo@psu.edu.\n"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
print("\nThis agreement applies to all named models in this package:\n")
|
|
60
|
+
|
|
61
|
+
if model_classes: # Avoid error if model_classes is empty
|
|
62
|
+
max_len = max(len(model) for model in model_classes) + 2
|
|
63
|
+
else:
|
|
64
|
+
max_len = 0
|
|
65
|
+
|
|
66
|
+
for model in model_classes:
|
|
67
|
+
model_string = ''
|
|
68
|
+
for submodel in model_classes[model]:
|
|
69
|
+
model_string += str(submodel) + ', '
|
|
70
|
+
print(f"-> {model:<{max_len}}: {model_string[:-3]}")
|
|
71
|
+
|
|
72
|
+
print("-" * 40)
|
|
73
|
+
|
|
74
|
+
try:
|
|
75
|
+
response = input("Do you agree to these terms? Type 'Yes' to continue: ")
|
|
76
|
+
except EOFError:
|
|
77
|
+
# If we get here, it means we're in an environment that can't take
|
|
78
|
+
# input -- default to no agreement.
|
|
79
|
+
print("\n[!] No terminal detected. Skipping license prompt.")
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
if response.strip().lower() in ['yes', 'y']:
|
|
83
|
+
try:
|
|
84
|
+
config_dir.mkdir(parents=True, exist_ok=True)
|
|
85
|
+
agreement_file.write_text(
|
|
86
|
+
f"accepted_on = {datetime.now().isoformat()}Z\nversion = 1\n",
|
|
87
|
+
encoding="utf-8",
|
|
88
|
+
)
|
|
89
|
+
log.warning(
|
|
90
|
+
f"License accepted. Agreement written to {agreement_file}\n"
|
|
91
|
+
)
|
|
92
|
+
except OSError as e:
|
|
93
|
+
log.warning(f"Failed to save agreement file {agreement_file}: {e}")
|
|
94
|
+
print(
|
|
95
|
+
"You may need to run with administrator privileges to avoid "
|
|
96
|
+
"repeating this process at runtime.",
|
|
97
|
+
)
|
|
98
|
+
else:
|
|
99
|
+
print("\n>| License agreement not accepted. Exiting. <|")
|
|
100
|
+
raise SystemExit(1)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _should_skip_license():
|
|
104
|
+
"""Returns True if the license check should be bypassed."""
|
|
105
|
+
# 1. Check if we are in a Non-Interactive shell (No user to type 'Yes')
|
|
106
|
+
if not sys.stdin.isatty():
|
|
107
|
+
return True
|
|
108
|
+
|
|
109
|
+
# 2. Check for common 'Silent' or 'CI' flags
|
|
110
|
+
if os.environ.get('CI') or os.environ.get('NGEN_SILENT'):
|
|
111
|
+
return True
|
|
112
|
+
|
|
113
|
+
# 3. Check for the Docker flag (as a fallback)
|
|
114
|
+
if os.path.exists('/.dockerenv'):
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
return False
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
# This only runs once when package is first imported.
|
|
121
|
+
if not _should_skip_license():
|
|
122
|
+
_check_license_agreement()
|
hydrodl2/_version.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '1.3.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 3, 0)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
hydrodl2/api/__init__.py
ADDED
hydrodl2/api/methods.py
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Note: If adding new public methods, please add them to __all__
|
|
3
|
+
at the top of the file and in api/__init__.py.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import importlib.util
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import re
|
|
10
|
+
|
|
11
|
+
from torch.nn import Module
|
|
12
|
+
|
|
13
|
+
from hydrodl2.core.utils import _get_dir, get_model_dirs, get_model_files
|
|
14
|
+
|
|
15
|
+
log = logging.getLogger("hydrodl2")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def available_models() -> dict[str, list[str]]:
|
|
19
|
+
"""Identify and list all available models in hydroDL2.
|
|
20
|
+
|
|
21
|
+
Returns
|
|
22
|
+
-------
|
|
23
|
+
list
|
|
24
|
+
A list of available models.
|
|
25
|
+
"""
|
|
26
|
+
# Path to the models directory
|
|
27
|
+
model_dir = _get_dir('models')
|
|
28
|
+
models = {}
|
|
29
|
+
|
|
30
|
+
dirs, _ = get_model_dirs(model_dir)
|
|
31
|
+
for dir in dirs:
|
|
32
|
+
_, file_names = get_model_files(dir)
|
|
33
|
+
models[dir.name] = file_names
|
|
34
|
+
|
|
35
|
+
return models
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _list_available_models() -> list[str]:
|
|
39
|
+
"""List all available models in hydroDL2 without the dict nesting
|
|
40
|
+
of available_models().
|
|
41
|
+
|
|
42
|
+
Returns
|
|
43
|
+
-------
|
|
44
|
+
list
|
|
45
|
+
A list of available models.
|
|
46
|
+
"""
|
|
47
|
+
model_dir = _get_dir('models')
|
|
48
|
+
models = []
|
|
49
|
+
dirs, _ = get_model_dirs(model_dir)
|
|
50
|
+
for dir in dirs:
|
|
51
|
+
_, file_names = get_model_files(dir)
|
|
52
|
+
for file in file_names:
|
|
53
|
+
models.append(file)
|
|
54
|
+
|
|
55
|
+
return models
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def available_modules() -> dict[str, list[str]]:
|
|
59
|
+
"""Identify and list all available modules in the hydroDL2.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
list
|
|
64
|
+
A list of available modules.
|
|
65
|
+
"""
|
|
66
|
+
# Path to the modules directory
|
|
67
|
+
model_dir = _get_dir('modules')
|
|
68
|
+
modules = {}
|
|
69
|
+
|
|
70
|
+
dirs, _ = get_model_dirs(model_dir)
|
|
71
|
+
for dir in dirs:
|
|
72
|
+
_, file_names = get_model_files(dir)
|
|
73
|
+
modules[dir.name] = file_names
|
|
74
|
+
|
|
75
|
+
return modules
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def load_model(model: str, ver_name: str = None) -> Module:
|
|
79
|
+
"""Load a model from the models directory.
|
|
80
|
+
|
|
81
|
+
Each model file in `models/` directory should only contain one model class.
|
|
82
|
+
|
|
83
|
+
Parameters
|
|
84
|
+
----------
|
|
85
|
+
model
|
|
86
|
+
The model name.
|
|
87
|
+
ver_name
|
|
88
|
+
The version name (class) of the model to load within the model file.
|
|
89
|
+
|
|
90
|
+
Returns
|
|
91
|
+
-------
|
|
92
|
+
Module
|
|
93
|
+
The uninstantiated model.
|
|
94
|
+
"""
|
|
95
|
+
parent_dir = _get_dir('models')
|
|
96
|
+
|
|
97
|
+
if ver_name is None:
|
|
98
|
+
ver_name = model # Default to the model name if no version is specified
|
|
99
|
+
|
|
100
|
+
# Construct file path
|
|
101
|
+
model = re.sub(
|
|
102
|
+
r'([a-z])([A-Z])', r'\1_\2', model
|
|
103
|
+
).lower() # Convert camelCase to snake_case
|
|
104
|
+
model_dir = model.split('_')[
|
|
105
|
+
0
|
|
106
|
+
].lower() # Model class name is first word in snake_case
|
|
107
|
+
model_subpath = os.path.join(model_dir, f'{model}.py')
|
|
108
|
+
|
|
109
|
+
# Path to the module file in the model directory
|
|
110
|
+
source = os.path.join(parent_dir, model_subpath)
|
|
111
|
+
|
|
112
|
+
# Load the model dynamically as a module
|
|
113
|
+
try:
|
|
114
|
+
spec = importlib.util.spec_from_file_location(model, source)
|
|
115
|
+
module = importlib.util.module_from_spec(spec)
|
|
116
|
+
spec.loader.exec_module(module)
|
|
117
|
+
except ImportError as e:
|
|
118
|
+
raise ImportError(f"Model '{model}' not found.") from e
|
|
119
|
+
|
|
120
|
+
# Retrieve version name if possible, otherwise get first class in module
|
|
121
|
+
try:
|
|
122
|
+
cls = getattr(module, ver_name)
|
|
123
|
+
except AttributeError as e:
|
|
124
|
+
# Find first class in module (NOTE: not guaranteed accurate)
|
|
125
|
+
classes = [
|
|
126
|
+
attr
|
|
127
|
+
for attr in dir(module)
|
|
128
|
+
if isinstance(getattr(module, attr), type) and attr != 'Any'
|
|
129
|
+
]
|
|
130
|
+
if not classes:
|
|
131
|
+
raise ImportError(f"Model version '{model}' not found.") from e
|
|
132
|
+
|
|
133
|
+
log.warning(
|
|
134
|
+
f"Model class '{ver_name}' not found in module '{module.__file__}'. "
|
|
135
|
+
f"Falling back to the first available: '{classes[0]}'."
|
|
136
|
+
)
|
|
137
|
+
cls = getattr(module, classes[0])
|
|
138
|
+
|
|
139
|
+
return cls
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def load_module():
|
|
143
|
+
"""Load a module from the modules directory."""
|
|
144
|
+
raise NotImplementedError("This function is not yet implemented.")
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
from .fdj import finite_difference_jacobian_p
|
|
2
|
+
from .uh_routing import uh_conv, uh_gamma
|
|
3
|
+
from .utils import change_param_range, param_bounds_2d
|
|
4
|
+
|
|
5
|
+
__all__ = [
|
|
6
|
+
'change_param_range',
|
|
7
|
+
'param_bounds_2d',
|
|
8
|
+
'uh_gamma',
|
|
9
|
+
'uh_conv',
|
|
10
|
+
'finite_difference_jacobian_p',
|
|
11
|
+
]
|