diff --git a/.gitignore b/.gitignore index 539b012..3024373 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ venv/ *.egg-info/ *.egg PyOCN.egg-info/ +docs/_build/ # compiled markdown /readme_files/ @@ -34,6 +35,8 @@ sandbox.ipynb */__pycache__/ *.py[cod] *.pyo +*.pyc +*.cpython-*.pyc # C build files/libraries PyOCN/c_src/*.o diff --git a/PyOCN/__pycache__/__init__.cpython-311.pyc b/PyOCN/__pycache__/__init__.cpython-311.pyc index 4a7f50e..aeb2167 100644 Binary files a/PyOCN/__pycache__/__init__.cpython-311.pyc and b/PyOCN/__pycache__/__init__.cpython-311.pyc differ diff --git a/PyOCN/_flowgrid_convert.py b/PyOCN/_flowgrid_convert.py index 81bb0b2..0f05233 100644 --- a/PyOCN/_flowgrid_convert.py +++ b/PyOCN/_flowgrid_convert.py @@ -59,8 +59,10 @@ def from_digraph(G: nx.DiGraph, resolution:float=1, verbose:bool=False, validate Whether to check the input graph for validity before conversion. Default True. If false, the user is responsible for ensuring the graph is valid. For internal use only. - Returns: - p_c_graph: pointer to the created C FlowGrid structure. + Returns + ------- + p_c_graph : POINTER + pointer to the created C FlowGrid structure. """ if verbose: @@ -259,11 +261,10 @@ def validate_digraph(dag:nx.DiGraph, verbose:bool=False) -> bool|str: """ Validate the integrity of a FlowGrid. - Parameters: - dag (nx.DiGraph): The directed acyclic graph to validate. - - Returns: - either True if valid, or an error message string if invalid. + Parameters + ---------- + dag : nx.DiGraph + The directed acyclic graph to validate. """ try: p_c_graph = from_digraph(dag, verbose=verbose) @@ -277,11 +278,10 @@ def validate_flowgrid(c_graph:_bindings.FlowGrid_C, verbose:bool=False) -> bool| """ Validate the integrity of a FlowGrid_C structure. - Parameters: - c_graph (FlowGrid_C): The FlowGrid_C structure to validate. - - Returns: - either True if valid, or an error message string if invalid. + Parameters + ---------- + c_graph : _bindings.FlowGrid_C + The FlowGrid_C structure to validate. """ try: dag = to_digraph(c_graph, verbose=verbose) diff --git a/PyOCN/ocn.py b/PyOCN/ocn.py index 22b79a6..bbbe506 100644 --- a/PyOCN/ocn.py +++ b/PyOCN/ocn.py @@ -1,3 +1,22 @@ +import warnings +import ctypes +from typing import Any, Callable, TYPE_CHECKING, Union +from os import PathLike +from numbers import Number +from pathlib import Path + +import networkx as nx +import numpy as np +from tqdm import tqdm + +from ._statushandler import check_status +from .utils import simulated_annealing_schedule, net_type_to_dag, unwrap_digraph, assign_subwatersheds +from . import _libocn_bindings as _bindings +from . import _flowgrid_convert as fgconv + +if TYPE_CHECKING: + import xarray as xr + """ High-level Optimized Channel Network (OCN) interface. @@ -6,8 +25,8 @@ constructing and optimizing river network models using simulated annealing. -Notes ------ +Note +---- - The underlying data structure managed by :class:`OCN` is a FlowGrid owned by ``libocn``. Pointer lifetime and destruction are handled safely within this class. @@ -23,27 +42,12 @@ Helper functions for visualization and plotting """ -""" -TODO: relax the even dims requirement -TODO: have to_rasterio use the option to set the root node to 0,0 by using to_xarray as the backend instead of numpy? -""" -import warnings -import ctypes -from typing import Any, Callable -from os import PathLike -from numbers import Number -from pathlib import Path -from dataclasses import dataclass +# TODO: relax the even dims requirement +# TODO: have to_rasterio use the option to set the root node to 0,0 by using to_xarray as the backend instead of numpy? + -import networkx as nx -import numpy as np -from tqdm import tqdm -from ._statushandler import check_status -from .utils import simulated_annealing_schedule, net_type_to_dag, unwrap_digraph, assign_subwatersheds -from . import _libocn_bindings as _bindings -from . import _flowgrid_convert as fgconv class OCN: """ @@ -52,39 +56,32 @@ class OCN: Use :meth:`OCN.from_net_type` or :meth:`OCN.from_digraph` to construct an instance. - Constructor Methods + Methods ------------------- - :meth:`from_net_type` + from_net_type Create an OCN from a predefined network type and dimensions. - :meth:`from_digraph` + from_digraph Create an OCN from an existing NetworkX DiGraph. - - Export Methods - -------------- - :meth:`to_digraph` + to_digraph Export the current grid to a NetworkX DiGraph. - :meth:`to_numpy` + to_numpy Export raster arrays (energy, drained area, watershed_id) as numpy arrays. - :meth:`to_xarray` + to_xarray Export raster arrays as an xarray Dataset (requires xarray). - :meth:`to_gtiff` + to_gtiff Export raster arrays to a GeoTIFF file (requires rasterio). - :meth:`copy` + copy Create a deep copy of the OCN. - - Optimization Methods - -------------------- - :meth:`single_erosion_event` + single_erosion_event Perform a single erosion event at a given temperature. - :meth:`fit` + fit Optimize the network using the simulated annealing method from Carraro et al (2020). - :meth:`fit_custom_cooling` + fit_custom_cooling Optimize the network using a custom cooling function. - - Other methods - ------------- - :meth:`compute_energy` + compute_energy Compute the current energy of the network. + copy + Create a deep copy of the OCN. Attributes ---------- @@ -98,8 +95,6 @@ class OCN: Number of root nodes in the current OCN grid (read-only property). gamma : float Exponent in the energy model. - master_seed : int - The seed used to initialize the internal RNG. verbosity : int Verbosity level for underlying library output (0-2). wrap : bool @@ -107,6 +102,9 @@ class OCN: history : np.ndarray numpy array of shape (n_iterations, 3) recording the iteration index, energy, and temperature at each iteration during optimization. Updated each time an optimization method is called. + rng : int + the current random state of the internal RNG + Examples -------- @@ -128,8 +126,8 @@ def __init__(self, dag: nx.DiGraph, resolution: float=1.0, gamma: float = 0.5, r """ Construct an :class:`OCN` from a valid NetworkX ``DiGraph``. - Notes - ----- + Attention + --------- Please use the classmethods :meth:`OCN.from_net_type` or :meth:`OCN.from_digraph` to instantiate an OCN. """ @@ -157,7 +155,7 @@ def __init__(self, dag: nx.DiGraph, resolution: float=1.0, gamma: float = 0.5, r self.__history = np.empty((0, 3), dtype=np.float64) @classmethod - def from_net_type(cls, net_type:str, dims:tuple, resolution:float=1, gamma : float = 0.5, random_state=None, verbosity:int=0, wrap : bool = False): + def from_net_type(cls, net_type:str, dims:tuple[int, int], resolution:float=1, gamma : float = 0.5, random_state=None, verbosity:int=0, wrap : bool = False): """ Create an :class:`OCN` from a predefined network type and dimensions. @@ -227,8 +225,8 @@ def from_digraph(cls, dag: nx.DiGraph, resolution:float=1, gamma=0.5, random_sta OCN A newly constructed instance encapsulating the provided graph. - Notes - ----- + Important + --------- The input graph must satisfy all of the following: - It is a directed acyclic graph (DAG). @@ -331,96 +329,35 @@ def compute_energy(self) -> float: ------- float The computed energy value. - - Notes - ----- - This constructs a temporary ``DiGraph`` view to aggregate node - energies from drained areas using the exponent ``gamma``. """ return _bindings.libocn.ocn_compute_energy(self.__p_c_graph, self.gamma) @property def energy(self) -> float: - """ - Energy of the current OCN. - - Returns - ------- - float - Current energy. - """ return self.__p_c_graph.contents.energy @property def resolution(self) -> float: - """ - Resolution of the current OCN grid in m (read-only). - - Returns - ------- - float - Current resolution. - """ return self.__p_c_graph.contents.resolution @property def nroots(self) -> int: - """ - Number of root nodes in the current OCN grid (read-only). - - Returns - ------- - int - Current number of root nodes. - """ return int(self.__p_c_graph.contents.nroots) @property def dims(self) -> tuple[int, int]: - """ - Grid dimensions of the FlowGrid (read-only). - - Returns - ------- - tuple[int, int] - ``(rows, cols)``. - """ return ( int(self.__p_c_graph.contents.dims.row), int(self.__p_c_graph.contents.dims.col) ) @property def wrap(self) -> bool: - """ - Whether the grid allows wrapping around the edges (toroidal). - - Returns - ------- - bool - Current wrap setting. - """ return self.__p_c_graph.contents.wrap @property - def rng(self) -> tuple[int, int, int, int]: - """ - Returns - ------- - tuple[int, int, int, int] - the current random state of the internal RNG as four 32-bit unsigned integers. - """ - - s = tuple(self.__p_c_graph.contents.rng.s) + def rng(self) -> int: + s0, s1, s2, s3 = self.__p_c_graph.contents.rng.s + s = (s0 << 96) | (s1 << 64) | (s2 << 32) | s3 return s @rng.setter def rng(self, random_state:int|None|np.random.Generator=None): - """ - Seed the internal RNG. - - Parameters - ---------- - random_state : int | numpy.random.Generator | None, optional - Seed or generator for RNG. If None, system entropy is used. - If an integer or Generator is provided, the - new seed is drawn from it directly. - """ if not isinstance(random_state, (int, np.integer, type(None), np.random.Generator)): raise ValueError("RNG must be initialized with an integer/Generator/None.") seed = np.random.default_rng(random_state).integers(0, int(2**32 - 1)) @@ -430,15 +367,6 @@ def rng(self, random_state:int|None|np.random.Generator=None): @property def history(self) -> np.ndarray: - """ - numpy array of shape (n_iterations, 3) recording the iteration index, energy, and temperature at each iteration during optimization. - If multiple fit calls are made, history is appended to this array. - - Returns - ------- - np.ndarray - The optimization history. - """ return self.__history def to_digraph(self) -> nx.DiGraph: @@ -665,7 +593,7 @@ def to_xarray(self, unwrap:bool=True) -> "xr.Dataset": "description": "OCN fit result arrays", "resolution": self.resolution, "gamma": self.gamma, - "master_seed": int(self.rng), + "rng": self.rng, "wrap": self.wrap, } ) @@ -726,8 +654,7 @@ def fit( array_reports:int=0, tol:float=None, max_iterations_per_loop=10_000, - unwrap:bool=True, - ) -> "xr.Dataset | None": + unwrap:bool=True,) -> "xr.Dataset | None": """ Convenience function to optimize the OCN using the simulated annealing algorithm from Carraro et al (2020). For finer control over the optimization process, use :meth:`fit_custom_cooling` or use :meth:`single_erosion_event` in a loop. @@ -742,8 +669,6 @@ def fit( Parameters ---------- - ocn : OCN - The OCN instance to optimize. cooling_rate : float, default 1.0 Cooling rate parameter in the annealing algorithm. Typical range is 0.5-1.5. Higher values result in faster cooling and a greedier search. @@ -764,26 +689,7 @@ def fit( Number of timepoints (approximately) at which to save the state of the FlowGrid. If 0 (default), returns None. If >0, returns an xarray.Dataset containing the state of the FlowGrid at approximately evenly spaced intervals - throughout the optimization, including the initial and final states. Requires xarray to be installed. - - The returned xarray.Dataset will have coordinates: - - `y` (float) representing the northing coordinate of each row - - `x` (float) representing the easting coordinate of each column - - `iteration` (int) representing the iteration index at which the data was recorded - data variables: - - `energy_rasters` (np.float64) representing energy at each grid cell - - `area_rasters` (np.float64) representing drained area at each grid cell - - `watershed_id` (np.int32). NA value is -9999. Roots have value -1. Represents the watershed membership ID for each grid cell. - The coordiante (0, 0) is the top-left corner of the grid. - - If the OCN has a periodic boundary condition, the following changes apply: - - The (0,0) coordinate will be set to the position of the "main" root node, defined as - the root node with the smallest row*cols + col value - - The rasters will be unwrapped to a non-periodic representation, which may result in larger rasters. - - The size of the final rasters are the maximum extent of the unwrapped grid, taken across all iterations. - - Generating reports requires additional memory and computation time. - + throughout the optimization, including the initial and final states. Requires xarray to be installed. See notes on xarray output for details. tol : float, optional If provided, optimization will stop early if the relative change in energy between reports is less than `tol`. Must be positive. @@ -803,23 +709,43 @@ def fit( Returns ------- - xr.Dataset | None + ds : xr.Dataset | None + If ``array_reports > 0``, an xarray.Dataset containing the state of the FlowGrid + at approximately evenly spaced intervals throughout the optimization, including + the initial and final states. See notes on xarray output for details. + If ``array_reports == 0``, returns None. - Raises - ------ - ValueError + + + Note + ---- + The returned xarray.Dataset will have coordinates: + + - `y` (float) representing the northing coordinate of each row + - `x` (float) representing the easting coordinate of each column + - `iteration` (int) representing the iteration index at which the data was recorded - Warns - ----- - UserWarning + and data variables: - Optimization Algorithm - ---------------------- + - `energy_rasters` (np.float64) representing energy at each grid cell + - `area_rasters` (np.float64) representing drained area at each grid cell + - `watershed_id` (np.int32). NA value is -9999. Roots have value -1. Represents the watershed membership ID for each grid cell. The coordinate (0, 0) is the top-left corner of the grid. + + If the OCN has a periodic boundary condition, the following changes apply: + + - The (0,0) coordinate will be set to the position of the "main" root node, defined as the root node with the smallest row*cols + col value + - The rasters will be unwrapped to a non-periodic representation, which may result in larger rasters. + - The size of the final rasters are the maximum extent of the unwrapped grid, taken across all iterations. + + Generating reports requires additional memory and computation time. + + Note + ---- At iteration ``i``, the outflow of a random grid cell if proposed to be rerouted. The proposal is accepted with the probability + .. math:: - - P(\text{accept}) = e^{-\Delta E / T}, + P(\\text{accept}) = e^{-\Delta E / T}, where :math:`\Delta E` is the change in energy the change would cause and :math:`T` is the temperature of the network. @@ -833,12 +759,10 @@ def fit( Note that when :math:`\Delta E < 0`, the move is always accepted. - Simulated Annealing Schedule - ---------------------------- The cooling schedule used by this method is a piecewise function of iteration index: + .. math:: - - T(i) = \begin{cases} + T(i) = \\begin{cases} E_0 & i < C N \\ E_0 \cdot e^{\;i - C N} & i \ge C N \end{cases} @@ -898,8 +822,6 @@ def fit_custom_cooling( Parameters ---------- - ocn : OCN - The OCN instance to optimize. cooling_func : Callable[[np.ndarray], np.ndarray] A function that takes an array of iteration indices and returns an array of temperatures. This function defines the cooling schedule for the optimization. Note that the function @@ -920,14 +842,6 @@ def fit_custom_cooling( Returns ------- xr.Dataset | None - - Raises - ------ - ValueError - - Warns - ----- - UserWarning """ # validate inputs if n_iterations is None: @@ -1064,8 +978,8 @@ def fit_custom_cooling( and (e_new <= e_old) and (abs((e_old - e_new)/e_old) if e_old > 0 else np.inf < tol) ): - print("Convergence reached, stopping optimization.") - # if self.verbosity >= 0: + if self.verbosity > 1: + print("Convergence reached, stopping optimization.") break pbar.close() @@ -1127,7 +1041,7 @@ def fit_custom_cooling( "description": "OCN fit result arrays", "resolution": self.resolution, "gamma": self.gamma, - "master_seed": int(self.rng), + "rng": self.rng, "wrap": self.wrap, } ) diff --git a/PyOCN/plotting.py b/PyOCN/plotting.py index d9e928b..a149460 100644 --- a/PyOCN/plotting.py +++ b/PyOCN/plotting.py @@ -4,8 +4,8 @@ This module provides convenience functions to visualize OCNs using Matplotlib and NetworkX. -Notes ------ +Note +---- - Node positions are stored as ``pos=(row, col)`` in the data model. For visualization, these are converted to cartesian coordinates so that (row, col) = (0, 0) is at the bottom-left corner. @@ -39,8 +39,8 @@ def _pos_to_xy(dag: nx.DiGraph) -> dict[Any, tuple[float, float]]: Mapping from node to ``(x, y)`` where ``x=col`` and ``y`` is flipped to plot with origin at the bottom. - Notes - ----- + Note + ---- The vertical coordinate is transformed as ``y = nrows - row - 1`` to match typical plotting conventions. """ diff --git a/PyOCN/utils.py b/PyOCN/utils.py index d63b398..910dd62 100644 --- a/PyOCN/utils.py +++ b/PyOCN/utils.py @@ -4,7 +4,7 @@ from __future__ import annotations from itertools import product -from typing import Any, Literal, Callable, TYPE_CHECKING +from typing import Any, Literal, Callable, TYPE_CHECKING, Union from numbers import Number import networkx as nx import numpy as np @@ -13,15 +13,15 @@ if TYPE_CHECKING: from .ocn import OCN -_allowed_net_types = {"I", "H", "V", "T", "E"} +_allowed_net_types = {"I", "H", "V", "E"} #TODO: add ability to move root? -def net_type_to_dag(net_type:Literal["I", "H", "V", "T", "E"], dims:tuple, pbar: bool = False) -> nx.DiGraph: +def net_type_to_dag(net_type:Literal["I", "H", "V", "E"], dims:tuple, pbar: bool = False) -> nx.DiGraph: """Create a predefined OCN initialization network as a NetworkX DiGraph. Parameters ---------- - net_type : {"I", "H", "V", "T", "E"} + net_type : {"I", "H", "V", "E"} The type of network to create. Descriptions of allowed types: @@ -61,31 +61,12 @@ def net_type_to_dag(net_type:Literal["I", "H", "V", "T", "E"], dims:tuple, pbar: | / X--O--O--O - - "E": A network where every node on the edge of the grid is a root. - - :: - - X X X X X X - \ \ / / - X O O O O X - \ \ / / - X O O O O X - - X O O O O X - / / \ \ - X O O O O X - / / \ \ - X X X X X X - - - - "T": Not implemented yet. + - "E": A network where every node on the edge of the grid is a root. Initial flow moves away from center towards edges. dims : tuple The network dimensions as ``(rows, cols)``. Both must be positive even integers. pbar : bool, default False If True, display a progress bar while constructing the graph. - wrap : bool, default False - If True, create the graph with periodic boundary conditions (toroidal). Returns ------- @@ -97,8 +78,8 @@ def net_type_to_dag(net_type:Literal["I", "H", "V", "T", "E"], dims:tuple, pbar: ValueError If ``net_type`` is invalid or ``dims`` are not two positive even integers. - Notes - ----- + Note + ---- The returned graph assigns each grid cell exactly one node with a ``pos`` attribute equal to ``(row, col)``. """ @@ -168,14 +149,9 @@ def net_type_to_dag(net_type:Literal["I", "H", "V", "T", "E"], dims:tuple, pbar: return G -def simulated_annealing_schedule( - dims: tuple[int, int], - E0: float, - constant_phase: float, - n_iterations: int, - cooling_rate: float, -) -> Callable[[int], float | np.ndarray]: - """Create a simulated-annealing cooling schedule for OCN optimization. +def simulated_annealing_schedule(dims: tuple[int, int],E0: float,constant_phase: float,n_iterations: int,cooling_rate: float,) -> Callable[[int], Union[float, np.ndarray]]: + """ + Create a simulated-annealing cooling schedule for OCN optimization. This returns a callable ``schedule(i)`` that returns the temperature at iteration ``i``. The schedule consists of a constant-temperature phase @@ -198,20 +174,20 @@ def simulated_annealing_schedule( Returns ------- - Callable[[int], float] | numpy.ndarray + Callable[[int], Union[float, np.ndarray]] A function mapping an iteration index ``i`` to a temperature value. If vectorized evaluation is used, may return a NumPy array of temperatures. - Notes - ----- + Note + ---- The exponential phase follows the form .. math:: - T_i = E_0 \exp\left(-\frac{\text{cooling\_rate}\,(i - n_0)}{N}\right), + T_i = E_0 \exp\left(-\\frac{r\cdot(i - n_0)}{N}\\right), where ``E0`` is the initial energy, ``n0`` is the number of iterations in - the constant phase, and ``N = rows * cols``. + the constant phase, ``r`` is the cooling rate,and ``N = rows * cols``. """ if (not isinstance(constant_phase, Number) or constant_phase < 0 or constant_phase > 1): raise ValueError(f"constant_phase must be a number between 0 and 1. Got {constant_phase}") @@ -260,8 +236,8 @@ def unwrap_digraph(dag: nx.DiGraph, dims: tuple[int, int]) -> nx.DiGraph: If any node in the input graph lacks a 'pos' attribute or if the dimensions are not positive integers. - Notes - ----- + Important + --------- The function assumes that the input graph is a valid DAG and that the 'pos' attributes are correctly assigned. The output graph will no longer span a toroidal topology and will no longer cover a dense grid of nodes. @@ -340,8 +316,8 @@ def assign_subwatersheds(dag: nx.DiGraph) -> None: ValueError If any node in the input graph lacks a 'pos' attribute. - Notes - ----- + Note + ---- A subwatershed is defined as the set of nodes that drain to a common outlet, where an outlet is a node with out-degree zero. Each subwatershed is assigned a unique integer ID, starting from 0. Nodes that are outlets themselves are @@ -373,8 +349,8 @@ def get_subwatersheds(dag : nx.DiGraph, node : Any) -> set[nx.DiGraph]: set of nx.DiGraph A set of directed acyclic graphs, each representing a subwatershed. - Note - ---- + Danger + ------ The returned subwatersheds are subgraph views of the input graph and share node and edge data with the original graph. Unless copied, any changes to node or edge attributes in the subwatersheds will affect the original graph. diff --git a/docs/conf.py b/docs/conf.py index 1fa4726..cce89a2 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,12 +18,13 @@ # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration +# autodoc_mock_imports = ["matplotlib", "networkx", "numpy", "xarray", "rasterio", "tqdm"] extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.viewcode', 'sphinx.ext.napoleon', - 'sphinx_autodoc_typehints', # pip install sphinx-autodoc-typehints + # 'sphinx_autodoc_typehints', # pip install sphinx-autodoc-typehints 'myst_parser', # pip install myst-parser ] # MyST settings diff --git a/docs/index.rst b/docs/index.rst index 541391b..eded392 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -9,15 +9,6 @@ PyOCN Documentation This is a package to generate optimal channel networks (OCNs), based on the algorithm described in Carraro et al. (2020). *Generation and application of river network analogues for use in ecology and evolution. Ecology and Evolution.* doi:10.1002/ece3.6479 and mirrors some of the functionality of the OCNet R package (https://lucarraro.github.io/OCNet/). -.. toctree:: - :maxdepth: 2 - :caption: Contents: - - installation - quickstart - api - examples - Installation ============ @@ -50,28 +41,17 @@ Quick Start po.plot_ocn_raster(ocn) plt.show() -API Reference -============= - -.. automodule:: PyOCN - :members: - :undoc-members: - :show-inheritance: - -OCN Class ---------- - -.. autoclass:: PyOCN.OCN - :members: - :undoc-members: - :show-inheritance: +.. toctree:: + :maxdepth: 2 + :caption: API Reference: -Plotting Functions ------------------- + ocn_class + plotting + utilities -.. autofunction:: PyOCN.plot_ocn_raster -.. autofunction:: PyOCN.plot_ocn_as_dag -.. autofunction:: PyOCN.plot_positional_digraph +.. toctree:: + :maxdepth: 1 + :caption: Additional: Indices and tables ================== diff --git a/docs/ocn_class.rst b/docs/ocn_class.rst new file mode 100644 index 0000000..78547e9 --- /dev/null +++ b/docs/ocn_class.rst @@ -0,0 +1,29 @@ +OCN Class +========= + +The :class:`OCN` class is the main interface for creating and optimizing channel networks. + +Basic Usage +----------- + +.. code-block:: python + + import PyOCN as po + + # Create from a network type + ocn = po.OCN.from_net_type("I", dims=(64, 64), gamma=0.5) + + # Optimize the network + ocn.fit() + + # Access properties + print(f"Final energy: {ocn.energy}") + +Class Reference +--------------- + +.. autoclass:: PyOCN.OCN + :members: + :undoc-members: + :show-inheritance: + :special-members: __init__ \ No newline at end of file diff --git a/docs/plotting.rst b/docs/plotting.rst new file mode 100644 index 0000000..96c205b --- /dev/null +++ b/docs/plotting.rst @@ -0,0 +1,44 @@ +Plotting Functions +================== + +PyOCN provides several visualization functions for exploring and analyzing channel networks. + +Overview +-------- + +The plotting module offers three main visualization approaches: + +* **Raster plots**: Show the network as an array +* **Graph plots**: Display the network structure as a directed graph +* **Positional plots**: Combine spatial positioning with graph structure + +Basic Examples +-------------- + +.. code-block:: python + + import PyOCN as po + import matplotlib.pyplot as plt + + # Create and optimize a network + ocn = po.OCN.from_net_type("I", dims=(32, 32), gamma=0.5) + ocn.fit() + + # Raster visualization + fig, ax = plt.subplots() + po.plot_ocn_raster(ocn, ax=ax) + plt.show() + + # Graph visualization + fig, ax = plt.subplots() + po.plot_ocn_as_dag(ocn, ax=ax) + plt.show() + +Function Reference +------------------ + +.. autofunction:: PyOCN.plot_ocn_raster + +.. autofunction:: PyOCN.plot_ocn_as_dag + +.. autofunction:: PyOCN.plot_positional_digraph \ No newline at end of file diff --git a/docs/utilities.rst b/docs/utilities.rst new file mode 100644 index 0000000..5da427a --- /dev/null +++ b/docs/utilities.rst @@ -0,0 +1,60 @@ +Utility Functions +================= + +PyOCN includes several utility functions for working with channel networks, optimization schedules, and network analysis. + +Network Generation +------------------ + +.. autofunction:: PyOCN.utils.net_type_to_dag + +Optimization +------------ + +.. autofunction:: PyOCN.utils.simulated_annealing_schedule + +Example usage: + +.. code-block:: python + + import PyOCN as po + + ocn = po.OCN.from_net_type("I", dims=(64, 64)) + n_iterations = 50000 + # Create a custom cooling schedule + schedule = po.utils.simulated_annealing_schedule( + dims=(64, 64), + E0=ocn.energy, + constant_phase=0.1, + n_iterations=n_iterations, + cooling_rate=1.0 + ) + + # Use in optimization + ocn.fit_custom_cooling(cooling_func=schedule, n_iterations=n_iterations) + +Network Analysis +---------------- + +.. autofunction:: PyOCN.utils.unwrap_digraph + +.. autofunction:: PyOCN.utils.assign_subwatersheds + +.. autofunction:: PyOCN.utils.get_subwatersheds + +Example watershed analysis: + +.. code-block:: python + + import PyOCN as po + + # Create and optimize network + ocn = po.OCN.from_net_type("I", dims=(32, 32)) + ocn.fit(n_iterations=10000) + + # Get the network as a graph + dag = ocn.to_dag() + + # Analyze subwatersheds + subwatersheds = po.utils.get_subwatersheds(dag, node=5) + print(f"Found {len(subwatersheds)} outlet nodes") \ No newline at end of file diff --git a/tests/test_basic.py b/tests/test_basic.py index 694e787..8397564 100644 --- a/tests/test_basic.py +++ b/tests/test_basic.py @@ -89,7 +89,7 @@ def test_ocn_rng(self): dims=(64, 64), random_state=8472, ) - expected_rng = (510574073, 2087720647, 3836914231, 3781483648) + expected_rng = 40451845669725511932870495160880127104 self.assertEqual( ocn.rng,