Skip to content

Commit

Permalink
OK, paper is down, let's begin to complete the document
Browse files Browse the repository at this point in the history
  • Loading branch information
chooron committed Dec 9, 2024
1 parent 6af85e9 commit 3d23c6a
Show file tree
Hide file tree
Showing 24 changed files with 532 additions and 112 deletions.
9 changes: 9 additions & 0 deletions Bugs_and_Todo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## Bugs
- [ ] HydroModel cannot used as a component in another model

## TODO

- [ ] Add more examples
- [ ] Using the NamedTupleAdaptor, and remove the convert_to_ntp kwargs in component running
- [ ] Write more error messages
- [ ] macro building
41 changes: 40 additions & 1 deletion Manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

julia_version = "1.10.4"
manifest_format = "2.0"
project_hash = "eec1c26075e9afae150ae4fb562f8f2fdea39275"
project_hash = "de7e1a3bf89c00139c5d6afa1937bd039d9ad985"

[[deps.ADTypes]]
git-tree-sha1 = "72af59f5b8f09faee36b4ec48e014a79210f2f4f"
Expand Down Expand Up @@ -219,6 +219,12 @@ git-tree-sha1 = "0157e592151e39fa570645e2b2debcdfb8a0f112"
uuid = "00ebfdb7-1f24-5e51-bd34-a7502290713f"
version = "3.4.3"

[[deps.CSV]]
deps = ["CodecZlib", "Dates", "FilePathsBase", "InlineStrings", "Mmap", "Parsers", "PooledArrays", "PrecompileTools", "SentinelArrays", "Tables", "Unicode", "WeakRefStrings", "WorkerUtilities"]
git-tree-sha1 = "deddd8725e5e1cc49ee205a1964256043720a6c3"
uuid = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
version = "0.10.15"

[[deps.Cassette]]
git-tree-sha1 = "f8764df8d9d2aec2812f009a1ac39e46c33354b8"
uuid = "7057c7e9-c182-5462-911a-8362d720325c"
Expand Down Expand Up @@ -246,6 +252,12 @@ git-tree-sha1 = "05ba0d07cd4fd8b7a39541e31a7b0254704ea581"
uuid = "fb6a15b2-703c-40df-9091-08a04967cfa9"
version = "0.1.13"

[[deps.CodecZlib]]
deps = ["TranscodingStreams", "Zlib_jll"]
git-tree-sha1 = "bce6804e5e6044c6daab27bb533d1295e4a2e759"
uuid = "944b1d66-785c-5afd-91f1-9de20f533193"
version = "0.7.6"

[[deps.Combinatorics]]
git-tree-sha1 = "08c8b6831dc00bfea825826be0bc8336fc369860"
uuid = "861a8166-3701-5b0c-9a16-15d98fcdc6aa"
Expand Down Expand Up @@ -684,6 +696,17 @@ version = "1.1.1"
ReverseDiff = "37e2e3b7-166d-5795-8a7a-e32c996b4267"
Tracker = "9f7883ad-71c0-57eb-9f7f-b5c9e6d3789c"

[[deps.FilePathsBase]]
deps = ["Compat", "Dates"]
git-tree-sha1 = "7878ff7172a8e6beedd1dea14bd27c3c6340d361"
uuid = "48062228-2e41-5def-b9a4-89aafe57970f"
version = "0.9.22"
weakdeps = ["Mmap", "Test"]

[deps.FilePathsBase.extensions]
FilePathsBaseMmapExt = "Mmap"
FilePathsBaseTestExt = "Test"

[[deps.FileWatching]]
uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee"

Expand Down Expand Up @@ -2523,6 +2546,11 @@ weakdeps = ["PDMats"]
[deps.Tracker.extensions]
TrackerPDMatsExt = "PDMats"

[[deps.TranscodingStreams]]
git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742"
uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa"
version = "0.11.3"

