HBV-Lab 0.1.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.
HBV_Lab/uncertainty.py ADDED
@@ -0,0 +1,364 @@
1
+ class uncertainty:
2
+
3
+ def evaluate_uncertainty(self, n_runs=10000, objective='NSE', save_best=10,
4
+ plot_results=True, verbose=True, seed=None, narrow_percent=None,
5
+ exclude_warmup_in_plots=True):
6
+ """
7
+ Perform uncertainty analysis on an HBV model by sampling from parameter ranges.
8
+
9
+ Parameters:
10
+ -----------
11
+ self : HBVmodel
12
+ The HBV model instance to analyze (typically after calibration)
13
+ n_runs : int, default 10000
14
+ Number of model runs with different parameter sets
15
+ objective : str, default 'NSE'
16
+ Objective function to evaluate model performance. Options are:
17
+ - 'NSE': Nash-Sutcliffe Efficiency (higher is better)
18
+ - 'KGE': Kling-Gupta Efficiency (higher is better)
19
+ - 'RMSE': Root Mean Square Error (lower is better)
20
+ - 'MAE': Mean Absolute Error (lower is better)
21
+ save_best : int, default 10
22
+ Number of best parameter sets to save
23
+ plot_results : bool, default True
24
+ Whether to plot the results after analysis
25
+ verbose : bool, default True
26
+ Whether to print progress information
27
+ seed : int, optional
28
+ Random seed for reproducibility
29
+ narrow_percent : float, optional
30
+ If provided, narrows the parameter search space to this percentage around the default values
31
+ exclude_warmup_in_plots : bool, default True
32
+ Whether to exclude the warmup period from plots
33
+
34
+ Returns:
35
+ --------
36
+ dict
37
+ Dictionary containing the best parameter sets, their performance metrics,
38
+ and uncertainty statistics
39
+ """
40
+ import numpy as np
41
+ import pandas as pd
42
+ import matplotlib.pyplot as plt
43
+ import time
44
+ import copy
45
+ from tqdm.auto import tqdm
46
+
47
+ if self.data is None:
48
+ raise ValueError("No data loaded. Use load_data() method first.")
49
+
50
+ # Check if observed discharge data is available
51
+ if (self.column_names['obs_q'] is None or
52
+ self.column_names['obs_q'] not in self.data.columns):
53
+ raise ValueError("Observed discharge data is required for uncertainty analysis.")
54
+
55
+ # Set random seed for reproducibility if provided
56
+ if seed is not None:
57
+ np.random.seed(seed)
58
+
59
+ # Store the initial states and parameters to restore later
60
+ initial_states = copy.deepcopy(self.states)
61
+ original_params = copy.deepcopy(self.params)
62
+
63
+ # Extract observed discharge data
64
+ obs_q = self.data[self.column_names['obs_q']].values
65
+
66
+ # Get valid indices (where obs_q is not NaN)
67
+ valid_idx = ~np.isnan(obs_q)
68
+ if np.sum(valid_idx) == 0:
69
+ raise ValueError("No valid observed discharge values found.")
70
+
71
+ # Create flat parameter list for sampling
72
+ param_info = []
73
+ for group_name, group in self.params.items():
74
+ for param_name, param_data in group.items():
75
+ param_info.append({
76
+ 'group': group_name,
77
+ 'name': param_name,
78
+ 'min': param_data['min'],
79
+ 'max': param_data['max'],
80
+ 'default': param_data['default']
81
+ })
82
+ # Narrow ranges around the defaults (best fit) if narrow_percent is specified
83
+ if narrow_percent is not None:
84
+ for p in param_info:
85
+ best_val = p['default']
86
+ full_range = p['max'] - p['min']
87
+ delta = full_range * narrow_percent
88
+
89
+ new_min = max(p['min'], best_val - delta)
90
+ new_max = min(p['max'], best_val + delta)
91
+
92
+ p['min'] = new_min
93
+ p['max'] = new_max
94
+ if verbose:
95
+ print(f"Narrowed range {p['group']}_{p['name']}: {new_min:.4f} to {new_max:.4f}")
96
+
97
+ # Helper function to create parameter dictionary from sampled values
98
+ def create_param_dict(flat_params):
99
+ param_dict = {group_name: {} for group_name in set(p['group'] for p in param_info)}
100
+
101
+ for i, p in enumerate(param_info):
102
+ if p['name'] not in param_dict[p['group']]:
103
+ param_dict[p['group']][p['name']] = {}
104
+
105
+ param_dict[p['group']][p['name']]['min'] = p['min']
106
+ param_dict[p['group']][p['name']]['max'] = p['max']
107
+ param_dict[p['group']][p['name']]['default'] = flat_params[i]
108
+
109
+ return param_dict
110
+
111
+ # Evaluate model with a given parameter set
112
+ def evaluate_model(params):
113
+ # Create parameter dictionary
114
+ param_dict = create_param_dict(params)
115
+
116
+ # Update model parameters
117
+ self.params = param_dict
118
+
119
+ # Reset states
120
+ self.states = copy.deepcopy(initial_states)
121
+
122
+ # Run the model
123
+ self.run(verbose=False) # Setting verbose=False to reduce output clutter
124
+
125
+ # Calculate performance metrics
126
+ self.calculate_performance_metrics(verbose=False) # Setting verbose=False to reduce output clutter
127
+
128
+ # Return the specified objective
129
+ if objective == 'NSE':
130
+ return self.performance_metrics['NSE']
131
+ elif objective == 'KGE':
132
+ return self.performance_metrics['KGE']
133
+ elif objective == 'RMSE':
134
+ return -self.performance_metrics['RMSE'] # Negative for minimization
135
+ elif objective == 'MAE':
136
+ return -self.performance_metrics['MAE'] # Negative for minimization
137
+ else:
138
+ raise ValueError(f"Unknown objective function: {objective}")
139
+
140
+ # Prepare to store results
141
+ n_params = len(param_info)
142
+ results = {
143
+ 'parameters': np.zeros((n_runs, n_params)),
144
+ 'performance': np.zeros(n_runs)
145
+ }
146
+
147
+ # Start timing
148
+ start_time = time.time()
149
+
150
+ if verbose:
151
+ print(f"Starting uncertainty analysis with {n_runs} runs...")
152
+ print(f"Sampling {n_params} parameters uniformly across their ranges")
153
+ print(f"Evaluating with {objective} as the objective function")
154
+
155
+ # Run Monte Carlo simulations
156
+ for i in tqdm(range(n_runs)):
157
+ # Sample parameters uniformly from their ranges
158
+ sampled_params = np.array([np.random.uniform(p['min'], p['max']) for p in param_info])
159
+
160
+ # Store parameters
161
+ results['parameters'][i, :] = sampled_params
162
+
163
+ # Evaluate model and store performance
164
+ results['performance'][i] = evaluate_model(sampled_params)
165
+
166
+ # Sort results by performance (descending order)
167
+ sort_indices = np.argsort(-results['performance'])
168
+ sorted_performance = results['performance'][sort_indices]
169
+ sorted_parameters = results['parameters'][sort_indices]
170
+
171
+ # Get the best parameter sets
172
+ best_indices = sort_indices[:save_best]
173
+ best_performance = results['performance'][best_indices]
174
+ best_parameters = results['parameters'][best_indices]
175
+
176
+ # Save best parameter sets
177
+ best_param_sets = []
178
+ for i in range(save_best):
179
+ param_set = create_param_dict(best_parameters[i])
180
+ best_param_sets.append({
181
+ 'parameters': param_set,
182
+ 'performance': best_performance[i] if objective in ['NSE', 'KGE'] else -best_performance[i]
183
+ })
184
+
185
+ # Store performance with original (calibrated) parameters
186
+ self.params = original_params
187
+ self.states = copy.deepcopy(initial_states)
188
+ self.run(verbose=False)
189
+ self.calculate_performance_metrics(verbose=False)
190
+ original_performance = self.performance_metrics[objective]
191
+ if objective in ['RMSE', 'MAE']:
192
+ original_performance = -original_performance # Adjust sign for consistent comparison
193
+
194
+ # Compare best run with original (calibrated) run
195
+ best_vs_original = best_performance[0] - original_performance
196
+
197
+ # Generate prediction intervals
198
+ time_index = self.data.index
199
+ dates = self.results['dates'] # Get dates from model results
200
+ obs_q_valid = obs_q[valid_idx]
201
+
202
+ # Create a dataframe to store all the best model runs
203
+ best_runs_df = pd.DataFrame(index=time_index)
204
+ best_runs_df['dates'] = dates # Add dates column
205
+ best_runs_df['observed'] = self.data[self.column_names['obs_q']]
206
+
207
+ # Run model with best parameter sets and store results
208
+ for i in range(save_best):
209
+ self.params = create_param_dict(best_parameters[i])
210
+ self.states = copy.deepcopy(initial_states)
211
+ self.run(verbose=False)
212
+ best_runs_df[f'best_{i+1}'] = self.results['discharge']
213
+
214
+ # Run model with original parameters to get baseline
215
+ self.params = original_params
216
+ self.states = copy.deepcopy(initial_states)
217
+ self.run(verbose=False)
218
+ best_runs_df['original'] = self.results['discharge']
219
+
220
+ # Calculate 95% prediction interval from best runs
221
+ best_runs_df['q5'] = best_runs_df.filter(like='best_').quantile(0.025, axis=1)
222
+ best_runs_df['q95'] = best_runs_df.filter(like='best_').quantile(0.975, axis=1)
223
+
224
+ # Determine warmup period index for plotting
225
+ warmup_idx = 0
226
+ if exclude_warmup_in_plots and hasattr(self, 'warmup_end') and self.warmup_end is not None:
227
+ if 'dates' in best_runs_df.columns:
228
+ # Find the index of the first date after warmup_end
229
+ warmup_idx = np.sum(best_runs_df['dates'] <= self.warmup_end)
230
+ if verbose:
231
+ print(f"Excluding data up to {self.warmup_end} ({warmup_idx} timesteps) from plots as warmup period.")
232
+ else:
233
+ # Default to 10% if no dates available
234
+ warmup_idx = int(len(obs_q) * 0.1)
235
+ if verbose:
236
+ print(f"No dates found. Defaulting to exclude first {warmup_idx} timesteps (10% of data) from plots.")
237
+ elif exclude_warmup_in_plots:
238
+ # Default: exclude first 10% of the data
239
+ warmup_idx = int(len(obs_q) * 0.1)
240
+ if verbose:
241
+ print(f"No warmup_end specified. Excluding first {warmup_idx} timesteps (10% of data) from plots.")
242
+
243
+ # Plot results if requested
244
+ if plot_results:
245
+ plt.figure(figsize=(12, 6))
246
+
247
+ # Apply warmup exclusion for plotting
248
+ plot_df = best_runs_df.iloc[warmup_idx:].copy()
249
+ plot_dates = dates[warmup_idx:] if isinstance(dates, np.ndarray) else plot_df.index
250
+
251
+ # Plot uncertainty band
252
+ plt.fill_between(plot_dates, plot_df['q5'], plot_df['q95'],
253
+ color='lightgray', alpha=0.7, label='95% Prediction Interval')
254
+
255
+ # Plot best run
256
+ plt.plot(plot_dates, plot_df['best_1'], 'b-', linewidth=1, label='Best Run')
257
+
258
+ # Plot original (calibrated) run
259
+ plt.plot(plot_dates, plot_df['original'], 'r--', linewidth=1.5, label='Calibrated Run')
260
+
261
+ # Plot observed data
262
+ valid_obs = ~np.isnan(plot_df['observed'])
263
+ plt.plot(plot_dates[valid_obs], plot_df['observed'][valid_obs], 'k.',
264
+ markersize=3, label='Observed')
265
+
266
+ plt.title(f'Uncertainty Analysis Results (n={n_runs})')
267
+ plt.xlabel('Time')
268
+ plt.ylabel('Discharge (mm/day)')
269
+
270
+ # Set sensible y-axis limits
271
+ valid_data = np.concatenate([
272
+ plot_df['observed'][valid_obs],
273
+ plot_df['original'],
274
+ plot_df['best_1'],
275
+ plot_df['q95']
276
+ ])
277
+ max_val = np.nanmax(valid_data)
278
+ leeway = 0.1 * max_val # 10% extra space
279
+ plt.ylim(0, max_val + leeway)
280
+
281
+ plt.legend()
282
+ plt.grid(True, alpha=0.3)
283
+
284
+ # Add annotation about performance
285
+ if objective in ['NSE', 'KGE']:
286
+ better_text = "better" if best_vs_original > 0 else "worse"
287
+ diff_text = f"Best run is {abs(best_vs_original):.4f} {better_text} than calibrated run"
288
+ else:
289
+ better_text = "better" if best_vs_original < 0 else "worse"
290
+ diff_text = f"Best run is {abs(best_vs_original):.4f} {better_text} than calibrated run"
291
+
292
+ plt.figtext(0.5, 0.002, diff_text, ha='center', fontsize=12)
293
+
294
+ plt.tight_layout()
295
+ plt.show()
296
+
297
+ # Plot parameter distributions for the top runs
298
+ n_params = len(param_info)
299
+ n_cols = min(3, n_params)
300
+ n_rows = (n_params + n_cols - 1) // n_cols
301
+
302
+ plt.figure(figsize=(15, n_rows * 3))
303
+
304
+ for i, p in enumerate(param_info):
305
+ plt.subplot(n_rows, n_cols, i + 1)
306
+ param_values = best_parameters[:save_best, i]
307
+
308
+ plt.hist(param_values, bins=min(10, save_best), alpha=0.7)
309
+ plt.axvline(p['default'], color='r', linestyle='--',
310
+ linewidth=2, label='Calibrated')
311
+ plt.axvline(best_parameters[0, i], color='b', linestyle='-',
312
+ linewidth=2, label='Best Run')
313
+
314
+ plt.title(f"{p['group']}_{p['name']}")
315
+ plt.xlabel('Parameter Value')
316
+ plt.ylabel('Frequency')
317
+
318
+ # Only show legend on the first subplot
319
+ if i == 0:
320
+ plt.legend()
321
+
322
+ plt.tight_layout()
323
+ plt.show()
324
+
325
+ # Compute elapsed time
326
+ elapsed_time = time.time() - start_time
327
+
328
+ if verbose:
329
+ print(f"\nUncertainty analysis completed in {elapsed_time:.2f} seconds")
330
+ print(f"Analyzed {n_runs} parameter sets")
331
+
332
+ print("\nTop Performance Values:")
333
+ for i in range(min(5, save_best)):
334
+ perf_value = best_performance[i] if objective in ['NSE', 'KGE'] else -best_performance[i]
335
+ print(f" Run {i+1}: {objective} = {perf_value:.4f}")
336
+
337
+ print(f"\nOriginal (Calibrated) Performance: {objective} = {original_performance:.4f}")
338
+
339
+ if objective in ['NSE', 'KGE']:
340
+ better_text = "better" if best_vs_original > 0 else "worse"
341
+ else:
342
+ better_text = "better" if best_vs_original < 0 else "worse"
343
+
344
+ print(f"Best run is {abs(best_vs_original):.4f} {better_text} than calibrated run")
345
+
346
+ # Restore original parameters and states
347
+ self.params = original_params
348
+ self.states = copy.deepcopy(initial_states)
349
+
350
+ # Return results
351
+ return {
352
+ 'best_parameter_sets': best_param_sets,
353
+ 'uncertainty_bounds': {
354
+ 'lower': best_runs_df['q5'].tolist(),
355
+ 'upper': best_runs_df['q95'].tolist()
356
+ },
357
+ 'best_runs': best_runs_df,
358
+ 'objective': objective,
359
+ 'n_runs': n_runs,
360
+ 'save_best': save_best,
361
+ 'original_performance': original_performance,
362
+ 'best_performance': best_performance[0] if objective in ['NSE', 'KGE'] else -best_performance[0],
363
+ #'warmup_idx': warmup_idx
364
+ }
@@ -0,0 +1,86 @@
1
+ Metadata-Version: 2.4
2
+ Name: HBV_Lab
3
+ Version: 0.1.0
4
+ Summary: An intuitive, object-oriented and user-friendly Python implementation of a lumped conceptual HBV hydrological model for educational and research purposes.
5
+ Home-page: https://github.com/abdallaox/HBV_python_implementation
6
+ Author: Abdalla Mohammed
7
+ Author-email: abdalla.mohammed.ox@gmail.com
8
+ License: MIT
9
+ Keywords: hydrology HBV-model rainfall-runoff hydrological-modelling
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Topic :: Scientific/Engineering :: Hydrology
15
+ Requires-Python: >=3.7
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: numpy
19
+ Requires-Dist: pandas
20
+ Requires-Dist: matplotlib
21
+ Requires-Dist: scipy
22
+ Requires-Dist: tqdm
23
+ Dynamic: author
24
+ Dynamic: author-email
25
+ Dynamic: classifier
26
+ Dynamic: description
27
+ Dynamic: description-content-type
28
+ Dynamic: home-page
29
+ Dynamic: keywords
30
+ Dynamic: license
31
+ Dynamic: license-file
32
+ Dynamic: requires-dist
33
+ Dynamic: requires-python
34
+ Dynamic: summary
35
+
36
+ # HBV_Lab (Python implementation of a lumped conceptual HBV model)
37
+
38
+ HBV is a simple conceptual hydrological model that simulates the main hydrological processes related to snow, soil, groundwater, and routing [[1]](https://watermark.silverchair.com/147.pdf?token=AQECAHi208BE49Ooan9kkhW_Ercy7Dm3ZL_9Cf3qfKAc485ysgAAA_swggP3BgkqhkiG9w0BBwagggPoMIID5AIBADCCA90GCSqGSIb3DQEHATAeBglghkgBZQMEAS4wEQQMKaeJWQ7VWIq_mqLkAgEQgIIDrh5vsWeaZ0wY6F-ERiovjNb4KimkGt_o5Pj5ZbNlsoszUqDq-oFtdcns5O02KBex_JakkQDDkMBBlg_PFsx9vpuBqYn1kC7EnMp274TfQ2NxQC70hO3OXoYWvMcZRf-Zl3r9w2LYzlmGc9fk2PmF173MeCsuIKaaA79mD0QvqI_9hN8Dz6P43uY3ybzhZPAWUaOFtudQTFy6iC9DZ1jChvSOBK_LpqltPDB5kbyDXkB8OYQl1RQQ3bBtO_G3pwFuat3m2YyKINkI7kLl6alxTs0VQc9cEXgxs_QkBubg_VBtVIaD8mNa7BxQ3PrWpPyrn02TfcAq67SVekWeN9K94P4B8aBBYuyi1-W8vqlp22gzHzr6FrtiP7PEE-7ymBAWQgro7XwZ4bulncCHb1seCeoLbuStdR93KGvKto6hOyN3uxuQZ5e1iP7726MkrMI5MhmCuw8awj2qQeH4LL4g1UuublCNtCX0KNkTCtdj3OUJfxORlLqcCS6PMwM7xkZpw4ytgJ1ro77v3T-yn3V7TbgriNX7UB9BWU2HGVz6SsSkZQoEcop0GPb_EpoqMMkw3NwEn8alJBXTcSbjkrAzWPm6kgZ3agvzbAI53fvueVayzDiZ49ifMnImnn3ge6Dcp3tb-M1Yw6rj8et1UhgfhJMbE0VGc_05KvEdrQYTndorlZPPUPpRK-3-jHOTDwOjz2ME-orp_oAcwoRqaja-e1aeLAuNFImu7PKsRSyn1x3zTh_2lks1_xt6T8kQgXeRluGhJwth1hXCa2ld7qrd1wo6H-6x63UFwO35ygr5MuIOuUnf0W2Rfb7tQtiPY9Vn_snj3frwtqBPOsgQUYtS5jXkSL-FngaOnUedrBvtv0iybblBtdd-LnClDE3KRDdVSjAAa0VS3v5RoUBxs3c8p44N5D3S4NQvdc8PZy9aHeJ6Tl7VNg-gWneGm3_BrOgiW1ylifGMb3X0J6NoYHSDy0mzR51VcM3w-gSCg44ejKuMPqS1yI-yTkZrAfXEKI8ECNdjLz_A9OjaMHr_saFijNhrdHX_l9_bFlfQEzh1zq6ueHZR6Bx3bMriUQYyajTQ0zpNdaQQXm1m5Caq4A-Agkmd6m9SPQNRquMCSYkP10uEw1deYjz6nAhELvTJhG7T8e2ZZ_uYantsrPO54_MkPTPI--4Mx9ePzP9modf7Dc3c98Iu1230duLUQd5sfkkRCF1Ab7P-FlJpUYt1xA2bS7csXfYc_IToq4EcNXx_Zw). There are many software packages and off-the-shelf products that implement different versions of it [[2]](https://www.geo.uzh.ch/en/units/h2k/Services/HBV-Model.html) [[3]](https://hess.copernicus.org/articles/17/445/2013/).
39
+
40
+ I've been experimenting with the model lately and—in an endeavour to better understand the logic behind it—I decided to implement my own version—in Python, following an intuitive object-oriented programming approach.
41
+
42
+ This versioin implements the snow, soil, response and routing routines—controled by 14 calibratable parameters as shown below. In addition to calibration and uncertainty analysis modules.
43
+ ```python
44
+ parameters = {
45
+ 'snow': ['TT', 'CFMAX', 'SFCF', 'CFR', 'CWH'],
46
+ 'soil': ['FC', 'LP', 'BETA'],
47
+ 'response': ['K0', 'K1', 'K2', 'UZL', 'PERC']
48
+ 'routing' : [ 'MAXBAS'],
49
+ }
50
+ ```
51
+
52
+
53
+ This can be flexibly used for different modelling tasks, but can also be used in a classroom setup—to explain hydrological concepts (processes, calibration, uncertainty analysis, etc.).
54
+
55
+ ## Get Started
56
+
57
+ ### Install the Package
58
+ ```bash
59
+ pip install HBV_Lab
60
+ ```
61
+ ### How to Use
62
+ It is very intuitive—you create a model like an object which has attributes (data, parameters, initial conditions, etc.) that you can assign and access. The object also performs functions (calibration, uncertainty estimation, save, load, etc.)
63
+ ```python
64
+ from HBV_Lab import HBVModel
65
+ model = HBVModel()
66
+ model.load_data("pandas dataframe")
67
+ model.set_parameters(params)
68
+ model.run()
69
+ model.calibrate()
70
+ model.evaluate_uncertainity()
71
+ model.plot_results()
72
+ model.save_results()
73
+ model.save_model("path")
74
+ model.load_model("path")
75
+ ```
76
+ ### Tutorial
77
+ Start by following a simple case study in the notebook: [**quick_start_guide.ipynb**](quick_start_guide.ipynb)
78
+ ### Play with HBV
79
+ Get a feeling of how HBV model work and the role of the different parameters in [**HBVLAB**](https://www.linkedin.com/in/abdallaimam/) (which uses a model developed with this HBV implementation).
80
+ ### References
81
+ **[1]** Bergström, S., & Forsman, A. (1973). DEVELOPMENT OF A CONCEPTUAL DETERMINISTIC RAINFALL-RUNOFF MODEL. Hydrology Research, 4, 147-170.
82
+
83
+ **[2]** Seibert, J. and Vis, M. J. P.: Teaching hydrological modeling with a user-friendly catchment-runoff-model software package, Hydrol. Earth Syst. Sci., 16, 3315–3325, https://doi.org/10.5194/hess-16-3315-2012, 2012.
84
+
85
+ **[3]** AghaKouchak, A., Nakhjiri, N., and Habib, E.: An educational model for ensemble streamflow simulation and uncertainty analysis, Hydrol. Earth Syst. Sci., 17, 445–452, https://doi.org/10.5194/hess-17-445-2013, 2013.
86
+
@@ -0,0 +1,14 @@
1
+ HBV_Lab/HBV_model.py,sha256=qESzu_BtSeHioXjlWTvRZ75Z3JmcsZFv1251E6rtZ7w,34459
2
+ HBV_Lab/__init__.py,sha256=IVVuP29S05wRm2S6DPfv6T_HZLLdOBSHH1d5wvXUn3U,31
3
+ HBV_Lab/calibration.py,sha256=h7aXvYkrFB8x9XZF2Oq_yCKbJ4NrlKWUS87gzRboUOc,10596
4
+ HBV_Lab/hbv_step.py,sha256=bVuruQP-_-wPnBJPRmJAWTkgtxXG2yQWej37dyg9khY,2957
5
+ HBV_Lab/response.py,sha256=bmV_9M7JlX7DJgbLynb620t4_t-1f1OAF6ovcKmunDg,3123
6
+ HBV_Lab/routing.py,sha256=XG08CivFlGBNbpHY4jeyKRDDxuVIM826QPlloeWUxJU,1241
7
+ HBV_Lab/snow.py,sha256=3WLC0w7CugDElnu5OHtFveDV3qQNqKkuy96T37CA9wU,2532
8
+ HBV_Lab/soil.py,sha256=HwbAjusb0PstpkFKauCNWx5d8ad5qbcXurIuygVIPYY,2802
9
+ HBV_Lab/uncertainty.py,sha256=gqF8njeICYzOfG-03_DADBdeGz-RD-IL6Mn41n2ou2E,16436
10
+ hbv_lab-0.1.0.dist-info/licenses/LICENSE,sha256=Bfu0Cd9crnTcYQvU2HptthyOzhc7jBTUicQdMhKsDoU,1094
11
+ hbv_lab-0.1.0.dist-info/METADATA,sha256=22w42e2UVuNp10DSr5JFOwV8UfeuOgHew06AUD65pk0,5593
12
+ hbv_lab-0.1.0.dist-info/WHEEL,sha256=GHB6lJx2juba1wDgXDNlMTyM13ckjBMKf-OnwgKOCtA,91
13
+ hbv_lab-0.1.0.dist-info/top_level.txt,sha256=8PP6duueMX7y7CQk0_x7COQYhAfVDZBiMRp9j_sREkc,8
14
+ hbv_lab-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.3.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ABDALLA MOHAMMED
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ HBV_Lab