pyskby 0.2.1__tar.gz
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.
- pyskby-0.2.1/PKG-INFO +216 -0
- pyskby-0.2.1/pyproject.toml +86 -0
- pyskby-0.2.1/pyskby/__init__.py +24 -0
- pyskby-0.2.1/pyskby/cli.py +187 -0
- pyskby-0.2.1/pyskby/component.py +47 -0
- pyskby-0.2.1/pyskby/components.py +3202 -0
- pyskby-0.2.1/pyskby/examples/__init__.py +0 -0
- pyskby-0.2.1/pyskby/examples/data/liulan_fupan/RemoveEnd.exe +0 -0
- pyskby-0.2.1/pyskby/examples/data/liulan_fupan/data-test.csv +79 -0
- pyskby-0.2.1/pyskby/examples/data/liulan_fupan/params-testnew-template.csv +1236 -0
- pyskby-0.2.1/pyskby/examples/data/liulan_fupan/params-testnew.csv +1236 -0
- pyskby-0.2.1/pyskby/examples/sim/liulan_fupan.groovy +40 -0
- pyskby-0.2.1/pyskby/examples/sim/liulan_fupan_opt.groovy +117 -0
- pyskby-0.2.1/pyskby/io.py +82 -0
- pyskby-0.2.1/pyskby/io_oms.py +119 -0
- pyskby-0.2.1/pyskby/model.py +394 -0
- pyskby-0.2.1/pyskby/params.py +159 -0
- pyskby-0.2.1/pyskby/units.py +41 -0
- pyskby-0.2.1/pyskby.egg-info/PKG-INFO +216 -0
- pyskby-0.2.1/pyskby.egg-info/SOURCES.txt +24 -0
- pyskby-0.2.1/pyskby.egg-info/dependency_links.txt +1 -0
- pyskby-0.2.1/pyskby.egg-info/entry_points.txt +3 -0
- pyskby-0.2.1/pyskby.egg-info/requires.txt +1 -0
- pyskby-0.2.1/pyskby.egg-info/top_level.txt +1 -0
- pyskby-0.2.1/readme.md +195 -0
- pyskby-0.2.1/setup.cfg +4 -0
pyskby-0.2.1/PKG-INFO
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyskby
|
|
3
|
+
Version: 0.2.1
|
|
4
|
+
Summary: SKBY spatially and temporally variable source-area hydrological model (Python port of STVSRM)
|
|
5
|
+
Author: wly
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: hydrology,rainfall-runoff,watershed,runoff,source-area,infiltration,channel-routing,STVSRM,SKBY
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Science/Research
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Hydrology
|
|
17
|
+
Classifier: Topic :: Scientific/Engineering :: Atmospheric Science
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: numpy
|
|
21
|
+
|
|
22
|
+
# pyskby
|
|
23
|
+
|
|
24
|
+
SKBY(时空变源混合产流)水文模型的 Python 版 —— 由 Java 版 STVSRM 完整移植,
|
|
25
|
+
与 Java 参考模型**逐位对齐**(对拍通过),支持物理-守恒双模式、多外部水源接入。
|
|
26
|
+
|
|
27
|
+
- **发布名**:`pyskby`
|
|
28
|
+
- **导入名**:`pyskby`
|
|
29
|
+
- **版本**:`0.2.0`
|
|
30
|
+
- **Python**:≥ 3.10,依赖 `numpy`
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 1. 安装指令
|
|
35
|
+
|
|
36
|
+
### 方式一:从 PyPI 安装(发布后)
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install pyskby
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 方式二:从源码安装(开发模式)
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
cd skby_py
|
|
46
|
+
pip install -e .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 验证安装
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
python -c "import pyskby; print(pyskby.__version__)"
|
|
53
|
+
# 输出: 0.2.0
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## 2. 调用指令
|
|
59
|
+
|
|
60
|
+
### 2.1 Python API
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from pyskby.model import SKBYModel
|
|
64
|
+
|
|
65
|
+
# Faithful 模式(默认,与 Java 逐位对齐)
|
|
66
|
+
m = SKBYModel(
|
|
67
|
+
"data/liulan_fupan/params-testnew.csv", # 参数文件
|
|
68
|
+
"data/liulan_fupan/data-test.csv", # 驱动/时序数据
|
|
69
|
+
conservative=False,
|
|
70
|
+
)
|
|
71
|
+
m.run()
|
|
72
|
+
m.write_outlet_csv("outlet.csv") # 流域出口流量
|
|
73
|
+
m.write_qj_csv("QJ.csv") # 逐河道出流明细
|
|
74
|
+
|
|
75
|
+
# Conservative 守恒模式(修复运动波质量损失,≠ Java 基线)
|
|
76
|
+
m2 = SKBYModel("params.csv", "data.csv", conservative=True)
|
|
77
|
+
m2.run()
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### 2.2 命令行
|
|
81
|
+
|
|
82
|
+
pyskby 提供两种命令行入口,底层都调用同一套逻辑(`pyskby.cli:main`)。
|
|
83
|
+
|
|
84
|
+
**方式 A:安装后的 console script(推荐给使用者)**
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install pyskby # 安装后自动注册 skby-run 命令
|
|
88
|
+
skby-run liulan_fupan # faithful 模式(Java 参考/不守恒)
|
|
89
|
+
skby-run liulan_fupan --conservative # 守恒修复版
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`skby-run` 默认从**当前工作目录**查找 `sim/<sim>.groovy` 与 `data/`,因此使用者
|
|
93
|
+
在自己的算例目录(含 `sim/`、`data/`)下运行即可;也可用 `--root PATH` 指定根目录:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
cd my_case_study && skby-run liulan_fupan --root .
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
输出写到 `<root>/output/<sim>/out/outlet_py.csv` 与 `QJ_py.csv`。
|
|
100
|
+
|
|
101
|
+
**打包内置示例(安装后直接跑通)**
|
|
102
|
+
|
|
103
|
+
pyskby 在 wheel 中内置了示例算例 `liulan_fupan`。安装后用 `skby-example` 把示例
|
|
104
|
+
解包到当前目录,再用 `skby-run` 运行:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pip install pyskby
|
|
108
|
+
skby-example liulan_fupan # 复制 ./examples/{sim,data}/liulan_fupan
|
|
109
|
+
skby-run liulan_fupan --root examples # faithful 模式
|
|
110
|
+
skby-run liulan_fupan --root examples --conservative # 守恒模式
|
|
111
|
+
# 结果: ./examples/output/liulan_fupan/out/outlet_py.csv 与 QJ_py.csv
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
`skby-example` 不带参数则复制全部示例;已存在时跳过(加 `--force` 覆盖)。
|
|
115
|
+
|
|
116
|
+
> **关于 `--root` 与 `site-packages`**
|
|
117
|
+
> 安装后示例其实就在 `site-packages/pyskby/examples/`。`--root` 直接指向它
|
|
118
|
+
> (如 `--root $(python -c "import pyskby,os;print(os.path.dirname(pyskby.__file__))")/examples`)
|
|
119
|
+
> **也能算**,但输出会写到该安装目录下的 `.../examples/output/<sim>/out/`。不推荐:
|
|
120
|
+
> 会污染 pip 管理的安装目录(重装/升级会被清掉),且在无写权限环境下会报 `PermissionError`。
|
|
121
|
+
> 标准做法仍是 `skby-example` 把示例导出到你自己的工作目录再跑。
|
|
122
|
+
|
|
123
|
+
**方式 B:仓库内调试(`run.py`,加载本地源码)**
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
cd skby_py
|
|
127
|
+
python run.py liulan_fupan # 等价于 --root 指向仓库根
|
|
128
|
+
python run.py liulan_fupan --conservative
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`run.py` 会把自身所在目录(`skby_py`)插入 `sys.path` 最前,**强制优先加载本地
|
|
132
|
+
`pyskby` 源码**(而非已安装的 PyPI 副本)。因此调试时改动 `pyskby/` 下的代码,
|
|
133
|
+
`python run.py` 立即生效,不会误加载已 `pip install` 的版本。
|
|
134
|
+
|
|
135
|
+
也可以用模块方式调用(等价于安装后的 console script):
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
python -m pyskby.cli liulan_fupan --conservative --root /path/to/case
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### 2.3 可选水量平衡诊断
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
m.run(diag="diag.csv") # 落盘逐时步水量平衡
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
> 详细参数说明见仓库内 `skby_py/使用手册.md`(产汇流参数、水源/分洪/水库出流等)。
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 3. 打包发布指令
|
|
152
|
+
|
|
153
|
+
### 3.1 一次性安装发布工具
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
pip install --upgrade build twine
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### 3.2 构建分发包
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
cd skby_py
|
|
163
|
+
python -m build
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
会生成 `dist/pyskby-0.2.0-py3-none-any.whl`(wheel)和
|
|
167
|
+
`dist/pyskby-0.2.0.tar.gz`(sdist)。
|
|
168
|
+
|
|
169
|
+
### 3.3 发布到 PyPI
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# 方式一:只发布 wheel(不含 sdist 源码包,推荐)
|
|
173
|
+
twine upload dist/*.whl
|
|
174
|
+
|
|
175
|
+
# 方式二:同时发布 wheel + sdist
|
|
176
|
+
twine upload dist/*
|
|
177
|
+
|
|
178
|
+
# 先上传到测试服务器验证
|
|
179
|
+
twine upload --repository testpypi dist/*
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
> `twine upload` 会提示输入 PyPI 账号的 API Token(用户名填 `__token__`,密码填 token)。
|
|
183
|
+
|
|
184
|
+
### 3.4 发布后验证
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
pip install pyskby
|
|
188
|
+
python -c "import pyskby; print(pyskby.__version__)"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## 4. 升级发布版本
|
|
194
|
+
|
|
195
|
+
改版本号后需同步两处:
|
|
196
|
+
|
|
197
|
+
1. `skby_py/pyproject.toml` → `version = "x.y.z"`
|
|
198
|
+
2. `skby_py/pyskby/__init__.py` → `__version__ = "x.y.z"`
|
|
199
|
+
|
|
200
|
+
然后重新执行 §3.2 构建、§3.3 上传。
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## 5. 常见问题
|
|
205
|
+
|
|
206
|
+
- **发布名 vs 导入名**:PyPI 包名和 Python 导入名均为 `pyskby`(`import pyskby`)。
|
|
207
|
+
|
|
208
|
+
- **调试时改了 `pyskby/` 源码,`run.py` 用的还是旧代码?**
|
|
209
|
+
不会。`run.py` 在导入前把自身目录(`skby_py`)插到 `sys.path` 最前,因此
|
|
210
|
+
`python run.py ...` 永远加载**本地** `pyskby` 源码,即使系统里已 `pip install`
|
|
211
|
+
过正式版也优先本地。发布后的使用者用 `skby-run` 命令则加载其安装的副本。
|
|
212
|
+
|
|
213
|
+
- **`skby-run` 与 `python run.py` 的区别?**
|
|
214
|
+
二者调用同一套逻辑(`pyskby.cli:main`)。`skby-run` 是给**安装者**用的
|
|
215
|
+
console script(从 cwd 找算例);`run.py` 是给**开发者**调试用的入口
|
|
216
|
+
(默认把仓库根作为算例根,并强制加载本地源码)。
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyskby"
|
|
7
|
+
version = "0.2.1"
|
|
8
|
+
description = "SKBY spatially and temporally variable source-area hydrological model (Python port of STVSRM)"
|
|
9
|
+
readme = "readme.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "wly" },
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"hydrology",
|
|
17
|
+
"rainfall-runoff",
|
|
18
|
+
"watershed",
|
|
19
|
+
"runoff",
|
|
20
|
+
"source-area",
|
|
21
|
+
"infiltration",
|
|
22
|
+
"channel-routing",
|
|
23
|
+
"STVSRM",
|
|
24
|
+
"SKBY",
|
|
25
|
+
]
|
|
26
|
+
classifiers = [
|
|
27
|
+
"Development Status :: 4 - Beta",
|
|
28
|
+
"Intended Audience :: Science/Research",
|
|
29
|
+
"Intended Audience :: Developers",
|
|
30
|
+
"License :: OSI Approved :: MIT License",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3.10",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Topic :: Scientific/Engineering :: Hydrology",
|
|
36
|
+
"Topic :: Scientific/Engineering :: Atmospheric Science",
|
|
37
|
+
]
|
|
38
|
+
dependencies = [
|
|
39
|
+
"numpy",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
# Console entry points: `pip install pyskby` -> `skby-run` + `skby-example`
|
|
43
|
+
[project.scripts]
|
|
44
|
+
skby-run = "pyskby.cli:main"
|
|
45
|
+
skby-example = "pyskby.cli:copy_example"
|
|
46
|
+
|
|
47
|
+
[tool.setuptools.packages.find]
|
|
48
|
+
where = ["."]
|
|
49
|
+
include = ["pyskby*"]
|
|
50
|
+
|
|
51
|
+
# 随包分发的数据/文档文件(示例算例需打进 wheel,供 skby-example 解包)
|
|
52
|
+
[tool.setuptools.package-data]
|
|
53
|
+
pyskby = [
|
|
54
|
+
"*.py",
|
|
55
|
+
"examples/**/*",
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
# --- pytest: run the regression suite from the skby_py root ---
|
|
59
|
+
[tool.pytest.ini_options]
|
|
60
|
+
testpaths = ["autotest"]
|
|
61
|
+
pythonpath = ["."]
|
|
62
|
+
|
|
63
|
+
# --- ruff: lint + format (mirrors USGS pywatershed conventions) ---
|
|
64
|
+
[tool.ruff]
|
|
65
|
+
line-length = 100
|
|
66
|
+
target-version = "py310"
|
|
67
|
+
src = ["pyskby", "autotest"]
|
|
68
|
+
|
|
69
|
+
[tool.ruff.lint]
|
|
70
|
+
select = [
|
|
71
|
+
"E", # pycodestyle errors
|
|
72
|
+
"F", # pyflakes
|
|
73
|
+
"W", # pycodestyle warnings
|
|
74
|
+
"I", # isort
|
|
75
|
+
"B", # flake8-bugbear
|
|
76
|
+
"UP", # pyupgrade
|
|
77
|
+
]
|
|
78
|
+
ignore = [
|
|
79
|
+
"E501", # line too long (handled by formatter)
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
[tool.ruff.lint.isort]
|
|
83
|
+
known-first-party = ["pyskby"]
|
|
84
|
+
|
|
85
|
+
[tool.ruff.format]
|
|
86
|
+
quote-style = "double"
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""pyskby — SKBY (spatially and temporally variable source-area) hydrological model.
|
|
2
|
+
|
|
3
|
+
Pure-Python (numpy/pandas) port of STVSRM_old. The import name is ``pyskby``,
|
|
4
|
+
matching the PyPI package name.
|
|
5
|
+
|
|
6
|
+
Usage::
|
|
7
|
+
|
|
8
|
+
from pyskby.model import SKBYModel
|
|
9
|
+
m = SKBYModel(params_csv, data_csv)
|
|
10
|
+
m.run()
|
|
11
|
+
m.write_outlet_csv("outlet.csv")
|
|
12
|
+
|
|
13
|
+
All units follow the original OMS3/PRMS conventions (inches/feet/cms), with
|
|
14
|
+
conversion constants centralized in ``pyskby.units``.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
__author__ = "wly"
|
|
18
|
+
|
|
19
|
+
__version__ = "0.2.0"
|
|
20
|
+
|
|
21
|
+
from . import units
|
|
22
|
+
from .model import SKBYModel
|
|
23
|
+
|
|
24
|
+
__all__ = ["SKBYModel", "units", "__version__"]
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""Command-line entry points for the pyskby model.
|
|
2
|
+
|
|
3
|
+
Two console scripts are installed with the package:
|
|
4
|
+
|
|
5
|
+
* ``skby-run <sim> [--conservative] [--root PATH]`` — run a simulation case.
|
|
6
|
+
Reads ``sim/<sim>.groovy`` to learn which params/data CSVs to use, runs the
|
|
7
|
+
model, and writes ``output/<sim>/out/outlet_py.csv`` (plus ``QJ_py.csv``).
|
|
8
|
+
|
|
9
|
+
* ``skby-example [name]`` — copy the bundled example case(s) into the current
|
|
10
|
+
working directory (``examples/<sim>``) so they can be run with ``skby-run``.
|
|
11
|
+
|
|
12
|
+
Both are also runnable as a module::
|
|
13
|
+
|
|
14
|
+
python -m pyskby.cli liulan_fupan
|
|
15
|
+
python -m pyskby.cli --example liulan_fupan
|
|
16
|
+
|
|
17
|
+
Mimics ``java -jar RunModel.jar <sim_name> OFF``. The ``<sim_name>`` is resolved
|
|
18
|
+
against a project root, which defaults to the current working directory (so users
|
|
19
|
+
run the CLI from their own case-study folder that contains ``sim/`` and ``data/``).
|
|
20
|
+
Override with ``--root``.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
__author__ = "wly"
|
|
24
|
+
|
|
25
|
+
import argparse
|
|
26
|
+
import importlib.resources as _resources
|
|
27
|
+
import os
|
|
28
|
+
import re
|
|
29
|
+
import shutil
|
|
30
|
+
import sys
|
|
31
|
+
from pathlib import Path
|
|
32
|
+
|
|
33
|
+
from .model import SKBYModel
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _examples_dir() -> Path:
|
|
37
|
+
"""Filesystem path to the packaged example cases (works for both the source
|
|
38
|
+
tree and an installed wheel via importlib.resources)."""
|
|
39
|
+
return Path(str(_resources.files("pyskby").joinpath("examples")))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def copy_example(argv=None):
|
|
43
|
+
"""Console-script entry: ``skby-example [name]``.
|
|
44
|
+
|
|
45
|
+
Copies the bundled example(s) into ``./examples/<sim>`` of the current
|
|
46
|
+
working directory. With no ``name`` all examples are copied.
|
|
47
|
+
"""
|
|
48
|
+
parser = argparse.ArgumentParser(
|
|
49
|
+
prog="skby-example",
|
|
50
|
+
description="Copy bundled pyskby example case(s) into ./examples/.",
|
|
51
|
+
)
|
|
52
|
+
parser.add_argument(
|
|
53
|
+
"name",
|
|
54
|
+
nargs="?",
|
|
55
|
+
default=None,
|
|
56
|
+
help="example case name (defaults to all); e.g. liulan_fupan",
|
|
57
|
+
)
|
|
58
|
+
parser.add_argument(
|
|
59
|
+
"--force",
|
|
60
|
+
action="store_true",
|
|
61
|
+
help="overwrite an existing ./examples/<name> instead of skipping",
|
|
62
|
+
)
|
|
63
|
+
args = parser.parse_args(argv)
|
|
64
|
+
|
|
65
|
+
src_root = _examples_dir()
|
|
66
|
+
if not src_root.is_dir():
|
|
67
|
+
print(f"error: no bundled examples found at {src_root}", file=sys.stderr)
|
|
68
|
+
return 1
|
|
69
|
+
|
|
70
|
+
sim_dir = src_root.joinpath("sim")
|
|
71
|
+
data_dir = src_root.joinpath("data")
|
|
72
|
+
|
|
73
|
+
def _copy_one(sim_name):
|
|
74
|
+
# Preserve the bundled layout: examples/sim/<name>.groovy and
|
|
75
|
+
# examples/data/<name>/..., so `skby-run <name> --root examples` works.
|
|
76
|
+
dst_sim = os.path.join(os.getcwd(), "examples", "sim")
|
|
77
|
+
dst_data = os.path.join(os.getcwd(), "examples", "data", sim_name)
|
|
78
|
+
gsrc = sim_dir.joinpath(f"{sim_name}.groovy")
|
|
79
|
+
dsrc = data_dir.joinpath(sim_name)
|
|
80
|
+
if (not gsrc.is_file()) and (not dsrc.is_dir()):
|
|
81
|
+
print(f"warning: example '{sim_name}' not found", file=sys.stderr)
|
|
82
|
+
return
|
|
83
|
+
if gsrc.is_file():
|
|
84
|
+
if os.path.exists(os.path.join(dst_sim, f"{sim_name}.groovy")) and not args.force:
|
|
85
|
+
print(f"skip examples/sim/{sim_name}.groovy (exists; --force to overwrite)")
|
|
86
|
+
else:
|
|
87
|
+
os.makedirs(dst_sim, exist_ok=True)
|
|
88
|
+
shutil.copy2(gsrc, os.path.join(dst_sim, f"{sim_name}.groovy"))
|
|
89
|
+
if dsrc.is_dir():
|
|
90
|
+
if os.path.exists(dst_data) and not args.force:
|
|
91
|
+
print(f"skip examples/data/{sim_name} (exists; --force to overwrite)")
|
|
92
|
+
else:
|
|
93
|
+
shutil.copytree(dsrc, dst_data, dirs_exist_ok=True)
|
|
94
|
+
print(f"copied example '{sim_name}' -> ./examples/")
|
|
95
|
+
|
|
96
|
+
if args.name:
|
|
97
|
+
_copy_one(args.name)
|
|
98
|
+
else:
|
|
99
|
+
names = sorted(
|
|
100
|
+
p.stem for p in sim_dir.glob("*.groovy") if p.is_file()
|
|
101
|
+
)
|
|
102
|
+
if not names:
|
|
103
|
+
print("no example cases found", file=sys.stderr)
|
|
104
|
+
return 1
|
|
105
|
+
for n in names:
|
|
106
|
+
_copy_one(n)
|
|
107
|
+
print("done. Now run: skby-run <name> --root examples")
|
|
108
|
+
return 0
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def parse_groovy(root, sim_name):
|
|
113
|
+
"""Read sim/<sim>.groovy to find the params/data CSV filenames it references."""
|
|
114
|
+
gpath = os.path.join(root, "sim", f"{sim_name}.groovy")
|
|
115
|
+
if not os.path.exists(gpath):
|
|
116
|
+
raise FileNotFoundError(f"sim file not found: {gpath}")
|
|
117
|
+
params_file = "params-testnew.csv"
|
|
118
|
+
data_file = "data-test.csv"
|
|
119
|
+
with open(gpath, "r", encoding="utf-8") as f:
|
|
120
|
+
txt = f.read()
|
|
121
|
+
m = re.search(r'params.*?"([^"]+\.csv)"', txt)
|
|
122
|
+
if m:
|
|
123
|
+
params_file = m.group(1)
|
|
124
|
+
m = re.search(r'data.*?"([^"]+\.csv)"', txt)
|
|
125
|
+
if m:
|
|
126
|
+
data_file = m.group(1)
|
|
127
|
+
return params_file, data_file
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _resolve(root, sim, filename):
|
|
131
|
+
"""Resolve a params/data path: absolute, root-relative, or data/<sim>/-relative."""
|
|
132
|
+
if os.path.isabs(filename):
|
|
133
|
+
return filename
|
|
134
|
+
p = os.path.join(root, filename)
|
|
135
|
+
if os.path.exists(p):
|
|
136
|
+
return p
|
|
137
|
+
alt = os.path.join(root, "data", sim, os.path.basename(filename))
|
|
138
|
+
if os.path.exists(alt):
|
|
139
|
+
return alt
|
|
140
|
+
return p # fall back to the root-relative guess (will surface a clear error)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def main(argv=None):
|
|
144
|
+
parser = argparse.ArgumentParser(
|
|
145
|
+
prog="skby-run",
|
|
146
|
+
description="Run the pyskby hydrological model for a given simulation case.",
|
|
147
|
+
)
|
|
148
|
+
parser.add_argument("sim", help="simulation name (matches sim/<sim>.groovy)")
|
|
149
|
+
parser.add_argument(
|
|
150
|
+
"--conservative",
|
|
151
|
+
action="store_true",
|
|
152
|
+
help="mass-conserving routing (Newton-implicit non-linear reservoirs); "
|
|
153
|
+
"default is faithful (Java-reference behaviour, not water-conserving).",
|
|
154
|
+
)
|
|
155
|
+
parser.add_argument(
|
|
156
|
+
"--root",
|
|
157
|
+
default=os.getcwd(),
|
|
158
|
+
help="project root containing sim/ and data/ (default: current directory)",
|
|
159
|
+
)
|
|
160
|
+
args = parser.parse_args(argv)
|
|
161
|
+
|
|
162
|
+
root = os.path.abspath(args.root)
|
|
163
|
+
params_file, data_file = parse_groovy(root, args.sim)
|
|
164
|
+
ppath = _resolve(root, args.sim, params_file)
|
|
165
|
+
dpath = _resolve(root, args.sim, data_file)
|
|
166
|
+
|
|
167
|
+
print(f"params: {ppath}")
|
|
168
|
+
print(f"data: {dpath}")
|
|
169
|
+
print(f"mode: {'conservative (守恒修复版)' if args.conservative else 'faithful (Java参考/不守恒)'}")
|
|
170
|
+
|
|
171
|
+
model = SKBYModel(ppath, dpath, conservative=args.conservative)
|
|
172
|
+
model.run()
|
|
173
|
+
|
|
174
|
+
out_dir = os.path.join(root, "output", args.sim, "out")
|
|
175
|
+
os.makedirs(out_dir, exist_ok=True)
|
|
176
|
+
out_path = os.path.join(out_dir, "outlet_py.csv")
|
|
177
|
+
model.write_outlet_csv(out_path)
|
|
178
|
+
print(f"wrote {out_path}")
|
|
179
|
+
qj_path = os.path.join(out_dir, "QJ_py.csv")
|
|
180
|
+
model.write_qj_csv(qj_path)
|
|
181
|
+
print(f"wrote {qj_path}")
|
|
182
|
+
print(f"basin_cfs[0] = {model.basin_cfs_series[0]:.6f}")
|
|
183
|
+
return 0
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
if __name__ == "__main__":
|
|
187
|
+
sys.exit(main())
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Component interface contract (phase-2 pluggable-process design).
|
|
2
|
+
|
|
3
|
+
Per doc/升级改造说明.md §4.3, the model components will become interchangeable
|
|
4
|
+
so that algorithms (e.g. infiltration, PET) can be swapped without disturbing the
|
|
5
|
+
overall assembly -- mirroring USGS pywatershed's "replaceable conceptualization".
|
|
6
|
+
|
|
7
|
+
This module defines the shared contract. Existing components (components.py) are
|
|
8
|
+
NOT yet required to inherit from it; this is the intended target interface and a
|
|
9
|
+
safe place to evolve toward. New/refactored components should subclass
|
|
10
|
+
``BaseComponent`` and implement ``initialize``/``run``.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
__author__ = "wly"
|
|
14
|
+
|
|
15
|
+
from abc import ABC, abstractmethod
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class BaseComponent(ABC):
|
|
21
|
+
"""Common interface for every model process component.
|
|
22
|
+
|
|
23
|
+
Subclasses receive the parsed parameter object in ``__init__`` and expose a
|
|
24
|
+
``run`` method that advances one time step. State is held on the instance so
|
|
25
|
+
that multi-step runs preserve cross-step memory (e.g. channel/groundwater
|
|
26
|
+
states), matching the Java per-component state model.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
#: logical name used in diagnostics / registry (e.g. "HRUOut")
|
|
30
|
+
name: str = "component"
|
|
31
|
+
|
|
32
|
+
def __init__(self, cfg):
|
|
33
|
+
self.cfg = cfg
|
|
34
|
+
|
|
35
|
+
@abstractmethod
|
|
36
|
+
def initialize(self) -> None:
|
|
37
|
+
"""One-time setup before the simulation loop (allocate state arrays)."""
|
|
38
|
+
raise NotImplementedError
|
|
39
|
+
|
|
40
|
+
@abstractmethod
|
|
41
|
+
def run(self, *args, **kwargs) -> object:
|
|
42
|
+
"""Advance one time step; return the component's contribution(s)."""
|
|
43
|
+
raise NotImplementedError
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def nhru(self) -> int:
|
|
47
|
+
return int(self.cfg.get("nhru", 1))
|