[[deps.TriangularSolve]]
deps = ["CloseOpenIntervals", "IfElse", "LayoutPointers", "LinearAlgebra", "LoopVectorization", "Polyester", "Static", "VectorizationBase"]
git-tree-sha1 = "be986ad9dac14888ba338c2554dcfec6939e1393"
Expand Down Expand Up @@ -2597,6 +2625,12 @@ git-tree-sha1 = "8351f8d73d7e880bfc042a8b6922684ebeafb35c"
uuid = "19fa3120-7c27-5ec5-8db8-b0b0aa330d6f"
version = "0.2.0"

[[deps.WeakRefStrings]]
deps = ["DataAPI", "InlineStrings", "Parsers"]
git-tree-sha1 = "b1be2855ed9ed8eac54e5caff2afcdb442d52c23"
uuid = "ea10d353-3f73-51f8-a26c-33c1cb351aa5"
version = "1.4.2"

[[deps.WeightInitializers]]
deps = ["ArgCheck", "ConcreteStructs", "GPUArraysCore", "LinearAlgebra", "Random", "SpecialFunctions", "Statistics"]
git-tree-sha1 = "0b935265795ccccf12bc89e693b6380934451780"
Expand All @@ -2619,6 +2653,11 @@ version = "1.0.4"
Metal = "dde4c033-4e86-420c-a63e-0dd931031962"
oneAPI = "8f75cd03-7ff8-4ecb-9b8f-daf728133b1b"

[[deps.WorkerUtilities]]
git-tree-sha1 = "cd1659ba0d57b71a464a29e64dbc67cfe83d54e7"
uuid = "76eceee3-57b5-4d4a-8e66-0e911cebbf60"
version = "1.6.1"

[[deps.Zlib_jll]]
deps = ["Libdl"]
uuid = "83775a58-1f1d-513f-b197-d71354ab007a"
Expand Down
9 changes: 5 additions & 4 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ version = "0.1.0"

[deps]
Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697"
CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b"
ComponentArrays = "b0b7db55-cfe3-40fc-9ded-d10e2dbeff66"
DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0"
DataInterpolations = "82cc6244-b520-54b8-b5a6-8a565e85f1d0"
Expand Down Expand Up @@ -43,9 +44,9 @@ Accessors = "0.1"
Aqua = "0.8"
CSV = "0.10"
ComponentArrays = "0.15"
Dates = "1"
DataFrames = "1"
DataInterpolations = "6"
Dates = "1"
ForwardDiff = "0.10"
Graphs = "1"
Integrals = "4"
Expand All @@ -67,15 +68,15 @@ Reexport = "1"
RuntimeGeneratedFunctions = "0.5"
SciMLBase = "2"
SciMLSensitivity = "7"
Statistics = "1"
SparseArrays = "1"
StableRNGs = "1"
Statistics = "1"
SymbolicUtils = "3"
Symbolics = "6"
TOML = "1"
Test = "1"
Zygote = "0.6"
julia = "1.10"
Test = "1"
TOML = "1"

[extras]
Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595"
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,5 @@ Pkg.add("HydroModels")
## Contributing

HydroModels.jl is an open-source project available on Github. We welcome contributions from the community to help advance deep learning applications in hydrology.


4 changes: 2 additions & 2 deletions docs/Manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

julia_version = "1.10.4"
manifest_format = "2.0"
project_hash = "ad4b8e8aabb6dad13d819587f280889b1bd2f545"
project_hash = "620786bb65c2992bc73165cb134818fb2070b24e"

[[deps.ADTypes]]
git-tree-sha1 = "72af59f5b8f09faee36b4ec48e014a79210f2f4f"
Expand Down Expand Up @@ -1056,7 +1056,7 @@ uuid = "e33a78d0-f292-5ffc-b300-72abe9b543c8"
version = "2.11.2+1"

