diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index b6908357b..000000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,13 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - - -version: 2 -updates: -- package-ecosystem: pip - directory: "/" - schedule: - interval: daily - open-pull-requests-limit: 10 diff --git a/.github/workflows/github-pages-mkdocs.yml b/.github/workflows/github-pages-mkdocs.yml index ce599f35b..085c5998f 100644 --- a/.github/workflows/github-pages-mkdocs.yml +++ b/.github/workflows/github-pages-mkdocs.yml @@ -4,6 +4,10 @@ permissions: contents: write pages: write +concurrency: + group: gh-pages-deploy + cancel-in-progress: false + on: push: branches: @@ -30,7 +34,7 @@ jobs: with: trigger: 'pull_request' package-manager: 'pixi' - install-groups: "groups: docs" + install-groups: "docs" deploy-token: ${{ secrets.ACTIONS_DEPLOY_TOKEN }} deploy-main: @@ -44,7 +48,7 @@ jobs: with: trigger: 'main' package-manager: 'pixi' - install-groups: "groups: docs" + install-groups: "docs" deploy-token: ${{ secrets.ACTIONS_DEPLOY_TOKEN }} deploy-release: @@ -60,6 +64,6 @@ jobs: with: trigger: 'release' package-manager: 'pixi' - install-groups: "groups: docs" + install-groups: "docs" deploy-token: ${{ secrets.ACTIONS_DEPLOY_TOKEN }} mike-alias: 'latest' diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index c630e2433..1267059c0 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -64,4 +64,4 @@ jobs: prerelease-type: ${{ github.event.inputs.prerelease_type }} draft: ${{ github.event.inputs.draft }} package-manager: pixi - install-groups: "groups: dev" + install-groups: "dev" diff --git a/.github/workflows/pypi-release.yml b/.github/workflows/pypi-release.yml index dadbc942a..4bce1fd9e 100644 --- a/.github/workflows/pypi-release.yml +++ b/.github/workflows/pypi-release.yml @@ -21,12 +21,11 @@ jobs: with: fetch-depth: 0 ref: ${{ github.event.workflow_run.head_sha || github.sha }} - - name: Build and publish to PyPI uses: Serapieum-of-alex/github-actions/actions/release/pypi@pypi-release/v1 with: pypi-username: __token__ pypi-password: ${{ secrets.PYPI_PUBLISH }} package-manager: 'pixi' - install-groups: 'groups: dev' + install-groups: 'dev' verify-lock: 'false' diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 7332ebda5..92ceb94e5 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -67,12 +67,20 @@ repos: hooks: - id: pydocstyle name: "[py - check] pydocstyle" - files: ^src/llama_utils/.*\.py$ + files: ^src/Hapi/.*\.py$ - repo: https://github.com/pycqa/isort rev: 6.0.1 hooks: - id: isort name: "[py - format] isort" + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.15.0 + hooks: + - id: mypy + name: "[py - check] mypy" + args: [--config-file=pyproject.toml] + additional_dependencies: [numpy, pandas, scipy, types-PyYAML, types-requests] + files: ^src/Hapi/.*\.py$ - repo: https://github.com/psf/black rev: 25.1.0 hooks: diff --git a/.readthedocs.yml b/.readthedocs.yml deleted file mode 100644 index b4df2bba3..000000000 --- a/.readthedocs.yml +++ /dev/null @@ -1,21 +0,0 @@ -# .readthedocs.yml -# Read the Docs configuration file -# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details - -# Required -version: 2 - -# Build documentation in the docs/ directory with Sphinx -sphinx: - configuration: docs/conf.py - -build: - os: "ubuntu-22.04" - tools: - python: "mambaforge-22.9" - -conda: - environment: docs/environment.yml - -# Optionally build your docs in additional formats such as PDF and ePub -formats: all diff --git a/AUTHORS.rst b/AUTHORS.rst deleted file mode 100644 index 34d673f7a..000000000 --- a/AUTHORS.rst +++ /dev/null @@ -1,13 +0,0 @@ -======= -Credits -======= - -Development Lead ----------------- - -* Mostafa Farrag - -Contributors ------------- - -None yet. Why not be the first? diff --git a/docs/Hydrodynamic model.rst b/docs/Hydrodynamic model.rst deleted file mode 100644 index d20cb0c7a..000000000 --- a/docs/Hydrodynamic model.rst +++ /dev/null @@ -1,10 +0,0 @@ -================ -Hydrodynamic model -================ -.. toctree:: - :hidden: - :maxdepth: 1 - - Hydrodynamic model/main components.rst - Hydrodynamic model/statistical analysis.rst - Hydrodynamic model/event analysis.rst diff --git a/docs/Hydrological model.rst b/docs/Hydrological model.rst deleted file mode 100644 index 14f7ea85d..000000000 --- a/docs/Hydrological model.rst +++ /dev/null @@ -1,13 +0,0 @@ -================ -Tutorials -================ -.. toctree:: - :hidden: - :maxdepth: 1 - - Hydrological model/lumped models.rst.rst - Hydrological model/Inputs.rst - Hydrological model/Lumped_run.rst - Hydrological model/Lumped_calib.rst - Hydrological model/DistributedModel_run.rst - Hydrological model/DistributedModel_calib.rst diff --git a/docs/Hydrological model/DistributedModel_calib.rst b/docs/Hydrological model/DistributedModel_calib.rst deleted file mode 100644 index 9d9d1117e..000000000 --- a/docs/Hydrological model/DistributedModel_calib.rst +++ /dev/null @@ -1,141 +0,0 @@ -****************************** -Distributed Hydrological Model -****************************** -After preparing all the meteorological, GIS inputs required for the model, and Extracting the parameters for the catchment - -.. code-block:: py - - import numpy as np - import datetime as dt - import gdal - from Hapi.calibration import Calibration - import Hapi.rrm.hbv_bergestrom92 as HBV - - import statista.descriptors as metrics - - Path = Comp + "/data/distributed/coello" - PrecPath = Path + "/prec" - Evap_Path = Path + "/evap" - TempPath = Path + "/temp" - FlowAccPath = Path + "/GIS/acc4000.tif" - FlowDPath = Path + "/GIS/fd4000.tif" - CalibPath = Path + "/calibration" - SaveTo = Path + "/results" - - AreaCoeff = 1530 - #[sp,sm,uz,lz,wc] - InitialCond = [0,5,5,5,0] - Snow = 0 - - # Create the model object and read the input data - - Sdate = '2009-01-01' - Edate = '2011-12-31' - name = "Coello" - Coello = Calibration(name, Sdate, Edate, SpatialResolution = "Distributed") - - # Meteorological & GIS Data - Coello.readRainfall(PrecPath) - Coello.readTemperature(TempPath) - Coello.readET(Evap_Path) - - Coello.readFlowAcc(FlowAccPath) - Coello.readFlowDir(FlowDPath) - - # Lumped Model - Coello.readLumpedModel(HBV, AreaCoeff, InitialCond) - - # Gauges Data - Coello.readGaugeTable(Path+"/stations/gauges.csv", FlowAccPath) - GaugesPath = Path+"/stations/" - Coello.readDischargeGauges(GaugesPath, column='id', fmt="%Y-%m-%d") - - - --Spatial Variability Object -------------------------------- - -from Hapi.rrm.distparameters import DistParameters as DP - -- The `DistParameters` distribute the parameter vector on the cells following some sptial logic (same set of parameters for all cells, different parameters for each cell, HRU, different parameters for each class in aditional map) - -.. code-block:: py - - raster = gdal.Open(FlowAccPath) - #------------- - # for lumped catchment parameters - no_parameters = 12 - klb = 0.5 - kub = 1 - #------------ - no_lumped_par = 1 - lumped_par_pos = [7] - - SpatialVarFun = DP(raster, no_parameters, no_lumped_par=no_lumped_par, - lumped_par_pos=lumped_par_pos,Function=2, Klb=klb, Kub=kub) - # calculate no of parameters that optimization algorithm is going to generate - SpatialVarFun.ParametersNO - - -- Define the objective function ------------------------------------ - -.. code-block:: py - :linenos: - - coordinates = Coello.GaugesTable[['id','x','y','weight']][:] - - # define the objective function and its arguments - OF_args = [coordinates] - - def objective_function(Qobs, Qout, q_uz_routed, q_lz_trans, coordinates): - Coello.extractDischarge() - all_errors=[] - # error for all internal stations - for i in range(len(coordinates)): - all_errors.append((metrics.rmse(Qobs.loc[:,Qobs.columns[0]],Coello.Qsim[:,i]))) #*coordinates.loc[coordinates -.index[i],'weight'] - print(all_errors) - error = sum(all_errors) - return error - - Coello.read_objective_function(objective_function, OF_args) - --Calibration algorithm Arguments ------------------------------------- - -- Create the options dictionary all the optimization parameters should be passed to the optimization object inside the option dictionary: - -to see all options import Optimizer class and check the documentation of the -method setOption - -.. code-block:: py - :linenos: - - ApiObjArgs = dict(hms=50, hmcr=0.95, par=0.65, dbw=2000, fileout=1, - filename=SaveTo + "/Coello_"+str(dt.datetime.now())[0:10]+".txt") - - for i in range(len(ApiObjArgs)): - print(list(ApiObjArgs.keys())[i], str(ApiObjArgs[list(ApiObjArgs.keys())[i]])) - - pll_type = 'POA' - pll_type = None - - ApiSolveArgs = dict(store_sol=True, display_opts=True, store_hst=True,hot_start=False) - - OptimizationArgs=[ApiObjArgs, pll_type, ApiSolveArgs] - -- Run Calibration algorithm ------------------------------------- - -.. code-block:: py - - cal_parameters = Coello.runCalibration(SpatialVarFun, OptimizationArgs,printError=0) - -- Save results ----------------- - -.. code-block:: py - - SpatialVarFun.Function(Coello.Parameters, kub=SpatialVarFun.Kub, klb=SpatialVarFun.Klb) - SpatialVarFun.saveParameters(SaveTo) diff --git a/docs/Hydrological model/DistributedModel_run.rst b/docs/Hydrological model/DistributedModel_run.rst deleted file mode 100644 index d7419e6c5..000000000 --- a/docs/Hydrological model/DistributedModel_run.rst +++ /dev/null @@ -1,370 +0,0 @@ -********************************************* -Distributed Hydrological Model Calibration -********************************************* -The calibration of the Distributed rainfall runoff model follows the same steps of running the model with extra steps to define the calibration algorithm arguments - -1- Catchment Object ------------------------ -- Import the Catchment object which is the main object in the distributed model, to read and check the input data, and when the model finish the simulation it stores the results and do the visualization - - -.. code-block:: py - :linenos: - - class Catchment(): - - ====================== - Catchment - ====================== - Catchment class include methods to read the meteorological and Spatial inputs - of the distributed hydrological model. Catchment class also reads the data - of the gauges, it is a super class that has the run subclass, so you - need to build the catchment object and hand it as an inpit to the Run class - to run the model - - methods: - 1-readRainfall - 2-readTemperature - 3-readET - 4-readFlowAcc - 5-readFlowDir - 6-ReadFlowPathLength - 7-readParameters - 8-readLumpedModel - 9-readLumpedInputs - 10-readGaugeTable - 11-readDischargeGauges - 12-readParametersBounds - 13-extractDischarge - 14-plotHydrograph - 15-PlotDistributedQ - 16-saveResults - - def __init__(self, name, StartDate, EndDate, fmt="%Y-%m-%d", SpatialResolution = 'Lumped', - TemporalResolution = "Daily"): - ============================================================================= - Catchment(name, StartDate, EndDate, fmt="%Y-%m-%d", SpatialResolution = 'Lumped', - TemporalResolution = "Daily") - ============================================================================= - Parameters - ---------- - name : [str] - Name of the Catchment. - StartDate : [str] - starting date. - EndDate : [str] - end date. - fmt : [str], optional - format of the given date. The default is "%Y-%m-%d". - SpatialResolution : TYPE, optional - Lumped or 'Distributed' . The default is 'Lumped'. - TemporalResolution : TYPE, optional - "Hourly" or "Daily". The default is "Daily". - - -- To instantiate the object you need to provide the `name`, `statedate`, `enddate`, and the `SpatialResolution` - -.. code-block:: py - :linenos: - - from Hapi.catchment import Catchment - - start = "2009-01-01" - end = "2011-12-31" - name = "Coello" - - Coello = Catchment(name, start, end, SpatialResolution = "Distributed") - - -Read Meteorological Inputs -############################ - - -- First define the directory where the data exist - -.. code-block:: py - :linenos: - - Path = Comp + "/data/distributed/coello" - PrecPath = Path + "/prec" - Evap_Path = Path + "/evap" - TempPath = Path + "/temp" - FlowAccPath = Path + "/GIS/acc4000.tif" - FlowDPath = Path + "/GIS/fd4000.tif" - ParPathRun = Path + "/Parameter set-Avg/" - - -- Then use the each method in the object to read the coresponding data - -.. code-block:: py - :linenos: - - Coello.readRainfall(PrecPath) - Coello.readTemperature(TempPath) - Coello.readET(Evap_Path) - Coello.readFlowAcc(FlowAccPath) - Coello.readFlowDir(FlowDPath) - - -- To read the parameters you need to provide whether you need to consider the snow subroutine or not - -.. code-block:: py - :linenos: - Snow = 0 - Coello.readParameters(ParPathRun, Snow) - - -2- Lumped Model -------------------- - -- Get the Lumpde conceptual model you want to couple it with the distributed routing module which in our case HBV - and define the initial condition, and catchment area. - -.. code-block:: py - :linenos: - - import Hapi.hbv_bergestrom92 as HBV - - CatchmentArea = 1530 - InitialCond = [0,5,5,5,0] - Coello.readLumpedModel(HBV, CatchmentArea, InitialCond) - -- If the Inpus are consistent in dimensions you will get a the following message - - -.. image:: /img/check_inputs.png - :width: 400pt - - - -- to check the performance of the model we need to read the gauge hydrographs - -.. code-block:: py - :linenos: - - Coello.readGaugeTable("Hapi/Data/00inputs/Discharge/stations/gauges.csv", FlowAccPath) - GaugesPath = "Hapi/Data/00inputs/Discharge/stations/" - Coello.readDischargeGauges(GaugesPath, column='id', fmt="%Y-%m-%d") - - -3-Run Object ----------------- - - -- The `Run` object connects all the components of the simulation together, the `Catchment` object, the `Lake` object and the `distributedrouting` object -- import the Run object and use the `Catchment` object as a parameter to the `Run` object, then call the RunHapi method to start the simulation - -.. code-block:: py - - from Hapi.run import Run - Run.RunHapi(Coello) - - -- the result of the simulation will be stored as attributes in the Catchment object as follow - -.. code-block:: py - - Outputs: - 1-statevariables: [numpy attribute] - 4D array (rows,cols,time,states) states are [sp,wc,sm,uz,lv] - 2-qlz: [numpy attribute] - 3D array of the lower zone discharge - 3-quz: [numpy attribute] - 3D array of the upper zone discharge - 4-qout: [numpy attribute] - 1D timeseries of discharge at the outlet of the catchment - of unit m3/sec - 5-quz_routed: [numpy attribute] - 3D array of the upper zone discharge accumulated and - routed at each time step - 6-qlz_translated: [numpy attribute] - 3D array of the lower zone discharge translated at each time step - -4-Extract Hydrographs ------------------------ - -- The final step is to extract the simulated Hydrograph from the cells at the location of the gauges to compare -- The `extractDischarge` method extracts the hydrographs, however you have to provide in the gauge file the coordinates of the gauges with the same coordinate system of the `FlowAcc` raster - -.. code-block:: py - :linenos: - Coello.extractDischarge(Factor=Coello.GaugesTable['area ratio'].tolist()) - - for i in range(len(Coello.GaugesTable)): - gaugeid = Coello.GaugesTable.loc[i,'id'] - print("----------------------------------") - print("Gauge - " +str(gaugeid)) - print("RMSE= " + str(round(Coello.Metrics.loc['RMSE',gaugeid],2))) - print("NSE= " + str(round(Coello.Metrics.loc['NSE',gaugeid],2))) - print("NSEhf= " + str(round(Coello.Metrics.loc['NSEhf',gaugeid],2))) - print("KGE= " + str(round(Coello.Metrics.loc['KGE',gaugeid],2))) - print("WB= " + str(round(Coello.Metrics.loc['WB',gaugeid],2))) - print("Pearson CC= " + str(round(Coello.Metrics.loc['Pearson-CC',gaugeid],2))) - print("R2 = " + str(round(Coello.Metrics.loc['R2',gaugeid],2))) - - -- The `extractDischarge` will print the performance metics - - -5-Visualization -------------------- - -- Firts type of visualization we can do with the results is to compare the gauge hydrograph with the simulatied hydrographs -- Call the `plotHydrograph` method and provide the period you want to visualize with the order of the gauge - -.. code-block:: py - - gaugei = 5 - plotstart = "2009-01-01" - plotend = "2011-12-31" - - Coello.plotHydrograph(plotstart, plotend, gaugei) - - - -.. image:: /img/hydrograph.png -:width: 400pt - - -6-Animation ------------------ - -- the best way to visualize time series of distributed data is through visualization, for theis reason, The `Catchment` object has `plotDistributedResults` method which can animate all the results of the model - -.. code-block:: py - - ============================================================================= - AnimateArray(Arr, Time, NoElem, TicksSpacing = 2, Figsize=(8,8), PlotNumbers=True, - NumSize= 8, Title = 'Total Discharge',titlesize = 15, Backgroundcolorthreshold=None, - cbarlabel = 'Discharge m3/s', cbarlabelsize = 12, textcolors=("white","black"), - Cbarlength = 0.75, Interval = 200,cmap='coolwarm_r', Textloc=[0.1,0.2], - Gaugecolor='red',Gaugesize=100, ColorScale = 1,gamma=1./2.,linthresh=0.0001, - linscale=0.001, midpoint=0, orientation='vertical', rotation=-90,IDcolor = "blue", - IDsize =10, **kwargs) - ============================================================================= - Parameters - ---------- - Arr : [array] - the array you want to animate. - Time : [dataframe] - dataframe contains the date of values. - NoElem : [integer] - Number of the cells that has values. - TicksSpacing : [integer], optional - Spacing in the colorbar ticks. The default is 2. - Figsize : [tuple], optional - figure size. The default is (8,8). - PlotNumbers : [bool], optional - True to plot the values intop of each cell. The default is True. - NumSize : integer, optional - size of the numbers plotted intop of each cells. The default is 8. - Title : [str], optional - title of the plot. The default is 'Total Discharge'. - titlesize : [integer], optional - title size. The default is 15. - Backgroundcolorthreshold : [float/integer], optional - threshold value if the value of the cell is greater, the plotted - numbers will be black and if smaller the plotted number will be white - if None given the maxvalue/2 will be considered. The default is None. - textcolors : TYPE, optional - Two colors to be used to plot the values i top of each cell. The default is ("white","black"). - cbarlabel : str, optional - label of the color bar. The default is 'Discharge m3/s'. - cbarlabelsize : integer, optional - size of the color bar label. The default is 12. - Cbarlength : [float], optional - ratio to control the height of the colorbar. The default is 0.75. - Interval : [integer], optional - number to controlthe speed of the animation. The default is 200. - cmap : [str], optional - color style. The default is 'coolwarm_r'. - Textloc : [list], optional - location of the date text. The default is [0.1,0.2]. - Gaugecolor : [str], optional - color of the points. The default is 'red'. - Gaugesize : [integer], optional - size of the points. The default is 100. - IDcolor : [str] - the ID of the Point.The default is "blue". - IDsize : [integer] - size of the ID text. The default is 10. - ColorScale : integer, optional - there are 5 options to change the scale of the colors. The default is 1. - 1- ColorScale 1 is the normal scale - 2- ColorScale 2 is the power scale - 3- ColorScale 3 is the SymLogNorm scale - 4- ColorScale 4 is the PowerNorm scale - 5- ColorScale 5 is the BoundaryNorm scale - ------------------------------------------------------------------ - gamma : [float], optional - value needed for option 2 . The default is 1./2.. - linthresh : [float], optional - value needed for option 3. The default is 0.0001. - linscale : [float], optional - value needed for option 3. The default is 0.001. - midpoint : [float], optional - value needed for option 5. The default is 0. - ------------------------------------------------------------------ - orientation : [string], optional - orintation of the colorbar horizontal/vertical. The default is 'vertical'. - rotation : [number], optional - rotation of the colorbar label. The default is -90. - **kwargs : [dict] - keys: - Points : [dataframe]. - dataframe contains two columns 'cell_row', and cell_col to - plot the point at this location - - Returns - ------- - animation.FuncAnimation. - - - -- choose the period of time you want to animate and the result (total discharge, upper zone discharge, soil moisture,...) - -.. code-block:: py - plotstart = "2009-01-01" - plotend = "2009-02-01" - - Anim = Coello.plotDistributedResults(plotstart, plotend, Figsize=(9,9), Option = 1,threshold=160, PlotNumbers=True, - TicksSpacing = 5,Interval = 200, Gauges=True, cmap='inferno', Textloc=[0.1,0.2], - Gaugecolor='red',ColorScale = 1, IDcolor='blue', IDsize=25) - - -.. only:: html - - .. figure:: /img/anim.gif - - -- to save the animation - - - Please visit https://ffmpeg.org/ and download a version of ffmpeg compitable with your operating system - - Copy the content of the folder and paste it in the "c:/user/.matplotlib/ffmpeg-static/" - or - - - define the path where the downloaded folder "ffmpeg-static" exist to matplotlib using the following lines - -.. code-block:: py - import matplotlib as mpl - mpl.rcParams['animation.ffmpeg_path'] = "path where you saved the ffmpeg.exe/ffmpeg.exe" - - -.. code-block:: py - Path = SaveTo + "anim.gif" - Coello.saveAnimation(VideoFormat="gif",Path=Path,SaveFrames=3) - - - -7-Save the result into rasters ----------------------------------- - -- To save the results as rasters provide the period and the path - -.. code-block:: py - - StartDate = "2009-01-01" - EndDate = "2010-04-20" - Prefix = 'Qtot_' - - Coello.saveResults(FlowAccPath, Result=1, StartDate=StartDate, EndDate=EndDate, Path="F:/02Case studies/Coello/Hapi/Model/results/", Prefix=Prefix) diff --git a/docs/Hydrological model/Inputs.rst b/docs/Hydrological model/Inputs.rst deleted file mode 100644 index 1bfb525fd..000000000 --- a/docs/Hydrological model/Inputs.rst +++ /dev/null @@ -1,10 +0,0 @@ -================ -Inputs -================ -.. toctree:: - :hidden: - :maxdepth: 1 - - Hydrological model/Meteo_Inputs.rst - Hydrological model/GIS_inputs.rst - Hydrological model/Parameters.rst diff --git a/docs/Hydrological model/Lumped_run.rst b/docs/Hydrological model/Lumped_run.rst deleted file mode 100644 index 438e0f905..000000000 --- a/docs/Hydrological model/Lumped_run.rst +++ /dev/null @@ -1,131 +0,0 @@ -***** -Lumped Model Run -***** -To run the HBV lumped model inside Hapi you need to prepare the meteorological inputs (rainfall, temperature and potential evapotranspiration), HBV parameters, and the HBV model (you can load BergstrΓΆm, 1992 version of HBV from Hapi ) - -- First load the prepared lumped version of the HBV module inside Hapi, the triangular routing function and the wrapper function that runs the lumped model `RUN`. - -.. code-block:: py - :linenos: - - import Hapi.rrm.hbv_bergestrom92 as HBVLumped - from Hapi.run import Run - from Hapi.catchment import Catchment - from Hapi.rrm.routing import Routing - -- read the meteorological data, data has be in the form of numpy array with the following order [rainfall, ET, Temp, Tm], ET is the potential evapotranspiration, Temp is the temperature (C), and Tm is the long term monthly average temperature. - -.. code-block:: py - :linenos: - - Parameterpath = Comp + "/data/lumped/Coello_Lumped2021-03-08_muskingum.txt" - MeteoDataPath = Comp + "/data/lumped/meteo_data-MSWEP.csv" - - ### meteorological data - start = "2009-01-01" - end = "2011-12-31" - name = "Coello" - Coello = Catchment(name, start, end) - Coello.readLumpedInputs(MeteoDataPath) - -- Meteorological data - -.. code-block:: py - :linenos: - - start = "2009-01-01" - end = "2011-12-31" - name = "Coello" - Coello = Catchment(name, start, end) - Coello.readLumpedInputs(MeteoDataPath) - -- Lumped model - - prepare the initial conditions, cathcment area and the lumped model. - -.. code-block:: py - :linenos: - - # catchment area - AreaCoeff = 1530 - # [Snow pack, Soil moisture, Upper zone, Lower Zone, Water content] - InitialCond = [0,10,10,10,0] - - Coello.readLumpedModel(HBVLumped, AreaCoeff, InitialCond) - -- Load the pre-estimated parameters - snow option (if you want to simulate snow accumulation and snow melt or not) - -.. code-block:: py - :linenos: - - Snow = 0 # no snow subroutine - # if routing using Maxbas True, if Muskingum False - Coello.readParameters(Parameterpath, Snow) - - -- Prepare the routing options. - -.. code-block:: py - :linenos: - - # RoutingFn = Routing.TriangularRouting2 - RoutingFn = Routing.Muskingum_V - Route = 1 - -- now all the data required for the model are prepared in the right form, now you can call the `runLumped` wrapper to initiate the calculation - -.. code-block:: py - :linenos: - - Run.runLumped(Coello, Route, RoutingFn) - -to calculate some metrics for the quality assessment of the calculate discharge the `statista.descriptors` contains some -metrics like `rmse`, `nse`, `kge` and `wb` , you need to load it, a measured time series of doscharge for the same -period of the simulation is also needed for the comparison. - -all methods in `statista.descriptors` takes two numpy arrays of the same length and return real number. - -.. code-block:: py - :linenos: - import statista.descriptors as metrics - - Metrics = dict() - Qobs = Coello.QGauges['q'] - - Metrics['RMSE'] = metrics.rmse(Qobs, Coello.Qsim['q']) - Metrics['NSE'] = metrics.nse(Qobs, Coello.Qsim['q']) - Metrics['NSEhf'] = metrics.nse_hf(Qobs, Coello.Qsim['q']) - Metrics['KGE'] = metrics.kge(Qobs, Coello.Qsim['q']) - Metrics['WB'] = metrics.wb(Qobs, Coello.Qsim['q']) - - print("RMSE= " + str(round(Metrics['RMSE'],2))) - print("NSE= " + str(round(Metrics['NSE'],2))) - print("NSEhf= " + str(round(Metrics['NSEhf'],2))) - print("KGE= " + str(round(Metrics['KGE'],2))) - print("WB= " + str(round(Metrics['WB'],2))) - -To plot the calculated and measured discharge import matplotlib - -.. code-block:: py - :linenos: - - gaugei = 0 - plotstart = "2009-01-01" - plotend = "2011-12-31" - Coello.plotHydrograph(plotstart, plotend, gaugei, Title= "Lumped Model") - - - .. image:: /img/lumpedmodel.png - :width: 400pt - -- To save the results - -.. code-block:: py - :linenos: - - StartDate = "2009-01-01" - EndDate = "2010-04-20" - - Path = SaveTo + "Results-Lumped-Model" + str(dt.datetime.now())[0:10] + ".txt" - Coello.saveResults(Result=5, StartDate=StartDate, EndDate=EndDate, Path=Path) diff --git a/docs/Hydrological model/Muskingum.rst b/docs/Hydrological model/Muskingum.rst deleted file mode 100644 index 001d1be5b..000000000 --- a/docs/Hydrological model/Muskingum.rst +++ /dev/null @@ -1,37 +0,0 @@ -***** -Muskingum -***** -Muskingum is a hydrologic-routing method which employs the equation of continuity to predict magnitude, volume and temporal patterns of flow as it translates downstream of a channel. - -.. math:: - πΌβˆ’π‘„ = \frac{𝑑𝑆}{𝑑𝑑} - - -.. image:: /img/muskingum1.png - :width: 400pt - -.. image:: /img/muskingum2.png - :width: 400pt - - -Channel routing functions of inflow, outflow and storage where storage can be considered as two parts, prism & wedge storage. - -.. math:: - 𝑆 = πΎβˆ—[π‘₯βˆ—πΌ^{\m} +(1βˆ’π‘₯)βˆ—π‘„^{\π‘š}] - - -Where `k` is the travel time constant and `x` are weighting coefficient to determine the linearity of the water surface, and it ranges between 0 & 0.5, and `m` is an exponential constant varies from 0.6 for rectangle channel to 1. - - -For Muskingum version of the channel routing equation `m` equals one which made the relation between `S` and `I`, `Q`. Using coefficient `k` & `x` three weights can be calculated as follow: - -.. math:: - - C1 = \left(\frac{π›₯π‘‘βˆ’2𝐾𝑋}{2𝐾(1βˆ’π‘‹)+π›₯𝑑}\right)\label{eq:C1} - C2 = \left(\frac{π›₯𝑑+2𝐾𝑋}{2𝐾(1βˆ’π‘‹)+π›₯𝑑}\right)\label{eq:C2} - C3 = \left(\frac{2𝐾(1-𝑋)-π›₯𝑑}{2𝐾(1βˆ’π‘‹)+π›₯𝑑}\right)\label{eq:C3} - -To route the inflow hydrograph - -.. math:: - Q = \left(C1 * I_{j+1} + C2 * I_{j} + C3 * Q_{j} }\right)\label{eq:Q} diff --git a/docs/Hydrological model/lumped models.rst b/docs/Hydrological model/lumped models.rst deleted file mode 100644 index bd6572799..000000000 --- a/docs/Hydrological model/lumped models.rst +++ /dev/null @@ -1,9 +0,0 @@ -================ -Available Models -================ -.. toctree:: - :hidden: - :maxdepth: 1 - - Hydrological model/HBV.rst - Hydrological model/Muskingum.rst diff --git a/docs/api/calibration.md b/docs/api/calibration.md new file mode 100644 index 000000000..362cd1462 --- /dev/null +++ b/docs/api/calibration.md @@ -0,0 +1,4 @@ +# Calibration + +## Calibration +::: Hapi.calibration.Calibration diff --git a/docs/api/catchment.md b/docs/api/catchment.md new file mode 100644 index 000000000..aec044eab --- /dev/null +++ b/docs/api/catchment.md @@ -0,0 +1,4 @@ +# Catchment + +## Catchment +::: Hapi.catchment.Catchment diff --git a/docs/api/dem.md b/docs/api/dem.md new file mode 100644 index 000000000..847e4dcf6 --- /dev/null +++ b/docs/api/dem.md @@ -0,0 +1,4 @@ +# DEM + +## DEM +::: Hapi.dem.DEM diff --git a/docs/api/hbv-bergestrom92.md b/docs/api/hbv-bergestrom92.md new file mode 100644 index 000000000..3334bf4c6 --- /dev/null +++ b/docs/api/hbv-bergestrom92.md @@ -0,0 +1,4 @@ +# HBV rainfall runoff model + +## HBVBergestrom92 +::: Hapi.rrm.hbv_bergestrom92.HBVBergestrom92 diff --git a/docs/api/hbv-lake.md b/docs/api/hbv-lake.md new file mode 100644 index 000000000..f1224bdd0 --- /dev/null +++ b/docs/api/hbv-lake.md @@ -0,0 +1,4 @@ +# HBV rainfall runoff model + +## HBVLake +::: Hapi.rrm.hbv_lake.HBVLake diff --git a/docs/api/hbv.md b/docs/api/hbv.md new file mode 100644 index 000000000..e56b93202 --- /dev/null +++ b/docs/api/hbv.md @@ -0,0 +1,4 @@ +# HBV rainfall runoff model + +## HBV +::: Hapi.rrm.hbv.HBV diff --git a/docs/api/inputs.md b/docs/api/inputs.md new file mode 100644 index 000000000..4fc9dbc48 --- /dev/null +++ b/docs/api/inputs.md @@ -0,0 +1,4 @@ +# Inputs + +## Inputs +::: Hapi.inputs.Inputs diff --git a/docs/api/routing.md b/docs/api/routing.md new file mode 100644 index 000000000..b1bf64425 --- /dev/null +++ b/docs/api/routing.md @@ -0,0 +1,4 @@ +# Routing + +## Routing +::: Hapi.routing.Routing diff --git a/docs/api/rrm-parameters.md b/docs/api/rrm-parameters.md new file mode 100644 index 000000000..525c2c43e --- /dev/null +++ b/docs/api/rrm-parameters.md @@ -0,0 +1,4 @@ +# Parameters + +## Parameters +::: Hapi.rrm.parameters.Parameters diff --git a/docs/api/run.md b/docs/api/run.md new file mode 100644 index 000000000..0c948f532 --- /dev/null +++ b/docs/api/run.md @@ -0,0 +1,4 @@ +# Run + +## Run +::: Hapi.run.Run diff --git a/docs/api/wrapper.md b/docs/api/wrapper.md new file mode 100644 index 000000000..964ae583c --- /dev/null +++ b/docs/api/wrapper.md @@ -0,0 +1,4 @@ +# Wrapper + +## Wrapper +::: Hapi.wrapper.Wrapper diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 744dc9e92..000000000 --- a/docs/conf.py +++ /dev/null @@ -1,281 +0,0 @@ -# -# sphinx-quickstart on Wed April 23 2021. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import os -import sys - -# import sphinx_rtd_theme - -# General information about the project. -project = "Hapi" -author = "Mostafa Farrag" - -# copyright = u"2013-2019, " - -html_theme = "sphinxdoc" -# html_theme = "agogo" -html_theme_path = ["."] - - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -sys.path.insert(0, os.path.abspath("../Hapi")) -sys.path.insert(0, os.path.abspath("..")) -sys.path.insert(0, os.path.abspath("../examples")) - -# If your extensions are in another directory, add it here. If the -# directory is relative to the documentation root, use -# os.path.abspath to make it absolute, like shown here. -sys.path.append(os.path.abspath("sphinxext")) - -# import Hapi - - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [ - "matplotlib.sphinxext.plot_directive", - "sphinx.ext.todo", - "sphinx.ext.mathjax", - "sphinx.ext.autodoc", - "sphinx.ext.graphviz", - "sphinx.ext.doctest", - "sphinx.ext.autosectionlabel", -] - -autosectionlabel_prefix_document = True - -todo_include_todos = True -# Add any paths that contain templates here, relative to this directory. -templates_path = ["templates"] - -# The suffix of source filenames. -source_suffix = ".rst" - -# The encoding of source files. -# source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = "index" - - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -# today = '' -# Else, today_fmt is used as the format for a strftime call. -# today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ["build"] - -# The reST default role (used for this markup: `text`) to use for all documents. -# default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -# add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -# add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -# show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = "sphinx" - -# A list of ignored prefixes for module index sorting. -# modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = "sphinx_rtd_theme" - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -# html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -# html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -# html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -# html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -""" -html_logo = "images/Hapi.png" -""" -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -# html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ["static"] -# -# html_context = { -# 'css_files': [ -# '_static/theme_overrides.css', # override wide tables in RTD theme -# ], -# } - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} - -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -# html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -# html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -html_file_suffix = ".html" - -# Output file base name for HTML help builder. -htmlhelp_basename = "Hapidoc" - - -# -- Options for LaTeX output -------------------------------------------------- - - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ("index", "Hapi.tex", "Hapi Documentation", "Mostafa Farrag", "report") -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -# latex_logo = "_static/logo.png" - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -latex_use_parts = False - -# If true, show page references after internal links. -latex_show_pagerefs = False - -# If true, show URL addresses after external links. -latex_show_urls = False - -# Documents to append as an appendix to all manuals. -# latex_appendices = [] - -# If false, no module index is generated. -latex_domain_indices = False - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [("index", "Hapi", "Hapi Documentation", [author], 1)] - -# If true, show URL addresses after external links. -# man_show_urls = False - - -# -- Options for Texinfo output ------------------------------------------------ - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) -texinfo_documents = [ - ( - "index", - "Hapi", - "Hapi Documentation", - "Mostafa Farrag", - "Hapi", - "One line description of project.", - "Miscellaneous", - ) -] - -# Documents to append as an appendix to all manuals. -# texinfo_appendices = [] - -# If false, no module index is generated. -# texinfo_domain_indices = True - -# How to display URL addresses: 'footnote', 'no', or 'inline'. -# texinfo_show_urls = 'footnote' - -autodoc_mock_imports = [ - # "osgeo.gdal", - # "osgeo.gdalconst", - # "osgeo", - # "osgeo.ogr", - # "cftime", - # "xarray", - # "netCDF4", - # "netCDF4_utils", - # "netcdftime", - # "pyproj", - # "Hapi.version", -] diff --git a/docs/Installation.rst b/docs/dev/Installation.md similarity index 50% rename from docs/Installation.rst rename to docs/dev/Installation.md index b764e7c34..0a90a353d 100644 --- a/docs/Installation.rst +++ b/docs/dev/Installation.md @@ -1,108 +1,95 @@ -.. highlight:: shell +# Installation -============ -Installation -============ - - - -Stable release --------------- +## Stable release Please install Hapi in a Virtual environment so that its requirements don't tamper with your system's python -``Hapi`` works with all Python versions +`Hapi` works with all Python versions -conda ------ -the easiest way to install ``Hapi`` is using ``conda`` package manager. ``Hapi`` is available in the `conda-forge `_ channel. To install +## conda +the easiest way to install `Hapi` is using `conda` package manager. `Hapi` is available in the [conda-forge](https://conda-forge.org/) channel. To install you can use the following command: -+ ``conda install -c conda-forge hapi`` - +```shell +conda install -c conda-forge hapi +``` If this works it will install Hapi with all dependencies including Python and gdal, and you skip the rest of the installation instructions. -Installing Python and gdal dependencies ---------------------------------------- +## Installing Python and gdal dependencies The main dependencies for Hapi are an installation of Python 2.7+, and gdal -Installing Python ------------------ +## Installing Python For Python we recommend using the Anaconda Distribution for Python 3, which is available for download from https://www.anaconda.com/download/. The installer gives the option to -add ``python`` to your ``PATH`` environment variable. We will assume in the instructions -below that it is available in the path, such that ``python``, ``pip``, and ``conda`` are +add `python` to your `PATH` environment variable. We will assume in the instructions +below that it is available in the path, such that `python`, `pip`, and `conda` are all available from the command line. Note that there is no hard requirement specifically for Anaconda's Python, but often it makes installation of required dependencies easier using the conda package manager. -Install as a conda environment ------------------------------- +## Install as a conda environment The easiest and most robust way to install Hapi is by installing it in a separate -conda environment. In the root repository directory there is an ``environment.yml`` file. -This file lists all dependencies. Either use the ``environment.yml`` file from the master branch +conda environment. In the root repository directory there is an `environment.yml` file. +This file lists all dependencies. Either use the `environment.yml` file from the master branch (please note that the master branch can change rapidly and break functionality without warning), or from one of the releases {release}. Run this command to start installing all Hapi dependencies: -+ ``conda env create -f environment.yml`` +```shell +conda env create -f environment.yml +``` -This creates a new environment with the name ``hapi``. To activate this environment in +This creates a new environment with the name `hapi`. To activate this environment in a session, run: -+ ``activate hapi`` - +```shell +activate hapi +``` For the installation of Hapi there are two options (from the Python Package Index (PyPI) or from Github). To install a release of Hapi from the PyPI (available from release 2018.1): -+ ``pip install HAPI-Nile=={release}`` - +```shell +pip install HAPI-Nile=={release} +``` -From sources ------------- +## From sources - -The sources for HapiSM can be downloaded from the `Github repo`_. +The sources for HapiSM can be downloaded from the +[Github repo](https://github.com/Serapieum-of-alex/Hapi). You can either clone the public repository: -.. code-block:: console - - $ git clone git://github.com/MAfarrag/HapiSM - -Or download the `tarball`_: - -.. code-block:: console - - $ curl -OJL https://github.com/MAfarrag/HapiSM/tarball/master +```shell +$ git clone git://github.com/Serapieum-of-alex/Hapi +``` +Or download the [tarball](https://github.com/Serapieum-of-alex/Hapi/tarball/master): +```shell +$ curl -OJL https://github.com/Serapieum-of-alex/Hapi/tarball/master +``` Once you have a copy of the source, you can install it with: -.. code-block:: console - - $ python setup.py install - - -.. _Github repo: https://github.com/MAfarrag/HapiSM -.. _tarball: https://github.com/MAfarrag/HapiSM/tarball/master +```shell +$ python setup.py install +``` To install directly from GitHub (from the HEAD of the master branch): -+ ``pip install git+https://github.com/MAfarrag/HAPI.git`` ++ `pip install git+https://github.com/Serapieum-of-alex/Hapi.git` or from Github from a specific release: -+ ``pip install git+https://github.com/MAfarrag/HAPI.git@{release}`` ++ `pip install git+https://github.com/Serapieum-of-alex/Hapi.git@{release}` -Now you should be able to start this environment's Python with ``python``, try -``import Hapi`` to see if the package is installed. +Now you should be able to start this environment's Python with `python`, try +`import Hapi` to see if the package is installed. More details on how to work with conda environments can be found here: @@ -115,50 +102,52 @@ of you clone. This will not move a copy to your Python installation directory, b instead create a link in your Python installation pointing to the folder you installed it from, such that any changes you make there are directly reflected in your install. -+ ``git clone https://github.com/MAfarrag/HAPI.git`` -+ ``cd Hapi`` -+ ``activate Hapi`` -+ ``pip install -e .`` ++ `git clone https://github.com/Serapieum-of-alex/Hapi.git` ++ `cd Hapi` ++ `activate Hapi` ++ `pip install -e .` -Alternatively, if you want to avoid using ``git`` and simply want to test the latest -version from the ``master`` branch, you can replace the first line with downloading -a zip archive from GitHub: https://github.com/MAfarrag/HAPI/archive/master.zip -`libraries.io `_. +Alternatively, if you want to avoid using `git` and simply want to test the latest +version from the `master` branch, you can replace the first line with downloading +a zip archive from GitHub: https://github.com/Serapieum-of-alex/Hapi/archive/master.zip +[libraries.io](https://libraries.io/github/Serapieum-of-alex/Hapi). -Install using pip ------------------ +## Install using pip Besides the recommended conda environment setup described above, you can also install -Hapi with ``pip``. For the more difficult to install Python dependencies, it is best to +Hapi with `pip`. For the more difficult to install Python dependencies, it is best to use the conda package manager: -+ ``conda install numpy scipy gdal netcdf4 pyproj`` +```shell +conda install numpy scipy gdal netcdf4 pyproj +``` - -you can check `libraries.io `_. to check versions of the libraries +you can check [libraries.io](https://libraries.io/github/Serapieum-of-alex/Hapi). to check versions of the libraries Then install a release {release} of Hapi (available from release 2018.1) with pip: -+ ``pip install HAPI-Nile=={release}`` - +```shell +pip install HAPI-Nile=={release} +```` -Check if the installation is successful ---------------------------------------- +## Check if the installation is successful To check it the install is successful, go to the examples directory and run the following command: -+ ``python -m Hapi.*******`` +```shell +`python -m Hapi.*******` +``` This should run without errors. -.. note:: +> **Note:** - This documentation was generated on |today| + This documentation was generated on today Documentation for the development version: https://Hapi.readthedocs.org/en/latest/ Documentation for the stable version: - https://Hapi.readthedocs.org/en/stable/ + https://Hapi.readthedocs.org/en/stable/ \ No newline at end of file diff --git a/docs/examples/distributed-model-calib.md b/docs/examples/distributed-model-calib.md new file mode 100644 index 000000000..a5197913c --- /dev/null +++ b/docs/examples/distributed-model-calib.md @@ -0,0 +1,303 @@ +# Distributed Hydrological Model Calibration +The calibration of the Distributed rainfall runoff model follows the same steps of running the model with extra steps to define the calibration algorithm arguments + +## 1- Catchment Object +- Import the Catchment object which is the main object in the distributed model, to read and check the input data, and when the model finish the simulation it stores the results and do the visualization + + +```python + +class Catchment: + + def __init__(self, name, StartDate, EndDate, fmt="%Y-%m-%d", SpatialResolution = 'Lumped', + TemporalResolution = "Daily"): + """ + ============================================================================= + Catchment(name, StartDate, EndDate, fmt="%Y-%m-%d", SpatialResolution = 'Lumped', + TemporalResolution = "Daily") + ============================================================================= + Parameters + ---------- + name : [str] + Name of the Catchment. + StartDate : [str] + starting date. + EndDate : [str] + end date. + fmt : [str], optional + format of the given date. The default is "%Y-%m-%d". + SpatialResolution : TYPE, optional + Lumped or 'Distributed' . The default is 'Lumped'. + TemporalResolution : TYPE, optional + "Hourly" or "Daily". The default is "Daily". + """ +``` +- To instantiate the object you need to provide the `name`, `statedate`, `enddate`, and the `SpatialResolution` + +```python +from Hapi.catchment import Catchment + +start = "2009-01-01" +end = "2011-12-31" +name = "Coello" + +Coello = Catchment(name, start, end, SpatialResolution = "Distributed") +``` + +# Read Meteorological Inputs + + +- First define the directory where the data exist + +```python +Path = Comp + "/data/distributed/coello" +PrecPath = Path + "/prec" +Evap_Path = Path + "/evap" +TempPath = Path + "/temp" +FlowAccPath = Path + "/GIS/acc4000.tif" +FlowDPath = Path + "/GIS/fd4000.tif" +ParPathRun = Path + "/Parameter set-Avg/" +``` +- Then use the each method in the object to read the coresponding data + +```python +Coello.readRainfall(PrecPath) +Coello.readTemperature(TempPath) +Coello.readET(Evap_Path) +Coello.readFlowAcc(FlowAccPath) +Coello.readFlowDir(FlowDPath) +``` +- To read the parameters you need to provide whether you need to consider the snow subroutine or not + +```python +Snow = 0 +Coello.readParameters(ParPathRun, Snow) +``` + +## 2- Lumped Model + +- Get the Lumpde conceptual model you want to couple it with the distributed routing module which in our case HBV + and define the initial condition, and catchment area. + +```python +from Hapi.rrm.hbv_bergestrom92 import HBVBergestrom92 as HBV + +CatchmentArea = 1530 +InitialCond = [0,5,5,5,0] +Coello.readLumpedModel(HBV, CatchmentArea, InitialCond) +``` +- If the Inpus are consistent in dimensions you will get a the following message + +![check_inputs](../img/check_inputs.PNG) + + +- to check the performance of the model we need to read the gauge hydrographs + +```python +Coello.readGaugeTable("Hapi/Data/00inputs/Discharge/stations/gauges.csv", FlowAccPath) +GaugesPath = "Hapi/Data/00inputs/Discharge/stations/" +Coello.readDischargeGauges(GaugesPath, column='id', fmt="%Y-%m-%d") +``` +## 3-Run Object + + +- The `Run` object connects all the components of the simulation together, the `Catchment` object, the `Lake` object and the `distributedrouting` object +- import the Run object and use the `Catchment` object as a parameter to the `Run` object, then call the RunHapi method to start the simulation + +```python +from Hapi.run import Run +Run.RunHapi(Coello) +``` +- the result of the simulation will be stored as attributes in the Catchment object as follow + +```python +""" +Outputs: + 1-statevariables: [numpy attribute] + 4D array (rows,cols,time,states) states are [sp,wc,sm,uz,lv] + 2-qlz: [numpy attribute] + 3D array of the lower zone discharge + 3-quz: [numpy attribute] + 3D array of the upper zone discharge + 4-qout: [numpy attribute] + 1D timeseries of discharge at the outlet of the catchment + of unit m3/sec + 5-quz_routed: [numpy attribute] + 3D array of the upper zone discharge accumulated and + routed at each time step + 6-qlz_translated: [numpy attribute] + 3D array of the lower zone discharge translated at each time step +""" +``` +## 4-Extract Hydrographs + +- The final step is to extract the simulated Hydrograph from the cells at the location of the gauges to compare +- The `extractDischarge` method extracts the hydrographs, however you have to provide in the gauge file the coordinates of the gauges with the same coordinate system of the `FlowAcc` raster + +```python +Coello.extractDischarge(Factor=Coello.GaugesTable['area ratio'].tolist()) + +for i in range(len(Coello.GaugesTable)): + gaugeid = Coello.GaugesTable.loc[i,'id'] + print("----------------------------------") + print("Gauge - " +str(gaugeid)) + print("RMSE= " + str(round(Coello.Metrics.loc['RMSE',gaugeid],2))) + print("NSE= " + str(round(Coello.Metrics.loc['NSE',gaugeid],2))) + print("NSEhf= " + str(round(Coello.Metrics.loc['NSEhf',gaugeid],2))) + print("KGE= " + str(round(Coello.Metrics.loc['KGE',gaugeid],2))) + print("WB= " + str(round(Coello.Metrics.loc['WB',gaugeid],2))) + print("Pearson CC= " + str(round(Coello.Metrics.loc['Pearson-CC',gaugeid],2))) + print("R2 = " + str(round(Coello.Metrics.loc['R2',gaugeid],2))) +``` +- The `extractDischarge` will print the performance metics + + +## 5-Visualization + +- Firts type of visualization we can do with the results is to compare the gauge hydrograph with the simulatied hydrographs +- Call the `plotHydrograph` method and provide the period you want to visualize with the order of the gauge + +```python +gaugei = 5 +plotstart = "2009-01-01" +plotend = "2011-12-31" + +Coello.plotHydrograph(plotstart, plotend, gaugei) +``` +![hydrograph](../img/hydrograph.png) + + +## 6-Animation + +- the best way to visualize time series of distributed data is through visualization, for theis reason, The `Catchment` object has `plotDistributedResults` method which can animate all the results of the model + +```python +""" +============================================================================= +AnimateArray(Arr, Time, NoElem, TicksSpacing = 2, Figsize=(8,8), PlotNumbers=True, + NumSize= 8, Title = 'Total Discharge',titlesize = 15, Backgroundcolorthreshold=None, + cbarlabel = 'Discharge m3/s', cbarlabelsize = 12, textcolors=("white","black"), + Cbarlength = 0.75, Interval = 200,cmap='coolwarm_r', Textloc=[0.1,0.2], + Gaugecolor='red',Gaugesize=100, ColorScale = 1,gamma=1./2.,linthresh=0.0001, + linscale=0.001, midpoint=0, orientation='vertical', rotation=-90,IDcolor = "blue", + IDsize =10, **kwargs) +============================================================================= +Parameters +---------- +Arr : [array] + the array you want to animate. +Time : [dataframe] + dataframe contains the date of values. +NoElem : [integer] + Number of the cells that has values. +TicksSpacing : [integer], optional + Spacing in the colorbar ticks. The default is 2. +Figsize : [tuple], optional + figure size. The default is (8,8). +PlotNumbers : [bool], optional + True to plot the values intop of each cell. The default is True. +NumSize : integer, optional + size of the numbers plotted intop of each cells. The default is 8. +Title : [str], optional + title of the plot. The default is 'Total Discharge'. +titlesize : [integer], optional + title size. The default is 15. +Backgroundcolorthreshold : [float/integer], optional + threshold value if the value of the cell is greater, the plotted + numbers will be black and if smaller the plotted number will be white + if None given the maxvalue/2 will be considered. The default is None. +textcolors : TYPE, optional + Two colors to be used to plot the values i top of each cell. The default is ("white","black"). +cbarlabel : str, optional + label of the color bar. The default is 'Discharge m3/s'. +cbarlabelsize : integer, optional + size of the color bar label. The default is 12. +Cbarlength : [float], optional + ratio to control the height of the colorbar. The default is 0.75. +Interval : [integer], optional + number to controlthe speed of the animation. The default is 200. +cmap : [str], optional + color style. The default is 'coolwarm_r'. +Textloc : [list], optional + location of the date text. The default is [0.1,0.2]. +Gaugecolor : [str], optional + color of the points. The default is 'red'. +Gaugesize : [integer], optional + size of the points. The default is 100. +IDcolor : [str] + the ID of the Point.The default is "blue". +IDsize : [integer] + size of the ID text. The default is 10. +ColorScale : integer, optional + there are 5 options to change the scale of the colors. The default is 1. + 1- ColorScale 1 is the normal scale + 2- ColorScale 2 is the power scale + 3- ColorScale 3 is the SymLogNorm scale + 4- ColorScale 4 is the PowerNorm scale + 5- ColorScale 5 is the BoundaryNorm scale + ------------------------------------------------------------------ + gamma : [float], optional + value needed for option 2 . The default is 1./2.. + linthresh : [float], optional + value needed for option 3. The default is 0.0001. + linscale : [float], optional + value needed for option 3. The default is 0.001. + midpoint : [float], optional + value needed for option 5. The default is 0. + ------------------------------------------------------------------ +orientation : [string], optional + orintation of the colorbar horizontal/vertical. The default is 'vertical'. +rotation : [number], optional + rotation of the colorbar label. The default is -90. +**kwargs : [dict] + keys: + Points : [dataframe]. + dataframe contains two columns 'cell_row', and cell_col to + plot the point at this location + +Returns +------- +animation.FuncAnimation. + +""" +``` +- choose the period of time you want to animate and the result (total discharge, upper zone discharge, soil moisture,...) + +```python +plotstart = "2009-01-01" +plotend = "2009-02-01" + +Anim = Coello.plotDistributedResults(plotstart, plotend, Figsize=(9,9), Option = 1,threshold=160, PlotNumbers=True, + TicksSpacing = 5,Interval = 200, Gauges=True, cmap='inferno', Textloc=[0.1,0.2], + Gaugecolor='red',ColorScale = 1, IDcolor='blue', IDsize=25) +``` +![Animation](../img/anim.gif) + + +- to save the animation + + - Please visit https://ffmpeg.org/ and download a version of ffmpeg compitable with your operating system + - Copy the content of the folder and paste it in the "c:/user/.matplotlib/ffmpeg-static/" + or + + - define the path where the downloaded folder "ffmpeg-static" exist to matplotlib using the following lines + +```python +import matplotlib as mpl +mpl.rcParams['animation.ffmpeg_path'] = "path where you saved the ffmpeg.exe/ffmpeg.exe" +``` +```python +Path = SaveTo + "anim.gif" +Coello.saveAnimation(VideoFormat="gif",Path=Path,SaveFrames=3) +``` +## 7-Save the result into rasters + +- To save the results as rasters provide the period and the path + +```python +StartDate = "2009-01-01" +EndDate = "2010-04-20" +Prefix = 'Qtot_' + +Coello.saveResults(FlowAccPath, Result=1, StartDate=StartDate, EndDate=EndDate, Path="F:/02Case studies/Coello/Hapi/Model/results/", Prefix=Prefix) +``` \ No newline at end of file diff --git a/docs/examples/distributed-model-run.md b/docs/examples/distributed-model-run.md new file mode 100644 index 000000000..c187ba6fa --- /dev/null +++ b/docs/examples/distributed-model-run.md @@ -0,0 +1,132 @@ +# Distributed Hydrological Model +After preparing all the meteorological, GIS inputs required for the model, and Extracting the parameters for the catchment + +```python +import numpy as np +import datetime as dt +from osgeo import gdal +from Hapi.calibration import Calibration +import Hapi.rrm.hbv_bergestrom92 as HBV + +import statista.descriptors as metrics + +Path = Comp + "/data/distributed/coello" +PrecPath = Path + "/prec" +Evap_Path = Path + "/evap" +TempPath = Path + "/temp" +FlowAccPath = Path + "/GIS/acc4000.tif" +FlowDPath = Path + "/GIS/fd4000.tif" +CalibPath = Path + "/calibration" +SaveTo = Path + "/results" + +AreaCoeff = 1530 +#[sp,sm,uz,lz,wc] +InitialCond = [0,5,5,5,0] +Snow = 0 + +# Create the model object and read the input data + +Sdate = '2009-01-01' +Edate = '2011-12-31' +name = "Coello" +Coello = Calibration(name, Sdate, Edate, SpatialResolution = "Distributed") + +# Meteorological & GIS Data +Coello.readRainfall(PrecPath) +Coello.readTemperature(TempPath) +Coello.readET(Evap_Path) + +Coello.readFlowAcc(FlowAccPath) +Coello.readFlowDir(FlowDPath) + +# Lumped Model +Coello.readLumpedModel(HBV, AreaCoeff, InitialCond) + +# Gauges Data +Coello.readGaugeTable(Path+"/stations/gauges.csv", FlowAccPath) +GaugesPath = Path+"/stations/" +Coello.readDischargeGauges(GaugesPath, column='id', fmt="%Y-%m-%d") + + + +``` +## Spatial Variability Object + +- The `DistParameters` distribute the parameter vector on the cells following some spatial logic (same set of parameters for all cells, different parameters for each cell, HRU, different parameters for each class in an additional map) + +```python +from Hapi.rrm.distparameters import DistParameters as DP + +raster = gdal.Open(FlowAccPath) +#------------- +# for lumped catchment parameters +no_parameters = 12 +klb = 0.5 +kub = 1 +#------------ +no_lumped_par = 1 +lumped_par_pos = [7] + +SpatialVarFun = DP(raster, no_parameters, no_lumped_par=no_lumped_par, + lumped_par_pos=lumped_par_pos,Function=2, Klb=klb, Kub=kub) +# calculate no of parameters that optimization algorithm is going to generate +SpatialVarFun.ParametersNO + + +``` +## Define the objective function + +```python + +coordinates = Coello.GaugesTable[['id','x','y','weight']][:] + +# define the objective function and its arguments +OF_args = [coordinates] + +def objective_function(Qobs, Qout, q_uz_routed, q_lz_trans, coordinates): + Coello.extractDischarge() + all_errors=[] + # error for all internal stations + for i in range(len(coordinates)): + all_errors.append((metrics.rmse(Qobs.loc[:,Qobs.columns[0]],Coello.Qsim[:,i]))) #*coordinates.loc[coordinates.index[i],'weight'] + print(all_errors) + error = sum(all_errors) + return error + + Coello.read_objective_function(objective_function, OF_args) +``` +## Calibration algorithm Arguments + +- Create the options dictionary all the optimization parameters should be passed to the optimization object inside the option dictionary: + +to see all options import Optimizer class and check the documentation of the +method setOption + +```pythonthon + +ApiObjArgs = dict(hms=50, hmcr=0.95, par=0.65, dbw=2000, fileout=1, + filename=SaveTo + "/Coello_"+str(dt.datetime.now())[0:10]+".txt") + +for i in range(len(ApiObjArgs)): + print(list(ApiObjArgs.keys())[i], str(ApiObjArgs[list(ApiObjArgs.keys())[i]])) + +pll_type = 'POA' +pll_type = None + +ApiSolveArgs = dict(store_sol=True, display_opts=True, store_hst=True,hot_start=False) + +OptimizationArgs=[ApiObjArgs, pll_type, ApiSolveArgs] + +``` +## Run Calibration algorithm + +```python +cal_parameters = Coello.runCalibration(SpatialVarFun, OptimizationArgs,printError=0) + +``` +## Save results + +```python +SpatialVarFun.Function(Coello.Parameters, kub=SpatialVarFun.Kub, klb=SpatialVarFun.Klb) +SpatialVarFun.saveParameters(SaveTo) +``` \ No newline at end of file diff --git a/docs/Hydrological model/GIS_inputs.rst b/docs/examples/gis-inputs.md similarity index 85% rename from docs/Hydrological model/GIS_inputs.rst rename to docs/examples/gis-inputs.md index 3e1274ce2..312021062 100644 --- a/docs/Hydrological model/GIS_inputs.rst +++ b/docs/examples/gis-inputs.md @@ -1,27 +1,20 @@ -***** -GIS Inputs -***** +# GIS Inputs In order to determine the direction of the flow Hapi used the D8 `flow direction` and `flow accumulation` rasters. Since the avialable DEMs are in resolution of few tens of meters (30 or 90 m) and usually hydrological models are build to represent units much bigger than that so we need to get the River network from these fine resolution DEMs first and then burn the river into courser resolution (i.e. 5km ) that we want to use as a resolution for our distributed hydrological model. - .. image:: /img/flowdirection.png - :width: 400pt +![Flow Direction](../img/flowdirection.png){ width="400" } - .. image:: /img/UZ.png - :width: 400pt +![Upper Zone](../img/UZ.png){ width="400" } -Burning the River into the DEM -######## +# Burning the River into the DEM 1- Create suitable Flow accumulation After deriving the the flow accumulation form the fine resolution DEM (30 or 90 m) use the raster calulator tool in ArcMap/QGIS to obtain a raster of the cells that has accumulation more than 6 times the acumulation in the corse resolution -lets say you want to build the hydrological model for 5km resolution DEM and you have a 100x100 m2 DEM [(5000x5000) / (30x30)] / 30 and use the resulted number in the raster calculator -`` -"Flow_acc" > 75000 -`` +##### lets say you want to build the hydrological model for 5km resolution DEM and you have a 100x100 m2 DEM [(5000x5000) / (30x30)] / 30 and use the resulted number in the raster calculator +##### "Flow_acc" > 75000 output will be the raster `acc75000.tif` 2- Create Stream Links @@ -73,4 +66,4 @@ the following steps are done in QGIS this step we will multiply the DEM (5km) with the `Standardized_Burn.tif` raster - output will be the raster `DEM_Burn.tif` + output will be the raster `DEM_Burn.tif` \ No newline at end of file diff --git a/docs/Hydrological model/HBV.rst b/docs/examples/hbv.md similarity index 91% rename from docs/Hydrological model/HBV.rst rename to docs/examples/hbv.md index e1a580274..83dd41c40 100644 --- a/docs/Hydrological model/HBV.rst +++ b/docs/examples/hbv.md @@ -1,6 +1,4 @@ -***** -HBV Lumped conceptual model -***** +# HBV Lumped conceptual model The Hydrologiska Byrans Vattenbalansavdelning (HBV) model was introduced back in 1972 by the Swedisch Meteological and Hydrological Institute (SMHI). The HBV model is mainly used for runoff simulation and hydrological forecasting. The model is based on the HBV [BergstrΓΆm, 1992] model. However, the hydrological routing represent in HBV by a triangular function controlled by the MAXBAS parameter has been removed. Instead, Muskingum routing model is used @@ -12,7 +10,7 @@ The HBV model [BergstrΓΆm, 1992] is usually run with daily time steps, but highe HBV model consists of three main components: -- Snow Subroutine :ref:`snow` +- [Snow Subroutine](#snow) - Soil Moisture @@ -37,15 +35,11 @@ The model has 15 free parameters, values of which are found by calibration, some the 15 parameter by order are [`tt`,`rfcf`,`sfcf`,`cfmax`,`cwh`,`cfr`,`fc`,`beta`,`etf`,`lp`,`k0`,`k1`,`k2`,`uzl`,`perc`]. Two parameters are added for the correction of the rainfall values `rfcf` and for the correction of the calculated evapotranspiration values `Etf`, and in case the catchment does not have a snow then the HBV model used 10 parameter (excluding the first 5 parameters) - .. image:: /img/HBV_buckets.png - :width: 400pt + ![HBV Buckets](../img/HBV_buckets.png) [BergstrΓΆm, 1992] -.. _snow: - -Snow -######## +# Snow The snow routine controls snow accumulation and melt. The precipitation accumulates as snow when the air temperature drops below a temperature threshold value (TT). snow accumulation is adjusted by a free parameter, Sfcf, the snowfall correction factor. @@ -53,22 +47,19 @@ If temperature is TT, precipitation occurs as snowfall, and is added to the dry Melting starts with temperatures above the threshold, TT, according to a simple degree-day -`` Snow MELT = Cfmax * (T - TT) ; temp > TT Snow Refreezing = Cfr * Cfmax * (TT - T ) ; temp < TT where: Snow MELT & Snow Refreezing are in (mm/day) Cfmax = degree-day factor (mm/Β°C Β· day) -TT = temperature threshold (C). -`` +**TT = temperature threshold (C).** The maximum capacity of liquid water the snow can hold (holding water capacity WHC) has to be exceeded before any runoff is generated. A refreezing coefficient, which is used to refreeze free water in the snow if snowmelt is interrupted. The snow routine of the HBV model has primarily five free parameters that have to be estimated by calibration: `tt`,`sfcf`,`cfmax`,`cwh`,`cfr`. -Soil moisture -######## +# Soil moisture The soil moisture accounting routine computes an index of the wetness of the entire basin and integrates interception and soil moisture storage. Soil moisture subroutine is controlled by three free parameters, FC, BETA and LP. FC (Field capacity) is the maximum soil moisture storage in the basin and BETA (power parameter) determines the relative contribution to runoff from a millimeter of rain or snowmelt at a given soil moisture deficit. @@ -77,35 +68,29 @@ The soil moisture accounting routine computes an index of the wetness of the ent LP controls the shape of the reduction curve for potential evaporation. At soil moisture values below LP the actual evapotranspiration will be reduced. -To accounts for temperature anomalies a correction factor based on mean daily air temperatures and long term averages is used. -`` +**To accounts for temperature anomalies a correction factor based on mean daily air temperatures and long term averages is used.** Ea = (1 + (T - Tm) * ETF)*Ep where: Ea is calculated actual evapotranspiration Ecorr is evapotranspiration correction factor T is temperature (C) Tm is monthly long term average temperature (C) -Ep is monthly long term average potential evapotranspiration -`` +**Ep is monthly long term average potential evapotranspiration** ![Beta](../img/Evapotranspiration.png) -Runoff response -######## +# Runoff response The runoff response routine transforms excess water from the soil moisture routine to discharge. The routine consists of two reservoirs with three free parameters: three recession coefficients, `K0`, `K1` and `K2`, a threshold `UZL`, and a constant percolation rate, `PERC`. -Lake -######## +# Lake lakes can be included explicitly using a storage discharge curve relationship which requires dividing the catchment into sub-basins defined by outlet of lakes. In case of the existence of a lake in the catchment, the outflow from basins upstream of the lake will be summed and be used as an inflow to the lake. Storage in the lake will be computed according to water stage/storage curve or water stage/lake surface area table and outflow can be obtained from a rating curve (IHMS 2010). Lakes have a significant impact on the dynamics of runoff process and the routing and therefore modelled explicitly, and for that the presence of a lake in the catchment is an important factor for choosing substructure based on sub basins. (LindstrΓΆm et al. 1997) - .. image:: /img/lake.png - :width: 400pt +![lake](../img/lake.png) -References -######## +# References - BergstrΓΆm, Sten. 1992. β€œThe HBV Model - Its Structure and Applications.” Smhi Rh 4(4): 35. + BergstrΓΆm, Sten. 1992. β€œThe HBV Model - Its Structure and Applications.” Smhi Rh 4(4): 35. \ No newline at end of file diff --git a/docs/Hydrological model/Lumped_calib.rst b/docs/examples/lumped-model-calibration.md similarity index 93% rename from docs/Hydrological model/Lumped_calib.rst rename to docs/examples/lumped-model-calibration.md index 8812a9035..bd63a11e0 100644 --- a/docs/Hydrological model/Lumped_calib.rst +++ b/docs/examples/lumped-model-calibration.md @@ -1,18 +1,14 @@ -***** -Lumped Model Calibration -***** +# Lumped Model Calibration To calibrate the HBV lumped model inside Hapi you need to follow the same steps of running the lumped model with few extra steps to define the requirement of the calibration algorithm. -.. code-block:: py - :linenos: - +```python import pandas as pd import datetime as dt import Hapi.rrm.hbv_bergestrom92 as HBVLumped from Hapi.calibration import Calibration - from Hapi.rrm.routing import Routing + from Hapi.routing import Routing from Hapi.run import Run import statista.metrics as metrics @@ -62,12 +58,10 @@ To calibrate the HBV lumped model inside Hapi you need to follow the same steps OF=metrics.rmse Coello.read_objective_function(metrics.rmse, OF_args) - +``` - after defining all the components of the lumped model, we have to define the calibration arguments -.. code-block:: py - :linenos: - +```python ApiObjArgs = dict(hms=100, hmcr=0.95, par=0.65, dbw=2000, fileout=1, xinit =0, filename=Path + "/Lumped_History"+str(dt.datetime.now())[0:10]+".txt") @@ -80,15 +74,14 @@ To calibrate the HBV lumped model inside Hapi you need to follow the same steps ApiSolveArgs = dict(store_sol=True, display_opts=True, store_hst=True, hot_start=False) OptimizationArgs=[ApiObjArgs, pll_type, ApiSolveArgs] - +``` - Run Calibration -.. code-block:: py - :linenos: - +```python cal_parameters = Coello.lumpedCalibration(Basic_inputs, OptimizationArgs, printError=None) print("Objective Function = " + str(round(cal_parameters[0],2))) print("Parameters are " + str(cal_parameters[1])) print("Time = " + str(round(cal_parameters[2]['time']/60,2)) + " min") +``` \ No newline at end of file diff --git a/docs/examples/lumped-model-run.md b/docs/examples/lumped-model-run.md new file mode 100644 index 000000000..2faf65460 --- /dev/null +++ b/docs/examples/lumped-model-run.md @@ -0,0 +1,107 @@ +# Lumped Model Run +To run the HBV lumped model inside Hapi you need to prepare the meteorological inputs (rainfall, temperature and potential evapotranspiration), HBV parameters, and the HBV model (you can load BergstrΓΆm, 1992 version of HBV from Hapi ) + +- First load the prepared lumped version of the HBV module inside Hapi, the triangular routing function and the wrapper function that runs the lumped model `RUN`. + +```python +import Hapi.rrm.hbv_bergestrom92 as HBVLumped +from Hapi.run import Run +from Hapi.catchment import Catchment +from Hapi.routing import Routing +``` +- read the meteorological data, data has be in the form of numpy array with the following order [rainfall, ET, Temp, Tm], ET is the potential evapotranspiration, Temp is the temperature (C), and Tm is the long term monthly average temperature. + +```python +Parameterpath = Comp + "/data/lumped/Coello_Lumped2021-03-08_muskingum.txt" +MeteoDataPath = Comp + "/data/lumped/meteo_data-MSWEP.csv" + +### meteorological data +start = "2009-01-01" +end = "2011-12-31" +name = "Coello" +Coello = Catchment(name, start, end) +Coello.readLumpedInputs(MeteoDataPath) +``` +- Meteorological data + +```python +start = "2009-01-01" +end = "2011-12-31" +name = "Coello" +Coello = Catchment(name, start, end) +Coello.readLumpedInputs(MeteoDataPath) +``` +- Lumped model +prepare the initial conditions, cathcment area and the lumped model. + +```python +# catchment area +AreaCoeff = 1530 +# [Snow pack, Soil moisture, Upper zone, Lower Zone, Water content] +InitialCond = [0,10,10,10,0] + +Coello.readLumpedModel(HBVLumped, AreaCoeff, InitialCond) +``` +- Load the pre-estimated parameters + snow option (if you want to simulate snow accumulation and snow melt or not) + +```python +Snow = 0 # no snow subroutine +# if routing using Maxbas True, if Muskingum False +Coello.readParameters(Parameterpath, Snow) +``` +- Prepare the routing options. + +```python +# RoutingFn = Routing.TriangularRouting2 +RoutingFn = Routing.Muskingum_V +Route = 1 +``` +- now all the data required for the model are prepared in the right form, now you can call the `runLumped` wrapper to initiate the calculation + +```python +Run.runLumped(Coello, Route, RoutingFn) +``` +to calculate some metrics for the quality assessment of the calculate discharge the `statista.descriptors` contains some +metrics like `rmse`, `nse`, `kge` and `wb` , you need to load it, a measured time series of doscharge for the same +period of the simulation is also needed for the comparison. + +all methods in `statista.descriptors` takes two numpy arrays of the same length and return real number. + +```python +import statista.descriptors as metrics + +Metrics = dict() +Qobs = Coello.QGauges['q'] + +Metrics['RMSE'] = metrics.rmse(Qobs, Coello.Qsim['q']) +Metrics['NSE'] = metrics.nse(Qobs, Coello.Qsim['q']) +Metrics['NSEhf'] = metrics.nse_hf(Qobs, Coello.Qsim['q']) +Metrics['KGE'] = metrics.kge(Qobs, Coello.Qsim['q']) +Metrics['WB'] = metrics.wb(Qobs, Coello.Qsim['q']) + +print("RMSE= " + str(round(Metrics['RMSE'],2))) +print("NSE= " + str(round(Metrics['NSE'],2))) +print("NSEhf= " + str(round(Metrics['NSEhf'],2))) +print("KGE= " + str(round(Metrics['KGE'],2))) +print("WB= " + str(round(Metrics['WB'],2))) +``` +To plot the calculated and measured discharge import matplotlib + +```python +gaugei = 0 +plotstart = "2009-01-01" +plotend = "2011-12-31" +Coello.plotHydrograph(plotstart, plotend, gaugei, Title= "Lumped Model") +``` +![lumped-model](../img/lumpedmodel.png) + +- To save the results + +```python +StartDate = "2009-01-01" +EndDate = "2010-04-20" + +Path = SaveTo + "Results-Lumped-Model" + str(dt.datetime.now())[0:10] + ".txt" +Coello.saveResults(Result=5, StartDate=StartDate, EndDate=EndDate, Path=Path) +``` \ No newline at end of file diff --git a/docs/Hydrological model/Meteo_Inputs.rst b/docs/examples/meteo-inputs.md similarity index 66% rename from docs/Hydrological model/Meteo_Inputs.rst rename to docs/examples/meteo-inputs.md index 0474f3c73..e4802f8e4 100644 --- a/docs/Hydrological model/Meteo_Inputs.rst +++ b/docs/examples/meteo-inputs.md @@ -1,14 +1,10 @@ -***** -Rainfall Runoff Model Inputs -***** +# Rainfall Runoff Model Inputs The required inputs for the distributed model is divided into Meteorological, GIS inputs and Distributed model parameters - .. image:: /img/process.png - :width: 400pt +![process](../img/process.png) -Meteorological Inputs -######## +# Meteorological Inputs To be able to run the hydrologic simulation with Hapi the following meteorological inputs are required - rainfall @@ -19,13 +15,11 @@ To be able to run the hydrologic simulation with Hapi the following meteorologic Distributed meteorological data can be obtain from gauge data with some interpolation method or from remote sensing data -Remote Sensing Module -######## +# Remote Sensing Module The remote sensing module includes two classes to download ECMWF, and CHRIPS data -CHRIPS -######## +# CHRIPS The Climate Hazards Group InfraRed Precipitation with Station data (CHIRPS) is a quasi-global rainfall data set. As its title suggests it combines data from real-time observing meteorological stations with infra-red data to estimate precipitation. The data set runs from 1981 to the near present. CHIRPS incorporates 0.05Β° resolution satellite imagery with in-situ station data to create gridded rainfall time series for trend analysis and seasonal drought monitoring @@ -36,8 +30,9 @@ CHRIPS data are uploaded into a ftp server therefore and can be downloaded throu - First import the class from the remotesensing module - - from Hapi.remotesensing import CHIRPS +```python +from Hapi.remotesensing import CHIRPS +``` - Create the object with the following information - Period of time (start and end date) @@ -45,81 +40,74 @@ CHRIPS data are uploaded into a ftp server therefore and can be downloaded throu - Extend (Longitude/Latitude) - Path (directory to save the downloaded data) -.. code:: py - - StartDate = '2009-01-01' - EndDate = '2009-01-10' - Time = 'daily' - lat = [4.190755,4.643963] - lon = [-75.649243,-74.727286] - Path = "directory to save the data" - Coello = CHIRPS(StartDate=StartDate, EndDate=EndDate, Time=Time, - latlim=lat , lonlim=lon, Path=Path) - +```python +StartDate = '2009-01-01' +EndDate = '2009-01-10' +Time = 'daily' +lat = [4.190755,4.643963] +lon = [-75.649243,-74.727286] +Path = "directory to save the data" +Coello = CHIRPS(StartDate=StartDate, EndDate=EndDate, Time=Time, + latlim=lat , lonlim=lon, Path=Path) +``` - Call the `Download` method -.. code:: py - - Coello.Download() - +```python +Coello.Download() +``` - A Progress bar will appear and be updated with percent of the download - .. image:: /img/progress.png - :width: 400pt - - +![progress](../img/progress.png) - If the period is long and the Download method can run in parallel, to activate the parallel mode enter the number of cores with the keyword argument `cores` -.. code:: py - - Coello.Download(cores=4) +```python +Coello.Download(cores=4) +``` - -ECMWF -######## +# ECMWF ERA-Interim data set is a global atmospheric reanalysis that is available from 1 January 1979 to 31 August 2019 The ERA-Interim data assimilation and forecast suite produces: β€’ four analyses per day, at 00, 06, 12 and 18 UTC; β€’ two 10-day forecasts per day, initialized from analyses at 00 and 12 UTC -- Most archived ERA-Interim data can be downloaded from the ECMWF Data Server at `http://data.ecmwf.int/data `_. +- Most archived ERA-Interim data can be downloaded from the ECMWF Data Server at [http://data.ecmwf.int/data](http://data.ecmwf.int/data). - The ERA-Interim Archive is part of ECMWF’s Meteorological Archive and Retrieval System (MARS), which is accessible to registered users - The RemoteSensing and the ECMWF classes can retrieve the data from the ECMWF servers, if you are registered and setup the API Key in your machine so inorder to be able to use the following code to download ECMWF data you need to -- register and setup your account in the `ECMWF website `_. +- register and setup your account on the [ECMWF website](https://apps.ecmwf.int/registration/). -- Install ECMWF key `instruction are here `_. +- Install ECMWF key β€” [instructions here](https://confluence.ecmwf.int/display/WEBAPI/Access+ECMWF+Public+Datasets#AccessECMWFPublicDatasets-key). - ERA-Interim data set has a lot of meteorological variables which you can download - You need to provide the name of the variable using the `Variables` object - `Variables` contains the tame of the variable you need to give to the `ECMWF` object to get and the unit and description -.. code:: py - from Hapi.remotesensing import Variables - Vars = Variables('daily') - Vars.__str__() - - -For the information about the ECMWF data `https://apps.ecmwf.int/codes/grib/param-db/ `_. - -.. code:: py - - StartDate = '2009-01-01' - EndDate = '2009-01-10' - Time = 'daily' - lat = [4.190755,4.643963] - lon = [-75.649243,-74.727286] - Path = "/data/satellite_data/" - # Temperature, Evapotranspiration - variables = ['T','E'] - - Coello = RS(StartDate=StartDate, EndDate=EndDate, Time=Time, - latlim=lat , lonlim=lon, Path=Path, Vars=variables) - - Coello.ECMWF(Waitbar=1) +```python +from Hapi.remotesensing import Variables +Vars = Variables('daily') +Vars.__str__() +``` + +For information about ECMWF data, see [https://apps.ecmwf.int/codes/grib/param-db/](https://apps.ecmwf.int/codes/grib/param-db/). + +```python +StartDate = '2009-01-01' +EndDate = '2009-01-10' +Time = 'daily' +lat = [4.190755,4.643963] +lon = [-75.649243,-74.727286] +Path = "/data/satellite_data/" +# Temperature, Evapotranspiration +variables = ['T','E'] + +Coello = RS(StartDate=StartDate, EndDate=EndDate, Time=Time, + latlim=lat , lonlim=lon, Path=Path, Vars=variables) + +Coello.ECMWF(Waitbar=1) +``` \ No newline at end of file diff --git a/docs/examples/muskingum.md b/docs/examples/muskingum.md new file mode 100644 index 000000000..b29eb4c78 --- /dev/null +++ b/docs/examples/muskingum.md @@ -0,0 +1,47 @@ +# Muskingum + +Muskingum is a hydrologic-routing method which employs the equation +of continuity to predict magnitude, volume and temporal patterns of +flow as it translates downstream of a channel. + +$$ +I - Q = \frac{dS}{dt} +$$ + +![muskingum](../img/muskingum1.png) + +![muskingum1](../img/muskingum2.png) + +Channel routing functions of inflow, outflow and storage where +storage can be considered as two parts, prism & wedge storage. + +$$ +S = K \cdot [x \cdot I^{m} + (1 - x) \cdot Q^{m}] +$$ + +Where `k` is the travel time constant and `x` are weighting +coefficient to determine the linearity of the water surface, and it +ranges between 0 & 0.5, and `m` is an exponential constant varies +from 0.6 for rectangle channel to 1. + +For Muskingum version of the channel routing equation `m` equals one +which made the relation between `S` and `I`, `Q`. Using coefficient +`k` & `x` three weights can be calculated as follow: + +$$ +C_1 = \frac{\Delta t - 2KX}{2K(1 - X) + \Delta t} +$$ + +$$ +C_2 = \frac{\Delta t + 2KX}{2K(1 - X) + \Delta t} +$$ + +$$ +C_3 = \frac{2K(1 - X) - \Delta t}{2K(1 - X) + \Delta t} +$$ + +To route the inflow hydrograph: + +$$ +Q = C_1 \cdot I_{j+1} + C_2 \cdot I_{j} + C_3 \cdot Q_{j} +$$ diff --git a/docs/Hydrological model/Parameters.rst b/docs/examples/parameters.md similarity index 70% rename from docs/Hydrological model/Parameters.rst rename to docs/examples/parameters.md index de604c58e..91ead0402 100644 --- a/docs/Hydrological model/Parameters.rst +++ b/docs/examples/parameters.md @@ -1,69 +1,60 @@ -***** -Parameters -***** +# Parameters the 15 free parameters used by the HBV model for each cell can be obtained through calibration, However it it an extremely difficult task to keep the model simple and minimize uncertainty with such large number of parameters, Therefore in Hapi 1.0.0 we integrate the global hydrological parameters obtained by Beck et al., (2016), to reduce model complexity and uncertainty of parameters and to help obtain the best set of parameter during the calibration. Based on Beck et al., (2016) there is 10 sets of global parameters which are derived for catchment with good quality of data and are calibratedvery well calibrated, afterwards golbal parameters are derived by transfering parameters to from the good calibrated catchments to global grid of 0.5 degree cells where each cell receives the parameters of the best 10 catchments that have similar climatic and physiographic characteristics -Extract Distributed Parameters ----------- +### Extract Distributed Parameters The only input we need to extract parameters to our catchment is the DEM or any raster that has the full extent and allignment of the catchment and use the `Inputs` object to select the set of parameters we want - import the class from the inputs module - - from Hapi.inputs import Inputs - +```python +from Hapi.inputs import Inputs +``` - define the paths to the DEM and the directory to save the parameters -.. code:: py - - dem_path = "../../data/GIS/Hapi_GIS_Data/acc4000.tif" - outputpath = "../../data/parameters/03/" - +```python +dem_path = "../../data/GIS/Hapi_GIS_Data/acc4000.tif" +outputpath = "../../data/parameters/03/" +``` - call the `extractParameters` method -.. code:: py - - Inputs.extractParameters(dem_path, '03', AsRaster=True, SaveTo=outputpath) - +```python +Inputs.extractParameters(dem_path, '03', AsRaster=True, SaveTo=outputpath) +``` -Extract Calibration boundaries for the Parameters -######## +# Extract Calibration boundaries for the Parameters You will find the following example in the `ExtractParametersBounds.py` file under the folder `/Examples/Create Inputs`. There is no need for copy paste work. To Extract the parameters range needed for the Calibration you have to prepare a shapefile of the catchment you are developing a distributed model and read it using `geopandas`, -.. code:: py - - import geopandas as gpd - import numpy as np - import pandas as pd - import Hapi.inputs as IN - - BasinF = "Path to shapefile" - Basin = gpd.read_file(BasinF) - # parameters name with the same order inside the Input module - ind = ["tt","sfcf","cfmax","cwh","cfr","fc","beta","lp","k0","k1","k2","uzl","perc","maxbas"] - Par = pd.DataFrame(index = ind) +```python +import geopandas as gpd +import numpy as np +import pandas as pd +import Hapi.inputs as IN +BasinF = "Path to shapefile" +Basin = gpd.read_file(BasinF) +# parameters name with the same order inside the Input module +ind = ["tt","sfcf","cfmax","cwh","cfr","fc","beta","lp","k0","k1","k2","uzl","perc","maxbas"] +Par = pd.DataFrame(index = ind) +``` the `inputs` module in Hapi has a `extractParametersBoundaries` method to overlay the basin shapefile with the global parameters rasters and extract the max and min parameter values within the basin and plots your basin shapefile in top of the world map to make sure of the projection transformation from whatever projection your basin shapefile to the `WGS64` that the parameters rasters have -.. code:: py - - # extract parameters boundaries - Par['UB'], Par['LB'] = IN.extractParametersBoundaries(Basin) - +```python +# extract parameters boundaries +Par['UB'], Par['LB'] = IN.extractParametersBoundaries(Basin) +``` To extract the parameters from one of the ten scenarios developed to derive the Global model `extractParameters` method takes the number of the scenario as a string and return the parameters -.. code:: py - - # extract parameters in a specific scenarion from the 10 scenarios - Par['1'] = IN.extractParameters(Basin,"01") - -the extracted parameters needs to be modified incase you are not considering the snow bucket the first 5 parameters are disregarded +```python +# extract parameters in a specific scenarion from the 10 scenarios +Par['1'] = IN.extractParameters(Basin,"01") +``` +the extracted parameters needs to be modified incase you are not considering the snow bucket the first 5 parameters are disregarded \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 000000000..529117591 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,57 @@ +# Hapi - Hydrological library for Python + +[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.4686056.svg)](https://doi.org/10.5281/zenodo.4686056) +[![PyPI](https://img.shields.io/pypi/v/Hapi-nile)](https://pypi.org/project/HAPI-Nile/) +[![Conda](https://img.shields.io/conda/v/conda-forge/hapi?label=conda-forge)](https://anaconda.org/conda-forge/hapi) +[![Python](https://img.shields.io/pypi/pyversions/hapi-nile)](https://pypi.org/project/HAPI-Nile/) +[![Downloads](https://static.pepy.tech/badge/hapi-nile)](https://pepy.tech/project/hapi-nile) +[![Platforms](https://anaconda.org/conda-forge/hapi/badges/platforms.svg)](https://anaconda.org/conda-forge/hapi) + +![Hapi](img/Hapi.png){ width="400" } + +**Hapi** is a Python package providing a fast and flexible way to build hydrological models with +different spatial representations (lumped, semi-distributed, and conceptual distributed) using HBV96. +The package allows developers to change the structure of the defined conceptual model or to provide +their own model. It contains two routing functions: Muskingum-Cunge and MAXBAS triangular function. + +![Model Structure](img/Picture1.png){ width="400" } + +## Main Features + +- Modified version of HBV96 hydrological model (Bergstrom, 1992) with 15 parameters when + considering snow processes, and 10 parameters without snow, plus 2 Muskingum routing parameters +- GIS modules to prepare meteorological inputs and perform preprocessing (align rasters with the + DEM), plus methods to manipulate distributed data (rasters, NetCDF, shapefiles) +- Sensitivity analysis module based on One-At-a-Time (OAT) and Sobol interaction analysis + (Rusli et al., 2015) +- Statistical module with interpolation methods, frequency analysis distributions, and Maximum + Likelihood parameter estimation +- Visualization module for animating distributed model results and meteorological inputs +- Optimization module for calibrating the model using Harmony Search + +The recent version integrates global hydrological parameters from Beck et al. (2016) to reduce +model complexity and parameter uncertainty. + +## IHE-Delft Sessions + +- April 14-15: Two-day session for Masters and PhD students at IHE-Delft β€” + [Day 1](https://youtu.be/HbmUdN9ehSo), [Day 2](https://youtu.be/m7kHdOFQFIY) + +## Citation + +For using Hapi please cite Farrag et al. (2021) and Farrag & Corzo (2021). + +## References + +Farrag, M. & Corzo, G. (2021) MAfarrag/Hapi: Hapi. doi:10.5281/ZENODO.4662170 + +Farrag, M., Perez, G. C. & Solomatine, D. (2021) Spatio-Temporal Hydrological Model Structure +and Parametrization Analysis. J. Mar. Sci. Eng. 9(5), 467. doi:10.3390/jmse9050467 + +Beck, H. E. et al. (2016) Global-scale regionalization of hydrologic model parameters. +doi:10.1002/2015WR018247 + +Bergstrom, S. (1992) The HBV model - its structure and applications. SMHI RH 4(4), 35. + +Rusli, S. R., Yudianto, D. & Liu, J. (2015) Effects of temporal variability on HBV model +calibration. Water Sci. Eng. 8(4), 291-300. doi:10.1016/j.wse.2015.12.002 diff --git a/docs/index.rst b/docs/index.rst deleted file mode 100644 index d02338550..000000000 --- a/docs/index.rst +++ /dev/null @@ -1,179 +0,0 @@ - .. image:: https://zenodo.org/badge/DOI/10.5281/zenodo.4686056.svg - :target: https://doi.org/10.5281/zenodo.4686056 - - .. image:: https://img.shields.io/badge/binder-Hapi-F5A252.svg?logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAFkAAABZCAMAAABi1XidAAAB8lBMVEX///9XmsrmZYH1olJXmsr1olJXmsrmZYH1olJXmsr1olJXmsrmZYH1olL1olJXmsr1olJXmsrmZYH1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olJXmsrmZYH1olL1olL0nFf1olJXmsrmZYH1olJXmsq8dZb1olJXmsrmZYH1olJXmspXmspXmsr1olL1olJXmsrmZYH1olJXmsr1olL1olJXmsrmZYH1olL1olLeaIVXmsrmZYH1olL1olL1olJXmsrmZYH1olLna31Xmsr1olJXmsr1olJXmsrmZYH1olLqoVr1olJXmsr1olJXmsrmZYH1olL1olKkfaPobXvviGabgadXmsqThKuofKHmZ4Dobnr1olJXmsr1olJXmspXmsr1olJXmsrfZ4TuhWn1olL1olJXmsqBi7X1olJXmspZmslbmMhbmsdemsVfl8ZgmsNim8Jpk8F0m7R4m7F5nLB6jbh7jbiDirOEibOGnKaMhq+PnaCVg6qWg6qegKaff6WhnpKofKGtnomxeZy3noG6dZi+n3vCcpPDcpPGn3bLb4/Mb47UbIrVa4rYoGjdaIbeaIXhoWHmZYHobXvpcHjqdHXreHLroVrsfG/uhGnuh2bwj2Hxk17yl1vzmljzm1j0nlX1olL3AJXWAAAAbXRSTlMAEBAQHx8gICAuLjAwMDw9PUBAQEpQUFBXV1hgYGBkcHBwcXl8gICAgoiIkJCQlJicnJ2goKCmqK+wsLC4usDAwMjP0NDQ1NbW3Nzg4ODi5+3v8PDw8/T09PX29vb39/f5+fr7+/z8/Pz9/v7+zczCxgAABC5JREFUeAHN1ul3k0UUBvCb1CTVpmpaitAGSLSpSuKCLWpbTKNJFGlcSMAFF63iUmRccNG6gLbuxkXU66JAUef/9LSpmXnyLr3T5AO/rzl5zj137p136BISy44fKJXuGN/d19PUfYeO67Znqtf2KH33Id1psXoFdW30sPZ1sMvs2D060AHqws4FHeJojLZqnw53cmfvg+XR8mC0OEjuxrXEkX5ydeVJLVIlV0e10PXk5k7dYeHu7Cj1j+49uKg7uLU61tGLw1lq27ugQYlclHC4bgv7VQ+TAyj5Zc/UjsPvs1sd5cWryWObtvWT2EPa4rtnWW3JkpjggEpbOsPr7F7EyNewtpBIslA7p43HCsnwooXTEc3UmPmCNn5lrqTJxy6nRmcavGZVt/3Da2pD5NHvsOHJCrdc1G2r3DITpU7yic7w/7Rxnjc0kt5GC4djiv2Sz3Fb2iEZg41/ddsFDoyuYrIkmFehz0HR2thPgQqMyQYb2OtB0WxsZ3BeG3+wpRb1vzl2UYBog8FfGhttFKjtAclnZYrRo9ryG9uG/FZQU4AEg8ZE9LjGMzTmqKXPLnlWVnIlQQTvxJf8ip7VgjZjyVPrjw1te5otM7RmP7xm+sK2Gv9I8Gi++BRbEkR9EBw8zRUcKxwp73xkaLiqQb+kGduJTNHG72zcW9LoJgqQxpP3/Tj//c3yB0tqzaml05/+orHLksVO+95kX7/7qgJvnjlrfr2Ggsyx0eoy9uPzN5SPd86aXggOsEKW2Prz7du3VID3/tzs/sSRs2w7ovVHKtjrX2pd7ZMlTxAYfBAL9jiDwfLkq55Tm7ifhMlTGPyCAs7RFRhn47JnlcB9RM5T97ASuZXIcVNuUDIndpDbdsfrqsOppeXl5Y+XVKdjFCTh+zGaVuj0d9zy05PPK3QzBamxdwtTCrzyg/2Rvf2EstUjordGwa/kx9mSJLr8mLLtCW8HHGJc2R5hS219IiF6PnTusOqcMl57gm0Z8kanKMAQg0qSyuZfn7zItsbGyO9QlnxY0eCuD1XL2ys/MsrQhltE7Ug0uFOzufJFE2PxBo/YAx8XPPdDwWN0MrDRYIZF0mSMKCNHgaIVFoBbNoLJ7tEQDKxGF0kcLQimojCZopv0OkNOyWCCg9XMVAi7ARJzQdM2QUh0gmBozjc3Skg6dSBRqDGYSUOu66Zg+I2fNZs/M3/f/Grl/XnyF1Gw3VKCez0PN5IUfFLqvgUN4C0qNqYs5YhPL+aVZYDE4IpUk57oSFnJm4FyCqqOE0jhY2SMyLFoo56zyo6becOS5UVDdj7Vih0zp+tcMhwRpBeLyqtIjlJKAIZSbI8SGSF3k0pA3mR5tHuwPFoa7N7reoq2bqCsAk1HqCu5uvI1n6JuRXI+S1Mco54YmYTwcn6Aeic+kssXi8XpXC4V3t7/ADuTNKaQJdScAAAAAElFTkSuQmCC - :target: https://mybinder.org/v2/gh/MAfarrag/HAPI/master - - -Current release info -==================== - -.. image:: https://img.shields.io/pypi/v/Hapi-nile - :target: https://pypi.org/project/HAPI-Nile/1.0.4/ - - -.. image:: https://img.shields.io/conda/v/conda-forge/hapi?label=conda-forge - :target: https://pypi.org/project/HAPI-Nile/1.0.4/ - - -.. image:: https://img.shields.io/pypi/pyversions/hapi-nile - :target: https://pypi.org/project/HAPI-Nile/1.0.4/ - - -.. image:: https://img.shields.io/github/forks/mafarrag/hapi?style=social :alt: GitHub forks - - -.. image:: https://anaconda.org/conda-forge/hapi/badges/downloads.svg - :target: https://anaconda.org/conda-forge/hapi - - -.. image:: https://img.shields.io/conda/vn/conda-forge/hapi :alt: Conda (channel only) - :target: https://pypi.org/project/HAPI-Nile/1.0.4/ - - -.. image:: https://img.shields.io/gitter/room/mafarrag/Hapi - :alt: Gitter - - -.. image:: https://static.pepy.tech/personalized-badge/hapi-nile?period=total&units=international_system&left_color=grey&right_color=blue&left_text=Downloads - :target: https://pypi.org/project/HAPI-Nile/1.0.4/ - - -.. image:: https://img.shields.io/pypi/v/hapi-nile - :alt: PyPI - - -.. image:: https://anaconda.org/conda-forge/hapi/badges/platforms.svg - :target: https://anaconda.org/conda-forge/hapi - - -.. image:: https://static.pepy.tech/personalized-badge/hapi-nile?period=total&units=international_system&left_color=grey&right_color=blue&left_text=Downloads - :target: https://pepy.tech/project/hapi-nile - - - -.. image:: https://static.pepy.tech/personalized-badge/hapi-nile?period=month&units=international_system&left_color=grey&right_color=blue&left_text=Downloads - :target: https://pepy.tech/project/hapi-nile - - -.. image:: https://static.pepy.tech/personalized-badge/hapi-nile?period=week&units=international_system&left_color=grey&right_color=blue&left_text=Downloads - :target: https://pepy.tech/project/hapi-nile - - - - -Hapi - Hydrological library for Python -===================================================================== - - .. image:: /img/Hapi.png - :width: 400pt - :align: center - - - .. image:: /img/name.png - :width: 400pt - :align: center - - -**Hapi** is a Python package providing fast and flexible way to build Hydrological models with different spatial representations (lumped, semidistributed and conceptual distributed) using HBV96. -The package is very flexible to an extent that it allows developers to change the structure of the defined conceptual model or to enter -their own model, it contains two routing functions muskingum cunge, and MAXBAS triangular function. - - - -.. image:: /img/Picture1.png - :width: 400pt - :align: center - - .. image:: /img/Picture2.png - :width: 800pt - :align: center - -Main Features -------------- -- Modified version of HBV96 hydrological model (BergstrΓΆm, 1992) with 15 parameters in case of considering - snow processes, and 10 parameters without snow, in addition to 2 parameters of Muskingum routing method -- Remote sensing module to download the meteorological inputs required for the hydrologic model simulation (ECMWF) -- GIS modules to enable the modeler to fully prepare the meteorological inputs and do all the preprocessing - needed to build the model (align rasters with the DEM), in addition to various methods to manipulate and - convert different forms of distributed data (rasters, NetCDF, shapefiles) -- Sensitivity analysis module based on the concept of one-at-a-time OAT and analysis of the interaction among - model parameters using the Sobol concept ((Rusli et al., 2015)) and a visualization -- Statistical module containing interpolation methods for generating distributed data from gauge data, some - distribution for frequency analysis and Maximum likelihood method for distribution parameter estimation. -- Visualization module for animating the results of the distributed model, and the meteorological inputs -- Optimization module, for calibrating the model based on the Harmony search method - -The recent version of Hapi (Hapi 1.0.1 Farrag et al. (2021)) integrates the global hydrological parameters obtained by Beck et al., (2016), to reduce model complexity and uncertainty of parameters. - - -For using Hapi please cite Farrag et al. (2021) and Farrag & Corzo (2021) References - -IHE-Delft sessions ------------------- -- In April 14-15 we had a two days session for Masters and PhD student in IHE-Delft to explain the different modules and the distributed hydrological model in Hapi [Day 1](https://youtu.be/HbmUdN9ehSo) , [Day 2](https://youtu.be/m7kHdOFQFIY) - -.. digraph:: Linking - - Hapi -> GIS; - Hapi -> HM; - Hapi -> RemoteSensing; - Hapi -> RRM; - Hapi -> Statistics; - Hapi -> catchment; - Hapi -> weirdFn; - Hapi -> visualizer; - dpi=400; - -.. digraph:: Linking - - RRM -> HBV; - RRM -> calibration; - RRM -> distparameters; - RRM -> distrrm; - RRM -> routing; - RRM -> run; - RRM -> inputs; - RRM -> wrapper; - RRM -> hbv_lake; - RRM -> hbv_bergestrom92; - dpi=400; - -.. digraph:: Linking - - HM -> inputs; - HM -> event; - HM -> river; - HM -> calibration; - HM -> crosssection; - HM -> interface; - dpi=400; - -Future work -------------- -- Developing a regionalization method for connection model parameters with some catchment characteristics for better model calibration. -- Developing and integrate river routing method (kinematic and diffusive wave approximation) -- Apply the model for large scale (regional/continental) cases -- Developing a DEM processing module for generating the river network at different DEM spatial resolutions. - -References -========== - -Farrag, M. & Corzo, G. (2021) MAfarrag/Hapi: Hapi. doi:10.5281/ZENODO.4662170 - -Farrag, M., Perez, G. C. & Solomatine, D. (2021) Spatio-Temporal Hydrological Model Structure and Parametrization Analysis. J. Mar. Sci. Eng. 9(5), 467. doi:10.3390/jmse9050467 - -Beck, H. E., Dijk, A. I. J. M. van, Ad de Roo, Diego G. Miralles, T. R. M. & Jaap Schellekens, and L. A. B. (2016) Global-scale regionalization of hydrologic model parameters-Supporting materials 3599–3622. doi:10.1002/2015WR018247.Received - -BergstrΓΆm, S. (1992) The HBV model - its structure and applications. Smhi Rh 4(4), 35. - -Rusli, S. R., Yudianto, D. & Liu, J. tao. (2015) Effects of temporal variability on HBV model calibration. Water Sci. Eng. 8(4), 291–300. Elsevier Ltd. doi:10.1016/j.wse.2015.12.002 - - -.. toctree:: - :hidden: - :maxdepth: 1 - - Installation - Hydrological model - Hydrodynamic model diff --git a/docs/make b/docs/make deleted file mode 100644 index 928577974..000000000 --- a/docs/make +++ /dev/null @@ -1,4 +0,0 @@ -mkdir sphinxtest -cd sphinxtest -sphinx-quickstart -code . diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 000000000..10a36efdf --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,132 @@ +site_name: Hapi +site_url: https://serapieum-of-alex.github.io/Hapi/ +repo_url: https://github.com/Serapieum-of-alex/Hapi +site_description: Hapi is a Python package for distributed hydrological modeling using HBV96 +theme: + name: material + language: en + features: + - announce.dismiss + - content.action.edit + - content.action.view + - content.code.annotate + - content.code.copy + - content.tooltips + - navigation.footer + - navigation.indexes + - navigation.sections + - navigation.tabs + - navigation.tabs.sticky + - navigation.top + - search.highlight + - search.suggest + - toc.follow + palette: + - media: "(prefers-color-scheme)" + toggle: + icon: material/brightness-auto + name: Switch to light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: teal + accent: purple + toggle: + icon: material/weather-sunny + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: black + accent: lime + toggle: + icon: material/weather-night + name: Switch to system preference + +use_directory_urls: false +not_in_nav: | + LICENSE.md +plugins: + - search + - mkdocs-jupyter + - autorefs + - mkdocstrings: + default_handler: python + handlers: + python: + options: + show_root_heading: true + show_root_toc_entry: false + show_source: true + show_signature_annotations: true + heading_level: 3 + show_category_heading: false + group_by_category: false + inherited_members: false + docstring_style: google + - table-reader + - tags + - mike: + # These fields are all optional; the defaults are as below... + alias_type: symlink + redirect_template: null + deploy_prefix: '' + canonical_version: null + version_selector: true + css_dir: css + javascript_dir: js + +markdown_extensions: + - attr_list + - md_in_html + - admonition + - pymdownx.arithmatex: + generic: true + - pymdownx.highlight + - pymdownx.tabbed: + alternate_style: true + - pymdownx.superfences: + custom_fences: + - name: mermaid + class: mermaid + format: !!python/name:pymdownx.superfences.fence_code_format + - toc: + permalink: "#" + toc_depth: 1-4 + anchorlink: true + +nav: + - Home: index.md + - Installation: + - dev/Installation.md + - API Reference: + - api/catchment.md + - api/dem.md + - api/calibration.md + - api/inputs.md + - api/routing.md + - api/run.md + - api/wrapper.md + - api/distrrm.md + - api/hbv.md + - api/hbv-bergestrom92.md + - api/hbv-lake.md + - api/rrm-parameters.md + - Examples: + - examples/distributed-model-calib.md + - examples/distributed-model-run.md + - examples/gis-inputs.md + - examples/hbv.md + - examples/lumped-model-calibration.md + - examples/lumped-model-run.md + - examples/meteo-inputs.md + - examples/muskingum.md + - examples/parameters.md + - Change logs: change-log.md + +extra: + version: + provider: mike + version_selector: true + +extra_javascript: + - javascripts/mathjax.js + - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js diff --git a/pixi.lock b/pixi.lock index 707cb1d87..1f4cff832 100644 --- a/pixi.lock +++ b/pixi.lock @@ -13,30 +13,30 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/freexl-2.0.0-h9dce30a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.2-py313h1ee8c46_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.3-py313h1ee8c46_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/geos-3.14.1-h480dda7_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/json-c-0.18-h6688a6e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.6-gpl_hc2c16d8_100.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.12.2-h4f65170_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.12.3-h4f65170_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda @@ -45,13 +45,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libjxl-0.11.2-ha09017c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libkml-1.3.0-haa4a5bd_1022.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.56-h421ea60_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/librttopo-1.1.0-h46dd2a8_20.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libspatialite-5.1.0-gpl_hab3fe16_120.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda @@ -64,13 +64,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.2-hca6bf5a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.2-he237659_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-devel-2.15.2-he237659_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/minizip-4.0.10-h05a5f5f_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/muparser-2.3.5-h5888daf_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py313hf6604e3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py313hf6604e3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/proj-9.8.0-he0df7b0_0.conda @@ -83,7 +83,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/uriparser-0.9.8-hac33072_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xerces-c-3.3.0-hd9031aa_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2b/58/a199d245894b12db0b957d627516c78e055adc3a0d978bc7f65ddaf7c399/charset_normalizer-3.4.6-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl @@ -110,7 +110,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/05/50/33bf1745e54048baecc1cebc458e769960538aa204e7cd0a64d63d59ad1c/pyramids_gis-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/cf/06896db3f71c75902a8e9943b444a56e727418f6b4b4a90c98c934f51ed4/scikit_learn-1.8.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/15/e7/f591a01d9a16e0f9911c1330d59088d38e3ee3566d4460ee79d0b19723f7/serapeum_utils-0.2.0-py3-none-any.whl @@ -125,31 +125,31 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/freexl-2.0.0-hf297d47_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.2-py314h589ebb0_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.3-py314h589ebb0_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/geos-3.14.1-hdade9fe_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lerc-4.1.0-hd936e49_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.6-gpl_he24518a_100.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.2.0-hfd05255_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.19.0-h8206538_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libdeflate-1.25-h51727cc_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.12.2-h977623c_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.12.3-h977623c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwy-1.3.0-ha71e874_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.1.2-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libjxl-0.11.2-hf3f85d1_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libkml-1.3.0-h68a222c_1022.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-5_hf9ab0e9_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-6_hf9ab0e9_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.55-h7351971_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.56-h7351971_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/librttopo-1.1.0-haa95264_20.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libspatialite-5.1.0-gpl_h2fc3b25_120.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_0.conda @@ -160,14 +160,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-devel-2.15.2-h5d26750_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.2-h4fa8253_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lzo-2.10-h6a83c73_1002.conda - conda: https://conda.anaconda.org/conda-forge/win-64/minizip-4.0.10-h9fa1bad_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_11.conda - conda: https://conda.anaconda.org/conda-forge/win-64/muparser-2.3.5-he0c23c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.2-py314h06c3c77_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.3-py314h02f10f6_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-hd2b5f0e_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/proj-9.8.0-hd30e2cd_0.conda @@ -184,7 +184,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xerces-c-3.3.0-hac47afa_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.1-h2466b09_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/80/94/8434a02d9d7f168c25767c64671fead8d599744a05d6a6c877144c754246/charset_normalizer-3.4.6-cp314-cp314-win_amd64.whl @@ -212,7 +212,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/05/50/33bf1745e54048baecc1cebc458e769960538aa204e7cd0a64d63d59ad1c/pyramids_gis-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/76/18/a8def8f91b18cd1ba6e05dbe02540168cb24d47e8dcf69e8d00b7da42a08/scikit_learn-1.8.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/15/e7/f591a01d9a16e0f9911c1330d59088d38e3ee3566d4460ee79d0b19723f7/serapeum_utils-0.2.0-py3-none-any.whl @@ -237,30 +237,30 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/freexl-2.0.0-h9dce30a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.2-py313h1ee8c46_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.3-py313h1ee8c46_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/geos-3.14.1-h480dda7_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/json-c-0.18-h6688a6e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.6-gpl_hc2c16d8_100.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.12.2-h4f65170_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.12.3-h4f65170_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda @@ -269,13 +269,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libjxl-0.11.2-ha09017c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libkml-1.3.0-haa4a5bd_1022.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.56-h421ea60_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/librttopo-1.1.0-h46dd2a8_20.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libspatialite-5.1.0-gpl_hab3fe16_120.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda @@ -288,13 +288,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.2-hca6bf5a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.2-he237659_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-devel-2.15.2-he237659_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/minizip-4.0.10-h05a5f5f_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/muparser-2.3.5-h5888daf_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py313hf6604e3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py313hf6604e3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/proj-9.8.0-he0df7b0_0.conda @@ -307,20 +307,20 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/uriparser-0.9.8-hac33072_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xerces-c-3.3.0-hd9031aa_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl @@ -330,8 +330,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/22/9b14ee0f17f0aad219a2fb37a293a57b8324d9d195c6ef6807bcd0bf2055/commitizen-4.13.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/fa/ec878c28bc7f65b77e7e17af3522c9948a9711b9fa7fc4c5e3140a7e3578/decli-0.6.3-py3-none-any.whl @@ -357,19 +357,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/59/22/906c8108974c673ebef6356c506cebb6870d48cedea3c41e949e2dd556bb/ipython-9.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/49/c152890d49102b280ecf86ba5f80a8c111c3a155dafa3bd24aeb64fde9e1/jaraco_context-6.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/42/cf027b4ac873b076189d935b135397675dac80cb29acb13e1ab86ad6c631/json5-0.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl @@ -387,6 +387,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl @@ -395,12 +396,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/5c/eb1e3ce54c4e94c7734b3831756c63f21badb3de91a98d77b9e23c0ca76a/nbval-0.11.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/84/c0dc75c7fb596135f999e59a410d9f45bdabb989f1cb911f0016d22b747b/nh3-0.3.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d6/b7/ec1cbc6b297a808c513f59f501656389623fc09ad6a58c640851289c7854/nh3-0.3.4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/aa/cbd1deb9f07446241e88f8d5fecccd95b249bca0b4e5482214a4d1714c49/notebook-7.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl @@ -409,6 +412,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f2/85/ab6d04733a7d6ff32bfc8382bf1b07078228f5d6ebec5266b91bfc5c4ff7/pandas-3.0.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl @@ -422,23 +426,23 @@ environments: - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/35/b874f79d03e9f900012cf609f7fff97b77164f2e14ee5aac282f8a999c1b/pyogrio-0.12.1-cp313-cp313-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/85/c2b1706e51942de19076eff082f8495e57d5151364e78b5bef4af4a1d94a/pyproj-3.7.2-cp313-cp313-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/50/33bf1745e54048baecc1cebc458e769960538aa204e7cd0a64d63d59ad1c/pyramids_gis-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl @@ -483,31 +487,31 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/freexl-2.0.0-hf297d47_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.2-py314h589ebb0_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.3-py314h589ebb0_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/geos-3.14.1-hdade9fe_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lerc-4.1.0-hd936e49_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.6-gpl_he24518a_100.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.2.0-hfd05255_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.19.0-h8206538_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libdeflate-1.25-h51727cc_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.12.2-h977623c_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.12.3-h977623c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwy-1.3.0-ha71e874_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.1.2-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libjxl-0.11.2-hf3f85d1_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libkml-1.3.0-h68a222c_1022.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-5_hf9ab0e9_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-6_hf9ab0e9_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.55-h7351971_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.56-h7351971_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/librttopo-1.1.0-haa95264_20.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libspatialite-5.1.0-gpl_h2fc3b25_120.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_0.conda @@ -518,14 +522,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-devel-2.15.2-h5d26750_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.2-h4fa8253_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lzo-2.10-h6a83c73_1002.conda - conda: https://conda.anaconda.org/conda-forge/win-64/minizip-4.0.10-h9fa1bad_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_11.conda - conda: https://conda.anaconda.org/conda-forge/win-64/muparser-2.3.5-he0c23c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.2-py314h06c3c77_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.3-py314h02f10f6_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-hd2b5f0e_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/proj-9.8.0-hd30e2cd_0.conda @@ -542,20 +546,20 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xerces-c-3.3.0-hac47afa_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.1-h2466b09_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/bb/92/882c2d30831744296ce713f0feb4c1cd30f346ef747b530b5318715cc367/cffi-2.0.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl @@ -565,7 +569,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/22/9b14ee0f17f0aad219a2fb37a293a57b8324d9d195c6ef6807bcd0bf2055/commitizen-4.13.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7d/c2/57f54b03d0f22d4044b8afb9ca0e184f8b1afd57b4f735c2fa70883dc601/contourpy-1.3.3-cp314-cp314-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/d9/1f07395b54413432624d61524dfd98c1a7c7827d2abfdb8829ac92638205/debugpy-1.8.20-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/d8/fa/ec878c28bc7f65b77e7e17af3522c9948a9711b9fa7fc4c5e3140a7e3578/decli-0.6.3-py3-none-any.whl @@ -591,18 +595,18 @@ environments: - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/59/22/906c8108974c673ebef6356c506cebb6870d48cedea3c41e949e2dd556bb/ipython-9.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/49/c152890d49102b280ecf86ba5f80a8c111c3a155dafa3bd24aeb64fde9e1/jaraco_context-6.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/42/cf027b4ac873b076189d935b135397675dac80cb29acb13e1ab86ad6c631/json5-0.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl @@ -620,6 +624,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl @@ -628,12 +633,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/5c/eb1e3ce54c4e94c7734b3831756c63f21badb3de91a98d77b9e23c0ca76a/nbval-0.11.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/73/88/1ce287ef8649dc51365b5094bd3713b76454838140a32ab4f8349973883c/nh3-0.3.3-cp38-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/73/4f/af8e9071d7464575a7316831938237ffc9d92d27f163dbdd964b1309cd9b/nh3-0.3.4-cp38-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/aa/cbd1deb9f07446241e88f8d5fecccd95b249bca0b4e5482214a4d1714c49/notebook-7.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl @@ -642,6 +649,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/09/f8/8ce132104074f977f907442790eaae24e27bce3b3b454e82faa3237ff098/pandas-3.0.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl @@ -653,17 +661,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/bc/e4522f429c45a3b6ad28185849dd76e5c8718b780883c4795e7ee41841ae/pyogrio-0.12.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/b2/5a6610554306a83a563080c2cf2c57565563eadd280e15388efa00fb5b33/pyproj-3.7.2-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/50/33bf1745e54048baecc1cebc458e769960538aa204e7cd0a64d63d59ad1c/pyramids_gis-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/88/2ff917caff61e55f38bcdb27de06ee30597881b2cae44fbba7627be015c4/pywinpty-3.0.3-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl @@ -671,7 +679,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl @@ -724,30 +732,30 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/freexl-2.0.0-h9dce30a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.2-py313h1ee8c46_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.3-py313h1ee8c46_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/geos-3.14.1-h480dda7_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/json-c-0.18-h6688a6e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.6-gpl_hc2c16d8_100.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.12.2-h4f65170_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.12.3-h4f65170_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda @@ -756,13 +764,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libjxl-0.11.2-ha09017c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libkml-1.3.0-haa4a5bd_1022.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.56-h421ea60_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/librttopo-1.1.0-h46dd2a8_20.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libspatialite-5.1.0-gpl_hab3fe16_120.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda @@ -775,13 +783,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.2-hca6bf5a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.2-he237659_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-devel-2.15.2-he237659_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/minizip-4.0.10-h05a5f5f_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/muparser-2.3.5-h5888daf_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py313hf6604e3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py313hf6604e3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/proj-9.8.0-he0df7b0_0.conda @@ -794,14 +802,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/uriparser-0.9.8-hac33072_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xerces-c-3.3.0-hd9031aa_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c5/71/c754b1737ad99102e03fa3235acb6cb6d3ac9d6f596cbc3e5f236705abd8/backrefs-6.2-py313-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl @@ -828,12 +836,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/78/6a04792ace63a93e162f1305392d500ae8ddcb620e7eb88a22fd622b35bb/geopandas-1.1.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1f/7f/13cd798d180af4bf4c0ceddeefba2b864a63c71645abc0308b768d67bb81/hjson-3.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/77/4c0456ead6871b060d024204f891546499eb1e57b8ce523671f690f24281/hpc_utils-0.1.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/5c/368ae6c01c7628438358e6d337c19b05425727fbb221d2a3c4303c372f42/ipykernel-6.29.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/59/22/906c8108974c673ebef6356c506cebb6870d48cedea3c41e949e2dd556bb/ipython-9.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/9bd3b5c2b4774d5f33b2d544f1460be9df7df2fe42f352135381c347c69a/ipython_genutils-0.2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -841,7 +849,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/14/1c65fccf8413d5f5c6e8425f84675169654395098000d8bddc4e9d3390e1/jsbeautifier-1.15.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/a7/ef3b7c8b9d6730a21febdd0809084e4cea6d2a7e43892436adecdd0acbd4/jupyter_client-7.4.9-py3-none-any.whl @@ -872,9 +880,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/62/9fffba5bb9ed3d31a932ad35038ba9483d59850256ee0fea7f1187173983/mkdocs_macros_plugin-1.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/45/e1/e8080dcfa95cca267662a6f4afe29237452bdeb5a2a6555ac83646d21915/mkdocs_material-9.7.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/4b/6fd6dd632019b7f522f1b1f794ab6115cd79890330986614be56fd18f0eb/mkdocs_mermaid2_plugin-1.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3d/58/370541865a451fc9e06f74d407f6d99581224455b5ffe5f55504a43ba6e4/mkdocs_panzoom_plugin-0.5.2-py3-none-any.whl @@ -904,19 +912,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/35/b874f79d03e9f900012cf609f7fff97b77164f2e14ee5aac282f8a999c1b/pyogrio-0.12.1-cp313-cp313-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/85/c2b1706e51942de19076eff082f8495e57d5151364e78b5bef4af4a1d94a/pyproj-3.7.2-cp313-cp313-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/05/50/33bf1745e54048baecc1cebc458e769960538aa204e7cd0a64d63d59ad1c/pyramids_gis-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl @@ -955,31 +963,31 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/freexl-2.0.0-hf297d47_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.2-py314h589ebb0_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.3-py314h589ebb0_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/geos-3.14.1-hdade9fe_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lerc-4.1.0-hd936e49_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.6-gpl_he24518a_100.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.2.0-hfd05255_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.19.0-h8206538_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libdeflate-1.25-h51727cc_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.12.2-h977623c_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.12.3-h977623c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwy-1.3.0-ha71e874_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.1.2-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libjxl-0.11.2-hf3f85d1_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libkml-1.3.0-h68a222c_1022.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-5_hf9ab0e9_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-6_hf9ab0e9_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.55-h7351971_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.56-h7351971_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/librttopo-1.1.0-haa95264_20.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libspatialite-5.1.0-gpl_h2fc3b25_120.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_0.conda @@ -990,14 +998,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-devel-2.15.2-h5d26750_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.2-h4fa8253_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lzo-2.10-h6a83c73_1002.conda - conda: https://conda.anaconda.org/conda-forge/win-64/minizip-4.0.10-h9fa1bad_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_11.conda - conda: https://conda.anaconda.org/conda-forge/win-64/muparser-2.3.5-he0c23c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.2-py314h06c3c77_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.3-py314h02f10f6_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-hd2b5f0e_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/proj-9.8.0-hd30e2cd_0.conda @@ -1014,14 +1022,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xerces-c-3.3.0-hac47afa_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.1-h2466b09_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/af/75/be12ba31a6eb20dccef2320cd8ccb3f7d9013b68ba4c70156259fee9e409/backrefs-6.2-py314-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl @@ -1048,12 +1056,12 @@ environments: - pypi: https://files.pythonhosted.org/packages/da/71/ae30dadffc90b9006d77af76b393cb9dfbfc9629f339fc1574a1c52e6806/future-1.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/78/6a04792ace63a93e162f1305392d500ae8ddcb620e7eb88a22fd622b35bb/geopandas-1.1.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f7/ec/67fbef5d497f86283db54c22eec6f6140243aae73265799baaaa19cd17fb/ghp_import-2.1.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1f/7f/13cd798d180af4bf4c0ceddeefba2b864a63c71645abc0308b768d67bb81/hjson-3.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/77/4c0456ead6871b060d024204f891546499eb1e57b8ce523671f690f24281/hpc_utils-0.1.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/5c/368ae6c01c7628438358e6d337c19b05425727fbb221d2a3c4303c372f42/ipykernel-6.29.5-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/59/22/906c8108974c673ebef6356c506cebb6870d48cedea3c41e949e2dd556bb/ipython-9.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fa/bc/9bd3b5c2b4774d5f33b2d544f1460be9df7df2fe42f352135381c347c69a/ipython_genutils-0.2.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl @@ -1061,7 +1069,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2d/14/1c65fccf8413d5f5c6e8425f84675169654395098000d8bddc4e9d3390e1/jsbeautifier-1.15.4-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/a7/ef3b7c8b9d6730a21febdd0809084e4cea6d2a7e43892436adecdd0acbd4/jupyter_client-7.4.9-py3-none-any.whl @@ -1092,9 +1100,9 @@ environments: - pypi: https://files.pythonhosted.org/packages/22/5b/dbc6a8cddc9cfa9c4971d59fb12bb8d42e161b7e7f8cc89e49137c5b279c/mkdocs-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/de/a3e710469772c6a89595fc52816da05c1e164b4c866a89e3cb82fb1b67c5/mkdocs_autorefs-1.4.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/88/29/744136411e785c4b0b744d5413e56555265939ab3a104c6a4b719dad33fd/mkdocs_get_deps-0.2.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/51/62/9fffba5bb9ed3d31a932ad35038ba9483d59850256ee0fea7f1187173983/mkdocs_macros_plugin-1.5.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/45/e1/e8080dcfa95cca267662a6f4afe29237452bdeb5a2a6555ac83646d21915/mkdocs_material-9.7.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5b/54/662a4743aa81d9582ee9339d4ffa3c8fd40a4965e033d77b9da9774d3960/mkdocs_material_extensions-1.3.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/4b/6fd6dd632019b7f522f1b1f794ab6115cd79890330986614be56fd18f0eb/mkdocs_mermaid2_plugin-1.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3d/58/370541865a451fc9e06f74d407f6d99581224455b5ffe5f55504a43ba6e4/mkdocs_panzoom_plugin-0.5.2-py3-none-any.whl @@ -1122,20 +1130,20 @@ environments: - pypi: https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/bc/e4522f429c45a3b6ad28185849dd76e5c8718b780883c4795e7ee41841ae/pyogrio-0.12.1-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/04/b2/5a6610554306a83a563080c2cf2c57565563eadd280e15388efa00fb5b33/pyproj-3.7.2-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/05/50/33bf1745e54048baecc1cebc458e769960538aa204e7cd0a64d63d59ad1c/pyramids_gis-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/88/2ff917caff61e55f38bcdb27de06ee30597881b2cae44fbba7627be015c4/pywinpty-3.0.3-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/23/20/bb6982b26a40bb43951265ba29d4c246ef0ff59c9fdcdf0ed04e0687de4d/pyyaml-6.0.3-cp314-cp314-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/04/11/432f32f8097b03e3cd5fe57e88efb685d964e2e5178a48ed61e841f7fdce/pyyaml_env_tag-1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fc/7f/a21b20d577e4100c6a41795842028235998a643b1ad406a6d4163ea8f53e/pyzmq-27.1.0-cp312-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/51/17023c0f8f1869d8806b979a2bffa3f861f26a3f1a66b094288323fba52f/rfc3986_validator-0.1.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7e/71/44ce230e1b7fadd372515a97e32a83011f906ddded8d03e3c6aafbdedbb7/rfc3987_syntax-1.1.0-py3-none-any.whl @@ -1183,30 +1191,30 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/freexl-2.0.0-h9dce30a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.2-py311hd474574_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.3-py311hd474574_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/geos-3.14.1-h480dda7_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/json-c-0.18-h6688a6e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.6-gpl_hc2c16d8_100.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.12.2-h4f65170_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.12.3-h4f65170_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda @@ -1215,12 +1223,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libjxl-0.11.2-ha09017c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libkml-1.3.0-haa4a5bd_1022.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.56-h421ea60_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/librttopo-1.1.0-h46dd2a8_20.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libspatialite-5.1.0-gpl_hab3fe16_120.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda @@ -1234,13 +1242,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.2-hca6bf5a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.2-he237659_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-devel-2.15.2-he237659_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/minizip-4.0.10-h05a5f5f_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/muparser-2.3.5-h5888daf_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py311h2e04523_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py311h2e04523_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/proj-9.8.0-he0df7b0_0.conda @@ -1253,21 +1261,21 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/uriparser-0.9.8-hac33072_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xerces-c-3.3.0-hd9031aa_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d7/91/500d892b2bf36529a75b77958edfcd5ad8e2ce4064ce2ecfeab2125d72d1/cffi-2.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl @@ -1277,8 +1285,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/22/9b14ee0f17f0aad219a2fb37a293a57b8324d9d195c6ef6807bcd0bf2055/commitizen-4.13.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/5f/4b/6157f24ca425b89fe2eb7e7be642375711ab671135be21e6faa100f7448c/contourpy-1.3.3-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/fa/ec878c28bc7f65b77e7e17af3522c9948a9711b9fa7fc4c5e3140a7e3578/decli-0.6.3-py3-none-any.whl @@ -1302,22 +1310,22 @@ environments: - pypi: https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/01/09/ba70f8d662d5671687da55ad2cc0064cf795b15e1eea70907532202e7c97/ipython-9.10.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/49/c152890d49102b280ecf86ba5f80a8c111c3a155dafa3bd24aeb64fde9e1/jaraco_context-6.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/42/cf027b4ac873b076189d935b135397675dac80cb29acb13e1ab86ad6c631/json5-0.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl @@ -1335,6 +1343,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b2/a6/8006ae81227105476a45691f5831499e4d936b1c049b0c1feb17c11b02d1/librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/30/ac/0273f6fcb5f42e314c6d8cd99effae6a5354604d461b8d392b5ec9530a54/markupsafe-3.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl @@ -1343,12 +1352,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/5c/eb1e3ce54c4e94c7734b3831756c63f21badb3de91a98d77b9e23c0ca76a/nbval-0.11.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/84/c0dc75c7fb596135f999e59a410d9f45bdabb989f1cb911f0016d22b747b/nh3-0.3.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d6/b7/ec1cbc6b297a808c513f59f501656389623fc09ad6a58c640851289c7854/nh3-0.3.4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/aa/cbd1deb9f07446241e88f8d5fecccd95b249bca0b4e5482214a4d1714c49/notebook-7.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl @@ -1358,6 +1369,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/2e/7c/870c7e7daec2a6c7ff2ac9e33b23317230d4e4e954b35112759ea4a924a7/pandas-3.0.1-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a2/c8/46dfeac5825e600579157eea177be43e2f7ff4a99da9d0d0a49533509ac5/pillow-12.1.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl @@ -1371,23 +1383,23 @@ environments: - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/89/a4/0aef5837b4e11840f501e48e01c31242838476c4f4aff9c05e228a083982/pyogrio-0.12.1-cp311-cp311-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ad/ab/9bdb4a6216b712a1f9aab1c0fcbee5d3726f34a366f29c3e8c08a78d6b70/pyproj-3.7.2-cp311-cp311-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/50/33bf1745e54048baecc1cebc458e769960538aa204e7cd0a64d63d59ad1c/pyramids_gis-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/60/917329f640924b18ff085ab889a11c763e0b573da888e8404ff486657602/pyyaml-6.0.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/b1/c4/2a6fe5111a01005fc7af3878259ce17684fabb8852815eda6225620f3c59/pyzmq-27.1.0-cp311-cp311-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl @@ -1433,30 +1445,30 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/freexl-2.0.0-hf297d47_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.2-py311h0cea559_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.3-py311h0cea559_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/geos-3.14.1-hdade9fe_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lerc-4.1.0-hd936e49_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.6-gpl_he24518a_100.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.2.0-hfd05255_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.19.0-h8206538_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libdeflate-1.25-h51727cc_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.12.2-h977623c_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.12.3-h977623c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwy-1.3.0-ha71e874_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.1.2-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libjxl-0.11.2-hf3f85d1_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libkml-1.3.0-h68a222c_1022.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-5_hf9ab0e9_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-6_hf9ab0e9_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.55-h7351971_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.56-h7351971_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/librttopo-1.1.0-haa95264_20.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libspatialite-5.1.0-gpl_h2fc3b25_120.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_0.conda @@ -1467,14 +1479,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-devel-2.15.2-h5d26750_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.2-h4fa8253_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lzo-2.10-h6a83c73_1002.conda - conda: https://conda.anaconda.org/conda-forge/win-64/minizip-4.0.10-h9fa1bad_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_11.conda - conda: https://conda.anaconda.org/conda-forge/win-64/muparser-2.3.5-he0c23c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.2-py311h80b3fa1_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.3-py311h65cb7f3_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-hd2b5f0e_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/proj-9.8.0-hd30e2cd_0.conda @@ -1491,21 +1503,21 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xerces-c-3.3.0-hac47afa_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.1-h2466b09_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b9/fa/123043af240e49752f1c4bd24da5053b6bd00cad78c2be53c0d1e8b975bc/backports.tarfile-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ae/8f/dc5531155e7070361eb1b7e4c1a9d896d0cb21c49f807a6c03fd63fc877e/cffi-2.0.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl @@ -1515,7 +1527,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/22/9b14ee0f17f0aad219a2fb37a293a57b8324d9d195c6ef6807bcd0bf2055/commitizen-4.13.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/98/4b/9bd370b004b5c9d8045c6c33cf65bae018b27aca550a3f657cdc99acdbd8/contourpy-1.3.3-cp311-cp311-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d5/92/1cb532e88560cbee973396254b21bece8c5d7c2ece958a67afa08c9f10dc/debugpy-1.8.20-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/d8/fa/ec878c28bc7f65b77e7e17af3522c9948a9711b9fa7fc4c5e3140a7e3578/decli-0.6.3-py3-none-any.whl @@ -1539,21 +1551,21 @@ environments: - pypi: https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/01/09/ba70f8d662d5671687da55ad2cc0064cf795b15e1eea70907532202e7c97/ipython-9.10.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/49/c152890d49102b280ecf86ba5f80a8c111c3a155dafa3bd24aeb64fde9e1/jaraco_context-6.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/42/cf027b4ac873b076189d935b135397675dac80cb29acb13e1ab86ad6c631/json5-0.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl @@ -1571,6 +1583,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/71/02/796fe8f02822235966693f257bf2c79f40e11337337a657a8cfebba5febc/librt-0.8.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/8a/4414c03d3f891739326e1783338e48fb49781cc915b2e0ee052aa490d586/markupsafe-3.0.3-cp311-cp311-win_amd64.whl @@ -1579,12 +1592,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/5c/eb1e3ce54c4e94c7734b3831756c63f21badb3de91a98d77b9e23c0ca76a/nbval-0.11.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/73/88/1ce287ef8649dc51365b5094bd3713b76454838140a32ab4f8349973883c/nh3-0.3.3-cp38-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/73/4f/af8e9071d7464575a7316831938237ffc9d92d27f163dbdd964b1309cd9b/nh3-0.3.4-cp38-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/aa/cbd1deb9f07446241e88f8d5fecccd95b249bca0b4e5482214a4d1714c49/notebook-7.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl @@ -1594,6 +1609,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/1f/67/af63f83cd6ca603a00fe8530c10a60f0879265b8be00b5930e8e78c5b30b/pandas-3.0.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/31/03/bef822e4f2d8f9d7448c133d0a18185d3cce3e70472774fffefe8b0ed562/pillow-12.1.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl @@ -1605,17 +1621,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/34/97/e8f2ed8a339152b86f8403c258ae5d5f23ab32d690eeb0545bb3473d0c69/pyogrio-0.12.1-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/e0/b95584605cec9ed50b7ebaf7975d1c4ddeec5a86b7a20554ed8b60042bd7/pyproj-3.7.2-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/50/33bf1745e54048baecc1cebc458e769960538aa204e7cd0a64d63d59ad1c/pyramids_gis-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/79/c3/3e75075c7f71735f22b66fab0481f2c98e3a4d58cba55cb50ba29114bcf6/pywinpty-3.0.3-cp311-cp311-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/da/e3/ea007450a105ae919a72393cb06f122f288ef60bba2dc64b26e2646fa315/pyyaml-6.0.3-cp311-cp311-win_amd64.whl @@ -1623,7 +1639,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl @@ -1677,30 +1693,30 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/freexl-2.0.0-h9dce30a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.2-py312h5fc20e3_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.3-py312h5fc20e3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/geos-3.14.1-h480dda7_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/json-c-0.18-h6688a6e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.6-gpl_hc2c16d8_100.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.12.2-h4f65170_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.12.3-h4f65170_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda @@ -1709,12 +1725,12 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libjxl-0.11.2-ha09017c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libkml-1.3.0-haa4a5bd_1022.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.56-h421ea60_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/librttopo-1.1.0-h46dd2a8_20.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libspatialite-5.1.0-gpl_hab3fe16_120.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda @@ -1728,13 +1744,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.2-hca6bf5a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.2-he237659_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-devel-2.15.2-he237659_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/minizip-4.0.10-h05a5f5f_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/muparser-2.3.5-h5888daf_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py312h33ff503_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py312h33ff503_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/proj-9.8.0-he0df7b0_0.conda @@ -1747,20 +1763,20 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/uriparser-0.9.8-hac33072_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xerces-c-3.3.0-hd9031aa_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/78/2d/7fa73dfa841b5ac06c7b8855cfc18622132e365f5b81d02230333ff26e9e/cffi-2.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl @@ -1770,8 +1786,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/22/9b14ee0f17f0aad219a2fb37a293a57b8324d9d195c6ef6807bcd0bf2055/commitizen-4.13.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cc/8f/ec6289987824b29529d0dfda0d74a07cec60e54b9c92f3c9da4c0ac732de/contourpy-1.3.3-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/fa/ec878c28bc7f65b77e7e17af3522c9948a9711b9fa7fc4c5e3140a7e3578/decli-0.6.3-py3-none-any.whl @@ -1797,19 +1813,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/59/22/906c8108974c673ebef6356c506cebb6870d48cedea3c41e949e2dd556bb/ipython-9.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/49/c152890d49102b280ecf86ba5f80a8c111c3a155dafa3bd24aeb64fde9e1/jaraco_context-6.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/42/cf027b4ac873b076189d935b135397675dac80cb29acb13e1ab86ad6c631/json5-0.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl @@ -1827,6 +1843,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3c/2e/8d0c2ab90a8c1d9a24f0399058ab8519a3279d1bd4289511d74e909f060e/markupsafe-3.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl @@ -1835,12 +1852,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/5c/eb1e3ce54c4e94c7734b3831756c63f21badb3de91a98d77b9e23c0ca76a/nbval-0.11.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/84/c0dc75c7fb596135f999e59a410d9f45bdabb989f1cb911f0016d22b747b/nh3-0.3.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d6/b7/ec1cbc6b297a808c513f59f501656389623fc09ad6a58c640851289c7854/nh3-0.3.4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/aa/cbd1deb9f07446241e88f8d5fecccd95b249bca0b4e5482214a4d1714c49/notebook-7.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl @@ -1849,6 +1868,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/3d/fe/89d77e424365280b79d99b3e1e7d606f5165af2f2ecfaf0c6d24c799d607/pandas-3.0.1-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ff/79/6df7b2ee763d619cda2fb4fea498e5f79d984dae304d45a8999b80d6cf5c/pillow-12.1.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl @@ -1862,23 +1882,23 @@ environments: - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/36/f7/cf8bec9024625947e1a71441906f60a5fa6f9e4c441c4428037e73b1fcc8/pyogrio-0.12.1-cp312-cp312-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b8/be/212882c450bba74fc8d7d35cbd57e4af84792f0a56194819d98106b075af/pyproj-3.7.2-cp312-cp312-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/50/33bf1745e54048baecc1cebc458e769960538aa204e7cd0a64d63d59ad1c/pyramids_gis-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8b/9d/b3589d3877982d4f2329302ef98a8026e7f4443c765c46cfecc8858c6b4b/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl @@ -1923,30 +1943,30 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/freexl-2.0.0-hf297d47_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.2-py312h7ee17fb_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.3-py312h7ee17fb_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/geos-3.14.1-hdade9fe_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lerc-4.1.0-hd936e49_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.6-gpl_he24518a_100.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.2.0-hfd05255_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.19.0-h8206538_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libdeflate-1.25-h51727cc_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.12.2-h977623c_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.12.3-h977623c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwy-1.3.0-ha71e874_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.1.2-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libjxl-0.11.2-hf3f85d1_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libkml-1.3.0-h68a222c_1022.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-5_hf9ab0e9_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-6_hf9ab0e9_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.55-h7351971_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.56-h7351971_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/librttopo-1.1.0-haa95264_20.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libspatialite-5.1.0-gpl_h2fc3b25_120.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_0.conda @@ -1957,14 +1977,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-devel-2.15.2-h5d26750_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.2-h4fa8253_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lzo-2.10-h6a83c73_1002.conda - conda: https://conda.anaconda.org/conda-forge/win-64/minizip-4.0.10-h9fa1bad_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_11.conda - conda: https://conda.anaconda.org/conda-forge/win-64/muparser-2.3.5-he0c23c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.2-py312ha72d056_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.3-py312ha3f287d_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-hd2b5f0e_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/proj-9.8.0-hd30e2cd_0.conda @@ -1981,20 +2001,20 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xerces-c-3.3.0-hac47afa_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.1-h2466b09_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/ed/13bd4418627013bec4ed6e54283b1959cf6db888048c7cf4b4c3b5b36002/cffi-2.0.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl @@ -2004,7 +2024,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/22/9b14ee0f17f0aad219a2fb37a293a57b8324d9d195c6ef6807bcd0bf2055/commitizen-4.13.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/19/e8/6026ed58a64563186a9ee3f29f41261fd1828f527dd93d33b60feca63352/contourpy-1.3.3-cp312-cp312-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a1/39/2bef246368bd42f9bd7cba99844542b74b84dacbdbea0833e610f384fee8/debugpy-1.8.20-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/d8/fa/ec878c28bc7f65b77e7e17af3522c9948a9711b9fa7fc4c5e3140a7e3578/decli-0.6.3-py3-none-any.whl @@ -2030,18 +2050,18 @@ environments: - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/59/22/906c8108974c673ebef6356c506cebb6870d48cedea3c41e949e2dd556bb/ipython-9.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/49/c152890d49102b280ecf86ba5f80a8c111c3a155dafa3bd24aeb64fde9e1/jaraco_context-6.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/42/cf027b4ac873b076189d935b135397675dac80cb29acb13e1ab86ad6c631/json5-0.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl @@ -2059,6 +2079,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/aa/5b/bec5aa9bbbb2c946ca2733ef9c4ca91c91b6a24580193e891b5f7dbe8e1e/markupsafe-3.0.3-cp312-cp312-win_amd64.whl @@ -2067,12 +2088,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/5c/eb1e3ce54c4e94c7734b3831756c63f21badb3de91a98d77b9e23c0ca76a/nbval-0.11.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/73/88/1ce287ef8649dc51365b5094bd3713b76454838140a32ab4f8349973883c/nh3-0.3.3-cp38-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/73/4f/af8e9071d7464575a7316831938237ffc9d92d27f163dbdd964b1309cd9b/nh3-0.3.4-cp38-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/aa/cbd1deb9f07446241e88f8d5fecccd95b249bca0b4e5482214a4d1714c49/notebook-7.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl @@ -2081,6 +2104,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/75/08/67cc404b3a966b6df27b38370ddd96b3b023030b572283d035181854aac5/pandas-3.0.1-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3d/17/688626d192d7261bbbf98846fc98995726bddc2c945344b65bec3a29d731/pillow-12.1.1-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl @@ -2092,17 +2116,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ab/10/7c9f5e428273574e69f217eba3a6c0c42936188ad4dcd9e2c41ebb711188/pyogrio-0.12.1-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4a/a6/6fe724b72b70f2b00152d77282e14964d60ab092ec225e67c196c9b463e5/pyproj-3.7.2-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/50/33bf1745e54048baecc1cebc458e769960538aa204e7cd0a64d63d59ad1c/pyramids_gis-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7c/d4/aeb5e1784d2c5bff6e189138a9ca91a090117459cea0c30378e1f2db3d54/pywinpty-3.0.3-cp312-cp312-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/86/bf/899e81e4cce32febab4fb42bb97dcdf66bc135272882d1987881a4b519e9/pyyaml-6.0.3-cp312-cp312-win_amd64.whl @@ -2110,7 +2134,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl @@ -2163,30 +2187,30 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/c-ares-1.34.6-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-hbd8a1cb_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/freexl-2.0.0-h9dce30a_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.2-py313h1ee8c46_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.3-py313h1ee8c46_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/geos-3.14.1-h480dda7_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/giflib-5.2.2-hd590300_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/json-c-0.18-h6688a6e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-hb9d3cd8_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-ha1258a1_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libarchive-3.8.6-gpl_hc2c16d8_100.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-hb03c661_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-h17f619e_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h7949ede_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libev-4.33-hd590300_2.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-15.2.0-he0feb66_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-15.2.0-h69a702a_18.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.12.2-h4f65170_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.12.3-h4f65170_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-15.2.0-h68bc16d_18.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-15.2.0-he0feb66_18.conda @@ -2195,13 +2219,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.1.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libjxl-0.11.2-ha09017c_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libkml-1.3.0-haa4a5bd_1022.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h47877c9_openblas.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.56-h421ea60_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/librttopo-1.1.0-h46dd2a8_20.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libspatialite-5.1.0-gpl_hab3fe16_120.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.52.0-hf4e2dac_0.conda @@ -2214,13 +2238,13 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.2-hca6bf5a_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.2-he237659_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-devel-2.15.2-he237659_0.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.10.0-h5888daf_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lzo-2.10-h280c20c_1002.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/minizip-4.0.10-h05a5f5f_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/muparser-2.3.5-h5888daf_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-h2d0b736_3.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py313hf6604e3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py313hf6604e3_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.1-h35e630c_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-haa7fec5_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/proj-9.8.0-he0df7b0_0.conda @@ -2233,20 +2257,20 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2025c-hc9c84f9_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/uriparser-0.9.8-hac33072_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/xerces-c-3.3.0-hd9031aa_1.conda - - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/09/52/94108adfdd6e2ddf58be64f959a0b9c7d4ef2fa71086c38356d22dc501ea/argon2_cffi_bindings-25.1.0-cp39-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/98/df/0a1755e750013a2081e863e7cd37e0cdd02664372c754e5560099eb7aa44/cffi-2.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl @@ -2256,8 +2280,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/22/9b14ee0f17f0aad219a2fb37a293a57b8324d9d195c6ef6807bcd0bf2055/commitizen-4.13.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4b/32/e0f13a1c5b0f8572d0ec6ae2f6c677b7991fafd95da523159c19eff0696a/contourpy-1.3.3-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl - - pypi: https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e0/c3/7f67dea8ccf8fdcb9c99033bbe3e90b9e7395415843accb81428c441be2d/debugpy-1.8.20-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d8/fa/ec878c28bc7f65b77e7e17af3522c9948a9711b9fa7fc4c5e3140a7e3578/decli-0.6.3-py3-none-any.whl @@ -2283,19 +2307,19 @@ environments: - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/59/22/906c8108974c673ebef6356c506cebb6870d48cedea3c41e949e2dd556bb/ipython-9.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/49/c152890d49102b280ecf86ba5f80a8c111c3a155dafa3bd24aeb64fde9e1/jaraco_context-6.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b2/a3/e137168c9c44d18eff0376253da9f1e9234d0239e0ee230d2fee6cea8e55/jeepney-0.9.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/42/cf027b4ac873b076189d935b135397675dac80cb29acb13e1ab86ad6c631/json5-0.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl @@ -2313,6 +2337,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl @@ -2321,12 +2346,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/5c/eb1e3ce54c4e94c7734b3831756c63f21badb3de91a98d77b9e23c0ca76a/nbval-0.11.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/6f/84/c0dc75c7fb596135f999e59a410d9f45bdabb989f1cb911f0016d22b747b/nh3-0.3.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/d6/b7/ec1cbc6b297a808c513f59f501656389623fc09ad6a58c640851289c7854/nh3-0.3.4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/aa/cbd1deb9f07446241e88f8d5fecccd95b249bca0b4e5482214a4d1714c49/notebook-7.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl @@ -2335,6 +2362,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/f2/85/ab6d04733a7d6ff32bfc8382bf1b07078228f5d6ebec5266b91bfc5c4ff7/pandas-3.0.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9e/c3/059298687310d527a58bb01f3b1965787ee3b40dce76752eda8b44e9a2c5/pexpect-4.9.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl @@ -2348,23 +2376,23 @@ environments: - pypi: https://files.pythonhosted.org/packages/22/a6/858897256d0deac81a172289110f31629fc4cee19b6f01283303e18c8db3/ptyprocess-0.7.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/46/35/b874f79d03e9f900012cf609f7fff97b77164f2e14ee5aac282f8a999c1b/pyogrio-0.12.1-cp313-cp313-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/85/c2b1706e51942de19076eff082f8495e57d5151364e78b5bef4af4a1d94a/pyproj-3.7.2-cp313-cp313-manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/50/33bf1745e54048baecc1cebc458e769960538aa204e7cd0a64d63d59ad1c/pyramids_gis-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/74/27/e5b8f34d02d9995b80abcef563ea1f8b56d20134d8f4e5e81733b1feceb2/pyyaml-6.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/f8/9b/c108cdb55560eaf253f0cbdb61b29971e9fb34d9c3499b0e96e4e60ed8a5/pyzmq-27.1.0-cp312-abi3-manylinux_2_26_x86_64.manylinux_2_28_x86_64.whl - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl @@ -2409,31 +2437,31 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_9.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.2.25-h4c7d964_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/freexl-2.0.0-hf297d47_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.2-py313h6de05c7_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.3-py313h6de05c7_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/geos-3.14.1-hdade9fe_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h0ea6238_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lerc-4.1.0-hd936e49_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libarchive-3.8.6-gpl_he24518a_100.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.2.0-hfd05255_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.2.0-hfd05255_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libcurl-8.19.0-h8206538_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libdeflate-1.25-h51727cc_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.5.2-h3d046cb_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.12.2-h977623c_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.12.3-h977623c_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.12.2-default_h4379cf1_1000.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libhwy-1.3.0-ha71e874_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.1.2-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libjxl-0.11.2-hf3f85d1_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libkml-1.3.0-h68a222c_1022.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-5_hf9ab0e9_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-6_hf9ab0e9_mkl.conda - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.2-hfd05255_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.55-h7351971_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.56-h7351971_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/librttopo-1.1.0-haa95264_20.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libspatialite-5.1.0-gpl_h2fc3b25_120.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.52.0-hf5d6505_0.conda @@ -2444,14 +2472,14 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.2-h692994f_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.2-h5d26750_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-devel-2.15.2-h5d26750_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.2-h4fa8253_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.10.0-h2466b09_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lzo-2.10-h6a83c73_1002.conda - conda: https://conda.anaconda.org/conda-forge/win-64/minizip-4.0.10-h9fa1bad_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_11.conda - conda: https://conda.anaconda.org/conda-forge/win-64/muparser-2.3.5-he0c23c2_0.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.2-py313hce7ae62_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.3-py313ha8dc839_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.1-hf411b9b_1.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-hd2b5f0e_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/proj-9.8.0-hd30e2cd_0.conda @@ -2468,20 +2496,20 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.44.35208-h818238b_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.44.35208-h818238b_34.conda - conda: https://conda.anaconda.org/conda-forge/win-64/xerces-c-3.3.0-hac47afa_1.conda - - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.1-h2466b09_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_6.conda - - pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/4f/d3/a8b22fa575b297cd6e3e3b0155c7e25db170edf1c74783d6a31a2490b8d9/argon2_cffi-25.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e2/c6/a759ece8f1829d1f162261226fbfd2c6832b3ff7657384045286d2afa384/argon2_cffi_bindings-25.1.0-cp39-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ed/c9/d7977eaacb9df673210491da99e6a247e93df98c715fc43fd136ce1d3d33/arrow-1.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d2/39/e7eaf1799466a4aef85b6a4fe7bd175ad2b1c6345066aa33f1f58d4b18d0/asttokens-3.0.1-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/1a/39/47f9197bdd44df24d67ac8893641e16f386c984a0619ef2ee4c51fbbc019/beautifulsoup4-4.14.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cd/3a/577b549de0cc09d95f11087ee63c739bba856cd3952697eec4c4bb91350a/bleach-6.3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9a/3c/c17fb3ca2d9c3acff52e30b309f538586f9f5b9c9cf454f3845fc9af4881/certifi-2026.2.25-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/37/18/6519e1ee6f5a1e579e04b9ddb6f1676c17368a7aba48299c3759bbc3c8b3/cffi-2.0.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl @@ -2491,7 +2519,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/28/22/9b14ee0f17f0aad219a2fb37a293a57b8324d9d195c6ef6807bcd0bf2055/commitizen-4.13.9-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/18/0b/0098c214843213759692cc638fce7de5c289200a830e5035d1791d7a2338/contourpy-1.3.3-cp313-cp313-win_amd64.whl - - pypi: https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/13/f7/a0b368ce54ffff9e9028c098bd2d28cfc5b54f9f6c186929083d4c60ba58/debugpy-1.8.20-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/d8/fa/ec878c28bc7f65b77e7e17af3522c9948a9711b9fa7fc4c5e3140a7e3578/decli-0.6.3-py3-none-any.whl @@ -2517,18 +2545,18 @@ environments: - pypi: https://files.pythonhosted.org/packages/0e/61/66938bbb5fc52dbdf84594873d5b51fb1f7c7794e9c0f5bd885f30bc507b/idna-3.11-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/82/b9/e73d5d9f405cba7706c539aa8b311b49d4c2f3d698d9c12f815231169c71/ipykernel-7.2.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/59/22/906c8108974c673ebef6356c506cebb6870d48cedea3c41e949e2dd556bb/ipython-9.12.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/56/6d/0d9848617b9f753b87f214f1c682592f7ca42de085f564352f10f0843026/ipywidgets-8.1.8-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/55/e5326141505c5d5e34c5e0935d2908a74e4561eca44108fbfb9c13d2911a/isoduration-20.11.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7f/66/b15ce62552d84bbfcec9a4873ab79d993a1dd4edb922cbfccae192bd5b5f/jaraco.classes-3.4.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/f4/49/c152890d49102b280ecf86ba5f80a8c111c3a155dafa3bd24aeb64fde9e1/jaraco_context-6.1.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c0/5a/9cac0c82afec3d09ccd97c8b6502d48f165f9124db81b4bcb90b4af974ee/jedi-0.19.2-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/b8/42/cf027b4ac873b076189d935b135397675dac80cb29acb13e1ab86ad6c631/json5-0.14.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/41/45/1a4ed80516f02155c51f51e8cedb3c1902296743db0bbc66608a0db2814f/jsonschema_specifications-2025.9.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/38/64/285f20a31679bf547b75602702f7800e74dbabae36ef324f716c02804753/jupyter-1.1.1-py2.py3-none-any.whl @@ -2546,6 +2574,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/81/db/e655086b7f3a705df045bf0933bdd9c2f79bb3c97bfef1384598bb79a217/keyring-25.7.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/82/3d/14ce75ef66813643812f3093ab17e46d3a206942ce7376d31ec2d36229e7/lark-1.3.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl @@ -2554,12 +2583,14 @@ environments: - pypi: https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/9b/f7/4a5e785ec9fbd65146a27b6b70b6cdc161a66f2024e4b04ac06a67f5578b/mistune-3.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/83/a0/5b0c2f11142ed1dddec842457d3f65eaf71a0080894eb6f018755b319c3a/nbclient-0.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0d/4b/8d5f796a792f8a25f6925a96032f098789f448571eb92011df1ae59e8ea8/nbconvert-7.17.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a9/82/0340caa499416c78e5d8f5f05947ae4bc3cba53c9f038ab6e9ed964e22f1/nbformat-5.10.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/5c/eb1e3ce54c4e94c7734b3831756c63f21badb3de91a98d77b9e23c0ca76a/nbval-0.11.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/a0/c4/c2971a3ba4c6103a3d10c4b0f24f461ddc027f0f09763220cf35ca1401b3/nest_asyncio-1.6.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/73/88/1ce287ef8649dc51365b5094bd3713b76454838140a32ab4f8349973883c/nh3-0.3.3-cp38-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/73/4f/af8e9071d7464575a7316831938237ffc9d92d27f163dbdd964b1309cd9b/nh3-0.3.4-cp38-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f8/aa/cbd1deb9f07446241e88f8d5fecccd95b249bca0b4e5482214a4d1714c49/notebook-7.5.5-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/f9/33/bd5b9137445ea4b680023eb0469b2bb969d61303dedb2aac6560ff3d14a1/notebook_shim-0.2.4-py3-none-any.whl @@ -2568,6 +2599,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/d6/7d/216a1588b65a7aa5f4535570418a599d943c85afb1d95b0876fc00aa1468/pandas-3.0.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/ef/af/4fbc8cab944db5d21b7e2a5b8e9211a03a79852b1157e2c102fcc61ac440/pandocfilters-1.5.1-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/b6/61/fae042894f4296ec49e3f193aff5d7c18440da9e48102c3315e1bc4519a7/parso-0.8.6-py2.py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/ef/3c/2c197d226f9ea224a9ab8d197933f9da0ae0aac5b6e0f884e2b8d9c8e9f7/pathspec-1.0.4-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/de/f0/c81e05b613866b76d2d1066490adf1a3dbc4ee9d9c839961c3fc8a6997af/pip-26.0.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/63/d7/97f7e3a6abb67d8080dd406fd4df842c2be0efaf712d1c899c32a075027c/platformdirs-4.9.4-py3-none-any.whl @@ -2579,17 +2611,17 @@ environments: - pypi: https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/c3/c4/705678c9c4200130290b3a104b45c0cc10aaa48fcef3b2585b34e34ab3e1/pyogrio-0.12.1-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/be/0f/747974129cf0d800906f81cd25efd098c96509026e454d4b66868779ab04/pyproj-3.7.2-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/bd/24/12818598c362d7f300f18e74db45963dbcb85150324092410c8b49405e42/pyproject_hooks-1.2.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/05/50/33bf1745e54048baecc1cebc458e769960538aa204e7cd0a64d63d59ad1c/pyramids_gis-0.8.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3b/ab/b3226f0bd7cdcf710fbede2b3548584366da3b19b5021e74f5bde2a8fa3f/pytest-9.0.2-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/de/3d/8161f7711c017e01ac9f008dfddd9410dff3674334c233bde66e7ba65bbf/pywin32_ctypes-0.2.3-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e5/cb/58d6ed3fd429c96a90ef01ac9a617af10a6d41469219c25e7dc162abbb71/pywinpty-3.0.3-cp313-cp313-win_amd64.whl - pypi: https://files.pythonhosted.org/packages/97/c9/39d5b874e8b28845e4ec2202b5da735d0199dbe5b8fb85f91398814a9a46/pyyaml-6.0.3-cp313-cp313-win_amd64.whl @@ -2597,7 +2629,7 @@ environments: - pypi: https://files.pythonhosted.org/packages/3c/26/1062c7ec1b053db9e499b4d2d5bc231743201b74051c973dadeac80a8f43/questionary-2.1.1-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/e1/67/921ec3024056483db83953ae8e48079ad62b92db7880013ca77632921dd0/readme_renderer-44.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/2c/58/ca301544e1fa93ed4f80d724bf5b194f6e4b945841c5bfd555878eea9fcb/referencing-0.37.0-py3-none-any.whl - - pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/7b/44/4e421b96b67b2daff264473f7465db72fbdf36a07e05494f50300cc7b0c6/rfc3339_validator-0.1.4-py2.py3-none-any.whl - pypi: https://files.pythonhosted.org/packages/ff/9a/9afaade874b2fa6c752c36f1548f718b5b83af81ed9b76628329dab81c1b/rfc3986-2.0.0-py2.py3-none-any.whl @@ -2652,17 +2684,16 @@ packages: purls: [] size: 28948 timestamp: 1770939786096 -- pypi: https://files.pythonhosted.org/packages/38/0e/27be9fdef66e72d64c0cdc3cc2823101b80585f8119b5c112c2e8f5f7dab/anyio-4.12.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/da/42/e921fccf5015463e32a3cf6ee7f980a6ed0f395ceeaa45060b61d86486c2/anyio-4.13.0-py3-none-any.whl name: anyio - version: 4.12.1 - sha256: d405828884fc140aa80a3c667b8beed277f1dfedec42ba031bd6ac3db606ab6c + version: 4.13.0 + sha256: 08b310f9e24a9594186fd75b4f73f4a4152069e3853f1ed8bfbf58369f4ad708 requires_dist: - exceptiongroup>=1.0.2 ; python_full_version < '3.11' - idna>=2.8 - typing-extensions>=4.5 ; python_full_version < '3.13' - - trio>=0.32.0 ; python_full_version >= '3.10' and extra == 'trio' - - trio>=0.31.0 ; python_full_version < '3.10' and extra == 'trio' - requires_python: '>=3.9' + - trio>=0.32.0 ; extra == 'trio' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl name: argcomplete version: 3.6.3 @@ -2729,17 +2760,17 @@ packages: - pytest-cov ; extra == 'test' - pytest-xdist ; extra == 'test' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/13/5c/af990f019b8dd11c5492a6371fe74a5b0276357370030b67254a87329944/async_lru-2.2.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/e5/e2/c2e3abf398f80732e58b03be77bde9022550d221dd8781bf586bd4d97cc1/async_lru-2.3.0-py3-none-any.whl name: async-lru - version: 2.2.0 - sha256: e2c1cf731eba202b59c5feedaef14ffd9d02ad0037fcda64938699f2c380eafe + version: 2.3.0 + sha256: eea27b01841909316f2cc739807acea1c623df2be8c5cfad7583286397bb8315 requires_dist: - typing-extensions>=4.0.0 ; python_full_version < '3.11' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/3a/2a/7cc015f5b9f5db42b7d48157e23356022889fc354a2813c15934b7cb5c0e/attrs-25.4.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/64/b4/17d4b0b2a2dc85a6df63d1157e028ed19f90d4cd97c36717afef2bc2f395/attrs-26.1.0-py3-none-any.whl name: attrs - version: 25.4.0 - sha256: adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373 + version: 26.1.0 + sha256: c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309 requires_python: '>=3.9' - pypi: https://files.pythonhosted.org/packages/77/f5/21d2de20e8b8b0408f0681956ca2c69f1320a3848ac50e6e7f39c6159675/babel-2.18.0-py3-none-any.whl name: babel @@ -2840,10 +2871,10 @@ packages: purls: [] size: 49840 timestamp: 1733513605730 -- pypi: https://files.pythonhosted.org/packages/c5/0d/84a4380f930db0010168e0aa7b7a8fed9ba1835a8fbb1472bc6d0201d529/build-1.4.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/4a/57/3b7d4dd193ade4641c865bc2b93aeeb71162e81fc348b8dad020215601ed/build-1.4.2-py3-none-any.whl name: build - version: 1.4.0 - sha256: 6a07c1b8eb6f2b311b96fcbdbce5dab5fe637ffda0fd83c9cac622e927501596 + version: 1.4.2 + sha256: 7a4d8651ea877cb2a89458b1b198f2e69f536c95e89129dbf5d448045d60db88 requires_dist: - packaging>=24.0 - pyproject-hooks @@ -3243,66 +3274,66 @@ packages: - pytest-xdist ; extra == 'test-no-images' - wurlitzer ; extra == 'test-no-images' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/5d/a0/2ea570925524ef4e00bb6c82649f5682a77fac5ab910a65c9284de422600/coverage-7.13.4-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/29/3d/821a9a5799fac2556bcf0bd37a70d1d11fa9e49784b6d22e92e8b2f85f18/coverage-7.13.5-cp312-cp312-win_amd64.whl name: coverage - version: 7.13.4 - sha256: 2c048ea43875fbf8b45d476ad79f179809c590ec7b79e2035c662e7afa3192e3 + version: 7.13.5 + sha256: d2c87e0c473a10bffe991502eac389220533024c8082ec1ce849f4218dded810 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/61/08/3d9c8613079d2b11c185b865de9a4c1a68850cfda2b357fae365cf609f29/coverage-7.13.4-cp311-cp311-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/66/40/7732d648ab9d069a46e686043241f01206348e2bbf128daea85be4d6414b/coverage-7.13.5-cp313-cp313-win_amd64.whl name: coverage - version: 7.13.4 - sha256: 2421d591f8ca05b308cf0092807308b2facbefe54af7c02ac22548b88b95c98f + version: 7.13.5 + sha256: 631efb83f01569670a5e866ceb80fe483e7c159fac6f167e6571522636104a0b requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/76/53/c16972708cbb79f2942922571a687c52bd109a7bd51175aeb7558dff2236/coverage-7.13.4-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/8c/49/cd14b789536ac6a4778c453c6a2338bc0a2fb60c5a5a41b4008328b9acc1/coverage-7.13.5-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl name: coverage - version: 7.13.4 - sha256: 8e264226ec98e01a8e1054314af91ee6cde0eacac4f465cc93b03dbe0bce2fd7 + version: 7.13.5 + sha256: 03ccc709a17a1de074fb1d11f217342fb0d2b1582ed544f554fc9fc3f07e95f5 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/77/4e/c0a25a425fcf5557d9abd18419c95b63922e897bc86c1f327f155ef234a9/coverage-7.13.4-cp312-cp312-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/8d/b7/4758d4f73fb536347cc5e4ad63662f9d60ba9118cb6785e9616b2ce5d7fa/coverage-7.13.5-cp314-cp314-win_amd64.whl name: coverage - version: 7.13.4 - sha256: 9401ebc7ef522f01d01d45532c68c5ac40fb27113019b6b7d8b208f6e9baa126 + version: 7.13.5 + sha256: 2aa055ae1857258f9e0045be26a6d62bdb47a72448b62d7b55f4820f361a2633 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/a7/4d/1f8e723f6829977410efeb88f73673d794075091c8c7c18848d273dc9d73/coverage-7.13.4-cp314-cp314-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/92/be/b1afb692be85b947f3401375851484496134c5554e67e822c35f28bf2fbc/coverage-7.13.5-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl name: coverage - version: 7.13.4 - sha256: 245e37f664d89861cf2329c9afa2c1fe9e6d4e1a09d872c947e70718aeeac505 + version: 7.13.5 + sha256: ec10e2a42b41c923c2209b846126c6582db5e43a33157e9870ba9fb70dc7854b requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e2/0c/dbfafbe90a185943dcfbc766fe0e1909f658811492d79b741523a414a6cc/coverage-7.13.4-cp313-cp313-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/ac/68/1666e3a4462f8202d836920114fa7a5ee9275d1fa45366d336c551a162dd/coverage-7.13.5-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl name: coverage - version: 7.13.4 - sha256: e6f70dec1cc557e52df5306d051ef56003f74d56e9c4dd7ddb07e07ef32a84dd + version: 7.13.5 + sha256: 78e696e1cc714e57e8b25760b33a8b1026b7048d270140d25dafe1b0a1ee05a3 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/e4/dc/b2442d10020c2f52617828862d8b6ee337859cd8f3a1f13d607dddda9cf7/coverage-7.13.4-cp313-cp313-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/af/7f/4cd8a92531253f9d7c1bbecd9fa1b472907fb54446ca768c59b531248dc5/coverage-7.13.5-cp311-cp311-win_amd64.whl name: coverage - version: 7.13.4 - sha256: b720ce6a88a2755f7c697c23268ddc47a571b88052e6b155224347389fdf6a3b + version: 7.13.5 + sha256: 258354455f4e86e3e9d0d17571d522e13b4e1e19bf0f8596bcf9476d61e7d8a9 requires_dist: - tomli ; python_full_version <= '3.11' and extra == 'toml' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/6b/e7/237155ae19a9023de7e30ec64e5d99a9431a567407ac21170a046d22a5a3/cryptography-46.0.5-cp311-abi3-manylinux_2_28_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/34/71/1ea5a7352ae516d5512d17babe7e1b87d9db5150b21f794b1377eac1edc0/cryptography-46.0.6-cp311-abi3-manylinux_2_28_x86_64.whl name: cryptography - version: 46.0.5 - sha256: 3ee190460e2fbe447175cda91b88b84ae8322a104fc27766ad09428754a618ed + version: 46.0.6 + sha256: 22259338084d6ae497a19bae5d4c66b7ca1387d3264d1c2c0e72d9e9b6a77b97 requires_dist: - cffi>=1.14 ; python_full_version == '3.8.*' and platform_python_implementation != 'PyPy' - cffi>=2.0.0 ; python_full_version >= '3.9' and platform_python_implementation != 'PyPy' - typing-extensions>=4.13.2 ; python_full_version < '3.11' - bcrypt>=3.1.5 ; extra == 'ssh' - nox[uv]>=2024.4.15 ; extra == 'nox' - - cryptography-vectors==46.0.5 ; extra == 'test' + - cryptography-vectors==46.0.6 ; extra == 'test' - pytest>=7.4.0 ; extra == 'test' - pytest-benchmark>=4.0 ; extra == 'test' - pytest-cov>=2.10.1 ; extra == 'test' @@ -3731,13 +3762,13 @@ packages: version: 1.0.0 sha256: 929292d34f5872e70396626ef385ec22355a1fae8ad29e1a734c3e43f9fbc216 requires_python: '>=2.6,!=3.0.*,!=3.1.*,!=3.2.*' -- conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.2-py311hd474574_3.conda - sha256: 9fd7043814fb39b752e560b2953c7d955310dfe80a6eafe94feba160c7b73574 - md5: 46789411ae4934ab73084121941892d7 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.3-py311hd474574_0.conda + sha256: fbbf6c0500732883de1bec4619f7a2aadc7cfb833d416924704a26d0a76e4f21 + md5: 21ab887c65f33812f18d8ea63f6ec687 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - libgdal-core 3.12.2.* + - libgdal-core 3.12.3.* - libstdcxx >=14 - numpy >=1.23,<3 - python >=3.11,<3.12.0a0 @@ -3746,15 +3777,15 @@ packages: license_family: MIT purls: - pkg:pypi/gdal?source=hash-mapping - size: 1899693 - timestamp: 1772654066607 -- conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.2-py312h5fc20e3_3.conda - sha256: 39381fc36fbe10a299830843982678055a7b71249b08dbe7a8cdd2c065bcfd3e - md5: 2ae94c1e094380b31361c16f17d8ade7 + size: 1899245 + timestamp: 1774034560045 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.3-py312h5fc20e3_0.conda + sha256: 4a42124ef431b7010acc25aea3b46d0d135271e3a14f42e2eb109cd889cb5b0b + md5: 655079924f9c62d3e57e0f1ea96d2c62 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - libgdal-core 3.12.2.* + - libgdal-core 3.12.3.* - libstdcxx >=14 - numpy >=1.23,<3 - python >=3.12,<3.13.0a0 @@ -3763,15 +3794,15 @@ packages: license_family: MIT purls: - pkg:pypi/gdal?source=hash-mapping - size: 1870243 - timestamp: 1772653984241 -- conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.2-py313h1ee8c46_3.conda - sha256: 1b631ffc6896db182735465c5bcb7400f5f2c5b02634cd67cc692dde3de13eb8 - md5: 4c9d7b653e392761134b289ab5261c0b + size: 1873216 + timestamp: 1774034480398 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gdal-3.12.3-py313h1ee8c46_0.conda + sha256: 124e36d0dae96285d69f9d5c583eac07fb2c45891c98457a3d6bc31efce36cdc + md5: 3bae7ee1396f3c199701f72d5ec967e4 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - - libgdal-core 3.12.2.* + - libgdal-core 3.12.3.* - libstdcxx >=14 - numpy >=1.23,<3 - python >=3.13,<3.14.0a0 @@ -3780,13 +3811,13 @@ packages: license_family: MIT purls: - pkg:pypi/gdal?source=hash-mapping - size: 1882069 - timestamp: 1772654148932 -- conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.2-py311h0cea559_3.conda - sha256: 50eca841db2215beea8d1bff287378bdfe8bc916d2fd8ce81deb7216ef68558f - md5: a91e2f082c6ef3e6ba614f63131c95b4 + size: 1887555 + timestamp: 1774034398620 +- conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.3-py311h0cea559_0.conda + sha256: 7168cf696e25a57344fe82373da524d3276a3557a26c79d15b513aa017464a83 + md5: 82954dc347e24b9d1c2899227d2243ee depends: - - libgdal-core 3.12.2.* + - libgdal-core 3.12.3.* - numpy >=1.23,<3 - python >=3.11,<3.12.0a0 - python_abi 3.11.* *_cp311 @@ -3797,13 +3828,13 @@ packages: license_family: MIT purls: - pkg:pypi/gdal?source=hash-mapping - size: 1773339 - timestamp: 1772730826729 -- conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.2-py312h7ee17fb_3.conda - sha256: 04f82c71cf170882b6cfe7fe17d250de3f50232325db2a0dcd654977f9cb8045 - md5: e7b0d14350e78f00e245d6ec03c37b32 + size: 1765450 + timestamp: 1774037056540 +- conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.3-py312h7ee17fb_0.conda + sha256: 766d76a295939834b3a61288809cc516ea82d3f6b97ef566b50ab8a00bfcd663 + md5: c6b49d89fcf062b05f209e56168a528b depends: - - libgdal-core 3.12.2.* + - libgdal-core 3.12.3.* - numpy >=1.23,<3 - python >=3.12,<3.13.0a0 - python_abi 3.12.* *_cp312 @@ -3814,13 +3845,13 @@ packages: license_family: MIT purls: - pkg:pypi/gdal?source=hash-mapping - size: 1745216 - timestamp: 1772731703182 -- conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.2-py313h6de05c7_3.conda - sha256: 3b82d5cd0d84b7b92a6d6baaa09dcf17c0e92356041c7806576dbeb4dc233875 - md5: 7e4576ddf436ba55d76d4ecc3f8c7471 + size: 1747731 + timestamp: 1774036839350 +- conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.3-py313h6de05c7_0.conda + sha256: 44ad1626c9e98669d5e72e254cb6386f1e98e32f0344bc5bb8e1e6cc30428e55 + md5: 5004ccc59afd93098ba034cad3140154 depends: - - libgdal-core 3.12.2.* + - libgdal-core 3.12.3.* - numpy >=1.23,<3 - python >=3.13,<3.14.0a0 - python_abi 3.13.* *_cp313 @@ -3831,13 +3862,13 @@ packages: license_family: MIT purls: - pkg:pypi/gdal?source=hash-mapping - size: 1745395 - timestamp: 1772731479653 -- conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.2-py314h589ebb0_3.conda - sha256: 70acc6be7b4a33f7757768eb6e45ae211dd744007a31ed20abfb6a73f6af452a - md5: bf3f70c79c18c152114b3911d409eb9d + size: 1744761 + timestamp: 1774036185526 +- conda: https://conda.anaconda.org/conda-forge/win-64/gdal-3.12.3-py314h589ebb0_0.conda + sha256: c19f5cd4bac24965da7d37546a3b9cf4cee6b2cc4c733f31ab49610336389a1c + md5: 1f888a8c57bad39af6e373d2141a7e00 depends: - - libgdal-core 3.12.2.* + - libgdal-core 3.12.3.* - numpy >=1.23,<3 - python >=3.14,<3.15.0a0 - python_abi 3.14.* *_cp314 @@ -3848,8 +3879,8 @@ packages: license_family: MIT purls: - pkg:pypi/gdal?source=hash-mapping - size: 1751318 - timestamp: 1772731261151 + size: 1749469 + timestamp: 1774036626047 - pypi: https://files.pythonhosted.org/packages/3c/78/6a04792ace63a93e162f1305392d500ae8ddcb620e7eb88a22fd622b35bb/geopandas-1.1.3-py3-none-any.whl name: geopandas version: 1.1.3 @@ -3921,10 +3952,10 @@ packages: purls: [] size: 77248 timestamp: 1712692454246 -- pypi: https://files.pythonhosted.org/packages/4d/51/c936033e16d12b627ea334aaaaf42229c37620d0f15593456ab69ab48161/griffelib-2.0.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/11/8c/c9138d881c79aa0ea9ed83cbd58d5ca75624378b38cee225dcf5c42cc91f/griffelib-2.0.2-py3-none-any.whl name: griffelib - version: 2.0.0 - sha256: 01284878c966508b6d6f1dbff9b6fa607bc062d8261c5c7253cb285b06422a7f + version: 2.0.2 + sha256: 925c857658fb1ba40c0772c37acbc2ab650bd794d9c1b9726922e36ea4117ea1 requires_dist: - pip>=24.0 ; extra == 'pypi' - platformdirs>=4.2 ; extra == 'pypi' @@ -3938,14 +3969,14 @@ packages: - pypi: ./ name: hapi-nile version: 1.7.0 - sha256: f8a5e62c67a857b2465dbc15d7cb91abdf4136fa039e3390a08c6900040aa504 + sha256: a07139082e3ffd67431eaa6f8b393b29f3da6ac84832a9d6cbec135e59b757b0 requires_dist: - geopandas>=1.0.1 - loguru>=0.7.2 - numpy>=2.1.3 - pandas>=2.2.3 - pyyaml>=6.0.2 - - pyramids-gis>=0.6.0 + - pyramids-gis>=0.6.0,!=0.9.0 - requests>=2.31.0 - scipy>=1.11.4 - serapeum-utils>=0.2.0 @@ -3965,6 +3996,7 @@ packages: - jupyterlab ; extra == 'dev' - jupyter ; extra == 'dev' - commitizen>=4.8.4 ; extra == 'dev' + - mypy>=1.0 ; extra == 'dev' - mkdocs>=1.5.3 ; extra == 'docs' - mkdocs-material>=9.5.3 ; extra == 'docs' - mkdocstrings>=0.24.0 ; extra == 'docs' @@ -4042,9 +4074,9 @@ packages: - socksio==1.* ; extra == 'socks' - zstandard>=0.18.0 ; extra == 'zstd' requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.2-h33c6efd_0.conda - sha256: 142a722072fa96cf16ff98eaaf641f54ab84744af81754c292cb81e0881c0329 - md5: 186a18e3ba246eccfc7cff00cd19a870 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-h33c6efd_0.conda + sha256: fbf86c4a59c2ed05bbffb2ba25c7ed94f6185ec30ecb691615d42342baa1a16a + md5: c80d8a3b84358cb967fa81e7075fbc8a depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 @@ -4052,8 +4084,8 @@ packages: license: MIT license_family: MIT purls: [] - size: 12728445 - timestamp: 1767969922681 + size: 12723451 + timestamp: 1773822285671 - pypi: https://files.pythonhosted.org/packages/42/77/de194443bf38daed9452139e960c632b0ef9f9a5dd9ce605fdf18ca9f1b1/id-1.6.1-py3-none-any.whl name: id version: 1.6.1 @@ -4089,18 +4121,16 @@ packages: - pytest>=8.3.2 ; extra == 'all' - flake8>=7.1.1 ; extra == 'all' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/38/3d/2d244233ac4f76e38533cfcb2991c9eb4c7bf688ae0a036d30725b8faafe/importlib_metadata-9.0.0-py3-none-any.whl name: importlib-metadata - version: 8.7.1 - sha256: 5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151 + version: 9.0.0 + sha256: 2d21d1cc5a017bd0559e36150c21c830ab1dc304dedd1b7ea85d20f45ef3edd7 requires_dist: - zipp>=3.20 - pytest>=6,!=8.1.* ; extra == 'test' - packaging ; extra == 'test' - pyfakefs ; extra == 'test' - - flufl-flake8 ; extra == 'test' - pytest-perf>=0.9.2 ; extra == 'test' - - jaraco-test>=5.4 ; extra == 'test' - sphinx>=3.5 ; extra == 'doc' - jaraco-packaging>=9.3 ; extra == 'doc' - rst-linker>=1.9 ; extra == 'doc' @@ -4108,13 +4138,12 @@ packages: - sphinx-lint ; extra == 'doc' - jaraco-tidelift>=1.4 ; extra == 'doc' - ipython ; extra == 'perf' - - pytest-checkdocs>=2.4 ; extra == 'check' + - pytest-checkdocs>=2.14 ; extra == 'check' - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' - pytest-cov ; extra == 'cover' - pytest-enabler>=3.4 ; extra == 'enabler' - - pytest-mypy>=1.0.1 ; extra == 'type' - - mypy<1.19 ; platform_python_implementation == 'PyPy' and extra == 'type' - requires_python: '>=3.9' + - pytest-mypy>=1.0.1 ; platform_python_implementation != 'PyPy' and extra == 'type' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl name: iniconfig version: 2.3.0 @@ -4200,10 +4229,10 @@ packages: - pytest-timeout ; extra == 'test' - pytest>=7.0,<10 ; extra == 'test' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/3d/aa/898dec789a05731cd5a9f50605b7b44a72bd198fd0d4528e11fc610177cc/ipython-9.10.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/01/09/ba70f8d662d5671687da55ad2cc0064cf795b15e1eea70907532202e7c97/ipython-9.10.1-py3-none-any.whl name: ipython - version: 9.10.0 - sha256: c6ab68cc23bba8c7e18e9b932797014cc61ea7fd6f19de180ab9ba73e65ee58d + version: 9.10.1 + sha256: 82d18ae9fb9164ded080c71ef92a182ee35ee7db2395f67616034bebb020a232 requires_dist: - colorama>=0.4.4 ; sys_platform == 'win32' - decorator>=4.3.2 @@ -4246,10 +4275,10 @@ packages: - ipython[doc,matplotlib,terminal,test,test-extra] ; extra == 'all' - argcomplete>=3.0 ; extra == 'all' requires_python: '>=3.11' -- pypi: https://files.pythonhosted.org/packages/b2/90/45c72becc57158facc6a6404f663b77bbcea2519ca57f760e2879ae1315d/ipython-9.11.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/59/22/906c8108974c673ebef6356c506cebb6870d48cedea3c41e949e2dd556bb/ipython-9.12.0-py3-none-any.whl name: ipython - version: 9.11.0 - sha256: 6922d5bcf944c6e525a76a0a304451b60a2b6f875e86656d8bc2dfda5d710e19 + version: 9.12.0 + sha256: 0f2701e8ee86e117e37f50563205d36feaa259d2e08d4a6bc6b6d74b18ce128d requires_dist: - colorama>=0.4.4 ; sys_platform == 'win32' - decorator>=5.1.0 @@ -4345,10 +4374,10 @@ packages: - pytest-enabler>=2.2 ; extra == 'testing' - pytest-ruff>=0.2.1 ; extra == 'testing' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/f4/49/c152890d49102b280ecf86ba5f80a8c111c3a155dafa3bd24aeb64fde9e1/jaraco_context-6.1.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/f2/58/bc8954bda5fcda97bd7c19be11b85f91973d67a706ed4a3aec33e7de22db/jaraco_context-6.1.2-py3-none-any.whl name: jaraco-context - version: 6.1.1 - sha256: 0df6a0287258f3e364072c3e40d5411b20cafa30cb28c4839d24319cecf9f808 + version: 6.1.2 + sha256: bf8150b79a2d5d91ae48629d8b427a8f7ba0e1097dd6202a9059f29a36379535 requires_dist: - backports-tarfile ; python_full_version < '3.12' - pytest>=6,!=8.1.* ; extra == 'test' @@ -4360,13 +4389,12 @@ packages: - furo ; extra == 'doc' - sphinx-lint ; extra == 'doc' - jaraco-tidelift>=1.4 ; extra == 'doc' - - pytest-checkdocs>=2.4 ; extra == 'check' + - pytest-checkdocs>=2.14 ; extra == 'check' - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' - pytest-cov ; extra == 'cover' - pytest-enabler>=3.4 ; extra == 'enabler' - - pytest-mypy>=1.0.1 ; extra == 'type' - - mypy<1.19 ; platform_python_implementation == 'PyPy' and extra == 'type' - requires_python: '>=3.9' + - pytest-mypy>=1.0.1 ; platform_python_implementation != 'PyPy' and extra == 'type' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/fd/c4/813bb09f0985cb21e959f21f2464169eca882656849adf727ac7bb7e1767/jaraco_functools-4.4.0-py3-none-any.whl name: jaraco-functools version: 4.4.0 @@ -4472,16 +4500,16 @@ packages: purls: [] size: 82709 timestamp: 1726487116178 -- pypi: https://files.pythonhosted.org/packages/d7/9e/038522f50ceb7e74f1f991bf1b699f24b0c2bbe7c390dd36ad69f4582258/json5-0.13.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/b8/42/cf027b4ac873b076189d935b135397675dac80cb29acb13e1ab86ad6c631/json5-0.14.0-py3-none-any.whl name: json5 - version: 0.13.0 - sha256: 9a08e1dd65f6a4d4c6fa82d216cf2477349ec2346a38fd70cc11d2557499fbcc + version: 0.14.0 + sha256: 56cf861bab076b1178eb8c92e1311d273a9b9acea2ccc82c276abf839ebaef3a requires_python: '>=3.8.0' -- pypi: https://files.pythonhosted.org/packages/71/92/5e77f98553e9e75130c78900d000368476aed74276eb8ae8796f65f00918/jsonpointer-3.0.0-py2.py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/9e/6a/a83720e953b1682d2d109d3c2dbb0bc9bf28cc1cbc205be4ef4be5da709d/jsonpointer-3.1.1-py3-none-any.whl name: jsonpointer - version: 3.0.0 - sha256: 13e088adc14fca8b6aa8177c044e12701e6ad4b28ff10e65f2267a90109c9942 - requires_python: '>=3.7' + version: 3.1.1 + sha256: 8ff8b95779d071ba472cf5bc913028df06031797532f08a7d5b602d8b2a488ca + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/69/90/f63fb5873511e014207a475e2bb4e8b2e570d655b00ac19a9a0ca0a385ee/jsonschema-4.26.0-py3-none-any.whl name: jsonschema version: 4.26.0 @@ -5082,9 +5110,9 @@ packages: - atomicwrites ; extra == 'atomic-cache' - interegular>=0.3.1,<0.4.0 ; extra == 'interegular' requires_python: '>=3.8' -- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_101.conda - sha256: 565941ac1f8b0d2f2e8f02827cbca648f4d18cd461afc31f15604cd291b5c5f3 - md5: 12bd9a3f089ee6c9266a37dab82afabd +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.45.1-default_hbd61a6d_102.conda + sha256: 3d584956604909ff5df353767f3a2a2f60e07d070b328d109f30ac40cd62df6c + md5: 18335a698559cdbcd86150a48bf54ba6 depends: - __glibc >=2.17,<3.0.a0 - zstd >=1.5.7,<1.6.0a0 @@ -5093,8 +5121,8 @@ packages: license: GPL-3.0-only license_family: GPL purls: [] - size: 725507 - timestamp: 1770267139900 + size: 728002 + timestamp: 1774197446916 - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.1.0-hdb68285_0.conda sha256: f84cb54782f7e9cea95e810ea8fef186e0652d0fa73d3009914fa2c1262594e1 md5: a752488c68f2e7c456bcbd8f16eec275 @@ -5160,40 +5188,40 @@ packages: purls: [] size: 1106665 timestamp: 1773243755298 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-5_h4a7cf45_openblas.conda - build_number: 5 - sha256: 18c72545080b86739352482ba14ba2c4815e19e26a7417ca21a95b76ec8da24c - md5: c160954f7418d7b6e87eaf05a8913fa9 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-6_h4a7cf45_openblas.conda + build_number: 6 + sha256: 7bfe936dbb5db04820cf300a9cc1f5ee8d5302fc896c2d66e30f1ee2f20fbfd6 + md5: 6d6d225559bfa6e2f3c90ee9c03d4e2e depends: - - libopenblas >=0.3.30,<0.3.31.0a0 - - libopenblas >=0.3.30,<1.0a0 + - libopenblas >=0.3.32,<0.3.33.0a0 + - libopenblas >=0.3.32,<1.0a0 constrains: + - blas 2.306 openblas + - liblapack 3.11.0 6*_openblas + - liblapacke 3.11.0 6*_openblas + - libcblas 3.11.0 6*_openblas - mkl <2026 - - liblapack 3.11.0 5*_openblas - - libcblas 3.11.0 5*_openblas - - blas 2.305 openblas - - liblapacke 3.11.0 5*_openblas license: BSD-3-Clause license_family: BSD purls: [] - size: 18213 - timestamp: 1765818813880 -- conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-5_hf2e6a31_mkl.conda - build_number: 5 - sha256: f0cb7b2697461a306341f7ff32d5b361bb84f3e94478464c1e27ee01fc8f276b - md5: f9decf88743af85c9c9e05556a4c47c0 + size: 18621 + timestamp: 1774503034895 +- conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-6_hf2e6a31_mkl.conda + build_number: 6 + sha256: 10c8054f007adca8c780cd8bb9335fa5d990f0494b825158d3157983a25b1ea2 + md5: 95543eec964b4a4a7ca3c4c9be481aa1 depends: - - mkl >=2025.3.0,<2026.0a0 + - mkl >=2025.3.1,<2026.0a0 constrains: - - liblapack 3.11.0 5*_mkl - - libcblas 3.11.0 5*_mkl - - blas 2.305 mkl - - liblapacke 3.11.0 5*_mkl + - blas 2.306 mkl + - liblapacke 3.11.0 6*_mkl + - liblapack 3.11.0 6*_mkl + - libcblas 3.11.0 6*_mkl license: BSD-3-Clause license_family: BSD purls: [] - size: 67438 - timestamp: 1765819100043 + size: 68082 + timestamp: 1774503684284 - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-hb03c661_1.conda sha256: 318f36bd49ca8ad85e6478bd8506c88d82454cc008c1ac1c6bf00a3c42fa610e md5: 72c8fd1af66bd67bf580645b426513ed @@ -5267,36 +5295,36 @@ packages: purls: [] size: 252903 timestamp: 1764017901735 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-5_h0358290_openblas.conda - build_number: 5 - sha256: 0cbdcc67901e02dc17f1d19e1f9170610bd828100dc207de4d5b6b8ad1ae7ad8 - md5: 6636a2b6f1a87572df2970d3ebc87cc0 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-6_h0358290_openblas.conda + build_number: 6 + sha256: 57edafa7796f6fa3ebbd5367692dd4c7f552be42109c2dd1a7c89b55089bf374 + md5: 36ae340a916635b97ac8a0655ace2a35 depends: - - libblas 3.11.0 5_h4a7cf45_openblas + - libblas 3.11.0 6_h4a7cf45_openblas constrains: - - liblapacke 3.11.0 5*_openblas - - blas 2.305 openblas - - liblapack 3.11.0 5*_openblas + - blas 2.306 openblas + - liblapack 3.11.0 6*_openblas + - liblapacke 3.11.0 6*_openblas license: BSD-3-Clause license_family: BSD purls: [] - size: 18194 - timestamp: 1765818837135 -- conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-5_h2a3cdd5_mkl.conda - build_number: 5 - sha256: 49dc59d8e58360920314b8d276dd80da7866a1484a9abae4ee2760bc68f3e68d - md5: b3fa8e8b55310ba8ef0060103afb02b5 + size: 18622 + timestamp: 1774503050205 +- conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-6_h2a3cdd5_mkl.conda + build_number: 6 + sha256: 02b2a2225f4899c6aaa1dc723e06b3f7a4903d2129988f91fc1527409b07b0a5 + md5: 9e4bf521c07f4d423cba9296b7927e3c depends: - - libblas 3.11.0 5_hf2e6a31_mkl + - libblas 3.11.0 6_hf2e6a31_mkl constrains: - - liblapack 3.11.0 5*_mkl - - liblapacke 3.11.0 5*_mkl - - blas 2.305 mkl + - blas 2.306 mkl + - liblapacke 3.11.0 6*_mkl + - liblapack 3.11.0 6*_mkl license: BSD-3-Clause license_family: BSD purls: [] - size: 68079 - timestamp: 1765819124349 + size: 68221 + timestamp: 1774503722413 - conda: https://conda.anaconda.org/conda-forge/linux-64/libcurl-8.19.0-hcf29cc6_0.conda sha256: a0390fd0536ebcd2244e243f5f00ab8e76ab62ed9aa214cd54470fe7496620f4 md5: d50608c443a30c341c24277d28290f76 @@ -5375,33 +5403,33 @@ packages: purls: [] size: 112766 timestamp: 1702146165126 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.4-hecca717_0.conda - sha256: d78f1d3bea8c031d2f032b760f36676d87929b18146351c4464c66b0869df3f5 - md5: e7f7ce06ec24cfcfb9e36d28cf82ba57 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.7.5-hecca717_0.conda + sha256: e8c2b57f6aacabdf2f1b0924bd4831ce5071ba080baa4a9e8c0d720588b6794c + md5: 49f570f3bc4c874a06ea69b7225753af depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 constrains: - - expat 2.7.4.* + - expat 2.7.5.* license: MIT license_family: MIT purls: [] - size: 76798 - timestamp: 1771259418166 -- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.4-hac47afa_0.conda - sha256: b31f6fb629c4e17885aaf2082fb30384156d16b48b264e454de4a06a313b533d - md5: 1c1ced969021592407f16ada4573586d + size: 76624 + timestamp: 1774719175983 +- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.7.5-hac47afa_0.conda + sha256: 6850c3a4d5dc215b86f58518cfb8752998533d6569b08da8df1da72e7c68e571 + md5: bfb43f52f13b7c56e7677aa7a8efdf0c depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 constrains: - - expat 2.7.4.* + - expat 2.7.5.* license: MIT license_family: MIT purls: [] - size: 70323 - timestamp: 1771259521393 + size: 70609 + timestamp: 1774719377850 - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.5.2-h3435931_0.conda sha256: 31f19b6a88ce40ebc0d5a992c131f57d919f73c0b92cd1617a5bec83f6e961e6 md5: a360c33a5abe61c07959e449fa1453eb @@ -5449,18 +5477,18 @@ packages: purls: [] size: 27526 timestamp: 1771378224552 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.12.2-h4f65170_3.conda - sha256: 7ea31130adc61b2cda72c02f51086deec699bb30ec6f51b37b6c67d02c93d061 - md5: b14112013ed318cc93b7ab41e0325b4c +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgdal-core-3.12.3-h4f65170_0.conda + sha256: ea7093bd7ab1f22ec4e17dc161e351e13d20c6d23d52d62ada5ce0798ce6d549 + md5: 5cbd75ef90a585462b336d6aaa2417ba depends: - __glibc >=2.17,<3.0.a0 - blosc >=1.21.6,<2.0a0 - geos >=3.14.1,<3.14.2.0a0 - giflib >=5.2.2,<5.3.0a0 - json-c >=0.18,<0.19.0a0 - - lerc >=4.0.0,<5.0a0 - - libarchive >=3.8.5,<3.9.0a0 - - libcurl >=8.18.0,<9.0a0 + - lerc >=4.1.0,<5.0a0 + - libarchive >=3.8.6,<3.9.0a0 + - libcurl >=8.19.0,<9.0a0 - libdeflate >=1.25,<1.26.0a0 - libexpat >=2.7.4,<3.0a0 - libgcc >=14 @@ -5471,7 +5499,7 @@ packages: - liblzma >=5.8.2,<6.0a0 - libpng >=1.6.55,<1.7.0a0 - libspatialite >=5.1.0,<5.2.0a0 - - libsqlite >=3.51.2,<4.0a0 + - libsqlite >=3.52.0,<4.0a0 - libstdcxx >=14 - libwebp-base >=1.6.0,<2.0a0 - libxml2 @@ -5485,21 +5513,21 @@ packages: - xerces-c >=3.3.0,<3.4.0a0 - zstd >=1.5.7,<1.6.0a0 constrains: - - libgdal 3.12.2.* + - libgdal 3.12.3.* license: MIT license_family: MIT purls: [] - size: 12938414 - timestamp: 1772653869318 -- conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.12.2-h977623c_3.conda - sha256: f31d73e3d97ea4c18aeb8fa126675a2728a1ecfc74be1880e00e9bc95d9a1572 - md5: 77f9e36c8d183945babda0fcfd5b5375 + size: 12902924 + timestamp: 1774034123984 +- conda: https://conda.anaconda.org/conda-forge/win-64/libgdal-core-3.12.3-h977623c_0.conda + sha256: 312cacb78368b74e398eb74e473b567e7cce4f914dad664617de314f3c58f463 + md5: 5d46f0b6ec15e225110e65cadfb3236f depends: - blosc >=1.21.6,<2.0a0 - geos >=3.14.1,<3.14.2.0a0 - - lerc >=4.0.0,<5.0a0 - - libarchive >=3.8.5,<3.9.0a0 - - libcurl >=8.18.0,<9.0a0 + - lerc >=4.1.0,<5.0a0 + - libarchive >=3.8.6,<3.9.0a0 + - libcurl >=8.19.0,<9.0a0 - libdeflate >=1.25,<1.26.0a0 - libexpat >=2.7.4,<3.0a0 - libiconv >=1.18,<2.0a0 @@ -5509,7 +5537,7 @@ packages: - liblzma >=5.8.2,<6.0a0 - libpng >=1.6.55,<1.7.0a0 - libspatialite >=5.1.0,<5.2.0a0 - - libsqlite >=3.51.2,<4.0a0 + - libsqlite >=3.52.0,<4.0a0 - libwebp-base >=1.6.0,<2.0a0 - libxml2 - libxml2-16 >=2.14.6 @@ -5525,12 +5553,12 @@ packages: - xerces-c >=3.3.0,<3.4.0a0 - zstd >=1.5.7,<1.6.0a0 constrains: - - libgdal 3.12.2.* + - libgdal 3.12.3.* license: MIT license_family: MIT purls: [] - size: 9855284 - timestamp: 1772730589838 + size: 9834451 + timestamp: 1774035944616 - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-15.2.0-h69a702a_18.conda sha256: d2c9fad338fd85e4487424865da8e74006ab2e2475bd788f624d7a39b2a72aee md5: 9063115da5bc35fdc3e1002e69b9ef6e @@ -5709,36 +5737,36 @@ packages: purls: [] size: 1659205 timestamp: 1761132867821 -- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-5_h47877c9_openblas.conda - build_number: 5 - sha256: c723b6599fcd4c6c75dee728359ef418307280fa3e2ee376e14e85e5bbdda053 - md5: b38076eb5c8e40d0106beda6f95d7609 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-6_h47877c9_openblas.conda + build_number: 6 + sha256: 371f517eb7010b21c6cc882c7606daccebb943307cb9a3bf2c70456a5c024f7d + md5: 881d801569b201c2e753f03c84b85e15 depends: - - libblas 3.11.0 5_h4a7cf45_openblas + - libblas 3.11.0 6_h4a7cf45_openblas constrains: - - blas 2.305 openblas - - liblapacke 3.11.0 5*_openblas - - libcblas 3.11.0 5*_openblas + - blas 2.306 openblas + - liblapacke 3.11.0 6*_openblas + - libcblas 3.11.0 6*_openblas license: BSD-3-Clause license_family: BSD purls: [] - size: 18200 - timestamp: 1765818857876 -- conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-5_hf9ab0e9_mkl.conda - build_number: 5 - sha256: a2d33f5cc2b8a9042f2af6981c6733ab1a661463823eaa56595a9c58c0ab77e1 - md5: e62c42a4196dee97d20400612afcb2b1 + size: 18624 + timestamp: 1774503065378 +- conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-6_hf9ab0e9_mkl.conda + build_number: 6 + sha256: 2e6ac39e456ba13ec8f02fc0787b8a22c89780e24bd5556eaf642177463ffb36 + md5: 7e9cdaf6f302142bc363bbab3b5e7074 depends: - - libblas 3.11.0 5_hf2e6a31_mkl + - libblas 3.11.0 6_hf2e6a31_mkl constrains: - - libcblas 3.11.0 5*_mkl - - blas 2.305 mkl - - liblapacke 3.11.0 5*_mkl + - blas 2.306 mkl + - liblapacke 3.11.0 6*_mkl + - libcblas 3.11.0 6*_mkl license: BSD-3-Clause license_family: BSD purls: [] - size: 80225 - timestamp: 1765819148014 + size: 80571 + timestamp: 1774503757128 - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.2-hb03c661_0.conda sha256: 755c55ebab181d678c12e49cced893598f2bab22d582fbbf4d8b83c18be207eb md5: c7c83eecbb72d88b940c249af56c8b17 @@ -5787,23 +5815,23 @@ packages: purls: [] size: 89411 timestamp: 1769482314283 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.67.0-had1ee68_0.conda - sha256: a4a7dab8db4dc81c736e9a9b42bdfd97b087816e029e221380511960ac46c690 - md5: b499ce4b026493a13774bcf0f4c33849 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libnghttp2-1.68.1-h877daf1_0.conda + sha256: 663444d77a42f2265f54fb8b48c5450bfff4388d9c0f8253dd7855f0d993153f + md5: 2a45e7f8af083626f009645a6481f12d depends: - __glibc >=2.17,<3.0.a0 - - c-ares >=1.34.5,<2.0a0 + - c-ares >=1.34.6,<2.0a0 - libev >=4.33,<4.34.0a0 - libev >=4.33,<5.0a0 - libgcc >=14 - libstdcxx >=14 - libzlib >=1.3.1,<2.0a0 - - openssl >=3.5.2,<4.0a0 + - openssl >=3.5.5,<4.0a0 license: MIT license_family: MIT purls: [] - size: 666600 - timestamp: 1756834976695 + size: 663344 + timestamp: 1773854035739 - conda: https://conda.anaconda.org/conda-forge/linux-64/libnsl-2.0.1-hb9d3cd8_1.conda sha256: 927fe72b054277cde6cb82597d0fcf6baf127dcbce2e0a9d8925a68f1265eef5 md5: d864d34357c3b65a4b731f78c0801dc4 @@ -5815,44 +5843,79 @@ packages: purls: [] size: 33731 timestamp: 1750274110928 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.30-pthreads_h94d23a6_4.conda - sha256: 199d79c237afb0d4780ccd2fbf829cea80743df60df4705202558675e07dd2c5 - md5: be43915efc66345cccb3c310b6ed0374 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopenblas-0.3.32-pthreads_h94d23a6_0.conda + sha256: 6dc30b28f32737a1c52dada10c8f3a41bc9e021854215efca04a7f00487d09d9 + md5: 89d61bc91d3f39fda0ca10fcd3c68594 depends: - __glibc >=2.17,<3.0.a0 - libgcc >=14 - libgfortran - libgfortran5 >=14.3.0 constrains: - - openblas >=0.3.30,<0.3.31.0a0 + - openblas >=0.3.32,<0.3.33.0a0 license: BSD-3-Clause license_family: BSD purls: [] - size: 5927939 - timestamp: 1763114673331 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.55-h421ea60_0.conda - sha256: 36ade759122cdf0f16e2a2562a19746d96cf9c863ffaa812f2f5071ebbe9c03c - md5: 5f13ffc7d30ffec87864e678df9957b4 + size: 5928890 + timestamp: 1774471724897 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.56-h421ea60_0.conda + sha256: 4f9fca3bc21e485ec0b3eb88db108b6cf9ab9a481cdf7d2ac6f9d30350b45ead + md5: 97169784f0775c85683c3d8badcea2c3 depends: - libgcc >=14 - __glibc >=2.17,<3.0.a0 - - libzlib >=1.3.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 license: zlib-acknowledgement purls: [] - size: 317669 - timestamp: 1770691470744 -- conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.55-h7351971_0.conda - sha256: db23f281fa80597a0dc0445b18318346862602d7081ed76244df8cc4418d6d68 - md5: 43f47a9151b9b8fc100aeefcf350d1a0 + size: 317540 + timestamp: 1774513272700 +- conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.56-h7351971_0.conda + sha256: 0ab8890b7551bae4fc2a1aada8937789a6205c9ba9f322552a24e97b2d9b33b8 + md5: bedc0fc6a8fb31b8013878ea20c76bae depends: - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - - libzlib >=1.3.1,<2.0a0 + - libzlib >=1.3.2,<2.0a0 license: zlib-acknowledgement purls: [] - size: 383155 - timestamp: 1770691504832 + size: 383766 + timestamp: 1774513353959 +- pypi: https://files.pythonhosted.org/packages/30/7a/8d908a152e1875c9f8eac96c97a480df425e657cdb47854b9efaa4998889/librt-0.8.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: librt + version: 0.8.1 + sha256: 5bb54f1205a3a6ab41a6fd71dfcdcbd278670d3a90ca502a30d9da583105b6f7 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/52/05/27fdc2e95de26273d83b96742d8d3b7345f2ea2bdbd2405cc504644f2096/librt-0.8.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: librt + version: 0.8.1 + sha256: 5e4af413908f77294605e28cfd98063f54b2c790561383971d2f52d113d9c363 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/5c/73/c9fdf6cb2a529c1a092ce769a12d88c8cca991194dfe641b6af12fa964d2/librt-0.8.1-cp313-cp313-win_amd64.whl + name: librt + version: 0.8.1 + sha256: e11769a1dbda4da7b00a76cfffa67aa47cfa66921d2724539eee4b9ede780b79 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/71/02/796fe8f02822235966693f257bf2c79f40e11337337a657a8cfebba5febc/librt-0.8.1-cp311-cp311-win_amd64.whl + name: librt + version: 0.8.1 + sha256: c00e5c884f528c9932d278d5c9cbbea38a6b81eb62c02e06ae53751a83a4d52b + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/a0/76/cd4d010ab2147339ca2b93e959c3686e964edc6de66ddacc935c325883d7/librt-0.8.1-cp314-cp314-win_amd64.whl + name: librt + version: 0.8.1 + sha256: 7e2f3edca35664499fbb36e4770650c4bd4a08abc1f4458eab9df4ec56389730 + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/b2/a6/8006ae81227105476a45691f5831499e4d936b1c049b0c1feb17c11b02d1/librt-0.8.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: librt + version: 0.8.1 + sha256: f0af2bd2bc204fa27f3d6711d0f360e6b8c684a035206257a81673ab924aa11e + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/de/d1/fa5d5331b862b9775aaf2a100f5ef86854e5d4407f71bddf102f4421e034/librt-0.8.1-cp312-cp312-win_amd64.whl + name: librt + version: 0.8.1 + sha256: ff8baf1f8d3f4b6b7257fcb75a501f2a5499d0dda57645baa09d4d0d34b19444 + requires_python: '>=3.9' - conda: https://conda.anaconda.org/conda-forge/linux-64/librttopo-1.1.0-h46dd2a8_20.conda sha256: eb4082a5135102f5ba9c302da13164d4ed1181d5f0db9d49e5e11a815a7b526f md5: df81fd57eacf341588d728c97920e86d @@ -6199,48 +6262,46 @@ packages: purls: [] size: 124154 timestamp: 1772704766882 -- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda - sha256: d4bfe88d7cb447768e31650f06257995601f89076080e76df55e3112d4e47dc4 - md5: edb0dca6bc32e4f4789199455a1dbeb8 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_2.conda + sha256: 55044c403570f0dc26e6364de4dc5368e5f3fc7ff103e867c487e2b5ab2bcda9 + md5: d87ff7921124eccd67248aa483c23fec depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 constrains: - - zlib 1.3.1 *_2 + - zlib 1.3.2 *_2 license: Zlib license_family: Other purls: [] - size: 60963 - timestamp: 1727963148474 -- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda - sha256: ba945c6493449bed0e6e29883c4943817f7c79cbff52b83360f7b341277c6402 - md5: 41fbfac52c601159df6c01f875de31b9 + size: 63629 + timestamp: 1774072609062 +- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_2.conda + sha256: 88609816e0cc7452bac637aaf65783e5edf4fee8a9f8e22bdc3a75882c536061 + md5: dbabbd6234dea34040e631f87676292f depends: - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 constrains: - - zlib 1.3.1 *_2 + - zlib 1.3.2 *_2 license: Zlib license_family: Other purls: [] - size: 55476 - timestamp: 1727963768015 -- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.0-h4fa8253_0.conda - sha256: bb55a3736380759d338f87aac68df4fd7d845ae090b94400525f5d21a55eea31 - md5: e5505e0b7d6ef5c19d5c0c1884a2f494 + size: 58347 + timestamp: 1774072851498 +- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.2-h4fa8253_0.conda + sha256: fa8bd542624507309cbdfc620bdfe546ed823d418e6ba878977d48da7a0f6212 + md5: 29407a30bd93dc8c11c03ca60249a340 depends: - ucrt >=10.0.20348.0 - vc >=14.3,<15 - vc14_runtime >=14.44.35208 constrains: - - openmp 22.1.0|22.1.0.* - intel-openmp <0.0a0 + - openmp 22.1.2|22.1.2.* license: Apache-2.0 WITH LLVM-exception - license_family: APACHE purls: [] - size: 347404 - timestamp: 1772025050288 + size: 348400 + timestamp: 1774733045609 - pypi: https://files.pythonhosted.org/packages/0c/29/0348de65b8cc732daa3e33e67806420b2ae89bdce2b04af740289c5c6c8c/loguru-0.7.3-py3-none-any.whl name: loguru version: 0.7.3 @@ -6713,12 +6774,12 @@ packages: - platformdirs>=2.2.0 - pyyaml>=5.1 requires_python: '>=3.9' -- pypi: https://files.pythonhosted.org/packages/08/37/5f1fd5c3f6954b3256f8126275e62af493b96fb6aef6c0dbc4ee326032ad/mkdocs_jupyter-0.25.1-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/93/89/eb601278b12c471235860992f5973cf3c55ca3f77d1d6127389eb045a021/mkdocs_jupyter-0.26.1-py3-none-any.whl name: mkdocs-jupyter - version: 0.25.1 - sha256: 3f679a857609885d322880e72533ef5255561bbfdb13cfee2a1e92ef4d4ad8d8 + version: 0.26.1 + sha256: 527242c2c8f1d30970764bbab752de41243e5703f458d8bc05336ec53828192e requires_dist: - - ipykernel>6.0.0,<7.0.0 + - ipykernel>6.0.0,<8 - jupytext>1.13.8,<2 - mkdocs-material>9.0.0 - mkdocs>=1.4.0,<2 @@ -6748,10 +6809,10 @@ packages: - mkdocs-d2-plugin ; extra == 'test' - mkdocs-mermaid2-plugin ; extra == 'doc' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/45/e1/e8080dcfa95cca267662a6f4afe29237452bdeb5a2a6555ac83646d21915/mkdocs_material-9.7.5-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/2c/01/bc663630c510822c95c47a66af9fa7a443c295b47d5f041e5e6ae62ef659/mkdocs_material-9.7.6-py3-none-any.whl name: mkdocs-material - version: 9.7.5 - sha256: 7cf9df2ff121fd098ff6e05c732b0be3699afca9642e2dfe4926c40eb5873eec + version: 9.7.6 + sha256: 71b84353921b8ea1ba84fe11c50912cc512da8fe0881038fcc9a0761c0e635ba requires_dist: - babel>=2.10 - backrefs>=5.7.post1 @@ -6835,11 +6896,11 @@ packages: - griffelib>=2.0 - typing-extensions>=4.0 ; python_full_version < '3.11' requires_python: '>=3.10' -- conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.0-hac47afa_455.conda - sha256: b2b4c84b95210760e4d12319416c60ab66e03674ccdcbd14aeb59f82ebb1318d - md5: fd05d1e894497b012d05a804232254ed +- conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2025.3.1-hac47afa_11.conda + sha256: f2c2b2a3c2e7d08d78c10bef7c135a4262c80d1d48c85fb5902ca30d61d645f4 + md5: 3fd3009cef89c36e9898a6feeb0f5530 depends: - - llvm-openmp >=21.1.8 + - llvm-openmp >=22.1.1 - tbb >=2022.3.0 - ucrt >=10.0.20348.0 - vc >=14.3,<15 @@ -6847,8 +6908,8 @@ packages: license: LicenseRef-IntelSimplifiedSoftwareOct2022 license_family: Proprietary purls: [] - size: 100224829 - timestamp: 1767634557029 + size: 99997309 + timestamp: 1774449747739 - pypi: https://files.pythonhosted.org/packages/a4/8e/469e5a4a2f5855992e425f3cb33804cc07bf18d48f2db061aec61ce50270/more_itertools-10.8.0-py3-none-any.whl name: more-itertools version: 10.8.0 @@ -6878,6 +6939,123 @@ packages: purls: [] size: 148557 timestamp: 1747117340968 +- pypi: https://files.pythonhosted.org/packages/00/be/dd56c1fd4807bc1eba1cf18b2a850d0de7bacb55e158755eb79f77c41f8e/mypy-1.19.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: mypy + version: 1.19.1 + sha256: c35d298c2c4bba75feb2195655dfea8124d855dfd7343bf8b8c055421eaf0cf8 + requires_dist: + - typing-extensions>=4.6.0 + - mypy-extensions>=1.0.0 + - pathspec>=0.9.0 + - tomli>=1.1.0 ; python_full_version < '3.11' + - librt>=0.6.2 ; platform_python_implementation != 'PyPy' + - psutil>=4.0 ; extra == 'dmypy' + - setuptools>=50 ; extra == 'mypyc' + - lxml ; extra == 'reports' + - pip ; extra == 'install-types' + - orjson ; extra == 'faster-cache' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/2d/46/20f8a7114a56484ab268b0ab372461cb3a8f7deed31ea96b83a4e4cfcfca/mypy-1.19.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: mypy + version: 1.19.1 + sha256: a009ffa5a621762d0c926a078c2d639104becab69e79538a494bcccb62cc0331 + requires_dist: + - typing-extensions>=4.6.0 + - mypy-extensions>=1.0.0 + - pathspec>=0.9.0 + - tomli>=1.1.0 ; python_full_version < '3.11' + - librt>=0.6.2 ; platform_python_implementation != 'PyPy' + - psutil>=4.0 ; extra == 'dmypy' + - setuptools>=50 ; extra == 'mypyc' + - lxml ; extra == 'reports' + - pip ; extra == 'install-types' + - orjson ; extra == 'faster-cache' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/6f/63/e7493e5f90e1e085c562bb06e2eb32cae27c5057b9653348d38b47daaecc/mypy-1.19.1-cp312-cp312-win_amd64.whl + name: mypy + version: 1.19.1 + sha256: b10e7c2cd7870ba4ad9b2d8a6102eb5ffc1f16ca35e3de6bfa390c1113029d13 + requires_dist: + - typing-extensions>=4.6.0 + - mypy-extensions>=1.0.0 + - pathspec>=0.9.0 + - tomli>=1.1.0 ; python_full_version < '3.11' + - librt>=0.6.2 ; platform_python_implementation != 'PyPy' + - psutil>=4.0 ; extra == 'dmypy' + - setuptools>=50 ; extra == 'mypyc' + - lxml ; extra == 'reports' + - pip ; extra == 'install-types' + - orjson ; extra == 'faster-cache' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/98/76/d32051fa65ecf6cc8c6610956473abdc9b4c43301107476ac03559507843/mypy-1.19.1-cp313-cp313-win_amd64.whl + name: mypy + version: 1.19.1 + sha256: 016f2246209095e8eda7538944daa1d60e1e8134d98983b9fc1e92c1fc0cb8dd + requires_dist: + - typing-extensions>=4.6.0 + - mypy-extensions>=1.0.0 + - pathspec>=0.9.0 + - tomli>=1.1.0 ; python_full_version < '3.11' + - librt>=0.6.2 ; platform_python_implementation != 'PyPy' + - psutil>=4.0 ; extra == 'dmypy' + - setuptools>=50 ; extra == 'mypyc' + - lxml ; extra == 'reports' + - pip ; extra == 'install-types' + - orjson ; extra == 'faster-cache' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/cd/09/4ebd873390a063176f06b0dbf1f7783dd87bd120eae7727fa4ae4179b685/mypy-1.19.1-cp314-cp314-win_amd64.whl + name: mypy + version: 1.19.1 + sha256: 8bb5c6f6d043655e055be9b542aa5f3bdd30e4f3589163e85f93f3640060509f + requires_dist: + - typing-extensions>=4.6.0 + - mypy-extensions>=1.0.0 + - pathspec>=0.9.0 + - tomli>=1.1.0 ; python_full_version < '3.11' + - librt>=0.6.2 ; platform_python_implementation != 'PyPy' + - psutil>=4.0 ; extra == 'dmypy' + - setuptools>=50 ; extra == 'mypyc' + - lxml ; extra == 'reports' + - pip ; extra == 'install-types' + - orjson ; extra == 'faster-cache' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/d1/32/dd260d52babf67bad8e6770f8e1102021877ce0edea106e72df5626bb0ec/mypy-1.19.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: mypy + version: 1.19.1 + sha256: c9a6538e0415310aad77cb94004ca6482330fece18036b5f360b62c45814c4ef + requires_dist: + - typing-extensions>=4.6.0 + - mypy-extensions>=1.0.0 + - pathspec>=0.9.0 + - tomli>=1.1.0 ; python_full_version < '3.11' + - librt>=0.6.2 ; platform_python_implementation != 'PyPy' + - psutil>=4.0 ; extra == 'dmypy' + - setuptools>=50 ; extra == 'mypyc' + - lxml ; extra == 'reports' + - pip ; extra == 'install-types' + - orjson ; extra == 'faster-cache' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/fd/a3/47cbd4e85bec4335a9cd80cf67dbc02be21b5d4c9c23ad6b95d6c5196bac/mypy-1.19.1-cp311-cp311-win_amd64.whl + name: mypy + version: 1.19.1 + sha256: 4b84a7a18f41e167f7995200a1d07a4a6810e89d29859df936f1c3923d263042 + requires_dist: + - typing-extensions>=4.6.0 + - mypy-extensions>=1.0.0 + - pathspec>=0.9.0 + - tomli>=1.1.0 ; python_full_version < '3.11' + - librt>=0.6.2 ; platform_python_implementation != 'PyPy' + - psutil>=4.0 ; extra == 'dmypy' + - setuptools>=50 ; extra == 'mypyc' + - lxml ; extra == 'reports' + - pip ; extra == 'install-types' + - orjson ; extra == 'faster-cache' + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/79/7b/2c79738432f5c924bef5071f933bcc9efd0473bac3b4aa584a6f7c1c8df8/mypy_extensions-1.1.0-py3-none-any.whl + name: mypy-extensions + version: 1.1.0 + sha256: 1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 + requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/3d/fd/dfb6db427bb4e0a50c9802b11df0b69d9364192f3db999849cde9209c8d0/nbclassic-1.3.3-py3-none-any.whl name: nbclassic version: 1.3.3 @@ -7040,15 +7218,15 @@ packages: version: 1.6.0 sha256: 87af6efd6b5e897c81050477ef65c62e2b2f35d51703cae01aff2905b1852e1c requires_python: '>=3.5' -- pypi: https://files.pythonhosted.org/packages/6f/84/c0dc75c7fb596135f999e59a410d9f45bdabb989f1cb911f0016d22b747b/nh3-0.3.3-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +- pypi: https://files.pythonhosted.org/packages/73/4f/af8e9071d7464575a7316831938237ffc9d92d27f163dbdd964b1309cd9b/nh3-0.3.4-cp38-abi3-win_amd64.whl name: nh3 - version: 0.3.3 - sha256: e98fa3dbfd54e25487e36ba500bc29bca3a4cab4ffba18cfb1a35a2d02624297 + version: 0.3.4 + sha256: c10b1f0c741e257a5cb2978d6bac86e7c784ab20572724b20c6402c2e24bce75 requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/73/88/1ce287ef8649dc51365b5094bd3713b76454838140a32ab4f8349973883c/nh3-0.3.3-cp38-abi3-win_amd64.whl +- pypi: https://files.pythonhosted.org/packages/d6/b7/ec1cbc6b297a808c513f59f501656389623fc09ad6a58c640851289c7854/nh3-0.3.4-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl name: nh3 - version: 0.3.3 - sha256: 2efd17c0355d04d39e6d79122b42662277ac10a17ea48831d90b46e5ef7e4fc0 + version: 0.3.4 + sha256: 0961a27dc2057c38d0364cb05880e1997ae1c80220cbc847db63213720b8f304 requires_python: '>=3.8' - pypi: https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl name: nodeenv @@ -7131,146 +7309,146 @@ packages: - pytest-jupyter ; extra == 'test' - pytest-tornasync ; extra == 'test' requires_python: '>=3.7' -- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py311h2e04523_1.conda - sha256: 2f9971a62316b9acb6ade749cebb59ffe750d1c2d99fe7061c6440589f6d3299 - md5: a8105076864776eceae69d64d30e24d7 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py311h2e04523_0.conda + sha256: ea4e92bb68b58ce92c0d4152c308eecfee94f131d5ef247395fbe70b7697074d + md5: cfc8f864dea571677095ebae8e6f0c07 depends: - python + - libgcc >=14 - __glibc >=2.17,<3.0.a0 - libstdcxx >=14 - - libgcc >=14 - - libblas >=3.9.0,<4.0a0 - - python_abi 3.11.* *_cp311 - libcblas >=3.9.0,<4.0a0 - liblapack >=3.9.0,<4.0a0 + - python_abi 3.11.* *_cp311 + - libblas >=3.9.0,<4.0a0 constrains: - numpy-base <0a0 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/numpy?source=hash-mapping - size: 9385101 - timestamp: 1770098496391 -- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py312h33ff503_1.conda - sha256: fec4d37e1a7c677ddc07bb968255df74902733398b77acc1d05f9dc599e879df - md5: 3569a8fca2dd3202e4ab08f42499f6d3 + - pkg:pypi/numpy?source=compressed-mapping + size: 9384747 + timestamp: 1773839224422 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py312h33ff503_0.conda + sha256: 1aab7ba963affa572956b1bd8d239df52a9c7bc799c560f98bc658ab70224e10 + md5: 5930ee8a175a242b4f001b1e9e72024f depends: - python - libgcc >=14 - - __glibc >=2.17,<3.0.a0 - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 - liblapack >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 - python_abi 3.12.* *_cp312 - libcblas >=3.9.0,<4.0a0 - - libblas >=3.9.0,<4.0a0 constrains: - numpy-base <0a0 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/numpy?source=hash-mapping - size: 8757566 - timestamp: 1770098484112 -- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.2-py313hf6604e3_1.conda - sha256: 2eb8be25a7504f058a153a84be70471e0ebbf6bd0411ae2b6d34904b89d86fe3 - md5: ca9c6ba4beac38cb3d0a85afde27f94c + - pkg:pypi/numpy?source=compressed-mapping + size: 8757569 + timestamp: 1773839284329 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.3-py313hf6604e3_0.conda + sha256: bcf75998ea3ae133df3580fb427d1054b006b093799430f499fd7ce8207d34c7 + md5: c4a9d2e77eb9fee983a70cf5f047c202 depends: - python + - libstdcxx >=14 - libgcc >=14 - __glibc >=2.17,<3.0.a0 - - libstdcxx >=14 - - liblapack >=3.9.0,<4.0a0 - - libcblas >=3.9.0,<4.0a0 - python_abi 3.13.* *_cp313 + - libcblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 - libblas >=3.9.0,<4.0a0 constrains: - numpy-base <0a0 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/numpy?source=hash-mapping - size: 8857152 - timestamp: 1770098515258 -- conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.2-py311h80b3fa1_1.conda - sha256: c5cd26fb28d92d6c3843b96489f433ef87d1866d03a746f7228230b74bef431a - md5: a824c6667179120c458beb9e9394932f + - pkg:pypi/numpy?source=compressed-mapping + size: 8857056 + timestamp: 1773839226294 +- conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.3-py311h65cb7f3_0.conda + sha256: 93520cfec52161a24d7309ddc942b003ddd61c38ede4e134fbea6b0ec749871a + md5: e37a9cfab4d96b7945119a3095087648 depends: - python - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - - python_abi 3.11.* *_cp311 - - libcblas >=3.9.0,<4.0a0 - liblapack >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - python_abi 3.11.* *_cp311 - libblas >=3.9.0,<4.0a0 constrains: - numpy-base <0a0 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/numpy?source=hash-mapping - size: 7803678 - timestamp: 1770098404597 -- conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.2-py312ha72d056_1.conda - sha256: bae400995eed564cf68d3939d5b782680407b3e25dc7363687df19c6b2cf396f - md5: 52254edfb993f9e61552c63813041689 + - pkg:pypi/numpy?source=compressed-mapping + size: 7803616 + timestamp: 1773839131773 +- conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.3-py312ha3f287d_0.conda + sha256: f0b92b9f58406ce21c7d0f037e58cb62380daffb9232c7cb31ab5edc217527e6 + md5: 6169671e14dc7c36eebfd9870446f11c depends: - python - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - libcblas >=3.9.0,<4.0a0 - - python_abi 3.12.* *_cp312 - - libblas >=3.9.0,<4.0a0 - liblapack >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + - python_abi 3.12.* *_cp312 constrains: - numpy-base <0a0 license: BSD-3-Clause license_family: BSD purls: - - pkg:pypi/numpy?source=compressed-mapping - size: 7163949 - timestamp: 1770098408393 -- conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.2-py313hce7ae62_1.conda - sha256: a926b0f781c44fdd10e11ec7e7a86ac588ec40b339ac2b4a8459def6d99b613b - md5: 7db4fcf0a8a985d3f15270ddc7ac0aac + - pkg:pypi/numpy?source=hash-mapping + size: 7166412 + timestamp: 1773839142889 +- conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.3-py313ha8dc839_0.conda + sha256: b01143d91ac22a37595c96023616dab0509ca22ee7791747dd52cc5c651f9b11 + md5: 764b3adfdb549bbbf58a9419f237ac25 depends: - python - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - - liblapack >=3.9.0,<4.0a0 - - python_abi 3.13.* *_cp313 - - libblas >=3.9.0,<4.0a0 - libcblas >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + - python_abi 3.13.* *_cp313 + - liblapack >=3.9.0,<4.0a0 constrains: - numpy-base <0a0 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping - size: 7251046 - timestamp: 1770098409520 -- conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.2-py314h06c3c77_1.conda - sha256: 34fc25b81cfa987e1825586ddb1a4ac76a246fdef343c9171109017674ad6503 - md5: 2fccd2c4e9feb4e4c2a90043015525d6 + size: 7254954 + timestamp: 1773839147528 +- conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.3-py314h02f10f6_0.conda + sha256: e4afa67a7350836a1d652f8e7351fe4cb853f8eb8b5c86c9203cefff67669083 + md5: 54355aaff5c94c602b7b9540fbc3ca1d depends: - python - vc >=14.3,<15 - vc14_runtime >=14.44.35208 - ucrt >=10.0.20348.0 - - python_abi 3.14.* *_cp314 + - libblas >=3.9.0,<4.0a0 - libcblas >=3.9.0,<4.0a0 + - python_abi 3.14.* *_cp314 - liblapack >=3.9.0,<4.0a0 - - libblas >=3.9.0,<4.0a0 constrains: - numpy-base <0a0 license: BSD-3-Clause license_family: BSD purls: - pkg:pypi/numpy?source=hash-mapping - size: 7309134 - timestamp: 1770098414535 + size: 7311362 + timestamp: 1773839141373 - pypi: https://files.pythonhosted.org/packages/76/2a/d6dcec4a68ebd9056cd3bede600a5a085d21cc8df37c8b269d3570e1fc05/Oasis_Optimization-1.0.3-py3-none-any.whl name: oasis-optimization version: 1.0.3 @@ -8447,17 +8625,17 @@ packages: version: '3.0' sha256: b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992 requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl name: pygments - version: 2.19.2 - sha256: 86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b + version: 2.20.0 + sha256: 81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176 requires_dist: - colorama>=0.4.6 ; extra == 'windows-terminal' - requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/6f/2c/5b079febdc65e1c3fb2729bf958d18b45be7113828528e8a0b5850dd819a/pymdown_extensions-10.21-py3-none-any.whl + requires_python: '>=3.9' +- pypi: https://files.pythonhosted.org/packages/f7/27/a2fc51a4a122dfd1015e921ae9d22fee3d20b0b8080d9a704578bf9deece/pymdown_extensions-10.21.2-py3-none-any.whl name: pymdown-extensions - version: '10.21' - sha256: 91b879f9f864d49794c2d9534372b10150e6141096c3908a455e45ca72ad9d3f + version: 10.21.2 + sha256: 5c0fd2a2bea14eb39af8ff284f1066d898ab2187d81b889b75d46d4348c01638 requires_dist: - markdown>=3.6 - pyyaml @@ -8684,10 +8862,10 @@ packages: - setuptools ; extra == 'dev' - xmlschema ; extra == 'dev' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/9d/7a/d968e294073affff457b041c2be9868a40c1c71f4a35fcc1e45e5493067b/pytest_cov-7.1.0-py3-none-any.whl name: pytest-cov - version: 7.0.0 - sha256: 3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861 + version: 7.1.0 + sha256: a0461110b7865f9a271aa1b51e516c9a95de9d696734a2f71e3e78f46e1d4678 requires_dist: - coverage[toml]>=7.10.6 - pluggy>=1.2 @@ -8877,10 +9055,10 @@ packages: requires_dist: - six>=1.5 requires_python: '>=2.7,!=3.0.*,!=3.1.*,!=3.2.*' -- pypi: https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/67/0f/019d3949a40280f6193b62bc010177d4ce702d0fce424322286488569cd3/python_discovery-1.2.1-py3-none-any.whl name: python-discovery - version: 1.1.3 - sha256: 90e795f0121bc84572e737c9aa9966311b9fde44ffb88a5953b3ec9b31c6945e + version: 1.2.1 + sha256: b6a957b24c1cd79252484d3566d1b49527581d46e789aaf43181005e56201502 requires_dist: - filelock>=3.15.4 - platformdirs>=4.3.6,<5 @@ -8894,12 +9072,11 @@ packages: - pytest>=8.3.5 ; extra == 'testing' - setuptools>=75.1 ; extra == 'testing' requires_python: '>=3.8' -- pypi: https://files.pythonhosted.org/packages/51/e5/fecf13f06e5e5f67e8837d777d1bc43fac0ed2b77a676804df5c34744727/python_json_logger-4.0.0-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/27/be/0631a861af4d1c875f096c07d34e9a63639560a717130e7a87cbc82b7e3f/python_json_logger-4.1.0-py3-none-any.whl name: python-json-logger - version: 4.0.0 - sha256: af09c9daf6a813aa4cc7180395f50f2a9e5fa056034c9953aec92e381c5ba1e2 + version: 4.1.0 + sha256: 132994765cf75bf44554be9aa49b06ef2345d23661a96720262716438141b6b2 requires_dist: - - typing-extensions ; python_full_version < '3.10' - orjson ; implementation_name != 'pypy' and extra == 'dev' - msgspec ; implementation_name != 'pypy' and extra == 'dev' - validate-pyproject[all] ; extra == 'dev' @@ -8908,7 +9085,6 @@ packages: - mypy ; extra == 'dev' - pytest ; extra == 'dev' - freezegun ; extra == 'dev' - - backports-zoneinfo ; python_full_version < '3.9' and extra == 'dev' - tzdata ; extra == 'dev' - build ; extra == 'dev' - mkdocs ; extra == 'dev' @@ -8919,7 +9095,7 @@ packages: - mkdocs-gen-files ; extra == 'dev' - mkdocs-literate-nav ; extra == 'dev' - mike ; extra == 'dev' - requires_python: '>=3.8' + requires_python: '>=3.10' - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.11-8_cp311.conda build_number: 8 sha256: fddf123692aa4b1fc48f0471e346400d9852d96eeed77dbfdd746fa50a8ff894 @@ -9097,18 +9273,24 @@ packages: - rpds-py>=0.7.0 - typing-extensions>=4.4.0 ; python_full_version < '3.13' requires_python: '>=3.10' -- pypi: https://files.pythonhosted.org/packages/1e/db/4254e3eabe8020b458f1a747140d32277ec7a271daf1d235b70dc0b4e6e3/requests-2.32.5-py3-none-any.whl +- pypi: https://files.pythonhosted.org/packages/56/5d/c814546c2333ceea4ba42262d8c4d55763003e767fa169adc693bd524478/requests-2.33.0-py3-none-any.whl name: requests - version: 2.32.5 - sha256: 2462f94637a34fd532264295e186976db0f5d453d1cdd31473c85a6a161affb6 + version: 2.33.0 + sha256: 3324635456fa185245e24865e810cecec7b4caf933d7eb133dcde67d48cee69b requires_dist: - charset-normalizer>=2,<4 - idna>=2.5,<4 - - urllib3>=1.21.1,<3 - - certifi>=2017.4.17 + - urllib3>=1.26,<3 + - certifi>=2023.5.7 - pysocks>=1.5.6,!=1.5.7 ; extra == 'socks' - - chardet>=3.0.2,<6 ; extra == 'use-chardet-on-py3' - requires_python: '>=3.9' + - chardet>=3.0.2,<8 ; extra == 'use-chardet-on-py3' + - pytest-httpbin==2.1.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-mock ; extra == 'test' + - pytest-xdist ; extra == 'test' + - pysocks>=1.5.6,!=1.5.7 ; extra == 'test' + - pytest>=3 ; extra == 'test' + requires_python: '>=3.10' - pypi: https://files.pythonhosted.org/packages/3f/51/d4db610ef29373b879047326cbf6fa98b6c1969d6f6dc423279de2b1be2c/requests_toolbelt-1.0.0-py2.py3-none-any.whl name: requests-toolbelt version: 1.0.0 @@ -10643,31 +10825,30 @@ packages: - pytest-enabler>=2.2 ; extra == 'enabler' - pytest-mypy ; extra == 'type' requires_python: '>=3.9' -- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.1-hb9d3cd8_2.conda - sha256: 5d7c0e5f0005f74112a34a7425179f4eb6e73c92f5d109e6af4ddeca407c92ab - md5: c9f075ab2f33b3bbee9e62d4ad0a6cd8 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-1.3.2-h25fd6f3_2.conda + sha256: 245c9ee8d688e23661b95e3c6dd7272ca936fabc03d423cdb3cdee1bbcf9f2f2 + md5: c2a01a08fc991620a74b32420e97868a depends: - __glibc >=2.17,<3.0.a0 - - libgcc >=13 - - libzlib 1.3.1 hb9d3cd8_2 + - libzlib 1.3.2 h25fd6f3_2 license: Zlib license_family: Other purls: [] - size: 92286 - timestamp: 1727963153079 -- conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.1-h2466b09_2.conda - sha256: 8c688797ba23b9ab50cef404eca4d004a948941b6ee533ead0ff3bf52012528c - md5: be60c4e8efa55fddc17b4131aa47acbd + size: 95931 + timestamp: 1774072620848 +- conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_2.conda + sha256: ef408f85f664a4b9c9dac3cb2e36154d9baa15a88984ea800e11060e0f2394a1 + md5: 5187ecf958be3c39110fe691cbd6873e depends: - - libzlib 1.3.1 h2466b09_2 + - libzlib 1.3.2 hfd05255_2 - ucrt >=10.0.20348.0 - - vc >=14.2,<15 - - vc14_runtime >=14.29.30139 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 license: Zlib license_family: Other purls: [] - size: 107439 - timestamp: 1727963788936 + size: 850351 + timestamp: 1774072891049 - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_6.conda sha256: 68f0206ca6e98fea941e5717cec780ed2873ffabc0e1ed34428c061e2c6268c7 md5: 4a13eeac0b5c8e5b8ab496e6c4ddd829 diff --git a/pyproject.toml b/pyproject.toml index 74dfd3c29..cc5d17a8f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ dependencies = [ "numpy >=2.1.3", "pandas >=2.2.3", "PyYAML >=6.0.2", - "pyramids-gis >=0.6.0", + "pyramids-gis >=0.6.0,!=0.9.0", "requests >=2.31.0", "scipy >=1.11.4", "serapeum-utils >=0.2.0", @@ -54,7 +54,8 @@ dev = [ "pip", "jupyterlab", "jupyter", - "commitizen >=4.8.4" + "commitizen >=4.8.4", + "mypy >=1.0" ] docs = [ @@ -133,6 +134,52 @@ style = [ ["disabled", "fg:#858585 italic"] ] +[tool.mypy] +python_version = "3.11" +warn_return_any = true +warn_unused_configs = true +disallow_untyped_defs = false +disallow_incomplete_defs = false +check_untyped_defs = true +ignore_missing_imports = true +no_implicit_optional = true +warn_redundant_casts = true +warn_unused_ignores = true +show_error_codes = true +exclude = [ + "tests/", + "examples/", + "docs/", +] + +[[tool.mypy.overrides]] +module = [ + "Hapi.catchment", + "Hapi.run", + "Hapi.calibration", + "Hapi.wrapper", +] +# These modules use a builder pattern: attributes are typed X | None in +# __init__ and populated by separate read_*() methods before use. Methods +# assume attributes are non-None but mypy cannot verify call order. +# Fix: add assert-based narrowing helpers (e.g. _require_spatial_data()) +# that return non-None types, then remove these suppressions one by one. +# Error count without overrides: ~257 across these 4 modules. +disable_error_code = [ + "union-attr", # .attr on X | None (159 sites) + "attr-defined", # .attr on None (11 sites) + "arg-type", # passing X | None where X expected (13 sites) + "misc", # slice index, None not callable (13 sites) + "operator", # arithmetic on X | None (4 sites) + "index", # indexing X | None (2 sites) + "call-overload", # numpy overload mismatches on X | None (2 sites) +] + +[[tool.mypy.overrides]] +module = "Hapi.rrm.base_model" +disable_error_code = ["empty-body"] + + [tool.pytest.ini_options] pythonpath = ["src"] testpaths = ["tests"] diff --git a/src/Hapi/__init__.py b/src/Hapi/__init__.py index 27015af4b..94ac6e66d 100644 --- a/src/Hapi/__init__.py +++ b/src/Hapi/__init__.py @@ -1,20 +1,4 @@ -"""Hapi.""" - -from importlib.metadata import PackageNotFoundError # type: ignore -from importlib.metadata import version - - -try: - __version__ = version(__name__) -except PackageNotFoundError: # pragma: no cover - __version__ = "unknown" - -__author__ = "Mostafa Farrag" -__email__ = "moah.farag@gmail.com" -__docformat__ = "restructuredtext" - - -__doc__ = """ +""" Hapi - Hydrological library for Python ===================================================================== @@ -30,5 +14,14 @@ - Easy calibration of the model using Harmony search method and Genetic Algorithms - flexible GIS function to process rasters interpolate values and georeference calculated discharge values to the correct place - """ +from __future__ import annotations + +from importlib.metadata import PackageNotFoundError +from importlib.metadata import version + + +try: + __version__ = version(__name__) +except PackageNotFoundError: # pragma: no cover + __version__ = "unknown" \ No newline at end of file diff --git a/src/Hapi/calibration.py b/src/Hapi/calibration.py index 884935985..9fe641282 100644 --- a/src/Hapi/calibration.py +++ b/src/Hapi/calibration.py @@ -1,13 +1,14 @@ -"""Calibration. +"""Calibration module for the Hapi hydrological modeling framework. -calibration contains functions to to connect the parameter spatial distribution -function with the with both component of the spatial representation of the hydrological -process (conceptual model & spatial routing) to calculate the performance of predicted -runoff at known locations based on given performance function - -@author: Mostafa +The calibration module connects the parameter spatial distribution function +with both components of the spatial representation of the hydrological +process (conceptual model and spatial routing) to calculate the performance +of predicted runoff at known locations based on a given performance function. """ -from typing import Any, Optional, List +from __future__ import annotations + +from collections.abc import Callable +from typing import Any import numpy as np from Oasis.harmonysearch import HSapi @@ -18,16 +19,17 @@ class Calibration(Catchment): - """Calibration. + """Calibration class for distributed hydrological model parameter optimization. - Calibration class contains to connect the parameter spatial distribution - function with both components of the spatial representation of the - hydrological process (conceptual model & spatial routing) to calculate the - performance of predicted runoff at known locations based on given - performance function + The Calibration class connects the parameter spatial distribution function + with both components of the spatial representation of the hydrological + process (conceptual model and spatial routing) to calculate the + performance of predicted runoff at known locations based on a given + performance function. - The calibration class is subclass from the Catchment superclass, so you - need to create the Catchment object first to be able to run the calibration + The Calibration class is a subclass of the Catchment superclass, so you + need to create the Catchment object first to be able to run the + calibration. """ def __init__( @@ -36,29 +38,24 @@ def __init__( start: str, end: str, fmt: str = "%Y-%m-%d", - spatial_resolution: Optional[str] = "Lumped", - temporal_resolution: Optional[str] = "Daily", - routing_method: Optional[str] = "Muskingum", + spatial_resolution: str | None = "Lumped", + temporal_resolution: str | None = "Daily", + routing_method: str | None = "Muskingum", ): - """Calibration. - - to instantiate the Calibration object, you need to provide the following - arguments - - Parameters - ---------- - name : [str] - Name of the Catchment. - start : [str] - starting date. - end : [str] - end date. - fmt: [str], optional - format of the given date. The default is "%Y-%m-%d". - spatial_resolution: [str], optional - Lumped or 'Distributed'. The default is 'Lumped'. - temporal_resolution: [str], optional - "Hourly" or "Daily". The default is "Daily". + """Initialize the Calibration object. + + Args: + name (Any): Name of the Catchment. + start (str): Starting date as a string. + end (str): End date as a string. + fmt (str, optional): Format of the given date. + Default is "%Y-%m-%d". + spatial_resolution (str, optional): Spatial resolution mode, + either "Lumped" or "Distributed". Default is "Lumped". + temporal_resolution (str, optional): Temporal resolution mode, + either "Hourly" or "Daily". Default is "Daily". + routing_method (str, optional): Routing method name. + Default is "Muskingum". """ super().__init__( name, @@ -69,24 +66,24 @@ def __init__( temporal_resolution, routing_method, ) + self.objective_function: Callable[..., Any] | None = None + self.OFArgs: list | None = None + self.OFvalue: float | None = None - def read_objective_function(self, objective_function: callable, args): - """read_objective_function. + def read_objective_function(self, objective_function: Callable[..., Any], args): + """Read and store the objective function and its arguments. - read_objective_function method takes the objective function and any arguments - that are needed to be passed to the objective function. + Takes the objective function and any additional arguments that + need to be passed to the objective function during calibration. - Parameters - ---------- - objective_function : [function] - callable function to calculate any kind of metric to be used in the - calibration. - args: [positional/keyword arguments] - any kind of argument you want to pass to your objective function. + Args: + objective_function (callable): A callable function to calculate + any kind of metric to be used in the calibration. + args: Any positional or keyword arguments to pass to the + objective function. If None, defaults to an empty list. - Returns - ------- - None. + Raises: + AssertionError: If objective_function is not callable. """ # check objective_function assert callable( @@ -101,22 +98,33 @@ def read_objective_function(self, objective_function: callable, args): print("Objective function is read successfully") - def extract_discharge(self, factor: List = None): - """extractDischarge. - - extractDischarge method extracts the discharge hydrograph in the - Q - - Parameters - ---------- - factor: [list/None] - list of factor if you want to multiply the simulated discharge by - a factor you have to provide a list of the factor (as many factors - as the number of gauges). The default is False. - - Returns - ------- - None. + def extract_discharge( + self, + calculate_metrics: bool = True, + frame_work_1: bool = False, + factor: list | None = None, + only_outlet: bool = False, + ): + """Extract the simulated discharge hydrograph at gauge locations. + + Extracts discharge values from the total routed discharge array + (``self.Qtot``) at each gauge location and stores them in + ``self.Qsim``. Optionally applies a multiplication factor per + gauge. + + Args: + calculate_metrics (bool, optional): Whether to calculate + performance metrics. Not used in this override but + kept for signature compatibility. Default is True. + frame_work_1 (bool, optional): True if the routing + function is Maxbas. Not used in this override but + kept for signature compatibility. Default is False. + factor (list, optional): List of multiplication factors for + the simulated discharge, one per gauge. If None, no + scaling is applied. Default is None. + only_outlet (bool, optional): True to extract discharge + only at the outlet cell. Not used in this override but + kept for signature compatibility. Default is False. """ self.Qsim = np.zeros((self.TS - 1, len(self.GaugesTable))) # error = 0 @@ -142,74 +150,49 @@ def extract_discharge(self, factor: List = None): # return error def run_calibration(self, SpatialVarFun, OptimizationArgs, printError=None): - """Run Calibration. - - - This function runs the calibration algorithm for the conceptual distributed - hydrological model - - Parameters - ---------- - SpatialVarFun: [function] - - OptimizationArgs: [Dict] - - printError: [bool] - Default is None. - - Parameters that should be defined before running the function. - ConceptualModel: - [function] conceptual model and it should contain a function called simulate - - Basic_inputs: - 1-p2: - [List] list of unoptimized parameters - p2[0] = tfac, 1 for hourly, 0.25 for 15 min time step and 24 for daily time step - p2[1] = catchment area in km2 - 2-init_st: - [list] initial values for the state variables [sp,sm,uz,lz,wc] in mm - 3-UB: - [Numeric] upper bound of the values of the parameters - 4-LB: - [Numeric] Lower bound of the values of the parameters - Q_obs: - [Numeric] Observed values of discharge - - lumpedParNo: - [int] nomber of lumped parameters, you have to enter the value of - the lumped parameter at the end of the list, default is 0 (no lumped parameters) - lumpedParPos: - [List] list of order or position of the lumped parameter among all - the parameters of the lumped model (order starts from 0 to the length - of the model parameters), default is [] (empty), the following order - of parameters is used for the lumped HBV model used - [ltt, utt, rfcf, sfcf, ttm, cfmax, cwh, cfr, fc, beta, e_corr, etf, lp, - c_flux, k, k1, alpha, perc, pcorr, Kmuskingum, Xmuskingum] - objective_function: - [function] objective function to calculate the performance of the model - and to be used in the calibration - *args: - other arguments needed on the objective function - - Returns - ------- - st: [4D array] - state variables - q_out: [1D array] - calculated Discharge at the outlet of the catchment - q_uz: [3D array] - Distributed discharge for each cell - - Example: - ---------- - >>> prec_path = "meteodata/4000/calib/prec" - >>> evap_path = "meteodata/4000/calib/evap" - >>> temp_path = "meteodata/4000/calib/temp" - >>> flow_acc_path = "GIS/4000/acc4000.tif" - >>> flow_direction_path = "GIS/4000/fd4000.tif" - >>> ParPath = "meteodata/4000/parameters.txt" - >>> p2 = [1, 227.31] - >>> st, q_out, q_uz_routed = RunModel(prec_path, evap_path, temp_path, DemPath, - >>> flow_acc_path,flow_direction_path,ParPath,p2) + """Run the calibration algorithm for the distributed hydrological model. + + Executes the Harmony Search optimization algorithm to calibrate + parameters for the conceptual distributed hydrological model. + The method distributes parameters spatially using ``SpatialVarFun``, + runs the RRM model via ``Wrapper.RRMModel``, and evaluates + performance using the stored objective function. + + The following attributes must be set on the instance before calling + this method: + + - ``Prec``, ``ET``, ``Temp``: Meteorological input arrays. + - ``FlowDirArr``: Flow direction array. + - ``rows``, ``cols``: Grid dimensions. + - ``LB``, ``UB``: Lower and upper parameter bounds. + - ``objective_function``: Objective function for evaluation. + - ``QGauges``, ``GaugesTable``: Observed discharge data and + gauge metadata. + + Args: + SpatialVarFun: Spatial variable function object with a + ``Function`` method that distributes parameters and a + ``Par3d`` attribute holding the 3D parameter array, plus + ``no_parameters`` and ``no_elem`` attributes. + OptimizationArgs: A list of three elements: + - ``OptimizationArgs[0]`` (dict): Harmony Search API + objective arguments (e.g., HMS, HMCR, PAR). + - ``OptimizationArgs[1]``: Parallel type for the + optimizer. + - ``OptimizationArgs[2]`` (dict): Solver arguments with + keys ``"store_sol"``, ``"display_opts"``, + ``"store_hst"``, and ``"hot_start"``. + printError: If not 0, prints the error value and parameters + at each iteration. Default is None. + + Returns: + tuple: Optimization result tuple containing: + - res[0]: The optimal objective function value. + - res[1]: The optimal parameter set. + + Raises: + AssertionError: If input dimensions are inconsistent or if + optimization arguments are not dictionaries. """ # input dimensions # [rows,cols] = self.FlowAcc.ReadAsArray().shape @@ -326,66 +309,45 @@ def opt_fun(par): return res def FW1Calibration(self, SpatialVarFun, OptimizationArgs, printError=None): - """FW1Calibration. - - this function runs the calibration algorithm for the conceptual distributed - hydrological model - - Parameters - ---------- - ConceptualModel: [function] - conceptual model and it should contain a function called simulate - - 2-Basic_inputs: - 1-p2: [List] - list of unoptimized parameters - p2[0] = tfac, 1 for hourly, 0.25 for 15 min time step and 24 for daily time step - p2[1] = catchment area in km2 - 2-init_st: [list] - initial values for the state variables [sp,sm,uz,lz,wc] in mm - 3-UB: [Numeric] - upper bound of the values of the parameters - 4-LB: [Numeric] - Lower bound of the values of the parameters - 3-Q_obs: [Numeric] - Observed values of discharge - - 6-lumpedParNo: [int] - nomber of lumped parameters, you have to enter the value of - the lumped parameter at the end of the list, default is 0 (no lumped parameters) - 7-lumpedParPos: [List] - list of order or position of the lumped parameter among all - the parameters of the lumped model (order starts from 0 to the length - of the model parameters), default is [] (empty), the following order - of parameters is used for the lumped HBV model used - [ltt, utt, rfcf, sfcf, ttm, cfmax, cwh, cfr, fc, beta, e_corr, etf, lp, - c_flux, k, k1, alpha, perc, pcorr, Kmuskingum, Xmuskingum] - 8-objective_function: [function] - objective function to calculate the performance of the model - and to be used in the calibration - 9-*args: - other arguments needed on the objective function - - Returns - ------- - st: [4D array] - state variables - q_out: [1D array] - calculated Discharge at the outlet of the catchment - q_uz: [3D array] - Distributed discharge for each cell - - Example - ------- - prec_path = prec_path="meteodata/4000/calib/prec" - evap_path = evap_path="meteodata/4000/calib/evap" - temp_path = temp_path="meteodata/4000/calib/temp" - flow_acc_path = "GIS/4000/acc4000.tif" - flow_direction_path = "GIS/4000/fd4000.tif" - ParPath = "meteodata/4000/"+"parameters.txt" - p2=[1, 227.31] - st, q_out, q_uz_routed = RunModel(prec_path,evap_path,temp_path,DemPath, - flow_acc_path,flow_direction_path,ParPath,p2) + """Run calibration using the FW1 (Focussed Width-1) routing scheme. + + Executes the Harmony Search optimization algorithm to calibrate + parameters for the conceptual distributed hydrological model using + the FW1 routing approach via ``Wrapper.FW1``. + + The following attributes must be set on the instance before calling + this method: + + - ``Prec``, ``ET``, ``Temp``: Meteorological input arrays. + - ``rows``, ``cols``: Grid dimensions. + - ``LB``, ``UB``: Lower and upper parameter bounds. + - ``objective_function``: Objective function for evaluation. + - ``QGauges``, ``GaugesTable``: Observed discharge data and + gauge metadata. + + Args: + SpatialVarFun: Spatial variable function object with a + ``Function`` method that distributes parameters and a + ``Par3d`` attribute holding the 3D parameter array. + OptimizationArgs: A list of three elements: + - ``OptimizationArgs[0]`` (dict): Harmony Search API + objective arguments (e.g., HMS, HMCR, PAR). + - ``OptimizationArgs[1]``: Parallel type for the + optimizer. + - ``OptimizationArgs[2]`` (dict): Solver arguments with + keys ``"store_sol"``, ``"display_opts"``, + ``"store_hst"``, and ``"hot_start"``. + printError: If not 0, prints the error value and parameters + at each iteration. Default is None. + + Returns: + tuple: Optimization result tuple containing: + - res[0]: The optimal objective function value. + - res[1]: The optimal parameter set. + + Raises: + AssertionError: If input dimensions are inconsistent or if + optimization arguments are not dictionaries. """ # input dimensions # [rows,cols] = self.FlowAcc.ReadAsArray().shape @@ -486,68 +448,50 @@ def opt_fun(par): return res def lumpedCalibration(self, Basic_inputs, OptimizationArgs, printError=None): - """runCalibration. - - this function runs the calibration algorithm for the Lumped conceptual hydrological model - - Parameters - ---------- - ConceptualModel: - [function] conceptual model and it should contain a function called simulate - data: - [numpy array] meteorological data as array with the first column as precipitation - second as evapotranspiration, third as temperature and forth column as - long term average temperature - Basic_inputs: - 1-p2: - [List] list of unoptimized parameters - p2[0] = tfac, 1 for hourly, 0.25 for 15 min time step and 24 for daily time step - p2[1] = catchment area in km2 - 2-init_st: - [list] initial values for the state variables [sp,sm,uz,lz,wc] in mm - 3-UB: - [Numeric] upper bound of the values of the parameters - 4-LB: - [Numeric] Lower bound of the values of the parameters - Q_obs: - [Numeric] Observed values of discharge - - lumpedParNo: - [int] nomber of lumped parameters, you have to enter the value of - the lumped parameter at the end of the list, default is 0 (no lumped parameters) - lumpedParPos: - [List] list of order or position of the lumped parameter among all - the parameters of the lumped model (order starts from 0 to the length - of the model parameters), default is [] (empty), the following order - of parameters is used for the lumped HBV model used - [ltt, utt, rfcf, sfcf, ttm, cfmax, cwh, cfr, fc, beta, e_corr, etf, lp, - c_flux, k, k1, alpha, perc, pcorr, Kmuskingum, Xmuskingum] - objective_function: - [function] objective function to calculate the performance of the model - and to be used in the calibration - *args: - other arguments needed on the objective function - - Returns - ------- - st: [4D array] - state variables - q_out: [1D array] - calculated Discharge at the outlet of the catchment - q_uz: [3D array] - Distributed discharge for each cell - - Examples - -------- - prec_path = prec_path="meteodata/4000/calib/prec" - evap_path = evap_path="meteodata/4000/calib/evap" - temp_path = temp_path="meteodata/4000/calib/temp" - flow_acc_path = "GIS/4000/acc4000.tif" - flow_direction_path = "GIS/4000/fd4000.tif" - ParPath = "meteodata/4000/"+"parameters.txt" - p2=[1, 227.31] - st, q_out, q_uz_routed = RunModel(prec_path,evap_path,temp_path,DemPath, - flow_acc_path,flow_direction_path,ParPath,p2) + """Run the calibration algorithm for the lumped hydrological model. + + Executes the Harmony Search optimization algorithm to calibrate + parameters for the lumped conceptual hydrological model. The + method runs the model via ``Wrapper.Lumped`` and evaluates + performance using the stored objective function. Muskingum + routing constraints are enforced as inequality constraints. + + The following attributes must be set on the instance before calling + this method: + + - ``LB``, ``UB``: Lower and upper parameter bounds. + - ``objective_function``: Objective function for evaluation. + - ``OFArgs``: Arguments for the objective function. + - ``QGauges``: Observed discharge DataFrame. + - ``dt``: Time step duration. + + Args: + Basic_inputs (dict): Dictionary containing: + - ``"Route"`` (int): Routing flag (1 to enable routing). + - ``"RoutingFn"`` (callable): Routing function to use. + - ``"InitialValues"`` (list, optional): Initial parameter + values for the optimizer. Defaults to an empty list if + not provided. + OptimizationArgs: A list of three elements: + - ``OptimizationArgs[0]`` (dict): Harmony Search API + objective arguments (e.g., HMS, HMCR, PAR). + - ``OptimizationArgs[1]``: Parallel type for the + optimizer. + - ``OptimizationArgs[2]`` (dict): Solver arguments with + keys ``"store_sol"``, ``"display_opts"``, + ``"store_hst"``, and ``"hot_start"``. + printError: If not 0, prints the error value and constraint + values at each iteration. Default is None. + + Returns: + tuple: Optimization result tuple containing: + - res[0]: The optimal objective function value. + - res[1]: The optimal parameter set. + + Raises: + AssertionError: If ``Basic_inputs`` is missing required keys + ``"Route"`` or ``"RoutingFn"``, or if optimization + arguments are not dictionaries. """ # basic inputs # check if all inputs are included diff --git a/src/Hapi/catchment.py b/src/Hapi/catchment.py index 16e7800a0..4500f40e4 100644 --- a/src/Hapi/catchment.py +++ b/src/Hapi/catchment.py @@ -1,12 +1,24 @@ -"""Catchment.""" - -__name__ = "catchment" +"""Catchment module for the Hapi hydrological modeling framework. + +This module provides the Catchment and Lake classes for reading +meteorological and spatial inputs, running distributed hydrological +models, extracting discharge, and saving results. The Catchment class +is the base class that reads all inputs required by the model +(rainfall, temperature, ET, flow accumulation, flow direction, +parameters, and gauge data). It supports both lumped and distributed +spatial modes with daily or hourly temporal resolutions. + +The Lake class provides similar functionality for simulating a lake +as a lumped model using a rating curve, where the lake and its +upstream sub-catchments are treated as one lumped model. +""" +from __future__ import annotations import datetime as dt import inspect import math import os -from typing import Optional, Union +from typing import TYPE_CHECKING import geopandas as gpd import matplotlib.dates as dates @@ -22,34 +34,21 @@ from Hapi.dem import DEM +if TYPE_CHECKING: + import matplotlib.animation + from Hapi.rrm.base_model import BaseConceptualModel + STATE_VARIABLES = ["SP", "SM", "UZ", "LZ", "WC"] class Catchment: - """Catchment. - - The class includes methods to read the meteorological and Spatial inputs of the distributed hydrological model. - Catchment class also reads the data of the gauges, it is a superclass that has the run subclass, - so you need to build the catchment object and hand it as an input to the Run class to run the model. - - Methods - ------- - 1-readRainfall - 2-readTemperature - 3-readET - 4-readFlowAcc - 5-readFlowDir - 6-ReadFlowPathLength - 7-readParameters - 8-readLumpedModel - 9-readLumpedInputs - 10-readGaugeTable - 11-readDischargeGauges - 12-readParametersBounds - 13-extractDischarge - 14-plotHydrograph - 15-PlotDistributedQ - 16-saveResults + """Catchment for reading meteorological/spatial inputs and running the model. + + The Catchment class includes methods to read the meteorological and + spatial inputs of the distributed hydrological model. It also reads the + data of the gauges. It is a superclass that has the Run subclass, so you + need to build the Catchment object and hand it as an input to the Run + class to run the model. """ def __init__( @@ -58,30 +57,30 @@ def __init__( start_data: str, end: str, fmt: str = "%Y-%m-%d", - spatial_resolution: Optional[str] = "Lumped", - temporal_resolution: Optional[str] = "Daily", - routing_method: Optional[str] = "Muskingum", + spatial_resolution: str | None = "Lumped", + temporal_resolution: str | None = "Daily", + routing_method: str | None = "Muskingum", ): - """Catchment. - - Parameters - ---------- - name : [str] - Name of the Catchment. - start_data : [str] - starting date. - end : [str] - end date. - fmt: [str], optional - format of the given date. The default is "%Y-%m-%d". - spatial_resolution: [str, optional] - Lumped or 'Distributed'. The default is 'Lumped'. - temporal_resolution : TYPE, optional - "Hourly" or "Daily". The default is "Daily". - - Returns - ------- - None. + """Initialize a Catchment instance. + + Args: + name (str): Name of the Catchment. + start_data (str): Starting date. + end (str): End date. + fmt (str, optional): Format of the given date. + Default is "%Y-%m-%d". + spatial_resolution (str, optional): "Lumped" or + "Distributed". Default is "Lumped". + temporal_resolution (str, optional): "Hourly" or "Daily". + Default is "Daily". + routing_method (str, optional): Routing method name. + Default is "Muskingum". + + Raises: + ValueError: If `spatial_resolution` is not "lumped" or + "distributed". + ValueError: If `temporal_resolution` is not "daily" or + "hourly". """ self.name = name self.start = dt.datetime.strptime(start_data, fmt) @@ -113,100 +112,91 @@ def __init__( self.conversion_factor = 24 self.routing_method = routing_method - self.Parameters = None - self.data = None - self.Prec = None - self.TS = None - self.Temp = None - self.ET = None - self.ll_temp = None - self.QGauges = None - self.Snow = None - self.Maxbas = None - self.LumpedModel = None - self.CatArea = None - self.InitialCond = None - self.q_init = None - self.GaugesTable = None - self.UB = None - self.LB = None - self.cols = None - self.rows = None - self.NoDataValue = None - self.FlowAccArr = None - self.no_elem = None - self.acc_val = None - self.Outlet = None - self.CellSize = None - self.px_area = None - self.px_tot_area = None - self.FlowDirArr = None - self.FDT = None - self.FPLArr = None - ( - self.DEM, - self.BankfullDepth, - self.RiverWidth, - self.RiverRoughness, - self.FloodPlainRoughness, - ) = (None, None, None, None, None) - self.qout, self.Qtot = None, None - self.quz_routed, self.qlz_translated, self.state_variables = None, None, None - self.anim = None - self.quz, self.qlz = None, None - self.Qsim = None - self.Metrics = None + self.Parameters: np.ndarray | list | None = None + self.data: np.ndarray | None = None + self.Prec: np.ndarray | None = None + self.TS: int | None = None + self.Temp: np.ndarray | None = None + self.ET: np.ndarray | None = None + self.ll_temp: np.ndarray | float | None = None + self.QGauges: pd.DataFrame | None = None + self.Snow: int | None = None + self.Maxbas: bool | None = None + self.LumpedModel: BaseConceptualModel | None = None + self.CatArea: float | int | None = None + self.InitialCond: list | None = None + self.q_init: float | None = None + self.GaugesTable: gpd.GeoDataFrame | pd.DataFrame | None = None + self.UB: np.ndarray | None = None + self.LB: np.ndarray | None = None + self.cols: int | None = None + self.rows: int | None = None + self.NoDataValue: float | None = None + self.FlowAccArr: np.ndarray | None = None + self.no_elem: int | None = None + self.acc_val: list[int] | None = None + self.Outlet: tuple | None = None + self.CellSize: float | None = None + self.px_area: float | None = None + self.px_tot_area: float | None = None + self.FlowDirArr: np.ndarray | None = None + self.FDT: dict | None = None + self.FPLArr: np.ndarray | None = None + self.DEM: np.ndarray | None = None + self.BankfullDepth: np.ndarray | None = None + self.RiverWidth: np.ndarray | None = None + self.RiverRoughness: np.ndarray | None = None + self.FloodPlainRoughness: np.ndarray | None = None + self.qout: np.ndarray | None = None + self.Qtot: np.ndarray | None = None + self.quz_routed: np.ndarray | None = None + self.qlz_translated: np.ndarray | None = None + self.state_variables: np.ndarray | None = None + self.anim: matplotlib.animation.FuncAnimation | None = None + self.quz: np.ndarray | None = None + self.qlz: np.ndarray | None = None + self.Qsim: np.ndarray | None = None + self.Metrics: pd.DataFrame | None = None def read_rainfall( self, path: str, - start: str = None, - end: str = None, + start: str | None = None, + end: str | None = None, fmt: str = "%Y-%m-%d", regex_string=r"\d{4}.\d{2}.\d{2}", date: bool = True, - file_name_data_fmt: str = None, + file_name_data_fmt: str | None = None, extension: str = ".tif", ): - r"""readRainfall. - - Parameters - ---------- - path: [str] - A path to the Folder contains precipitation rasters. - fmt: [str] - format of the given date - start: [str] - start date if you want to read the input temperature for a specific period only, - if not given all rasters in the given path will be read. - end: [str] - end date if you want to read the input temperature for a specific period only, - if not given all rasters in the given path will be read. - regex_string: [str] - a regex string that we can use to locate the date in the file names.Default is r"\d{4}.\d{ - 2}.\d{2}". - >>> fname = "MSWEP_YYYY.MM.DD.tif" - >>> regex_string = r"\d{4}.\d{2}.\d{2}" - - or - >>> fname = "MSWEP_YYYY_M_D.tif" - >>> regex_string = r"\d{4}_\d{1}_\d{1}" - - if there is a number at the beginning of the name - >>> fname = "1_MSWEP_YYYY_M_D.tif" - >>> regex_string = r"\d+" - date: [bool]. Default is True. - True if the number in the file name is a date. - file_name_data_fmt : [str] - if the files names' have a date and you want to read them ordered .Default is None - >>> "MSWEP_YYYY.MM.DD.tif" - >>> file_name_data_fmt = "%Y.%m.%d" - extension: [str] - the extension of the files you want to read from the given path. Default is ".tif". - - - Returns - ------- - prec: [array attribute] - array containing the spatial rainfall values + r"""Read rainfall rasters into a 3D numpy array. + + Args: + path (str): Path to the folder containing precipitation + rasters. + start (str, optional): Start date to read a specific + period only. If not given, all rasters in the path + will be read. Default is None. + end (str, optional): End date to read a specific period + only. If not given, all rasters in the path will be + read. Default is None. + fmt (str, optional): Format of the given date. Default + is "%Y-%m-%d". + regex_string (str, optional): A regex string to locate + the date in the file names. Default is + r"\d{4}.\d{2}.\d{2}". + date (bool, optional): True if the number in the file + name is a date. Default is True. + file_name_data_fmt (str, optional): Date format in file + names for ordered reading. Default is None. + extension (str, optional): The extension of the files to + read from the given path. Default is ".tif". + + Raises: + FileNotFoundError: If the path does not exist or the + folder is empty. + TypeError: If the resulting precipitation array is not a + numpy ndarray. """ if self.Prec is None: # data type @@ -241,55 +231,46 @@ def read_rainfall( def read_temperature( self, path: str, - ll_temp: Union[list, np.ndarray] = None, - start: str = None, - end: str = None, + ll_temp: list | np.ndarray | None = None, + start: str | None = None, + end: str | None = None, fmt: str = "%Y-%m-%d", regex_string=r"\d{4}.\d{2}.\d{2}", date: bool = True, - file_name_data_fmt: str = None, + file_name_data_fmt: str | None = None, extension: str = ".tif", ): - r"""readTemperature. - - Parameters - ---------- - path: [str] - A path to the Folder contains temperature rasters. - fmt: [str] - format of the given date - start: [str] - start date if you want to read the input temperature for a specific period only, - if not given all rasters in the given path will be read. - end: [str] - end date if you want to read the input temperature for a specific period only, - if not given all rasters in the given path will be read. - ll_temp: [list/ ndarray] - long-term temperature - regex_string: [str] - a regex string that we can use to locate the date in the file names. Default is r"\d{4}.\d{ - 2}.\d{2}". - >>> fname = "MSWEP_YYYY.MM.DD.tif" - >>> regex_string = r"\d{4}.\d{2}.\d{2}" - - or - >>> fname = "MSWEP_YYYY_M_D.tif" - >>> regex_string = r"\d{4}_\d{1}_\d{1}" - - if there is a number at the beginning of the name - >>> fname = "1_MSWEP_YYYY_M_D.tif" - >>> regex_string = r"\d+" - date: [bool] - True if the number in the file name is a date. Default is True. - file_name_data_fmt : [str] - if the files names' have a date, and you want to read them ordered .Default is None - >>> "MSWEP_YYYY.MM.DD.tif" - >>> file_name_data_fmt = "%Y.%m.%d" - extension: [str] - the extension of the files you want to read from the given path. Default is ".tif". - - Returns - ------- - Temp: [array attribute] - array containing the spatial temperature values + r"""Read temperature rasters into a 3D numpy array. + + Args: + path (str): Path to the folder containing temperature + rasters. + ll_temp (list | np.ndarray, optional): Long-term + average temperature array. If None, it is computed + from the mean of the temperature data. Default is + None. + start (str, optional): Start date to read a specific + period only. If not given, all rasters in the path + will be read. Default is None. + end (str, optional): End date to read a specific period + only. If not given, all rasters in the path will be + read. Default is None. + fmt (str, optional): Format of the given date. Default + is "%Y-%m-%d". + regex_string (str, optional): A regex string to locate + the date in the file names. Default is + r"\d{4}.\d{2}.\d{2}". + date (bool, optional): True if the number in the file + name is a date. Default is True. + file_name_data_fmt (str, optional): Date format in file + names for ordered reading. Default is None. + extension (str, optional): The extension of the files to + read from the given path. Default is ".tif". + + Raises: + AssertionError: If the path does not exist or the + resulting array is not a numpy ndarray. + Exception: If the folder is empty. """ if self.Temp is None: # data type @@ -329,52 +310,41 @@ def read_temperature( def read_et( self, path: str, - start: str = None, - end: str = None, + start: str | None = None, + end: str | None = None, fmt: str = "%Y-%m-%d", regex_string=r"\d{4}.\d{2}.\d{2}", date: bool = True, - file_name_data_fmt: str = None, + file_name_data_fmt: str | None = None, extension: str = ".tif", ): - r"""readET. - - Parameters - ---------- - path : [String] - path to the Folder contains Evapotranspiration rasters. - fmt: [str] - format of the given date - start: [str] - start date if you want to read the input temperature for a specific period only, - if not given all rasters in the given path will be read. - end: [str] - end date if you want to read the input temperature for a specific period only, - if not given all rasters in the given path will be read. - regex_string: [str] - a regex string that we can use to locate the date in the file names.Default is r"\d{4}.\d{ - 2}.\d{2}". - >>> fname = "MSWEP_YYYY.MM.DD.tif" - >>> regex_string = r"\d{4}.\d{2}.\d{2}" - - or - >>> fname = "MSWEP_YYYY_M_D.tif" - >>> regex_string = r"\d{4}_\d{1}_\d{1}" - - if there is a number at the beginning of the name - >>> fname = "1_MSWEP_YYYY_M_D.tif" - >>> regex_string = r"\d+" - date: [bool] - True if the number in the file name is a date. Default is True. - file_name_data_fmt : [str] - if the files names' have a date and you want to read them ordered .Default is None - >>> "MSWEP_YYYY.MM.DD.tif" - >>> file_name_data_fmt = "%Y.%m.%d" - extension: [str] - the extension of the files you want to read from the given path. Default is ".tif". - - Returns - ------- - ET: [array attribute] - array containing the spatial Evapotranspiration values + r"""Read evapotranspiration rasters into a 3D numpy array. + + Args: + path (str): Path to the folder containing + evapotranspiration rasters. + start (str, optional): Start date to read a specific + period only. If not given, all rasters in the path + will be read. Default is None. + end (str, optional): End date to read a specific period + only. If not given, all rasters in the path will be + read. Default is None. + fmt (str, optional): Format of the given date. Default + is "%Y-%m-%d". + regex_string (str, optional): A regex string to locate + the date in the file names. Default is + r"\d{4}.\d{2}.\d{2}". + date (bool, optional): True if the number in the file + name is a date. Default is True. + file_name_data_fmt (str, optional): Date format in file + names for ordered reading. Default is None. + extension (str, optional): The extension of the files to + read from the given path. Default is ".tif". + + Raises: + AssertionError: If the path does not exist or the + resulting array is not a numpy ndarray. + Exception: If the folder is empty. """ if self.ET is None: # data type @@ -404,26 +374,20 @@ def read_et( logger.debug("Potential Evapotranspiration data are read successfully") def read_flow_acc(self, path: str): - """readFlowAcc. - - Parameters - ---------- - path : [String] - path to the Flow Accumulation raster of the catchment - (it should include the raster name and extension). - - Returns - ------- - flow_acc: [array attribute] - array containing the spatial Evapotranspiration values - rows: [integer] - number of rows in the flow acc array - cols:[integer] - number of columns in the flow acc array - NoDataValue:[numeric] - the NoDataValue - no_elem : [integer] - number of cells in the domain + """Read flow accumulation raster and compute cell properties. + + Reads the flow accumulation raster, extracts the number of rows, + columns, NoDataValue, number of domain cells, outlet location, + cell size, and pixel area. + + Args: + path (str): Path to the flow accumulation raster file + (must include the raster name and .tif extension). + + Raises: + TypeError: If `path` is not a string. + AssertionError: If the path does not exist or does not + end with ".tif". """ # data type if not isinstance(path, str): @@ -438,6 +402,8 @@ def read_flow_acc(self, path: str): assert os.path.exists(path), path + " you have provided does not exist" flow_acc = gdal.Open(path) + if flow_acc is None: + raise FileNotFoundError(f"GDAL could not open: {path}") [self.rows, self.cols] = flow_acc.ReadAsArray().shape # check flow accumulation input raster self.NoDataValue = flow_acc.GetRasterBand(1).GetNoDataValue() @@ -452,8 +418,8 @@ def read_flow_acc(self, path: str): if math.isclose(self.FlowAccArr[i, j], self.NoDataValue, rel_tol=0.001): self.FlowAccArr[i, j] = np.nan - self.no_elem = np.size(self.FlowAccArr[:, :]) - np.count_nonzero( - (self.FlowAccArr[np.isnan(self.FlowAccArr)]) + self.no_elem = int(np.size(self.FlowAccArr[:, :]) - np.count_nonzero( + (self.FlowAccArr[np.isnan(self.FlowAccArr)])) ) self.acc_val = [ int(self.FlowAccArr[i, j]) @@ -496,21 +462,18 @@ def read_flow_acc(self, path: str): logger.debug("Flow Accmulation input is read successfully") def read_flow_dir(self, path: str): - """Read Flow Direction. - - reads the flow direction raster. - - Parameters - ---------- - path : [str] - path to the flow direction raster. - - Returns - ------- - FlowDirArr: [array]. - array of the flow direction raster - FDT: [dictionary] - flow direction table + """Read the flow direction raster and build the flow direction table. + + Args: + path (str): Path to the flow direction raster file (must + include the raster name and .tif extension). + + Raises: + AssertionError: If `path` is not a string or does not + exist. + ValueError: If the file does not have a ".tif" extension. + AssertionError: If the raster contains values other than + 1, 2, 4, 8, 16, 32, 64, 128. """ # data type assert isinstance(path, str), "path input should be string type" @@ -524,6 +487,8 @@ def read_flow_dir(self, path: str): # check whether the path exists or not assert os.path.exists(path), path + " you have provided does not exist" flow_dir = gdal.Open(path) + if flow_dir is None: + raise FileNotFoundError(f"GDAL could not open: {path}") [rows, cols] = flow_dir.ReadAsArray().shape self.FlowDirArr = flow_dir.ReadAsArray().astype(float) @@ -553,27 +518,19 @@ def read_flow_dir(self, path: str): logger.debug("Flow Direction input is read successfully") def read_flow_path_length(self, path: str): - """Read Flow path Length method. - - reads the flow path length. - - Parameters - ---------- - path : [str] - path to the file. - - Returns - ------- - FPLArr : [array] - flow path length array - rows : [integer] - number of rows in the flow acc array - cols : [integer] - number of columns in the flow acc array - NoDataValue : [numeric] - the NoDataValue - no_elem : [integer] - number of cells in the domain + """Read the flow path length raster. + + Reads the flow path length raster and extracts rows, columns, + NoDataValue, and the number of domain cells. + + Args: + path (str): Path to the flow path length raster file + (must include the raster name and .tif extension). + + Raises: + AssertionError: If `path` is not a string or does not + exist. + ValueError: If the file does not have a ".tif" extension. """ # data type assert isinstance(path, str), "path input should be string type" @@ -587,6 +544,8 @@ def read_flow_path_length(self, path: str): assert os.path.exists(path), path + " you have provided does not exist" fpl = gdal.Open(path) + if fpl is None: + raise FileNotFoundError(f"GDAL could not open: {path}") [self.rows, self.cols] = fpl.ReadAsArray().shape self.FPLArr = fpl.ReadAsArray() self.NoDataValue = fpl.GetRasterBand(1).GetNoDataValue() @@ -596,9 +555,9 @@ def read_flow_path_length(self, path: str): if math.isclose(self.FPLArr[i, j], self.NoDataValue, rel_tol=0.001): self.FPLArr[i, j] = np.nan # check flow accumulation input raster - self.no_elem = np.size(self.FPLArr[:, :]) - np.count_nonzero( + self.no_elem = int(np.size(self.FPLArr[:, :]) - np.count_nonzero( (self.FPLArr[np.isnan(self.FPLArr)]) - ) + )) logger.debug("Flow path length input is read successfully") @@ -610,50 +569,58 @@ def read_river_geometry( river_roughness_file: str, floodplain_roughness_file: str, ): - """ReadRiverGeometry. - - Parameters - ---------- - dem_file - bankfull_depth_file - river_width_file - river_roughness_file - floodplain_roughness_file - - Returns - ------- - None + """Read river geometry rasters for hydraulic routing. + + Reads the DEM, bankfull depth, river width, river roughness, + and floodplain roughness rasters required for hydraulic + routing computations. + + Args: + dem_file (str): Path to the DEM raster file. + bankfull_depth_file (str): Path to the bankfull depth + raster file. + river_width_file (str): Path to the river width raster + file. + river_roughness_file (str): Path to the river roughness + raster file. + floodplain_roughness_file (str): Path to the floodplain + roughness raster file. """ - self.DEM = gdal.Open(dem_file).ReadAsArray() - self.BankfullDepth = gdal.Open(bankfull_depth_file).ReadAsArray() - self.RiverWidth = gdal.Open(river_width_file).ReadAsArray() - self.RiverRoughness = gdal.Open(river_roughness_file).ReadAsArray() - self.FloodPlainRoughness = gdal.Open(floodplain_roughness_file).ReadAsArray() + for name, fpath in [ + ("DEM", dem_file), + ("BankfullDepth", bankfull_depth_file), + ("RiverWidth", river_width_file), + ("RiverRoughness", river_roughness_file), + ("FloodPlainRoughness", floodplain_roughness_file), + ]: + ds = gdal.Open(fpath) + if ds is None: + raise FileNotFoundError( + f"GDAL could not open {name} file: {fpath}" + ) + setattr(self, name, ds.ReadAsArray()) def read_parameters(self, path: str, snow: bool = False, maxbas: bool = False): - """read_parameters. - - read_parameters method reads the parameters' raster - - Parameters - ---------- - path: [str] - path to the folder where the raster exists. - snow: [integer] - False if you don't want to run the snow-related processes and 1 if there is snow. - in the case of 1 (simulate snow processes), parameters related to snow simulation - have to be provided. The default is 0. - maxbas: [bool], optional - True if the routing is Maxbas. The default is False. - - Returns - ------- - Parameters: [array]. - 3d array containing the parameters - Snow: [integer] - 0/1 - Maxbas: [bool] - True/False + """Read model parameter rasters or a CSV parameter file. + + For distributed mode, reads parameter rasters from a folder. + For lumped mode, reads parameters from a CSV file. + + Args: + path (str): Path to the folder containing parameter + rasters (distributed mode) or to a CSV file (lumped + mode). + snow (bool, optional): Whether to simulate snow + processes. If True, snow-related parameters must be + provided. Default is False. + maxbas (bool, optional): True if the routing method is + Maxbas. Default is False. + + Raises: + FileNotFoundError: If the path does not exist. + ValueError: If `snow` is not a boolean or if the number + of parameters does not match the expected count for + the given snow/maxbas configuration. """ if self.spatial_resolution.lower() == "distributed": # data type @@ -743,32 +710,27 @@ def read_parameters(self, path: str, snow: bool = False, maxbas: bool = False): def read_lumped_model( self, lumped_model, - catchment_area: Union[float, int], + catchment_area: float | int, initial_condition: list, q_init=None, ): - """readLumpedModel. - - Parameters - ---------- - lumped_model : [module] - HBV module. - catchment_area : [numeric] - Catchment area (km2). - initial_condition : [list] - list of the initial condition [SnowPack, SoilMoisture, Upper Zone, - Lower Zone, Water Content]. - q_init: [numeric], optional - initial discharge. The default is None. - - Returns - ------- - LumpedModel: [module]. - the lumped conceptual model. - q_init : [numeric] - initial discharge. - InitialCond : [list] - initial conditions. + """Read and set up a lumped conceptual model. + + Args: + lumped_model: A class representing the lumped conceptual + model (e.g., HBV). + catchment_area (float | int): Catchment area in + km2. + initial_condition (list): List of 5 initial condition + values: [SnowPack, SoilMoisture, Upper Zone, + Lower Zone, Water Content]. + q_init (float, optional): Initial discharge. Default is + None. + + Raises: + ValueError: If `lumped_model` is not a class or if + `initial_condition` does not contain exactly 5 + values. """ if not inspect.isclass(lumped_model): @@ -795,26 +757,22 @@ def read_lumped_model( logger.debug("Lumped model is read successfully") - def read_lumped_inputs(self, path: str, ll_temp: Union[list, np.ndarray] = None): - """readLumpedInputs. readLumpedInputs method read the meteorological data of lumped mode. - - [precipitation, Evapotranspiration, temperature, long-term average temperature] - - Parameters - ---------- - path : [string] - Path to the input file, data has to be in the order of - [date, precipitation, ET, Temp]. - ll_temp: [bool], optional - average long-term temperature, if None it is calculated inside the - code. The default is None. - - Returns - ------- - data: [array]. - meteorological data. - ll_temp: [array] - average long-term temperature. + def read_lumped_inputs(self, path: str, ll_temp: list | np.ndarray | None = None): + """Read meteorological inputs for lumped mode. + + Reads precipitation, evapotranspiration, temperature, and + optionally long-term average temperature from a CSV file. + + Args: + path (str): Path to the input CSV file. Data columns must + be in the order [date, precipitation, ET, Temp]. + ll_temp (list | np.ndarray, optional): Average + long-term temperature. If None, it is calculated as + the mean of the temperature column. Default is None. + + Raises: + ValueError: If the input data does not have 3 or 4 + columns (excluding the date index). """ self.data = pd.read_csv(path, header=0, delimiter=",", index_col=0) self.data = self.data.values @@ -833,25 +791,19 @@ def read_lumped_inputs(self, path: str, ll_temp: Union[list, np.ndarray] = None) def read_gauge_table( self, path: str, flow_acc_file: str = "", fmt: str = "%Y-%m-%d" ): - """readGaugeTable. readGaugeTable reads the table where the data about the gauges are listed. - - [x coordinate, y coordinate, 'area ratio', 'weight'], the coordinates are - mandatory to enter, to locate the gauges and be able to extract the - discharge at the corresponding cells. - - Parameters - ---------- - path: [str] - Path to the gauge file. - flow_acc_file: [str], optional - Path to the Flow acc raster. The default is ''. - fmt: [str] - Default is "%Y-%m-%d" - - Returns - ------- - hm_gauges: [dataframe] - the table of the gauges. + """Read the gauge table listing gauge locations and properties. + + Reads gauge data including coordinates (x, y), area ratio, and + weight. The coordinates are mandatory to locate the gauges and + extract discharge at the corresponding cells. + + Args: + path (str): Path to the gauge file (CSV or GeoJSON). + flow_acc_file (str, optional): Path to the flow + accumulation raster used to map gauge coordinates to + array indices. Default is "". + fmt (str, optional): Date format for start/end columns + in the gauge table. Default is "%Y-%m-%d". """ # read the gauge table if path.endswith(".geojson"): @@ -871,6 +823,10 @@ def read_gauge_table( if flow_acc_file != "" and "cell_row" not in col_list: # if hasattr(self, 'flow_acc'): flow_acc = gdal.Open(flow_acc_file) + if flow_acc is None: + raise FileNotFoundError( + f"GDAL could not open: {flow_acc_file}" + ) # calculate the nearest cell to each station dataset = Dataset(flow_acc) loc_arr = dataset.map_to_array_coordinates(self.GaugesTable) @@ -885,39 +841,40 @@ def read_discharge_gauges( column: str = "id", fmt: str = "%Y-%m-%d", split: bool = False, - start_date: str = "", - end_date: str = "", + start_date: str | dt.datetime = "", + end_date: str | dt.datetime = "", readfrom: str = "", ): - """read_discharge_gauges. - - read_discharge_gauges method read the gauge discharge data, discharge of each gauge has to be stored separately - in a file, and the name of the file has to be stored in the Gauges table you entered using the method - "readGaugeTable" under the column "id", the file should contain the date at the first column. - - Parameters - ---------- - path: [str] - path to the gauge discharge data. - delimiter: [str], optional - the delimiter between the date and the discharge column. The default is ",". - column: [str], optional - the name of the column where you stored the files' names. The default is 'id'. - fmt: [str], optional - date format. The default is "%Y-%m-%d". - split: bool, optional - True if you want to split the data between two dates. The default is False. - start_date: [str], optional - start date. The default is ''. - end_date: [str], optional - end date. The default is ''. - readfrom: [str] - Default is "". - - Returns - ------- - GaugesTable: [dataframe]. - dataframe containing the discharge data + """Read gauge discharge data from CSV files. + + For distributed mode, each gauge's discharge must be stored in a + separate CSV file. File names must match the "id" column in the + gauge table (read via ``read_gauge_table``). For lumped mode, a + single CSV file with the discharge data is expected. + + Args: + path (str): Path to the gauge discharge data directory + (distributed) or file (lumped). + delimiter (str, optional): Delimiter between the date and + the discharge column. Default is ",". + column (str, optional): Name of the column in the gauge + table containing the file names. Default is "id". + fmt (str, optional): Date format in the discharge files. + Default is "%Y-%m-%d". + split (bool, optional): True to subset the data between + `start_date` and `end_date`. Default is False. + start_date (str, optional): Start date for subsetting. + Default is "". + end_date (str, optional): End date for subsetting. + Default is "". + readfrom (str, optional): Number of rows to skip when + reading the CSV. Default is "". + + Raises: + FileNotFoundError: If the discharge file does not exist + (lumped mode). + AssertionError: If the gauge table has not been read yet + (distributed mode). """ if self.temporal_resolution.lower() == "daily": ind = pd.date_range(self.start, self.end, freq="D") @@ -970,34 +927,28 @@ def read_discharge_gauges( def read_parameters_bound( self, - upper_bound: Union[list, np.ndarray], - lower_bound: Union[list, np.ndarray], + upper_bound: list | np.ndarray, + lower_bound: list | np.ndarray, snow: bool = False, maxbas: bool = False, ): - """readParametersBounds. readParametersBounds method reads the lower and upper boundaries for each parameter. - - Parameters - ---------- - upper_bound : [list] - upper bound. - lower_bound : [list] - lower bound. - snow : [integer] - 0 if you don't want to run the snow-related processes and 1 if there is snow. - in case of 1 (simulate snow processes), parameters related to snow simulation - have to be provided. The default is 0. - maxbas: [bool] - True if the parameters have maxbas. - - Returns - ------- - UB: [list] - upper bound. - LB: [list] - lower bound. - Snow: [integer] - Snow + """Read the lower and upper parameter bounds for calibration. + + Args: + upper_bound (list | np.ndarray): Upper bound values + for each parameter. + lower_bound (list | np.ndarray): Lower bound values + for each parameter. + snow (bool, optional): Whether to simulate snow + processes. If True, snow-related parameters must be + bounded. Default is False. + maxbas (bool, optional): True if the parameters include + maxbas. Default is False. + + Raises: + AssertionError: If the lengths of `upper_bound` and + `lower_bound` are not equal. + ValueError: If `snow` is not a boolean. """ assert len(upper_bound) == len( lower_bound @@ -1017,32 +968,26 @@ def read_parameters_bound( def extract_discharge( self, calculate_metrics=True, frame_work_1=False, factor=None, only_outlet=False ): - """extractDischarge. - - extractDischarge method extracts and sums the discharge from the Quz_routed and Qlz_translated arrays at the - location of the gauges. - - Parameters - ---------- - calculate_metrics : bool, optional - True if you want to calculate the performance metrics. The default is True. - frame_work_1: [bool], optional - True if the routing function is Maxbas. The default is False. - factor : [list/None] - list of factor if you want to multiply the simulated discharge by - a factor you have to provide a list of the factor (as many factors - as the number of gauges). The default is False. - only_outlet: [bool] - True to extract the discharge only at the outlet. - - Returns - ------- - q_sim : [dataframe] - dataframe containing the discharge time series of the cells where - the gauges are located. - Metrics : [dataframe] - data frame containing the following metrics ['RMSE', 'NSE', 'NSEhf', 'KGE', 'WB','Pearson-CC', - 'R2'] calculated between the simulated hydrographs and the gauge data. + """Extract and sum discharge at gauge locations. + + Extracts and sums the discharge from the routed upper zone and + translated lower zone arrays at each gauge location. Optionally + computes performance metrics (RMSE, NSE, NSEhf, KGE, WB, + Pearson-CC, R2) between simulated and observed hydrographs. + + Args: + calculate_metrics (bool, optional): Whether to calculate + performance metrics. Default is True. + frame_work_1 (bool, optional): True if the routing + function is Maxbas. Default is False. + factor (list, optional): List of multiplication factors + for simulated discharge at each gauge. Must have the + same length as the number of gauges. Default is None. + only_outlet (bool, optional): True to extract discharge + only at the outlet cell. Default is False. + + Raises: + ValueError: If the gauge table has not been read yet. """ if self.GaugesTable is None: raise ValueError("please read the gauges' table first.") @@ -1126,67 +1071,55 @@ def extract_discharge( def plot_hydrograph( self, - start_date: str, - end_date: str, + start_date: str | dt.datetime, + end_date: str | dt.datetime, gauge: int, - hapi_color: Union[tuple, str] = "#004c99", - gauge_color: Union[tuple, str] = "#DC143C", + hapi_color: tuple | str = "#004c99", + gauge_color: tuple | str = "#DC143C", line_width: int = 3, hapi_order: int = 1, gauge_order: int = 0, label_font_size: int = 10, - x_major_fmt: str = "%Y-%m-%d", + x_major_fmt: str | dates.DateFormatter = "%Y-%m-%d", n_ticks: int = 5, title: str = "", x_axis_fmt: str = "%d\n%m", label: str = "", fmt: str = "%Y-%m-%d", ): - r"""Plot Hydrograph. - - plot the simulated and gauge hydrograph. - - Parameters - ---------- - start_date: [str] - starting date. - end_date: [str] - end date. - gauge: [integer] - order if the gauge in the GaugeTable. - hapi_color: [str], optional - color of the Simulated hydrograph. The default is "#004c99". - gauge_color: [str], optional - color of the gauge. The default is "#DC143C". - line_width: [numeric], optional - line width. The default is 3. - hapi_order: [integer], optional - the order of the simulated hydrograph to control which hydrograph - is in the front. The default is 1. - gauge_order: TYPE, optional - the order of the simulated hydrograph to control which hydrograph - is in the front. The default is 0. - label_font_size: numeric, optional - label size. The default is 10. - x_major_fmt: [str], optional - format of the x-axis labels. The default is '%Y-%m-%d'. - n_ticks: [integer], optional - number of x-axis ticks. The default is 5. - title: [str] - Default is "". - x_axis_fmt: [str] - Default is "%d\n%m". - label: [str] - Default is "". - fmt:[str] - Default is "%Y-%m-%d". - - Returns - ------- - fig: TYPE - DESCRIPTION. - ax: [matplotlib axes] - you can control the figure from the axes. + r"""Plot simulated and observed hydrographs for a given gauge. + + Args: + start_date (str): Starting date for the plot. + end_date (str): End date for the plot. + gauge (int): Index of the gauge in the GaugesTable. + hapi_color (tuple | str, optional): Color of the + simulated hydrograph. Default is "#004c99". + gauge_color (tuple | str, optional): Color of the + observed gauge hydrograph. Default is "#DC143C". + line_width (int, optional): Line width for the + hydrographs. Default is 3. + hapi_order (int, optional): Z-order of the simulated + hydrograph to control layering. Default is 1. + gauge_order (int, optional): Z-order of the observed + hydrograph to control layering. Default is 0. + label_font_size (int, optional): Font size for axis tick + labels. Default is 10. + x_major_fmt (str, optional): Format for x-axis major + tick labels. Default is "%Y-%m-%d". + n_ticks (int, optional): Maximum number of x-axis ticks. + Default is 5. + title (str, optional): Title of the plot. Default is "". + x_axis_fmt (str, optional): Format for x-axis minor + tick labels. Default is "%d\n%m". + label (str, optional): Label for the simulated + hydrograph in the legend. Default is "". + fmt (str, optional): Date format for parsing + `start_date` and `end_date`. Default is "%Y-%m-%d". + + Returns: + tuple: A tuple of (fig, ax) where fig is the matplotlib + Figure and ax is the matplotlib Axes object. """ start_date = dt.datetime.strptime(start_date, fmt) end_date = dt.datetime.strptime(end_date, fmt) @@ -1270,99 +1203,49 @@ def plot_hydrograph( def plot_distributed_results( self, - start: str, - end: str, + start: str | dt.datetime, + end: str | dt.datetime, fmt: str = "%Y-%m-%d", option: int = 1, gauges: bool = False, **kwargs, ): - """plotDistributedResults. - - plotDistributedResults animate the time series of the meteorological inputs and the result calculated by the - model like the total discharge, upper zone, and lower zone discharge and the state variables. - - Parameters - ---------- - start: [str] - starting date - end: [str] - end date - fmt: [str] - format of the given date. The default is "%Y-%m-%d" - option : [str] - 1- Total discharge, 2-Upper zone discharge, 3-ground water, - 4-Snowpack state variable, 5-Soil moisture, 6-Upper zone, - 7-Lower zone, 8-Water content, 9-Precipitation input. 10-ET, - 11-Temperature. The default is 1. - gauges: [str] - gauge name. The default is False - **kwargs : - possible keyword args - TicksSpacing: [integer], optional - Spacing in the color bar ticks. The default is 2. - Figsize: [tuple], optional - figure size. The default is (8,8). - PlotNumbers: [bool], optional - True to plot the values on top of each cell. The default is True. - NumSize: integer, optional - size of the numbers plotted on top of each cell. The default is 8. - title: [str], optional - title of the plot. The default is 'Total Discharge'. - title_size: [integer], optional - title size. The default is 15. - Backgroundcolorthreshold: [float/integer], optional - threshold value if the value of the cell is greater, the plotted - numbers will be black and if smaller the plotted number will be white - if None given the maxvalue/2 will be considered. The default is None. - textcolors: TYPE, optional - Two colors to be used to plot the values i top of each cell. The default is ("white","black"). - cbarlabel: str, optional - label of the color bar. The default is 'Discharge m3/s'. - cbarlabelsize: integer, optional - size of the color bar label. The default is 12. - Cbarlength: [float], optional - ratio to control the height of the colorbar. The default is 0.75. - Interval: [integer], optional - number to control the speed of the animation. The default is 200. - cmap: [str], optional - color style. The default is 'coolwarm_r'. - Textloc: [list], optional - location of the date text. The default is [0.1,0.2]. - Gaugecolor: [str], optional - color of the points. The default is 'red'. - Gaugesize: [integer], optional - size of the points. The default is 100. - ColorScale: integer, optional - there are 5 options to change the scale of the colors. The default is 1. - 1- ColorScale 1 is the normal scale - 2- ColorScale 2 is the power scale - 3- ColorScale 3 is the SymLogNorm scale - 4- ColorScale 4 is the PowerNorm scale - 5- ColorScale 5 is the BoundaryNorm scale - ------------------------------------------------------------------ - gamma: [float], optional - value needed for option 2 . The default is 1./2.. - linthresh: [float], optional - value needed for option 3. The default is 0.0001. - linscale: [float], optional - value needed for option 3. The default is 0.001. - midpoint: [float], optional - value needed for option 5. The default is 0. - ------------------------------------------------------------------ - orientation: [string], optional - orintation of the colorbar horizontal/vertical. The default is 'vertical'. - rotation: [number], optional - rotation of the colorbar label. The default is -90. - **kwargs: [dict] - keys: - Points : [dataframe]. - dataframe contains two columns 'cell_row', and cell_col to - plot the point at this location - - Returns - ------- - animation.FuncAnimation. + """Animate distributed model results or meteorological inputs. + + Creates an animation of the time series of meteorological inputs + or model results (discharge, state variables) over the spatial + domain. + + Args: + start (str): Starting date for the animation. + end (str): End date for the animation. + fmt (str, optional): Format of the given date. Default + is "%Y-%m-%d". + option (int, optional): Variable to animate. Options are: + 1 - Total discharge, 2 - Upper zone discharge, + 3 - Ground water, 4 - Snow pack, 5 - Soil moisture, + 6 - Upper zone, 7 - Lower zone, 8 - Water content, + 9 - Precipitation, 10 - ET, 11 - Temperature. + Default is 1. + gauges (bool, optional): Whether to plot gauge locations + on the animation. Default is False. + **kwargs: Additional keyword arguments passed to + ``ArrayGlyph.animate``. Common options include: + TicksSpacing (int), Figsize (tuple), + PlotNumbers (bool), NumSize (int), title (str), + title_size (int), Backgroundcolorthreshold (float), + textcolors (tuple), cbarlabel (str), + cbarlabelsize (int), Cbarlength (float), + Interval (int), cmap (str), Textloc (list), + Gaugecolor (str), Gaugesize (int), + ColorScale (int), orientation (str), + rotation (float), Points (DataFrame). + + Returns: + matplotlib.animation.FuncAnimation: The animation object. + + Raises: + ValueError: If `option` is not between 1 and 11. """ start = dt.datetime.strptime(start, fmt) end = dt.datetime.strptime(end, fmt) @@ -1457,37 +1340,42 @@ def save_results( self, flow_acc_path: str = "", result: int = 1, - start: str = "", - end: str = "", + start: str | dt.datetime = "", + end: str | dt.datetime = "", path: str = "", prefix: str = "", fmt: str = "%Y-%m-%d", ): - """save_results. saveResults save the results into rasters. - - Parameters - ---------- - flow_acc_path : [path] - Path to Flow acc raster. - result: [integer], optional - 1 for the total discharge, 2 for the upper zone discharge, 3 for - the lower zone discharge, 4 for the snow pack, 5 for the soil - moisture, 6 upper zone, 7 for the lower zone, 8 for the water content. - The default is 1. - start: [str], optional - start date. The default is ''. - end: [str], optional - end date. The default is ''. - path: [str], optional - path to the directory where you want to save the results. The default is ''. - prefix: [str], optional - prefix to add to the name of the result files. The default is ''. - fmt: [str], optional - format of the date. The default is "%Y-%m-%d". - - Returns - ------- - None. + """Save model results to raster files or CSV. + + For distributed mode, saves results as GeoTIFF rasters. For + lumped mode, saves results as a CSV file. + + Args: + flow_acc_path (str, optional): Path to the flow + accumulation raster (required for distributed mode). + Default is "". + result (int, optional): Type of result to save: + 1 - Total discharge, 2 - Upper zone discharge, + 3 - Lower zone discharge, 4 - Snow pack, + 5 - Soil moisture, 6 - Upper zone, 7 - Lower zone, + 8 - Water content. For lumped mode, 5 saves all + variables. Default is 1. + start (str, optional): Start date for the output period. + If empty, uses the first index. Default is "". + end (str, optional): End date for the output period. If + empty, uses the last index. Default is "". + path (str, optional): Path to the output directory + (distributed) or file (lumped). Default is "". + prefix (str, optional): Prefix for the output file + names. Default is "". + fmt (str, optional): Date format for parsing `start` and + `end`. Default is "%Y-%m-%d". + + Raises: + Exception: If `flow_acc_path` is not provided in + distributed mode. + ValueError: If `result` is not a valid option. """ if start == "": start = self.Index[0] @@ -1509,6 +1397,10 @@ def save_results( ) src = gdal.Open(flow_acc_path) + if src is None: + raise FileNotFoundError( + f"GDAL could not open: {flow_acc_path}" + ) if prefix == "": prefix = "Result_" @@ -1575,18 +1467,13 @@ def save_results( class Lake: - """Lake. - - Lake class reads the meteorological inputs, and the module to simulate a lake as a lumped model, using a - rating curve, the lake and the upstream sub-catchments are going to be considered as one lumped model than - result in a discharge input to the lake, the discharge input is going to change the volume of the water in - the lake, and from the volume-outflow curve the outflow can be obtained. - - Methods - ------- - 1- readMeteoData - 2- readParameters - 3- readLumpedModel + """Lake simulation using a lumped model with a rating curve. + + The Lake class reads meteorological inputs and a lumped model module to + simulate a lake. The lake and its upstream sub-catchments are treated as + one lumped model that produces a discharge input to the lake. The + discharge input changes the volume of the water in the lake, and the + outflow is obtained from the volume-outflow (stage-discharge) curve. """ def __init__( @@ -1597,28 +1484,20 @@ def __init__( temporal_resolution: str = "Daily", split: bool = False, ): - """Lake. Lake class for lake simulation. - - Parameters - ---------- - start: [str], optional - start date. The default is ''. - end: [str], optional - end date. The default is ''. - fmt: [str], optional - date format. The default is "%Y-%m-%d". - temporal_resolution: [str], optional - "Daily" ot "Hourly". The default is "Daily". - split : bool, optional - true if you want to split the date between two dates. The default is False. - - Returns - ------- - None. + """Initialize a Lake instance for lake simulation. + + Args: + start (str, optional): Start date. Default is "". + end (str, optional): End date. Default is "". + fmt (str, optional): Date format. Default is "%Y-%m-%d". + temporal_resolution (str, optional): "Daily" or "Hourly". + Default is "Daily". + split (bool, optional): True to subset the data between + the start and end dates. Default is False. """ - self.OutflowCell = None - self.Snow = None + self.OutflowCell: list | None = None + self.Snow: int | None = None self.Split = split self.start = dt.datetime.strptime(start, fmt) self.end = dt.datetime.strptime(end, fmt) @@ -1630,33 +1509,26 @@ def __init__( else: assert False, "Error" - self.MeteoData = None - self.Parameters = None - self.LumpedModel, self.CatArea, self.LakeArea, self.InitialCond = ( - None, - None, - None, - None, - ) - self.StageDischargeCurve = None + self.MeteoData: np.ndarray | None = None + self.Parameters: list | None = None + self.LumpedModel: BaseConceptualModel | None = None + self.CatArea: float | None = None + self.LakeArea: float | None = None + self.InitialCond: list | None = None + self.StageDischargeCurve: np.ndarray | None = None def read_meteo_data(self, path: str, fmt: str): - """readMeteoData. readMeteoData reads the meteorological data for the lake. - - [rainfall, ET, temperature] - - Parameters - ---------- - path : [str] - path to the meteo data file, containing the data in the following - order [date, rainfall, ET, temperature]. - fmt : [str] - date format. - - Returns - ------- - MeteoData: [array]. - array containing the meteorological data + """Read meteorological data for the lake simulation. + + Reads rainfall, evapotranspiration, and temperature data from a + CSV file. + + Args: + path (str): Path to the meteorological data CSV file. + Columns must be in the order [date, rainfall, ET, + temperature]. + fmt (str): Date format string used to parse the date + index. """ df = pd.read_csv(path, index_col=0) @@ -1670,16 +1542,10 @@ def read_meteo_data(self, path: str, fmt: str): logger.debug("Lake Meteo data are read successfully") def read_parameters(self, path): - """readParameters. readParameters method reads the lake parameters. - - Parameters - ---------- - path : [str] - path to the parameter file. + """Read lake model parameters from a text file. - Returns - ------- - Parameters: [array]. + Args: + path (str): Path to the parameter text file. """ self.Parameters = np.loadtxt(path).tolist() logger.debug("Lake Parameters are read successfully") @@ -1694,41 +1560,27 @@ def read_lumped_model( stage_discharge_curve, snow, ): - """readLumpedModel. - - readLumpedModel reads the lumped model module - - Parameters - ---------- - lumped_model : [module] - lumped conceptual model. - catchment_area : [numeric] - catchment area in mk2. - lake_area : [numeric] - area of the lake in km2. - initial_condition : [list] - initial condition [Snow Pack, Soil Moisture, Upper Zone, Lower Zone, - Water Content, Lake volume]. - outflow_cell : [list] - indexes of the cell where the lake hydrograph is going to be added. - stage_discharge_curve : [array] - volume-outflow curve. - snow : [integer] - 0 if you don't want to run the snow-related processes and 1 if there is snow. - in case of 1 (simulate snow processes) parameters related to snow simulation - have to be provided. The default is 0. - - Returns - ------- - LumpedModel : [module] - HBV module. - CatArea : [numeric] - Catchment area (km2). - InitialCond : [list] - list of the initial condition [SnowPack, SoilMoisture, Upper Zone, Lower Zone, Water Content]. - Snow : [integer] - 0/1 - StageDischargeCurve: [array] + """Read and set up a lumped model for lake simulation. + + Args: + lumped_model: A class representing the lumped conceptual + model (e.g., HBV). + catchment_area (float): Catchment area in km2. + lake_area (float): Area of the lake in km2. + initial_condition (list): Initial conditions list + containing [Snow Pack, Soil Moisture, Upper Zone, + Lower Zone, Water Content, Lake volume]. + outflow_cell (list): Indices of the cell where the lake + hydrograph is to be added. + stage_discharge_curve (np.ndarray): Volume-outflow + (stage-discharge) curve array. + snow (int): 0 to skip snow processes, 1 to simulate + snow. If 1, snow-related parameters must be + provided. + + Raises: + ValueError: If `lumped_model` is not a class. + AssertionError: If `initial_condition` is not a list. """ if not inspect.isclass(lumped_model): raise ValueError( diff --git a/src/Hapi/dem.py b/src/Hapi/dem.py index 001b42bff..3873c87bf 100644 --- a/src/Hapi/dem.py +++ b/src/Hapi/dem.py @@ -1,30 +1,50 @@ -"""DEM related functions.""" -from typing import Dict +"""Digital Elevation Model module for the Hapi package. + +This module provides the `DEM` class, which extends +``pyramids.dataset.Dataset`` with flow-direction utilities used by +the distributed rainfall-runoff modelling pipeline. +""" +from __future__ import annotations + import numpy as np from pyramids.dataset import Dataset class DEM(Dataset): - """DEM.""" + """Digital Elevation Model dataset with flow-direction helpers. + + ``DEM`` wraps a GDAL-backed raster dataset (via + ``pyramids.dataset.Dataset``) and adds methods that convert + standard 8-direction flow codes (1, 2, 4, 8, 16, 32, 64, 128) + into downstream-cell indices and upstream-cell lookup tables. + + Args: + src: A GDAL dataset or a file path to a DEM raster that can + be opened by ``pyramids.dataset.Dataset``. + """ def __init__(self, src): super().__init__(src) def flow_direction_index(self) -> np.ndarray: - """flow_direction_index. - - flow_direction_index takes flow direction raster and converts codes for the 8 directions - (1,2,4,8,16,32,64,128) into indices of the Downstream cell. - - flow_direct: - [gdal.dataset] flow direction raster obtained from catchment delineation - it only contains values [1,2,4,8,16,32,64,128] - - Returns - ------- - [numpy array]: - with the same dimensions of the raster and 2 layers - first layer for rows index and second rows for column index + """Convert flow-direction codes into downstream-cell indices. + + Reads the flow-direction band from the underlying raster and + maps each of the eight D8 direction codes + (1, 2, 4, 8, 16, 32, 64, 128) to the row/column index of the + downstream neighbour cell. + + Returns: + numpy.ndarray: A 3-D array of shape ``(rows, cols, 2)``. + The first layer (``[:, :, 0]``) holds the row index + and the second layer (``[:, :, 1]``) holds the column + index of the downstream cell. Cells with no valid + flow direction are set to ``NaN``. + + Raises: + ValueError: If the flow-direction raster contains values + other than 1, 2, 4, 8, 16, 32, 64, or 128 (excluding + the no-data value). """ # check flow direction input raster no_val = self.no_data_value[0] @@ -72,23 +92,18 @@ def flow_direction_index(self) -> np.ndarray: return fd_cell - def flow_direction_table(self) -> Dict: - """Flow Direction Table. - - - flow_direction_table takes flow direction indices created by FlowDirectِِIndex function and creates a - dictionary with the cells' indices as a key and indices of directly upstream cells as values - (list of tuples). - + def flow_direction_table(self) -> dict: + """Build an upstream-cell lookup table from flow directions. - flow_direct: - [gdal.dataset] flow direction raster obtained from catchment delineation - it only contains values [1,2,4,8,16,32,64,128] + Uses ``flow_direction_index`` to determine downstream + neighbours, then inverts the relationship so that each cell + maps to the list of cells that flow directly into it. - Returns - ------- - flowAccTable: - [Dict] dictionary with the cells indices as a key and indices of directly - upstream cells as values (list of tuples) + Returns: + dict[str, list[tuple[int, int]]]: A dictionary keyed by + ``"row,col"`` strings. Each value is a list of + ``(row, col)`` tuples identifying the cells whose + flow direction points directly into the key cell. """ flow_direction_index = self.flow_direction_index() @@ -109,7 +124,7 @@ def flow_direction_table(self) -> Dict: celli_content.append(flow_direction_index[i, j, 0]) cellj_content.append(flow_direction_index[i, j, 1]) - flow_acc_table = {} + flow_acc_table: dict[str, list[tuple[int, int]]] = {} # for each cell store the directly giving cells for i in range(rows): for j in range(cols): diff --git a/src/Hapi/hapi_warnings.py b/src/Hapi/hapi_warnings.py index 08631bf5c..98ea84cae 100644 --- a/src/Hapi/hapi_warnings.py +++ b/src/Hapi/hapi_warnings.py @@ -1,9 +1,38 @@ -"""Custom warning and silencing warnings.""" +"""Custom warning classes and utilities for silencing warnings. + +The ``hapi_warnings`` module provides custom warning types used +throughout the Hapi package and helper functions to suppress +specific warning categories during runtime. + +Examples: + >>> from Hapi.hapi_warnings import InstabilityWarning + >>> import warnings + >>> warnings.warn( + ... "Simulation diverged at step 5", + ... InstabilityWarning, + ... ) +""" +from __future__ import annotations + import warnings class InstabilityWarning(UserWarning): - """Issued when results may be unstable.""" + """Warning issued when numerical results may be unstable. + + ``InstabilityWarning`` is a subclass of ``UserWarning`` that + flags potential numerical instability in hydrological simulation + results, such as diverging flows or extreme parameter values. + + Examples: + >>> from Hapi.hapi_warnings import InstabilityWarning + >>> import warnings + >>> with warnings.catch_warnings(record=True) as w: + ... warnings.simplefilter("always") + ... warnings.warn("unstable result", InstabilityWarning) + ... len(w) + 1 + """ pass @@ -13,6 +42,14 @@ class InstabilityWarning(UserWarning): def SilencePandasWarning(): - """Silence pandas future warning.""" - warnings.simplefilter(action="ignore", category=FutureWarning) + """Silence pandas ``FutureWarning`` messages. + Configures the warnings filter to ignore all + ``FutureWarning`` instances, which are commonly emitted by + pandas when deprecated APIs are used. + + Examples: + >>> from Hapi.hapi_warnings import SilencePandasWarning + >>> SilencePandasWarning() + """ + warnings.simplefilter(action="ignore", category=FutureWarning) diff --git a/src/Hapi/inputs.py b/src/Hapi/inputs.py index 01107ec36..f4518363c 100644 --- a/src/Hapi/inputs.py +++ b/src/Hapi/inputs.py @@ -1,10 +1,20 @@ -"""Rainfall-runoff Inputs.""" +"""Rainfall-runoff Inputs. + +The inputs module provides the `Inputs` class for preparing meteorological +and parameter raster data for distributed hydrological modeling. It handles +alignment of rasters to a source DEM, extraction of HBV model parameters +from global datasets, creation of lumped inputs from distributed data, and +renaming of raster files with date-based ordering. + +The module relies on the ``pyramids`` library for raster I/O and +manipulation, and uses the ``HAPI_DATA_DIR`` environment variable to +locate pre-downloaded global parameter sets (Beck et al., 2016). +""" +from __future__ import annotations import datetime as dt import os from pathlib import Path -from typing import Union - import pandas as pd from geopandas import GeoDataFrame from pyramids.datacube import Datacube @@ -33,63 +43,59 @@ class Inputs: - """Rainfall-runoff Inputs class. - - Inputs class contains methods to prepare the inputs for the distributed - hydrological model - - Methods - ------- - 1- prepareInputs - 2- extractParametersBoundaries - 3- extractParameters - 4- createLumpedInputs - 5- renameFiles - 8- ListAttributes + """Rainfall-runoff inputs preparation for distributed hydrological models. + + The Inputs class provides methods to prepare meteorological and parameter + raster data so they align with a reference DEM. It supports extracting + HBV model parameter boundaries, computing lumped inputs from distributed + rasters, and renaming files with date-based ordering. + + Attributes: + source_dem: Path to the reference DEM raster used for spatial + alignment (coordinate system, rows, columns, resolution). + + Examples: + >>> from Hapi.inputs import Inputs + >>> inp = Inputs("data/dem.tif") """ def __init__(self, src: str): - """Rainfall Inputs. - - Parameters - ---------- - src: [str] - path to the spatial information source raster to get the spatial information - (coordinate system, no of rows & columns) A_path should include the name of the raster - and the extension like "data/dem.tif". + """Initialize the Inputs instance with a reference DEM path. + + Args: + src: Path to the spatial information source raster used to + obtain the coordinate system, number of rows and columns, + and resolution. The path should include the file name and + extension (e.g., ``"data/dem.tif"``). """ self.source_dem = src def prepare_inputs( - self, inputs_dir: Union[str, Path], outputs_dir: Union[str, Path] + self, inputs_dir: str | Path, outputs_dir: str | Path ): - """prepareInputs. - - this function prepares downloaded raster data to have the same alignment and - nodatavalue from a GIS raster (DEM, flow accumulation, flow direction raster) - and returns a folder with the output rasters with a name "New_Rasters" - - Parameters - ---------- - inputs_dir: [str/Path] - path of the folder of the rasters you want to adjust their no of rows, columns and resolution (alignment) - like a source raster. - outputs_dir: [str] - name to create a folder to store resulted rasters. - - Example - ------- - Ex1: - >>> dem_path = "01GIS/inputs/4000/acc4000.tif" - >>> prec_in_path = "02Precipitation/CHIRPS/Daily/" - >>> In = Inputs(dem_path) - >>> In.prepare_inputs(prec_in_path, "prec") - Ex2: - >>> dem_path="01GIS/inputs/4000/acc4000.tif" - >>> output_path="00inputs/meteodata/4000/" - >>> evap_in_path="03Weather_Data/evap/" - >>> In = Inputs(dem_path) - >>> Inputs.prepare_inputs(evap_in_path, f"{output_path}/evap") + """Align and crop input rasters to match the source DEM. + + Reads all rasters from ``inputs_dir``, aligns them to the source + DEM's spatial properties (CRS, resolution, extent, nodata value), + crops them to the DEM footprint, and writes the results to + ``outputs_dir``. + + Args: + inputs_dir: Path to the folder containing the rasters to be + aligned and cropped to match the source DEM. + outputs_dir: Path to the output folder where the aligned + rasters will be saved. + + Raises: + FileNotFoundError: If ``inputs_dir`` does not exist. + + Examples: + >>> from Hapi.inputs import Inputs + >>> inp = Inputs("GIS/inputs/acc4000.tif") + >>> inp.prepare_inputs( + ... "Precipitation/CHIRPS/Daily/", + ... "outputs/prec", + ... ) """ if not isinstance(outputs_dir, str): print("output_folder input should be string type") @@ -107,26 +113,31 @@ def prepare_inputs( @staticmethod def extract_parameters_boundaries(basin: GeoDataFrame): - """extractParametersBoundaries. - - extractParametersBoundaries - - Parameters - ---------- - basin: [GeoDataFrame] - catchment polygon, make sure that the geodataframe contains one row only, if not merge all the polygons - in the shapefile first. - - Returns - ------- - ub: [list] - list of the upper bound of the parameters. - lb: [list] - list of the lower bound of the parameters. - - the parameters are - ["tt", "sfcf","cfmax","cwh","cfr","fc","beta", - "lp","k0","k1","k2","uzl","perc", "maxbas"] + """Extract upper and lower parameter boundaries for a catchment. + + Reads the global maximum and minimum HBV parameter rasters from + the directory specified by the ``HAPI_DATA_DIR`` environment + variable, clips them to the given basin polygon, and returns the + max/min statistics for each parameter. + + The 18 HBV parameters are: + ``tt, rfcf, sfcf, cfmax, cwh, cfr, fc, beta, etf, lp, k0, k1, + k2, uzl, perc, maxbas, K_muskingum, x_muskingum``. + + Args: + basin: A GeoDataFrame containing the catchment polygon. Must + contain exactly one row; merge all polygons first if the + shapefile has multiple features. + + Returns: + pandas.DataFrame: A DataFrame indexed by parameter name with + columns ``"ub"`` (upper bound) and ``"lb"`` (lower bound). + + Raises: + ValueError: If the ``HAPI_DATA_DIR`` environment variable is + not set. + FileNotFoundError: If the parameter data directory or the + ``max``/``min`` subdirectories do not exist. """ data_dir = Inputs._check_data_dir() max_dir = data_dir / "max" @@ -166,53 +177,61 @@ def extract_parameters_boundaries(basin: GeoDataFrame): def extract_parameters( self, - gdf: Union[GeoDataFrame, str], + gdf: GeoDataFrame | str, scenario: str, as_raster: bool = False, save_to: str = "", ): - """extract_parameters. - - extractParameters method extracts the parameter raster at the location - of the source raster, there are 12 set of parameters 10 sets of parameters - (Beck et al., (2016)) and the max, min and average of all sets - - - Beck, H. E., Dijk, A. I. J. M. van, Ad de Roo, Diego G. Miralles, - T. R. M. & Jaap Schellekens, and L. A. B. (2016) Global-scale - regionalization of hydrologic model parameters-Supporting materials - 3599–3622. doi:10.1002/2015WR018247.Received - - Parameters - ---------- - gdf: [GeoDataFrame] - geodataframe of catchment polygon, make sure that the geodataframe contains - one row only, if not merge all the polygons in the shapefile first. - scenario: [str] - name of the parameter set, there are 12 sets of parameters - ["1","2","3","4","5","6","7","8","9","10","avg","max","min"] - as_raster: [bool] - Default is False. - save_to: [str] - path to a directory where you want to save the raster's. - - Returns - ------- - Parameters : [list] - list of the upper bound of the parameters. - - - the parameters are - ["tt", rfcf,"sfcf","cfmax","cwh","cfr","fc","beta",'etf' - "lp","k0","k1","k2","uzl","perc", "maxbas",'K_muskingum', - 'x_muskingum'] + """Extract HBV parameter values or rasters for a catchment. + + Retrieves one of 12 global HBV parameter sets (Beck et al., 2016) + from the directory specified by the ``HAPI_DATA_DIR`` environment + variable. When ``as_raster`` is False, computes zonal statistics + (min, max, mean, std) over the catchment polygon. When + ``as_raster`` is True, aligns and crops the parameter rasters to + the source DEM and saves them to ``save_to``. + + Reference: + Beck, H. E., Dijk, A. I. J. M. van, Ad de Roo, + Diego G. Miralles, T. R. M. & Jaap Schellekens, and + L. A. B. (2016). Global-scale regionalization of hydrologic + model parameters. Water Resources Research, 3599-3622. + doi:10.1002/2015WR018247. + + The 18 HBV parameters are: + ``tt, rfcf, sfcf, cfmax, cwh, cfr, fc, beta, etf, lp, k0, k1, + k2, uzl, perc, maxbas, K_muskingum, x_muskingum``. + + Args: + gdf: A GeoDataFrame of the catchment polygon. Must contain + one row; merge all polygons first if the shapefile has + multiple features. Can be None when ``as_raster`` is True. + scenario: Name of the parameter set. One of ``"1"`` through + ``"10"``, ``"avg"``, ``"max"``, or ``"min"``. + as_raster: If True, save aligned parameter rasters to + ``save_to`` instead of returning statistics. Default is + False. + save_to: Path to the directory where aligned parameter rasters + will be saved. Only used when ``as_raster`` is True. + + Returns: + pandas.DataFrame: When ``as_raster`` is False, a DataFrame + indexed by parameter name with columns ``"min"``, + ``"max"``, ``"mean"``, and ``"std"``. Returns None when + ``as_raster`` is True. + + Raises: + ValueError: If the ``HAPI_DATA_DIR`` environment variable is + not set. + FileNotFoundError: If the parameter data directory does not + exist. """ data_dir = self._check_data_dir() parameters_path = data_dir / scenario if not as_raster: dataset = Dataset.read_file(f"{parameters_path}/{PARAMETERS_LIST[0]}.tif") - gdf = gdf.to_crs(crs=dataset.crs) + gdf = gdf.to_crs(crs=dataset.crs) # type: ignore[union-attr] stats = pd.DataFrame(columns=["min", "max", "mean", "std"]) for i in range(len(PARAMETERS_LIST)): @@ -232,52 +251,48 @@ def create_lumped_inputs( path: str, regex_string=r"\d{4}.\d{2}.\d{2}", date: bool = True, - file_name_data_fmt: str = None, - start: str = None, - end: str = None, + file_name_data_fmt: str | None = None, + start: str | None = None, + end: str | None = None, fmt: str = "%Y-%m-%d", extension: str = ".tif", ) -> list: - """create_lumped_inputs. - - create_lumped_inputs method generates lumped parameters from distributed parameters by taking the average - - Parameters - ---------- - path: [str] - path to folder that contains the parameter rasters. - regex_string: [str] - a regex string that we can use to locate the date in the file names.Default is - r"d{4}.d{2}.d{2}". - >>> fname = 'MSWEP_YYYY.MM.DD.tif' - >>> regex_string = r'd{4}.d{2}.d{2}' - - or - >>> fname = 'MSWEP_YYYY_M_D.tif' - >>> regex_string = r'd{4}_d{1}_d{1}' - - if there is a number at the beginning of the name - >>> fname = '1_MSWEP_YYYY_M_D.tif' - >>> regex_string = r'd+' - date: [bool] - True if the number in the file name is a date. Default is True. - file_name_data_fmt : [str] - if the files' names have a date, and you want to read them ordered .Default is None - >>> "MSWEP_YYYY.MM.DD.tif" - >>> file_name_data_fmt = "%Y.%m.%d" - start: [str] - start date if you want to read the input raster for a specific period only and not all rasters, - if not given all rasters in the given path will be read. - end: [str] - end date if you want to read the input temperature for a specific period only, - if not given all rasters in the given path will be read. - fmt: [str] - format of the given date in the start/end parameter. - extension: [str] - the extension of the files you want to read from the given path. Default is ".tif". - - Returns - ------- - List: - list containing the average values of the distributed parameters. + """Create lumped inputs by averaging distributed raster values. + + Reads a time series of rasters from the given directory, computes + the spatial mean of each raster, and returns the averages as a + list. This is used to convert distributed meteorological or + parameter data into lumped (catchment-average) values. + + Args: + path: Path to the folder containing the raster files. + regex_string: A regex pattern to locate the date (or ordering + number) within each file name. Default is + ``r"\\d{4}.\\d{2}.\\d{2}"``. + date: If True, the number extracted from file names is + interpreted as a date. Default is True. + file_name_data_fmt: The date format string matching dates in + the file names (e.g., ``"%Y.%m.%d"``). Default is None. + start: Start date to filter the rasters. If not provided, all + rasters in the directory are read. + end: End date to filter the rasters. If not provided, all + rasters in the directory are read. + fmt: Format of the ``start`` and ``end`` date strings. + Default is ``"%Y-%m-%d"``. + extension: File extension to filter by. Default is ``".tif"``. + + Returns: + list: A list of float values, each being the spatial mean of + the corresponding raster in chronological order. + + Examples: + >>> from Hapi.inputs import Inputs + >>> avg = Inputs.create_lumped_inputs( + ... "tests/rrm/data/coello/prec", + ... regex_string=r"\\d{4}.\\d{2}.\\d{2}", + ... date=True, + ... file_name_data_fmt="%Y.%m.%d", + ... ) """ cube = Datacube.read_multiple_files( path, @@ -303,26 +318,29 @@ def create_lumped_inputs( def rename_files( path: str, prefix: str = "", fmt: str = "%Y.%m.%d", freq: str = "daily" ): - """renameFiles. - - renameFiles method takes the path to a folder where you want to put a number - at the beginning of the raster names indicating the order of the raster based on - its date - - Parameters - ---------- - path : [str] - path where the rasters are stored. - prefix: [str] - any string you want to add to the raster names, (i.e., the dataset name precipitation_ecmwf). Default is "". - fmt: [String], optional - the format of the date. The default is '%Y.%m.%d'. - freq: [str] - Default is "daily". - - Returns - ------- - files in the Path are going to have a new name including the order at the beginning of the name. + """Rename raster files with a sequential order prefix based on date. + + Reads all ``.tif`` files in the given directory, extracts dates + from their names, sorts them chronologically, and renames each + file with a leading index number indicating its temporal order. + + The new file name format is: + ``{order}_{prefix}_{date_string}.tif`` + + Args: + path: Path to the directory containing the raster files. + prefix: An optional string to include in the new file names, + such as a dataset identifier (e.g., + ``"precipitation_ecmwf"``). Default is ``""``. + fmt: The date format in the original file names. Default is + ``"%Y.%m.%d"``. + freq: The temporal frequency of the data, which controls the + date format in the new file names. One of ``"daily"``, + ``"hourly"``, or any other value for minute-level. + Default is ``"daily"``. + + Raises: + FileNotFoundError: If ``path`` does not exist. """ if not os.path.exists(path): raise FileNotFoundError("The directory you have entered does not exist") @@ -369,11 +387,24 @@ def rename_files( @staticmethod def _check_data_dir() -> Path: - data_dir = os.getenv("HAPI_DATA_DIR") - if data_dir is None: + """Validate and return the HAPI parameter data directory. + + Reads the ``HAPI_DATA_DIR`` environment variable and verifies + that the directory exists on disk. + + Returns: + Path: The resolved path to the HAPI data directory. + + Raises: + ValueError: If the ``HAPI_DATA_DIR`` environment variable + is not set. + FileNotFoundError: If the directory specified by + ``HAPI_DATA_DIR`` does not exist. + """ + data_dir_env: str | None = os.getenv("HAPI_DATA_DIR") + if data_dir_env is None: raise ValueError("HAPI_DATA_DIR environment variable is not set") - else: - data_dir = Path(data_dir) - if not data_dir.exists(): - raise FileNotFoundError(f"{data_dir} does not exist") - return data_dir \ No newline at end of file + data_dir: Path = Path(data_dir_env) + if not data_dir.exists(): + raise FileNotFoundError(f"{data_dir} does not exist") + return data_dir diff --git a/src/Hapi/parameters/parameters.py b/src/Hapi/parameters/parameters.py index bd147c2f8..ca4834d3d 100644 --- a/src/Hapi/parameters/parameters.py +++ b/src/Hapi/parameters/parameters.py @@ -1,33 +1,51 @@ -"""hydrological-model parameter.""" +"""Hydrological model parameters. + +The ``Hapi.parameters.parameters`` module provides classes for +interacting with the Figshare API to retrieve, download, and manage +global hydrological parameter sets used by the Hapi framework. + +The module contains four main classes: + +- ``FigshareAPIClient``: low-level HTTP client for the Figshare REST API. +- ``FileManager``: static helpers for downloading files and clearing + directories. +- ``ParameterManager``: maps user-friendly parameter-set IDs to Figshare + article IDs and orchestrates file listing / downloading. +- ``Parameter``: high-level facade that wires everything together and + exposes a CLI-friendly interface. +""" +from __future__ import annotations import json import os from pathlib import Path -from typing import Dict, List, Optional, Union from urllib.parse import urlparse -import requests +import requests # type: ignore[import-untyped] from loguru import logger BASE_URL = "https://api.figshare.com/v2" class FigshareAPIClient: - """ - A client for interacting with the Figshare API. + """A client for interacting with the Figshare API. - Parameters - ---------- - headers : dict, optional - Headers to include in the API requests, by default None. + Attributes: + base_url (str): The base URL for the Figshare API. + headers (dict): Headers included in every API request. - Examples - -------- - >>> client = FigshareAPIClient() + Examples: + >>> client = FigshareAPIClient() """ - def __init__(self, headers: Optional[dict] = None): - """initialize.""" + def __init__(self, headers: dict | None = None): + """Initialize FigshareAPIClient. + + Args: + headers (dict, optional): Headers to include in the API + requests. Defaults to None, which uses + ``{"Content-Type": "application/json"}``. + """ self.base_url = BASE_URL self.headers = headers or {"Content-Type": "application/json"} @@ -35,80 +53,30 @@ def send_request( self, method: str, endpoint: str, - data: Optional[dict] = None, + data: dict | None = None, binary: bool = False, - ) -> Dict[str, int]: - """ - Send an HTTP request to the Figshare API. - - Parameters - ---------- - method : str - HTTP method (e.g., 'GET', 'POST'). - endpoint : str - API endpoint to interact with. - data : dict, optional - Payload to include in the request, by default None. - binary : bool, optional - Whether the data payload is binary, by default False. - - Returns - ------- - dict - The parsed JSON response from the API. - - Raises - ------ - requests.exceptions.HTTPError - If the API request fails. - - Examples - -------- - >>> client = FigshareAPIClient() - >>> response = client.send_request("GET", "articles/19999901") #doctest: +SKIP - >>> print(response) #doctest: +SKIP - {'files': [{'id': 35589521, - 'name': '01_TT.tif', - 'size': 1048736, - 'is_link_only': False, - 'download_url': 'https://ndownloader.figshare.com/files/35589521', - 'supplied_md5': '1ddb354132c2f7f54dec6e72bdb62422', - 'computed_md5': '1ddb354132c2f7f54dec6e72bdb62422', - 'mimetype': 'image/tiff'}, - 'authors': [{'id': 11888465, - 'full_name': 'Mostafa Farrag', - 'first_name': 'Mostafa', - 'last_name': 'Farrag', - 'is_active': True, - 'url_name': 'Mostafa_Farrag', - 'orcid_id': '0000-0002-1673-0126'}], - 'figshare_url': 'https://figshare.com/articles/dataset/parameter_set-1/19999901', - 'download_disabled': False, - ... - 'version': 2, - 'status': 'public', - 'size': 19878928, - 'created_date': '2022-06-04T14:15:43Z', - 'modified_date': '2022-06-04T14:15:44Z', - 'is_public': True, - 'is_confidential': False, - 'is_metadata_record': False, - 'confidential_reason': '', - 'metadata_reason': '', - 'license': {'value': 1, - 'name': 'CC BY 4.0', - 'id': 19999901, - 'title': 'Parameter set-1', - 'doi': '10.6084/m9.figshare.19999901.v2', - 'url': 'https://api.figshare.com/v2/articles/19999901', - 'published_date': '2022-06-04T14:15:43Z', - 'url_private_api': 'https://api.figshare.com/v2/account/articles/19999901', - 'url_public_api': 'https://api.figshare.com/v2/articles/19999901', - 'url_private_html': 'https://figshare.com/account/articles/19999901', - 'url_public_html': 'https://figshare.com/articles/dataset/parameter_set-1/19999901', - 'timeline': {'posted': '2022-06-04T14:15:43', - 'firstOnline': '2022-06-04T13:52:54'}, - } + ) -> dict[str, int] | None: + """Send an HTTP request to the Figshare API. + + Args: + method (str): HTTP method (e.g., ``'GET'``, ``'POST'``). + endpoint (str): API endpoint to interact with. + data (dict, optional): Payload to include in the request. + Defaults to None. + binary (bool, optional): Whether the data payload is binary. + Defaults to False. + + Returns: + dict[str, int] | None: The parsed JSON response from the API, or None if the response body is empty. + + Raises: + requests.exceptions.HTTPError: If the API request fails. + + Examples: + >>> client = FigshareAPIClient() # doctest: +SKIP + >>> response = client.send_request( + ... "GET", "articles/19999901" + ... ) # doctest: +SKIP """ url = f"{self.base_url}/{endpoint}" payload = json.dumps(data) if data and not binary else data @@ -121,134 +89,89 @@ def send_request( logger.error(f"HTTPError: {error}, Response: {response.text}") raise - def get_article_version(self, article_id: int, version: int) -> Dict[str, int]: - """ - Retrieve a specific version of an article from the Figshare API. - - Parameters - ---------- - article_id : int - The ID of the article to retrieve. - version : int - The version number of the article to retrieve. - - Returns - ------- - dict - Details of the specific version of the article. - - Raises - ------ - requests.exceptions.HTTPError - If the API request fails. - - Examples - -------- - >>> client = FigshareAPIClient() - >>> response = client.get_article_version(19999901, 1) #doctest: +SKIP - >>> print(response) #doctest: +SKIP - { - 'url_public_html': 'https://figshare.com/articles/dataset/parameter_set-1/19999901/1', - 'files': [ - {'id': 35589521, 'name': '01_TT.tif', 'download_url': 'https://ndownloader.figshare.com/files/35589521', - 'mimetype': 'image/tiff'}, - {'id': 35589524, 'name': '02_RFCF.tif', 'download_url': 'https://ndownloader.figshare.com/files/35589524', - ], - 'custom_fields': [], - 'authors': [ - {'id': 11888465, 'full_name': 'Mostafa Farrag', 'first_name': 'Mostafa', 'last_name': 'Farrag', - 'is_active': True, 'url_name': 'Mostafa_Farrag', 'orcid_id': '0000-0002-1673-0126'} - ], - 'figshare_url': 'https://figshare.com/articles/dataset/parameter_set-1/19999901', - 'description': '

hydrology

', 'version': 1, 'status': 'public', 'created_date': '2022-06-04T13:52:54Z', - 'modified_date': '2022-06-04T14:15:43Z', 'is_public': True, 'is_confidential': False, - 'license': { - 'value': 1, 'name': 'CC BY 4.0', 'url': 'https://creativecommons.org/licenses/by/4.0/'}, - 'tags': ['hydrology; precipitation; river basin; discharge; modelling; flood forecasting', 'Hydrology'], - 'citation': 'Farrag, Mostafa (2022). parameter set-1. figshare. Dataset. - https://doi.org/10.6084/m9.figshare.19999901.v1', 'id': 19999901, 'title': 'parameter - set-1', 'doi': '10.6084/m9.figshare.19999901.v1', 'url': 'https://api.figshare.com/v2/articles/19999901', - 'published_date': '2022-06-04T13:52:54Z', 'defined_type_name': 'dataset', - 'url_public_api': 'https://api.figshare.com/v2/articles/19999901', - 'timeline': {'posted': '2022-06-04T13:52:54', 'firstOnline': '2022-06-04T13:52:54'} - } + def get_article_version(self, article_id: int, version: int) -> dict[str, int] | None: + """Retrieve a specific version of an article from the API. + + Args: + article_id (int): The ID of the article to retrieve. + version (int): The version number of the article to + retrieve. + + Returns: + dict[str, int]: Details of the specific version of the + article. + + Raises: + requests.exceptions.HTTPError: If the API request fails. + + Examples: + >>> client = FigshareAPIClient() # doctest: +SKIP + >>> response = client.get_article_version( + ... 19999901, 1 + ... ) # doctest: +SKIP """ endpoint = f"articles/{article_id}/versions/{version}" return self.send_request("GET", endpoint) - def list_article_versions(self, article_id: int) -> List[Dict[str, int]]: - """ - Retrieve all available versions of a specific article from the Figshare API. + def list_article_versions(self, article_id: int) -> list[dict[str, int]] | None: + """Retrieve all available versions of a specific article. - Parameters - ---------- - article_id : int - The ID of the article to retrieve versions for. + Args: + article_id (int): The ID of the article to retrieve + versions for. - Returns - ------- - List[Dict[str, int]]: - A list of available versions for the specified article. + Returns: + list[dict[str, int]] | None: A list of available versions for the + specified article, or None if the response body is empty. - Raises - ------ - requests.exceptions.HTTPError - If the API request fails. + Raises: + requests.exceptions.HTTPError: If the API request fails. - Examples - -------- - >>> client = FigshareAPIClient() - >>> versions = client.list_article_versions(19999901) #doctest: +SKIP - >>> print(versions) #doctest: +SKIP - [{'version': 1, - 'url': 'https://api.figshare.com/v2/articles/19999901/versions/1'}, - {'version': 2, - 'url': 'https://api.figshare.com/v2/articles/19999901/versions/2'}] + Examples: + >>> client = FigshareAPIClient() # doctest: +SKIP + >>> versions = client.list_article_versions( + ... 19999901 + ... ) # doctest: +SKIP """ endpoint = f"articles/{article_id}/versions" - return self.send_request("GET", endpoint) + return self.send_request("GET", endpoint) # type: ignore[return-value] class FileManager: - r""" - Handle file operations such as downloading and saving files. - - Methods - ------- - download_file(url: str, dest_path: Path): - Download a file from the specified URL to the destination path. - clear_directory(directory: Path): - Clears all files in the specified directory. - - Examples - -------- - The FileManager class can be used to download files and clear directories: - >>> FileManager.download_file( - ... "https://ndownloader.figshare.com/files/35589521", "examples/data/parameters/01_TT.tif" - ... ) # doctest: +SKIP - 2025-01-05 17:24:25.610 | DEBUG | Hapi.parameters.parameters:download_file:227 - File downloaded: examples\data\parameters\01_TT.tif - >>> FileManager.clear_directory("./downloads") # doctest: +SKIP + r"""Handle file operations such as downloading and saving files. + + This class exposes only static methods and does not need to be + instantiated. + + Examples: + >>> FileManager.download_file( # doctest: +SKIP + ... "https://ndownloader.figshare.com/files/35589521", + ... "examples/data/parameters/01_TT.tif", + ... ) + >>> FileManager.clear_directory( + ... "./downloads" + ... ) # doctest: +SKIP """ @staticmethod def download_file(url: str, download_path: Path): - r""" - Download a file from the specified URL to the destination path. - - Parameters - ---------- - url : str - The URL of the file to download. - download_path : Path - The local file path where the file will be saved. - - Examples - -------- - The FileManager class can be used to download files and clear directories: - >>> FileManager.download_file( - ... "https://ndownloader.figshare.com/files/35589521", "examples/data/parameters/01_TT.tif" - ... ) # doctest: +SKIP - 2025-01-05 17:24:25.610 | DEBUG | Hapi.parameters.parameters:download_file:227 - File downloaded: examples\data\parameters\01_TT.tif + r"""Download a file from a URL to a local path. + + Args: + url (str): The URL of the file to download. + download_path (Path): The local file path where the file + will be saved. + + Raises: + ValueError: If the URL scheme is not ``http`` or ``https``. + requests.exceptions.HTTPError: If the download request + fails. + + Examples: + >>> FileManager.download_file( # doctest: +SKIP + ... "https://ndownloader.figshare.com/files/35589521", + ... "examples/data/parameters/01_TT.tif", + ... ) """ allowed_schemes = {"http", "https"} scheme = urlparse(url).scheme @@ -271,18 +194,19 @@ def download_file(url: str, download_path: Path): logger.debug(f"File downloaded: {download_path}") @staticmethod - def clear_directory(directory: Union[Path, str]): - """ - Clear all files in the specified directory. + def clear_directory(directory: Path | str): + """Clear all files in the specified directory. + + Only regular files are removed; subdirectories are left + untouched. - Parameters - ---------- - directory : Path/str - The directory to clear. + Args: + directory (Path | str): The directory to clear. - Examples - -------- - >>> FileManager.clear_directory("./downloads") + Examples: + >>> FileManager.clear_directory( + ... "./downloads" + ... ) # doctest: +SKIP """ directory = Path(directory) if isinstance(directory, str) else directory if directory.exists(): @@ -293,71 +217,31 @@ def clear_directory(directory: Union[Path, str]): class ParameterManager: - r""" - Manages hydrological parameters and integrates with Figshare for data retrieval. - - Attributes - ---------- - ARTICLE_IDS : list - List of article IDs corresponding to parameter sets. - PARAMETER_NAMES : list - List of parameter names. - PARAMETER_SET_ID : list - User-friendly IDs for parameter sets (e.g., 1-10, avg, max, min). - - Methods - ------- - get_article_details(article_id: int, version: Optional[int] = None): - Retrieves details of an article from the Figshare API. - list_files(article_id: int, version: Optional[int] = None): - Lists all files in an article. - download_files(article_id: int, dest_directory: Path, version: Optional[int] = None): - Downloads all files in an article to the specified directory. - get_article_id_from_friendly_id(friendly_id: Union[int, str]) -> int: - Maps a user-friendly ID to the corresponding article ID. - - Examples - -------- - First create the Figshare API client: + r"""Manage hydrological parameters via the Figshare API. + + This class maps user-friendly parameter-set identifiers (1--10, + ``"avg"``, ``"max"``, ``"min"``) to Figshare article IDs and + provides convenience methods for listing and downloading files. + + Attributes: + ARTICLE_IDS (list): Figshare article IDs for each parameter + set. + PARAMETER_NAMES (list): Canonical names of all 18 HBV + parameters. + PARAMETER_SET_ID (list): User-friendly IDs (1--10, ``"avg"``, + ``"max"``, ``"min"``). + api_client (FigshareAPIClient): The underlying API client. + + Examples: >>> api_client = FigshareAPIClient() - - Then create the ParameterManager: >>> manager = ParameterManager(api_client) - - Retrieve details of a parameter set: - >>> set_id = 1 - >>> files = manager.list_files(set_id) # doctest: +SKIP - >>> print(files) # doctest: +SKIP - [ - {'id': 35589521, 'name': '01_TT.tif', 'download_url': 'https://ndownloader.figshare.com/files/35589521', ...}, - {'id': 35589524, 'name': '02_RFCF.tif', 'download_url': 'https://ndownloader.figshare.com/files/35589524', ...}, - {'id': 35589527, 'name': '03_SFCF.tif', 'download_url': 'https://ndownloader.figshare.com/files/35589527', ...}, - ... - ] - - Get parameter set details: - >>> details = manager.get_parameter_set_details(1) # doctest: +SKIP - >>> print(details.keys()) # doctest: +SKIP - dict_keys( - [ - 'files', 'custom_fields', 'authors', 'figshare_url', 'download_disabled', 'description', 'funding', - 'funding_list', 'version', 'status', 'size', 'created_date', 'modified_date', 'is_public', - 'is_confidential', 'is_metadata_record', 'confidential_reason', 'metadata_reason', 'license', 'tags', - 'categories', 'references', 'has_linked_file', 'citation', 'related_materials', 'is_embargoed', - 'embargo_date', 'embargo_type', 'embargo_title', 'embargo_reason', 'embargo_options', 'id', 'title', - 'doi', 'handle', 'url', 'published_date', 'thumb', 'defined_type', 'defined_type_name', 'group_id', - 'url_private_api', 'url_public_api', 'url_private_html', 'url_public_html', 'timeline', - 'resource_title', 'resource_doi' - ] - ) - - Download all files in a parameter set: - >>> manager.download_files(1, "examples/data/downloads") # doctest: +SKIP - 2025-01-05 16:48:55.532 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\01_TT.tif - 2025-01-05 16:48:56.158 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\02_RFCF.tif - 2025-01-05 16:48:56.631 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\03_SFCF.tif - 2025-01-05 16:48:57.233 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\04_CFMAX.tif - ... + >>> files = manager.list_files(1) # doctest: +SKIP + >>> details = manager.get_parameter_set_details( + ... 1 + ... ) # doctest: +SKIP + >>> manager.download_files( + ... 1, "examples/data/downloads" + ... ) # doctest: +SKIP """ ARTICLE_IDS = [ @@ -400,55 +284,39 @@ class ParameterManager: PARAMETER_SET_ID = list(range(1, 11)) + ["avg", "max", "min"] def __init__(self, api_client: FigshareAPIClient): - """initialize.""" + """Initialize ParameterManager. + + Args: + api_client (FigshareAPIClient): A configured Figshare API + client instance. + """ self.api_client = api_client def get_parameter_set_details( - self, set_id: Union[int, str], version: Optional[int] = None - ) -> Dict[str, int]: - """ - Retrieve details of a parameter set from the Figshare API. - - Parameters - ---------- - set_id : int/str - The ID of the parameter set to retrieve. - version : int, optional, default is None - The version of the parameter set. - - Returns - ------- - Dict[str, int]: - Details of the parameter set. - - Raises - ------ - requests.exceptions.HTTPError - If the API request fails. - - Examples - -------- - First create the Figshare API client: - >>> api_client = FigshareAPIClient() + self, set_id: int | str, version: int | None = None + ) -> dict[str, int] | None: + """Retrieve details of a parameter set from the Figshare API. - Then create the ParameterManager: - >>> manager = ParameterManager(api_client) + Args: + set_id (int | str): The user-friendly ID of the + parameter set (1--10, ``"avg"``, ``"max"``, ``"min"``). + version (int, optional): The version of the parameter set. + Defaults to None (latest version). - Get parameter set details: - >>> details = manager.get_parameter_set_details(1) # doctest: +SKIP - >>> print(details.keys()) # doctest: +SKIP - dict_keys( - [ - 'files', 'custom_fields', 'authors', 'figshare_url', 'download_disabled', 'description', 'funding', - 'funding_list', 'version', 'status', 'size', 'created_date', 'modified_date', 'is_public', - 'is_confidential', 'is_metadata_record', 'confidential_reason', 'metadata_reason', 'license', 'tags', - 'categories', 'references', 'has_linked_file', 'citation', 'related_materials', 'is_embargoed', - 'embargo_date', 'embargo_type', 'embargo_title', 'embargo_reason', 'embargo_options', 'id', 'title', - 'doi', 'handle', 'url', 'published_date', 'thumb', 'defined_type', 'defined_type_name', 'group_id', - 'url_private_api', 'url_public_api', 'url_private_html', 'url_public_html', 'timeline', - 'resource_title', 'resource_doi' - ] - ) + Returns: + dict[str, int]: A dictionary containing full article + metadata (files, authors, license, dates, etc.). + + Raises: + requests.exceptions.HTTPError: If the API request fails. + ValueError: If *set_id* is not a valid parameter-set ID. + + Examples: + >>> api_client = FigshareAPIClient() + >>> manager = ParameterManager(api_client) + >>> details = manager.get_parameter_set_details( + ... 1 + ... ) # doctest: +SKIP """ article_id = self.get_article_id(set_id) endpoint = f"articles/{article_id}" @@ -456,73 +324,48 @@ def get_parameter_set_details( endpoint += f"/versions/{version}" return self.api_client.send_request("GET", endpoint) - def list_files(self, set_id: Union[int, str], version: Optional[int] = None): - """ - List all files in an article. - - Parameters - ---------- - set_id : int - The ID of the article to list files for. - version : int, optional - The version of the article, by default None. - - Returns - ------- - list - A list of files in the article. - - Examples - -------- - First create the Figshare API client: - >>> api_client = FigshareAPIClient() + def list_files(self, set_id: int | str, version: int | None = None): + """List all files in a parameter set. - Then create the ParameterManager: - >>> manager = ParameterManager(api_client) + Args: + set_id (int | str): The user-friendly ID of the + parameter set. + version (int, optional): The version of the article. + Defaults to None. + + Returns: + list: A list of file dictionaries, each containing keys + such as ``"id"``, ``"name"``, and + ``"download_url"``. - Retrieve details of a parameter set: - >>> set_id = 1 - >>> files = manager.list_files(set_id) # doctest: +SKIP - >>> print(files) # doctest: +SKIP - [ - {'id': 35589521, 'name': '01_TT.tif', 'download_url': 'https://ndownloader.figshare.com/files/35589521', ...}, - {'id': 35589524, 'name': '02_RFCF.tif', 'download_url': 'https://ndownloader.figshare.com/files/35589524', ...}, - {'id': 35589527, 'name': '03_SFCF.tif', 'download_url': 'https://ndownloader.figshare.com/files/35589527', ...}, - ... - ] + Examples: + >>> api_client = FigshareAPIClient() + >>> manager = ParameterManager(api_client) + >>> files = manager.list_files(1) # doctest: +SKIP """ details = self.get_parameter_set_details(set_id, version) + if details is None: + return [] return details.get("files", []) def download_files( - self, set_id: Union[int, str], download_dir: Path, version: Optional[int] = None + self, set_id: int | str, download_dir: Path, version: int | None = None ): - r""" - Download all files in an article to the specified directory. - - Parameters - ---------- - set_id : int - The ID of the article to download files from. - download_dir : Path - The local directory to save the files. - version : int, optional, by default None. - The version of the article. - - Examples - -------- - First create the Figshare API client: - >>> api_client = FigshareAPIClient() + r"""Download all files in a parameter set to a local directory. - Then create the ParameterManager: - >>> manager = ParameterManager(api_client) + Args: + set_id (int | str): The user-friendly ID of the + parameter set. + download_dir (Path): The local directory to save the files. + version (int, optional): The version of the article. + Defaults to None. - >>> manager.download_files(1, "examples/data/downloads") # doctest: +SKIP - 2025-01-05 16:48:55.532 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\01_TT.tif - 2025-01-05 16:48:56.158 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\02_RFCF.tif - 2025-01-05 16:48:56.631 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\03_SFCF.tif - 2025-01-05 16:48:57.233 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\04_CFMAX.tif - ... + Examples: + >>> api_client = FigshareAPIClient() + >>> manager = ParameterManager(api_client) + >>> manager.download_files( + ... 1, "examples/data/downloads" + ... ) # doctest: +SKIP """ download_dir = ( Path(download_dir) if isinstance(download_dir, str) else download_dir @@ -533,34 +376,23 @@ def download_files( dest_path = download_dir / file["name"] FileManager.download_file(file["download_url"], dest_path) - def get_article_id(self, set_id: Union[int, str]) -> int: - """ - Map a user-friendly ID (1-10, avg, max, min) to the corresponding article ID. - - Parameters - ---------- - set_id : int or str - The parameter set id (1-10, avg, max, min). - - Returns - ------- - int - The corresponding article ID. - - Raises - ------ - ValueError - If the friendly ID is invalid. - - Examples - -------- - First create the Figshare API client: - >>> api_client = FigshareAPIClient() + def get_article_id(self, set_id: int | str) -> int: + """Map a user-friendly set ID to a Figshare article ID. - Then create the ParameterManager: - >>> manager = ParameterManager(api_client) + Args: + set_id (int | str): The parameter set ID (1--10, + ``"avg"``, ``"max"``, ``"min"``). + + Returns: + int: The corresponding Figshare article ID. - Then get the article ID for a parameter set: + Raises: + ValueError: If *set_id* is not found in + ``PARAMETER_SET_ID``. + + Examples: + >>> api_client = FigshareAPIClient() + >>> manager = ParameterManager(api_client) >>> manager.get_article_id(1) 19999901 """ @@ -574,119 +406,108 @@ def get_article_id(self, set_id: Union[int, str]) -> int: class Parameter: - r""" - A simplified interface for handling hydrological parameters. - - `HAPI_DATA_DIR` environment variable must be set to the directory where parameter sets will be saved. - - Attributes - ---------- - version : int - The version of the parameter sets to retrieve. - - Methods - ------- - get_parameters(dest_directory: Path): - Downloads all parameter sets to the specified directory. - get_parameter_by_friendly_id(friendly_id: Union[int, str], dest_directory: Path): - Downloads a specific parameter set based on a user-friendly ID. - list_parameter_names() -> List[str]: - Lists all parameter names. - - Examples - -------- - First create the Parameter object: - >>> parameter = Parameter(version=1) - - Then download all parameter sets: - >>> parameter.get_parameters("examples/data/parameters") # doctest: +SKIP - 2025-01-05 16:48:55.532 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\01_TT.tif - 2025-01-05 16:48:56.158 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\02_RFCF.tif - 2025-01-05 16:48:56.631 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\03_SFCF.tif - 2025-01-05 16:48:57.233 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\04_CFMAX.tif - ... - - Get a specific parameter set: - >>> parameter.get_parameter_set(1, "examples/data/parameters") # doctest: +SKIP - 2025-01-05 16:48:55.532 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\01_TT.tif - 2025-01-05 16:48:56.158 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\02_RFCF.tif - 2025-01-05 16:48:56.631 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\03_SFCF.tif - 2025-01-05 16:48:57.233 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\04_CFMAX.tif - ... - - Then list all parameter names: - >>> names = parameter.list_parameter_names() - >>> print(names) - ['01_tt', '02_rfcf', '03_sfcf', '04_cfmax', '05_cwh', '06_cfr', '07_fc', '08_beta', '09_etf', - '10_lp', '11_k0', '12_k1', '13_k2', '14_uzl', '15_perc', '16_maxbas', '17_K_muskingum', - '18_x_muskingum'] + r"""High-level interface for handling hydrological parameters. + + The ``HAPI_DATA_DIR`` environment variable must be set to the + directory where parameter sets will be saved when *download_dir* is + not provided. + + Attributes: + version (int): The version of the parameter sets to retrieve. + api_client (FigshareAPIClient): The underlying Figshare API + client. + manager (ParameterManager): The parameter manager that + handles article lookups and downloads. + download_dir (Path): The directory where parameter sets are + saved. + + Examples: + >>> parameter = Parameter(version=1) # doctest: +SKIP + >>> parameter.get_parameters( + ... "examples/data/parameters" + ... ) # doctest: +SKIP + >>> parameter.get_parameter_set( + ... 1, "examples/data/parameters" + ... ) # doctest: +SKIP + >>> names = Parameter.list_parameter_names() + >>> len(names) + 18 + >>> names[0] + '01_tt' + >>> names[-1] + '18_x_muskingum' """ - def __init__(self, version: int = 1, download_dir: Optional[Path] = None): - """initialize.""" + def __init__(self, version: int = 1, download_dir: Path | None = None): + """Initialize Parameter. + + Args: + version (int, optional): The Figshare article version to + use. Defaults to 1. + download_dir (Path, optional): Directory where parameter + files are saved. Defaults to None, which reads from + the ``HAPI_DATA_DIR`` environment variable. + + Raises: + ValueError: If *download_dir* is None and the + ``HAPI_DATA_DIR`` environment variable is not set. + """ self.version = version self.api_client = FigshareAPIClient() self.manager = ParameterManager(self.api_client) if download_dir is None: - download_dir = os.getenv("HAPI_DATA_DIR") - if download_dir is None: + download_dir_env: str | None = os.getenv("HAPI_DATA_DIR") + if download_dir_env is None: raise ValueError("HAPI_DATA_DIR environment variable is not set") else: - download_dir = Path(download_dir) + download_dir = Path(download_dir_env) download_dir.mkdir(parents=True, exist_ok=True) self.download_dir = download_dir - def get_parameters(self, download_dir: Optional[Path] = None): - r""" - Download all parameter sets to the specified directory. - - Parameters - ---------- - download_dir : Path - The directory where parameter sets will be saved. - - Examples - -------- - First create the Parameter object: - >>> parameter = Parameter(version=1) - - Then download all parameter sets: - >>> parameter.get_parameters("examples/data/parameters") # doctest: +SKIP - 2025-01-05 16:48:55.532 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\01_TT.tif - 2025-01-05 16:48:56.158 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\02_RFCF.tif - 2025-01-05 16:48:56.631 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\03_SFCF.tif - 2025-01-05 16:48:57.233 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\04_CFMAX.tif - ... + def get_parameters(self, download_dir: Path | None = None): + r"""Download all parameter sets. + + Iterates over every entry in + ``ParameterManager.PARAMETER_SET_ID`` and downloads the + corresponding files. + + Args: + download_dir (Path, optional): The directory where + parameter sets will be saved. Defaults to None (uses + the instance ``download_dir``). + + Examples: + >>> parameter = Parameter(version=1) # doctest: +SKIP + >>> parameter.get_parameters( + ... "examples/data/parameters" + ... ) # doctest: +SKIP """ for set_id in ParameterManager.PARAMETER_SET_ID: self.get_parameter_set(set_id, download_dir) logger.debug(f"Downloaded parameter set: {set_id} to {download_dir}") def get_parameter_set( - self, set_id: Union[int, str], download_dir: Optional[Path] = None + self, set_id: int | str, download_dir: Path | None = None ): - r""" - Download all parameter sets to the specified directory. - - Parameters - ---------- - set_id: int - The ID of the parameter set to download. - download_dir : Path, optional, default is None - The directory where parameter sets will be saved. - - Examples - -------- - First create the Parameter object: - >>> parameter = Parameter(version=1) - - Get a specific parameter set: - >>> parameter.get_parameter_set(1, "examples/data/parameters") # doctest: +SKIP - 2025-01-05 16:48:55.532 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\01_TT.tif - 2025-01-05 16:48:56.158 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\02_RFCF.tif - 2025-01-05 16:48:56.631 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\03_SFCF.tif - 2025-01-05 16:48:57.233 | DEBUG | Hapi.parameters.parameters:download_file:224 - File downloaded: examples\data\downloads\04_CFMAX.tif - ... + r"""Download a specific parameter set. + + Args: + set_id (int | str): The user-friendly ID of the + parameter set to download (1--10, ``"avg"``, ``"max"``, + ``"min"``). + download_dir (Path, optional): The directory where the + parameter set will be saved. Defaults to None (uses + the instance ``download_dir`` with a subdirectory + named after *set_id*). + + Raises: + ValueError: If *set_id* is not a valid parameter-set ID. + + Examples: + >>> parameter = Parameter(version=1) # doctest: +SKIP + >>> parameter.get_parameter_set( + ... 1, "examples/data/parameters" + ... ) # doctest: +SKIP """ if set_id not in ParameterManager.PARAMETER_SET_ID: raise ValueError( @@ -702,106 +523,60 @@ def get_parameter_set( logger.debug(f"Downloaded parameter set: {set_id} to {download_dir}") @staticmethod - def list_parameter_names() -> List[str]: - """ - List all parameter names. - - Returns - ------- - list - A list of parameter names. - - Examples - -------- - First create the Parameter object: - >>> parameter = Parameter(version=1) - - Then list all parameter names: - >>> names = parameter.list_parameter_names() - >>> print(names) - ['01_tt', '02_rfcf', '03_sfcf', '04_cfmax', '05_cwh', '06_cfr', '07_fc', '08_beta', '09_etf', - '10_lp', '11_k0', '12_k1', '13_k2', '14_uzl', '15_perc', '16_maxbas', '17_K_muskingum', - '18_x_muskingum'] + def list_parameter_names() -> list[str]: + """List all parameter names. + + Returns: + list[str]: A list of the 18 HBV parameter names. + + Examples: + >>> names = Parameter.list_parameter_names() + >>> len(names) + 18 + >>> names[0] + '01_tt' + >>> names[-1] + '18_x_muskingum' """ return ParameterManager.PARAMETER_NAMES def main(): - """ - CLI. - - Hapi CLI for Hydrological Parameter Operations. - This command-line interface (CLI) provides tools to manage and interact with hydrological parameters. - Users can download parameter sets, retrieve specific parameter sets, or list all available parameter names. - - Parameters - ---------- - None - The function does not take any parameters directly. All input is handled through command-line arguments. - - Returns - ------- - None - The function does not return any values. Outputs are sent to stdout or files, depending on the command. - - Raises - ------ - ValueError - If invalid or insufficient arguments are provided. - FileNotFoundError - If the specified directory for downloads does not exist and cannot be created. - requests.exceptions.RequestException - If there is an error communicating with the Figshare API. - - Examples - -------- - Download All Parameter Sets - --------------------------- - ```bash - download-parameters --directory /path/to/save --version 1 - ``` - - `--directory`: Optional. Specifies the directory to save downloaded parameters. Defaults to the `HAPI_DATA_DIR` environment variable. - - `--version`: Optional. Specifies the version of the parameters. Defaults to 1. - - Download a Specific Parameter Set - ---------------------------------- - ```bash - download-parameter-set 1 --directory /path/to/save --version 1 - ``` - - Replace `1` with the desired parameter set ID (e.g., `avg`, `max`). - - `--directory`: Optional. Specifies the directory to save the parameter set. - - `--version`: Optional. Specifies the version of the parameter set. - - List Parameter Names - --------------------- - ```bash - list-parameter-names - ``` - This command lists all available parameter names. - - See Also - -------- - Hapi.parameters.parameters.Parameter - For details on the `Parameter` class and its methods. - Hapi.parameters.parameters.ParameterManager - For managing parameter-related operations. - requests.exceptions.RequestException - For handling errors in HTTP requests. - - Notes - ----- - - Ensure that the `HAPI_DATA_DIR` environment variable is set if the `--directory` option is not used. - - The CLI depends on the Figshare API for fetching parameter data. - - Author - ------ - Mostafa Farrag - - Email: moah.farag@gmail.com - - Project: HAPI-Nile - - References - ---------- - [1] HAPI GitHub Repository: https://github.com/Serapieum-of-alex/Hapi + """Run the Hapi CLI for hydrological parameter operations. + + This entry point provides three sub-commands: + + - ``download-parameters`` -- download every parameter set. + - ``download-parameter-set `` -- download one parameter + set. + - ``list-parameter-names`` -- print all parameter names to stdout. + + Raises: + ValueError: If invalid or insufficient arguments are provided. + FileNotFoundError: If the specified directory does not exist + and cannot be created. + requests.exceptions.RequestException: If there is an error + communicating with the Figshare API. + + Examples: + Download all parameter sets:: + + download-parameters --directory /path/to/save --version 1 + + Download a specific parameter set:: + + download-parameter-set 1 --directory /path/to/save \\ + --version 1 + + List parameter names:: + + list-parameter-names + + See Also: + ``Hapi.parameters.parameters.Parameter``: For details on the + ``Parameter`` class and its methods. + ``Hapi.parameters.parameters.ParameterManager``: For managing + parameter-related operations. """ import argparse diff --git a/src/Hapi/routing.py b/src/Hapi/routing.py index 0802e0735..353a208cc 100644 --- a/src/Hapi/routing.py +++ b/src/Hapi/routing.py @@ -1,59 +1,75 @@ -"""Routing File contains all Routing functions. +"""Routing module for hydrograph routing in the Hapi hydrological model. -1- Muskingum cunge -2- triangular (MAXBAS) routing function +This module provides channel routing methods used to translate and +attenuate flood hydrographs as they travel through the river network. +It includes: + +1. Muskingum-Cunge routing (iterative and vectorized variants). +2. Triangular (MAXBAS) routing using transfer function weights. """ +from __future__ import annotations -from typing import Union import numpy as np class Routing: - """Routing class contains routing method. - - Methods - ------- - 1- Muskingum - 2- Muskingum_V - 3- TriangularRouting1 - functions : - 1- CalculateWeights - 4- TriangularRouting2 - functions - 1- Tf + """Routing methods for translating and attenuating discharge hydrographs. + + This class provides static methods for two families of routing + approaches: + + - **Muskingum routing**: classical linear channel routing using the + Muskingum storage equation, available in both iterative + (`Muskingum`) and vectorized (`Muskingum_V`) forms. + - **Triangular routing**: MAXBAS-based transfer-function routing + that distributes discharge over a triangular weighting kernel. + Two variants are provided: `TriangularRouting1` supports + fractional MAXBAS values; `TriangularRouting2` uses integer + MAXBAS values. """ def __init__(self): - """Routing model does not need any parameters to be instantiated.""" + """Initialize the Routing instance. + + The Routing class uses only static methods, so no parameters + are required for instantiation. + """ pass @staticmethod def Muskingum(inflow, Qinitial, k, x, dt): - """Muskingum. - - Parameters - ---------- - inflow: [numpy array] - time series of inflow hydrograph - Qinitial: [numeric] - initial value for outflow - k: [numeric] - travelling time (hours) - x: [numeric] - surface nonlinearity coefficient (0,0.5) - dt: [numeric] - delta t - - Returns - ------- - outflow: [numpy array] - time series of routed hydrograph - - Examples - -------- - >>> q = [] # discharge time series - >>> time_resolution = 1 # hourly time step - >>> q_routed = Routing.Muskingum(q, q[0], k, x, time_resolution) + """Route an inflow hydrograph using the Muskingum method. + + Applies the Muskingum linear storage routing equation to + translate and attenuate an inflow time series. The three + Muskingum coefficients (c1, c2, c3) are derived from the + travel time ``k``, the weighting factor ``x``, and the time + step ``dt``. + + Args: + inflow (numpy.ndarray): Time series of inflow discharge + values. + Qinitial (float): Initial outflow value at the first time + step. + k (float): Channel travel time in the same units as + ``dt`` (typically hours). + x (float): Weighting factor for inflow versus storage, + ranging from 0 (maximum attenuation) to 0.5 (no + attenuation). + dt (float): Computational time step in the same units as + ``k``. + + Returns: + numpy.ndarray: Routed outflow hydrograph with the same + length as ``inflow``, rounded to four decimal places. + + Examples: + >>> import numpy as np + >>> from Hapi.routing import Routing + >>> inflow = np.array([0, 1, 3, 7, 10, 9, 6, 3, 1, 0]) + >>> q_routed = Routing.Muskingum( + ... inflow, Qinitial=0, k=2, x=0.2, dt=1 + ... ) """ c1 = (dt - 2 * k * x) / (2 * k * (1 - x) + dt) c2 = (dt + 2 * k * x) / (2 * k * (1 - x) + dt) @@ -75,38 +91,42 @@ def Muskingum(inflow, Qinitial, k, x, dt): @staticmethod def Muskingum_V( inflow: np.ndarray, - Qinitial: Union[int, float], - k: Union[int, float], - x: Union[int, float], - dt: Union[int, float], + Qinitial: int | float, + k: int | float, + x: int | float, + dt: int | float, ) -> np.ndarray: - """Muskingum_V. - - Vectorized version of Muskingum - - Parameters - ---------- - inflow: [numpy array] - time series of inflow hydrograph - Qinitial: [numeric] - initial value for outflow - k: [numeric] - travelling time (hours) - x: [numeric] - surface nonlinearity coefficient (0,0.5) - dt: [numeric] - delta t - - Returns - ------- - outflow: - [numpy array] time series of routed hydrograph - - Examples - -------- - >>> q = [] # discharge time series - >>> time_resolution = 1 # hourly time step - >>> q_routed = Routing.Muskingum_V(q, q[0], k, x, time_resolution) + """Route an inflow hydrograph using a vectorized Muskingum method. + + This is a performance-optimized variant of `Muskingum` that + pre-computes the c1 and c2 terms in a vectorized manner before + applying the recursive c3 correction. Negative outflow values + that would result from the c3 term are suppressed. + + Args: + inflow (numpy.ndarray): Time series of inflow discharge + values. + Qinitial (int | float): Initial outflow value at the + first time step. + k (int | float): Channel travel time in the same + units as ``dt`` (typically hours). + x (int | float): Weighting factor for inflow versus + storage, ranging from 0 (maximum attenuation) to 0.5 + (no attenuation). + dt (int | float): Computational time step in the + same units as ``k``. + + Returns: + numpy.ndarray: Routed outflow hydrograph with the same + length as ``inflow``. + + Examples: + >>> import numpy as np + >>> from Hapi.routing import Routing + >>> inflow = np.array([0, 1, 3, 7, 10, 9, 6, 3, 1, 0]) + >>> q_routed = Routing.Muskingum_V( + ... inflow, Qinitial=0, k=2, x=0.2, dt=1 + ... ) """ c1 = (dt - 2 * k * x) / (2 * k * (1 - x) + dt) c2 = (dt + 2 * k * x) / (2 * k * (1 - x) + dt) @@ -128,25 +148,27 @@ def Muskingum_V( @staticmethod def Tf(maxbas): - """Tf. - - Transfer function weight generator in a shape of a triangle. - - Parameters - ---------- - maxbas: [integer] - number of time steps that the triangular routing function - is going to divide the discharge into, based on the weights - generated from this function, min value is 1 and default value is 1 - - Returns - ------- - wi: [numpy array] - array of normalised weights - - Examples - -------- - >>> ws = Routing.Tf(5) + """Generate triangular transfer-function weights. + + Builds a normalized weight array shaped as a triangle with a + rising limb for the first half and a falling limb for the + second half. The weights sum to 1 and are used by + `TriangularRouting2` to distribute discharge across + ``maxbas`` time steps. + + Args: + maxbas (int): Number of time steps over which to spread + the discharge. Must be >= 1. + + Returns: + numpy.ndarray: Array of normalized weights with length + ``maxbas`` that sum to 1.0. + + Examples: + >>> from Hapi.routing import Routing + >>> weights = Routing.Tf(5) + >>> print(weights.sum()) + 1.0 """ wi = [] @@ -166,28 +188,32 @@ def Tf(maxbas): @staticmethod def TriangularRouting2(q, maxbas=1): - """Triangular Routing. - - The function implements the transfer function using a triangular function (considers only integer values of - Maxbas parameter) - - Parameters - ---------- - q: [numpy array] - time series of discharge hydrographs - maxbas: [integer] - number of time steps that the triangular routing function - is going to divide the discharge into, based on the weights - generated from this function, min value is 1 and default value is 1 - - Returns - ------- - q_r: [numpy array] - time series of routed hydrograph - - Examples - -------- - >>> q_sim = Routing.TriangularRouting2(np.array(q_sim), parameters[-1]) + """Route discharge using a triangular transfer function (integer MAXBAS). + + Convolves the input discharge time series with a triangular + weighting kernel whose width is determined by ``maxbas``. Only + integer values of ``maxbas`` are supported; the value is + rounded to the nearest integer internally. Weights are + generated by `Tf`. + + Args: + q (numpy.ndarray): Time series of discharge values to be + routed. + maxbas (int): Number of time steps for the triangular + routing kernel. Must be >= 1. Defaults to 1. + + Returns: + numpy.ndarray: Routed discharge time series with the same + length as ``q``. + + Raises: + AssertionError: If ``maxbas`` is less than 1. + + Examples: + >>> import numpy as np + >>> from Hapi.routing import Routing + >>> q = np.array([0.0, 1.0, 3.0, 7.0, 10.0, 9.0, 6.0]) + >>> q_routed = Routing.TriangularRouting2(q, maxbas=3) """ # input data validation assert maxbas >= 1, "Maxbas value has to be larger than 1" @@ -203,28 +229,34 @@ def TriangularRouting2(q, maxbas=1): q_temp = np.float32(q) for w_i in w: q_r += q_temp * w_i - q_temp = np.insert(q_temp, 0, 0.0)[:-1] + q_temp = np.insert(q_temp, 0, 0.0)[:-1] # type: ignore[assignment] return q_r @staticmethod def CalculateWeights(MAXBAS): - """Calculate Weights. - - - calculate the MAXBAS Weights based on a MAXBAX number The MAXBAS is a HBV parameter that - controls the routing. - - It is important to mention that this function allows to obtain weights - not only for interger values but from decimals values as well. - - Parameters - ---------- - MAXBAS: [Numeric] - - Examples - -------- - >>> maxbasW = Routing.CalculateWeights(5) - >>> print(maxbasW) - >>> 0.0800 0.2400 0.3600 0.2400 0.0800 + """Calculate triangular routing weights for a given MAXBAS value. + + Computes normalized weights based on the area under an + equilateral-triangle transfer function. Unlike `Tf`, this + method supports fractional (non-integer) MAXBAS values by + computing exact trapezoidal areas under the triangle curve. + + Args: + MAXBAS (float): The MAXBAS routing parameter controlling + the number of time steps over which discharge is + distributed. Can be an integer or a decimal value. + + Returns: + numpy.ndarray: Array of normalized routing weights. The + length is ``floor(MAXBAS)`` for integer values, or + ``floor(MAXBAS) + 1`` for non-integer values. + + Examples: + >>> from Hapi.routing import Routing + >>> weights = Routing.CalculateWeights(5) + >>> print(weights) + [0.08 0.24 0.36 0.24 0.08] """ yant = 0 Total = 0 # Just to verify how far from the unit is the result @@ -291,19 +323,33 @@ def CalculateWeights(MAXBAS): @staticmethod def TriangularRouting1(Q, MAXBAS): - """TriangularRouting1. - - calculate the routing from a input hydrograph using the MAXBAS parameter from the HBV - model (considers float values of Maxbas parameter). - - Examples - -------- - [Qout,maxbasW]=RoutingMAXBAS(Q,5); - where: - Qout = output hydrograph - maxbasW = MAXBAS weight - Q = input hydrograph - 5 = MAXBAS parameter value. + """Route discharge using triangular weights (fractional MAXBAS). + + Distributes the input hydrograph over time using MAXBAS + triangular weights computed by `CalculateWeights`. Unlike + `TriangularRouting2`, this method supports fractional + (non-integer) MAXBAS values. + + The routing is performed by constructing a weighted discharge + matrix and summing along the anti-diagonals to produce the + output hydrograph. + + Args: + Q (numpy.ndarray): Input discharge time series to be + routed. + MAXBAS (float): The MAXBAS routing parameter. Can be an + integer or a decimal value. Controls the number of + time steps over which the discharge is spread. + + Returns: + numpy.ndarray: Routed output hydrograph with the same + length as ``Q``. + + Examples: + >>> import numpy as np + >>> from Hapi.routing import Routing + >>> Q = np.array([0.0, 1.0, 3.0, 7.0, 10.0, 9.0, 6.0]) + >>> q_out = Routing.TriangularRouting1(Q, MAXBAS=5) """ # CALCULATE MAXBAS WEIGHTS maxbasW = Routing.CalculateWeights(MAXBAS) diff --git a/src/Hapi/rrm/__init__.py b/src/Hapi/rrm/__init__.py index ad281ac3b..731f1f5c5 100644 --- a/src/Hapi/rrm/__init__.py +++ b/src/Hapi/rrm/__init__.py @@ -1 +1,2 @@ """Rainfall-runoff model.""" +from __future__ import annotations diff --git a/src/Hapi/rrm/base_model.py b/src/Hapi/rrm/base_model.py index 7a6b27fd6..0a9273778 100644 --- a/src/Hapi/rrm/base_model.py +++ b/src/Hapi/rrm/base_model.py @@ -1,11 +1,69 @@ +"""Base class for conceptual rainfall-runoff models. + +The ``Hapi.rrm.base_model`` module defines the abstract base class +:class:`BaseConceptualModel` from which all conceptual hydrological +models in the Hapi framework inherit. The class prescribes a common +interface of subroutines that every model must implement: + +- **precipitation** -- partition incoming precipitation into rainfall + and snowfall. +- **snow** -- simulate snow accumulation, melt, and refreezing. +- **soil** -- compute soil moisture changes, evapotranspiration, and + recharge. +- **response** -- transform upper and lower zone storage into + discharge. +- **routing** -- apply a transfer function to route discharge through + time. +- **simulate** -- run the full model over a precipitation time series. + +Concrete implementations include +:class:`~Hapi.rrm.hbv.HBV`, +:class:`~Hapi.rrm.hbv_bergestrom92.HBVBergestrom92`, and +:class:`~Hapi.rrm.hbv_lake.HBVLake`. +""" +from __future__ import annotations + from abc import ABC, abstractmethod -from typing import Tuple import numpy as np class BaseConceptualModel(ABC): - """Base class for conceptual models.""" + """Abstract base class for conceptual rainfall-runoff models. + + ``BaseConceptualModel`` defines the interface that every conceptual + hydrological model in the Hapi framework must satisfy. Subclasses + provide concrete implementations of the precipitation, snow, soil, + response, routing, and simulate subroutines. + + The typical modelling pipeline is: + + 1. Call :meth:`precipitation` to split total precipitation into + rainfall and snowfall based on temperature thresholds. + 2. Call :meth:`snow` to update the snow pack and compute + infiltration. + 3. Call :meth:`soil` to update soil moisture and compute recharge + to the upper zone. + 4. Call :meth:`response` to convert zone storages into discharge + components. + 5. Call :meth:`routing` to apply a transfer function that smooths + the discharge hydrograph. + 6. Call :meth:`simulate` to execute the full pipeline over the + entire time series. + + Examples: + Subclass ``BaseConceptualModel`` to create a custom model: + + >>> from Hapi.rrm.hbv import HBV + >>> model = HBV() + >>> rf, sf = model.precipitation( + ... temp=10.0, ltt=0.0, utt=2.0, prec=15.0, + ... rfcf=1.0, sfcf=1.0, + ... ) + >>> print(f"rainfall={rf}, snowfall={sf}") + rainfall=15.0, snowfall=0.0 + """ + @staticmethod def precipitation( temp: float, @@ -14,15 +72,82 @@ def precipitation( rfcf: float, sfcf: float, pcorr: float = 1.0, - ) -> Tuple[float, float]: - """Split precipitation into rainfall and snowfall.""" + ) -> tuple[float, float]: + """Split precipitation into rainfall and snowfall. + + Partitions total precipitation into a rainfall component and a + snowfall component based on temperature relative to a lower + threshold (``ltt``) and an upper threshold (``utt``). + Correction factors ``rfcf`` and ``sfcf`` are applied to + rainfall and snowfall respectively. + + Args: + temp (float): Measured air temperature [C]. + ltt (float): Lower temperature threshold [C]. Below this + value all precipitation is snowfall. + utt (float): Upper temperature threshold [C]. Above this + value all precipitation is rainfall. + rfcf (float): Rainfall correction factor [-]. + sfcf (float): Snowfall correction factor [-]. + pcorr (float): General precipitation correction factor + [-]. Defaults to 1.0. + + Returns: + tuple[float, float]: A tuple of ``(rainfall, snowfall)`` + in mm. + + Examples: + >>> from Hapi.rrm.hbv import HBV + >>> rf, sf = HBV.precipitation( + ... temp=10.0, ltt=0.0, utt=2.0, prec=15.0, + ... rfcf=1.0, sfcf=1.0, + ... ) + >>> print(f"rainfall={rf}, snowfall={sf}") + rainfall=15.0, snowfall=0.0 + """ ... @staticmethod def snow( temp, ttm, cfmax, cfr, cwh, rf, sf, wc_old, sp_old - ) -> Tuple[float, float, float]: - """Snow accumulation/melt routine.""" + ) -> tuple[float, float, float]: + """Simulate snow accumulation, melt, and refreezing. + + Updates the snow pack state by computing melt (when + temperature exceeds the melting threshold ``ttm``) or + refreezing (when temperature is below ``ttm``). Water that + exceeds the holding capacity of the snow pack drains into the + soil as infiltration. + + Args: + temp (float): Air temperature [C]. + ttm (float): Temperature threshold for melting [C]. + cfmax (float): Day degree factor [mm C^-1 h^-1]. + cfr (float): Refreezing factor [-]. + cwh (float): Capacity for water holding in snow pack as + a fraction of the snow water equivalent [-]. + rf (float): Rainfall [mm]. + sf (float): Snowfall [mm]. + wc_old (float): Water content in previous state [mm]. + sp_old (float): Snow pack in previous state [mm]. + + Returns: + tuple[float, float, float]: A tuple of + ``(infiltration, wc_new, sp_new)`` where + ``infiltration`` is the water draining into the soil + [mm], ``wc_new`` is the updated liquid water content + [mm], and ``sp_new`` is the updated snow pack [mm]. + + Examples: + >>> from Hapi.rrm.hbv import HBV + >>> inf, wc_new, sp_new = HBV.snow( + ... cfmax=0.1, temp=5.0, ttm=0.0, cfr=0.05, + ... cwh=0.1, rf=2.0, sf=0.0, + ... wc_old=0.0, sp_old=10.0, + ... ) + >>> print(f"infiltration={inf:.2f}") + infiltration=1.55 + """ ... @staticmethod @@ -40,23 +165,187 @@ def soil( ep, sm_old, uz_old, - ) -> Tuple[float, float]: - """Soil moisture and runoff routine.""" + ) -> tuple[float, float]: + """Compute soil moisture balance and recharge to the upper zone. + + Calculates the updated soil moisture after accounting for + infiltration, recharge to the upper groundwater zone, actual + evapotranspiration, and capillary rise from the upper zone. + + Args: + fc (float): Field capacity [mm]. + beta (float): Shape coefficient for effective + precipitation separation [-]. + etf (float): Total potential evapotranspiration factor + [-]. + temp (float): Air temperature [C]. + tm (float): Long-term average temperature [C]. + e_corr (float): Evapotranspiration correction factor [-]. + lp (float): Wilting point as a fraction of field capacity + [-]. + tfac (float): Time factor (e.g., 24 for daily, 1 for + hourly) [-]. + c_flux (float): Maximum capillary flux in the root zone + [mm]. + infiltration (float): Actual infiltration from + precipitation and snowmelt [mm]. + ep (float): Potential evapotranspiration [mm]. + sm_old (float): Previous soil moisture value [mm]. + uz_old (float): Previous upper zone storage value [mm]. + + Returns: + tuple[float, float]: A tuple of ``(sm_new, uz_int_1)`` + where ``sm_new`` is the new soil moisture [mm] and + ``uz_int_1`` is the new direct runoff into the upper + zone [mm]. + + Examples: + >>> from Hapi.rrm.hbv import HBV + >>> sm_new, uz_int_1 = HBV.soil( + ... fc=200.0, beta=2.0, etf=0.1, temp=20.0, + ... tm=18.0, e_corr=1.0, lp=0.3, c_flux=0.01, + ... inf=5.0, ep=3.0, sm_old=100.0, uz_old=10.0, + ... ) + >>> print(f"sm_new={sm_new:.2f}, uz_int_1={uz_int_1:.2f}") + sm_new=101.13, uz_int_1=11.25 + """ ... @staticmethod def response( tfac, perc, alpha, k, k1, area, lz_old, uz_int_1 - ) -> Tuple[float, float, float]: - """Convert runoff to stream discharge.""" + ) -> tuple[float, float, float]: + """Convert upper and lower zone storage into stream discharge. + + Transforms the current values of the upper and lower storage + zones into discharge components using recession coefficients. + Also controls the recharge of the lower zone tank (baseflow) + via percolation from the upper zone. + + Args: + tfac (float): Time conversion factor (e.g., 24 for daily, + 1 for hourly) [-]. + perc (float): Percolation rate from the upper zone to the + lower zone [mm/h]. + alpha (float): Response box parameter controlling the + non-linearity of the upper zone outflow [-]. + k (float): Upper zone recession coefficient [h^-1]. + k1 (float): Lower zone recession coefficient [h^-1]. + area (float): Catchment area [km^2]. + lz_old (float): Previous lower zone storage [mm]. + uz_int_1 (float): Previous upper zone storage before + percolation [mm]. + + Returns: + tuple[float, float, float]: A tuple of + ``(q_new, uz_new, lz_new)`` where ``q_new`` is the total + discharge [m^3/s], ``uz_new`` is the updated upper zone + storage [mm], and ``lz_new`` is the updated lower zone + storage [mm]. + + Examples: + >>> from Hapi.rrm.hbv import HBV + >>> q_0, q_1, uz_new, lz_new = HBV.response( + ... perc=0.5, alpha=0.5, k=0.01, k1=0.001, + ... lz_old=20.0, uz_int_1=15.0, + ... ) + >>> print(f"q_upper={q_0:.4f}, q_lower={q_1:.4f}") + q_upper=0.0381, q_lower=0.0205 + """ ... def routing(self, q: np.ndarray, maxbas: int = 1) -> np.ndarray: - """Apply triangular routing function.""" + """Apply a triangular transfer function to route discharge. + + Smooths the discharge hydrograph by distributing the signal + over ``maxbas`` time steps using a triangular weighting + function. + + Args: + q (numpy.ndarray): Discharge time series [mm]. + maxbas (int): Number of time steps for the transfer + function base. Must be >= 1. Defaults to 1. + + Returns: + numpy.ndarray: Routed discharge time series with the same + length as ``q``. + + Raises: + AssertionError: If ``maxbas`` is less than 1. + + Examples: + >>> import numpy as np + >>> from Hapi.rrm.hbv import HBV + >>> model = HBV() + >>> q = np.array([0.0, 0.0, 5.0, 3.0, 1.0, 0.0]) + >>> q_routed = model.routing(q, maxbas=3) + >>> print(q_routed.round(4)) + [0. 0. 2.5 4. 2.5 0.5] + """ ... @abstractmethod def simulate( self, prec, temp, et, ll_temp, par, init_st=None, q_init=None, snow=0 - ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: + ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """Run the model simulation over a full precipitation time series. + + Executes all model subroutines sequentially for each time step + in the input arrays, producing discharge and state variable + time series. + + Subclasses must implement this method with the specific model + logic (parameter parsing, subroutine calls, state updates). + + Args: + prec (numpy.ndarray): Average precipitation time series + of length ``n`` [mm/h]. + temp (numpy.ndarray): Average temperature time series of + length ``n`` [C]. + et (numpy.ndarray): Potential evapotranspiration time + series of length ``n`` [mm/h]. + ll_temp (numpy.ndarray): Long-term average temperature + time series of length ``n`` [C]. + par (numpy.ndarray): Parameter vector. The length depends + on the specific model implementation and whether + snow processes are active. + init_st (list[float]): Initial model states as + ``[sp, sm, uz, lz, wc]`` [mm]. If None, the model + uses default initial values. + q_init (float): Initial discharge value. If None, the + model computes it from the initial states and + parameters. + snow (int): Set to 1 to run the snow subroutine, 0 to + skip it. Defaults to 0. + + Returns: + tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]: + A tuple of ``(q_uz, q_lz, states)`` where: + + - ``q_uz``: Upper zone discharge array of length + ``n+1``. + - ``q_lz``: Lower zone discharge array of length + ``n+1``. + - ``states``: Model states array of shape ``(n+1, 5)``. + + Examples: + >>> import numpy as np + >>> from Hapi.rrm.hbv import HBV + >>> model = HBV() + >>> par = np.array([ + ... 1.0, 200.0, 2.0, 0.1, 0.3, 0.01, + ... 0.005, 0.0005, 0.5, 0.5, + ... ]) + >>> n = 5 + >>> prec = np.array([10.0, 8.0, 12.0, 6.0, 4.0]) + >>> temp_arr = np.full(n, 25.0) + >>> et = np.full(n, 3.0) + >>> init_st = [0.0, 100.0, 10.0, 15.0, 0.0] + >>> q_uz, q_lz, states = model.simulate( + ... prec, temp_arr, et, par, + ... init_st=init_st, snow=0, + ... ) + >>> print(f"q_uz length={len(q_uz)}") + q_uz length=6 + """ ... diff --git a/src/Hapi/rrm/distrrm.py b/src/Hapi/rrm/distrrm.py index c402b2184..b05e73330 100644 --- a/src/Hapi/rrm/distrrm.py +++ b/src/Hapi/rrm/distrrm.py @@ -1,3 +1,15 @@ +"""Distributed rainfall-runoff model execution and spatial routing. + +This module provides the ``DistributedRRM`` class, which runs a lumped +rainfall-runoff model (e.g., HBV) independently for each grid cell and +then routes the resulting discharge between cells following the river +network defined by a flow direction raster. + +The module belongs to the ``Hapi.rrm`` package and supports both +Muskingum and triangular (MAXBAS) routing strategies. +""" +from __future__ import annotations + import numpy as np from pyramids.dataset import Dataset @@ -5,18 +17,14 @@ class DistributedRRM: - """DistributedRRM class. - - runs simulation in lumped form for each cell separetly and rout the discharge between the cells following - the river network. - - Methods - ------- - 1-run_lumped_model - 2-SpatialRouting - 3-DistMaxBas1 - 4-DistMaxBas2 - 5-Dist_HBV2 + """Distributed rainfall-runoff model runner and spatial router. + + Runs a lumped hydrological model separately for each grid cell + and routes the resulting discharge between cells following the + river network. + + The class is stateless; all methods are static and operate on a + ``Model`` object that carries the required arrays and parameters. """ def __init__(self): @@ -24,53 +32,43 @@ def __init__(self): @staticmethod def run_lumped_model(Model): - """Run Lumped RRM. - - - runs the rainfall runoff lumped model (HBV, GR4,...) separately for each cell and return a time series - of arrays. - - Parameters - ---------- - Model: - ConceptualModel: [function] - conceptual model function - Raster: [gdal.dataset] - raster to get the spatial information (nodata cells) - raster input could be dem, flow accumulation or flow direction raster of the catchment - but the nodata value stored in the raster should be far from the - range of values that could result from the calculation - sp_prec: [numpy array] - 3d array of the precipitation data, sp_prec should - have the same 2d dimension of raster input - sp_et: [numpy array] - 3d array of the evapotranspiration data, sp_et should - have the same 2d dimension of raster input - sp_temp: [numpy array] - 3d array of the temperature data, sp_temp should - have the same 2d dimension of raster input - sp_pars: [numpy array] - number of 2d arrays of the catchment properties spatially - distributed in 2d and the third dimension is the number of parameters, - sp_pars should have the same 2d dimension of raster input - p2: [List] - list of unoptimized parameters - p2[0] = tfac, 1 for hourly, 0.25 for 15 min time step and 24 for daily time step - p2[1] = catchment area in km2 - init_st: [list] - initial state variables values [sp, sm, uz, lz, wc]. default=None - ll_temp: [numpy array] - 3d array of the long term average temperature data - q_init: [float] - initial discharge m3/s - - Returns - ------- - state_variables : [numpy ndarray] - 4D array (rows,cols,time,states) states are [sp,wc,sm,uz,lv] - qlz : [numpy ndarray] - 3D array of the lower zone discharge (lumped calculation for each cell separately) - quz : [numpy ndarray] - 3D array of the upper zone discharge + """Run lumped rainfall-runoff model for every grid cell. + + Executes the lumped conceptual model (e.g., HBV) independently + for each non-NaN cell in the catchment grid and converts the + resulting discharge from mm/time-step to m3/s. + + After execution the following attributes are set on *Model*: + ``state_variables``, ``quz``, and ``qlz``. + + Args: + Model: A catchment model object carrying the following + attributes: + + - ``rows`` (int): Number of grid rows. + - ``cols`` (int): Number of grid columns. + - ``TS`` (int): Number of time steps. + - ``FlowAccArr`` (numpy.ndarray): 2-D flow accumulation + array; NaN marks cells outside the domain. + - ``LumpedModel``: Lumped model instance with a + ``simulate`` method. + - ``Prec`` (numpy.ndarray): 3-D precipitation array + ``(rows, cols, TS)``. + - ``Temp`` (numpy.ndarray): 3-D temperature array. + - ``ET`` (numpy.ndarray): 3-D evapotranspiration array. + - ``ll_temp`` (numpy.ndarray): 3-D long-term average + temperature array. + - ``Parameters`` (numpy.ndarray): 3-D parameter array + ``(rows, cols, n_params)``. + - ``InitialCond`` (list): Initial state variable values + ``[sp, sm, uz, lz, wc]``. + - ``q_init`` (float): Initial discharge in m3/s. + - ``Snow`` (int): Snow module flag (0 or 1). + - ``CatArea`` (float): Catchment area in km2. + - ``px_tot_area`` (float): Total pixel area in km2. + - ``px_area`` (float): Single pixel area in km2. + - ``conversion_factor`` (float): Unit conversion + factor (``tfac * 3.6``). """ Model.state_variables = np.zeros( [Model.rows, Model.cols, Model.TS, 5], dtype=np.float32 @@ -109,38 +107,42 @@ def run_lumped_model(Model): @staticmethod def SpatialRouting(Model): - """Spatial Routing method. - - routes the discharge from cell to another following the flow direction input raster. - - Parameters - ---------- - Model: - qlz: [numpy ndarray] - 3D array of the lower zone discharge - quz: [numpy ndarray] - 3D array of the upper zone discharge - flow_acc: [gdal.dataset] - flow accumulation raster file of the catchment (clipped to the catchment only) - flow_direct: [gdal.dataset] - flow Direction raster file of the catchment (clipped to the catchment only) - sp_pars: [numpy ndarray] - 3D array of the parameters - p2: [List] - list of unoptimized parameters - p2[0] = tfac, 1 for hourly, 0.25 for 15 min time step and 24 for daily time step - p2[1] = catchment area in km2 - - Returns - ------- - qout: [numpy array] - 1D timeseries of discharge at the outlet of the catchment - of unit m3/sec - quz_routed: [numpy ndarray] - 3D array of the upper zone discharge accumulated and - routed at each time step - qlz_translated: [numpy ndarray] - 3D array of the lower zone discharge translated at each time step + """Route discharge between cells following the flow direction. + + Accumulates and routes upper-zone discharge (``quz``) using + Muskingum routing from upstream to downstream cells according + to the flow direction raster. Lower-zone discharge (``qlz``) + is translated (accumulated without attenuation) so that total + discharge can be computed at any internal point. + + After execution the following attributes are set on *Model*: + ``quz_routed``, ``qlz_translated``, and ``Qtot``. + + Args: + Model: A catchment model object carrying the following + attributes: + + - ``rows`` (int): Number of grid rows. + - ``cols`` (int): Number of grid columns. + - ``TS`` (int): Number of time steps. + - ``FlowAccArr`` (numpy.ndarray): 2-D flow accumulation + array; NaN marks cells outside the domain. + - ``quz`` (numpy.ndarray): 3-D upper-zone discharge + array ``(rows, cols, TS)`` in m3/s. + - ``qlz`` (numpy.ndarray): 3-D lower-zone discharge + array ``(rows, cols, TS)`` in m3/s. + - ``acc_val`` (list): Sorted unique flow accumulation + values. + - ``FDT`` (dict): Flow direction table mapping + ``"row,col"`` keys to lists of upstream cell + index pairs. + - ``Parameters`` (numpy.ndarray): 3-D parameter array + where indices 10 and 11 are Muskingum K and X. + - ``dt`` (float): Time-step factor (``tfac``). + - ``routing_method`` (str): Routing method name (e.g., + ``"Muskingum"``). + - ``BankfullDepth`` (numpy.ndarray): 2-D bankfull + depth array used for non-Muskingum methods. """ # # routing lake discharge with DS cell k & x and adding to cell Q # q_lake=Routing.Muskingum_V(q_lake,q_lake[0],sp_pars[lakecell[0],lakecell[1],10],sp_pars[lakecell[0],lakecell[1],11],p2[0]) @@ -212,16 +214,27 @@ def SpatialRouting(Model): @staticmethod def DistMaxbas1(Model): - """DistMaxbas1 method rout the discharge directly to the outlet from each cell using triangular function. - - Parameters - ---------- - Model : TYPE - DESCRIPTION. - - Returns - ------- - None. + """Route discharge to the outlet using a triangular function. + + Applies triangular (MAXBAS) routing to the upper-zone + discharge of each cell independently. The MAXBAS parameter + is read from the last column of the spatially distributed + parameter array. + + The ``Model.quz`` array is modified in place. + + Args: + Model: A catchment model object carrying the following + attributes: + + - ``rows`` (int): Number of grid rows. + - ``cols`` (int): Number of grid columns. + - ``FlowAccArr`` (numpy.ndarray): 2-D flow accumulation + array; NaN marks cells outside the domain. + - ``Parameters`` (numpy.ndarray): 3-D parameter array + where the last index holds the MAXBAS value. + - ``quz`` (numpy.ndarray): 3-D upper-zone discharge + array ``(rows, cols, TS)`` in m3/s. """ Maxbas = Model.Parameters[:, :, -1] @@ -235,12 +248,30 @@ def DistMaxbas1(Model): @staticmethod def DistMaxbas2(Model): - """DistMaxbas2 method rout the discharge directly to the outlet from each cell using triangular function, the maxbas parameters are going to be calculated based on the flow path length input. - - Parameters - ---------- - Model : TYPE - DESCRIPTION. + """Route discharge using a triangular function scaled by flow path length. + + Similar to ``DistMaxbas1``, but the MAXBAS parameter for each + cell is rescaled proportionally to its flow path length so that + cells farther from the outlet receive more attenuation. + + The ``Model.quz`` array is modified in place. + + Args: + Model: A catchment model object carrying the following + attributes: + + - ``rows`` (int): Number of grid rows. + - ``cols`` (int): Number of grid columns. + - ``FlowAccArr`` (numpy.ndarray): 2-D flow accumulation + array; NaN marks cells outside the domain. + - ``FPLArr`` (numpy.ndarray): 2-D flow path length + array. + - ``NoDataValue`` (float): No-data value used in the + flow path length raster. + - ``Parameters`` (numpy.ndarray): 3-D parameter array + where the last index holds the maximum MAXBAS value. + - ``quz`` (numpy.ndarray): 3-D upper-zone discharge + array ``(rows, cols, TS)`` in m3/s. """ MAXBAS = np.nanmax(Model.Parameters[:, :, -1]) # replace novalue cells by nan @@ -279,7 +310,62 @@ def Dist_HBV2( ll_temp=None, q_0=None, ): - """Original Routing function.""" + """Run distributed HBV model with lake routing (legacy). + + Executes the HBV conceptual model for every grid cell, routes + lake discharge into the downstream cell using Muskingum + routing, and then routes upper-zone discharge through the + river network. Lower-zone discharge is averaged across all + cells and converted to m3/s. + + Args: + conceptual_model: Lumped model object with a ``simulate`` + method. + lakecell (list[int]): Two-element list ``[row, col]`` + giving the grid indices of the lake cell. + q_lake (numpy.ndarray): 1-D array of lake discharge + time series in m3/s. + DEM: GDAL dataset of the catchment DEM. + flow_acc (dict): Flow direction table mapping + ``"row,col"`` keys to lists of upstream cell index + pairs. + flow_acc_plan (numpy.ndarray): 2-D array of flow + accumulation values; NaN marks no-data cells. + sp_prec (numpy.ndarray): 3-D precipitation array + ``(rows, cols, time_steps)``. + sp_et (numpy.ndarray): 3-D evapotranspiration array. + sp_temp (numpy.ndarray): 3-D temperature array. + sp_pars (numpy.ndarray): 3-D parameter array + ``(rows, cols, n_params)``. Indices 5, 6, 7 are + K1, K, and alpha; indices 10, 11 are Muskingum + K and X. + p2 (list): Unoptimized parameters. + + - ``p2[0]``: tfac -- 1 for hourly, 0.25 for 15 min, + 24 for daily. + - ``p2[1]``: Catchment area in km2. + init_st (list, optional): Initial state variable values + ``[sp, sm, uz, lz, wc]``. Defaults to None. + ll_temp (numpy.ndarray, optional): 3-D long-term average + temperature array. Defaults to None. + q_0 (float, optional): Initial discharge in m3/s. + Defaults to None. + + Returns: + tuple: A five-element tuple containing: + + - **qout** (*numpy.ndarray*): 1-D discharge time + series at the catchment outlet in m3/s. + - **st** (*numpy.ndarray*): 4-D state variable array + ``(rows, cols, time_steps, 5)`` with states + ``[sp, sm, uz, lz, wc]``. + - **quz_routed** (*numpy.ndarray*): 3-D routed + upper-zone discharge array in m3/s. + - **qlz** (*numpy.ndarray*): 1-D spatially averaged + lower-zone discharge in m3/s. + - **quz** (*numpy.ndarray*): 3-D upper-zone + discharge array in m3/s (before spatial routing). + """ n_steps = sp_prec.shape[2] + 1 # no of time steps =length of time series +1 # initialize vector of nans to fill states dummy_states = np.empty([n_steps, 5]) # [sp,sm,uz,lz,wc] @@ -369,9 +455,9 @@ def Dist_HBV2( quz.append(np.array(q_uzi)) # upper zone routed discharge mm/timestep # ------------------------------------------------------------------------------ # convert to arrays - st = np.array(st) - qlz = np.array(qlz) - quz = np.array(quz) + st = np.array(st) # type: ignore + qlz = np.array(qlz) # type: ignore + quz = np.array(quz) # type: ignore # convert quz from mm/time step to m3/sec area_coef = p2[1] / px_tot_area quz = quz * px_area * area_coef / (p2[0] * 3.6) @@ -444,8 +530,8 @@ def Dist_HBV2( outletx = outlet[0][0] outlety = outlet[1][0] - qlz = np.array( - [np.nanmean(qlz[:, :, i]) for i in range(n_steps)] + qlz = np.array( # type: ignore[assignment] + [np.nanmean(qlz[:, :, i]) for i in range(n_steps)] # type: ignore[call-overload] ) # average of all cells (not routed mm/timestep) # convert Qlz to m3/sec qlz = qlz * p2[1] / (p2[0] * 3.6) # generation diff --git a/src/Hapi/rrm/hbv.py b/src/Hapi/rrm/hbv.py index a4f2c4d59..1297395c2 100644 --- a/src/Hapi/rrm/hbv.py +++ b/src/Hapi/rrm/hbv.py @@ -1,25 +1,32 @@ """Lumped Conceptual HBV model. -HBV is lumped conceptual model consists of precipitation, snow melt, -soil moisture and response subroutine to convert precipitation into o runoff, -where state variables are updated each time step to represent a specific -hydrologic behaviour the catchment - -This version was edited based on a Master Thesis on "Spatio-temporal simulation -of catchment response based on dynamic weighting of hydrological models" on april 2018 - -- Model inputs are Precipitation, evapotranspiration and temperature, initial - state variables, and initial discharge. -- Model output is Qalculated dicharge at time t+1 -- Model equations are solved using explicit scheme -- model structure uses 18 parameters if the catchment has snow - [ltt, utt, rfcf, sfcf, ttm, cfmax, cwh, cfr, fc, beta, e_corr, etf, lp, - c_flux, k, k1, alpha, perc] - - otherwise it uses 10 parameters +The ``Hapi.rrm.hbv`` module implements the HBV-96 lumped conceptual +hydrological model. The model consists of precipitation partitioning, +snow accumulation and melt, soil moisture accounting, and a response +routine that converts precipitation into runoff. State variables are +updated at each time step to represent the hydrologic behaviour of the +catchment. + +This version was edited based on a Master Thesis on +"Spatio-temporal simulation of catchment response based on dynamic +weighting of hydrological models" in April 2018. + +Model inputs are precipitation, evapotranspiration, and temperature, +along with initial state variables and initial discharge. Model output +is calculated discharge at time ``t+1``. Model equations are solved +using an explicit scheme. + +The model structure uses 18 parameters when the catchment has snow:: + + [ltt, utt, rfcf, sfcf, ttm, cfmax, cwh, cfr, fc, beta, + e_corr, etf, lp, c_flux, k, k1, alpha, perc] + +Otherwise it uses 10 parameters:: + [rfcf, fc, beta, etf, lp, c_flux, k, k1, alpha, perc] """ -from typing import Tuple +from __future__ import annotations + import numpy as np from Hapi.rrm.base_model import BaseConceptualModel @@ -76,37 +83,89 @@ # return np.random.uniform(P_LB, P_UB) class HBV(BaseConceptualModel): + """HBV-96 lumped conceptual rainfall-runoff model. + + The HBV (Hydrologiska Byrans Vattenbalansavdelning) model is a + conceptual hydrological model that simulates catchment discharge + from precipitation, temperature, and potential evapotranspiration. + The model consists of four main routines: + + - **Precipitation**: partitions incoming precipitation into rainfall + and snowfall based on temperature thresholds. + - **Snow**: simulates snow accumulation, melt, and refreezing using + a degree-day approach. + - **Soil**: computes soil moisture changes, actual evapotranspiration, + capillary rise, and recharge to the upper zone. + - **Response**: transforms upper and lower zone storage into + discharge via recession coefficients. + + The class inherits from + :class:`~Hapi.rrm.base_model.BaseConceptualModel` and provides + concrete implementations of all required subroutines. + + Examples: + >>> from Hapi.rrm.hbv import HBV + >>> model = HBV() + >>> rf, sf = model.precipitation( + ... temp=5.0, ltt=0.0, utt=2.0, prec=10.0, + ... rfcf=1.0, sfcf=1.0, + ... ) + >>> print(f"rainfall={rf}, snowfall={sf}") + rainfall=10.0, snowfall=0.0 + """ @staticmethod - def precipitation(temp, ltt, utt, prec, rfcf, sfcf): - """Precipitation routine of the HBV96 model. - - If temperature is lower than ltt, all the precipitation is considered as - snow. If the temperature is higher than utt, all the precipitation is - considered as rainfall. In case that the temperature is between ltt and - utt, precipitation is a linear mix of rainfall and snowfall. - - Parameters - ---------- - temp : float - Measured temperature [C] - ltt : float - Lower temperature treshold [C] - utt : float - Upper temperature treshold [C] - prec : float - Precipitation [mm] - rfcf : float - Rainfall corrector factor - sfcf : float - Snowfall corrector factor - - Returns - ------- - _rf : float - Rainfall [mm] - _sf : float - Snowfall [mm] + def precipitation(temp, ltt, utt, prec, rfcf, sfcf): # type: ignore[override] + """Partition precipitation into rainfall and snowfall. + + If temperature is lower than ``ltt``, all precipitation is + considered snowfall. If temperature is higher than ``utt``, all + precipitation is considered rainfall. When temperature falls + between ``ltt`` and ``utt``, precipitation is linearly mixed + between rainfall and snowfall. + + Args: + temp (float): Measured temperature [C]. + ltt (float): Lower temperature threshold [C]. + utt (float): Upper temperature threshold [C]. + prec (float): Precipitation [mm]. + rfcf (float): Rainfall correction factor. + sfcf (float): Snowfall correction factor. + + Returns: + tuple[float, float]: A tuple of ``(rainfall, snowfall)`` + in mm. + + Examples: + Temperature above the upper threshold produces only + rainfall: + + >>> from Hapi.rrm.hbv import HBV + >>> rf, sf = HBV.precipitation( + ... temp=10.0, ltt=0.0, utt=2.0, prec=15.0, + ... rfcf=1.0, sfcf=1.0, + ... ) + >>> print(f"rainfall={rf}, snowfall={sf}") + rainfall=15.0, snowfall=0.0 + + Temperature below the lower threshold produces only + snowfall: + + >>> rf, sf = HBV.precipitation( + ... temp=-5.0, ltt=0.0, utt=2.0, prec=15.0, + ... rfcf=1.0, sfcf=1.0, + ... ) + >>> print(f"rainfall={rf}, snowfall={sf}") + rainfall=0.0, snowfall=15.0 + + Temperature between thresholds produces a mix: + + >>> rf, sf = HBV.precipitation( + ... temp=1.0, ltt=0.0, utt=2.0, prec=10.0, + ... rfcf=1.0, sfcf=1.0, + ... ) + >>> print(f"rainfall={rf}, snowfall={sf}") + rainfall=5.0, snowfall=5.0 """ if temp <= ltt: # if temp <= lower temp threshold @@ -124,48 +183,63 @@ def precipitation(temp, ltt, utt, prec, rfcf, sfcf): return rf, sf @staticmethod - def snow(cfmax, temp, ttm, cfr, cwh, rf, sf, wc_old, sp_old) -> Tuple[float, float, float]: - """Snow routine of the HBV-96 model. - - The snow pack consists of two states: Water Content (wc) and Snow Pack - (sp). The first corresponds to the liquid part of the water in the snow, - while the latter corresponds to the solid part. If the temperature is - higher than the melting point, the snow pack will melt and the solid snow - will become liquid. In the opposite case, the liquid part of the snow will - refreeze, and turn into solid. The water that cannot be stored by the solid - part of the snow pack will drain into the soil as part of infiltration. - - Parameters - ---------- - cfmax : float - Day degree factor - tfac : float - Temperature correction factor - temp : float - Temperature [C] - ttm : float - Temperature treshold for Melting [C] - cfr : float - Refreezing factor - cwh : float - Capacity for water holding in snow pack - _rf : float - Rainfall [mm] - _sf : float - Snowfall [mm] - wc_old : float - Water content in previous state [mm] - sp_old : float - snow pack in previous state [mm] - - Returns - ------- - _in : float - Infiltration [mm] - _wc_new : float - Water content in posterior state [mm] - _sp_new : float - Snowpack in posterior state [mm] + def snow(cfmax, temp, ttm, cfr, cwh, rf, sf, wc_old, sp_old) -> tuple[float, float, float]: # type: ignore[override] + """Simulate snow accumulation, melt, and refreezing. + + The snow pack consists of two states: Water Content (``wc``) + and Snow Pack (``sp``). The water content corresponds to the + liquid part of the water in the snow, while the snow pack + corresponds to the solid part. If temperature exceeds the + melting threshold, the snow pack melts and solid snow becomes + liquid. Otherwise, the liquid part refreezes into solid snow. + Water that exceeds the holding capacity of the snow pack drains + into the soil as infiltration. + + Args: + cfmax (float): Day degree factor [mm C^-1 h^-1]. + temp (float): Temperature [C]. + ttm (float): Temperature threshold for melting [C]. + cfr (float): Refreezing factor. + cwh (float): Capacity for water holding in snow pack. + rf (float): Rainfall [mm]. + sf (float): Snowfall [mm]. + wc_old (float): Water content in previous state [mm]. + sp_old (float): Snow pack in previous state [mm]. + + Returns: + tuple[float, float, float]: A tuple of + ``(infiltration, wc_new, sp_new)`` where + ``infiltration`` is the water draining into the soil + [mm], ``wc_new`` is the updated water content [mm], + and ``sp_new`` is the updated snow pack [mm]. + + Examples: + When temperature exceeds the melt threshold, snow melts + and infiltration occurs: + + >>> from Hapi.rrm.hbv import HBV + >>> inf, wc_new, sp_new = HBV.snow( + ... cfmax=0.1, temp=5.0, ttm=0.0, cfr=0.05, + ... cwh=0.1, rf=2.0, sf=0.0, + ... wc_old=0.0, sp_old=10.0, + ... ) + >>> print(f"infiltration={inf:.2f}") + infiltration=1.55 + >>> print(f"wc_new={wc_new:.2f}, sp_new={sp_new:.2f}") + wc_new=0.95, sp_new=9.5 + + When temperature is below the melt threshold, water + refreezes: + + >>> inf, wc_new, sp_new = HBV.snow( + ... cfmax=0.1, temp=-2.0, ttm=0.0, cfr=0.5, + ... cwh=0.1, rf=0.0, sf=5.0, + ... wc_old=2.0, sp_old=10.0, + ... ) + >>> print(f"infiltration={inf:.2f}") + infiltration=1.40 + >>> print(f"sp_new={sp_new:.2f}") + sp_new=15.1 """ if temp > ttm: # if temp > melting threshold @@ -206,49 +280,49 @@ def snow(cfmax, temp, ttm, cfr, cwh, rf, sf, wc_old, sp_old) -> Tuple[float, flo return inf, wc_new, sp_new @staticmethod - def soil(fc, beta, etf, temp, tm, e_corr, lp, c_flux, inf, ep, sm_old, uz_old) -> Tuple[float, float]: # tfac, - """Soil routine of the HBV-96 model. - - The model checks for the amount of water that can infiltrate the soil, - coming from the liquid precipitation and the snow pack melting. A part of - the water will be stored as soil moisture, while other will become runoff, - and routed to the upper zone tank. - - Parameters - ---------- - fc : float - Filed capacity - beta : float - Shape coefficient for effective precipitation separation - etf : float - Total potential evapotranspiration - temp : float - Temperature - tm : float - Average long term temperature - e_corr : float - Evapotranspiration corrector factor - lp : float _soil - wilting point - tfac : float - Time conversion factor - c_flux : float - Capilar flux in the root zone - _in : float - actual infiltration - ep : float - actual evapotranspiration - sm_old : float - Previous soil moisture value - uz_old : float - Previous Upper zone value - - Returns - ------- - sm_new : float - New value of soil moisture - uz_int_1 : float - New value of direct runoff into upper zone + def soil(fc, beta, etf, temp, tm, e_corr, lp, c_flux, inf, ep, sm_old, uz_old) -> tuple[float, float]: # type: ignore[override] # tfac, + """Compute soil moisture balance and recharge to the upper zone. + + The soil routine checks the amount of water that can infiltrate + the soil from liquid precipitation and snow pack melting. A + portion of the water is stored as soil moisture, while the + remainder becomes runoff routed to the upper zone tank. The + routine also accounts for actual evapotranspiration and + capillary rise from the upper zone. + + Args: + fc (float): Field capacity [mm]. + beta (float): Shape coefficient for effective precipitation + separation. + etf (float): Total potential evapotranspiration factor. + temp (float): Temperature [C]. + tm (float): Average long-term temperature [C]. + e_corr (float): Evapotranspiration correction factor. + lp (float): Wilting point as a fraction of field capacity. + c_flux (float): Capillary flux in the root zone [mm]. + inf (float): Actual infiltration [mm]. + ep (float): Actual evapotranspiration [mm]. + sm_old (float): Previous soil moisture value [mm]. + uz_old (float): Previous upper zone value [mm]. + + Returns: + tuple[float, float]: A tuple of ``(sm_new, uz_int_1)`` + where ``sm_new`` is the new soil moisture [mm] and + ``uz_int_1`` is the new direct runoff into the upper + zone [mm]. + + Examples: + Compute soil moisture update for a warm day with + infiltration: + + >>> from Hapi.rrm.hbv import HBV + >>> sm_new, uz_int_1 = HBV.soil( + ... fc=200.0, beta=2.0, etf=0.1, temp=20.0, + ... tm=18.0, e_corr=1.0, lp=0.3, c_flux=0.01, + ... inf=5.0, ep=3.0, sm_old=100.0, uz_old=10.0, + ... ) + >>> print(f"sm_new={sm_new:.2f}, uz_int_1={uz_int_1:.2f}") + sm_new=101.13, uz_int_1=11.25 """ qdr = max( @@ -282,28 +356,44 @@ def soil(fc, beta, etf, temp, tm, e_corr, lp, c_flux, inf, ep, sm_old, uz_old) - return sm_new, uz_int_1 @staticmethod - def response(perc, alpha, k, k1, lz_old, uz_int_1) -> Tuple[float, float, float, float]: # tfac,area, - r"""The response routine of the HBV-96 model. - - The response routine is in charge of transforming the current values of - upper and lower zone into discharge. This routine also controls the - recharge of the lower zone tank (baseflow). The transformation of units - also occurs in this point. - - Parameters - ---------- - perc : [float] - Percolation value [mm\hr] - alpha : float - Response box parameter - k : float - Upper zone recession coefficient - k1 : float - Lower zone recession coefficient - lz_old : float - Previous lower zone value [mm] - uz_int_1 : float - Previous upper zone value before percolation [mm] + def response(perc, alpha, k, k1, lz_old, uz_int_1) -> tuple[float, float, float, float]: # type: ignore[override] # tfac,area, + r"""Transform upper and lower zone storage into discharge. + + The response routine converts the current values of the upper + and lower storage zones into discharge components. It also + controls the recharge of the lower zone tank (baseflow) via + percolation from the upper zone. + + Args: + perc (float): Percolation value [mm/hr]. + alpha (float): Response box parameter controlling the + non-linearity of the upper zone outflow. + k (float): Upper zone recession coefficient [h^-1]. + k1 (float): Lower zone recession coefficient [h^-1]. + lz_old (float): Previous lower zone value [mm]. + uz_int_1 (float): Previous upper zone value before + percolation [mm]. + + Returns: + tuple[float, float, float, float]: A tuple of + ``(q_0, q_1, uz_new, lz_new)`` where ``q_0`` is the + upper zone discharge [mm], ``q_1`` is the lower zone + discharge [mm], ``uz_new`` is the updated upper zone + storage [mm], and ``lz_new`` is the updated lower zone + storage [mm]. + + Examples: + Compute discharge from upper and lower zone storages: + + >>> from Hapi.rrm.hbv import HBV + >>> q_0, q_1, uz_new, lz_new = HBV.response( + ... perc=0.5, alpha=0.5, k=0.01, k1=0.001, + ... lz_old=20.0, uz_int_1=15.0, + ... ) + >>> print(f"q_upper={q_0:.4f}, q_lower={q_1:.4f}") + q_upper=0.0381, q_lower=0.0205 + >>> print(f"uz_new={uz_new:.4f}, lz_new={lz_new:.4f}") + uz_new=14.4619, lz_new=20.4795 """ # upper zone # if perc > Quz then perc = Quz and Quz = 0 if not perc = value and Quz= Quz-perc so take the min @@ -334,8 +424,30 @@ def response(perc, alpha, k, k1, lz_old, uz_int_1) -> Tuple[float, float, float, return q_0, q_1, uz_new, lz_new # ,uz_int_2, lz_int_1 @staticmethod - def tf(maxbas) -> Tuple[float]: - """Transfer function weight generator.""" + def tf(maxbas) -> np.ndarray: + """Generate transfer function weights using a triangular shape. + + Creates a set of weights for the triangular transfer function + used in discharge routing. The weights grow linearly up to the + midpoint of ``maxbas`` and then recede linearly, and are + normalized to sum to 1. + + Args: + maxbas (int): Number of time steps for the transfer + function base. Must be >= 1. + + Returns: + numpy.ndarray: Array of normalized weights with length + ``maxbas``. + + Examples: + >>> from Hapi.rrm.hbv import HBV + >>> weights = HBV.tf(5) + >>> print(weights.round(4)) + [0.1429 0.2857 0.2857 0.1429 0.1429] + >>> print(f"sum={weights.sum():.4f}") + sum=1.0000 + """ wi = [] for x in range(1, maxbas + 1): if x <= (maxbas) / 2.0: @@ -347,11 +459,38 @@ def tf(maxbas) -> Tuple[float]: # Normalise weights wi = np.array(wi) / np.sum(wi) - return wi + return wi # type: ignore[no-any-return] def routing(self, q, maxbas=1): - """Routing This function implements the transfer function using a triangular function.""" + """Route discharge through a triangular transfer function. + + Applies the triangular transfer function to the discharge time + series. The transfer function smooths the hydrograph by + distributing the discharge over ``maxbas`` time steps using + weights generated by :meth:`tf`. + + Args: + q (numpy.ndarray): Discharge time series [mm]. + maxbas (int): Number of time steps for the transfer + function base. Must be >= 1. Default is 1. + + Returns: + numpy.ndarray: Routed discharge time series with the same + length as ``q``. + + Raises: + AssertionError: If ``maxbas`` is less than 1. + + Examples: + >>> import numpy as np + >>> from Hapi.rrm.hbv import HBV + >>> model = HBV() + >>> q = np.array([0.0, 0.0, 5.0, 3.0, 1.0, 0.0]) + >>> q_routed = model.routing(q, maxbas=3) + >>> print(q_routed.round(4)) + [0. 0. 2.5 4. 2.5 0.5] + """ assert maxbas >= 1, "Maxbas value has to be larger than 1" # Get integer part of maxbas # maxbas = int(maxbas) @@ -370,31 +509,66 @@ def routing(self, q, maxbas=1): return q_r def step_run(self, p: np.ndarray, v: np.ndarray, state_variable: np.ndarray, snow=0): - """Step run. - - The function makes the calculation of next step of discharge and states. - - Parameters - ---------- - p : array_like [18] - Parameter vector, set up as: - [ltt, utt, ttm, cfmax, fc, ecorr, etf, lp, k, k1, - alpha, beta, cwh, cfr, c_flux, perc, rfcf, sfcf] - v : array_like [4] - Input vector setup as: - [prec, temp, evap, llt] - state_variable : array_like [5] - Previous model states setup as: - [sp, sm, uz, lz, wc] - snow: - 1 to run the snow subroutine and 0 for not. Default is 0. - - Returns - ------- - q_new : float - Discharge [m3/s] - St : array_like [5] - Posterior model states + """Execute a single time step of the HBV model. + + Parses the parameter vector, input vector, and state variables, + then sequentially runs the precipitation, snow, soil, and + response routines to compute discharge and updated states for + the next time step. + + Args: + p (numpy.ndarray): Parameter vector. When ``snow=1``, must + have 18 elements:: + + [ltt, utt, rfcf, sfcf, ttm, cfmax, cwh, cfr, + fc, beta, e_corr, etf, lp, c_flux, + k, k1, alpha, perc] + + When ``snow=0``, must have 10 elements:: + + [rfcf, fc, beta, etf, lp, c_flux, + k, k1, alpha, perc] + + v (numpy.ndarray): Input vector with 4 elements:: + + [prec, temp, evap, ll_temp] + + state_variable (numpy.ndarray): Previous model states with + 5 elements:: + + [sp, sm, uz, lz, wc] + + snow (int): Set to 1 to run the snow subroutine, 0 to + skip it. Default is 0. + + Returns: + tuple[float, float, list[float]]: A tuple of + ``(q_uz, q_lz, states)`` where ``q_uz`` is the upper + zone discharge [mm], ``q_lz`` is the lower zone + discharge [mm], and ``states`` is a list of five + updated state variables ``[sp, sm, uz, lz, wc]``. + + Raises: + AssertionError: If ``snow=1`` and the parameter vector + does not have 18 elements. + + Examples: + Run a single step without snow: + + >>> import numpy as np + >>> from Hapi.rrm.hbv import HBV + >>> model = HBV() + >>> par = np.array([ + ... 1.0, 200.0, 2.0, 0.1, 0.3, 0.01, + ... 0.005, 0.0005, 0.5, 0.5, + ... ]) + >>> inputs = np.array([10.0, 25.0, 3.0, 20.0]) + >>> states = np.array([0.0, 100.0, 10.0, 15.0, 0.0]) + >>> q_uz, q_lz, new_states = model.step_run( + ... par, inputs, states, snow=0, + ... ) + >>> print(f"q_uz={q_uz:.4f}, q_lz={q_lz:.4f}") + q_uz=0.0727, q_lz=0.0078 """ ## Parse of parameters from input vector to model # picipitation function @@ -441,7 +615,7 @@ def step_run(self, p: np.ndarray, v: np.ndarray, state_variable: np.ndarray, sno # soil function fc = p[1] beta = p[2] - e_corr = 1 # p[2] + e_corr = 1 # type: ignore[assignment] # p[2] etf = p[3] lp = p[4] c_flux = p[5] @@ -483,35 +657,72 @@ def step_run(self, p: np.ndarray, v: np.ndarray, state_variable: np.ndarray, sno # return q_new, [sp_new, sm_new, uz_new, lz_new, wc_new], uz_int_2, lz_int_1 return q_uz, q_lz, [sp_new, sm_new, uz_new, lz_new, wc_new] - def simulate(self, prec, temp, et, par, init_st=None, ll_temp=None, q_init=None, snow=0): - """Simulate Run the HBV model for the number of steps (n) in precipitation. The resluts are (n+1) simulation of discharge as the model calculates step n+1. - - Parameters - ---------- - prec : array_like [n] - Average precipitation [mm/h] - temp : array_like [n] - Average temperature [C] - et : array_like [n] - Potential Evapotranspiration [mm/h] - par : array_like [18] - Parameter vector, set up as: - [ltt, utt, ttm, cfmax, fc, ecorr, etf, lp, k, k1, - alpha, beta, cwh, cfr, c_flux, perc, rfcf, sfcf] - init_st : array_like [5], optional - Initial model states, [sp, sm, uz, lz, wc]. If unspecified, - [0.0, 30.0, 30.0, 30.0, 0.0] mm - ll_temp : array_like [n], optional - Long term average temptearature. If unspecified, calculated from temp. - q_init : float, optional - Initial discharge value. If unspecified set to 10.0 - - Returns - ------- - q_sim : array_like [n] - Discharge for the n time steps of the precipitation vector [m3/s] - st : array_like [n, 5] - Model states for the complete time series [mm] + def simulate(self, prec, temp, et, par, init_st=None, ll_temp=None, q_init=None, snow=0): # type: ignore[override] + """Run the HBV model for the full precipitation time series. + + Executes the HBV model for ``n`` time steps (the length of the + precipitation array). The results contain ``n+1`` values as + the model calculates discharge for step ``n+1`` from step + ``n`` inputs. + + Args: + prec (numpy.ndarray): Average precipitation time series + of length ``n`` [mm/h]. + temp (numpy.ndarray): Average temperature time series of + length ``n`` [C]. + et (numpy.ndarray): Potential evapotranspiration time + series of length ``n`` [mm/h]. + par (numpy.ndarray): Parameter vector. When ``snow=1``, + must have 18 elements; when ``snow=0``, must have + 10 elements. See :meth:`step_run` for the full + parameter layout. + init_st (list[float]): Initial model states as + ``[sp, sm, uz, lz, wc]`` [mm]. If None, defaults + to ``[0.0, 10.0, 10.0, 10.0, 0.0]``. + ll_temp (numpy.ndarray): Long-term average temperature + time series of length ``n`` [C]. If None, computed + as the mean of ``temp``. + q_init (float): Initial discharge value. If None, + computed from the initial states and parameters. + snow (int): Set to 1 to run the snow subroutine, 0 to + skip it. Default is 0. + + Returns: + tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]: + A tuple of ``(q_uz, q_lz, states)`` where: + + - ``q_uz``: Upper zone discharge array of length + ``n+1`` (float32). + - ``q_lz``: Lower zone discharge array of length + ``n+1`` (float32). + - ``states``: Model states array of shape + ``(n+1, 5)`` (float32). + + Raises: + AssertionError: If ``init_st`` does not have 5 elements + or if ``snow`` is not 0 or 1. + + Examples: + Run a short simulation without snow: + + >>> import numpy as np + >>> from Hapi.rrm.hbv import HBV + >>> model = HBV() + >>> par = np.array([ + ... 1.0, 200.0, 2.0, 0.1, 0.3, 0.01, + ... 0.005, 0.0005, 0.5, 0.5, + ... ]) + >>> n = 5 + >>> prec = np.array([10.0, 8.0, 12.0, 6.0, 4.0]) + >>> temp_arr = np.full(n, 25.0) + >>> et = np.full(n, 3.0) + >>> init_st = [0.0, 100.0, 10.0, 15.0, 0.0] + >>> q_uz, q_lz, states = model.simulate( + ... prec, temp_arr, et, par, + ... init_st=init_st, snow=0, + ... ) + >>> print(f"q_uz length={len(q_uz)}, first={q_uz[0]:.4f}") + q_uz length=6, first=0.0727 """ # data type assert ( @@ -545,7 +756,7 @@ def simulate(self, prec, temp, et, par, init_st=None, ll_temp=None, q_init=None, for i in range(len(prec)): v = [prec[i], temp[i], et[i], ll_temp[i]] # q_out, st_out, uz_int_2_out, lz_int_1_out = StepRun(par, p2, v, st[i], snow=0) - q_uzi, q_lzi, st_out = self.step_run(par, v, st[i], snow=0) # p2, + q_uzi, q_lzi, st_out = self.step_run(par, v, st[i], snow=0) # type: ignore[arg-type] # p2, # q_sim.append(q_out) q_uz.append(q_uzi) q_lz.append(q_lzi) @@ -553,4 +764,4 @@ def simulate(self, prec, temp, et, par, init_st=None, ll_temp=None, q_init=None, # uz_int_2.append(uz_int_2_out) # upper zone - perc # lz_int_1.append(lz_int_1_out) # lower zone + perc - return np.float32(q_uz), np.float32(q_lz), np.float32(st) + return np.float32(q_uz), np.float32(q_lz), np.float32(st) # type: ignore[arg-type] diff --git a/src/Hapi/rrm/hbv_bergestrom92.py b/src/Hapi/rrm/hbv_bergestrom92.py index cdf7c3896..1e043a398 100644 --- a/src/Hapi/rrm/hbv_bergestrom92.py +++ b/src/Hapi/rrm/hbv_bergestrom92.py @@ -1,29 +1,31 @@ -"""Lumped Conceptual HBV model. - -HBV is lumped conceptual model consists of precipitation, snow melt, -soil moisture and response subroutine to convert precipitation into o runoff, -where state variables are updated each time step to represent a specific -hydrologic behaviour the catchment - -This version was edited based on a Master Thesis on "Spatio-temporal simulation -of catchment response based on dynamic weighting of hydrological models" on april 2018 - -- Model inputs are Precipitation, evapotranspiration and temperature, initial - state variables, and initial discharge. -- Model output is Qalculated dicharge at time t+1 -- Model equations are solved using explicit scheme -- model structure uses 15 parameters if the catchment has snow - [tt, rfcf, sfcf, cfmax, cwh, cfr, fc, beta, e_corr, lp, c_flux, k, k1, - alpha, perc] - - otherwise it uses 10 parameters - [rfcf, fc, beta, etf, lp, c_flux, k, k1, alpha, perc] - -this HBV is based on Bergstrom, 1992 two reservoirs with three linear responses -surface runoff, interflow and baseflow +"""HBV Bergestrom 1992 Lumped Conceptual Hydrological Model. + +The ``Hapi.rrm.hbv_bergestrom92`` module implements the HBV-96 lumped +conceptual model based on Bergstrom (1992), using two reservoirs with +three linear responses: surface runoff, interflow, and baseflow. + +The HBV model consists of precipitation, snow melt, soil moisture, and +response subroutines that convert precipitation into runoff. State +variables are updated each time step to represent specific hydrologic +behaviour of the catchment. + +This version was edited based on a Master Thesis on +"Spatio-temporal simulation of catchment response based on dynamic +weighting of hydrological models" in April 2018. + +Model characteristics: + - Inputs: precipitation, evapotranspiration, temperature, initial + state variables, and initial discharge. + - Output: calculated discharge at time t+1. + - Equations are solved using an explicit scheme. + - Uses 15 parameters if the catchment has snow: + ``[tt, rfcf, sfcf, cfmax, cwh, cfr, fc, beta, e_corr, lp, + k, k1, k2, uzl, perc]`` + - Uses 10 parameters otherwise: + ``[rfcf, fc, beta, e_corr, lp, k, k1, k2, uzl, perc]`` """ +from __future__ import annotations -from typing import Tuple import numpy as np from Hapi.rrm.base_model import BaseConceptualModel @@ -33,34 +35,69 @@ class HBVBergestrom92(BaseConceptualModel): + """HBV Bergestrom 1992 lumped conceptual hydrological model. + + This class implements the HBV-96 model variant based on + Bergstrom (1992), featuring two groundwater reservoirs (upper and + lower zones) with three linear outflow equations for surface + runoff, interflow, and baseflow. + + The model inherits from + :class:`~Hapi.rrm.base_model.BaseConceptualModel` and implements + the ``precipitation``, ``snow``, ``soil``, ``response``, + ``routing``, and ``simulate`` methods. + + Examples: + >>> from Hapi.rrm.hbv_bergestrom92 import HBVBergestrom92 + >>> model = HBVBergestrom92() + """ def __init__(self): - """HBV model constructor.""" + """Initialize the HBVBergestrom92 model.""" pass @staticmethod - def precipitation(prec, temp, tt, rfcf, sfcf): - """Precipitaiton routine of the HBV96 model. If the temperature is lower than TT [degree C], all the precipitation is considered as snow. If the temperature is higher than tt, all the precipitation is considered as rainfall. - - Parameters - ---------- - temp : float - Measured temperature [C] - tt : float - Lower temperature treshold [C] - prec : float - Precipitation [mm] - rfcf : float - Rainfall corrector factor - sfcf : float - Snowfall corrector factor - - Returns - ------- - rf : float - Rainfall [mm] - sf : float - Snowfall [mm] + def precipitation(prec, temp, tt, rfcf, sfcf): # type: ignore[override] + """Partition precipitation into rainfall and snowfall. + + If the temperature is lower than or equal to the threshold + ``tt``, all precipitation is considered snowfall. If the + temperature is higher than ``tt``, all precipitation is + considered rainfall. Correction factors are applied to each + component. + + Args: + prec (float): Precipitation [mm]. + temp (float): Measured temperature [C]. + tt (float): Lower temperature threshold [C]. + rfcf (float): Rainfall correction factor [-]. + sfcf (float): Snowfall correction factor [-]. + + Returns: + tuple[float, float]: A tuple of ``(rf, sf)`` where: + - **rf** (*float*): Rainfall [mm]. + - **sf** (*float*): Snowfall [mm]. + + Examples: + >>> from Hapi.rrm.hbv_bergestrom92 import HBVBergestrom92 + >>> rf, sf = HBVBergestrom92.precipitation( + ... prec=10.0, temp=-2.0, tt=0.0, rfcf=1.0, sfcf=0.8 + ... ) + >>> rf + 0.0 + >>> sf + 8.0 + + When temperature exceeds the threshold, all precipitation + becomes rainfall: + + >>> rf, sf = HBVBergestrom92.precipitation( + ... prec=10.0, temp=5.0, tt=0.0, rfcf=1.0, sfcf=0.8 + ... ) + >>> rf + 10.0 + >>> sf + 0.0 """ # if temp <= lower temp threshold if temp <= tt: @@ -76,47 +113,58 @@ def precipitation(prec, temp, tt, rfcf, sfcf): return rf, sf @staticmethod - def snow(temp, rf, sf, wc_old, sp_old, tt, cfmax, cfr, cwh): - """Snow routine. The snow pack consists of two states: Water Content (wc) and Snow Pack (sp). The first corresponds to the liquid part of the water in the snow, while the latter corresponds to the solid part. If the temperature is higher than the melting point, the snow pack will melt and the solid snow will become liquid. In the opposite case, the liquid part of the snow will refreeze, and turn into solid. The water that cannot be stored by the solid part of the snow pack will drain into the soil as part of infiltration. - - All precipitation simulated to be snow, i.e., falling when the temperature - is below TT, is multiplied by a snowfall correction factor, SFCF [-]. - - Snowmelt is calculated with the degree-day method (cfmax). Meltwater and - rainfall is retained within the snowpack until it exceeds a certain - fraction, CWH [%] of the water equivalent of the snow - - Liquid water within the snowpack refreezes using cfr - - Parameters - ---------- - cfmax : float - Day degree factor - temp : float - Temperature [C] - tt : float - Temperature treshold for Melting [C] - cfr : float - Refreezing factor - cwh : float - Capacity for water holding in snow pack [%] - rf : float - Rainfall [mm] - sf : float - Snowfall [mm] - wc_old : float - Water content in previous state [mm] - sp_old : float - snow pack in previous state [mm] - - Returns - ------- - in : float - Infiltration [mm] - wc_new : float - Liquid Water content in the snow[mm] - sp_new : float - Snowpack in posterior state [mm] + def snow(temp, rf, sf, wc_old, sp_old, tt, cfmax, cfr, cwh): # type: ignore[override] + """Compute snow accumulation, melt, and infiltration. + + The snow pack consists of two states: water content (``wc``) + and snow pack (``sp``). The water content corresponds to the + liquid part of the water in the snow, while the snow pack + corresponds to the solid part. + + If the temperature is higher than the melting point, the snow + pack will melt and the solid snow will become liquid. In the + opposite case, the liquid part of the snow will refreeze and + turn into solid. The water that cannot be stored by the solid + part of the snow pack will drain into the soil as + infiltration. + + Snowmelt is calculated with the degree-day method using + ``cfmax``. Meltwater and rainfall are retained within the + snowpack until they exceed the fraction ``cwh`` of the water + equivalent of the snow. Liquid water within the snowpack + refreezes using ``cfr``. + + Args: + temp (float): Temperature [C]. + rf (float): Rainfall [mm]. + sf (float): Snowfall [mm]. + wc_old (float): Water content in previous state [mm]. + sp_old (float): Snow pack in previous state [mm]. + tt (float): Temperature threshold for melting [C]. + cfmax (float): Day degree factor [mm/C/timestep]. + cfr (float): Refreezing factor [-]. + cwh (float): Capacity for water holding in snow pack + as a fraction [-]. + + Returns: + tuple[float, float, float]: A tuple of + ``(inf, wc_new, sp_new)`` where: + - **inf** (*float*): Infiltration into the soil [mm]. + - **wc_new** (*float*): New liquid water content in + the snow [mm]. + - **sp_new** (*float*): New snow pack state [mm]. + + Examples: + >>> from Hapi.rrm.hbv_bergestrom92 import HBVBergestrom92 + >>> inf, wc_new, sp_new = HBVBergestrom92.snow( + ... temp=5.0, rf=3.0, sf=0.0, wc_old=2.0, + ... sp_old=10.0, tt=0.0, cfmax=3.0, cfr=0.05, + ... cwh=0.1, + ... ) + >>> sp_new + 0.0 + >>> inf > 0 + True """ # if temp > melting threshold if temp > tt: @@ -158,45 +206,51 @@ def snow(temp, rf, sf, wc_old, sp_old, tt, cfmax, cfr, cwh): return inf, wc_new, sp_new @staticmethod - def soil(temp, inf, ep, sm_old, uz_old, tm, fc, beta, e_corr, lp): - """Soil routine of the HBV-96 model. The model checks for the amount of water that can infiltrate the soil, coming from the liquid precipitation and the snow pack melting. A part of the water will be stored as soil moisture, while other will become runoff, and routed to the upper zone tank. - - - Actual evaporation from the soil box equals the potential evaporation if SM/FC is above LP [%] while a - linear reduction is used when SM/FC is below LP. - - Groundwater recharge (r) is added to the upper groundwater box (UZ [mm]) - - PERC [mm d-1]defines the maximum percolation rate from the upper to the lower groundwater box (LZ [mm]) - - Parameters - ---------- - fc : float - Filed capacity - beta : float - Shape coefficient for effective precipitation separation - etf : float - Total potential evapotranspiration - temp : float - Temperature - tm : float - Average long term temperature - e_corr : float - Evapotranspiration corrector factor - lp : float _soil - wilting point - _in : float - actual infiltration - ep : float - actual evapotranspiration - sm_old : float - Previous soil moisture value - uz_old : float - Previous Upper zone value - - Returns - ------- - sm_new : float - New value of soil moisture - uz_int_1 : float - New value of direct runoff into upper zone + def soil(temp, inf, ep, sm_old, uz_old, tm, fc, beta, e_corr, lp): # type: ignore[override] + """Compute soil moisture balance and upper zone recharge. + + The model checks the amount of water that can infiltrate the + soil from liquid precipitation and snow pack melting. A part + of the water is stored as soil moisture, while the rest + becomes runoff routed to the upper zone tank. + + Actual evaporation from the soil box equals the potential + evaporation if ``SM/FC`` is above ``LP``, while a linear + reduction is used when ``SM/FC`` is below ``LP``. + Groundwater recharge is added to the upper groundwater box. + + Args: + temp (float): Temperature [C]. + inf (float): Actual infiltration [mm]. + ep (float): Potential evapotranspiration [mm]. + sm_old (float): Previous soil moisture value [mm]. + uz_old (float): Previous upper zone value [mm]. + tm (float): Average long term temperature [C]. + fc (float): Field capacity [mm]. + beta (float): Shape coefficient for effective + precipitation separation [-]. + e_corr (float): Evapotranspiration correction factor [-]. + lp (float): Wilting point as a fraction of field + capacity [-]. + + Returns: + tuple[float, float]: A tuple of + ``(sm_new, uz_int_1)`` where: + - **sm_new** (*float*): New soil moisture value [mm]. + - **uz_int_1** (*float*): New value of direct runoff + into the upper zone [mm]. + + Examples: + >>> from Hapi.rrm.hbv_bergestrom92 import HBVBergestrom92 + >>> sm_new, uz_int_1 = HBVBergestrom92.soil( + ... temp=20.0, inf=5.0, ep=3.0, sm_old=50.0, + ... uz_old=10.0, tm=18.0, fc=200.0, beta=2.0, + ... e_corr=1.0, lp=0.9, + ... ) + >>> sm_new > 0 + True + >>> uz_int_1 > uz_old + True """ # recharge to the upper zone r = ((sm_old / fc) ** beta) * inf @@ -228,43 +282,56 @@ def soil(temp, inf, ep, sm_old, uz_old, tm, fc, beta, e_corr, lp): return sm_new, uz_int_1 @staticmethod - def response(lz_old, uz_int_1, perc, k, k1, k2, uzl): - """Response. - - The response routine of the HBV-96 model. - The response routine is in charge of transforming the current values of - upper and lower zone into discharge. This routine also controls the - recharge of the lower zone tank (baseflow). The transformation of units - also occurs in this point. - - PERC [mm d-1]defines the maximum percolation rate from the upper to the - lower groundwater box (SLZ [mm]) - - Runoff from the groundwater boxes is computed as the sum of two or three - linear outflow equatins depending on whether UZ is above a threshold value, - UZL [mm], or not - - Parameters - ---------- - perc : float - Percolation value [mm/hr] - k : float - direct runoff recession coefficient - k1 : float - Upper zone recession coefficient - k2 : float - Lower zone recession coefficient - uzl: float - Upper zone threshold value - lz_old : float - Previous lower zone value [mm] - uz_int_1 : float - Previous upper zone value before percolation [mm] - - Returns - ------- - q_uz:[float] - upper zone discharge (mm/timestep) for both surface runoff & interflow - q_2:[float] - Lower zone discharge (mm/timestep). + def response(lz_old, uz_int_1, perc, k, k1, k2, uzl): # type: ignore[override] + """Compute the runoff response from upper and lower zones. + + The response routine transforms the current values of upper + and lower zone storages into discharge. It also controls the + recharge of the lower zone tank (baseflow). + + ``perc`` defines the maximum percolation rate from the upper + to the lower groundwater box. Runoff from the groundwater + boxes is computed as the sum of two or three linear outflow + equations depending on whether the upper zone storage is + above the threshold value ``uzl``. + + Args: + lz_old (float): Previous lower zone value [mm]. + uz_int_1 (float): Previous upper zone value before + percolation [mm]. + perc (float): Percolation value [mm/timestep]. + k (float): Direct runoff (surface) recession + coefficient [-]. + k1 (float): Upper zone (interflow) recession + coefficient [-]. + k2 (float): Lower zone (baseflow) recession + coefficient [-]. + uzl (float): Upper zone threshold value [mm]. + + Returns: + tuple[float, float, float, float]: A tuple of + ``(q_uz, q_lz, uz_new, lz_new)`` where: + - **q_uz** (*float*): Upper zone discharge + (surface runoff + interflow) [mm/timestep]. + - **q_lz** (*float*): Lower zone discharge + (baseflow) [mm/timestep]. + - **uz_new** (*float*): New upper zone storage [mm]. + - **lz_new** (*float*): New lower zone storage [mm]. + + Examples: + >>> from Hapi.rrm.hbv_bergestrom92 import HBVBergestrom92 + >>> q_uz, q_lz, uz_new, lz_new = HBVBergestrom92.response( + ... lz_old=30.0, uz_int_1=20.0, perc=1.0, k=0.005, + ... k1=0.03, k2=0.015, uzl=10.0, + ... ) + >>> q_uz > 0 + True + >>> q_lz > 0 + True + >>> uz_new >= 0 + True + >>> lz_new >= 0 + True """ # upper zone # if perc > Quz then perc = Quz and Quz = 0 if not perc = value and Quz= Quz-perc so take the min @@ -300,7 +367,30 @@ def response(lz_old, uz_int_1, perc, k, k1, k2, uzl): @staticmethod def tf(maxbas): - """Transfer function weight generator.""" + """Generate transfer function weights for triangular routing. + + Computes a set of normalized weights based on a triangular + transfer function. The weights grow linearly for the first + half of the ``maxbas`` interval and recede linearly for the + second half. + + Args: + maxbas (int): Number of time steps for the triangular + transfer function. + + Returns: + numpy.ndarray: Normalized weights for the transfer + function, summing to 1.0. + + Examples: + >>> from Hapi.rrm.hbv_bergestrom92 import HBVBergestrom92 + >>> import numpy as np + >>> w = HBVBergestrom92.tf(3) + >>> np.isclose(w.sum(), 1.0) + True + >>> len(w) + 3 + """ wi = [] for x in range(1, maxbas + 1): if x <= maxbas / 2.0: @@ -315,16 +405,32 @@ def tf(maxbas): return wi def routing(self, q, maxbas=1): - """Routing. - - This function implements the transfer function using a triangular function. - - Parameters - ---------- - q: [array] - discharge array - maxbas: [int] - maxbas parameter value. + """Apply triangular transfer function routing to discharge. + + Routes the discharge signal through a triangular transfer + function defined by the ``maxbas`` parameter. The transfer + function weights are generated by :meth:`tf`. + + Args: + q (numpy.ndarray): Discharge array [mm/timestep]. + maxbas (int): Transfer function length in time steps. + Must be >= 1. Defaults to 1. + + Returns: + numpy.ndarray: Routed discharge array with the same + shape as ``q``. + + Raises: + AssertionError: If ``maxbas`` is less than 1. + + Examples: + >>> from Hapi.rrm.hbv_bergestrom92 import HBVBergestrom92 + >>> import numpy as np + >>> model = HBVBergestrom92() + >>> q = np.array([0.0, 1.0, 2.0, 3.0, 2.0, 1.0]) + >>> q_r = model.routing(q, maxbas=1) + >>> len(q_r) == len(q) + True """ assert maxbas >= 1, "Maxbas value has to be larger than 1" # Get integer part of maxbas @@ -344,44 +450,71 @@ def routing(self, q, maxbas=1): def simulate( self, prec, temp, et, ll_temp, par, init_st=None, q_init=None, snow=0 - ) -> Tuple[np.ndarray, np.ndarray, np.ndarray]: - """Simulate. - - Run the HBV model for the number of steps (n) in precipitation. - The results are (n+1) simulation of discharge as the model calculates the step n+1. - - - Parameters - ---------- - prec : array_like [n] - Average precipitation [mm/h] - temp : array_like [n] - Average temperature [C] - et : array_like [n] - Potential Evapotranspiration [mm/h] - par : array_like [18] - Parameter vector, set up as: - [ltt, utt, ttm, cfmax, fc, ecorr, etf, lp, k, k1, - alpha, beta, cwh, cfr, c_flux, perc, rfcf, sfcf] - init_st : array_like [5], optional - Initial model states, [sp, sm, uz, lz, wc]. - If unspecified, - [0.0, 30.0, 30.0, 30.0, 0.0] mm - ll_temp : array_like [n], optional - Long term average temptearature. - If unspecified, calculated from temp. - q_init : float, optional - Initial discharge value. - If unspecified set to 10.0 - snow: [int] - 1 if snow is considered 0 if not. - - Returns - ------- - q_sim : array_like [n] - Discharge for the n time steps of the precipitation vector [m3/s] - st : array_like [n, 5] - Model states for the complete time series [mm] + ) -> tuple[np.ndarray, np.ndarray, np.ndarray]: + """Run the HBV Bergestrom92 model simulation. + + Executes the HBV model for the number of time steps in the + precipitation input. The model sequentially calls the + precipitation, snow, soil, and response routines at each + time step, updating state variables accordingly. + + Args: + prec (array_like): Average precipitation [mm/timestep], + array of length ``n``. + temp (array_like): Average temperature [C], array of + length ``n``. + et (array_like): Potential evapotranspiration + [mm/timestep], array of length ``n``. + ll_temp (array_like): Long term average temperature [C], + array of length ``n``. + par (array_like): Parameter vector. When ``snow=1``, + expects 15 parameters: + ``[tt, rfcf, sfcf, cfmax, cwh, cfr, fc, beta, + e_corr, lp, k, k1, k2, uzl, perc]``. + When ``snow=0``, expects 10 parameters: + ``[rfcf, fc, beta, e_corr, lp, k, k1, k2, uzl, + perc]``. + init_st (array_like, optional): Initial model states + ``[sp, sm, uz, lz, wc]`` in mm. Defaults to + ``[0.0, 10.0, 10.0, 10.0, 0.0]``. + q_init (float, optional): Initial discharge value. If + not specified, it is computed from initial states + and parameters. + snow (int): Flag indicating whether snow processes are + active. Use ``1`` for snow, ``0`` for no snow. + Defaults to 0. + + Returns: + tuple[numpy.ndarray, numpy.ndarray, numpy.ndarray]: + A tuple of ``(q_uz, q_lz, st)`` where: + - **q_uz** (*numpy.ndarray*): Upper zone discharge + (surface runoff + interflow) for ``n+1`` time + steps [mm/timestep]. + - **q_lz** (*numpy.ndarray*): Lower zone discharge + (baseflow) for ``n+1`` time steps [mm/timestep]. + - **st** (*numpy.ndarray*): Model states array of + shape ``(n+1, 5)`` with columns + ``[sp, sm, uz, lz, wc]`` in mm. + + Examples: + >>> from Hapi.rrm.hbv_bergestrom92 import HBVBergestrom92 + >>> import numpy as np + >>> np.random.seed(42) + >>> model = HBVBergestrom92() + >>> n = 10 + >>> prec = np.random.uniform(0, 20, n) + >>> temp = np.random.uniform(15, 30, n) + >>> et = np.random.uniform(0, 5, n) + >>> ll_temp = np.full(n, 20.0) + >>> par = [1.0, 200.0, 2.0, 1.0, 0.9, 0.005, 0.03, + ... 0.015, 10.0, 1.0] + >>> q_uz, q_lz, st = model.simulate( + ... prec, temp, et, ll_temp, par, snow=0, + ... ) + >>> q_uz.shape == (n + 1,) + True + >>> st.shape == (n + 1, 5) + True """ st = np.zeros([len(prec) + 1, 5], dtype=np.float32) q_0 = np.zeros([len(prec) + 1], dtype=np.float32) diff --git a/src/Hapi/rrm/hbv_lake.py b/src/Hapi/rrm/hbv_lake.py index b6ef7d204..f241fbfb6 100644 --- a/src/Hapi/rrm/hbv_lake.py +++ b/src/Hapi/rrm/hbv_lake.py @@ -1,6 +1,21 @@ -"""HBV model with a lake function.""" +"""HBV model with a lake function. -from __future__ import division, print_function +The ``Hapi.rrm.hbv_lake`` module provides the ``HBVLake`` class, an +extension of the HBV-96 rainfall-runoff model that includes an explicit +lake representation. Inflow to the lake is computed using the standard +HBV precipitation, snow, soil, and response routines. Lake outflow is +then derived from a storage-discharge rating curve using interpolation. + +Classes: + HBVLake: HBV-96 model variant with an explicit lake sub-model. + +Module-level Constants: + DEF_ST: Default initial state variables + ``[sp, sm, uz, lz, wc, lake_volume]``. + DEF_q0: Default initial discharge value (2.3 m3/s). +""" + +from __future__ import annotations import numpy as np from scipy.interpolate import InterpolatedUnivariateSpline as interp11 @@ -15,40 +30,82 @@ class HBVLake(BaseConceptualModel): + """HBV-96 model variant with an explicit lake sub-model. + + ``HBVLake`` extends the standard HBV-96 rainfall-runoff model by + adding a lake component that transforms the catchment outflow using + a storage-discharge rating curve. The lake receives inflow from the + HBV response routine, accounts for precipitation on the lake surface + and lake evaporation, and computes outflow via interpolation on the + supplied rating curve. + + The model pipeline per time step is: + + precipitation -> snow -> soil -> response -> lake -> outflow + + Inherits from: + BaseConceptualModel + + Examples: + >>> import numpy as np + >>> from Hapi.rrm.hbv_lake import HBVLake, DEF_ST, DEF_q0 + >>> model = HBVLake() + >>> n = 10 + >>> prec = np.array([5.0] * n) + >>> temp = np.array([25.0] * n) + >>> et = np.array([2.0] * n) + >>> par = [200, 2.0, 0.01, 0.6, 0.5, 0.01, 0.05, 1.0, 1.0, + ... 1.0, 1.0] + >>> p2 = [24, 100.0, 10.0] + >>> curve = np.column_stack([ + ... np.linspace(0, 100, 50), + ... np.linspace(0, 1e10, 50), + ... ]) + >>> q_sim, states = model.simulate( + ... prec, temp, et, par, p2, curve, + ... q_0=DEF_q0, init_st=DEF_ST, + ... ) + >>> len(q_sim) == n + 1 + True + """ @staticmethod def _precipitation(temp, ltt, utt, prec, rfcf, sfcf, tfac, pcorr): - """Precipitation. - - Precipitaiton routine of the HBV96 model. - If temperature is lower than ltt, all the precipitation is considered as - snow. If the temperature is higher than utt, all the precipitation is - considered as rainfall. In case that the temperature is between ltt and - utt, precipitation is a linear mix of rainfall and snowfall. - - inputs (precipitation, Temperature) - outputs: (rainfall, snowfall) - - Parameters - ---------- - temp : float - Measured temperature [C] - ltt : float - Lower temperature treshold [C] - utt : float - Upper temperature treshold [C] - prec : float - Precipitation [mm] - rfcf : float - Rainfall corrector factor - sfcf : float - Snowfall corrector factor - Returns - ------- - _rf : float - Rainfall [mm] - _sf : float - Snowfall [mm] + """Separate precipitation into rainfall and snowfall. + + If the temperature is at or below ``ltt``, all precipitation is + snowfall. If the temperature is at or above ``utt``, all + precipitation is rainfall. Between the two thresholds, + precipitation is linearly partitioned. + + Args: + temp (float): Measured temperature [C]. + ltt (float): Lower temperature threshold [C]. + utt (float): Upper temperature threshold [C]. + prec (float): Precipitation [mm]. + rfcf (float): Rainfall correction factor. + sfcf (float): Snowfall correction factor. + tfac (float): Temperature correction factor (unused in + this routine but kept for interface consistency). + pcorr (float): Precipitation correction factor applied to + rainfall. + + Returns: + tuple[float, float]: + - Rainfall [mm]. + - Snowfall [mm]. + + Examples: + >>> HBVLake._precipitation( + ... temp=0.0, ltt=1.0, utt=2.0, prec=10.0, + ... rfcf=1.0, sfcf=1.0, tfac=1.0, pcorr=1.0, + ... ) + (0.0, 10.0) + >>> HBVLake._precipitation( + ... temp=3.0, ltt=1.0, utt=2.0, prec=10.0, + ... rfcf=1.0, sfcf=1.0, tfac=1.0, pcorr=1.0, + ... ) + (10.0, 0.0) """ # if temp <= lower temp threshold if temp <= ltt: @@ -71,50 +128,41 @@ def _precipitation(temp, ltt, utt, prec, rfcf, sfcf, tfac, pcorr): @staticmethod def _snow(cfmax, tfac, temp, ttm, cfr, cwh, _rf, _sf, wc_old, sp_old): - """Snow. - - Snow routine of the HBV-96 model. - - The snow pack consists of two states: Water Content (wc) and Snow Pack - (sp). The first corresponds to the liquid part of the water in the snow, - while the latter corresponds to the solid part. If the temperature is - higher than the melting point, the snow pack will melt and the solid snow - will become liquid. In the opposite case, the liquid part of the snow will - refreeze, and turn into solid. The water that cannot be stored by the solid - part of the snow pack will drain into the soil as part of infiltration. - - Parameters - ---------- - cfmax : float - Day degree factor # melting factor - tfac : float - Temperature correction factor - temp : float - Temperature [C] - ttm : float - Temperature treshold for Melting [C] - cfr : float - Refreezing factor - cwh : float - Capacity for water holding in snow pack - water-holding capacity of snow (meltwater is retained in snowpack until it exceeds the WHC) - _rf : float - Rainfall [mm] - _sf : float - Snowfall [mm] - wc_old : float - Water content in previous state [mm] - sp_old : float - snow pack in previous state [mm] - - Returns - ------- - _in : float - Infiltration [mm] - _wc_new : float - Water content in posterior state [mm] - _sp_new : float - Snowpack in posterior state [mm] + """Compute snow accumulation, melt, and infiltration. + + The snow pack has two states: solid snow pack (``sp``) and + liquid water content (``wc``). When the temperature exceeds + the melting threshold, snow melts and the solid portion + decreases. When the temperature is below the threshold, the + liquid water refreezes. Excess water that exceeds the holding + capacity of the snow pack becomes infiltration. + + Args: + cfmax (float): Day-degree melting factor [mm/C/day]. + tfac (float): Temperature correction factor. + temp (float): Temperature [C]. + ttm (float): Temperature threshold for melting [C]. + cfr (float): Refreezing factor. + cwh (float): Water-holding capacity of snow pack (fraction + of solid snow pack). + _rf (float): Rainfall [mm]. + _sf (float): Snowfall [mm]. + wc_old (float): Water content in the previous state [mm]. + sp_old (float): Snow pack in the previous state [mm]. + + Returns: + tuple[float, float, float]: + - Infiltration [mm]. + - Updated water content [mm]. + - Updated snow pack [mm]. + + Examples: + >>> HBVLake._snow( + ... cfmax=4.0, tfac=1.0, temp=5.0, ttm=0.0, + ... cfr=0.05, cwh=0.1, _rf=2.0, _sf=0.0, + ... wc_old=0.0, sp_old=10.0, + ... ) + (2.0, 1.0, 0.0) """ # if temp > melting threshold if temp > ttm: @@ -161,54 +209,51 @@ def _snow(cfmax, tfac, temp, ttm, cfr, cwh, _rf, _sf, wc_old, sp_old): def _soil( fc, beta, etf, temp, tm, e_corr, lp, tfac, c_flux, inf, ep, sm_old, uz_old ): - """Soil. Soil routine of the HBV-96 model. - - The model checks for the amount of water that can infiltrate the soil, - coming from the liquid precipitation and the snow pack melting. A part of - the water will be stored as soil moisture, while other will become runoff, - and routed to the upper zone tank. - - Parameters - ---------- - fc : float - Filed capacity - beta : float - Shape coefficient for effective precipitation separation - Controls the contribution of the increase in the soil moisture or - to the response function - etf : float - Total potential evapotranspiration - temp : float - Temperature - tm : float - Average long term temperature - e_corr : float - Evapotranspiration corrector factor - lp : float _soil - - tfac : float - Time conversion factor - c_flux : float - Capilar flux in the root zone - _in : float - actual infiltration - ep : float - Long term mean potential evapotranspiration - sm_old : float - Previous soil moisture value - uz_old : float - Previous Upper zone value - Returns - ------- - sm_new : float - New value of soil moisture - uz_int_1 : float - New value of direct runoff into upper zone - - variables - ------- - EPInt : - Adjusted potential evapotranspiration + """Compute soil moisture balance and recharge to the upper zone. + + This routine determines how much infiltrating water is stored as + soil moisture and how much becomes runoff routed to the upper + zone. Actual evapotranspiration is computed from the adjusted + potential evapotranspiration, and capillary rise from the upper + zone is also accounted for. + + Args: + fc (float): Field capacity [mm]. + beta (float): Shape coefficient controlling the split + between soil moisture increase and upper zone recharge. + etf (float): Evapotranspiration temperature adjustment + factor. + temp (float): Temperature [C]. + tm (float): Long-term average temperature [C]. + e_corr (float): Evapotranspiration correction factor. + lp (float): Fraction of field capacity above which actual + ET equals potential ET. + tfac (float): Time conversion factor. + c_flux (float): Maximum capillary flux from upper zone to + root zone [mm]. + inf (float): Actual infiltration [mm]. + ep (float): Long-term mean potential evapotranspiration + [mm]. + sm_old (float): Previous soil moisture value [mm]. + uz_old (float): Previous upper zone value [mm]. + + Returns: + tuple[float, float, float]: + - Updated soil moisture [mm]. + - Upper zone inflow after capillary rise [mm]. + - Direct runoff [mm] (always 0 in this + implementation). + + Examples: + >>> sm, uz, qdr = HBVLake._soil( + ... fc=200, beta=2.0, etf=0.01, temp=25.0, tm=20.0, + ... e_corr=1.0, lp=0.6, tfac=24, c_flux=0.5, inf=5.0, + ... ep=2.0, sm_old=100.0, uz_old=10.0, + ... ) + >>> sm > 0 + True + >>> qdr + 0 """ # qdr = max(sm_old + inf - fc, 0) # direct run off as soil moisture exceeded the field capacity @@ -242,37 +287,39 @@ def _soil( @staticmethod def _response(tfac, perc, alpha, k, k1, area, lz_old, uz_int_1, qdr): - r"""Response. - - The response routine of the HBV-96 model. - The response routine is in charge of transforming the current values of - upper and lower zone into discharge. This routine also controls the - recharge of the lower zone tank (baseflow). The transformation of units - also occurs in this point. - - Parameters - ---------- - tfac : float - Number of hours in the time step - perc : float - Percolation value [mm\hr] - alpha : float - Response box parameter - upper zone runoff coefficient - k : float - Upper zone recession coefficient - Upper zone response coefficient - k1 : float - Lower zone recession coefficient - Lowe zone response coefficient - area : float - Catchment area [Km2] - lz_old : float - Previous lower zone value [mm] - uz_int_1 : float - Previous upper zone value before percolation [mm] - qdr : float - Direct runoff [mm] + r"""Transform upper and lower zone storage into discharge. + + The response routine percolates water from the upper zone to the + lower zone, computes non-linear upper zone discharge and linear + lower zone (baseflow) discharge, and converts the total from mm + to m3/s. + + Args: + tfac (float): Number of hours in the time step. + perc (float): Maximum percolation rate [mm/hr]. + alpha (float): Non-linearity exponent for upper zone + response. + k (float): Upper zone recession coefficient. + k1 (float): Lower zone recession coefficient. + area (float): Catchment area [km2]. + lz_old (float): Previous lower zone value [mm]. + uz_int_1 (float): Upper zone value before percolation [mm]. + qdr (float): Direct runoff [mm]. + + Returns: + tuple[float, float, float]: + - Discharge [m3/s]. + - Updated upper zone storage [mm]. + - Updated lower zone storage [mm]. + + Examples: + >>> q, uz, lz = HBVLake._response( + ... tfac=24, perc=1.0, alpha=1.0, k=0.05, + ... k1=0.01, area=100.0, lz_old=10.0, + ... uz_int_1=15.0, qdr=0.0, + ... ) + >>> q > 0 + True """ # upper zone # if perc > Quz then perc = Quz and Quz = 0 if not perc = value and Quz= Quz-perc so take the min @@ -303,29 +350,52 @@ def _response(tfac, perc, alpha, k, k1, area, lz_old, uz_int_1, qdr): @staticmethod def _lake(temp, curve, tfac, rf, sf, q_new, lv_old, ltt, c_le, ep, lakeA): - """lake. lake model. - - Parameters - ---------- - temp - curve - tfac - rf - sf - q_new - lv_old - ltt - c_le - ep - lakeA + """Compute lake outflow and updated lake volume. + + The lake receives inflow from the catchment response routine, + gains water from precipitation on the lake surface, and loses + water through evaporation. Outflow is determined by + interpolating the storage-discharge rating curve at the average + storage level. + + Args: + temp (float): Temperature [C]. + curve (numpy.ndarray): Two-column array where column 0 is + discharge [m3/s] and column 1 is storage [m3]. + tfac (float): Number of hours in the time step. + rf (float): Rainfall [mm]. + sf (float): Snowfall [mm]. + q_new (float): Inflow discharge from the catchment [m3/s]. + lv_old (float): Previous lake volume [m3]. + ltt (float): Lower temperature threshold [C] controlling + whether precipitation falls as rain or snow on the + lake. + c_le (float): Lake evaporation correction factor. + ep (float): Potential evapotranspiration [mm]. + lakeA (float): Lake surface area [km2]. + + Returns: + tuple[float, float]: + - Lake outflow discharge [m3/s]. + - Updated lake volume [m3]. + + Examples: + >>> import numpy as np + >>> curve = np.column_stack([ + ... np.linspace(0, 100, 50), + ... np.linspace(0, 1e10, 50), + ... ]) + >>> qout, lv = HBVLake._lake( + ... temp=25.0, curve=curve, tfac=24, + ... rf=5.0, sf=0.0, q_new=10.0, + ... lv_old=5e9, ltt=1.0, c_le=1.0, + ... ep=2.0, lakeA=10.0, + ... ) + >>> qout >= 0 + True """ # lower zone # explicit representation of the lake where lake will be represented by a rating curve - """ - l_ea :lake evaporation - c_le : lake _evaporation correction factor - - """ # lake evaporation if temp >= ltt: l_ea = ( @@ -362,9 +432,26 @@ def _lake(temp, curve, tfac, rf, sf, q_new, lv_old, ltt, c_le, ep, lakeA): @staticmethod def _tf(maxbas): - """tf. - - Transfer function weight generator in a shape of a triangle. + """Generate triangular transfer function weights. + + Produces a normalised weight array with a triangular shape. + The first half of the weights form the rising limb and the + second half form the falling limb. + + Args: + maxbas (int): Number of time steps over which to distribute + the transfer function. + + Returns: + numpy.ndarray: Normalised weights summing to 1.0. + + Examples: + >>> import numpy as np + >>> w = HBVLake._tf(3) + >>> abs(w.sum() - 1.0) < 1e-10 + True + >>> len(w) + 3 """ wi = [] @@ -383,10 +470,30 @@ def _tf(maxbas): return wi def _routing(self, q, maxbas=1): - """routing. - - This function implements the transfer function using a triangular - function. + """Route a discharge time series using a triangular function. + + Convolves the input discharge array with triangular weights + generated by ``_tf`` to smooth the hydrograph. + + Args: + q (numpy.ndarray): Input discharge time series. + maxbas (int): Number of time steps for the triangular + transfer function. Must be >= 1. + + Returns: + numpy.ndarray: Routed discharge time series with the same + length as ``q``. + + Raises: + AssertionError: If ``maxbas`` < 1. + + Examples: + >>> import numpy as np + >>> model = HBVLake() + >>> q = np.array([0.0, 1.0, 2.0, 3.0, 2.0, 1.0, 0.0]) + >>> q_r = model._routing(q, maxbas=3) + >>> len(q_r) == len(q) + True """ assert maxbas >= 1, "Maxbas value has to be larger than 1" # Get integer part of maxbas @@ -406,27 +513,28 @@ def _routing(self, q, maxbas=1): @staticmethod def calculate_max_bas(maxbas): - """calculate_max_bas. - - The calculate_max_bas function calculate the maxbas Weights based on a MAXBAX number The maxbas is a HBV - parameter that controls the routing. - - Parameters - ---------- - maxbas: [int] - maxbas weight - - Example - ------- - maxbasW = calculate_max_bas(5) - maxbasW = - - 0.0800 0.2400 0.3600 0.2400 0.0800 - - Notes - ----- - - It is important to mention that this function allows one to obtain weights not only for interger values but - from decimal values as well. + """Compute MAXBAS routing weights using an equilateral triangle. + + Generates routing weights based on an equilateral triangle whose + base length equals ``maxbas``. Unlike ``_tf``, this method + supports non-integer values of ``maxbas``. + + Args: + maxbas (float): MAXBAS parameter controlling the routing + duration. Can be a non-integer value. + + Returns: + numpy.ndarray: Array of routing weights. The length is + ``floor(maxbas)`` for integer values or + ``floor(maxbas) + 1`` for non-integer values. + + Examples: + >>> import numpy as np + >>> w = HBVLake.calculate_max_bas(5) + >>> len(w) + 5 + >>> abs(w.sum() - 1.0) < 0.01 + True """ yant = 0 total = 0 # Just to verify how far from the unit is the result @@ -495,17 +603,28 @@ def calculate_max_bas(maxbas): return maxbas_w def routing_maxbas(self, q, maxbas): - """routing_maxbas. - - This function calculate the routing from a input hydrograph using the maxbas parameter from the HBV model. - - Parameters - ---------- - q: [array] - input hydrograph - maxbas: [] - maxbas weight - Qout = output hydrograph + """Route a hydrograph using MAXBAS equilateral-triangle weights. + + Computes routing by convolving the input hydrograph with weights + derived from ``calculate_max_bas``. This method supports + non-integer ``maxbas`` values. + + Args: + q (numpy.ndarray): Input hydrograph discharge array. + maxbas (float): MAXBAS parameter controlling the routing + duration. + + Returns: + numpy.ndarray: Routed output hydrograph with shape + ``(len(q), 1)``. + + Examples: + >>> import numpy as np + >>> model = HBVLake() + >>> q = np.array([0, 1, 3, 5, 3, 1, 0], dtype=float) + >>> qout = model.routing_maxbas(q, maxbas=3) + >>> qout.shape == (7, 1) + True """ # CALCULATE maxbas WEIGHTS maxbas_w = self.calculate_max_bas(maxbas) @@ -546,30 +665,54 @@ def mm(A, s): return qout def _step_run(self, parameters, catchment_parameters, v, state_variables, curve): - """Step run. Makes the calculation of next step of discharge and states. - - Parameters - ---------- - parameters : array_like [10] - Parameter vector, set up as: - [fc, beta, e_corr, etf, lp, c_flux, k, k1, alpha, perc] - 9 fixed parameters + 9 optimized parameters + 1 parameter for the lake - catchment_parameters : array_like [3] - Problem parameter vector setup as: - [tfac, area, Lake_area] - v : array_like [4] - Input vector setup as: - [prec, temp, evap, llt] - state_variables : array_like [5] - Previous model states setup as: - [sp, sm, uz, lz, wc] - - Returns - ------- - q_new : float - Discharge [m3/s] - St : array_like [5] - Posterior model states + """Execute a single time step of the HBV-lake model. + + Runs all sub-routines (precipitation, snow, soil, response, and + lake) for one time step and returns the lake outflow discharge + and updated state variables. + + Args: + parameters (array_like): Parameter vector of length 11, + ordered as ``[fc, beta, etf, lp, c_flux, k, k1, alpha, + perc, c_le, pcorr]``. Nine parameters control the + standard HBV routines, ``c_le`` is the lake evaporation + correction factor, and ``pcorr`` is the precipitation + correction factor. + catchment_parameters (array_like): Problem parameter vector + of length 3, ordered as ``[tfac, lake_sub, lake_area]`` + where ``tfac`` is hours per time step, ``lake_sub`` is + the lake sub-catchment area [km2], and ``lake_area`` is + the lake surface area [km2]. + v (array_like): Input vector of length 4, ordered as + ``[prec, temp, evap, ll_temp]``. + state_variables (array_like): Previous model states of + length 6, ordered as ``[sp, sm, uz, lz, wc, + lake_volume]``. + curve (numpy.ndarray): Two-column lake rating curve array + where column 0 is discharge [m3/s] and column 1 is + storage [m3]. + + Returns: + tuple[float, list[float]]: + - Lake outflow discharge [m3/s]. + - Updated state variables ``[sp, sm, uz, lz, wc, + lake_volume]``. + + Examples: + >>> import numpy as np + >>> from Hapi.rrm.hbv_lake import HBVLake, DEF_ST + >>> model = HBVLake() + >>> par = [200, 2.0, 0.01, 0.6, 0.5, 0.01, 0.05, + ... 1.0, 1.0, 1.0, 1.0] + >>> p2 = [24, 100.0, 10.0] + >>> v = [5.0, 25.0, 2.0, 20.0] + >>> curve = np.column_stack([ + ... np.linspace(0, 100, 50), + ... np.linspace(0, 1e10, 50), + ... ]) + >>> q, st = model._step_run(par, p2, v, DEF_ST, curve) + >>> len(st) + 6 """ ## Parse of parameters from input vector to model # picipitation function @@ -649,7 +792,7 @@ def _step_run(self, parameters, catchment_parameters, v, state_variables, curve) return qout, [sp_new, sm_new, uz_new, lz_new, wc_new, lv_new] - def simulate( + def simulate( # type: ignore[override] self, avg_prec, temp, @@ -662,39 +805,67 @@ def simulate( ll_temp=None, lake_sim=False, ): - """Simulate. - - Run the HBV model for the number of steps (n) in precipitation. The - resluts are (n+1) simulation of discharge as the model calculates step n+1 - - Parameters - ---------- - avg_prec : array_like [n] - Average precipitation [mm/h] - temp : array_like [n] - Average temperature [C] - et : array_like [n] - Potential Evapotranspiration [mm/h] - par : array_like [10] - Parameter vector, set up as: - [fc, beta, e_corr, etf, lp, c_flux, k, k1, alpha, perc] - p2 : array_like [3] - Problem parameter vector setup as: - [tfac, area, Lake_area] - init_st : array_like [5], optional - Initial model states, [sp, sm, uz, lz, wc]. If unspecified, - [0.0, 30.0, 30.0, 30.0, 0.0] mm - ll_temp : array_like [n], optional - Long term average temptearature. If unspecified, calculated from temp. - q_0 : float, optional - Initial discharge value. If unspecified set to 10.0 - - Returns - ------- - q_sim : array_like [n] - Discharge for the n time steps of the precipitation vector [m3/s] - st : array_like [n, 5] - Model states for the complete time series [mm] + """Run the HBV-lake model over a precipitation time series. + + Iterates ``_step_run`` for each time step in the precipitation + array and returns the full discharge time series and model + states. The result has ``n + 1`` entries because the initial + condition is included. + + Args: + avg_prec (array_like): Average precipitation for each time + step [mm/h], length ``n``. + temp (array_like): Average temperature for each time step + [C], length ``n``. + et (array_like): Potential evapotranspiration for each time + step [mm/h], length ``n``. + par (array_like): Parameter vector of length 11. See + ``_step_run`` for the ordering. + p2 (array_like): Problem parameter vector + ``[tfac, lake_sub, lake_area]``. + curve (numpy.ndarray): Two-column lake rating curve where + column 0 is discharge [m3/s] and column 1 is storage + [m3]. + q_0 (float): Initial discharge value [m3/s]. Defaults to + ``DEF_q0`` (2.3). + init_st (list[float] or None): Initial model states + ``[sp, sm, uz, lz, wc, lake_volume]``. If ``None``, + ``DEF_ST`` is used. + ll_temp (array_like or None): Long-term average temperature + for each time step [C]. If ``None``, computed as the + mean of ``temp``. + lake_sim (bool): Unused legacy parameter retained for + interface compatibility. + + Returns: + tuple[list[float], list[list[float]]]: + - Simulated discharge for ``n + 1`` time steps [m3/s] + (includes the initial value). + - Model states for ``n + 1`` time steps. + + Examples: + >>> import numpy as np + >>> from Hapi.rrm.hbv_lake import HBVLake, DEF_ST, DEF_q0 + >>> model = HBVLake() + >>> n = 5 + >>> prec = np.array([4.0] * n) + >>> temp = np.array([25.0] * n) + >>> et = np.array([2.0] * n) + >>> par = [200, 2.0, 0.01, 0.6, 0.5, 0.01, 0.05, 1.0, + ... 1.0, 1.0, 1.0] + >>> p2 = [24, 100.0, 10.0] + >>> curve = np.column_stack([ + ... np.linspace(0, 100, 50), + ... np.linspace(0, 1e10, 50), + ... ]) + >>> q_sim, states = model.simulate( + ... prec, temp, et, par, p2, curve, + ... q_0=DEF_q0, init_st=DEF_ST, + ... ) + >>> len(q_sim) + 6 + >>> len(states) + 6 """ if init_st is None: # If unspecified, [0.0, 30.0, 30.0, 30.0, 0.0] mm diff --git a/src/Hapi/rrm/parameters.py b/src/Hapi/rrm/parameters.py index fd984c0e8..d2b136fef 100644 --- a/src/Hapi/rrm/parameters.py +++ b/src/Hapi/rrm/parameters.py @@ -1,10 +1,12 @@ -"""Parameters. +"""Hapi.rrm.parameters module. -The module contains functions that are responsible for distributing parameters spatially -(totally distributed, totally distributed with some parameters lumped, all parameters are lumped, hydrologic -response units) and also save generated parameters into rasters. +This module contains functions responsible for distributing parameters +spatially (totally distributed, totally distributed with some parameters +lumped, all parameters lumped, hydrologic response units) and saving +generated parameters into rasters. """ -from typing import List +from __future__ import annotations + import datetime as dt import math import os @@ -14,21 +16,11 @@ class Parameters: - """Parameters. - - parameter class is used to distribute the values of the parameter vector in the calibration - process into the 3D array, considering if some of the parameters are lumped parameters, if you want to - distribute the parameters in HRUs. - - Methods - ------- - - par3d - - par3dLumped - - par2dLumpedK1_lake - - HRU - - HRU_HAND - - ParametersNumber - - saveParameters + """Parameter distribution class for hydrological model calibration. + + The Parameters class distributes values from a parameter vector during + the calibration process into a 3D array, handling lumped parameters + and hydrologic response units (HRUs). """ def __init__( @@ -36,7 +28,7 @@ def __init__( raster, no_parameters: int, no_lumped_par: int = 0, - lumped_par_pos: List[int] = None, + lumped_par_pos: list[int] | None = None, lake: bool = False, snow: bool = False, hru: bool = False, @@ -45,47 +37,50 @@ def __init__( k_lower_bound: int = 50, muskingum: bool = False, ): - """Parameters. - - To initiate the Parameters class, you have to provide the Flow Acc raster. - - Parameters - ---------- - raster : [gdal.dataset] - raster to get the spatial information of the catchment - (DEM, flow accumulation or flow direction raster) - no_parameters : [integer] - Number of parameters in the HBV model. - no_lumped_par : [integer], optional - Number of lumped parameters, you have to enter the value of - the lumped parameter at the end of the list, default is 0 (no lumped parameters) - lumped_par_pos : [integer], optional - list of order or position of the lumped parameter among all - the parameters of the lumped model (order starts from 0 to the length - of the model parameters), default is [] (empty), the following order - of parameters is used for the lumped HBV model used - [ltt, utt, rfcf, sfcf, ttm, cfmax, cwh, cfr, fc, beta, e_corr, etf, lp, - c_flux, k, k1, alpha, perc, pcorr, Kmuskingum, Xmuskingum] - lake : [integer], optional - 0 if there is no lake and 1 if there is a lake. The default is 0. - snow : [integer] - 0 if you don't want to run the snow-related processes and 1 if there is snow. - in case of 1 (simulate snow processes) parameters related to snow simulation - have to be provided. The default is 0. - hru: [integer], optional - if the parameters will consider using HRUs. The default is 0. - function: [integer], optional - function you use to distribute parameters. The default is 1. - k_upper_bound: [numeric], optional - upper bound of K value (traveling time in muskingum routing method). Default is 1 hour - k_lower_bound: [numeric], optional - Lower bound of K value (traveling time in muskingum routing method). Default is 0.5 hour (30 min) - muskingum: [bool], optional - if the routing function is muskingum. The default is False. - - Returns - ------- - None. + """Initialize the Parameters class. + + To initiate the Parameters class, you have to provide the Flow Acc + raster. + + Args: + raster: A gdal.Dataset raster to get the spatial information + of the catchment (DEM, flow accumulation or flow direction + raster). + no_parameters: Number of parameters in the HBV model. + no_lumped_par: Number of lumped parameters. You have to enter + the value of the lumped parameter at the end of the list. + Defaults to 0 (no lumped parameters). + lumped_par_pos: List of the order or position of lumped + parameters among all the parameters of the lumped model + (order starts from 0 to the length of the model + parameters). Defaults to None (empty). The following + order of parameters is used for the lumped HBV model: + [ltt, utt, rfcf, sfcf, ttm, cfmax, cwh, cfr, fc, beta, + e_corr, etf, lp, c_flux, k, k1, alpha, perc, pcorr, + Kmuskingum, Xmuskingum]. + lake: True if there is a lake, False otherwise. + Defaults to False. + snow: True to run the snow-related processes, False otherwise. + When True, parameters related to snow simulation have to + be provided. Defaults to False. + hru: True if the parameters will consider using HRUs. + Defaults to False. + function: Function to use for distributing parameters. + Defaults to 1. + k_upper_bound: Upper bound of K value (traveling time in + muskingum routing method). Defaults to 1 hour. + k_lower_bound: Lower bound of K value (traveling time in + muskingum routing method). Defaults to 50. + muskingum: True if the routing function is muskingum. + Defaults to False. + + Raises: + AssertionError: If `raster` is not a gdal.Dataset, if + `no_parameters` is not an integer, if `no_lumped_par` is + not an integer, or if the length of `lumped_par_pos` does + not match `no_lumped_par`. + ValueError: If `lumped_par_pos` is not a list when + `no_lumped_par` >= 1. """ if lumped_par_pos is None: lumped_par_pos = [] @@ -145,7 +140,7 @@ def __init__( ) self.no_elem = len(self.values) else: - self.no_elem = np.size(self.raster_A[:, :]) - np.count_nonzero( + self.no_elem = np.size(self.raster_A[:, :]) - np.count_nonzero( # type: ignore[assignment] (self.raster_A[np.isnan(self.raster_A)]) ) @@ -181,7 +176,7 @@ def __init__( elif function == 2: self.Function = self.par3d elif function == 3: - self.Function = self.par2d_lumped_k1_lake + self.Function = self.par2d_lumped_k1_lake # type: ignore[assignment] elif function == 4: self.Function = self.hydrologic_response_units # to overwrite any choice user choose if the is HRUs @@ -193,56 +188,22 @@ def __init__( pass def par3d(self, par_g): # , kub=1,klb=0.5, Maskingum=True - """par3d. - - par3d method takes a list of parameters [saved as one column or generated as 1D list from optimization - algorithm] and distribute them horizontally on number of cells given by a raster. - - Parameters - ---------- - par_g : [list] - list of parameters - - Returns - ------- - par_3d : [3d array] - 3D array of the parameters distributed horizontally on the cells - - Examples - -------- - EX1:totally distributed parameters - raster=gdal.Open("dem.tif") - [fc, beta, etf, lp, c_flux, k, k1, alpha, perc, pcorr, Kmuskingum, Xmuskingum] - no_lumped_par=0 - lumped_par_pos=[] - par_g=np.random.random(no_elem*(no_parameters-no_lumped_par)) - - tot_dist_par=par3d(par_g,raster,no_parameters,no_lumped_par,lumped_par_pos,kub=1,klb=0.5) - - EX2: One Lumped Parameter [K1] - raster=gdal.Open("dem.tif") - given values of parameters are of this order - [fc, beta, etf, lp, c_flux, k, alpha, perc, pcorr, Kmuskingum, Xmuskingum,k1] - K1 is lumped so its value is inserted at the end and its order should - be after K - - no_lumped_par=1 - lumped_par_pos=[6] - par_g=np.random.random(no_elem* (no_parameters-no_lumped_par)) - # insert the value of k1 at the end - par_g=np.append(par_g,0.005) - - dist_par=par3d(par_g,raster,no_parameters,no_lumped_par,lumped_par_pos,kub=1,klb=0.5) - - EX3:Two Lumped Parameter [K1, Perc] - raster=gdal.Open("dem.tif") - no_lumped_par=2 - lumped_par_pos=[6,8] - par_g=np.random.random(no_elem* (no_parameters-no_lumped_par)) - par_g=np.append(par_g,0.005) - par_g=np.append(par_g,0.006) - - dist_par=par3d(par_g,raster,no_parameters,no_lumped_par,lumped_par_pos,kub=1,klb=0.5) + """Distribute parameters horizontally across grid cells. + + Takes a list of parameters (saved as one column or generated as a + 1D list from an optimization algorithm) and distributes them + horizontally on the number of cells given by a raster. + + Args: + par_g: 1D list or numpy array of parameters. For totally + distributed parameters, the length should be + ``no_elem * no_parameters``. For lumped parameters, the + lumped parameter values should be appended at the end. + + Raises: + AssertionError: If the length of `par_g` does not match the + expected number of parameters based on the number of + elements and lumped parameters. """ # input data validation # data type @@ -271,7 +232,7 @@ def par3d(self, par_g): # , kub=1,klb=0.5, Maskingum=True # parameters in array # create a 2d array [no_parameters, no_cells] - self.Par2d = np.ones((self.no_parameters, self.no_elem)) + self.Par2d = np.ones((self.no_parameters, self.no_elem)) # type: ignore[assignment] # take the parameters from the generated parameters or the 1D list and # assign them to each cell for i in range(self.no_elem): @@ -314,32 +275,19 @@ def par3d(self, par_g): # , kub=1,klb=0.5, Maskingum=True # ) def par3d_lumped(self, par_g): # , kub=1, klb=0.5, Maskingum = True - r"""par3dLumped method. - - takes a list of parameters [saved as one column or generated as 1D list from - optimization algorithm] and distribute them horizontally on number of cells given by a raster. - - Parameters - ---------- - par_g : [list] - list of parameters - - Returns - ------- - par_3d: [3d array] - 3D array of the parameters distributed horizontally on the cells - - Example - ------- - EX1:Lumped parameters - [fc, beta, etf, lp, c_flux, k, k1, alpha, perc, pcorr, Kmuskingum, Xmuskingum] - - >>> from Hapi.rrm.parameters import Parameters as dp - >>> raster = gdal.Open("soil_classes.tif") - >>> no_parameters = 12 - >>> lumped_par_pos = [] - >>> par_g = np.random.random(no_parameters) #no_elem*(no_parameters-no_lumped_par) - >>> tot_dist_par = dp.par3d_lumped(par_g, raster, no_parameters, lumped_par_pos, kub=1, klb=0.5) + r"""Distribute lumped parameters horizontally across grid cells. + + Takes a list of parameters (saved as one column or generated as a + 1D list from an optimization algorithm) and distributes them + horizontally on the number of cells given by a raster, where all + parameters are lumped (same value for every cell). + + Args: + par_g: 1D list or numpy array of lumped parameters. + The length should equal ``no_parameters``. + + Raises: + ValueError: If `par_g` is not a numpy ndarray or a list. """ # input data validation # data type @@ -367,22 +315,24 @@ def par3d_lumped(self, par_g): # , kub=1, klb=0.5, Maskingum = True @staticmethod def calculate_k(x, position, upper_bound, lower_bound): - """calculateK. - - calculateK method takes value of x parameter and generate 100 random value of k parameters between upper & - lower constraint then the output will be the value coresponding to the giving position. - - Parameters - ---------- - x : [numeric] - weighting coefficient to determine the linearity of the water surface - (one of the parameters of muskingum routing method) - position : [integer] - random position between upper and lower bounds of the k parameter - upper_bound : [numeric] - upper bound for k parameter - lower_bound : [numeric] - Lower bound for k parameter + """Calculate K parameter for Muskingum routing. + + Takes the value of x parameter and generates 100 random values of + the K parameter between the upper and lower constraints, then + returns the value corresponding to the given position. + + Args: + x: Weighting coefficient to determine the linearity of the + water surface (one of the parameters of the Muskingum + routing method). + position: Random position between upper and lower bounds of + the K parameter. + upper_bound: Upper bound for the K parameter. + lower_bound: Lower bound for the K parameter. + + Returns: + The K parameter value corresponding to the given position + within the constrained range. """ # k has to be smaller than this constraint constraint1 = 0.5 * 1 / (1 - x) @@ -400,38 +350,29 @@ def calculate_k(x, position, upper_bound, lower_bound): return k def par2d_lumped_k1_lake(self, par_g, no_parameters_lake): # ,kub,klb - """par2d_lumpedK1_lake. - - method takes a list of parameters and distribute them horizontally on number of cells given by a raster. - - Parameters - ---------- - par_g : [list] - list of parameters - no_parameters_lake : [integer] - no of lake parameters - - Returns - ------- - Par3d: [3d array] - 3D array of the parameters distributed horizontally on the cells - lake_par: [list] - list of the lake parameters. - - Example: - ---------- - a list of 155 value,all parameters are distributed except lower zone coefficient - (is written at the end of the list) each cell(14 cells) has 11 parameter plus lower zone - (12 parameters) function will take each 11 parameter and assing them to a specific cell - then assign the last value (lower zone parameter) to all cells - 14*11=154 + 1 = 155 + """Distribute parameters with a lumped K1 and lake parameters. + + Takes a list of parameters and distributes them horizontally on + the number of cells given by a raster. All parameters are + distributed except the lower zone coefficient (K1), which is + lumped and appended at the end of the parameter list. Lake + parameters are extracted from the end of the parameter list. + + Args: + par_g: 1D list or numpy array of parameters. Each cell's + distributed parameters are listed sequentially, followed + by the lumped K1 value, followed by lake parameters at + the end. For example, with 14 cells and 11 distributed + parameters: ``14 * 11 = 154 + 1 (K1) = 155``. + no_parameters_lake: Number of lake parameters to extract + from the end of `par_g`. """ # parameters in array # remove a place for the lumped parameter (k1) lower zone coefficient no_parameters = self.no_parameters - 1 # create a 2d array [no_parameters, no_cells] - self.Par2d = np.ones((no_parameters, self.no_elem)) + self.Par2d = np.ones((no_parameters, self.no_elem)) # type: ignore[assignment] # take the parameters from the generated parameters or the 1D list and # assign them to each cell @@ -468,47 +409,28 @@ def par2d_lumped_k1_lake(self, par_g, no_parameters_lake): # ,kub,klb # return self.Par3d, lake_par def hydrologic_response_units(self, par_g): # ,kub=1,klb=0.5 - """HRU. - - method takes a list of parameters [saved as one column or generated as 1D list from optimization algorithm] - and distribute them horizontally on number of cells given by a raster the input raster should be classified - raster (by numbers) into class to be used to define the HRUs. - - Parameters - ---------- - par_g: - [list] list of parameters - - Returns - ------- - par_3d: - 3D array of the parameters distributed horizontally on the cells - - Examples - -------- - EX1:HRU without lumped parameters - [fc, beta, etf, lp, c_flux, k, k1, alpha, perc, pcorr, Kmuskingum, Xmuskingum] - >>> raster = gdal.Open("soil_types.tif") - >>> no_lumped_par = 0 - >>> lumped_par_pos = [] - >>> par_g = np.random.random(no_elem*(no_parameters-no_lumped_par)) - >>> par_hru = HRU(par_g, raster, no_parameters, no_lumped_par, lumped_par_pos, kub=1,klb=0.5) - - - EX2: HRU with one lumped parameters - given values of parameters are of this order - [fc, beta, etf, lp, c_flux, k, alpha, perc, pcorr, Kmuskingum, Xmuskingum,k1] - K1 is lumped so its value is inserted at the end and its order should - be after K - - >>> raster = gdal.Open("soil_types.tif") - >>> no_lumped_par = 1 - >>> lumped_par_pos = [6] - >>> par_g = np.random.random(no_elem* (no_parameters-no_lumped_par)) - >>> # insert the value of k1 at the end - >>> par_g = np.append(par_g,0.005) - - >>> par_hru = HRU(par_g, raster, no_parameters, no_lumped_par, lumped_par_pos, kub=1, klb=0.5) + """Distribute parameters using Hydrologic Response Units (HRUs). + + Takes a list of parameters (saved as one column or generated as a + 1D list from an optimization algorithm) and distributes them + horizontally on the number of cells given by a raster. The input + raster should be a classified raster (by numbers) into classes to + define the HRUs. Each HRU receives the same set of generated + parameters. + + Args: + par_g: 1D list or numpy array of parameters. For HRU without + lumped parameters, the length should be + ``no_elem * no_parameters``. For HRU with lumped + parameters, the lumped parameter values should be + appended at the end. + + Raises: + ValueError: If `par_g` is not a numpy ndarray or a list, or + if the length of `par_g` does not match the expected + number of parameters. + AssertionError: If there are lumped parameters and the length + of `par_g` does not match the expected total. """ # input data validation # data type @@ -578,30 +500,30 @@ def hydrologic_response_units(self, par_g): # ,kub=1,klb=0.5 @staticmethod def hru_hand(dem, flow_direction, flow_path_length, river): - """hru_hand. - - hru_hand this function calculates inputs for the hand (height above nearest drainage) method for land use - classification. - - Parameters - ---------- - dem: [gdal.dataset] - raster to get the spatial information of the catchment (DEM raster) - flow_direction: [gdal.dataset] - flow direction raster to get the spatial information of the catchment - flow_path_length: [gdal.dataset] - raster to get the spatial information of the catchment - river: [gdal.dataset] - raster to get the spatial information of the catchment - - - Returns - ------- - hand: [numpy ndarray] - Height above nearest drainage - - dist_to_nearest_drain: [numpy ndarray] - Distance to nearest drainage + """Calculate Height Above Nearest Drainage (HAND) for HRU classification. + + Calculates inputs for the HAND method for land use + classification by tracing flow direction from each cell to the + nearest river reach, then computing the elevation difference + and the flow path distance. + + Args: + dem: A gdal.Dataset of the DEM raster. + flow_direction: A gdal.Dataset of the flow direction raster. + flow_path_length: A gdal.Dataset of the flow path length + raster. + river: A gdal.Dataset of the river location raster, where + cells with value 1 indicate river presence. + + Returns: + A tuple of two numpy ndarrays: + - hand: Height above nearest drainage for each cell. + - dist_to_nearest_drain: Distance to nearest drainage + for each cell. + + Raises: + ValueError: If the catchment boundaries contain anomalies + (e.g., after cropping with a polygon). """ # Use DEM raster information to run all loops dem_a = dem.ReadAsArray() @@ -683,27 +605,20 @@ def hru_hand(dem, flow_direction, flow_path_length, river): return hand, dist_to_nearest_drain def parameters_number(self): - """parameters_number. - - ParametersNO method calculates the nomber of parameters that the optimization algorithm is going top search - for, use it only in case of totally distributed catchment parameters (in case of lumped parameters no of - parameters are the same as the no of parameters of the conceptual model) - - Parameters - ---------- - The Parameters object should have the following attributes before tyring so use the saveParameters function - self - raster : [gdal.dataset] - raster to get the spatial information of the catchment - (DEM, flow accumulation or flow direction raster) - no_parameters : [integer] - no of parameters of the cell according to the rainfall runoff model - no_lumped_par : [integer] - nomber of lumped parameters, you have to enter the value of - the lumped parameter at the end of the list, default is 0 (no lumped parameters) - HRUs : [0 or 1] - 0 to define that no hydrologic response units (HRUs), 1 to define that - HRUs are used + """Calculate the total number of parameters for the optimization. + + Calculates the number of parameters that the optimization + algorithm will search for. Use this only in case of totally + distributed catchment parameters. In case of lumped parameters, + the number of parameters is the same as the number of parameters + of the conceptual model. + + The result is stored in the ``ParametersNO`` attribute. + + Note: + The Parameters object should have the following attributes + before calling this method: ``raster``, ``no_parameters``, + ``no_lumped_par``, and ``HRUs``. """ if not self.HRUs: if self.no_lumped_par > 0: @@ -725,54 +640,22 @@ def parameters_number(self): self.ParametersNO = self.no_elem * self.no_parameters def save_parameters(self, path): - """SaveParameters. - - saveParameters method takes generated parameters by the calibration algorithm, distributed them with a given - function and save them as a rasters. - - Parameters - ---------- - The DistParameters object should have the following attributes before tyring so use the saveParameters function - self: - DistParFn: - [function] function to distribute the parameters (all functions are - in Hapi.DistParameters ) - raster: - [gdal.dataset] raster to get the spatial information - Par - [list or numpy ndarray] parameters as 1D array or list - no_parameters: - [int] number of the parameters in the conceptual model - snow: - [integer] number to define whether to take parameters of - the conceptual model with snow subroutine or without - kub: - [numeric] upper bound for k parameter in muskingum function - klb: - [numeric] lower bound for k parameter in muskingum function - path: - [string] path to the folder you want to save the parameters in - default value is None (parameters are going to be saved in the - current directory) - - Returns - ------- - Rasters for parameters of the distributed model - - Examples - -------- - >>> from Hapi.rrm.parameters import Parameters as DP - >>> DemPath = "GIS/4000/dem4000.tif" - >>> raster = gdal.Open(DemPath) - >>> ParPath = "par15_7_2018.txt" - >>> par = np.loadtxt(ParPath) - >>> klb = 0.5 - >>> kub = 1 - >>> no_parameters = 12 - >>> DistParFn = DP.par3d_lumped - >>> Path = "parameters/" - >>> snow = 0 - >>> DP.save_parameters(DistParFn, raster, par, no_parameters, snow, kub, klb, path) + """Save distributed parameters as raster files. + + Takes the generated 3D parameter array and saves each parameter + layer as a separate GeoTIFF raster file. + + Args: + path: Path to the folder where the parameter rasters will + be saved. + + Raises: + AssertionError: If `path` is not a string or does not exist. + + Note: + The Parameters object should have the following attributes + set before calling this method: ``DistParFn``, ``raster``, + ``Par``, ``no_parameters``, ``snow``, ``kub``, and ``klb``. """ assert isinstance(path, str), "path should be of type string" assert os.path.exists(path), f"{path} you have provided does not exist" diff --git a/src/Hapi/run.py b/src/Hapi/run.py index b6a5dd39b..d3631c435 100644 --- a/src/Hapi/run.py +++ b/src/Hapi/run.py @@ -1,14 +1,12 @@ -"""RUN. +"""Run module for the Hapi hydrological model. -RUN contains functions to connect the parameter spatial distribution -function with both components of the spatial representation of the hydrological -process (conceptual model & spatial routing) to calculate the predicted -runoff at known locations based on given performance function - -Created on Sun Jun 24 21:02:34 2018 - -@author: Mostafa +The run module connects the parameter spatial distribution function with +both components of the spatial representation of the hydrological process +(conceptual model and spatial routing) to calculate the predicted runoff +at known locations based on a given performance function. """ +from __future__ import annotations + import numpy as np import pandas as pd from loguru import logger @@ -22,42 +20,45 @@ class Run(Catchment): """Run the catchment model. - Run sub-class validate the spatial data and hand it to the wrapper class, It is - a sub-class from the catchment class, so you need to create the Catchment - object first to run the model - - Methods - ------- - 1- RunHapi - 2- runHAPIwithLake - 3- runFW1 - 4- RunFW1withLake - 5- runLumped + The Run sub-class validates the spatial data and hands it to the + Wrapper class. It is a sub-class of the Catchment class, so you + need to create the Catchment object first to run the model. + + Methods: + RunHapi: Run the distributed hydrological model. + runHAPIwithLake: Run the distributed model with a lake component. + runFW1: Run the FW1 distributed model. + RunFW1withLake: Run the FW1 model with a lake component. + runLumped: Run the lumped conceptual model. """ def __init__(self): - self.Qsim = None - pass + self.Qsim: np.ndarray | pd.DataFrame | None = None def RunHapi(self): - """Run Model. - - Returns - ------- - state_variables: [numpy attribute] - 4D array (rows,cols,time,states) states are [sp,wc,sm,uz,lv] - qlz: [numpy attribute] - 3D array of the lower zone discharge - quz: [numpy attribute] - 3D array of the upper zone discharge - qout: [numpy attribute] - 1D timeseries of discharge at the outlet of the catchment - of unit m3/sec - quz_routed: [numpy attribute] - 3D array of the upper zone discharge accumulated and - routed at each time step - qlz_translated: [numpy attribute] - 3D array of the lower zone discharge translated at each time step + """Run the distributed hydrological model. + + Validates that all input arrays (precipitation, evapotranspiration, + temperature, parameters, and flow direction) have consistent + dimensions, then executes the rainfall-runoff model via the + Wrapper. + + The following instance attributes are set after execution: + + - ``state_variables``: 4D array (rows, cols, time, states) where + states are [sp, wc, sm, uz, lv]. + - ``qlz``: 3D array of the lower zone discharge. + - ``quz``: 3D array of the upper zone discharge. + - ``qout``: 1D timeseries of discharge at the catchment outlet + in m3/sec. + - ``quz_routed``: 3D array of the upper zone discharge + accumulated and routed at each time step. + - ``qlz_translated``: 3D array of the lower zone discharge + translated at each time step. + + Raises: + AssertionError: If input data arrays have inconsistent + row counts, column counts, or temporal lengths. """ # input dimensions [fd_rows, fd_cols] = self.FlowDirArr.shape @@ -88,9 +89,16 @@ def RunHapi(self): print("Model Run has finished") def RunFloodModel(self): - """Run flood model. + """Run the flood model. + + Runs the conceptual distributed hydrological model with + additional validation for river geometry inputs (bankfull depth, + river width, river roughness, and flood plain roughness). - - This function runs the conceptual distributed hydrological model + Raises: + AssertionError: If meteorological input arrays, parameter + arrays, or river geometry arrays have inconsistent + dimensions. """ # input dimensions [fd_rows, fd_cols] = self.FlowDirArr.shape @@ -136,18 +144,23 @@ def RunFloodModel(self): # print("1D model Run has finished") def runHAPIwithLake(self, Lake): - """Run model with lake. - - - This function runs the conceptual distributed hydrological model - - Returns - ------- - st: [4D array] - state variables. - q_out: [1D array] - calculated Discharge at the outlet of the catchment. - q_uz: [3D array] - Distributed discharge for each cell. + """Run the distributed model with a lake component. + + Validates that all input arrays have consistent dimensions and + that the lake meteorological data matches the simulation period, + then executes the rainfall-runoff model with lake routing via + the Wrapper. + + Args: + Lake: Lake object containing lake configuration and + meteorological data. Must have a ``MeteoData`` attribute + with shape ``(time_steps, >= 3)`` where columns are + rain, ET, and temperature. + + Raises: + AssertionError: If input data arrays have inconsistent + dimensions or if the lake meteorological data length + does not match the distributed raster data length. """ # input dimensions [fd_rows, fd_cols] = self.FlowDirArr.shape @@ -185,18 +198,21 @@ def runHAPIwithLake(self, Lake): print("Model Run has finished") def runFW1(self): - """Run DistwithLake. - - This function runs the conceptual distributed hydrological model - - Returns - ------- - st: [4D array] - state variables - q_out: [1D array] - calculated Discharge at the outlet of the catchment - q_uz: [3D array] - Distributed discharge for each cell + """Run the FW1 distributed hydrological model. + + Validates that all input arrays have consistent dimensions, + then executes the FW1 model via the Wrapper. + + The following instance attributes are set after execution: + + - ``st``: 4D array of state variables. + - ``q_out``: 1D array of calculated discharge at the catchment + outlet. + - ``q_uz``: 3D array of distributed discharge for each cell. + + Raises: + AssertionError: If input data arrays have inconsistent + row counts, column counts, or temporal lengths. """ assert ( np.shape(self.Prec)[0] == self.rows @@ -220,53 +236,39 @@ def runFW1(self): print("Model Run has finished") def RunFW1withLake(self, Lake): - """RunDistwithLake. - - this function runs the conceptual distributed hydrological model - - Parameters - ---------- - 1-Paths: - 1-prec_path: - [String] path to the Folder contains precipitation rasters - 2-evap_path: - [String] path to the Folder contains Evapotranspiration rasters - 3-temp_path: - [String] path to the Folder contains Temperature rasters - 4-flow_acc_path: - [String] path to the Flow Accumulation raster of the catchment (it should - include the raster name and extension) - 5-flow_direction_path: - [String] path to the Flow Direction raster of the catchment (it should - include the raster name and extension) - 7-ParPath: - [String] path to the Folder contains parameters rasters of the catchment - 8-p2: - [List] list of unoptimized parameters - p2[0] = tfac, 1 for hourly, 0.25 for 15 min time step and 24 for daily time step - p2[1] = catchment area in km2 - - Returns - ------- - st: [4D array] - state variables - q_out: [1D array] - calculated Discharge at the outlet of the catchment - q_uz: [3D array] - Distributed discharge for each cell - - Example - ------- - prec_path = prec_path="meteodata/4000/calib/prec" - evap_path = evap_path="meteodata/4000/calib/evap" - temp_path = temp_path="meteodata/4000/calib/temp" - DemPath = "GIS/4000/dem4000.tif" - flow_acc_path = "GIS/4000/acc4000.tif" - flow_direction_path = "GIS/4000/fd4000.tif" - ParPath = "meteodata/4000/parameters" - p2=[1, 227.31] - st, q_out, q_uz_routed = RunModel(prec_path,evap_path,temp_path,DemPath, - flow_acc_path,flow_direction_path,ParPath,p2) + """Run the FW1 distributed model with a lake component. + + Validates that all input arrays have consistent dimensions and + that the lake meteorological data matches the simulation period, + then executes the FW1 model with lake routing via the Wrapper. + + Args: + Lake: Lake object containing lake configuration and + meteorological data. Must have a ``MeteoData`` attribute + with shape ``(time_steps, >= 3)`` where columns are + rain, ET, and temperature. + + Note: + The following catchment attributes should be set before + calling this method: + + - ``prec_path``: Path to the folder containing precipitation + rasters. + - ``evap_path``: Path to the folder containing + evapotranspiration rasters. + - ``temp_path``: Path to the folder containing temperature + rasters. + - ``flow_acc_path``: Path to the flow accumulation raster. + - ``flow_direction_path``: Path to the flow direction raster. + - ``ParPath``: Path to the folder containing parameter + rasters. + - ``p2``: List of unoptimized parameters where ``p2[0]`` + is tfac and ``p2[1]`` is catchment area in km2. + + Raises: + AssertionError: If input data arrays have inconsistent + dimensions or if the lake meteorological data length + does not match the distributed raster data length. """ # input data validation @@ -302,53 +304,45 @@ def runLumped( Route: int = 0, RoutingFn=None, ): - """Run lumped model. - - - This function runs lumped conceptual model - - Parameters - ---------- - Route: [0 or 1] - to decide wether t route the generated discharge hydrograph or not - RoutingFn: [function] - function to route the dischrge hydrograph. - - Parameters that should be defined before calling the function - ConceptualModel: [function] - conceptual model and it should contain a function called simulate - data: [numpy array] - meteorological data as array with the first column as precipitation - second as evapotranspiration, third as temperature and forth column as - long term average temperature - parameters: [numpy array] - conceptual model parameters as array - p2: [List] - list of unoptimized parameters - p2[0] = tfac, 1 for hourly, 0.25 for 15 min time step and 24 for daily time step - p2[1] = catchment area in km2 - init_st: [list] - initial state variables values [sp, sm, uz, lz, wc]. - - Returns - ------- - st: [numpy array] - 3d array of the 5 state variable data for each cell - q_lz: [numpy array] - 1d array of the calculated discharge. - - Examples - -------- - >>> p2 = [24, 1530] - >>> #[sp,sm,uz,lz,wc] - >>> init_st = [0,5,5,5,0] - >>> snow = 0 + """Run the lumped conceptual model. + + Executes a lumped conceptual hydrological model, optionally + routing the generated discharge hydrograph. The simulated + discharge is stored in ``self.Qsim`` as a pandas DataFrame + indexed by the simulation date range. + + Args: + Route: Flag to decide whether to route the generated + discharge hydrograph. Use 0 for no routing or 1 to + enable routing. Defaults to 0. + RoutingFn: Function to route the discharge hydrograph. + If None, an empty list is used. Defaults to None. + + Note: + The following attributes should be defined before calling + this method: + + - ``LumpedModel``: Conceptual model containing a + ``simulate`` method. + - ``data``: Numpy array of meteorological data with + columns for precipitation, evapotranspiration, + temperature, and long-term average temperature. + - ``Parameters``: Numpy array of conceptual model + parameters. + - ``CatArea``: Catchment area in km2. + - ``conversion_factor``: Time conversion factor + (e.g., 24 for daily). + - ``InitialCond``: List of initial state variable + values [sp, sm, uz, lz, wc]. + - ``Snow``: Whether to use the snow subroutine (0 or 1). + - ``q_init``: Initial discharge value. """ - if RoutingFn is None: - RoutingFn = [] + if RoutingFn is None and Route != 0: + raise ValueError("RoutingFn must be a callable when Route != 0") if self.temporal_resolution.lower() == "daily": ind = pd.date_range(self.start, self.end, freq="D") else: - ind = pd.date_range(self.startdate, self.enddate, freq="h") + ind = pd.date_range(self.start, self.end, freq="h") Qsim = pd.DataFrame(index=ind) diff --git a/src/Hapi/wrapper.py b/src/Hapi/wrapper.py index 6a565d8dc..6051368c5 100644 --- a/src/Hapi/wrapper.py +++ b/src/Hapi/wrapper.py @@ -1,7 +1,14 @@ -"""Created on Sun Apr 29 17:17:54 2018. +"""Wrapper module for connecting rainfall-runoff model components. -@author: Mostafa +This module provides the Wrapper class that connects the distributed +rainfall-runoff model execution with spatial routing schemes. It +supports multiple configurations including Muskingum routing, +triangular routing, and lake integration. """ +from __future__ import annotations + +from collections.abc import Callable +from typing import TYPE_CHECKING import numpy as np @@ -9,87 +16,77 @@ from Hapi.rrm.distrrm import DistributedRRM as distrrm from Hapi.rrm.hbv_lake import HBVLake +if TYPE_CHECKING: + from Hapi.catchment import Catchment, Lake + class Wrapper: - """Wrapper. - - The class connect different commponent together (lumped run of the distributed with the spatial routing) for Hapi - and for FW1. - - Methods - ------- - 1- HapiModel - 2- RRMWithlake - 3- FW1 - 4- FW1Withlake - 5- Lumped + """Connects rainfall-runoff model components with spatial routing. + + The Wrapper class connects different components together including + the lumped run of the distributed model with the spatial routing + for Hapi and for FW1 (triangular routing). + + Methods: + RRMModel: Run distributed RRM with Muskingum spatial routing. + RRMWithlake: Run distributed RRM with lake and Muskingum + spatial routing. + FW1: Run distributed RRM with triangular routing. + FW1Withlake: Run distributed RRM with lake and triangular + routing. + Lumped: Run a lumped conceptual model with optional routing. """ def __init__(self): - """Run the whole setup.""" + """Initialize the Wrapper class.""" pass @staticmethod - def RRMModel(Model, ll_temp=None, q_0=None): - """RRMModel. - - HapiModel connect two modules : - 1- The distributed rainfall runoff: model runs separately for each cell - 2- The Spatial routing scheme (routing is following river network) - - Parameters - ---------- - Model - DEM: [gdal.dataset] - DEM raster file of the catchment (clipped to the catchment only) - flow_acc: [gdal.dataset] - flow accumulation raster file of the catchment (clipped to the catchment only) - flow_direct: [gdal.dataset] - flow Direction raster file of the catchment (clipped to the catchment only) - sp_prec: [numpy array] - 3d array of the precipitation data, sp_prec should - have the same 2d dimension of raster input - sp_et: [numpy array] - 3d array of the evapotranspiration data, sp_et should - have the same 2d dimension of raster input - sp_temp: [numpy array] - 3d array of the temperature data, sp_temp should - have the same 2d dimension of raster input - sp_par: [numpy array] - number of 2d arrays of the catchment properties spatially - distributed in 2d and the third dimension is the number of parameters, - sp_pars should have the same 2d dimension of raster input - p2: [List] - list of unoptimized parameters - p2[0] = tfac, 1 for hourly, 0.25 for 15 min time step and 24 for daily time step - p2[1] = catchment area in km2 - kub: [float] - upper bound of K value (traveling time in muskingum routing method) - klb: [float] - Lower bound of K value (traveling time in muskingum routing method) - init_st: [list] - initial state variables values [sp, sm, uz, lz, wc]. default=None - ll_temp: [numpy array] - 3d array of the long term average temperature data - q_0: [float] - initial discharge m3/s - - Returns - ------- - state_variables: - [numpy ndarray] 4D array (rows,cols,time,states) states are [sp,wc,sm,uz,lv] - qlz: - [numpy ndarray] 3D array of the lower zone discharge - quz: - [numpy ndarray] 3D array of the upper zone discharge - qout: - [numpy array] 1D timeseries of discharge at the outlet of the catchment - of unit m3/sec - quz_routed: - [numpy ndarray] 3D array of the upper zone discharge accumulated and - routed at each time step - qlz_translated: - [numpy ndarray] 3D array of the lower zone discharge translated at each time step + def RRMModel(Model: Catchment, ll_temp=None, q_0=None): + """Run the distributed rainfall-runoff model with spatial routing. + + Connects two modules: + + 1. The distributed rainfall-runoff model that runs separately + for each cell. + 2. The spatial routing scheme that routes flow following the + river network. + + The method stores results directly on the Model object, + including ``quz``, ``qlz``, ``qout``, ``quz_routed``, and + ``qlz_translated`` arrays. + + Args: + Model: Catchment model object containing: + + - DEM (gdal.dataset): DEM raster clipped to the + catchment. + - flow_acc (gdal.dataset): Flow accumulation raster + clipped to the catchment. + - flow_direct (gdal.dataset): Flow direction raster + clipped to the catchment. + - sp_prec (numpy.ndarray): 3D precipitation array + with the same 2D dimensions as the raster input. + - sp_et (numpy.ndarray): 3D evapotranspiration array + with the same 2D dimensions as the raster input. + - sp_temp (numpy.ndarray): 3D temperature array with + the same 2D dimensions as the raster input. + - sp_par (numpy.ndarray): 3D array of spatially + distributed catchment parameters. + - p2 (list): Unoptimized parameters where p2[0] is + tfac (1 for hourly, 0.25 for 15 min, 24 for daily) + and p2[1] is catchment area in km2. + - kub (float): Upper bound of K value for Muskingum + routing. + - klb (float): Lower bound of K value for Muskingum + routing. + - init_st (list): Initial state variable values + [sp, sm, uz, lz, wc]. + + ll_temp (numpy.ndarray, optional): 3D array of long-term + average temperature data. Defaults to None. + q_0 (float, optional): Initial discharge in m3/s. + Defaults to None. """ # run the rainfall runoff model separately distrrm.run_lumped_model(Model) @@ -100,26 +97,36 @@ def RRMModel(Model, ll_temp=None, q_0=None): # Model.qout = Model.qout[:-1] @staticmethod - def RRMWithlake(Model, Lake, ll_temp=None, q_0=None): - """RRMWithlake. - - RRMWithlake connects three modules the lake, the distributed - ranfall-runoff module and spatial routing module - - Parameters - ---------- - Model : [Catchment object] - DESCRIPTION. - Lake : TYPE - DESCRIPTION. - ll_temp : TYPE, optional - DESCRIPTION. The default is None. - q_0 : TYPE, optional - DESCRIPTION. The default is None. - - Returns - ------- - None. + def RRMWithlake(Model: Catchment, Lake: Lake, ll_temp=None, q_0=None): + """Run the distributed RRM with lake simulation and routing. + + Connects three modules: the lake module, the distributed + rainfall-runoff module, and the spatial routing module. The + lake discharge is simulated using HBVLake, routed via + Muskingum, and added to the downstream cell before spatial + routing. + + Args: + Model: Catchment model object containing the distributed + model configuration, parameters, and spatial data. + Lake: Lake object containing: + + - MeteoData (numpy.ndarray): 2D array with columns + for precipitation, evapotranspiration, temperature, + and long-term average temperature. + - Parameters (numpy.ndarray): Lake model parameters. + - CatArea (float): Lake catchment area in km2. + - LakeArea (float): Lake surface area in km2. + - StageDischargeCurve (numpy.ndarray): Stage-discharge + relationship. + - InitialCond (list): Initial condition values. + - OutflowCell (tuple): Row and column indices of the + lake outflow cell. + + ll_temp (numpy.ndarray, optional): 3D array of long-term + average temperature data. Defaults to None. + q_0 (float, optional): Initial discharge in m3/s. + Defaults to None. """ plake = Lake.MeteoData[:, 0] @@ -174,24 +181,24 @@ def RRMWithlake(Model, Lake, ll_temp=None, q_0=None): # Model.qout = Model.qout[:-1] @staticmethod - def FW1(Model, ll_temp=None, q_0=None): - """FW1. - - FW1 connects two module : - 1- The distributed rainfall-runoff module - 2- Triangular function-1 routing method - Parameters - ---------- - Model : TYPE - DESCRIPTION. - ll_temp : TYPE, optional - DESCRIPTION. The default is None. - q_0 : TYPE, optional - DESCRIPTION. The default is None. - - Returns - ------- - None. + def FW1(Model: Catchment, ll_temp=None, q_0=None): + """Run the distributed RRM with triangular function-1 routing. + + Connects two modules: + + 1. The distributed rainfall-runoff module. + 2. The triangular function-1 (MAXBAS) routing method. + + The output discharge is computed as the sum of routed upper + zone and unrouted lower zone discharge across all cells. + + Args: + Model: Catchment model object containing the distributed + model configuration, parameters, and spatial data. + ll_temp (numpy.ndarray, optional): 3D array of long-term + average temperature data. Defaults to None. + q_0 (float, optional): Initial discharge in m3/s. + Defaults to None. """ # subcatchment @@ -211,28 +218,38 @@ def FW1(Model, ll_temp=None, q_0=None): Model.qout = Model.qout[:-1] @staticmethod - def FW1Withlake(Model, Lake, ll_temp=None, q_0=None): - """FW1Withlake. - - FW1 connects two module : - 1- The distributed rainfall-runoff module - 2- Triangular function-1 routing method - 3- Lake module - - Parameters - ---------- - Model : TYPE - DESCRIPTION. - Lake : TYPE - DESCRIPTION. - ll_temp : TYPE, optional - DESCRIPTION. The default is None. - q_0 : TYPE, optional - DESCRIPTION. The default is None. - - Returns - ------- - None. + def FW1Withlake(Model: Catchment, Lake: Lake, ll_temp=None, q_0=None): + """Run the distributed RRM with lake and triangular routing. + + Connects three modules: + + 1. The distributed rainfall-runoff module. + 2. The triangular function-1 (MAXBAS) routing method. + 3. The lake simulation module. + + The lake discharge is simulated using HBVLake, routed via + Muskingum, and combined with the subcatchment discharge that + has been routed using the triangular function. + + Args: + Model: Catchment model object containing the distributed + model configuration, parameters, and spatial data. + Lake: Lake object containing: + + - MeteoData (numpy.ndarray): 2D array with columns + for precipitation, evapotranspiration, temperature, + and long-term average temperature. + - Parameters (numpy.ndarray): Lake model parameters. + - CatArea (float): Lake catchment area in km2. + - LakeArea (float): Lake surface area in km2. + - StageDischargeCurve (numpy.ndarray): Stage-discharge + relationship. + - InitialCond (list): Initial condition values. + + ll_temp (numpy.ndarray, optional): 3D array of long-term + average temperature data. Defaults to None. + q_0 (float, optional): Initial discharge in m3/s. + Defaults to None. """ plake = Lake.MeteoData[:, 0] @@ -256,7 +273,7 @@ def FW1Withlake(Model, Lake, ll_temp=None, q_0=None): # qlake is in m3/sec # lake routing - Lake.QlakeR = routing.muskingum( + Lake.QlakeR = routing.Muskingum_V( Lake.Qlake, Lake.Qlake[0], Lake.Parameters[11], @@ -267,7 +284,7 @@ def FW1Withlake(Model, Lake, ll_temp=None, q_0=None): # subcatchment distrrm.run_lumped_model(Model) - distrrm.DistMAXBAS(Model) + distrrm.DistMaxbas1(Model) qlz1 = np.array( [ @@ -289,48 +306,56 @@ def FW1Withlake(Model, Lake, ll_temp=None, q_0=None): Model.qout = qout[:-1] + Lake.QlakeR @staticmethod - def Lumped(Model, Routing=0, RoutingFn=[]): - """Lumped. - - Parameters - ---------- - Model: [function] - conceptual model and it should contain a function called simulate - data: [numpy array] - meteorological data as array with the first column as precipitation - second as evapotranspiration, third as temperature and forth column as - long term average temperature - parameters: [numpy array] - conceptual model parameters as array - p2: [List] - list of unoptimized parameters - p2[0] = tfac, 1 for hourly, 0.25 for 15 min time step and 24 for daily time step - p2[1] = catchment area in km2 - init_st: [list] - initial state variables values [sp, sm, uz, lz, wc]. - Routing: [0 or 1] - to decide wether t route the generated discharge hydrograph or not - RoutingFn: [function] - function to route the dischrge hydrograph. - - Returns - ------- - st: [numpy array] - 3d array of the 5 state variable data for each cell - q_lz: [numpy array] - 1d array of the calculated discharge. - - Examples - -------- - >>> p2=[24, 1530] - >>> #[sp,sm,uz,lz,wc] - >>> init_st=[0,5,5,5,0] - >>> snow=0 + def Lumped(Model: Catchment, Routing: int = 0, RoutingFn: Callable | None = None): + """Run a lumped conceptual model with optional routing. + + Executes a lumped rainfall-runoff model (e.g., HBV) to + compute the upper and lower zone discharge, then optionally + routes the combined discharge using the provided routing + function. + + The discharge is converted from mm/timestep to m3/s using + the catchment area and conversion factor. Results are stored + on the Model object as ``quz``, ``qlz``, ``Qsim``, and + ``state_variables``. + + Args: + Model: Lumped model object containing: + + - data (numpy.ndarray): 2D meteorological data array + with columns for precipitation, + evapotranspiration, temperature, and long-term + average temperature. + - Parameters (numpy.ndarray): Conceptual model + parameters. + - LumpedModel: Conceptual model instance with a + ``simulate`` method. + - InitialCond (list): Initial state variable values + [sp, sm, uz, lz, wc]. + - q_init (float): Initial discharge value. + - Snow (int): Flag to include snow module (0 or 1). + - CatArea (float): Catchment area in km2. + - conversion_factor (float): Time step conversion + factor (1 for hourly, 0.25 for 15 min, 24 for + daily). + - Maxbas (bool): Whether to use MAXBAS triangular + routing. + - dt (float): Time step duration. + + Routing (int, optional): Flag to enable routing. Set to + 0 to disable, nonzero to enable. Defaults to 0. + RoutingFn (callable): Routing function to apply to the + discharge hydrograph. Must be callable. + + Raises: + AssertionError: If ``RoutingFn`` is not callable when + routing is enabled. """ ### input data validation - assert callable( - RoutingFn - ), "routing function should be of type callable (function that takes arguments)" + if Routing != 0: + assert callable( + RoutingFn + ), "routing function should be of type callable (function that takes arguments)" # data p = Model.data[:, 0]