[[deps.HydroModels]]
deps = ["Accessors", "ComponentArrays", "DataFrames", "DataInterpolations", "Dates", "ForwardDiff", "Graphs", "Integrals", "IterTools", "LinearAlgebra", "LinearSolve", "Lux", "LuxCore", "ModelingToolkit", "NNlib", "NamedTupleTools", "Optimization", "OptimizationBBO", "OptimizationOptimisers", "OrdinaryDiffEq", "ProgressMeter", "Random", "Reexport", "RuntimeGeneratedFunctions", "SciMLBase", "SciMLSensitivity", "SparseArrays", "StableRNGs", "Statistics", "SymbolicUtils", "Symbolics", "TOML", "Zygote"]
deps = ["Accessors", "CSV", "ComponentArrays", "DataFrames", "DataInterpolations", "Dates", "ForwardDiff", "Graphs", "Integrals", "IterTools", "LinearAlgebra", "LinearSolve", "Lux", "LuxCore", "ModelingToolkit", "NNlib", "NamedTupleTools", "Optimization", "OptimizationBBO", "OptimizationOptimisers", "OrdinaryDiffEq", "ProgressMeter", "Random", "Reexport", "RuntimeGeneratedFunctions", "SciMLBase", "SciMLSensitivity", "SparseArrays", "StableRNGs", "Statistics", "SymbolicUtils", "Symbolics", "TOML", "Zygote"]
path = "E:\\JlCode\\HydroModels"
uuid = "7e3cde01-c141-467b-bff6-5350a0af19fc"
version = "0.1.0"
Expand Down
2 changes: 2 additions & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Lux = "b2108857-7c20-44ae-9111-449ecde12c47"
LuxCore = "bb33d45b-7691-41d6-9220-0943567d0623"
Measures = "442fdcdd-2543-5da2-b0f3-8c86c306513e"
ModelingToolkit = "961ee093-0014-501f-94e3-6117800e7a78"
NamedTupleTools = "d9ec5142-1e00-5aa0-9d6a-321866360f50"
Optimization = "7f7a1694-90dd-40f0-9382-eb1efda571ba"
OptimizationBBO = "3e6eede4-6085-4f62-9a71-46d9bc1eb92b"
OptimizationOptimisers = "42dfb2eb-d2b4-4451-abcd-913932933ac1"
Expand All @@ -21,3 +22,4 @@ SciMLSensitivity = "1ed8b502-d754-442c-8d5d-10ac956f44a1"
StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Symbolics = "0c5d862f-8b57-4792-8d23-62f2024744c7"
Zygote = "e88e6eb3-aa80-5325-afca-941959d7151f"
Binary file removed docs/docs.rar
Binary file not shown.
6 changes: 5 additions & 1 deletion docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ makedocs(
"Tutorials" => [
"Run a Bucket Model" => "tutorials/run_a_bucket.md",
"Run ExpHydro Model" => "tutorials/run_a_exphydro_model.md"
]
],
"Extent Content" => [
"Modeling Framework Comparisons" => "docs/src/extent/framework comparision - en.md",
],

],
# 其他选项
checkdocs = :none,
Expand Down
142 changes: 142 additions & 0 deletions docs/src/extent/framework comparision - en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Comparing Programming Styles for Custom Models in superflexpy, MARRMoT, and HydroModels.jl

This content corresponds to Section 4.1 of the paper, where we discuss the differences in programming styles for custom model implementation across these three software libraries. Using the GR4J model implementation as an example, we compare the programming styles for customizing model computation formulas.

## [Superflexpy](https://github.com/dalmo1991/superflexPy/tree/master)

Superflexpy is a Python implementation based on the SUPERFLEX concept. For implementing hydrological model computations, this framework typically uses Python syntax to express model calculation formulas, as shown below.

```python
@staticmethod
def _flux_function_python(S, S0, ind, P, x1, alpha, beta, ni, PET, dt):
if ind is None:
return (
[
P * (1 - (S / x1) ** alpha), # Ps
-PET * (2 * (S / x1) - (S / x1) ** alpha), # Evaporation
-((x1 ** (1 - beta)) / ((beta - 1) * dt)) * (ni ** (beta - 1)) * (S**beta), # Perc
],
0.0,
S0 + P * (1 - (S / x1) ** alpha) * dt,
)
else:
return (
[
P[ind] * (1 - (S / x1[ind]) ** alpha[ind]), # Ps
-PET[ind] * (2 * (S / x1[ind]) - (S / x1[ind]) ** alpha[ind]), # Evaporation
-((x1[ind] ** (1 - beta[ind])) / ((beta[ind] - 1) * dt[ind]))
* (ni[ind] ** (beta[ind] - 1))
* (S ** beta[ind]), # Perc
],
0.0,
S0 + P[ind] * (1 - (S / x1[ind]) ** alpha[ind]) * dt[ind],
[
-(P[ind] * alpha[ind] / x1[ind]) * ((S / x1[ind]) ** (alpha[ind] - 1)),
-(PET[ind] / x1[ind]) * (2 - alpha[ind] * ((S / x1[ind]) ** (alpha[ind] - 1))),
-beta[ind]
* ((x1[ind] ** (1 - beta[ind])) / ((beta[ind] - 1) * dt[ind]))
* (ni[ind] ** (beta[ind] - 1))
* (S ** (beta[ind] - 1)),
],
)

@staticmethod
@nb.jit(
"Tuple((UniTuple(f8, 3), f8, f8, UniTuple(f8, 3)))"
"(optional(f8), f8, i4, f8[:], f8[:], f8[:], f8[:], f8[:], f8[:], f8[:])",
nopython=True,
)
def _flux_function_numba(S, S0, ind, P, x1, alpha, beta, ni, PET, dt):
return (
(
P[ind] * (1 - (S / x1[ind]) ** alpha[ind]), # Ps
-PET[ind] * (2 * (S / x1[ind]) - (S / x1[ind]) ** alpha[ind]), # Evaporation
-((x1[ind] ** (1 - beta[ind])) / ((beta[ind] - 1) * dt[ind]))
* (ni[ind] ** (beta[ind] - 1))
* (S ** beta[ind]), # Perc
),
0.0,
S0 + P[ind] * (1 - (S / x1[ind]) ** alpha[ind]) * dt[ind],
(
-(P[ind] * alpha[ind] / x1[ind]) * ((S / x1[ind]) ** (alpha[ind] - 1)),
-(PET[ind] / x1[ind]) * (2 - alpha[ind] * ((S / x1[ind]) ** (alpha[ind] - 1))),
-beta[ind]
* ((x1[ind] ** (1 - beta[ind])) / ((beta[ind] - 1) * dt[ind]))
* (ni[ind] ** (beta[ind] - 1))
* (S ** (beta[ind] - 1)),
),
)
```

As shown, when implementing the GR4J production module calculations [origin code](https://github.com/dalmo1991/superflexPy/blob/master/superflexpy/implementation/elements/gr4j.py), the superflexpy framework requires separate construction of numpy and numba computation code. The function must specify hydrological fluxes `Ps`, `Evaporation`, `Perc`, and state variable balance equations. This approach couples multiple computational process implementations in a single code block, resulting in reduced code reusability (only reusable at the module level) and increased code volume and redundancy.

## [MARRMoT](https://github.com/wknoben/MARRMoT)

MARRMoT is a hydrological modeling framework developed in MATLAB. This framework constructs model types separately based on different hydrological models, storing the calculation formulas and state change equations involved. The simplified implementation code for the GR4J model in this framework is shown below.


```matlab
function [dS, fluxes] = model_fun(obj, S)
% definitions
...
% fluxes functions
flux_pn = max(P-Ep,0);
flux_en = max(Ep-P,0);
flux_ef = P - flux_pn;
flux_ps = saturation_4(S1,x1,flux_pn);
flux_es = evap_11(S1,x1,flux_en);
flux_perc = percolation_3(S1,x1);
flux_q9 = route(.9.*(flux_pn - flux_ps + flux_perc), uh_q9);
flux_q1 = route(.1.*(flux_pn - flux_ps + flux_perc), uh_q1);
flux_fr = recharge_2(3.5,S2,x3,x2);
flux_fq = flux_fr;
flux_qr = baseflow_3(S2,x3);
flux_qt = flux_qr + max(flux_q1 + flux_fq,0);
% this flux is not included in original MARRMoT,
% but it is useful to calculate the water balance
flux_ex = flux_fr + max(flux_q1 + flux_fq,0) - flux_q1;
% stores ODEs
dS1 = flux_ps - flux_es - flux_perc;
dS2 = flux_q9 + flux_fr - flux_qr;
% outputs
dS = [dS1 dS2];
fluxes = [flux_pn, flux_en, flux_ef, flux_ps, flux_es,...
flux_perc, flux_q9, flux_q1, flux_fr, flux_fq,...
flux_qr, flux_qt, flux_ex];
end
```

As evident, the MARRMoT framework expresses each Flux calculation formula through functions, allowing users to directly call functions for calculations (according to provided documentation). This approach significantly simplifies code writing requirements compared to superflexpy. However, MARRMoT has poor support for module reusability and cannot be reused like superflexpy. Additionally, combining variable assignments, flux calculations, and state change equations in one function results in poor decoupling of computational content.

## HydroModels.jl

```julia
#* define the production store
prod_funcs = [
HydroFlux([prcp, ep] => [pn, en], exprs=[prcp - min(prcp, ep), ep - min(prcp, ep)]),
HydroFlux([pn, soilwater] => [ps], [x1], exprs=[max(0.0, pn * (1 - (soilwater / x1)^2))]),
HydroFlux([en, soilwater] => [es], [x1], exprs=[en * (2 * soilwater / x1 - (soilwater / x1)^2)]),
HydroFlux([soilwater] => [perc], [x1], exprs=[((x1)^(-4)) / 4 * ((4 / 9)^(4)) * (soilwater^5)]),
HydroFlux([pn, ps, perc] => [pr], exprs=[pn - ps + perc]),
HydroFlux([pr] => [slowflow, fastflow], exprs=[0.9 * pr, 0.1 * pr]),
]
prod_dfuncs = [StateFlux([ps] => [es, perc], soilwater)]
prod_ele = HydroBucket(name=:gr4j_prod, funcs=prod_funcs, dfuncs=prod_dfuncs)

#* uh function
uh_flux_1 = HydroModels.UnitHydrograph([slowflow] => [slowflow_routed], x4, uhfunc=HydroModels.UHFunction(:UH_1_HALF), solvetype=:SPARSE)
uh_flux_2 = HydroModels.UnitHydrograph([fastflow] => [fastflow_routed], x4, uhfunc=HydroModels.UHFunction(:UH_2_FULL), solvetype=:SPARSE)

#* define the routing store
rst_funcs = [
HydroFlux([routingstore] => [exch], [x2, x3], exprs=[x2 * abs(routingstore / x3)^3.5]),
HydroFlux([routingstore, slowflow, exch] => [routedflow], [x3], exprs=[x3^(-4) / 4 * (routingstore + slowflow + exch)^5]),
HydroFlux([routedflow, fastflow_routed, exch] => [flow], exprs=[routedflow + max(fastflow_routed + exch, 0.0)]),
]
rst_dfuncs = [StateFlux([exch, slowflow] => [routedflow], routingstore)]
rst_ele = HydroBucket(name=:gr4j_rst, funcs=rst_funcs, dfuncs=rst_dfuncs)

gr4j_model = HydroModel(name=:gr4j, components=[prod_ele, uh_flux_1, uh_flux_2, rst_ele])
```

Returning to the GR4J model implementation in HydroModels.jl framework, we sequentially define the production store, unit hydrograph, and routing store. In this process, the model can achieve reusability while maintaining variable correspondence. The construction process includes instantiation of classes such as HydroFlux, StateFlux, UnitHydrograph, HydroModel, and HydroBucket. Unlike the previous two frameworks that use functional construction, these class instantiations express calculation formulas through symbolic programming. After model construction, calculations are performed through input data and parameters, demonstrating a design philosophy that completely decouples model structure and formulas from parameters and data, achieving high reusability.
Loading

0 comments on commit 3d23c6a

Please sign in to comment.