From 923b4dfe5dda6f4ec2e532b1f3322e69f4841fe8 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 22:06:34 -0500 Subject: [PATCH 01/26] Mark MaxPool2D's docstring raw so its LaTeX macros survive --- pytensor_ml/layers/conv.py | 6 ++--- tests/test_docstrings.py | 48 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 tests/test_docstrings.py diff --git a/pytensor_ml/layers/conv.py b/pytensor_ml/layers/conv.py index 03151fd..cdf2767 100644 --- a/pytensor_ml/layers/conv.py +++ b/pytensor_ml/layers/conv.py @@ -1118,11 +1118,11 @@ def __call__(self, X: pt.TensorLike) -> TensorVariable: class MaxPool2D(_PoolNd): - """ + r""" Downsample by taking the largest activation in each window, over two spatial axes. Takes ``(batch, height, width, channels)`` and returns ``(batch, out_height, out_width, channels)``. - Padding fills with :math:`-\\infty` so a padded position never wins a window, which zero-filling + Padding fills with :math:`-\infty` so a padded position never wins a window, which zero-filling would do wherever every real activation is negative. Where a window ties, the whole gradient goes to the earliest tap; a backend that pools with its own kernel may instead split the gradient between the tied taps, but every backend returns exactly the gradient the window received. @@ -1139,7 +1139,7 @@ class MaxPool2D(_PoolNd): dilation : int or tuple of int, optional Spacing between the positions a window covers. Default is 1. padding : {"valid", "same"}, int, or tuple of int, optional - No padding, enough to leave each output extent at :math:`\\lceil \text{extent} / s \rceil`, or + No padding, enough to leave each output extent at :math:`\lceil \text{extent} / s \rceil`, or an explicit number of elements on each side. Default is "valid". """ diff --git a/tests/test_docstrings.py b/tests/test_docstrings.py new file mode 100644 index 0000000..cda90df --- /dev/null +++ b/tests/test_docstrings.py @@ -0,0 +1,48 @@ +import importlib +import pkgutil + +import pytest + +import pytensor_ml + +# A LaTeX macro in a docstring that forgot its `r` prefix silently becomes a control character: +# `\text` is a tab, `\rceil` a carriage return, `\b` a backspace. The docstring still renders, just +# wrong, so nothing but a scan like this catches it. +CONTROL_CHARACTERS = {"\t": r"\t", "\r": r"\r", "\x08": r"\b", "\x0c": r"\f", "\x0b": r"\v"} + + +# Importing a backend dispatch module pulls in the backend itself, and the core test jobs deliberately +# install none of them. +BACKEND_DISPATCH = "pytensor_ml.dispatch." + + +def _public_objects() -> list[tuple[str, object]]: + objects: list[tuple[str, object]] = [] + for module_info in pkgutil.walk_packages(pytensor_ml.__path__, f"{pytensor_ml.__name__}."): + if module_info.name.startswith(BACKEND_DISPATCH): + continue + module = importlib.import_module(module_info.name) + objects.append((module_info.name, module)) + for name, obj in vars(module).items(): + # Skip re-exports so each object is checked once, under the module that defines it. + if not name.startswith("_") and getattr(obj, "__module__", None) == module_info.name: + objects.append((f"{module_info.name}.{name}", obj)) + return objects + + +PUBLIC_OBJECTS = _public_objects() + + +@pytest.mark.parametrize( + "qualified_name, obj", PUBLIC_OBJECTS, ids=[name for name, _ in PUBLIC_OBJECTS] +) +def test_docstring_has_no_control_characters(qualified_name, obj): + docstring = getattr(obj, "__doc__", None) + if not docstring: + return + + found = {escape for character, escape in CONTROL_CHARACTERS.items() if character in docstring} + assert not found, ( + f"{qualified_name} has {sorted(found)} in its docstring, which means a LaTeX macro was " + f'interpreted as an escape sequence. Mark the docstring raw: r"""..."""' + ) From 0d6c0352111a0e7dbec10c7ea61c15ffe3578ad3 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 22:06:56 -0500 Subject: [PATCH 02/26] Name every docstring return value --- pytensor_ml/json_serialize.py | 2 +- pytensor_ml/layers/attention.py | 2 +- pytensor_ml/layers/combinators.py | 2 +- pytensor_ml/layers/conv.py | 8 ++++---- pytensor_ml/layers/padding.py | 2 +- pytensor_ml/layers/recurrent.py | 8 ++++---- pytensor_ml/optim/base.py | 4 ++-- pytensor_ml/optim/clipping.py | 4 ++-- pytensor_ml/optim/guards.py | 2 +- pytensor_ml/optim/policy.py | 2 +- pytensor_ml/optim/rules.py | 18 +++++++++--------- pytensor_ml/optim/schedules.py | 14 +++++++------- pytensor_ml/optim/transform.py | 8 ++++---- pytensor_ml/pytensorf/collect.py | 2 +- pytensor_ml/pytensorf/compile.py | 4 ++-- pytensor_ml/pytensorf/rewrite.py | 4 ++-- pytensor_ml/pytensorf/rng.py | 4 ++-- pytensor_ml/rewriting/scan.py | 2 +- pytensor_ml/state.py | 4 ++-- 19 files changed, 48 insertions(+), 48 deletions(-) diff --git a/pytensor_ml/json_serialize.py b/pytensor_ml/json_serialize.py index ecdaedb..ed5090a 100644 --- a/pytensor_ml/json_serialize.py +++ b/pytensor_ml/json_serialize.py @@ -40,7 +40,7 @@ def serialize_graph(inputs: Sequence[Variable], outputs: Sequence[Variable]) -> Returns ------- - dict + serialized_graph : dict A JSON-native description of the graph's structure (no parameter values). """ return graph_to_json(list(inputs), list(outputs)) diff --git a/pytensor_ml/layers/attention.py b/pytensor_ml/layers/attention.py index b4516ec..6fae51c 100644 --- a/pytensor_ml/layers/attention.py +++ b/pytensor_ml/layers/attention.py @@ -110,7 +110,7 @@ def scaled_dot_product_attention( Returns ------- - TensorVariable + output : TensorVariable Attention output, shape ``(..., n_head, q_len, v_dim)``. """ q, k, v = (pt.as_tensor(t).copy() for t in (q, k, v)) diff --git a/pytensor_ml/layers/combinators.py b/pytensor_ml/layers/combinators.py index 655a4ce..8612c87 100644 --- a/pytensor_ml/layers/combinators.py +++ b/pytensor_ml/layers/combinators.py @@ -41,7 +41,7 @@ def Flatten(X: pt.TensorLike) -> pt.TensorVariable: Returns ------- - TensorVariable + flattened : TensorVariable Shape ``(batch, features)``, with ``features`` the product of every remaining axis. """ return pt.join_dims(X, start_axis=1) diff --git a/pytensor_ml/layers/conv.py b/pytensor_ml/layers/conv.py index cdf2767..382f975 100644 --- a/pytensor_ml/layers/conv.py +++ b/pytensor_ml/layers/conv.py @@ -304,7 +304,7 @@ def _extract_patches( Returns ------- - TensorVariable + patches : TensorVariable Shape ``(batch, *out_spatial, *kernel_size, channels)``, where ``out_spatial`` counts the windows that fit. """ @@ -772,7 +772,7 @@ def __call__(self, X: pt.TensorLike) -> TensorVariable: Returns ------- - TensorVariable + output : TensorVariable Shape ``(batch, *out_spatial, out_channels)``. """ X = pt.as_tensor(X) @@ -938,7 +938,7 @@ def __call__(self, X: pt.TensorLike) -> TensorVariable: Returns ------- - TensorVariable + output : TensorVariable Shape ``(batch, *out_spatial, out_channels)``, where each output axis is ``(spatial - 1) * stride - 2 * padding + dilation * (kernel_size - 1) + output_padding + 1``. """ @@ -1104,7 +1104,7 @@ def __call__(self, X: pt.TensorLike) -> TensorVariable: Returns ------- - TensorVariable + pooled : TensorVariable Shape ``(batch, *out_spatial, channels)``, with the channel axis untouched. """ X = pt.as_tensor(X) diff --git a/pytensor_ml/layers/padding.py b/pytensor_ml/layers/padding.py index 29c26d0..1286cde 100644 --- a/pytensor_ml/layers/padding.py +++ b/pytensor_ml/layers/padding.py @@ -114,7 +114,7 @@ def __call__(self, X: pt.TensorLike) -> TensorVariable: Returns ------- - TensorVariable + padded : TensorVariable Shape ``(batch, *padded_spatial, channels)``, with batch and channels untouched. """ X = pt.as_tensor(X) diff --git a/pytensor_ml/layers/recurrent.py b/pytensor_ml/layers/recurrent.py index dc5955b..9fcc42c 100644 --- a/pytensor_ml/layers/recurrent.py +++ b/pytensor_ml/layers/recurrent.py @@ -41,7 +41,7 @@ def step(self, x_t: TensorVariable, *state: TensorVariable) -> tuple[TensorVaria Returns ------- - tuple of TensorVariable + state : tuple of TensorVariable The new state, in the same order. Its first element is the cell's output, which is what :class:`Recurrent` stacks over time -- an LSTM carrying :math:`(h, c)` returns :math:`h` first. """ @@ -56,7 +56,7 @@ def initial_state(self, X: TensorVariable) -> tuple[TensorVariable, ...]: Returns ------- - tuple of TensorVariable + state : tuple of TensorVariable Zero-filled state, in the order :meth:`step` takes and returns it. """ @@ -115,7 +115,7 @@ def __call__( Returns ------- - TensorVariable + outputs : TensorVariable The cell's output at each step, shape ``(..., time, n_out)``. """ reverse = self.reverse if reverse is None else reverse @@ -766,7 +766,7 @@ def __call__(self, X: pt.TensorLike, *, mask: pt.TensorLike | None = None) -> Te Returns ------- - TensorVariable + outputs : TensorVariable Both directions' outputs, shape ``(..., time, n_forward + n_backward)``. """ out = pt.concatenate( diff --git a/pytensor_ml/optim/base.py b/pytensor_ml/optim/base.py index 26c43a1..18425a7 100644 --- a/pytensor_ml/optim/base.py +++ b/pytensor_ml/optim/base.py @@ -79,7 +79,7 @@ def get_gradients( Returns ------- - list of TensorVariable + gradients : list of TensorVariable One gradient per parameter, in the order of ``parameters``. """ if isinstance(loss_or_gradients, list | tuple): @@ -291,7 +291,7 @@ def chain(head, *rest: Transform): Returns ------- - UpdateRule or Transform + chained : UpdateRule or Transform A callable matching the head, applying every argument in sequence. """ diff --git a/pytensor_ml/optim/clipping.py b/pytensor_ml/optim/clipping.py index 61bf1af..a17c01c 100644 --- a/pytensor_ml/optim/clipping.py +++ b/pytensor_ml/optim/clipping.py @@ -20,7 +20,7 @@ def clip_by_global_norm(max_norm: float = 1.0) -> Transform: Returns ------- - Transform + transform : Transform A transform that clips the updates dict by global norm. """ @@ -49,7 +49,7 @@ def clip_by_value(min_value: float = -1.0, max_value: float = 1.0) -> Transform: Returns ------- - Transform + transform : Transform A transform that clips the updates dict element-wise. """ diff --git a/pytensor_ml/optim/guards.py b/pytensor_ml/optim/guards.py index ba86a8b..b9b5e15 100644 --- a/pytensor_ml/optim/guards.py +++ b/pytensor_ml/optim/guards.py @@ -160,7 +160,7 @@ def skip_if( Returns ------- - UpdateRule + guarded_rule : UpdateRule The guarded rule, which also writes both skip counters. """ if max_consecutive_skips is not None and max_consecutive_skips < 1: diff --git a/pytensor_ml/optim/policy.py b/pytensor_ml/optim/policy.py index 7c0de6e..dc4fab8 100644 --- a/pytensor_ml/optim/policy.py +++ b/pytensor_ml/optim/policy.py @@ -67,7 +67,7 @@ def reduce_on_plateau( Returns ------- - UpdateRule + wrapped_rule : UpdateRule The wrapped rule, which also writes the scale and the policy's own history. """ if not 0.0 < factor < 1.0: diff --git a/pytensor_ml/optim/rules.py b/pytensor_ml/optim/rules.py index 8a1dfe2..8dcba27 100644 --- a/pytensor_ml/optim/rules.py +++ b/pytensor_ml/optim/rules.py @@ -41,7 +41,7 @@ def sgd_updates( Returns ------- - Updates + updates : Updates Mapping from each parameter to its next value. """ gradients = get_gradients(loss_or_gradients, parameters) @@ -90,7 +90,7 @@ def adam_updates( Returns ------- - Updates + updates : Updates Mapping from each parameter and its moment buffers to their next values. """ return _adam_family_updates( @@ -239,7 +239,7 @@ def adamw_updates( Returns ------- - Updates + updates : Updates Mapping from each parameter and its moment buffers to their next values. """ return _adam_family_updates( @@ -292,7 +292,7 @@ def nadam_updates( Returns ------- - Updates + updates : Updates Mapping from each parameter and its moment buffers to their next values. """ gradients = get_gradients(loss_or_gradients, parameters) @@ -361,7 +361,7 @@ def adamax_updates( Returns ------- - Updates + updates : Updates Mapping from each parameter and its state buffers to their next values. """ gradients = get_gradients(loss_or_gradients, parameters) @@ -416,7 +416,7 @@ def adagrad_updates( Returns ------- - Updates + updates : Updates Mapping from each parameter and its accumulator to their next values. """ gradients = get_gradients(loss_or_gradients, parameters) @@ -474,7 +474,7 @@ def rmsprop_updates( Returns ------- - Updates + updates : Updates Mapping from each parameter and its state buffers to their next values. """ gradients = get_gradients(loss_or_gradients, parameters) @@ -538,7 +538,7 @@ def adadelta_updates( Returns ------- - Updates + updates : Updates Mapping from each parameter and its two accumulators to their next values. """ gradients = get_gradients(loss_or_gradients, parameters) @@ -625,7 +625,7 @@ def rprop_updates( Returns ------- - Updates + updates : Updates Mapping from each parameter and its state buffers to their next values. """ _require_numeric_learning_rate(learning_rate) diff --git a/pytensor_ml/optim/schedules.py b/pytensor_ml/optim/schedules.py index 182cddf..9fed3a0 100644 --- a/pytensor_ml/optim/schedules.py +++ b/pytensor_ml/optim/schedules.py @@ -60,7 +60,7 @@ def cosine_schedule( Returns ------- - Schedule + schedule : Schedule A callable mapping a symbolic step count to a scalar learning rate, ready to hand to a rule as its ``learning_rate``. @@ -124,7 +124,7 @@ def linear_schedule( Returns ------- - Schedule + schedule : Schedule A callable mapping a symbolic step count to a scalar learning rate, ready to hand to a rule as its ``learning_rate``. """ @@ -178,7 +178,7 @@ def exponential_schedule( Returns ------- - Schedule + schedule : Schedule A callable mapping a symbolic step count to a scalar learning rate, ready to hand to a rule as its ``learning_rate``. """ @@ -241,7 +241,7 @@ def polynomial_schedule( Returns ------- - Schedule + schedule : Schedule A callable mapping a symbolic step count to a scalar learning rate, ready to hand to a rule as its ``learning_rate``. """ @@ -299,7 +299,7 @@ def step_decay( Returns ------- - Schedule + schedule : Schedule A callable mapping a symbolic step count to a scalar learning rate, ready to hand to a rule as its ``learning_rate``. """ @@ -336,7 +336,7 @@ def constant_schedule(learning_rate: float) -> Schedule: Returns ------- - Schedule + schedule : Schedule A callable mapping a symbolic step count to a scalar learning rate, ready to hand to a rule as its ``learning_rate``. """ @@ -373,7 +373,7 @@ def join_schedules(schedules: Sequence[Schedule], boundaries: Sequence[int]) -> Returns ------- - Schedule + schedule : Schedule A callable mapping a symbolic step count to a scalar learning rate, ready to hand to a rule as its ``learning_rate``. """ diff --git a/pytensor_ml/optim/transform.py b/pytensor_ml/optim/transform.py index 36244cb..d84b354 100644 --- a/pytensor_ml/optim/transform.py +++ b/pytensor_ml/optim/transform.py @@ -29,7 +29,7 @@ def trace(decay: float = 0.9, nesterov: bool = False) -> Transform: Returns ------- - Transform + transform : Transform A transform that folds momentum into the updates dict. """ @@ -61,7 +61,7 @@ def scale(factor: Rate) -> Transform: Returns ------- - Transform + transform : Transform A transform that rescales the updates dict. """ @@ -104,7 +104,7 @@ def scale_by_schedule(schedule: Schedule, *, namespace: str = "scale_by_schedule Returns ------- - Transform + transform : Transform A transform that rescales the updates dict by the rate its clock currently reads. """ @@ -136,7 +136,7 @@ def add_weight_decay( Returns ------- - Transform + transform : Transform A transform that folds weight decay into the updates dict. """ diff --git a/pytensor_ml/pytensorf/collect.py b/pytensor_ml/pytensorf/collect.py index f984e5d..8524df2 100644 --- a/pytensor_ml/pytensorf/collect.py +++ b/pytensor_ml/pytensorf/collect.py @@ -75,7 +75,7 @@ def collect_differentiable_params( Returns ------- - list of TrainableParameter + parameters : list of TrainableParameter The differentiable parameters, in graph-input order. """ output_list = as_output_list(outputs) diff --git a/pytensor_ml/pytensorf/compile.py b/pytensor_ml/pytensorf/compile.py index f1cd0ea..d93e428 100644 --- a/pytensor_ml/pytensorf/compile.py +++ b/pytensor_ml/pytensorf/compile.py @@ -52,7 +52,7 @@ def function( Returns ------- - Function + compiled_function : Function The compiled function. """ updates = dict(kwargs.pop("updates", {})) @@ -144,7 +144,7 @@ def compile_predict( Returns ------- - Function + predict_function : Function The compiled prediction function. """ specialized = rewrite_for_prediction(prediction) diff --git a/pytensor_ml/pytensorf/rewrite.py b/pytensor_ml/pytensorf/rewrite.py index ea972a0..fb877c3 100644 --- a/pytensor_ml/pytensorf/rewrite.py +++ b/pytensor_ml/pytensorf/rewrite.py @@ -19,7 +19,7 @@ def hoist_scan_draws(outputs): Returns ------- - list of Variable + rewritten : list of Variable The rewritten graphs, in the order given. """ fgraph = FunctionGraph(outputs=list(outputs), clone=True, copy_inputs=False) @@ -54,7 +54,7 @@ def rewrite_for_prediction(graph): Returns ------- - FunctionGraph, Variable, or list of Variable + specialized_graph : FunctionGraph, Variable, or list of Variable The specialized graph, matching the form of ``graph``. A FunctionGraph is rewritten in place and returned; a Variable or sequence is rewritten on a clone, leaving the original untouched. """ diff --git a/pytensor_ml/pytensorf/rng.py b/pytensor_ml/pytensorf/rng.py index 5595648..fef47dc 100644 --- a/pytensor_ml/pytensorf/rng.py +++ b/pytensor_ml/pytensorf/rng.py @@ -63,7 +63,7 @@ def find_generators_drawn_from( Returns ------- - list of RandomGeneratorSharedVariable + generators : list of RandomGeneratorSharedVariable The generators a draw op consumes, in graph-input order. """ fgraph = FunctionGraph(outputs=list(outputs), clone=False) @@ -110,7 +110,7 @@ def collect_default_updates( Returns ------- - dict mapping Variable to Variable + updates : dict mapping Variable to Variable Each RNG variable to the expression for its next state. """ diff --git a/pytensor_ml/rewriting/scan.py b/pytensor_ml/rewriting/scan.py index 0d1abc5..9b2823d 100644 --- a/pytensor_ml/rewriting/scan.py +++ b/pytensor_ml/rewriting/scan.py @@ -120,7 +120,7 @@ def hoist_draws_out_of_scan(fgraph: FunctionGraph, node: Apply) -> list[Variable Returns ------- - list of Variable or None + outputs : list of Variable or None The rebuilt scan's outputs, or None when the loop has no such draw. """ args = ScanArgs.from_node(node, clone=True) diff --git a/pytensor_ml/state.py b/pytensor_ml/state.py index aaad761..f6cb93a 100644 --- a/pytensor_ml/state.py +++ b/pytensor_ml/state.py @@ -287,7 +287,7 @@ def initializer(sample_fn: Callable[..., np.ndarray]) -> type[Initializer]: Returns ------- - type of Initializer + initializer_class : type of Initializer A class to instantiate with the parameters, e.g. ``scaled_normal(std=0.02)``. Examples @@ -427,7 +427,7 @@ def initialize_params( Returns ------- - list of ndarray + values : list of ndarray Drawn values, matching the shapes and dtypes of ``params``. """ # Resolve once and share: a seed handed to each _sample_like call would repeat draws across parameters. From e22daad511dc0f13e4fcf74c48ab2c117017ca9c Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 22:07:03 -0500 Subject: [PATCH 03/26] Wrap Squeeze and Concatenate so they document their own contract --- pytensor_ml/layers/combinators.py | 44 ++++++++++++++++++++++++++++--- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/pytensor_ml/layers/combinators.py b/pytensor_ml/layers/combinators.py index 8612c87..ef86316 100644 --- a/pytensor_ml/layers/combinators.py +++ b/pytensor_ml/layers/combinators.py @@ -1,4 +1,4 @@ -from collections.abc import Callable +from collections.abc import Callable, Sequence import pytensor.tensor as pt @@ -47,5 +47,43 @@ def Flatten(X: pt.TensorLike) -> pt.TensorVariable: return pt.join_dims(X, start_axis=1) -Squeeze = pt.squeeze -Concatenate = pt.concatenate +def Squeeze(X: pt.TensorLike, axis: int | Sequence[int] | None = None) -> pt.TensorVariable: + """ + Drop length-1 axes, so a layer that emits a singleton axis feeds one that does not expect it. + + Parameters + ---------- + X : TensorLike + Tensor to squeeze. + axis : int or sequence of int, optional + Axes to drop. An axis whose length is statically known to be anything but 1 is rejected as the + graph is built; an axis of unknown length is accepted and checked when the function runs. + Default None, which drops every axis already known to have length 1 and leaves the rest. + + Returns + ------- + squeezed : TensorVariable + ``X`` with the selected axes removed. + """ + return pt.squeeze(X, axis=axis) + + +def Concatenate(tensors: Sequence[pt.TensorLike], axis: int = 0) -> pt.TensorVariable: + """ + Join tensors end to end along one axis. + + Parameters + ---------- + tensors : sequence of TensorLike + Tensors to join. Every one must agree in rank, and in size on every axis but ``axis``. + axis : int, optional + Axis to join along; negative values count from the right. Default 0, which for a batched + activation is the batch axis -- merging two ``(batch, features)`` branches feature-wise wants + ``axis=-1``. + + Returns + ------- + joined : TensorVariable + The inputs joined, with extent along ``axis`` equal to the sum of the inputs' extents. + """ + return pt.concatenate(tensors, axis=axis) From 94ba8b057f208a709a6855dcb896d81894e67454 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 22:07:13 -0500 Subject: [PATCH 04/26] Retitle the MNIST example and plot its confusion matrix in matplotlib --- examples/mnist_feed_forward.ipynb | 635 ++++++++++++++++++++++++++++-- 1 file changed, 598 insertions(+), 37 deletions(-) diff --git a/examples/mnist_feed_forward.ipynb b/examples/mnist_feed_forward.ipynb index 043384f..a1e828c 100644 --- a/examples/mnist_feed_forward.ipynb +++ b/examples/mnist_feed_forward.ipynb @@ -1,21 +1,28 @@ { "cells": [ { - "cell_type": "code", - "execution_count": null, - "id": "15279e7f", + "cell_type": "markdown", + "id": "intro", "metadata": {}, - "outputs": [], "source": [ - "%load_ext autoreload\n", - "%autoreload 2" + "# Feed-forward classifier on MNIST digits\n", + "\n", + "Train a fully-connected network with batch normalization and dropout on scikit-learn's\n", + "handwritten digits, then evaluate it with a confusion matrix." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "00915a61", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T02:48:52.027191Z", + "iopub.status.busy": "2026-08-25T02:48:52.027111Z", + "iopub.status.idle": "2026-08-25T02:48:52.173089Z", + "shell.execute_reply": "2026-08-25T02:48:52.172683Z" + } + }, "outputs": [], "source": [ "import pytensor\n", @@ -26,9 +33,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "fae24c05", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T02:48:52.174352Z", + "iopub.status.busy": "2026-08-25T02:48:52.174243Z", + "iopub.status.idle": "2026-08-25T02:48:53.229547Z", + "shell.execute_reply": "2026-08-25T02:48:53.228886Z" + } + }, "outputs": [], "source": [ "import numpy as np\n", @@ -46,9 +60,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "65882b64", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T02:48:53.230866Z", + "iopub.status.busy": "2026-08-25T02:48:53.230715Z", + "iopub.status.idle": "2026-08-25T02:48:53.240267Z", + "shell.execute_reply": "2026-08-25T02:48:53.239910Z" + } + }, "outputs": [], "source": [ "X, y = load_digits(return_X_y=True)\n", @@ -58,9 +79,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "f81f9076", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T02:48:53.241415Z", + "iopub.status.busy": "2026-08-25T02:48:53.241358Z", + "iopub.status.idle": "2026-08-25T02:48:53.244263Z", + "shell.execute_reply": "2026-08-25T02:48:53.243923Z" + } + }, "outputs": [], "source": [ "X_in = Input(\"X_in\", shape=(None, 64))" @@ -68,10 +96,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "41840fac", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T02:48:53.245236Z", + "iopub.status.busy": "2026-08-25T02:48:53.245172Z", + "iopub.status.idle": "2026-08-25T02:48:53.279826Z", + "shell.execute_reply": "2026-08-25T02:48:53.279416Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "prediction_network = Sequential(\n", " Linear(\"Linear_1\", n_in=64, n_out=256),\n", @@ -92,9 +138,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "3bfa0ddc", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T02:48:53.280917Z", + "iopub.status.busy": "2026-08-25T02:48:53.280849Z", + "iopub.status.idle": "2026-08-25T02:48:53.282487Z", + "shell.execute_reply": "2026-08-25T02:48:53.282211Z" + } + }, "outputs": [], "source": [ "loss_fn = CrossEntropy(expect_onehot_labels=True, expect_logits=True, reduction=\"mean\")" @@ -102,9 +155,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "290d3191", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T02:48:53.283397Z", + "iopub.status.busy": "2026-08-25T02:48:53.283336Z", + "iopub.status.idle": "2026-08-25T02:48:53.747712Z", + "shell.execute_reply": "2026-08-25T02:48:53.747271Z" + } + }, "outputs": [], "source": [ "# `model.compile_train` is the training companion to `model.predict`: it builds the loss against a target,\n", @@ -115,20 +175,60 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "e77b697f", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T02:48:53.749148Z", + "iopub.status.busy": "2026-08-25T02:48:53.749065Z", + "iopub.status.idle": "2026-08-25T02:48:53.751419Z", + "shell.execute_reply": "2026-08-25T02:48:53.751075Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "model.initialize()" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "dd6dede5", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T02:48:53.752358Z", + "iopub.status.busy": "2026-08-25T02:48:53.752291Z", + "iopub.status.idle": "2026-08-25T02:49:01.599179Z", + "shell.execute_reply": "2026-08-25T02:49:01.598803Z" + } + }, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "abe107262e274a1c98bc59c2510f10fc", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + " 0%| | 0/10000 [00:00]" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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hx4ygYR2twQbiKgOHTrI5s2b/d5evHhxc9DuCwAAKJyCCka0EeXBBx803S2///671KlTJ08Punz5ctN9E02WbD9o9SEAAGBLscF2zXz++efyww8/mFojKSkpZr02wZQoUcJcHjZsmOzatUs++eQTc33kyJFSu3ZtadKkiZw6dUomTJhg8kd0sVrx2CKSnnnGXH55ynr5/oFLrD4kAABsJ6hgZMyYMeZv9+7dsw3xveOOO8xlrSGSmJjouk0DkKFDh5oARQMWDUo016Rv375itaJFYlyXixWlGC0AAAUqgTUaE2CC1fjZaXL81Glz+eI65eWrf3YM2b4BALC7tHAmsBYWMX4uAwCAyLF3MBKTFYK4XQQAABFk62DEXQxtIwAAWMLWwYhHNw0tIwAAWMLWwYh7NEIwAgCANewdjLihmwYAAGsQjJxDywgAANawdTBCmggAANazdTByeeMqrsuHj2dYeiwAANiVrYORuztnTfSXePC4pccCAIBd2ToYiS1K0TMAAKxm62DEHfkjAABYw9bBiHsAUoThNAAAWMLWwYi7jNNnrD4EAABsydbBiMPtctrJTAuPBAAA+7J3MOIejQAAAEvYOxjxaBsBAABWsHUwknmaYAQAAKvZOhg5mXHa6kMAAMD2bB2MZJ6hZQQAAKvZOhg5QzACAIDlbB2M0DICAID1bB2MnKZlBAAAy9k6GIkrauunDwBAVLD1t3HHCytYfQgAANierYORokWYqxcAAKvZOhgBAADWIxgBAACWIhgBAACWIhgBAACWIhgBAACWIhhx898Zm6x7JQAAsCmCETdv/baZ+WoAAIgwghEvMZQeAQAgoghGAACApQhGvDgc1rwQAADYFcEIAACwFMEIAACwFMGIF3ppAACILNsHI89c2TjCpxwAALizfTDSskaCxwkBAACRZftgRMSzsIiD4TQAAESU7YMRipwBAGAtghGLXwAAAOyOYMSraYTRNAAARJbtgxEAAFCAgpERI0ZIu3btpHTp0lKpUiW59tprZePGjTneb/bs2dKmTRuJj4+XunXrytixYyVa0E0DAEABCkY0qHjggQdk4cKFMmPGDMnMzJRevXrJsWPH/N5n27Zt0rdvX+nSpYssX75chg8fLoMHD5ZJkyZJNCawMpgGAIDIig1m42nTpnlc/+ijj0wLydKlS6Vr164+76OtIDVr1pSRI0ea640aNZIlS5bIG2+8If379xerxdA2AgBAwc0ZSU1NNX/Lly/vd5sFCxaY1hN3vXv3NgFJRkZGfh4eAADYrWXEuzjYkCFDpHPnztK0aVO/26WkpEjlypU91ul17eLZv3+/VK1aNdt90tPTzeKUlpYm4UKdEQAACmjLyIMPPiirVq2SL774Ivjhs+cSM7zXuyfKJiQkuJYaNWpIpDgY3AsAQPQHIw899JBMnjxZZs6cKdWrVw+4bZUqVUzriLu9e/dKbGysVKhQwed9hg0bZrqAnEtSUpKECy0jAAAUoG4abdHQQOS7776TWbNmSZ06dXK8T8eOHeXHH3/0WDd9+nRp27atxMXF+bxP8eLFzRIJJLACAFCAWkZ0WO+ECRPk888/N7VGtMVDlxMnTni0agwYMMB1fdCgQbJjxw6TX7J+/Xr58MMPZdy4cTJ06FCJRgztBQAgioORMWPGmG6T7t27m8RT5zJx4kTXNsnJyZKYmOi6rq0nU6dONS0pLVu2lJdeeklGjRoVFcN6Fd00AAAUsG6anIwfPz7bum7dusmyZcskGhGMAABgLeamAQAAliIYAQAAlrJ9MMJoGgAArEUwwkR5AABYyvbBCAAAsBbBCAAAsBTBiBfmpgEAILJsH4z4nqoPAABECsGIVzRyND0zYicfAAAQjGTzxi8beV8AABBBtm8Z8bZl79FInn8AAGyPYMQraySGyWoAAIgoghEAAGAp2wcjNIQAAGAt2wcjAADAWgQjXhwOhzWvBAAANmX7YMS76BmhCAAAkWX7YMQbDSMAAESW7YOREsWKepwQumkAAIgs2wcj5UoW8zghW/cdi/BLAACAvdk+GImP82wZOcLcNAAARJTtgxEAAGAtghEAAGApghEAAGApghEAAGApghEAAGApghEAAGApghEAAGApghEAAGApghEfTp9hujwAACKFYMSHr5ckRewFAADA7ghGfNiQciTyrwQAADZFMOLDJwu2R/6VAADApghGfCBlBACAyCEY8eNkxukIvgwAANgXwYgfnV77PbKvBAAANkUw4sfBY6ci+0oAAGBTBCMAAMBSBCMiElc0xtpXAQAAGyMYEZHGVctY/ToAAGBbBCMAAMBSBCMAAMBSBCMAAMBSBCMAAMBSBCMi4rD2NQAAwNYIRgAAgKUIRkTEX5WRzxclytVvz5N9R9Ij+6oAAGAjQQcjc+bMkauuukqqVasmMTEx8v333wfcftasWWY772XDhg0S7YZ/t1pW7UyVN2dstPpQAAAotGKDvcOxY8ekRYsWcuedd0r//v1zfb+NGzdKmTJZxcXOP/98iRaZZwJnjRw/xQy+AABETTDSp08fswSrUqVKUrZsWYlGBBsAANggZ6RVq1ZStWpV6dmzp8ycOVOiyRNXNLT6EAAAsK2gW0aCpQHIe++9J23atJH09HT59NNPTUCiuSRdu3b1eR/dThentLS0sB7jFU2rhHX/AADAwmCkQYMGZnHq2LGjJCUlyRtvvOE3GBkxYoS88MIL4T40AABg16G9HTp0kM2bN/u9fdiwYZKamupaNHgJt28GdQz7YwAAAAtaRnxZvny56b7xp3jx4maJpNLxcUHXIQEAABYEI0ePHpUtW7a4rm/btk1WrFgh5cuXl5o1a5pWjV27dsknn3xibh85cqTUrl1bmjRpIqdOnZIJEybIpEmTzBJNHBSFBwCgYAQjS5YskR49eriuDxkyxPwdOHCgjB8/XpKTkyUxMdF1uwYgQ4cONQFKiRIlTFAyZcoU6du3r0STknH+T4VWIXE4HKZYGwAACK0Yh37LRjkdTZOQkGDyR9wLp4Va7Sen+L2tYZXSMnVwFylShIAEAIBQfn8zN00ubUg5IneOX5zbzQEAQC4RjARh9qZ9wWwOAABygWAEAABYimAEAABYimAEAABYimAEAABYimAkSBmnz5iaIwAAIDQIRoJU/6mfpdVLM+SjP7aF6CUAAMDeCEby4PDxDHnhx3WhfzUAALAhghEAAGApghEAAGApghEAAGApghEAAGApghEAAGApghEAAGApghEAAGApgpF8+HxRYuheCQAAbIpgJB+Gf7daUk9khO7VAADAhghG8ik983RoXgkAAGyKYAQAAFiKYMTNN4M6WvdKAABgUwQjbtrWLm/dKwEAgE0RjIRA5ukz8te+o+JwOEKxOwAAbIVgJL8cIg99sVx6/me2fLUkKSQvCgAAdkIwEgI/r0kxf9+dvTUUuwMAwFYIRgAAgKUIRvLJI0skJr97AwDAfghGAACApQhGAACApQhGQoheGgAAgkcwkk8v/rguv7sAAMDWCEbyacrq5NC8EgAA2BTBSAjFxHh21Ow7km4qswIAAP8IRry8d3sb+UfXuhIK7V751VRm3XX4REj2BwBAYUQw4qVXkypyy8U1Q3qS1+5KDen+AAAoTAhGQmjL3qNy5gyT5QEAEAyCER+8Uj+CMn3dnrzfGQAAGyIY8cGRj8aN1BOn8vFyAABgPwQjAADAUgQjIe6mCcaRkxniyE8zDAAAhQDBiA/xcUXzfEJXJB2W9+b8lWMi69Idh6TZ89PlsW9W5fmxAAAoDGKtPoBoVLlMfJ7v+8WfSebv1NUpAbd7+/fN5u83S3fKG9e3yPPjAQBQ0NEy4sfbt7TK14nVFhIAAJAzghE/4mPz3lXjq0z83iMnZcysv2T/0fSQ7RcAgMKAbho/ujc4P2QnWZNU7/14iazcmSq/rt8jk+7rlG0eGwAA7IqWET9ii4b21Ggg4kxcVYyiAQDgLIKRCHh5yvpIPAwAAPYIRubMmSNXXXWVVKtWzXQ1fP/99zneZ/bs2dKmTRuJj4+XunXrytixY8VOEg8ez7aObhoAAPIYjBw7dkxatGghb7/9dq6237Ztm/Tt21e6dOkiy5cvl+HDh8vgwYNl0qRJwT40AAAohIJOYO3Tp49ZcktbQWrWrCkjR4401xs1aiRLliyRN954Q/r37y92RfoqAAARyhlZsGCB9OrVy2Nd7969TUCSkZHh8z7p6emSlpbmsRQ27vVZ0076Pg8AANhB2IORlJQUqVy5ssc6vZ6ZmSn79+/3eZ8RI0ZIQkKCa6lRo4YUZs2fn271IQAAULhH03gnazqHtfpL4hw2bJikpqa6lqSksyXWCxO6aQAAiFDRsypVqpjWEXd79+6V2NhYqVChgs/7FC9e3CwAAKDwC3vLSMeOHWXGjBke66ZPny5t27aVuLi4cD88AAAobMHI0aNHZcWKFWZxDt3Vy4mJia4ulgEDBri2HzRokOzYsUOGDBki69evlw8//FDGjRsnQ4cOlYKicdUyId3fE9+sksMnSFoFACBP3TQ6CqZHjx6u6xpkqIEDB8r48eMlOTnZFZioOnXqyNSpU+XRRx+Vd955xxRLGzVqVIEa1ptQIrQtOBOX+M+BOXPGIRMW7ZDWNctJ0wsSQvq4AAAUimCke/fuAedV0YDEW7du3WTZsmVS0FRNiJfk1JPSqmZZWbD1QEQec/LK3fLsD2vN5e2v9YvIYwIAYCVm7Q1gzuM95GTGaflsUVZLT7itTy58NVUAAAiEYCSAuKJFzBJJ/tucAAAonJi1NxcC9EqFzOHjp849FuEIAMBeCEaixNCvV1l9CAAAWIJgJBfiioa/Xuqv6/fIut1pEWmFAQAgmhCM5MJNF9cM/yshIn1HzZVPF+4I2f5WJB2W137eIMdPZYZsnwAAhBrBSC6UKh4bsWG26ZlnQrava9/5Q8bO/kve+m1zyPYJAECoEYwUcJrwunZ3qhmC7M+WPUcjekwAAASDYKSA+2lVsvQbNU+uH7vA6kMBACBPCEai3K7DJ6THG7Pk4/nbfd7+1bnS8qt3pea4rx9W7JLrRv8hyaknQn6cAADkFcFIECqWKiaRDkRGTF0v2/Yfk+cmr5Ute4/ka38Pf7lCliUelhd/XBeyYwQAIL8IRoLQqmY5iaTt+4/JKbeE1n98utR1WXNEtJVj7ub9Qe/3yElG1wAAogfBSBQ7eOyUxMR4Xnf6fvku08oRTp8s2C7/C9FInC17j8rzk9fK3rSTIdkfAKDwYG6aIIS/9Jmnh75YLp3rVfR526nToRsC7I9z9uBrWl4gNSuUzNe+rn57nhw/dVrWJafJV//sGKIjBAAUBrSMRLl5W4LvhsmvY+me3TjHM/LfraOBiFq9M+dEWwCAvRCMBKFymXiJFqEuG59x+oxZpqxKlibP/SLvzNwS2gcAAMAPummCMLRXg5CWa88LTVy95+MlknToeLbblu44KG1qlQ96n6fPOOTiV36VmJgYV+n413/ZKOHgngMDAIAiGAlCQsk4S981h49nmPlrtu475vP25YmHpV6l0vLTqt3St2lVj9tmbtzrd7+aGHvoeIbf22Mini0DALATgpECxl8gos44HDL4i+Uye9M+eXP6Jo9k1zs/Wuz3fg4J3OeT0+0AAOQHOSN5NLTXRRJtdICNBiLqgNswYPdaJfmh+SRXjJxjhukCABAqBCN5pPkV0eb0Gd9Bx6JtBwPfMYeGD2c3zQOfL5MNKUfkX1+t8Lut5tRMXrk7F0cLAMBZBCOFyG8b/OeFuNt56LjUfnKKaeXQ5NVgrdyZKmt8zIWTdPC4PPP9GtNV5E/0hXAAAKsRjBQimsCaG9sPnB2Jo60cWsk1LxkhgyZklaZ3Sj3hPwkWAAB/SGC1uX99vVJq5VBddeLiJGl6QZlsQ4wBAAgFghHIjnMtJf58+Me2bOt0KPDPq5Pl0kaVpHhsUc4iACDP6KZBnmiuyX2fLZMbxi7webvD4TALAAA5oWUkj/iizUpmHT1rixlx06V+1qR+N7y7QEoWi5Xxd7bzGHkUjaOQAADWIhhBvv172tnS8U2qXexat3j7IfN37e40aVItK9+EIA4A4I1uGoTMgA//zLbuyv/Nk//9HnjSvR9W7JJ/fLIk22zBasFfB2Te5sjPXAwAiByCEYTdmzM2BeymefjLFTJ93R55d/ZfHuvTM0/Lze8vlNvGLZIjJ6Nr2LBWtdX5fnwFUACA4BCM5FHNCufl9a7wQ0foaDG1V6ask+cnr5WTGVkVZY+l534osZbE/3Be9hFAofT6LxvMfD//+HRJWB8HAOyAnJEgffmPDrIs8ZBc2axqwEqj8C1Q+uq2/cdMt47T5Y0r5+k0DjzXXaS5Ku3rVgjLS/HZokTz948tB8KyfwCwE1pGgtShbgW5v3s9KVIkRn4d0k3euqlleF4ZG5q3xTM35PiprNaQFUmH5EyQpeuTU0+G7NgAAOFDMJIP9SqVkmtaXhC6VwN+W1EGTVgmExbtiJozFGwJFW310dL7wQZUAGAHBCMh8Nk97UOxG1t6ctIqv7d557p+fq5rJBQ27zkij329UhJzqD7riw5PPhFkOfweb8ySRyauYEZjAPCBYCQELqlXUba/1k+KUM8r1zJPn5GNKUfky8VJfrcJZ32068bMl6+X7pS7Pl4c9H1Pnc5KrPVl35F0mf/Xfp81VZbuOFt/BQCQhWAEEXUkPVMOHz9lWgl6j5yTp30M+WqFPP396lzNuaMzCf+yNkXu/2ypx6zCR06eHZK7Ze9RCbWOI36TW95fZIb+enPkaY5kzzL8AFDYEIwg4gZNWCo/rUrOcTstMe9t56Hj8u2yXTJhYaKpQ6K0BeLVqevlyz89u3H+++smafXidPnnp0tl6uoUGflrVr0Tf/JSITbDq6Uk81zAMGdTaIu1aTDV9uUZjOICUOgQjITQq39rFsrdFVoLtx7M3YZesciGlCPyhVvA4YwbtPT8e3O2ypPfZm8tcW9I2HskPeDDHT+VKV1fn2laXpwOHTtlrmslWH9e/mmdRMLklbtNLRb9CwCFCcFICN10cU1Z9szl8uyVjc31SqWLh3L3EJF3ZmZVadV5b2o/OcVMypcrOTR6NH72F0k6eMK0vDi9NGWdua6VYP35eMEOmbR0p+l+AgAEj2AkxMqfV0wGdqot793eRqY+3CXUu7eVnPJX+4+ZH/Q+L3tztjR97pcct7vl/YXy6YLtPkfbLDk3CaC7f329Uu7+OOdqrHnoBYpKyxMPyTVvz5PF27NauQ4eOyV3fvSnTF2dnKvcF83XYeJEAIpgJAyKFomRXk2qSMVS2VtGfnyws1xSLzxVQQsbX/PY5ERbSvxJOnTcfAEe9ZpP5tYPFspzP6zxWDf/rwPyzA9rfe5n7e5Un+u9R8p4P44vJ4McIhxqWvdk2ppkSU49EdT9bnx3oazcmSrXj81qlXr9l40yc+M+uf+zZTnef/i3q01g+P7crXk6bgCFC8FIhDWrniCvXEtuiRVW7fQdRGhJd+1qySk5Vb88dYTMq1M35Orxvlm6M+DtOhtxw2emyVu/bs52294jJ+XvY+bLd8sD7yOvtEVCWzL0GLWgXOf/m5nv4c0HjwXOyXE3ccnZId2+nnthRksQ4BvBiAXCWT+jMPnHJ9ZPQqe//p20VUUnxwuVp84NT9ZRP95em7pBluw4JI9OXOmz28N9FmO97Jw9ePfhE/LOzC0m8VaP98HPl5l6LjrLcOrxrPs888Maaf3SDHl28pqQDRn2Nfopx/sE+M+gz6kwDWX+eXWyXPzqb/LntlwmcCPstY4Wbj1geetkOKWeyCgwz49gxAJ5+dC2o/TMwMXFotH2/cc8rjuHH3vTDwitg+JPmluw4avb4+nv17j23+z56dLkuV9Ml8tN7y003SWaT6MtOTqEWltYtAJsixenyx/n5v/RodFnj+OMpUG2v5YCbbXR59T3rbl5OhYdGRVtrRD3fbbMFMS7fdwiiXZ67v49bYOZwqCwemP6JvP/5aFCOuHp0fRMafHCdGn54nQpCAhGwqxFjbKuyyWLFTV/aRkpvLq/Mcvjun7Yuf8y0dl+tQsmpw9AX9+j7t/1P6zYbb4w9qZldY2sT0mTxINnA5ytbkGRFprbdfhsTsitH1jzRah5KTM3ZC8C58+cTfvM3417jvi8XZ/7+D+2+axou2XvETMy6sEo/ZIpCK09OpR99Ky/THFCbyuTDsuGlDQp6D76Y5v5O2PdHiks1ienuYo7bkhOC/kPjnAiGAk3t2+VqYPPjq6Jsh9sCKPliYflHq9RNhqg5PQB6CycFsi0NSke1/uNmidW8hdkayuH5qXcOX6xLN1xMM9zDL05Y5O89vPZfB2tqvv8j+t8jqj66I/t5u+Uc4X1NLAZMXW9aZaPBuH6MaLdcnvTQjNT9YFjvoepa1ffNe/8IVeMnBt1LU92t3THQenz1lzp/H+/Z7tNfxD9tn6Pqzu30AQjo0ePljp16kh8fLy0adNG5s7135Q6a9Ys0y/svWzYkLskwIKuakIJ1+XaFc8zf8/wn9hW5p3rGnHaus+zK8eX2edaBgLJ6zw3Om9OKDnrq/jrfkxzK8Pff8wCGf5dzqX8vemH6KjfNsvY2X+ZlqXclvHX/JkBH/4p787ZKl8tCU8ycDTQ0VDaLac5KeEMmPYdDU2wEw0KWyj16/q9HlNduHvpp3Wm9MADn+c80q3ABCMTJ06URx55RJ566ilZvny5dOnSRfr06SOJiYF/7WzcuFGSk5NdS/369cUOXrymifRuUlkm3J01s693MFKqeKwFR4Zoov3zczf7D0B8/Qp1zysJhs6b48uyxEOmmmzSua4eXzQv5YUfPYc87zzke1jwml2pMuzbVbL/aHqOCaya46H79ue02/PPPO3Ideui5s9kHWfwMzS7n/9oLmqnzfORxm+q6FIkQIubdg+rWRtz/pFTYIKRN998U+6++2655557pFGjRjJy5EipUaOGjBkzJuD9KlWqJFWqVHEtRYuezZ8o7CqViZd3b28rnetXdK2rXeE8aXZBguv66FtbS79mVS06QkQD7Z+/fdyfptS7r5YDLXnvTX/p68zDoXLd6Pnywbxt0vPN2X63mbF+j6sbxJ32U3v/mr7yf/Pkiz+T5IlJq3L8Va85Hrf5Sew0ox68yvE7IvxLePCXK6TlizNk0Vb/0wLYIYG9oOQf5Ea4XwUdwRaOiTgL4/sq6GDk1KlTsnTpUunVq5fHer0+f37gapitWrWSqlWrSs+ePWXmzMA1DdLT0yUtLc1jKUyKFImRHx64xGNdXNGsN9Kdl9S24KgQDQZ/sdxnToV+sP26PnueiXZdhJo+1j8/XWKGBPvK//D27A9rTNb+3M2+u3/+CtAtpRn/zpwaLTTnS4cRv8s/Pl1qaU7Gj+fmA9JuomgUqVYK98kmC3o3R7iPX4Nr7TrzV5FYW9r0/1qoxBTsWCS4YGT//v1y+vRpqVy5ssd6vZ6S4plM56QByHvvvSeTJk2Sb7/9Vho0aGACkjlz/E8fP2LECElISHAt2vJS2GhA4v4mcv+PcXWLapYcE6KXvkci2cT6y9o9ctN7C3L1pbcs8bD568ziD5bOMRSo2Jyvbp68fPkWlG4F7X7z7pbTc/Cf6Rt9dqGNnrVFRpxL7A03fwEjsnPWk/H142JP2knT0qZD7q2sWB1NYkPxpPU/jr8TocGHLk4dO3aUpKQkeeONN6Rr164+7zNs2DAZMmSI67q2jBTGgMSpUul4vx+UbWqVM7Uk1uwqXK1DCM6gIFoGQkVnCLZiRICOEnp/7tlhl7mlExXq/EC3tq/p9xeidvfoEls0coMIA302+rJud5r0HTVXLmtUWT4Y2Na1/tGJK0zL05eLk2TxU5d53Off07LyYsLhzekbZUivrM9wKwV7PqOVc+i6c8h9KBT0sxLU/8qKFSuaXA/vVpC9e/dmay0JpEOHDrJ5s//m5eLFi0uZMmU8lsJo/J3t5P/6N5MGVUp7rHePS74Z1FG+u9+zSwf2o7VCrKJDOXXOn9837JEPz9VmCBcdbZObQMTh1ciugYh7op4vmg/T4z+zgh6S6j366Nd1e0wp/5x8smC7tH35V9m854hn8muAb43x888+d+8uuUVbz/7K1qJpwfhhxS55+vvV+aptMur3LT7Xh3tor7aOXTf6D9e8Uf83bYOZtkC7CnUI839nbJKU1MIzuie/vGO0AtIQmLdgpFixYmYo74wZMzzW6/VOnTrlej86Cke7b+yue4NKcmO7mubyBeWyhgAXc/vlpr8C4ooWke2v9bPkGAEtcqXuGr8kogl5ua3M6z0hoXshOG9JB0+Y7e8av9jk5+TmC9V99JHJcflkickHyKnM9rM/rDX1Oi7/7xz5P7fWi3D/gtUAxDnnz8NfrjDVdn9adTbnJdzJkM9PXmtacUIRqGjrgXYBOueNGjPrL9OS8MHcrXLvp0vlrd82y8AP/5RooEPI7/l4sUf3YiCBzk7SwePSb9RcMy/Vpj1Hcl0sMD8JrFo/xl+16KjtptHuk9tvv13atm1rulw0H0SH9Q4aNMjVxbJr1y755JNPzHUdbVO7dm1p0qSJSYCdMGGCyR/RBVk04ACiTaBZkK2i/ewP98wqDdD0uV88bp+eQ0G539bvld/PfcBf1riyydH6dtlOkw/xxBUN5fzSxU2god1F7qPgVMbprK8RLbP9f/2bS+8mVSQ+rqhriLL+X/b+//zFn4kRy13RAER1vSjr2HWuolBz+KqKO//sSKsHL60nF55fKl/799ea43ALkP1V6I005xDy+k/9LB8MaGveV/5a8XLy3OS1Jo9K56Vy+umhztLUbQRmbob25jY02XHgmHR7fZaUKxkny5/1HJwS1cHIjTfeKAcOHJAXX3zR1Atp2rSpTJ06VWrVqmVu13XuNUc0ABk6dKgJUEqUKGGCkilTpkjfvn1D+0wKuAaVPbtqgIJME/TCSX8V59XxU1m/AHXW4pbVy8qQr85+8C/ZflBmPdbDTF747uytUrtCyYDDXJ1f/H+92tcEMDqfzgVlS8gfT14a1IzHL/64LlsXmH4ZFz33DZN5Jus+OgJj9a7DsmnPUbn54rMtq74cdsv5OXbqtPxt9B/SpV5FefTyiwLmXWgLhHaJ5Ieez/6tL5Aa5UtKjwaV8rQPf8fo/ryikbac+WrJ3rrvqExcnCT3dq0bsGkkxUfX04aUI9mCEQ3+tJXQGQjnNZVGAxGrcsTyncB6//33m8WX8ePHe1x//PHHzYLA+jarIi9d08TMZRNMK8n7A9rKvVEwuy3gTifoKygWb8+aRXf7uckLf16d4nE9J9v2H5WU1PSgkhJnbdxrCiBe2rCyz1wczTm585I6Zn/ujQQTlyTJM+cmSqxwXrFc5Rl9umCHpKSdNNMT6N9//72F3/vlNhBxb+FxfjE6acuFs/VCv5g1cVhnodbHb1EjQTpd6NniFEwRL/dWJqtp10ZiDu8RZ/fJVf+bZ4LCdclpcmXzrDSF1TtT5cvFiTLk8oukQqnismVf7rpCde4lnfJg7uM9TNDnr/hgQUHpzyihvwJu71jb9R9b36wVSxX32Eab0byj1871KsraF3qbcr9//LXf9Ilf1qiSqzQwYAV9P0Yr92ZzzUtwjmxw52/KhhXnvmC9TV6Z7FHzRVs6AtFWlDs+Wmwur3red9O4JrFqMHLJa55zjTgDERWo/ormxLgezy0fQIvlXd+2hrSrXV7yQ4OWZ65sbC7fN2GZTFvru7yDfmHqts5JHNVDl9aTksVi5dDxU/LkFQ09Sh04PwO1EGBeqw3rMPOEEnESblqo0DmEN6f3mwYiakXiYY9g5Kq3z84ppYm5Y25rk+vuFefcSxMW7ZBhfRqZkVYFGcFIlAYmb9/SOtv6nx/uakqG/2f6JvPrxum84rHyWv/mrg85bbaLxr5+IBq8MmV9wNv1izDYvA7v4nM5jTpyb42JxORl3s/n+rELZNPLfUw3kLMrSH+h5/QF7v5FOW7eNjmRcVpe/Vszv4GI8jUfyv/cRujodBganLh3y+jcTO5zLz31XVYAlpM3ftkob8/cIu/c0lr6uX3ph0NOgYhPWlfKx/vr5zUp5vPbO0E7nPOZRcvkkYqsyQKkSkK8+UWjXTP+OPsPr21J4TQgp5wRXzQpMpT1H3xxb92IRHlvX8mgbV6aIb1HznGV5Ndf6F1fD1wd23svWtBLk3bzQ2djnrDw7IgZp92HPfMmAs3b5E0DEfXc5LPnWCsJ62P4a9XyRVspwjl02RFgNJIvj3+zKuj3iSOH7iWdC+qTcyOVogEtIwVQbpofC0NhIMAK+anJkVvuuSj+vvO0eT9Uwy29hz87c0qOnBuq/dfenGeS9kfnFcqvZ35Y6+qmVt7Pu0g+Ps+cAZe2XmkrjnZt1/RKTNbgUxOPlc4/dON7C00e3w1ta0jdiqWybR8uU/yUjnenSdZ/bMl7JdwjJzOkzUu/SpMLykTVJK3RcyQIqWtaVpPvlu8yowGcH3yNqpYxRdQ04x9AdJSN9/dw+oXT4OlpETmGnL7rH/9mpczZtD/HVqX8+GpJkmnZ1aHW3lMAxOQwY3G9SqXk7o+XSMlzLcNq/9FTcuO7nlMaOCehNKNd3E685uVMuq+TqXj93pytZt3U1SlmUVte6ZOtcq/OSp0XMfl8j/19rOdz0vmS4uNy38mhBft0RJcmE3dvcL5EC7ppCnFBtV8e6WryTJzOK1bU5Jf8++/NTZDy27+6WXqMQDTyNRlgOHknqEaa5pcl51DJVJNe3fPUwkG7IjTpdlnioaBaevu8Ndd8QWsisnfuyqIAOR3e9T/6j/E/2WvDZ6aZYnJOmtsRqOBaTl1C/mqPxEjejDxX5C433E9l0ShqQadlpAByfyMHei95l5mvVObs6BxtetQFQHZrduftF29BNvRcOf1ocN3o+XJ/9ws91vmaLNGdcxhxbnV7faZHATv3Giu/+ah4mnnGYWrKlCkRJ80vSJBe/51jquv6c+07f4SlC/1kDlV/3Ufa+EuM3rwna+iwr1FMVnXxE4wUcLlp7vvwjramJPTzVzWJxCEBBVq4J55DztyH9YbDDj+1QXKqsXLnR4tNbZdAgUhuZPgYMZObXKacWrACjWBy+s+MTa7LC71mYdbKrzlVeg0XumkKoGCz7E1RpTvaSaUy8TluO25gW/n8nvb5ODoAKLzyEojobMzulvtpyUk76X9k0vVj53tOuBiGCTh1qLZVCEYKcTdNbjhrDDhVKh0vnepVlH9dfpHf+3x818Wuy8EkTgGA3Szefkj6jprrUZDthxXBTVyodNLAv432n9dSEJO33fFNYnN/POE5h4YzuHnIbSIyb61qljV/nUPhAAC+6VxCyBnBiM1pIbVg6BC6MvFxpgT9rMe6S9f657sCk0iUXwYAhEc4C73lhGCkAKpRrqQ0rFJaWtcsK8VjQ/sS5lRcqGPdCuavDhHWCf1e/3sLM+3614M6yid3XSzlzytmunh0XH5uTfxHh3wfNwAgf5b6GFYdKYymKYB0ONbUwV1Ml0qoh2G57279i1eYIXDlShYzJbJ9SSgZJ/edG4ZXrWwJWfbM5Xl4TN/PQeercJ/DAgAQPrvDPA1CILSMFOCAJBzjwd1bRkoUKyoLhvWUnx/u4ppBuFeTyiF5nIqliplWFp3Mypd/dK0r/+rVICSPBQCQiM6HFCxaRuCherkSPkfb/D60myQeOJ6nMeg9GpwvMzfuk0cuq++qFFjhvOLyxbnuGfeZL+c/eakpLBRNcyYAAMKLT3zIda0vkL/2HpUPBrYzuSC+aNJqXovhXNe6uvzvltYmwHAGI6fdEqXcAyDt6gmX2CIxppIiACA7K6vD000DefOGlvLDg53l/NJnu2JCRRNbL2tUSa5oWsXV0tGveVXz975uF3oEIJ/f216mDO6cbR91zz/P7/41WXbeEz08gqpArm5ZLU/PAwDsIMbCx6ZlBGFzNrHVc46JUTe1kid6N8w2JXenCyv63EfL6mVl6z7f05t/evfFUjo+azjx0/0aS83yJaV3kypm8ixfwdG3y7ImuwIARAeCEUSU5qB4ByKBPHNlY4kvVlQWbT0gf50LSnSa7wl3tzcJtuqDAW3NfrWl5JHLLjJzOPhSuUy8dKlfUeZu3h+iZwMAhUeMhf00dNMgqpU7r5i8+rdm8uuQbq51j/du4ApE1GWNK0uPhpV83n+IV1n79we09bldibis/QXDq5o+ACAPCEZQqCP2MvGejX/xcUXlz+E9ZcnTl0mxollv/+XPZtVH0WHM7euUN5f/e2OLgPt/66ZWAW9f+VyvPB03ANgpgZVuGhS6wES7bG6+uIaZkGpAx9qycmeqNKpa2nW7c/bino0qyc9rUuSiyqVMkOJUqnhRmfjPjq7rj05c6fexul50thy+L1/c28GUyNfS+Vq/5Ys/E+XFn9bl+nkCgF0QjKDAqVY25/l0RlzX3HX5vze29LnNa/2bmxaQvudG+DgllCzmc/v/XN9CmlVPMF06Xf498+y2JeLkjk61Zfz87a7tnr+qsfRpVtXkqCjncGkrf3V409af/UfTrT4MAFEkxsLxNHTToMD46aHOJnG1erncJ8AGYgKJS+pIpdJng4ZxA9ua+X5G+gleSsfHykWVS0uN8iXlzRtayId3nM0/ef7qJtLbrTLt31pXdwUi7nIzB9XF57qHvP1f/2Zm9FAglcvkfmj2bR1qSmkKywFwQ50RIBe06Frn+r6HAIdCz0aV5dv7L5E6FT1rmzhL4betXd6jkNulDbMCkP/c0FL+3qa6CVCCnb34jeuz8lJaVM8qLDfIrRbLje1qSpf658u//57V4uOtcdUyOT7W3Md7yEd3tpMHetQzsy57q3Ce71ahQJq7HTMA5AXdNEAO/niyh5w8dcZMCuiPFnVzDyqCUadiSflbqwtk1+ET0r5OBXl/7jazvmWNstm2vaFtDXn8m1U+96O3NapaRi6pV1HqVyplitjNWLdHpq/bI98s3WlyY7TarbbsqAqlikvZknFy+HiGuf7d/Z2kbsVS0uLF6UG1sLx4dVOpO3xqnp47ACiCESAHxWOLmiWU3rqppTz85YpseS0nTp2WSqWLm6BCu37+dflF0txHUOIvcffxKxp6rOvVpIpZ/AVKt7WvJW/P3CKd61WUVjXL+d33BWVLmGDJW2yRImbSRq0H85Kf5FwdvXTxq79JqDWoXNrvbNIAChZyRoAIaV83q5vnmpYX+Mwl0fopOlng+DvbmVFDD/WsL928Ruz88khXeax3aGY01skLP7+nvbw3oE3A7XSiRK3R8tu/suq9uLu7cx3XZc27caejl358sLN8e3+noI5t0n2d5LpW/kv8P9WvkcnzAVDwEYwAEdKkWoJJwl381GXmep+mVUyeh3d3TGzRIgGHLzeoUtrkfHjrcGGFoI9JH6tTvYpSspj/RtLbO9QyLUOXN64sF55fyu92W1/tKzOHdjd5N05NLzibx6KjkFrXLCdrXujt/1i8KsjVKFfCY0JFb42rlZHuDXwXu8utSfdlDeFWt7avma/9AcgbghEgwkm4zgkJx9zWxkwOqAFBfjzdr5FseOkKM7NyOHRv4Nkyo/ktvmh3jXfyr9ZXceecMNGXOK/zoC0qHer6DrDuuqSOSSzWrqmceLfUeIrxe7w3X+wZmMz2kfDr3o3lPQz8pWubSijU9TqnoaLvG8BdOGdNzwnBCFDA54LQL2T3om355aw+q19W/7u5lVzqVWpf81s6ngsSbmxXI1+P5R5LnPHRCqJJuTrUWkcBuZ+qZ69q7Lp8Sb3sAUuLGmWlb7MqZvSRdvf4c6HXrNDujzGwUy3XZe2ecg4B96WW23xL2pLUv011CZV7u9aVcPCVIA17u9XClkESWAF4cK8+689n97Q3FW517qBAclNbJdC2Gmhde64lRuMEX7vT2jMHjp2SeZv3yyMTzyYFFysaI6NvzcqD0WBm9a5U2bL3qJQsVlSW7jhkJlYsW7KYydHp9NrvrsdwT87V23S0kXZPZZ4+4/fY/3NDC1m7K02+X7FLHruiQbZ9+UsADkSHie86dEJualfDdOm1fHGG5EW/5lVlyqrkbOvrunW56bB0HXGlnuzTUF77eUPAejZ70iiYV9i8/vfmIf1REyxaRoACzlnhNZK0SyanQEQ5fIYPvluGcto20D60y8YZtCjvmZt1OHPfZlVlcM/6ck+XuqaL7JW/NcvWNK3baZ5LzfIlpXaFkuY2zU1R2p2mrSwaGLj7Z7e6UjWhhJmw8e1bWru6y4rHZn28/vJoV9flOy+pbVpt/CUhf/XPjvL7v7qZOja3d6xtnp8GTTriyZ+xt7V2XX64Z33X5V6NK5uWJQ28vOk6zfFZNLyn/KvXRSZg0mPS+jaBhqnrucktnf9Jn29eJpRc+Wze5nUae1sbUwU5N3TaiIKkXiX/OVv5dX1ba88FLSNAAfXslY1l/l/75Vq3kTnRQpNRM8845OLauU+q1fhBC7IN/ny5z+JuJnDJZVOLVyySIx29NGvjPjOXkeaj6N195aO0qVVO2tRqI7WfnOJaVzLO98foVS2qyddLdprEYvdcmRrlSspzVzUxl6/WbZbulFG/bc6xCu/1bavLvC37TZCjQZO28qhtI/p6BHU3XVxD7u9xocdwdO1GOnjsVLZ9uuf4zHuih2s/2lIy9Gv/czI5aXCnQ9E1D+qfny4167rUryhzN++Xng0rybg72pl1T1zRUBo+M02CKaQXqK5PIPoUtLKyLqnHMwLWzXm6X2P54s+kXO23Yqlisv9o9nMYbgkl4kwrpHrxmiZyy/uLpDAiGAEKqLs61zFLNNIcCy24dmv7rLwLXzS/wjmvzw1tq0uPBpXMTMfa8uItNz+uO11YQeb/dcDsNxg6KieYkTmv/q2ZDP9utSmpf1fn2j630SbvrwYF7vLSoGLI5ReZ3BWtOzP40uyjpJw0cNEvJm2p2XckXe79eIkM7d3AFUDMeLSrpJ3MMK00voKGOz9aHDDg8Ze/VKN8CfN8bx/3p2vdO7e0lg0pafLoZfWz3e/eLnXNsOt6bt1AwTT/a+uM7sObtlit2ZWW41xL7keTU0Djq1VR841euqaJXP32H1635L55Z9TNrWTwF8slL5pUKyNrd6e5kpcfufwi177C2TJiNYIRACFXq8J5pjskJ8P7NpK2tctJxukz0qfp2QkLfQUiKje5vuPvvFi27T9mqs2G0y3ta5om/lAkIDvrzmgwFGgqAX0sZ8CkybTzh/X0uL1+5ayZqb1pkLf06cvk+KnTZvboOy7xHUC50yHoB46lS8MqZXzmoejii9bK8XUfnVtJqwdrt1F65hl57JuV2XJPdDoC92HrOvR9XXKaaaXSAn1Pfrva52Pe3/1CvzNiawHB9clnv9zj44rIyYwz2UZbLUs87LquLT3Nq5f1OTdVbieX1MAx9fgp+WXtHtOa5U0LGja7IEHemL7J5CWlncyQjEyH1K9cyrQq3fvJErPdjCHdzHOvmhBv3hv+kqg1+D917pwO69NIbv3Af+uJtmIdOJoedOthuJEzAiCitLnbqVhsEbmyeTX5W6vqIUme0/1pHZZQBQmB5PUxWvkZahzsnEbB0vL/2hKjVXoDjQxy/9LyFVSU8zOrtVYL1mHfbWv5ruSrcystGNZT2tetIF0vOl8WDb8s24ieC71++Y+7o638s2tdmTW0e7ZgVGfLvrdLHTP1wQ3tapj8FNXS6/z++OAlpuDeX6/2lUcuuyhbF5W2YmiXkq+cG++qyU6aU6OJvM+55aaU82qF0XyfCfe0N0O8nZNqOr1+fQt58NL6sv21fiYvqWGVMqYWj/4f0Ofj5OwqbFe7vJmk0xc955pgrUHXTw91MdNB/MPPCCztgtRpHza+3Me8Xu55T1aLcTiCyXe3RlpamiQkJEhqaqqUKZPzZGAAopf+Sn3xx3Wmi0FzMHLrjy37TV7Cy9c29UhWLSh2HjouSQdPSMc8FKeLBr+sTZGP52+XN29oKVUScg5mcuPw8VMyc+NeeXTiSlf3kSbw+qKjfZx5LHMe6yE13YZTq2PpmWbR+jT+aAvc/37fIqt3HpYXrm6abR86HYO27CgdnbX3yEkzKaaOpNIEZmeu0MJhPV3nwLlOk2aPZ5yWyxtV9tlK9e9pG2T0rL9MEKUzfQfy57aDUqFUMZ9FBm8ft8i0njhd1/oC85p40/Pq3jVn9vtUT49A1Hnses795SpF6vubYARAgXHmjMNvNw4KLueX4mWNKskHA88mvXrTQOGqt+dJh7rl5eVrz46EirSU1JNy5GSGR7DhPPZ/929uWmgCvXc37T0iF1Uqna/3sMPhkBMZp6Xxs7+Y6/1bVzdDy33574xN8ta55GhNKtfiiO5FFjVA2rrvmIy+tXXY/l/lNhghZwRAgUEgUji9fUsr+XDeNnnhGv9Va7XFQpN0I9EF54+2hvhrFcrpsPS966vbK1gxMTFm+gZNSv5s4Q559HLf3UpKh1V/t3yXmcpBE4O9qz17T6xpJVpGAADII2fLiNagCabb0S4thWm0jAAAEF7fP3CJ7DhwLOKBiIr2QCQYdNMAAJBHOiKIeX7yj6G9AADAUgQjAADAUgQjAACg4AUjo0ePljp16kh8fLy0adNG5s6dG3D72bNnm+10+7p168rYsWPzerwAAMDuwcjEiRPlkUcekaeeekqWL18uXbp0kT59+khiYqLP7bdt2yZ9+/Y12+n2w4cPl8GDB8ukSZNCcfwAAKCAC7rOSPv27aV169YyZswY17pGjRrJtddeKyNGjMi2/RNPPCGTJ0+W9evXu9YNGjRIVq5cKQsWLMjVY1IOHgCAgie3399BtYycOnVKli5dKr169fJYr9fnz5/v8z4acHhv37t3b1myZIlkZGT4vE96erp5Au4LAAAonIIKRvbv3y+nT5+WypUre6zX6ykpKT7vo+t9bZ+ZmWn254u2sGgk5Vxq1PBf7x8AANgwgdV7bgDt6Qk0X4Cv7X2tdxo2bJhp0nEuSUlJeTlMAABQ2CqwVqxYUYoWLZqtFWTv3r3ZWj+cqlSp4nP72NhYqVDB91TaxYsXNwsAACj8gmoZKVasmBmiO2PGDI/1er1Tp04+79OxY8ds20+fPl3atm0rcXFxeTlmAABg526aIUOGyAcffCAffvihGSHz6KOPmmG9OkLG2cUyYMAA1/a6fseOHeZ+ur3eb9y4cTJ06NDQPhMAAGCPifJuvPFGOXDggLz44ouSnJwsTZs2lalTp0qtWrXM7brOveaIFkfT2zVoeeedd6RatWoyatQo6d+/f2ifCQAAsEedEStoEmvZsmVNImugccoAACB6aGkOHRF7+PBhMzo2ZC0jVjhy5Ij5yxBfAAAKHv0eDxSMFIiWkTNnzsju3buldOnSAYcQ5zVio8Ul/DjXkcF55jwXJryfC/551hBDAxFN0ShSpEjBbhnRJ1C9evWw7V9PPt0/kcG55jwXJryfOc+FSZkwfRcGahHJV9EzAACAUCEYAQAAlrJ1MKJVXp977jmqvXKuCw3e05znwoT3s33Oc4FIYAUAAIWXrVtGAACA9QhGAACApQhGAACApQhGAACApWwdjIwePdpM5BcfHy9t2rSRuXPnWn1IUWvEiBHSrl07UwW3UqVKcu2118rGjRs9ttFc6Oeff95U2itRooR0795d1q5d67FNenq6PPTQQ1KxYkU577zz5Oqrr5adO3d6bHPo0CG5/fbbTaEcXfSyzmtg1/OuVYcfeeQR1zrOc2js2rVLbrvtNqlQoYKULFlSWrZsKUuXLuU8h1BmZqY8/fTT5nNWPxPq1q1rJlnVqtpOvJ/zZs6cOXLVVVeZz1v9jPj+++89bo/kedXJcfVYdB+6r8GDB8upU6eCe0IOm/ryyy8dcXFxjvfff9+xbt06x8MPP+w477zzHDt27LD60KJS7969HR999JFjzZo1jhUrVjj69evnqFmzpuPo0aOubV577TVH6dKlHZMmTXKsXr3aceONNzqqVq3qSEtLc20zaNAgxwUXXOCYMWOGY9myZY4ePXo4WrRo4cjMzHRtc8UVVziaNm3qmD9/vln08pVXXumwmz///NNRu3ZtR/Pmzc3704nznH8HDx501KpVy3HHHXc4Fi1a5Ni2bZvj119/dWzZsoXzHEIvv/yyo0KFCo6ffvrJnOOvv/7aUapUKcfIkSM5z/k0depUx1NPPWU+b/Wr/LvvvvO4PVKfE7qtrtP76j50X9WqVXM8+OCDQT0f2wYjF198sXkh3DVs2NDx5JNPWnZMBcnevXvNf4DZs2eb62fOnHFUqVLF/AdwOnnypCMhIcExduxYc/3w4cMmANRA0GnXrl2OIkWKOKZNm2aua2Co+124cKFrmwULFph1GzZscNjFkSNHHPXr1zf/sbt16+YKRjjPofHEE084Onfu7Pd2znNo6I+Wu+66y2Pddddd57jttts4zyEkXsFIJN+/GhTpffS+Tl988YWjePHijtTU1Fw/B1t202jzkTbH9urVy2O9Xp8/f75lx1WQpKammr/ly5c3f7dt2yYpKSke51QL6HTr1s11TvWcZ2RkeGyjTYhNmzZ1bbNgwQLTFNi+fXvXNh06dDDr7PTaPPDAA9KvXz+57LLLPNZznkNj8uTJ0rZtW7n++utNt2OrVq3k/fff5zyHWOfOneW3336TTZs2mesrV66UefPmSd++fc113s/hsS2Cn8e6jd5H7+vUu3dv0wXk3u2ZkwIxUV6o7d+/X06fPi2VK1f2WK/X9QVEYBqIDxkyxHzQ6JtQOc+br3O6Y8cO1zbFihWTcuXK+T3v+le/HLzpOru8Nl9++aUsW7ZMFi9enO02znNobN26VcaMGWPex8OHD5c///zT9HPrB/aAAQM4zyHyxBNPmB8uDRs2lKJFi5rP3VdeeUVuvvlmczvv5/BIieDnsf71fhzdp+47mM9sWwYjTpr04/0l670O2T344IOyatUq8wsnFOfUextf29vltdEpvB9++GGZPn26Saz2h/OcP5pAqS0jr776qrmuLSOa3KcBigYjnOfQmDhxokyYMEE+//xzadKkiaxYscIkY+uv6IEDB3KewywmQp/HofjMtmU3jWb7apTuHbXt3bs3W4QHT5p5rU3cM2fOlOrVq7vWV6lSxfwNdE51G+0i0+zsQNvs2bMn22nft2+fLV4bbdbU86Gju2JjY80ye/ZsGTVqlLnsPAec5/ypWrWqNG7c2GNdo0aNzKgAxfs5NB577DF58skn5aabbpJmzZqZkRiPPvqoGSXGeQ6fKhH8PNZtvB9H96ldQMF8ZtsyGNHmI/2wnzFjhsd6vd6pUyfLjiuaaZSrLSLffvut/P7772aonju9rm9K93Oqb3T9InWeUz3ncXFxHtskJyfLmjVrXNt07NjRNOtqs7nTokWLzDo7vDY9e/aU1atXm1+QzkV/wd96663msg6N5Dzn3yWXXJJtaLrmNdSqVctc5v0cGsePH5ciRTy/ZvSHoHNoL+c5POpE8PNYt9H76H2dtGVXuzz1MXLNYfOhvePGjTMZw4888ogZ2rt9+3arDy0q3XfffSYTe9asWY7k5GTXcvz4cdc2mrmt23z77bdmKNnNN9/scyhZ9erVzTBKHQZ26aWX+hxKpsNZNWtbl2bNmtlyaK+T+2gaxXkOzbDp2NhYxyuvvOLYvHmz47PPPnOULFnSMWHCBM5zCA0cONAMHXUO7dXPhooVKzoef/xxznMIRtwtX77cLPpV/uabb5rLzvIUkfqccA7t7dmzp9mH7kv3ydDeILzzzjum1kCxYsUcrVu3dg1TRXb6Zve1aO0R9+Fkzz33nBlSpsO6unbtav4TuDtx4oR5k5YvX95RokQJ86ZOTEz02ObAgQOOW2+91YyR10UvHzp0yLYvi3cwwnkOjR9//NF8iOp7VYf1v/feex63c57zT7/49L2rNYni4+MddevWNbUx0tPTOc/5NHPmTJ+fyRoARvr9qwGQDuPWfei+dJ86lDgYMfpP/hqEAAAA8s6WOSMAACB6EIwAAABLEYwAAABLEYwAAABLEYwAAABLEYwAAABLEYwAAABLEYwAAABLEYwAAABLEYwAAABLEYwAAABLEYwAAACx0v8D1HNWqu6EnoAAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "import matplotlib.pyplot as plt\n", "\n", @@ -156,9 +284,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "fb38dd99", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T02:49:01.862156Z", + "iopub.status.busy": "2026-08-25T02:49:01.862036Z", + "iopub.status.idle": "2026-08-25T02:49:02.289368Z", + "shell.execute_reply": "2026-08-25T02:49:02.288834Z" + } + }, "outputs": [], "source": [ "from scipy.special import softmax\n", @@ -172,16 +307,62 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "4393b9c3", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-08-25T02:49:02.290781Z", + "iopub.status.busy": "2026-08-25T02:49:02.290698Z", + "iopub.status.idle": "2026-08-25T02:49:02.500943Z", + "shell.execute_reply": "2026-08-25T02:49:02.500631Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "import seaborn as sns\n", + "import matplotlib.pyplot as plt\n", "\n", "from sklearn.metrics import confusion_matrix\n", "\n", - "sns.heatmap(confusion_matrix(y, y_pred), annot=True, fmt=\"0.0f\")" + "counts = confusion_matrix(y, y_pred)\n", + "\n", + "fig, ax = plt.subplots(figsize=(6, 5.5), dpi=144)\n", + "image = ax.imshow(counts, cmap=\"Blues\")\n", + "\n", + "# White reads on the dark end of the ramp, ink on the light end.\n", + "dark_cell_floor = counts.max() / 2\n", + "\n", + "for (true_digit, predicted_digit), count in np.ndenumerate(counts):\n", + " if count:\n", + " text_color = \"white\" if count > dark_cell_floor else \"0.15\"\n", + " ax.text(\n", + " predicted_digit,\n", + " true_digit,\n", + " count,\n", + " ha=\"center\",\n", + " va=\"center\",\n", + " color=text_color,\n", + " fontsize=8,\n", + " )\n", + "\n", + "ax.set(xlabel=\"Predicted digit\", ylabel=\"True digit\", title=\"Confusion matrix\")\n", + "ax.set_xticks(range(10))\n", + "ax.set_yticks(range(10))\n", + "ax.tick_params(length=0)\n", + "for spine in ax.spines.values():\n", + " spine.set_visible(False)\n", + "fig.colorbar(image, ax=ax, shrink=0.8, label=\"Examples\")\n", + "plt.show()" ] } ], @@ -190,6 +371,386 @@ "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.14.6" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": { + "0bcdce547bc8446ba9950b2a6e8999b2": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_ea54cb9334144ea99d84dc21cc7c0ea6", + "placeholder": "​", + "style": "IPY_MODEL_2a298c03a7f041efae80cedf9ab23d08", + "tabbable": null, + "tooltip": null, + "value": " 10000/10000 [00:07<00:00, 2503.90it/s]" + } + }, + "2a298c03a7f041efae80cedf9ab23d08": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "34cce22552b64a3380060cf22320bfb0": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "65921c5a2e444a7e91f9d582e65e7407": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_665e01e9a39d432da11fb7a80eb45a5b", + "placeholder": "​", + "style": "IPY_MODEL_91fd3a5117aa448a8144bce91a69026c", + "tabbable": null, + "tooltip": null, + "value": "100%" + } + }, + "665e01e9a39d432da11fb7a80eb45a5b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "88c307250c7a4eb19a0cf850d723a947": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "91fd3a5117aa448a8144bce91a69026c": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "9a022c9eaec14e249c4608ec26adc36b": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_34cce22552b64a3380060cf22320bfb0", + "max": 10000.0, + "min": 0.0, + "orientation": "horizontal", + "style": "IPY_MODEL_88c307250c7a4eb19a0cf850d723a947", + "tabbable": null, + "tooltip": null, + "value": 10000.0 + } + }, + "9c6a6dabb2054400b3e72ff114b6c647": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "abe107262e274a1c98bc59c2510f10fc": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_65921c5a2e444a7e91f9d582e65e7407", + "IPY_MODEL_9a022c9eaec14e249c4608ec26adc36b", + "IPY_MODEL_0bcdce547bc8446ba9950b2a6e8999b2" + ], + "layout": "IPY_MODEL_9c6a6dabb2054400b3e72ff114b6c647", + "tabbable": null, + "tooltip": null + } + }, + "ea54cb9334144ea99d84dc21cc7c0ea6": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + } + }, + "version_major": 2, + "version_minor": 0 + } } }, "nbformat": 4, From b621b94ddd5cb4b71b1da30e7fa5e039241d9f26 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 22:07:29 -0500 Subject: [PATCH 05/26] Add a Sphinx documentation site Read the Docs cannot read a pixi environment, so conda_envs/environment-docs.yml duplicates the docs dependency list that pyproject.toml also carries. --- .github/workflows/rtd-preview.yml | 30 +++ .readthedocs.yaml | 24 ++ conda_envs/environment-docs.yml | 35 +++ conda_envs/pytensor_ml.yml | 7 - docs/.gitignore | 20 ++ docs/Makefile | 34 +++ docs/make.bat | 29 +++ docs/source/_static/.gitkeep | 0 docs/source/_templates/autosummary/class.rst | 34 +++ docs/source/api.rst | 18 ++ docs/source/api/activations.rst | 17 ++ docs/source/api/layers.rst | 101 ++++++++ docs/source/api/loss.rst | 12 + docs/source/api/model.rst | 9 + docs/source/api/optim.rst | 101 ++++++++ docs/source/api/pytensorf.rst | 33 +++ docs/source/api/serialization.rst | 24 ++ docs/source/api/state.rst | 37 +++ docs/source/api/util.rst | 9 + docs/source/conf.py | 176 +++++++++++++ docs/source/dev/contributing.rst | 35 +++ docs/source/dev/docs.rst | 87 +++++++ docs/source/dev/index.rst | 8 + docs/source/get_started/about.rst | 24 ++ docs/source/get_started/index.rst | 9 + docs/source/get_started/install.rst | 53 ++++ docs/source/get_started/quickstart.rst | 58 +++++ docs/source/index.rst | 70 +++++ docs/source/references.bib | 44 ++++ docs/source/user_guide/backends.rst | 9 + docs/source/user_guide/design.rst | 9 + docs/source/user_guide/index.rst | 12 + docs/source/user_guide/layers.rst | 9 + docs/source/user_guide/optimizers.rst | 9 + docs/source/user_guide/serialization.rst | 9 + docs/source/user_guide/training.rst | 9 + docs/sphinxext/generate_gallery.py | 254 +++++++++++++++++++ pyproject.toml | 1 + 38 files changed, 1452 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/rtd-preview.yml create mode 100644 .readthedocs.yaml create mode 100644 conda_envs/environment-docs.yml create mode 100644 docs/.gitignore create mode 100644 docs/Makefile create mode 100644 docs/make.bat create mode 100644 docs/source/_static/.gitkeep create mode 100644 docs/source/_templates/autosummary/class.rst create mode 100644 docs/source/api.rst create mode 100644 docs/source/api/activations.rst create mode 100644 docs/source/api/layers.rst create mode 100644 docs/source/api/loss.rst create mode 100644 docs/source/api/model.rst create mode 100644 docs/source/api/optim.rst create mode 100644 docs/source/api/pytensorf.rst create mode 100644 docs/source/api/serialization.rst create mode 100644 docs/source/api/state.rst create mode 100644 docs/source/api/util.rst create mode 100644 docs/source/conf.py create mode 100644 docs/source/dev/contributing.rst create mode 100644 docs/source/dev/docs.rst create mode 100644 docs/source/dev/index.rst create mode 100644 docs/source/get_started/about.rst create mode 100644 docs/source/get_started/index.rst create mode 100644 docs/source/get_started/install.rst create mode 100644 docs/source/get_started/quickstart.rst create mode 100644 docs/source/index.rst create mode 100644 docs/source/references.bib create mode 100644 docs/source/user_guide/backends.rst create mode 100644 docs/source/user_guide/design.rst create mode 100644 docs/source/user_guide/index.rst create mode 100644 docs/source/user_guide/layers.rst create mode 100644 docs/source/user_guide/optimizers.rst create mode 100644 docs/source/user_guide/serialization.rst create mode 100644 docs/source/user_guide/training.rst create mode 100644 docs/sphinxext/generate_gallery.py diff --git a/.github/workflows/rtd-preview.yml b/.github/workflows/rtd-preview.yml new file mode 100644 index 0000000..dc2cf7b --- /dev/null +++ b/.github/workflows/rtd-preview.yml @@ -0,0 +1,30 @@ +name: Read the Docs PR preview + +on: + pull_request_target: + types: + - opened + - synchronize + - reopened + # Only fire when something that could change the docs build is touched. + paths: + - "docs/**" + - ".readthedocs.yaml" + - "conda_envs/environment-docs.yml" + - "examples/**" + - "pytensor_ml/**" + - "pyproject.toml" + +permissions: + pull-requests: write + +jobs: + documentation-links: + runs-on: ubuntu-latest + steps: + - uses: readthedocs/actions/preview@v1 + with: + # Project slug as configured on Read the Docs. Verify this matches the slug shown in the + # RTD project URL (https://readthedocs.org/projects//) once the project is imported + # there. + project-slug: "pytensor-ml" diff --git a/.readthedocs.yaml b/.readthedocs.yaml new file mode 100644 index 0000000..6af33a9 --- /dev/null +++ b/.readthedocs.yaml @@ -0,0 +1,24 @@ +version: 2 + +sphinx: + configuration: docs/source/conf.py + fail_on_warning: false + +conda: + environment: conda_envs/environment-docs.yml + +python: + install: + - method: pip + path: . + +build: + os: "ubuntu-22.04" + tools: + python: "miniforge3-latest" + jobs: + # hatch-vcs derives the version from git tags; RTD's default checkout is shallow and tagless, so + # fetch tags or pytensor_ml.__version__ falls back to 0.0.0+unknown and the version selector + # labels break. + post_checkout: + - git fetch --tags --unshallow || true diff --git a/conda_envs/environment-docs.yml b/conda_envs/environment-docs.yml new file mode 100644 index 0000000..e550874 --- /dev/null +++ b/conda_envs/environment-docs.yml @@ -0,0 +1,35 @@ +# The environment Read the Docs builds from. `pixi run docs-build` uses the equivalent list under +# [tool.pixi.feature.docs] in pyproject.toml; the two have to move together. +name: pytensor_ml-docs +channels: + - conda-forge + - nodefaults + +dependencies: + - python>=3.12 + # Runtime deps: autodoc imports pytensor_ml, so the full runtime stack has to be in scope. + - pytensor>=3.3.0,<3.4.0 + - numpy + - safetensors + # The gallery extension renders notebook thumbnails with matplotlib. + - matplotlib + + # Docs build deps. + - ipython + - jupyter + - sphinx>=7 + - pydata-sphinx-theme + - myst-nb + - numpydoc + - sphinx-copybutton + - sphinx-design + - sphinx-codeautolink + - sphinx-sitemap + - sphinx-notfound-page + - sphinx-autobuild + - jupyter-sphinx + - sphinxcontrib-bibtex + - pip + - pip: + # pytensor_ml itself so autodoc resolves the current source tree. + - -e .. diff --git a/conda_envs/pytensor_ml.yml b/conda_envs/pytensor_ml.yml index 2152ac1..fba4723 100644 --- a/conda_envs/pytensor_ml.yml +++ b/conda_envs/pytensor_ml.yml @@ -20,13 +20,6 @@ dependencies: - pytest-mock - pyyaml # tests/test_workflow_groups.py reads the CI matrix - # For building docs - - sphinx - - sphinx_rtd_theme - - pygments - - pydot - - ipython - # developer tools - pre-commit # Pinned rather than floated: a local run that disagrees with CI about the version is a local run diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..7872e43 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,20 @@ +# Sphinx build output +build/ +jupyter_execute/ + +# Python cache +__pycache__/ +*.pyc + +# Autosummary-generated stubs (one .rst per object, regenerated on build) +source/api/generated/ +source/api/**/generated/ +source/api/**/classmethods/ + +# Notebook gallery artifacts written by docs/sphinxext/generate_gallery.py +source/examples/gallery.rst +source/examples/examples/ +source/examples/introductory/ +source/examples/advanced/ +source/examples/case_study/ +source/_thumbnails/ diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..09c0c53 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,34 @@ +# Minimal Makefile for Sphinx documentation. +# Mirrors the standard sphinx-quickstart output. + +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = source +BUILDDIR = build + +.PHONY: help clean html show livehtml linkcheck Makefile + +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +clean: + rm -rf "$(BUILDDIR)" + rm -rf "$(SOURCEDIR)"/api/generated + rm -rf "$(SOURCEDIR)"/api/*/generated + rm -rf "$(SOURCEDIR)"/_thumbnails + rm -f "$(SOURCEDIR)"/examples/gallery.rst + +html: + @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +show: html + @open "$(BUILDDIR)/html/index.html" + +livehtml: + sphinx-autobuild "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) $(O) + +linkcheck: + @$(SPHINXBUILD) -M linkcheck "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..49fa0ba --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,29 @@ +@ECHO OFF + +pushd %~dp0 + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set SOURCEDIR=source +set BUILDDIR=build + +%SPHINXBUILD% >NUL 2>NUL +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Install Sphinx, then re-run. + exit /b 1 +) + +if "%1" == "" goto help + +%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% +goto end + +:help +%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% + +:end +popd diff --git a/docs/source/_static/.gitkeep b/docs/source/_static/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/docs/source/_templates/autosummary/class.rst b/docs/source/_templates/autosummary/class.rst new file mode 100644 index 0000000..f1f165f --- /dev/null +++ b/docs/source/_templates/autosummary/class.rst @@ -0,0 +1,34 @@ +{{ fullname | escape | underline}} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + + {% block methods %} + {% if methods %} + + .. rubric:: Methods + + .. autosummary:: + :toctree: classmethods + + {% for item in methods %} + {%- if item not in inherited_members %} + {{ objname }}.{{ item }} + {% endif %} + {%- endfor %} + {% endif %} + {% endblock %} + + {% block attributes %} + {% if attributes %} + .. rubric:: Attributes + + .. autosummary:: + {% for item in attributes %} + {%- if item not in inherited_members %} + ~{{ name }}.{{ item }} + {% endif %} + {%- endfor %} + {% endif %} + {% endblock %} diff --git a/docs/source/api.rst b/docs/source/api.rst new file mode 100644 index 0000000..c700208 --- /dev/null +++ b/docs/source/api.rst @@ -0,0 +1,18 @@ +.. _api: + +API Reference +============= + +.. toctree:: + :maxdepth: 1 + :titlesonly: + + api/model + api/layers + api/activations + api/loss + api/optim + api/state + api/serialization + api/pytensorf + api/util diff --git a/docs/source/api/activations.rst b/docs/source/api/activations.rst new file mode 100644 index 0000000..98b27d8 --- /dev/null +++ b/docs/source/api/activations.rst @@ -0,0 +1,17 @@ +Activations +=========== + +.. currentmodule:: pytensor_ml.activations + +.. autosummary:: + :toctree: generated/ + + Activation + ReLU + LeakyReLU + GELU + Swish + Sigmoid + SoftPlus + Softmax + Tanh diff --git a/docs/source/api/layers.rst b/docs/source/api/layers.rst new file mode 100644 index 0000000..2b335ac --- /dev/null +++ b/docs/source/api/layers.rst @@ -0,0 +1,101 @@ +Layers +====== + +.. currentmodule:: pytensor_ml.layers + +Base +---- + +.. autosummary:: + :toctree: generated/ + + Layer + +Combinators +----------- + +.. autosummary:: + :toctree: generated/ + + Input + Sequential + Concatenate + Flatten + Squeeze + +Dense +----- + +.. autosummary:: + :toctree: generated/ + + Linear + Embedding + +Convolution and pooling +----------------------- + +.. autosummary:: + :toctree: generated/ + + Conv1D + Conv2D + ConvTranspose1D + ConvTranspose2D + MaxPool1D + MaxPool2D + AvgPool1D + AvgPool2D + +Padding +------- + +.. autosummary:: + :toctree: generated/ + + ZeroPad1D + ZeroPad2D + ConstantPad1D + ConstantPad2D + ReflectionPad1D + ReflectionPad2D + ReplicationPad1D + ReplicationPad2D + +Normalization and regularization +-------------------------------- + +.. autosummary:: + :toctree: generated/ + + BatchNorm + LayerNorm + Dropout + +Recurrent +--------- + +.. autosummary:: + :toctree: generated/ + + RNN + LSTM + GRU + Bidirectional + Recurrent + RecurrentCell + ElmanCell + LSTMCell + GRUCell + +Attention and transformers +-------------------------- + +.. autosummary:: + :toctree: generated/ + + MultiheadAttention + CausalSelfAttention + FeedForward + TransformerBlock + scaled_dot_product_attention diff --git a/docs/source/api/loss.rst b/docs/source/api/loss.rst new file mode 100644 index 0000000..6c38855 --- /dev/null +++ b/docs/source/api/loss.rst @@ -0,0 +1,12 @@ +Losses +====== + +.. currentmodule:: pytensor_ml.loss + +.. autosummary:: + :toctree: generated/ + + Loss + SquaredError + CrossEntropy + supervised_loss diff --git a/docs/source/api/model.rst b/docs/source/api/model.rst new file mode 100644 index 0000000..8ecce4b --- /dev/null +++ b/docs/source/api/model.rst @@ -0,0 +1,9 @@ +Model +===== + +.. currentmodule:: pytensor_ml.model + +.. autosummary:: + :toctree: generated/ + + Model diff --git a/docs/source/api/optim.rst b/docs/source/api/optim.rst new file mode 100644 index 0000000..3807d31 --- /dev/null +++ b/docs/source/api/optim.rst @@ -0,0 +1,101 @@ +Optimization +============ + +.. currentmodule:: pytensor_ml.optim + +Training +-------- + +.. autosummary:: + :toctree: generated/ + + compile_train + +Update rules +------------ + +.. autosummary:: + :toctree: generated/ + + sgd + adam + adamw + nadam + adamax + rmsprop + rprop + adagrad + adadelta + +Transforms +---------- + +.. autosummary:: + :toctree: generated/ + + chain + scale + scale_by_schedule + add_weight_decay + trace + clip_by_global_norm + clip_by_value + +Guards and policies +------------------- + +.. autosummary:: + :toctree: generated/ + + skip_if + apply_if_finite + nonfinite + large_step + reduce_on_plateau + Decision + SkipCondition + +Schedules +--------- + +.. autosummary:: + :toctree: generated/ + + constant_schedule + linear_schedule + cosine_schedule + exponential_schedule + polynomial_schedule + step_decay + join_schedules + +Building blocks +--------------- + +.. autosummary:: + :toctree: generated/ + + UpdateRule + Transform + Schedule + Rate + LearningRate + get_gradients + scalar_state + to_floatx + +Low-level update functions +-------------------------- + +.. autosummary:: + :toctree: generated/ + + sgd_updates + adam_updates + adamw_updates + nadam_updates + adamax_updates + rmsprop_updates + rprop_updates + adagrad_updates + adadelta_updates diff --git a/docs/source/api/pytensorf.rst b/docs/source/api/pytensorf.rst new file mode 100644 index 0000000..c91614f --- /dev/null +++ b/docs/source/api/pytensorf.rst @@ -0,0 +1,33 @@ +Graph tools +=========== + +.. currentmodule:: pytensor_ml.pytensorf + +Compilation +----------- + +.. autosummary:: + :toctree: generated/ + + function + compile_predict + rewrite_for_prediction + rewrite_pregrad + +Graph inspection +---------------- + +.. autosummary:: + :toctree: generated/ + + collect_graph_inputs + collect_data_inputs + collect_trainable_params + collect_non_trainable_params + collect_differentiable_params + collect_shared_variables + collect_step_counters + collect_clock_updates + collect_non_trainable_updates + find_rng_nodes + as_output_list diff --git a/docs/source/api/serialization.rst b/docs/source/api/serialization.rst new file mode 100644 index 0000000..3dd8ef3 --- /dev/null +++ b/docs/source/api/serialization.rst @@ -0,0 +1,24 @@ +Saving and loading +================== + +.. currentmodule:: pytensor_ml + +Networks +-------- + +.. autosummary:: + :toctree: generated/ + + save_pretrained + from_pretrained + save_network + load_network + +Weights +------- + +.. autosummary:: + :toctree: generated/ + + save_state + load_state diff --git a/docs/source/api/state.rst b/docs/source/api/state.rst new file mode 100644 index 0000000..97b63b5 --- /dev/null +++ b/docs/source/api/state.rst @@ -0,0 +1,37 @@ +Parameters and initialization +============================= + +.. currentmodule:: pytensor_ml.params + +Parameters +---------- + +.. autosummary:: + :toctree: generated/ + + TrainableParameter + NonTrainableParameter + StepCounter + trainable + non_trainable + step_counter + +.. currentmodule:: pytensor_ml.state + +Initializers +------------ + +.. autosummary:: + :toctree: generated/ + + Initializer + ZeroInitializer + OneInitializer + NormalInitializer + UnitUniformInitializer + XavierNormalInitializer + XavierUniformInitializer + OrthogonalInitializer + UnrecordedInitializer + initialize_params + fans diff --git a/docs/source/api/util.rst b/docs/source/api/util.rst new file mode 100644 index 0000000..b3b1bd7 --- /dev/null +++ b/docs/source/api/util.rst @@ -0,0 +1,9 @@ +Utilities +========= + +.. currentmodule:: pytensor_ml.util + +.. autosummary:: + :toctree: generated/ + + DataLoader diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..bd6a1cb --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,176 @@ +import os +import sys + +from pathlib import Path + +root_dir = Path("../..").resolve() +sys.path.insert(0, str(root_dir)) +sys.path.insert(0, str(root_dir / "docs" / "sphinxext")) + +import pytensor_ml # noqa: E402 + +# -- Project information ----------------------------------------------------- +project = "pytensor_ml" +copyright = "2025, PyMC Developers" +author = "PyMC Developers" +language = "en" +html_baseurl = "https://pytensor-ml.readthedocs.io" + +# -- Version handling -------------------------------------------------------- +# Mirrors the gEconpy / pytensor pattern so RTD version selector labels match +# what users see in the package. +version = pytensor_ml.__version__ +on_readthedocs = os.environ.get("READTHEDOCS", None) +rtd_version = os.environ.get("READTHEDOCS_VERSION", "") +if on_readthedocs: + if rtd_version.lower() == "stable": + version = pytensor_ml.__version__.split("+")[0] + elif rtd_version.lower() == "latest": + version = "dev" + else: + version = rtd_version +else: + rtd_version = "local" +release = version + +# -- General configuration --------------------------------------------------- +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.autosummary", + "sphinx.ext.autosectionlabel", + "sphinx.ext.intersphinx", + "sphinx.ext.mathjax", + "numpydoc", + "myst_nb", + "sphinx_design", + "sphinx_copybutton", + "sphinx_codeautolink", + "sphinx_sitemap", + "notfound.extension", + "jupyter_sphinx", + "sphinxcontrib.bibtex", + "generate_gallery", +] + +# Bibliographic citations: drop new entries into docs/source/references.bib +# and cite from prose with `{cite:t}` (textual) or `{cite:p}` (parenthetical). +bibtex_bibfiles = ["references.bib"] +bibtex_default_style = "unsrt" +bibtex_reference_style = "author_year" + +# Use the document path as prefix for autosectionlabel anchors so the same +# section title in two files doesn't collide. +autosectionlabel_prefix_document = True + +templates_path = ["_templates"] + +exclude_patterns = [ + "_build", + "**.ipynb_checkpoints", + "*/autosummary/*.rst", + "Thumbs.db", + ".DS_Store", +] + +source_suffix = { + ".rst": "restructuredtext", + ".md": "myst-nb", + ".ipynb": "myst-nb", + ".myst": "myst-nb", +} + +master_doc = "index" + +# -- Autodoc / autosummary --------------------------------------------------- +autosummary_generate = True +autodoc_typehints = "none" +autoclass_content = "class" +# Class method pages live under api/.../classmethods/ — keep them out of the +# global toctree so they don't pollute the sidebar. +remove_from_toctrees = ["**/classmethods/*"] + +numpydoc_show_class_members = False +numpydoc_xref_param_type = True +numpydoc_xref_ignore = { + "of", + "or", + "optional", + "default", + "numeric", + "type", + "scalar", + "instance", + "array", + "array_like", + "1D", + "2D", + "3D", + "nD", + "M", + "N", + "D", + "K", +} + +# -- HTML output ------------------------------------------------------------- +html_theme = "pydata_sphinx_theme" +html_title = "pytensor_ml" +html_short_title = "pytensor_ml" +html_last_updated_fmt = "" + +sitemap_url_scheme = f"{{lang}}{rtd_version}/{{link}}" + +html_theme_options = { + "secondary_sidebar_items": ["page-toc", "edit-this-page", "sourcelink"], + "navbar_start": ["navbar-logo"], + "show_prev_next": True, + "icon_links": [ + { + "url": "https://github.com/pymc-devs/pytensor-ml", + "icon": "fa-brands fa-github", + "name": "GitHub", + "type": "fontawesome", + }, + ], +} + +github_version = version if "." in rtd_version else "main" +html_context = { + "github_url": "https://github.com", + "github_user": "pymc-devs", + "github_repo": "pytensor-ml", + "github_version": github_version, + "doc_path": "docs/source", + "default_mode": "dark", +} + +html_sidebars = {"**": ["sidebar-nav-bs.html", "searchbox.html"]} +html_static_path = ["_static"] + +# -- MyST / MyST-NB config --------------------------------------------------- +myst_enable_extensions = [ + "colon_fence", + "deflist", + "dollarmath", + "amsmath", + "substitution", +] +myst_dmath_double_inline = True + +# Notebooks ship pre-rendered. Re-executing them on RTD would require pinning +# every numerical dep and would slow the build significantly; flip this to +# "auto" or "force" later if the gallery becomes the source of truth. +nb_execution_mode = "off" + +# -- Intersphinx ------------------------------------------------------------- +intersphinx_mapping = { + "python": ("https://docs.python.org/3/", None), + "numpy": ("https://numpy.org/doc/stable/", None), + "scipy": ("https://docs.scipy.org/doc/scipy/", None), + "jax": ("https://jax.readthedocs.io/en/latest/", None), + "torch": ("https://docs.pytorch.org/docs/stable/", None), + "pytensor": ("https://pytensor.readthedocs.io/en/latest/", None), + "pymc": ("https://www.pymc.io/projects/docs/en/stable/", None), + "myst": ("https://myst-parser.readthedocs.io/en/latest/", None), + "myst-nb": ("https://myst-nb.readthedocs.io/en/latest/", None), +} diff --git a/docs/source/dev/contributing.rst b/docs/source/dev/contributing.rst new file mode 100644 index 0000000..258875d --- /dev/null +++ b/docs/source/dev/contributing.rst @@ -0,0 +1,35 @@ +Contributing +============ + +.. note:: + + **WRITEME.** This page is a stub. Cover the branching and PR workflow, + test conventions (PyTorch as the reference implementation, per-backend + parametrization), and where new layers and optimizers should live. + +Development install +------------------- + +.. code-block:: bash + + git clone https://github.com/pymc-devs/pytensor-ml.git + cd pytensor-ml + pip install -e ".[dev]" + pre-commit install + +Running tests +------------- + +.. code-block:: bash + + pytest + +Style and typing +---------------- + +Formatting and linting run through ``ruff`` under pre-commit, and ``mypy`` +checks ``pytensor_ml/``. Both also run in CI, so a clean +``pre-commit run --all-files`` locally is the fastest way to keep a PR green. + +Bug reports and feature requests belong in the +`issue tracker `_. diff --git a/docs/source/dev/docs.rst b/docs/source/dev/docs.rst new file mode 100644 index 0000000..96cf7e6 --- /dev/null +++ b/docs/source/dev/docs.rst @@ -0,0 +1,87 @@ +Working on the docs +=================== + +This page covers how the documentation system is wired and the workflows +needed to extend it — adding an API page, a notebook example, or a citation. + +Building locally +---------------- + +The docs build from ``docs/`` using the ``pytensor_ml-docs`` conda +environment: + +.. code-block:: bash + + conda env update -f conda_envs/environment-docs.yml + cd docs + make show # build + open the rendered HTML in the default browser + make livehtml # auto-rebuild + auto-refresh on every save (sphinx-autobuild) + make clean # wipe build/ and all generated source (gallery, thumbnails, autosummary stubs) + +Read the Docs builds the same way; ``.readthedocs.yaml`` points at the same +conda env and ``docs/source/conf.py``. + +Layout +------ + +Source content lives under ``docs/source/``: + +.. list-table:: + :header-rows: 1 + :widths: 30 70 + + * - Path + - Purpose + * - ``index.rst`` + - Landing page + top-level toctree. + * - ``api.rst`` + ``api/*.rst`` + - Autosummary entry points; one file per public submodule. + * - ``get_started/`` + - Install + quickstart + about. Hand-written narrative. + * - ``user_guide/`` + - Conceptual pages (layers, training, optimizers, backends, design). + * - ``examples/gallery.rst`` + - Notebook gallery landing page. **Generated** at build time. + * - ``examples//*.ipynb`` + - Notebook copies staged from ``examples/``. **Generated**. + * - ``dev/`` + - This page and other contributor docs. + * - ``references.bib`` + - BibTeX entries; cited via ``{cite:t}`` or ``{cite:p}``. + * - ``_templates/autosummary/`` + - Sphinx autosummary class template (per-method subpages). + +Build-time-generated paths are gitignored via ``docs/.gitignore``; never +commit anything under ``source/_thumbnails/``, ``source/examples//``, +``source/api/**/generated/``, or ``source/examples/gallery.rst``. + +The custom Sphinx extension lives at ``docs/sphinxext/generate_gallery.py``. +It discovers notebooks under ``examples/``, copies them into +``docs/source/examples//``, extracts thumbnails, and emits +``examples/gallery.rst``. + +Adding an API page +------------------ + +Public objects are documented through ``autosummary`` stubs, so a new class or +function only needs an entry in the relevant ``docs/source/api/*.rst`` file +under the right section heading; the stub page is generated on the next build. +A whole new module gets its own ``api/.rst`` plus a line in the +``api.rst`` toctree. + +Adding a notebook example +------------------------- + +Drop the ``.ipynb`` under ``examples/``. The ``generate_gallery`` extension +auto-discovers it on the next build, extracts the last image output as a +thumbnail, and emits a grid card. Ship the notebook with its outputs already +rendered — ``nb_execution_mode`` is ``"off"``, so nothing is re-run at build +time. + +To group notebooks into named categories, create subdirectories under +``examples/`` (e.g. ``examples/introductory/foo.ipynb``). The subdir name +becomes the category id; pretty titles are looked up in ``CATEGORY_TITLES`` +inside ``generate_gallery.py`` and fall back to title-casing the folder name. + +Notebooks must be tracked by git to appear in the gallery, so untracked +work-in-progress notebooks under ``examples/`` don't pollute the build. diff --git a/docs/source/dev/index.rst b/docs/source/dev/index.rst new file mode 100644 index 0000000..1937f13 --- /dev/null +++ b/docs/source/dev/index.rst @@ -0,0 +1,8 @@ +Developer Guide +=============== + +.. toctree:: + :maxdepth: 1 + + contributing + docs diff --git a/docs/source/get_started/about.rst b/docs/source/get_started/about.rst new file mode 100644 index 0000000..dadcb5b --- /dev/null +++ b/docs/source/get_started/about.rst @@ -0,0 +1,24 @@ +About pytensor_ml +================= + +.. note:: + + **WRITEME.** This page is a stub. Fill in project motivation, scope, and + how pytensor_ml relates to the rest of the ecosystem (PyTorch, JAX/Flax, + Keras) and to PyMC. + +pytensor_ml is a deep learning library built on PyTensor's symbolic graph and +rewrite system. A network is a graph, not an object hierarchy with a runtime +attached: layers are graph constructors, parameters are shared variables, and +a training step is a compiled PyTensor function whose updates are the +optimizer. + +That design is what the library trades on. Gradients come from PyTensor's +symbolic differentiation, performance comes from its rewrites and its +backends, and a model composes with any other PyTensor graph — including a +PyMC model — because there is nothing else to interoperate with. + +.. note:: + + pytensor_ml is pre-alpha. The API is still moving and there is no + release-to-release compatibility guarantee yet. diff --git a/docs/source/get_started/index.rst b/docs/source/get_started/index.rst new file mode 100644 index 0000000..31840e0 --- /dev/null +++ b/docs/source/get_started/index.rst @@ -0,0 +1,9 @@ +Getting Started +=============== + +.. toctree:: + :maxdepth: 1 + + install + quickstart + about diff --git a/docs/source/get_started/install.rst b/docs/source/get_started/install.rst new file mode 100644 index 0000000..5c584d2 --- /dev/null +++ b/docs/source/get_started/install.rst @@ -0,0 +1,53 @@ +Installation +============ + +.. note:: + + **WRITEME.** This page is a stub. Flesh out with per-backend install + notes and any platform-specific caveats (MLX is macOS-only, JAX GPU + wheels, Numba threading layers). + +pytensor_ml targets Python ``>= 3.12``. Its hard dependencies are PyTensor +(``>= 3.2.3``), NumPy, and safetensors. + +From PyPI +--------- + +.. code-block:: bash + + pip install pytensor-ml + +From source +----------- + +.. code-block:: bash + + git clone https://github.com/pymc-devs/pytensor-ml.git + cd pytensor-ml + pip install -e . + +Backends +-------- + +The default C backend needs nothing extra. Every other backend is an optional +dependency, installed separately and imported only when a graph is actually +compiled against it: + +.. code-block:: bash + + pip install numba # mode="NUMBA" + pip install jax # mode="JAX" + pip install torch # mode="PYTORCH" + pip install mlx # mode="MLX", macOS only + +Development install +------------------- + +.. code-block:: bash + + git clone https://github.com/pymc-devs/pytensor-ml.git + cd pytensor-ml + pip install -e ".[dev]" + pre-commit install + +See :doc:`/dev/contributing` for the rest of the contributor setup. diff --git a/docs/source/get_started/quickstart.rst b/docs/source/get_started/quickstart.rst new file mode 100644 index 0000000..2bac0cb --- /dev/null +++ b/docs/source/get_started/quickstart.rst @@ -0,0 +1,58 @@ +Quickstart +========== + +.. note:: + + **WRITEME.** This page is a stub. Walk a new user end-to-end through + building, training, evaluating, and saving a model. Cross-link to the + :doc:`/examples/gallery` for the full notebooks. + +The snippet below trains a small classifier on scikit-learn's digits dataset. + +.. code-block:: python + + import numpy as np + import pytensor + + pytensor.config.floatX = "float32" + + from sklearn.datasets import load_digits + + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import Input, Linear, Sequential + from pytensor_ml.loss import CrossEntropy + from pytensor_ml.model import Model + from pytensor_ml.optim import adam, chain, clip_by_global_norm, cosine_schedule + from pytensor_ml.util import DataLoader + + X, y = load_digits(return_X_y=True) + X = (X / 16.0).astype("float32") + y_onehot = np.eye(10, dtype="float32")[y] + + X_in = Input("X_in", shape=(None, 64)) + network = Sequential( + Linear("fc1", n_in=64, n_out=128), + ReLU(), + Linear("logits", n_in=128, n_out=10), + ) + model = Model(X_in, network(X_in)).initialize(seed=0) + + rule = chain(adam(learning_rate=cosine_schedule(1e-3, total_steps=500)), clip_by_global_norm(1.0)) + loss_fn = CrossEntropy(expect_onehot_labels=True, expect_logits=True, reduction="mean") + step = model.compile_train(rule, loss_fn, ndim_out=2) + + loader = DataLoader(X, y_onehot, batch_size=64, random_state=0) + for _ in range(500): + loss_value = step(*loader()) + + accuracy = (model.predict(X).argmax(axis=-1) == y).mean() + +:meth:`~pytensor_ml.model.Model.compile_train` builds the loss against a +target placeholder, differentiates it, folds in any stateful layer updates +(batch norm running statistics, RNG advances, the training clock a schedule +reads), and compiles a one-step function. +:meth:`~pytensor_ml.model.Model.predict` compiles a separate inference pass, +with dropout removed and batch norm reading its running statistics. + +A :class:`~pytensor_ml.model.Model` is a convenience, not a requirement: +:func:`pytensor_ml.optim.compile_train` trains any loss graph you hand it. diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..c9d3d0c --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,70 @@ +pytensor_ml +=========== + +A(nother) deep learning library, built on top of PyTensor. + +Networks are ordinary PyTensor graphs. You build one out of layers, and +everything PyTensor already does — symbolic differentiation, graph rewrites, +and compilation to C, Numba, JAX, PyTorch, or MLX — applies to it unchanged. +Training is a compiled function that takes a batch and returns a loss; there +is no separate runtime or tape. + +pytensor_ml ships the usual layer library (dense, convolutional, recurrent, +attention, normalization), composable optimizers with learning-rate schedules +and step guards, and safetensors-backed serialization that round-trips both +weights and architecture. + +Quick install +------------- + +.. code-block:: bash + + pip install pytensor-ml + +See the :doc:`installation guide ` for backend extras. + +Quick example +------------- + +.. code-block:: python + + import numpy as np + + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import Input, Linear, Sequential + from pytensor_ml.loss import CrossEntropy + from pytensor_ml.model import Model + from pytensor_ml.optim import adam, chain, clip_by_global_norm, cosine_schedule + from pytensor_ml.util import DataLoader + + X_in = Input("X_in", shape=(None, 64)) + network = Sequential( + Linear("fc1", n_in=64, n_out=128), + ReLU(), + Linear("logits", n_in=128, n_out=10), + ) + model = Model(X_in, network(X_in)).initialize(seed=0) + + rule = chain(adam(learning_rate=cosine_schedule(1e-3, total_steps=500)), clip_by_global_norm(1.0)) + loss_fn = CrossEntropy(expect_onehot_labels=True, expect_logits=True, reduction="mean") + step = model.compile_train(rule, loss_fn, ndim_out=2) + + loader = DataLoader(X, y_onehot, batch_size=64, random_state=0) + for _ in range(500): + loss_value = step(*loader()) + + predictions = model.predict(X).argmax(axis=-1) + +See the :doc:`example gallery ` for full end-to-end +walkthroughs. + +.. toctree:: + :maxdepth: 1 + :hidden: + :titlesonly: + + get_started/index + user_guide/index + examples/gallery + api + dev/index diff --git a/docs/source/references.bib b/docs/source/references.bib new file mode 100644 index 0000000..f5fe3e2 --- /dev/null +++ b/docs/source/references.bib @@ -0,0 +1,44 @@ +@inproceedings{kingma2015adam, + title = {Adam: A Method for Stochastic Optimization}, + author = {Kingma, Diederik P. and Ba, Jimmy}, + booktitle = {International Conference on Learning Representations}, + year = {2015}, +} + +@inproceedings{loshchilov2019adamw, + title = {Decoupled Weight Decay Regularization}, + author = {Loshchilov, Ilya and Hutter, Frank}, + booktitle = {International Conference on Learning Representations}, + year = {2019}, +} + +@inproceedings{ioffe2015batchnorm, + title = {Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift}, + author = {Ioffe, Sergey and Szegedy, Christian}, + booktitle = {International Conference on Machine Learning}, + year = {2015}, +} + +@article{srivastava2014dropout, + title = {Dropout: A Simple Way to Prevent Neural Networks from Overfitting}, + author = {Srivastava, Nitish and Hinton, Geoffrey and Krizhevsky, Alex and Sutskever, Ilya and Salakhutdinov, Ruslan}, + journal = {Journal of Machine Learning Research}, + volume = {15}, + number = {56}, + pages = {1929--1958}, + year = {2014}, +} + +@inproceedings{vaswani2017attention, + title = {Attention Is All You Need}, + author = {Vaswani, Ashish and Shazeer, Noam and Parmar, Niki and Uszkoreit, Jakob and Jones, Llion and Gomez, Aidan N. and Kaiser, Lukasz and Polosukhin, Illia}, + booktitle = {Advances in Neural Information Processing Systems}, + year = {2017}, +} + +@inproceedings{glorot2010init, + title = {Understanding the Difficulty of Training Deep Feedforward Neural Networks}, + author = {Glorot, Xavier and Bengio, Yoshua}, + booktitle = {International Conference on Artificial Intelligence and Statistics}, + year = {2010}, +} diff --git a/docs/source/user_guide/backends.rst b/docs/source/user_guide/backends.rst new file mode 100644 index 0000000..825b47c --- /dev/null +++ b/docs/source/user_guide/backends.rst @@ -0,0 +1,9 @@ +Backends +======== + +.. note:: + + **WRITEME.** This page is a stub. Cover choosing a compile mode, which + ops have backend-specific implementations, how ``pytensor_ml.dispatch`` + registers them lazily, and what to expect performance-wise from each + backend. diff --git a/docs/source/user_guide/design.rst b/docs/source/user_guide/design.rst new file mode 100644 index 0000000..3daa691 --- /dev/null +++ b/docs/source/user_guide/design.rst @@ -0,0 +1,9 @@ +Design +====== + +.. note:: + + **WRITEME.** This page is a stub. Cover why a network is a graph rather + than a module tree, how parameters and RNGs live as shared variables, the + rewrite passes the library adds, and the consequences for interoperating + with plain PyTensor and PyMC. diff --git a/docs/source/user_guide/index.rst b/docs/source/user_guide/index.rst new file mode 100644 index 0000000..9fa188e --- /dev/null +++ b/docs/source/user_guide/index.rst @@ -0,0 +1,12 @@ +User Guide +========== + +.. toctree:: + :maxdepth: 1 + + layers + training + optimizers + serialization + backends + design diff --git a/docs/source/user_guide/layers.rst b/docs/source/user_guide/layers.rst new file mode 100644 index 0000000..66a3311 --- /dev/null +++ b/docs/source/user_guide/layers.rst @@ -0,0 +1,9 @@ +Building networks +================= + +.. note:: + + **WRITEME.** This page is a stub. Cover what a layer is (a graph + constructor, not a stateful module), naming and parameter ownership, + composing with ``Sequential`` and the other combinators, and writing a + custom layer. diff --git a/docs/source/user_guide/optimizers.rst b/docs/source/user_guide/optimizers.rst new file mode 100644 index 0000000..cb2c66e --- /dev/null +++ b/docs/source/user_guide/optimizers.rst @@ -0,0 +1,9 @@ +Optimizers and schedules +======================== + +.. note:: + + **WRITEME.** This page is a stub. Cover update rules as compositions of + transforms, ``chain``, gradient clipping and weight decay, learning-rate + schedules and the training clock they read, and step guards + (``skip_if``, ``apply_if_finite``). diff --git a/docs/source/user_guide/serialization.rst b/docs/source/user_guide/serialization.rst new file mode 100644 index 0000000..97ea95c --- /dev/null +++ b/docs/source/user_guide/serialization.rst @@ -0,0 +1,9 @@ +Saving and loading +================== + +.. note:: + + **WRITEME.** This page is a stub. Cover weights-only checkpoints + (``save_state`` / ``load_state``) versus full-network round-trips + (``save_pretrained`` / ``from_pretrained``), the on-disk layout, and what + graph serialization does and does not support. diff --git a/docs/source/user_guide/training.rst b/docs/source/user_guide/training.rst new file mode 100644 index 0000000..d277864 --- /dev/null +++ b/docs/source/user_guide/training.rst @@ -0,0 +1,9 @@ +Training +======== + +.. note:: + + **WRITEME.** This page is a stub. Cover the training step as a compiled + function, losses and target placeholders, batching with ``DataLoader``, + stateful layers (batch norm statistics, dropout RNGs) and how their + updates are threaded, and the train/predict graph split. diff --git a/docs/sphinxext/generate_gallery.py b/docs/sphinxext/generate_gallery.py new file mode 100644 index 0000000..d732413 --- /dev/null +++ b/docs/sphinxext/generate_gallery.py @@ -0,0 +1,254 @@ +# Sphinx plugin that stages every notebook under examples/ into the docs tree, extracts a thumbnail +# from its last image output, and emits the grid-card gallery page. Categories come from the +# subdirectory a notebook sits in; see docs/source/dev/docs.rst. +# Adapted from gEconpy, which adapted it from PyMC / seaborn / mpld3. + +import base64 +import json +import shutil +import subprocess + +from pathlib import Path + +import matplotlib + +matplotlib.use("Agg") +import matplotlib.pyplot as plt +import sphinx + +from matplotlib import image + +logger = sphinx.util.logging.getLogger(__name__) + +# Repo root: docs/sphinxext/generate_gallery.py -> repo +REPO_ROOT = Path(__file__).resolve().parent.parent.parent +NOTEBOOKS_ROOT = REPO_ROOT / "examples" + +# Pretty titles for known subfolders. Anything not listed is title-cased. +CATEGORY_TITLES = { + "examples": "Examples", + "introductory": "Introductory", + "advanced": "Advanced", + "case_study": "Case Studies", +} + +TITLE = """ +Example Gallery +=============== +""" + +TOCTREE_HEAD = """ +.. toctree:: + :hidden: + +""" + +SECTION_TEMPLATE = """ +.. _gallery-{section_id}: + +{section_title} +{underlines} + +.. grid:: 1 2 3 3 + :gutter: 4 + +""" + +ITEM_TEMPLATE = """ + .. grid-item-card:: :doc:`{doc_name}` + :img-top: {image} + :link: {doc_reference} + :link-type: {link_type} + :shadow: none +""" + + +def is_tracked_by_git(filepath): + try: + result = subprocess.run( + ["git", "ls-files", "--error-unmatch", str(filepath)], + capture_output=True, + check=False, + cwd=REPO_ROOT, + ) + except FileNotFoundError: + return True + else: + return result.returncode == 0 + + +def create_thumbnail(infile, width=275, height=275, cx=0.5, cy=0.5, border=4): + im = image.imread(infile) + rows, cols = im.shape[:2] + size = min(rows, cols) + if size == cols: + xslice = slice(0, size) + ymin = min(max(0, int(cy * rows - size // 2)), rows - size) + yslice = slice(ymin, ymin + size) + else: + yslice = slice(0, size) + xmin = min(max(0, int(cx * cols - size // 2)), cols - size) + xslice = slice(xmin, xmin + size) + thumb = im[yslice, xslice] + thumb[:border, :, :3] = thumb[-border:, :, :3] = 0 + thumb[:, :border, :3] = thumb[:, -border:, :3] = 0 + + dpi = 100 + fig = plt.figure(figsize=(width / dpi, height / dpi), dpi=dpi) + ax = fig.add_axes([0, 0, 1, 1], aspect="auto", frameon=False, xticks=[], yticks=[]) + ax.imshow(thumb, aspect="auto", resample=True, interpolation="bilinear") + fig.savefig(infile, dpi=dpi) + plt.close(fig) + + +class NotebookGenerator: + """Extract a thumbnail and stage a notebook for inclusion in the gallery.""" + + def __init__(self, src_nb: Path, category: str, examples_dir: Path, thumbnails_dir: Path): + self.src_nb = src_nb + self.stripped_name = src_nb.stem + self.category = category + self.staged_nb = examples_dir / category / f"{self.stripped_name}.ipynb" + self.png_path = thumbnails_dir / category / f"{self.stripped_name}.png" + + with src_nb.open(encoding="utf-8") as fid: + self.json_source = json.load(fid) + + def stage_notebook(self): + self.staged_nb.parent.mkdir(parents=True, exist_ok=True) + # Always re-copy: notebooks at the source can change between builds. + shutil.copyfile(self.src_nb, self.staged_nb) + + def extract_preview_pic(self): + pic = None + for cell in self.json_source["cells"]: + for output in cell.get("outputs", []): + if "image/png" in output.get("data", []): + pic = output["data"]["image/png"] + if pic is not None: + return base64.b64decode(pic) + return None + + def gen_previews(self): + self.png_path.parent.mkdir(parents=True, exist_ok=True) + if self.png_path.exists(): + logger.info( + f"Custom thumbnail already exists for {self.src_nb.name}, skipping extraction", + type="thumbnail_extractor", + ) + return + + preview = self.extract_preview_pic() + if preview is not None: + with self.png_path.open("wb") as buff: + buff.write(preview) + create_thumbnail(self.png_path) + else: + logger.warning( + f"No image found in {self.src_nb.name}; its gallery card will have no thumbnail. " + f"Re-run the notebook with its outputs saved, or drop a PNG at {self.png_path}.", + type="thumbnail_extractor", + ) + + +def discover_notebooks(): + """ + Group notebooks by the category they belong to. + + Returns + ------- + grouped : dict mapping str to list of pathlib.Path + Notebook paths keyed by category: the immediate subfolder of ``examples/`` a notebook sits in, + or ``"examples"`` for one at the top level. + """ + if not NOTEBOOKS_ROOT.exists(): + return {} + + grouped: dict[str, list[Path]] = {} + for path in sorted(NOTEBOOKS_ROOT.rglob("*.ipynb")): + if ".ipynb_checkpoints" in path.parts: + continue + rel = path.relative_to(NOTEBOOKS_ROOT) + category = rel.parts[0] if len(rel.parts) > 1 else "examples" + grouped.setdefault(category, []).append(path) + return grouped + + +def main(app): + logger.info("Starting pytensor_ml example gallery generation.") + + src_dir = Path(app.builder.srcdir) + examples_dir = src_dir / "examples" + thumbnails_dir = src_dir / "_thumbnails" + examples_dir.mkdir(parents=True, exist_ok=True) + thumbnails_dir.mkdir(parents=True, exist_ok=True) + + grouped = discover_notebooks() + + if not grouped: + logger.warning( + "No notebooks found under examples/; writing empty gallery.", + type="thumbnail_extractor", + ) + + toctree_entries: list[str] = [] + section_lines: list[str] = [] + + for category in sorted(grouped): + nb_paths = grouped[category] + title = CATEGORY_TITLES.get(category, category.replace("_", " ").title()) + section_lines.append( + SECTION_TEMPLATE.format( + section_title=title, + section_id=category, + underlines="-" * len(title), + ) + ) + + for nb_path in nb_paths: + if not is_tracked_by_git(nb_path): + logger.info( + f"Skipping {nb_path.name}, not tracked by git", + type="thumbnail_extractor", + ) + continue + + nbg = NotebookGenerator( + src_nb=nb_path, + category=category, + examples_dir=examples_dir, + thumbnails_dir=thumbnails_dir, + ) + nbg.stage_notebook() + nbg.gen_previews() + + doc_name = f"{category}/{nbg.stripped_name}" + toctree_entries.append(doc_name) + # Path is relative to docs/source/ — the leading slash makes + # Sphinx resolve it from the source root, matching gEconpy's + # convention so users can drop in custom thumbnails too. + img_path = f"/_thumbnails/{category}/{nbg.stripped_name}.png" + section_lines.append( + ITEM_TEMPLATE.format( + doc_name=doc_name, + image=img_path, + doc_reference=doc_name, + link_type="doc", + ) + ) + + # Assemble: title, hidden toctree (so notebooks register with Sphinx), + # then the visible grid-card sections. + file_lines = [TITLE, TOCTREE_HEAD] + file_lines.extend(f" {entry}\n" for entry in toctree_entries) + file_lines.append("\n") + file_lines.extend(section_lines) + + gallery_rst = examples_dir / "gallery.rst" + gallery_rst.write_text("\n".join(file_lines), encoding="utf-8") + logger.info(f"Wrote gallery to {gallery_rst.relative_to(src_dir)}") + + +def setup(app): + app.connect("builder-inited", main) + return {"parallel_read_safe": True, "parallel_write_safe": True} diff --git a/pyproject.toml b/pyproject.toml index dd4977c..7210396 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,6 +42,7 @@ dependencies = [ [project.urls] Homepage = "https://github.com/pymc-devs/pytensor-ml" +Documentation = "https://pytensor-ml.readthedocs.io" Source = "https://github.com/pymc-devs/pytensor-ml" Tracker = "https://github.com/pymc-devs/pytensor-ml/issues" From 5950b59881f62ddca49fd439a0a3ccb4c4986c91 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 22:07:43 -0500 Subject: [PATCH 06/26] Add pixi tasks to build and serve the docs --- pixi.lock | 10749 +++++++++++++++++++++++++++++++++++++++++++++++ pyproject.toml | 40 + 2 files changed, 10789 insertions(+) create mode 100644 pixi.lock diff --git a/pixi.lock b/pixi.lock new file mode 100644 index 0000000..d2734aa --- /dev/null +++ b/pixi.lock @@ -0,0 +1,10749 @@ +version: 7 +platforms: +- name: linux-64 +- name: osx-arm64 +- name: win-64 +environments: + default: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-py310h44b86e0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.7.0-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.2.0-he0feb66_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-h13e7031_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.3-h35e630c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.7-h399a421_100_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - pypi: https://files.pythonhosted.org/packages/01/a4/9b63d595d748e3aff8812b65eacc1a2c4bd90b7c2012e08e72373b4835eb/filelock-3.32.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/0f/26/885774c006de6620ed3d10f45d8e20fe0b8e6aad6d573211a2cbc8b3e528/numba-0.66.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/4d/da/007be278565871eadd1ce7abda66532cecda68b63363be44f321f3600c43/pytensor-3.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/6b/89/2a844506d49651e9aa1af6ef95b6bd8031cb1d5a4375edec6155037e04cf/scipy-1.18.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/d6/e1/05b50692b647cac3c18200ac485b04f342f00ed173c9cc46767274469a15/llvmlite-0.48.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + - pypi: https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-py310h579977c_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.7.0-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-hca69786_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.3-hd24854e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.7-hf4d206d_100_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda + - pypi: https://files.pythonhosted.org/packages/01/a4/9b63d595d748e3aff8812b65eacc1a2c4bd90b7c2012e08e72373b4835eb/filelock-3.32.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/2a/49/59ea385dc3a62ff498ddf3cfff7c2b41b0f9f9d3c4122b3f1dcb6d6327fe/scipy-1.18.1-cp314-cp314-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8d/8e/8170f2e0c217f88069c333d85bb976e536b332aecfcce606ddbdb249385f/llvmlite-0.48.0-cp314-cp314-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/96/7a/7e0e73550eb4e41ede6e72fb5371f4539537a4d770a3b73fa9b61aea0622/numba-0.66.0-cp314-cp314-macosx_12_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/9e/74/3b17e08f1d9facfa7772ed32e1614f2e676f44093b2b717dae89a0c44161/pytensor-3.3.0-cp314-cp314-macosx_11_0_arm64.whl + - pypi: https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl + win-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_10.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.1-hac47afa_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.7.0-h3d046cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.4-hf5d6505_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.3-hf411b9b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.7-h53f6dd8_100_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h967ab96_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-ha367084_41.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36247-habf1de7_41.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda + - pypi: https://files.pythonhosted.org/packages/01/a4/9b63d595d748e3aff8812b65eacc1a2c4bd90b7c2012e08e72373b4835eb/filelock-3.32.4-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/15/4d/0eae213c5197c6a0cb4080f984c7295cb53af6d0ecba9ece190a6ae9560f/pytensor-3.3.0-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/52/94/d73da0d28f16c45bb9b0a5691b91610b0275c5ef0eb5e43c87cf2dc1bf31/scipy-1.18.1-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/66/c5/b46ad28ac3681d035ea21365c5e052149062e1a0a9affd0563d2760ea6ff/numba-0.66.0-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl + - pypi: https://files.pythonhosted.org/packages/c9/2d/6a5171fb7236ac0895e1a02ccba3735bf291e8597239aa6421894d3c0ba8/llvmlite-0.48.0-cp314-cp314-win_amd64.whl + - pypi: https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl + docs: + channels: + - url: https://conda.anaconda.org/conda-forge/ + indexes: + - https://pypi.org/simple + packages: + linux-64: + - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.16.1-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-26.1.0-py314h89acca1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.46.1-default_hfdba357_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-2.309-mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.11.0-9_hcf00494_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-h505cf86_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-h9908984_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py314hcd2bdb6_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-he90730b_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.1.1-py314h8d76f0c_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/conda-gcc-specs-16.2.0-hf8037ed_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py314h97ea11e_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hac629b4_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.16.2-h24cb091_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.21-py314h42812f9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/double-conversion-3.4.0-hecca717_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.18.3-h4db4eae_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.3-ha770c72_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.16-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-16.2.0-hc6a0c74_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-16.2.0-h176d5d0_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.15-h54a6638_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/greenlet-3.5.5-py314h42812f9_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-16.2.0-hc6a0c74_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-16.2.0-h0d273dc_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-py310h44b86e0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-h7cc23a3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.0-py314h97ea11e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-hbc21106_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.19.1-h0c24ade_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.2.0-hdb68285_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-9_h5875eb1_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-h39a168f_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-ha411449_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-h018ffa1_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-9_hfef963f_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp22.1-22.1.8-default_h0acdd01_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libclang13-22.1.8-default_h5f7f9d4_9.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h7a8fb5f_6.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-hd45a770_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.129-h7cc23a3_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h373387f_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libegl-1.7.0-ha4b6fd6_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libegl-devel-1.7.0-ha4b6fd6_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.7.0-h3435931_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h5e6c136_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-16.2.0-h69a702a_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-16.2.0-h69a702a_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-16.2.0-h6b99dfc_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-devel-1.7.0-ha4b6fd6_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.88.3-h45c3219_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglvnd-1.7.0-ha4b6fd6_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libglx-devel-1.7.0-ha4b6fd6_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.2.0-he0feb66_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libharfbuzz-14.3.1-h23af247_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.13.0-default_he001693_1000.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h0cb94f2_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.2.0-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-9_h5e43f62_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.11.0-9_hdba1596_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm22-22.1.8-h474f4eb_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libopengl-1.7.0-ha4b6fd6_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.19-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.58-h922cc85_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.6-h9d76c99_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libraqm-0.11.0-h6406941_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-16.2.0-h3048135_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.22-hebe6cf0_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-h13e7031_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-16.2.0-hdf11a46_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.2-h9d88235_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.357.0-h0e34353_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-hb83e432_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.38-h280c20c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.13.2-h51789e4_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.3-hca6bf5a_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.3-h49c6c72_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxslt-1.1.43-h711ed8c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-22.1.8-h3206bd6_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/llvmlite-0.48.0-py314h8f0570d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.11.1-py314h815e797_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.11.1-py314h9a9c090_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2026.1.0-hecca717_244.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2026.1.0-ha770c72_244.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2026.1.0-ha770c72_244.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-service-2.8.0-py314h3e0429d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numba-0.66.0-py314h42812f9_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.6-py314h2b28147_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.13-hbde042b_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.3-h35e630c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pandoc-3.10.2-ha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-h8b3dc9c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.3.0-py314h8ec4b1a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py314hfe1a184_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb03c661_1003.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.11.2-py314h5d85b37_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytensor-3.3.0-py314ha04e0d8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pytensor-base-3.3.0-np2py314h6477eea_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.7-h399a421_100_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.2.0-py312h8a5ba0d_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.11.2-pl5321h9df5c37_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-2026.6.3-py314h7e8cd81_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/safetensors-0.8.0-py314h2e6c369_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.18.0-py314hf07bd8e_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/sqlalchemy-2.0.52-py314h0f05182_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2023.0.0-hab88423_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.8-py314h5bd0f2a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-17.0.1-py314h5bd0f2a_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/watchfiles-1.2.0-py314h1bee95f_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.26.0-h1964d1d_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/websockets-17.0.1-py314h518bba1_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.1-h4f16b4b_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-cursor-0.1.6-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-hb711507_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.1-hb711507_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.10-hb711507_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.2-hb711507_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xkeyboard-config-2.48-h280c20c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-h280c20c_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-h0d788c3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcomposite-0.4.7-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcursor-1.2.3-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdamage-1.1.6-hb9d3cd8_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-h7cc23a3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.2-h7cc23a3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.3-h7cc23a3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrandr-1.5.5-h7cc23a3_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb03c661_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-h7cc23a3_4.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxxf86vm-1.1.7-hb03c661_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-xorgproto-2025.1-h280c20c_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-hebe6cf0_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h09e67af_11.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hce19668_1.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/accessible-pygments-0.0.5-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-1.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.14.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.7.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.15.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.4.0-hac0b51c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.4.2-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.7-py314hd8ed1ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/docutils-0.22.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.32.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonttools-4.63.0-pyh7db6752_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/imagesize-2.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.3.0-pyha191276_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.16.1-pyh53cf698_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.9-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.15.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-1.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-builder-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-cache-1.0.1-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-sphinx-0.5.3-pyha770c72_5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_console-6.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.17-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-4.18.0-he073ed8_9.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/latexcodec-2.0.1-pyh9f0ad1d_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-16.2.0-he3ce08f_104.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-16.2.0-h86e191b_104.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mdit-py-plugins-0.6.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.3.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/myst-nb-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/myst-parser-5.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.17.1-hb502eef_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.17.1-h08b4883_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.11.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio2-1.7.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-7.6.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/numpydoc-1.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.11.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.26.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.53-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.53-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybtex-0.26.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybtex-docutils-1.0.3-pyhcf101f3_4.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.20.0-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.22.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.7-h4df99d1_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-3.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.9.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-9.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-autobuild-2025.8.25-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-codeautolink-0.19.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-design-0.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-last-updated-by-git-0.3.8-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-notfound-page-1.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-sitemap-2.9.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-bibtex-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-2.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/starlette-1.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.28-h4ee821c_9.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tabulate-0.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.16.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/uvicorn-0.52.4-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.8.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.16-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + - pypi: . + osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/accessible-pygments-0.0.5-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-1.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.14.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/appnope-1.0.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.7.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.15.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.4.0-hac0b51c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.4.2-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt22_osx-arm64-22.1.8-hb8825d9_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-arm64-22.1.8-hce30654_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.7-py314hd8ed1ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/docutils-0.22.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.32.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonttools-4.63.0-pyh7db6752_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/imagesize-2.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.3.0-pyh01cf8df_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.16.1-pyh53cf698_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.9-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.15.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-1.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-builder-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-cache-1.0.1-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-sphinx-0.5.3-pyha770c72_5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_console-6.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.17-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/latexcodec-2.0.1-pyh9f0ad1d_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/libcxx-headers-22.1.8-h707e725_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mdit-py-plugins-0.6.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.3.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/myst-nb-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/myst-parser-5.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.17.1-hb502eef_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.17.1-h08b4883_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.11.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio2-1.7.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-7.6.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/numpydoc-1.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.11.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.26.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.53-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.53-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybtex-0.26.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybtex-docutils-1.0.3-pyhcf101f3_4.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.20.0-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.22.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.7-h4df99d1_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-3.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.9.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-9.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-autobuild-2025.8.25-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-codeautolink-0.19.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-design-0.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-last-updated-by-git-0.3.8-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-notfound-page-1.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-sitemap-2.9.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-bibtex-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-2.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/starlette-1.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tabulate-0.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.16.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/uvicorn-0.52.4-pyhc90fa1f_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.8.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.16-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-26.1.0-py314h61c6340_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/blas-2.309-accelerate.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/blas-devel-3.11.0-9_h55bc449_accelerate.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.2.0-hf00d406_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-he4a93e4_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py314hee34562_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cairo-1.18.4-he0f2337_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cctools-1030.6.3-llvm22_1_hbe26303_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cctools_impl_osx-arm64-1030.6.3-llvm22_1_h7bf7afb_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.1.1-py314h618e29d_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang-22-22.1.8-default_h54a73ef_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang-22.1.8-default_cfg_ha85103e_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang-scan-deps-22.1.8-default_h79071a4_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang_impl_osx-arm64-22.1.8-default_h79071a4_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clangxx-22.1.8-default_cfg_h9e28d6e_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/clangxx_impl_osx-arm64-22.1.8-default_h350d358_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/compiler-rt-22.1.8-hce30654_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/compiler-rt22-22.1.8-hdb3d66b_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py314hf8a3a22_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.21-py314he609de1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fontconfig-2.18.3-h81aa574_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.3-hce30654_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fribidi-1.0.16-h84a0fba_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/graphite2-1.3.15-h784d473_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/greenlet-3.5.5-py314he609de1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-py310h579977c_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.5.0-py314hf8a3a22_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h34f8a20_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.19.1-hdfa7624_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ld64-956.6-llvm22_1_h5b97f1b_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ld64_osx-arm64-956.6-llvm22_1_h692d5aa_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.2.0-h1eee2c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-9_h3d1d584_accelerate.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-h1dcdb26_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-h5295a6a_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-h2ddc9cb_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-9_h752f6bc_accelerate.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libclang-cpp22.1-22.1.8-default_h79071a4_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libclang13-22.1.8-default_h54a73ef_9.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcompiler-rt-22.1.8-hdb3d66b_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.8-h55c6f16_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-devel-22.1.8-h6dc3340_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-he7e0567_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321h26f1114_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.7.0-hcf2aa1b_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.3-hce30654_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.3-h2ed5691_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-16.2.0-h3cf6597_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-16.2.0-h07b0088_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-16.2.0-hdb7a957_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.88.3-ha531971_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libharfbuzz-14.3.1-hcda0f7c_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-he4c29f2_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.2.0-h84a0fba_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-9_hcb0d94e_accelerate.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapacke-3.11.0-9_hbdd07e9_accelerate.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libllvm22-22.1.8-h759d1ac_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.58-hf5e6511_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libraqm-0.11.0-h45af499_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsigtool-0.1.3-h98dc951_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.22-h74c22ad_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-hca69786_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.2-h282da08_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.6.0-h202fb40_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.17.0-hbffa61f_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.3-h5ef1a60_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.3-h5654f7c_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.8-hc225544_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-tools-22-22.1.8-h79441dc_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-tools-22.1.8-hdb3d66b_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvmlite-0.48.0-py314h582f951_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py314h6e9b3f0_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-3.11.1-py314h314fc0d_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.11.1-py314h27b0870_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numba-0.66.0-py314h705d3de_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.6-py314hb79c6fa_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hd9e9057_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.3-hd24854e_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandoc-3.10.2-hce30654_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-he63d830_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.3.0-py314hab283cf_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.4-h784d473_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py314hd98292b_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pthread-stubs-0.4-h84a0fba_1003.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.2.2-py314h63b12ec_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.2.2-py314hddd3963_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytensor-3.3.0-py314h0ac4119_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytensor-base-3.3.0-np2py314hdd732f0_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.7-hf4d206d_100_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py314h6e9b3f0_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.2.0-py312hcedbef1_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/qhull-2020.2-h420ef59_5.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-2026.6.3-py314hc05cd11_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/safetensors-0.8.0-py314h54f3292_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.18.0-py314h18e1515_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sigtool-codesign-0.1.3-h98dc951_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/sqlalchemy-2.0.52-py314h5583935_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tapi-1600.0.11.8-hb561403_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.8-py314h6c2aa35_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.1-py314h6c2aa35_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/watchfiles-1.2.0-py314he1d1ac0_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/websockets-17.0.1-py314h2fbedac_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-h84a0fba_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-h84a0fba_2.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h74c22ad_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h10816f8_11.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-h31dac16_1.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda + - pypi: . + win-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/accessible-pygments-0.0.5-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-1.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.14.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.7.0-py314h680f03e_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.15.0-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.4.0-hac0b51c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-h4c7d964_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/click-8.4.2-pyh6dadd2b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.7-py314hd8ed1ab_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/docutils-0.22.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.32.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fonttools-4.63.0-pyh7db6752_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/imagesize-2.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.3.0-pyh6dadd2b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.16.1-pyhe2676ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.9-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.15.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-1.1.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-builder-1.2.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-cache-1.0.1-pyhff2d567_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-sphinx-0.5.3-pyha770c72_5.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.9.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_console-6.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyh6dadd2b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.20.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.6.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.17-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/latexcodec-2.0.1-pyh9f0ad1d_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_win-64-16.1.0-hecf7705_103.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_win-64-16.1.0-hc76ffd0_103.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/m2w64-sysroot_win-64-12.0.0.r4.gg4f2fc60ca-hd8ed1ab_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mdit-py-plugins-0.6.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mingw-w64-ucrt-x86_64-crt-git-12.0.0.r4.gg4f2fc60ca-hd8ed1ab_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mingw-w64-ucrt-x86_64-headers-git-12.0.0.r4.gg4f2fc60ca-hd8ed1ab_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mingw-w64-ucrt-x86_64-windows-default-manifest-6.4-he206cdd_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mingw-w64-ucrt-x86_64-winpthreads-git-12.0.0.r4.gg4f2fc60ca-hd8ed1ab_10.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.3.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/myst-nb-1.4.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/myst-parser-5.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.11.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.17.1-hb502eef_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.17.1-h08b4883_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.11.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio2-1.7.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-7.6.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/numpydoc-1.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.11.4-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.26.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.53-pyha770c72_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.53-hd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybtex-0.26.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pybtex-docutils-1.0.3-pyhcf101f3_4.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.20.0-pyhc364b38_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.22.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.7-h4df99d1_100.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-4.2.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.3-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-4.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh6dadd2b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-3.1.1-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.9.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-9.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-autobuild-2025.8.25-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-codeautolink-0.19.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.2-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-design-0.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-last-updated-by-git-0.3.8-pyhe01879c_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-notfound-page-1.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-sitemap-2.9.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-bibtex-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-2.0.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-2.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/starlette-1.6.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tabulate-0.10.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.16.1-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/uvicorn-0.52.4-pyh6dadd2b_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.8.2-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.16-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/_openmp_mutex-4.5-20_gnu.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-26.1.0-py314h5a2d7ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/binutils_impl_win-64-2.46.1-default_ha84baeb_102.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/blas-2.309-mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/blas-devel-3.11.0-9_h85df5b5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-1.2.0-hc8c2fe1_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-bin-1.2.0-hd477307_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py314h85cf176_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_10.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cairo-1.18.4-h477c42c_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.1.1-py314h5a2d7ad_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/conda-gcc-specs-16.1.0-h851ee6d_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/contourpy-1.3.3-py314hf309875_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.21-py314hb98de8c_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/double-conversion-3.4.0-hac47afa_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/fontconfig-2.18.3-hd47e2ca_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/freetype-2.14.3-h57928b3_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/fribidi-1.0.16-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/gcc-16.1.0-hb5e953d_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/gcc_impl_win-64-16.1.0-hf3f8c13_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/graphite2-1.3.15-h5112557_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/greenlet-3.5.5-py314hb98de8c_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/gxx-16.1.0-hb5e953d_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/gxx_impl_win-64-16.1.0-he3d2c83_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/icu-78.3-h5112557_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/kiwisolver-1.5.0-py314hf309875_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h719d79b_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lcms2-2.19.1-hf2c6c5f_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ld_impl_win-64-2.46.1-default_hfd38196_102.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lerc-4.2.0-hd936e49_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-9_h8455456_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.2.0-hf02afa3_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.2.0-h84f9c24_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.2.0-he2a975b_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-9_h2a3cdd5_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libclang13-22.1.8-default_hacd6ee9_9.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libdeflate-1.25-h1a1d4e4_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.1-hac47afa_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.7.0-h3d046cb_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype-2.14.3-h57928b3_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype6-2.14.3-hdbac1cb_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgcc-16.1.0-h110b43a_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libglib-2.88.3-he810d59_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libgomp-16.1.0-h8ee18e1_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libharfbuzz-14.3.1-h03b5201_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.13.0-default_h049141e_1000.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libintl-0.22.5-h5728263_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.2.0-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-9_hf9ab0e9_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblapacke-3.11.0-9_h3ae206f_mkl.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.58-hdc8cecf_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libraqm-0.11.0-h50d6d30_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.22-h6a83c73_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.4-hf5d6505_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libstdcxx-16.1.0-hae5796f_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libtiff-4.7.2-h8f73337_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libvulkan-loader-1.4.357.0-h477610d_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libwebp-base-1.6.0-h4d5522a_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxcb-1.17.0-h874e120_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.3-h3cfd58e_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.3-h8ef44ab_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxslt-1.1.43-h0fbe4c1_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.8-h4fa8253_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/llvmlite-0.48.0-py314h46b4103_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/m2-conda-epoch-20250515-0_x86_64.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py314h2359020_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-3.11.1-py314h30c6bc1_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.11.1-py314h2061cd4_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2026.1.0-hac47afa_234.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-devel-2026.1.0-h57928b3_234.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-include-2026.1.0-h57928b3_234.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/mkl-service-2.8.0-py314h6d79c4c_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numba-0.66.0-py314hb98de8c_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.6-py314h02f10f6_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.5.4-h0e57b4f_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.3-hf411b9b_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pandoc-3.10.2-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-h8466c1e_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pillow-12.3.0-py314h61b30b5_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pixman-0.46.4-h5112557_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py314hc5dbbe4_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pthread-stubs-0.4-hba3369d_1003.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyside6-6.11.2-py314h447aaf0_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pytensor-3.3.0-py314h443d124_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pytensor-base-3.3.0-np2py314hb7a55bc_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.7-h53f6dd8_100_cp314.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-312-py314hf700ef7_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-3.0.5-py314h51f0985_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py314h2359020_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.2.0-py312h343a6d4_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/qhull-2020.2-hc790b64_5.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/qt6-main-6.11.2-pl5321hfcac499_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-2026.6.3-py314h9f07db2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/safetensors-0.8.0-py314h9f07db2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/scipy-1.18.0-py314h221f224_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/sqlalchemy-2.0.52-py314hc5dbbe4_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2023.0.0-hd3d4ead_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h967ab96_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.8-py314h5a2d7ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/unicodedata2-17.0.1-py314h5a2d7ad_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-ha367084_41.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36247-habf1de7_41.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/watchfiles-1.2.0-py314hc980628_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/websockets-17.0.1-py314h13f4da2_0.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/win-64/xorg-libxau-1.0.12-hba3369d_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/xorg-libxdmcp-1.1.5-hba3369d_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h3a581c9_11.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda + - pypi: . +packages: +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-20_gnu.conda + build_number: 20 + sha256: 1dd3fffd892081df9726d7eb7e0dea6198962ba775bd88842135a4ddb4deb3c9 + md5: a9f577daf3de00bca7c3c76c0ecbd1de + depends: + - __glibc >=2.17,<3.0.a0 + - libgomp >=7.5.0 + constrains: + - openmp_impl <0.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + strong: + - _openmp_mutex >=4.5 + size: 28948 + timestamp: 1770939786096 +- conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-7_kmp_llvm.conda + build_number: 7 + sha256: c0cddb66070dd6355311f7667ce2acccf70d1013edaa6e97f22859502fefdb22 + md5: 887b70e1d607fba7957aa02f9ee0d939 + depends: + - llvm-openmp >=9.0.1 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - _openmp_mutex >=4.5 + size: 8244 + timestamp: 1764092331208 +- conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.16.1-hb03c661_0.conda + sha256: cf93ca0f1f107e95a35969a4622684e08fcb8cf37f8cf4a1e9e424828386c921 + md5: 8904e09bda369377b3dd07e2ac828c5d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: LGPL-2.1-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - alsa-lib >=1.2.16.1,<1.3.0a0 + size: 592377 + timestamp: 1781521980743 +- conda: https://conda.anaconda.org/conda-forge/linux-64/argon2-cffi-bindings-26.1.0-py314h89acca1_0.conda + sha256: 1b39b6275fd72e7d180d64dda68862f6f9598b2542552205fffad6a329323094 + md5: a4cf00be4eed0c82b87dfd7173ca6723 + depends: + - __glibc >=2.17,<3.0.a0 + - cffi >=2.0.0b1 + - libgcc >=15 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + run_exports: {} + size: 33021 + timestamp: 1787248105067 +- conda: https://conda.anaconda.org/conda-forge/linux-64/binutils_impl_linux-64-2.46.1-default_hfdba357_102.conda + sha256: fb7bf36984a37ce7e4714d1d1da0bd0e3bfc679520f5cdc184afc676fd4b5da2 + md5: a0c5e0b7f58c8ceeb08e5bc41251d5a2 + depends: + - ld_impl_linux-64 2.46.1 default_hbd61a6d_102 + - sysroot_linux-64 + - zstd >=1.5.7,<1.6.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + run_exports: {} + size: 3713752 + timestamp: 1784214522814 +- conda: https://conda.anaconda.org/conda-forge/linux-64/blas-2.309-mkl.conda + build_number: 9 + sha256: 9c4602333f515e8f39e1807a81cc693a4fb87f2fccd9ca90b8d4441c58d886e5 + md5: 9dd0f025349a526353ea91dd9cdf599b + depends: + - blas-devel 3.11.0 9*_mkl + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 18170 + timestamp: 1786059133387 +- conda: https://conda.anaconda.org/conda-forge/linux-64/blas-devel-3.11.0-9_hcf00494_mkl.conda + build_number: 9 + sha256: cf440e3dc59cf08d83116c4cd196beab485e08c8641703e93c0dccbc595f51cd + md5: 9163a094430cead29b0e1bf6149ce384 + depends: + - libblas 3.11.0 9_h5875eb1_mkl + - libcblas 3.11.0 9_hfef963f_mkl + - liblapack 3.11.0 9_h5e43f62_mkl + - liblapacke 3.11.0 9_hdba1596_mkl + - mkl >=2026.1.0,<2027.0a0 + - mkl-devel 2026.1.* + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 17790 + timestamp: 1786059022192 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-1.2.0-h505cf86_3.conda + sha256: 1f2ccfebe6e0113bfc473b21906372c1ee4eb69bf6faef0ad7f3d4d79af63a98 + md5: 6ff307b78fbfb7dcafb7e25ff8bc9c10 + depends: + - __glibc >=2.17,<3.0.a0 + - brotli-bin 1.2.0 h9908984_3 + - libbrotlidec 1.2.0 ha411449_3 + - libbrotlienc 1.2.0 h018ffa1_3 + - libgcc >=15 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + size: 20676 + timestamp: 1786622810792 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-bin-1.2.0-h9908984_3.conda + sha256: 56b2e5e8a49f9887af74cc63e0d55a3654e60b667ad5558da089549a36ae6a0c + md5: 8929291d9efc59715df1cfa7daf5e3ed + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlidec 1.2.0 ha411449_3 + - libbrotlienc 1.2.0 h018ffa1_3 + - libgcc >=15 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 21598 + timestamp: 1786622801722 +- conda: https://conda.anaconda.org/conda-forge/linux-64/brotli-python-1.2.0-py314hcd2bdb6_3.conda + sha256: e52ff7e1e3c5f4423421fbcd1f1ebf1d6ce123e22890ceb225d6552b7bbc551f + md5: bd1be0851060138e038f6f4e09cc1eb4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libstdcxx >=15 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + constrains: + - libbrotlicommon 1.2.0 h39a168f_3 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=compressed-mapping + run_exports: {} + size: 367948 + timestamp: 1786622843866 +- conda: https://conda.anaconda.org/conda-forge/linux-64/bzip2-1.0.8-hda65f42_10.conda + sha256: 1a0d382c515ebf55f8ee1f38c8b81bc95af5c2acc42ad53b66bc5df932032f96 + md5: e675fabcf81499adc7edf58124fb1e01 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + run_exports: + weak: + - bzip2 >=1.0.8,<2.0a0 + size: 257808 + timestamp: 1785906269155 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cairo-1.18.4-he90730b_1.conda + sha256: 06525fa0c4e4f56e771a3b986d0fdf0f0fc5a3270830ee47e127a5105bde1b9a + md5: bb6c4808bfa69d6f7f6b07e5846ced37 + depends: + - __glibc >=2.17,<3.0.a0 + - fontconfig >=2.15.0,<3.0a0 + - fonts-conda-ecosystem + - icu >=78.1,<79.0a0 + - libexpat >=2.7.3,<3.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libgcc >=14 + - libglib >=2.86.3,<3.0a0 + - libpng >=1.6.53,<1.7.0a0 + - libstdcxx >=14 + - libxcb >=1.17.0,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - pixman >=0.46.4,<1.0a0 + - xorg-libice >=1.1.2,<2.0a0 + - xorg-libsm >=1.2.6,<2.0a0 + - xorg-libx11 >=1.8.12,<2.0a0 + - xorg-libxext >=1.3.6,<2.0a0 + - xorg-libxrender >=0.9.12,<0.10.0a0 + license: LGPL-2.1-only or MPL-1.1 + purls: [] + run_exports: + weak: + - cairo >=1.18.4,<2.0a0 + size: 989514 + timestamp: 1766415934926 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cffi-2.1.1-py314h8d76f0c_2.conda + sha256: 9d181949eead0d4092ed9ffa3b7ec066a15572d515327939c8a074c224262528 + md5: 314853abf64fc052dea08bd17df17b65 + depends: + - __glibc >=2.17,<3.0.a0 + - libffi >=3.7.0,<3.8.0a0 + - libgcc >=15 + - pycparser + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + run_exports: {} + size: 306626 + timestamp: 1786775112485 +- conda: https://conda.anaconda.org/conda-forge/linux-64/conda-gcc-specs-16.2.0-hf8037ed_4.conda + sha256: 24825798d2b016cfa873485bc1bfb31bba32d7c8a4e8a3ab991165536523b79c + md5: d30388235d4b77c78289bf93573c57cf + depends: + - gcc_impl_linux-64 >=16.2.0,<16.2.1.0a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: {} + size: 32505 + timestamp: 1787618879364 +- conda: https://conda.anaconda.org/conda-forge/linux-64/contourpy-1.3.3-py314h97ea11e_4.conda + sha256: b0314a7f1fb4a294b1a8bcf5481d4a8d9412a9fee23b7e3f93fb10e4d504f2cc + md5: 95bede9cdb7a30a4b611223d52a01aa4 + depends: + - numpy >=1.25 + - python + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/contourpy?source=hash-mapping + run_exports: {} + size: 324013 + timestamp: 1769155968691 +- conda: https://conda.anaconda.org/conda-forge/linux-64/cyrus-sasl-2.1.28-hac629b4_1.conda + sha256: 7684da83306bb69686c0506fb09aa7074e1a55ade50c3a879e4e5df6eebb1009 + md5: af491aae930edc096b58466c51c4126c + depends: + - __glibc >=2.17,<3.0.a0 + - krb5 >=1.22.2,<1.23.0a0 + - libgcc >=13 + - libntlm >=1.8,<2.0a0 + - libstdcxx >=13 + - libxcrypt >=4.4.36 + - openssl >=3.5.5,<4.0a0 + license: BSD-3-Clause-Attribution + license_family: BSD + purls: [] + run_exports: + weak: + - cyrus-sasl >=2.1.28,<3.0a0 + size: 210103 + timestamp: 1771943128249 +- conda: https://conda.anaconda.org/conda-forge/linux-64/dbus-1.16.2-h24cb091_1.conda + sha256: 8bb557af1b2b7983cf56292336a1a1853f26555d9c6cecf1e5b2b96838c9da87 + md5: ce96f2f470d39bd96ce03945af92e280 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + - libglib >=2.86.2,<3.0a0 + - libexpat >=2.7.3,<3.0a0 + license: AFL-2.1 OR GPL-2.0-or-later + purls: [] + run_exports: + weak: + - dbus >=1.16.2,<2.0a0 + size: 447649 + timestamp: 1764536047944 +- conda: https://conda.anaconda.org/conda-forge/linux-64/debugpy-1.8.21-py314h42812f9_0.conda + sha256: c16696b23e1d75b6eea7d0c8b9c31c03d1987eeb61852f09b6d4042ca015acd3 + md5: a7eb8029c4fe320c0179085707017c2d + depends: + - python + - libgcc >=14 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/debugpy?source=hash-mapping + run_exports: {} + size: 2842115 + timestamp: 1780390153580 +- conda: https://conda.anaconda.org/conda-forge/linux-64/double-conversion-3.4.0-hecca717_0.conda + sha256: 40cdd1b048444d3235069d75f9c8e1f286db567f6278a93b4f024e5642cfaecc + md5: dbe3ec0f120af456b3477743ffd99b74 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - double-conversion >=3.4.0,<3.5.0a0 + size: 71809 + timestamp: 1765193127016 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fontconfig-2.18.3-h4db4eae_1.conda + sha256: 5a3eb10b18a97223ab06b3a7f0d7f56658db2f2800e2f2af95836fe3bf55ba63 + md5: 922776b528a470ab5afa81fd42abfa1d + depends: + - __glibc >=2.17,<3.0.a0 + - libexpat >=2.8.1,<3.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libgcc >=15 + - libuuid >=2.42.2,<3.0a0 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - fontconfig >=2.18.3,<3.0a0 + - fonts-conda-ecosystem + size: 296288 + timestamp: 1786667377340 +- conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.14.3-ha770c72_2.conda + sha256: 612a8e0c1a6ecae23da54d81ef395f6ebb5c8e4468ec2611593ebce75876a4e0 + md5: 2d0ea23b23603e07ca47f23f949f67b6 + depends: + - libfreetype 2.14.3 ha770c72_2 + - libfreetype6 2.14.3 h5e6c136_2 + license: GPL-2.0-only OR FTL + purls: [] + run_exports: + weak: + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + size: 175239 + timestamp: 1786641011029 +- conda: https://conda.anaconda.org/conda-forge/linux-64/fribidi-1.0.16-hb03c661_1.conda + sha256: 4846a3ca0402f3fe33ad84ed50ab213c6aafde4a0faef3c5002f6bf753e21671 + md5: 1cd10eda5692519d01bb20e086e214c9 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - fribidi >=1.0.16,<2.0a0 + size: 61782 + timestamp: 1785912528684 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc-16.2.0-hc6a0c74_4.conda + sha256: 1c974f7e764250ddf8ea314553c99412dad37da593f756028c7e2640953e50e6 + md5: 2918c9054a9556dab9bef29861ddd927 + depends: + - conda-gcc-specs + - gcc_impl_linux-64 16.2.0 h176d5d0_4 + license: BSD-3-Clause + purls: [] + run_exports: {} + size: 29416 + timestamp: 1787618988431 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-16.2.0-h176d5d0_4.conda + sha256: d09c1e8ae19bc03a6336516fb2b55f09d1b86bbe16242d911dbf4d23f709b5d5 + md5: 15426bff4573d78ab030e6d439fc820b + depends: + - binutils_impl_linux-64 >=2.46.1 + - libgcc >=16.2.0 + - libgcc-devel_linux-64 16.2.0 he3ce08f_104 + - libgomp >=16.2.0 + - libsanitizer 16.2.0 h3048135_4 + - libstdcxx >=16.2.0 + - libstdcxx-devel_linux-64 16.2.0 h86e191b_104 + - sysroot_linux-64 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: {} + size: 86474258 + timestamp: 1787618763737 +- conda: https://conda.anaconda.org/conda-forge/linux-64/graphite2-1.3.15-h54a6638_1.conda + sha256: 7fa3b6a9c081fa3e545573152a788d061a0a0ba57df7251cc0f4f75225fc93e7 + md5: f9fe2984587fa8235a6af6004760cd18 + depends: + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + license: LGPL-2.0-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - graphite2 >=1.3.15,<2.0a0 + size: 102835 + timestamp: 1786118485753 +- conda: https://conda.anaconda.org/conda-forge/linux-64/greenlet-3.5.5-py314h42812f9_0.conda + sha256: 8077b6042be044d2c571d5eb340a1ad7a1949534113fb8d9cafb213982b7c60b + md5: a0423baf08abf2f98136e8cc103e46dd + depends: + - python + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/greenlet?source=compressed-mapping + run_exports: {} + size: 278368 + timestamp: 1786384047792 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx-16.2.0-hc6a0c74_4.conda + sha256: a34a318620ab87f253f99fc41d9da5ace5762beb1f8dcb35d006ccaa07fc5b32 + md5: dbed457da766863500b5a751fa78bfbe + depends: + - conda-gcc-specs + - gcc 16.2.0 hc6a0c74_4 + - gxx_impl_linux-64 16.2.0 h0d273dc_4 + license: BSD-3-Clause + purls: [] + run_exports: {} + size: 28811 + timestamp: 1787619084791 +- conda: https://conda.anaconda.org/conda-forge/linux-64/gxx_impl_linux-64-16.2.0-h0d273dc_4.conda + sha256: 839b4701fa8eb6b315b28ac8a245310b08aec62893a64727d7cabf260467281a + md5: d7f18a0ab325cc612bfd7842bf53756e + depends: + - gcc_impl_linux-64 16.2.0 h176d5d0_4 + - libstdcxx-devel_linux-64 16.2.0 h86e191b_104 + - sysroot_linux-64 + - tzdata + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: {} + size: 17668584 + timestamp: 1787618949985 +- conda: https://conda.anaconda.org/conda-forge/linux-64/icu-78.3-py310h44b86e0_2.conda + sha256: 9f07834f0c546ab14d885ce0366285f61f44e326c0edd1fc63b8294e113ae432 + md5: 72a381cbad04f24b1c2a43ef707f45b4 + depends: + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - icu >=78.3,<79.0a0 + size: 14459115 + timestamp: 1786545741408 +- conda: https://conda.anaconda.org/conda-forge/linux-64/keyutils-1.6.3-h7cc23a3_1.conda + sha256: dd053c96dcb0dcfd59422aefea9d2fe937a190167f34fba7893ec1e10a7e8963 + md5: ba55d1b89fd7775e67de8291029b4059 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - keyutils >=1.6.3,<2.0a0 + size: 135295 + timestamp: 1786739238128 +- conda: https://conda.anaconda.org/conda-forge/linux-64/kiwisolver-1.5.0-py314h97ea11e_0.conda + sha256: e3488ea4a336f29e57de8f282bf40c0505cfc482e03004615e694b48e7d9c79f + md5: 7397e418cab519b8d789936cf2dde6f6 + depends: + - python + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/kiwisolver?source=hash-mapping + run_exports: {} + size: 77363 + timestamp: 1773067048780 +- conda: https://conda.anaconda.org/conda-forge/linux-64/krb5-1.22.2-hbc21106_2.conda + sha256: 2a5c38c85e63df84c4e69ee71439841ce570d259ae3060627bb9a49a938d66f4 + md5: 53318d715316929a574f83591308b1f8 + depends: + - __glibc >=2.17,<3.0.a0 + - keyutils >=1.6.3,<2.0a0 + - libedit >=3.1.20250104,<3.2.0a0 + - libedit >=3.1.20250104,<4.0a0 + - libgcc >=15 + - libstdcxx >=15 + - openssl >=3.5.7,<4.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - krb5 >=1.22.2,<1.23.0a0 + size: 1394333 + timestamp: 1786762112514 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lcms2-2.19.1-h0c24ade_1.conda + sha256: 112b5b9462572d970f4abd2912f76a25ee7db158b1e7260163d91dd8a630db84 + md5: 8b3ce45e929cd8e8e5f4d18586b56d8b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - libtiff >=4.7.1,<4.8.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - lcms2 >=2.19.1,<3.0a0 + size: 251971 + timestamp: 1780211695895 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ld_impl_linux-64-2.46.1-default_hbd61a6d_102.conda + sha256: 27d83f1188cd19bcb7754a078b3fa7f4cfb8527f8eb2fde54dd01fc529d1adec + md5: 449500f2c089da11c40f5c21312e3e07 + depends: + - __glibc >=2.17,<3.0.a0 + - zstd >=1.5.7,<1.6.0a0 + constrains: + - binutils_impl_linux-64 2.46.1 + license: GPL-3.0-only + license_family: GPL + purls: [] + run_exports: {} + size: 745303 + timestamp: 1784214507189 +- conda: https://conda.anaconda.org/conda-forge/linux-64/lerc-4.2.0-hdb68285_0.conda + sha256: bf9fdebf55d8bc99d83531cdffda00703f4dc5f93a1a956768c147362c72feda + md5: fb9d356b1a57d6d54768be7ebd5fce09 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - lerc >=4.2.0,<5.0a0 + size: 271158 + timestamp: 1785036167977 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libblas-3.11.0-9_h5875eb1_mkl.conda + build_number: 9 + sha256: a8c8b832fb56469221612e1f33c1c01868146c85721966b663a35d4248121e7e + md5: 1c05815b5b3742a5f4398d94fec7aa11 + depends: + - mkl >=2026.1.0,<2027.0a0 + constrains: + - blas 2.309 mkl + - libcblas 3.11.0 9*_mkl + - liblapack 3.11.0 9*_mkl + - liblapacke 3.11.0 9*_mkl + track_features: + - blas_mkl + - blas_mkl_2 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libblas >=3.11.0,<4.0a0 + size: 18489 + timestamp: 1786058995640 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlicommon-1.2.0-h39a168f_3.conda + sha256: e5864f257f839ffc27d681659bac95901f524f602b9121e5dcc5e2df18437f2d + md5: 7a2499a177753582fb7ae7e9dc4a908a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libbrotlicommon >=1.2.0,<1.3.0a0 + size: 80265 + timestamp: 1786622773969 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlidec-1.2.0-ha411449_3.conda + sha256: dad31b6d104973deb89710929a35651033aad692d4e7793cdb5b786a9bd54678 + md5: 6ab3315dc56618d652c1da42a648a129 + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlicommon 1.2.0 h39a168f_3 + - libgcc >=15 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libbrotlidec >=1.2.0,<1.3.0a0 + size: 34828 + timestamp: 1786622783405 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libbrotlienc-1.2.0-h018ffa1_3.conda + sha256: d37124d0f51816e7d5e3a94bfc9ed3d6174d077f9b4f832d20c5a08b52bebf1f + md5: 2ac965638d4c6b2b38383bb1aebaf543 + depends: + - __glibc >=2.17,<3.0.a0 + - libbrotlicommon 1.2.0 h39a168f_3 + - libgcc >=15 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libbrotlienc >=1.2.0,<1.3.0a0 + size: 298639 + timestamp: 1786622792145 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcblas-3.11.0-9_hfef963f_mkl.conda + build_number: 9 + sha256: 8ce1b4cdc6e0feb53c5f8e3cf69b1b2c034f5e2726a4027b2fa6feeb0e7fb93e + md5: cbdd209a7b8cef670cc50830428a3b9d + depends: + - libblas 3.11.0 9_h5875eb1_mkl + constrains: + - blas 2.309 mkl + - liblapack 3.11.0 9*_mkl + - liblapacke 3.11.0 9*_mkl + track_features: + - blas_mkl + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libcblas >=3.11.0,<4.0a0 + size: 18097 + timestamp: 1786059002112 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libclang-cpp22.1-22.1.8-default_h0acdd01_9.conda + sha256: 91acd638bc94afb56a5192fd15214977e92bd56f8050a1482fab1383775fd194 + md5: 465f5e508f83cef46cb056c1b5ceae7e + depends: + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=15 + - libgcc >=15 + - libxml2 + - libxml2-16 >=2.15.3 + - zstd >=1.5.7,<1.6.0a0 + - libzlib >=1.3.2,<2.0a0 + - libllvm22 >=22.1.8,<22.2.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: + weak: + - libclang-cpp22.1 >=22.1.8,<22.2.0a0 + size: 24529647 + timestamp: 1787349870338 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libclang13-22.1.8-default_h5f7f9d4_9.conda + sha256: b1a62c3e6328c911e209ffe873e2bee5d8c66b50352c59fc88efd91be12d3bf2 + md5: ae740654c4a0e3731d07fc1162924ca6 + depends: + - libclang-cpp22.1 ==22.1.8 default_h0acdd01_9 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=15 + - libgcc >=15 + - libxml2 + - libxml2-16 >=2.15.3 + - zstd >=1.5.7,<1.6.0a0 + - libzlib >=1.3.2,<2.0a0 + - libllvm22 >=22.1.8,<22.2.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: + weak: + - libclang13 >=22.1.8 + size: 14558259 + timestamp: 1787349870338 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libcups-2.3.3-h7a8fb5f_6.conda + sha256: 205c4f19550f3647832ec44e35e6d93c8c206782bdd620c1d7cf66237580ff9c + md5: 49c553b47ff679a6a1e9fc80b9c5a2d4 + depends: + - __glibc >=2.17,<3.0.a0 + - krb5 >=1.22.2,<1.23.0a0 + - libgcc >=14 + - libstdcxx >=14 + - libzlib >=1.3.1,<2.0a0 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - libcups >=2.3.3,<2.4.0a0 + size: 4518030 + timestamp: 1770902209173 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdeflate-1.25-hd45a770_1.conda + sha256: 82e134c8a08b1eed9a2ed8ab578b89aa1730dcde3dea8dd87645ed0637878e54 + md5: 40f9b31aa9cf007789867df0decd0492 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libdeflate >=1.25,<1.26.0a0 + size: 73710 + timestamp: 1785908694612 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libdrm-2.4.129-h7cc23a3_0.conda + sha256: ea46b0ca0fa16af1f8b329b740e6cd8b4577c5378fa8717b28a885f70668633d + md5: 64cc91512b6278c315349dc13c53f680 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libpciaccess >=0.19,<0.20.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libdrm >=2.4.129,<2.5.0a0 + size: 313461 + timestamp: 1786684701973 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libedit-3.1.20250104-pl5321h373387f_1.conda + sha256: 6473eb8caf2aae830f37caa93db9b26dddf7ac84b63229e8bf7fc0e5c3ab95b0 + md5: 50708d3b951d0f8e2d7f2df5b5edc040 + depends: + - ncurses + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - ncurses >=6.6,<7.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libedit >=3.1.20250104,<3.2.0a0 + size: 135098 + timestamp: 1786616658086 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libegl-1.7.0-ha4b6fd6_5.conda + sha256: 13d3fde9c1dcf254968cc70652222a43f25d04e69555ccf855553110e753288e + md5: 3a1b41c4591a0a1734382af3e2b8bc08 + depends: + - __glibc >=2.17,<3.0.a0 + - libglvnd 1.7.0 ha4b6fd6_5 + license: LicenseRef-libglvnd + purls: [] + run_exports: {} + size: 46694 + timestamp: 1787310030923 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libegl-devel-1.7.0-ha4b6fd6_5.conda + sha256: fd2794bbcff7e692fb476c17886702702893d074071d429217bad7cfb072abfd + md5: 577800fc8c9d8b7d9727246bbfde704e + depends: + - __glibc >=2.17,<3.0.a0 + - libegl 1.7.0 ha4b6fd6_5 + - libgl-devel 1.7.0 ha4b6fd6_5 + - xorg-libx11 + license: LicenseRef-libglvnd + purls: [] + run_exports: + weak: + - libegl >=1.7.0,<2.0a0 + size: 31242 + timestamp: 1787310065866 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libexpat-2.8.1-hecca717_1.conda + sha256: 16feffd9ddbbe5b718515d38ee376c685ba95491cd901244e24671d20b952a77 + md5: b24d3c612f71e7aa74158d92106318b2 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - expat 2.8.1.* + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 77856 + timestamp: 1781203599810 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libffi-3.7.0-h3435931_0.conda + sha256: ac38603008bf1e99b8ed379b1a656a67a70e2841f2b6a069c630cdf6316012d2 + md5: 0abe40a9880086ca4d2e5daf09dceff9 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libffi >=3.7.0,<3.8.0a0 + size: 67576 + timestamp: 1783520858222 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype-2.14.3-ha770c72_2.conda + sha256: fb12ecb46c30d18d928c0e8fc346ab13b5f6fc8a9cacae4f2f4bb188782586f5 + md5: f8054e759d0ddddaf34a5c8fedc900a1 + depends: + - libfreetype6 >=2.14.3 + license: GPL-2.0-only OR FTL + purls: [] + run_exports: {} + size: 8407 + timestamp: 1786641007099 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libfreetype6-2.14.3-h5e6c136_2.conda + sha256: ec607dd5445dd17ff6bf8b7fe6832e5504c226dd91d3aaea7ba83569808b0ce4 + md5: b72a266a9317036fd0464cb98b027cd0 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libpng >=1.6.58,<1.7.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - freetype >=2.14.3 + license: GPL-2.0-only OR FTL + purls: [] + run_exports: {} + size: 387671 + timestamp: 1786641006460 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-16.2.0-ha9f2e26_4.conda + sha256: 24090e675d34403b4ee1cd4372d8f6c0937da7ecfd66a19a57cac2ed0f4ea793 + md5: cba14d01083fc62ffd32c24d7d390633 + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex >=4.5 + constrains: + - libgcc-ng ==16.2.0=*_4 + - libgomp 16.2.0 he0feb66_4 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: {} + size: 1058083 + timestamp: 1787618680111 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgcc-ng-16.2.0-h69a702a_4.conda + sha256: d8e66c14e23f2b3c70410cff5979d9d357e6edfb990b28d8e630852f4d395629 + md5: b3e52878163a841f6fb951989cc0b217 + depends: + - libgcc 16.2.0 ha9f2e26_4 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: + strong: + - libgcc + size: 28403 + timestamp: 1787618684957 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran-16.2.0-h69a702a_4.conda + sha256: 7653be4d88a4d74676c38dd892914d027dcecc0c65c406be772d31cb641f8cfd + md5: 5e92b8413fd1c8f8f3006f4661b12def + depends: + - libgfortran5 16.2.0 h6b99dfc_4 + constrains: + - libgfortran-ng ==16.2.0=*_4 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: {} + size: 28377 + timestamp: 1787618711732 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgfortran5-16.2.0-h6b99dfc_4.conda + sha256: a5510cbcea3b9e9ab24b336429de997fa727af1dba323031500a962a20e38600 + md5: c22348a769bb072b6184eb6f7f05e4f2 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=16.2.0 + constrains: + - libgfortran 16.2.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: {} + size: 2526008 + timestamp: 1787618692926 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-1.7.0-ha4b6fd6_5.conda + sha256: 7b2e7e31e8a4f5a01c45ecadcd8aa68c8f733b035240bc7ba9881835ef4bf7b4 + md5: 81141db127a106eb5a91df69a29c9918 + depends: + - __glibc >=2.17,<3.0.a0 + - libglvnd 1.7.0 ha4b6fd6_5 + - libglx 1.7.0 ha4b6fd6_5 + license: LicenseRef-libglvnd + purls: [] + run_exports: {} + size: 131988 + timestamp: 1787310049847 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgl-devel-1.7.0-ha4b6fd6_5.conda + sha256: 3bebdbeb3ce451287794dddd5cc4d7f4970aac200b2fb797f0d17ac727a71cb7 + md5: f5f7fc038c611a25b22758c0a40d25c9 + depends: + - __glibc >=2.17,<3.0.a0 + - libgl 1.7.0 ha4b6fd6_5 + - libglx-devel 1.7.0 ha4b6fd6_5 + license: LicenseRef-libglvnd + purls: [] + run_exports: + weak: + - libgl >=1.7.0,<2.0a0 + size: 116212 + timestamp: 1787310061570 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libglib-2.88.3-h45c3219_1.conda + sha256: 4499458124bcf0bd45e8bd17576f9f007cb1373cb3b01659105443f522bab45c + md5: 533cb021c1bfeba9f720e669cd863fdf + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - pcre2 >=10.47,<10.48.0a0 + - libzlib >=1.3.2,<2.0a0 + - libiconv >=1.18,<2.0a0 + - libffi >=3.7.0,<3.8.0a0 + constrains: + - glib >2.66 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - libglib >=2.88.3,<3.0a0 + size: 4755172 + timestamp: 1786457663614 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libglvnd-1.7.0-ha4b6fd6_5.conda + sha256: 6eb77601a44b78c4631d886d54ef54f321c2bdc69fdefc4065a1e0491f030c76 + md5: cfcf11edcfd4acf0094771a9c3b8587f + depends: + - __glibc >=2.17,<3.0.a0 + license: LicenseRef-libglvnd + purls: [] + run_exports: {} + size: 133827 + timestamp: 1787310026653 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libglx-1.7.0-ha4b6fd6_5.conda + sha256: d1f0d51aea6bcc5d915969cbed32e72a8ed6a95931b87357ef8d0866573688c4 + md5: 614e10aae180f87b84f0d1ca8ceb7d9e + depends: + - __glibc >=2.17,<3.0.a0 + - libglvnd 1.7.0 ha4b6fd6_5 + - xorg-libx11 >=1.8.13,<2.0a0 + license: LicenseRef-libglvnd + purls: [] + run_exports: {} + size: 79834 + timestamp: 1787310042550 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libglx-devel-1.7.0-ha4b6fd6_5.conda + sha256: aff3841e3404d78e1887fef988ca4842930c577399d566fa0980808756b1df51 + md5: fb00b4fb800f2d431303a5c1625ef9d0 + depends: + - __glibc >=2.17,<3.0.a0 + - libglx 1.7.0 ha4b6fd6_5 + - xorg-libx11 >=1.8.13,<2.0a0 + - xorg-xorgproto + license: LicenseRef-libglvnd + purls: [] + run_exports: + weak: + - libglx >=1.7.0,<2.0a0 + size: 27698 + timestamp: 1787310053582 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libgomp-16.2.0-he0feb66_4.conda + sha256: 0fe5cb8e0752241ab55e11656ed1b9726248b522d23b929fe7c95b83eb55b9bb + md5: 89d2c1231f47bd818f5d624b9411459d + depends: + - __glibc >=2.17,<3.0.a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: + strong: + - _openmp_mutex >=4.5 + size: 639968 + timestamp: 1787618616266 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libharfbuzz-14.3.1-h23af247_0.conda + sha256: a4eac5a21b84e0fc753b72de01781f634fc9ab70fd32a28d6a40a19700c9188a + md5: 29709e61096dd490fff9e833e4c869f6 + depends: + - __glibc >=2.17,<3.0.a0 + - cairo >=1.18.4,<2.0a0 + - graphite2 >=1.3.15,<2.0a0 + - icu >=78.3,<79.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libgcc >=15 + - libglib >=2.88.3,<3.0a0 + - libpng >=1.6.58,<1.7.0a0 + - libstdcxx >=15 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 1353639 + timestamp: 1786970872936 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libhwloc-2.13.0-default_he001693_1000.conda + sha256: 5041d295813dfb84652557839825880aae296222ab725972285c5abe3b6e4288 + md5: c197985b58bc813d26b42881f0021c82 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libstdcxx >=14 + - libxml2 + - libxml2-16 >=2.14.6 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libhwloc >=2.13.0,<2.13.1.0a0 + size: 2436378 + timestamp: 1770953868164 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libiconv-1.18-h0cb94f2_3.conda + sha256: f943117edb9cd4d9c61cc972eee5a34291dc55ea7a6e9e38da104995841cbcb6 + md5: f92233bf33e24a25668bb2119e2c51f9 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + license: LGPL-2.1-only + purls: [] + run_exports: + weak: + - libiconv >=1.18,<2.0a0 + size: 789471 + timestamp: 1787033836207 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libjpeg-turbo-3.2.0-hb03c661_1.conda + sha256: bba8538e6538ed58a8479b332337b96986561f975d06cfa2039a016c2d246ee4 + md5: 898d1c9793eaa52efc4727bd84d2e39a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - jpeg <0.0.0a + license: IJG AND BSD-3-Clause AND Zlib + purls: [] + run_exports: + weak: + - libjpeg-turbo >=3.2.0,<4.0a0 + size: 650434 + timestamp: 1785896381946 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapack-3.11.0-9_h5e43f62_mkl.conda + build_number: 9 + sha256: 3060d9393e7013a192eb55354def1a522348baa57c3ce4dc9cb904bf392a9ac1 + md5: 255025c2d2df85b72c9ab3105f7611e4 + depends: + - libblas 3.11.0 9_h5875eb1_mkl + constrains: + - blas 2.309 mkl + - libcblas 3.11.0 9*_mkl + - liblapacke 3.11.0 9*_mkl + track_features: + - blas_mkl + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - liblapack >=3.11.0,<3.12.0a0 + size: 18128 + timestamp: 1786059007914 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblapacke-3.11.0-9_hdba1596_mkl.conda + build_number: 9 + sha256: 68f7318c0859627e58708d8eb6e297a945f9c04bff29672ea9e6351791be4c88 + md5: 8a4e244b80d9239f7ab7a2d1d5786d55 + depends: + - libblas 3.11.0 9_h5875eb1_mkl + - libcblas 3.11.0 9_hfef963f_mkl + - liblapack 3.11.0 9_h5e43f62_mkl + constrains: + - blas 2.309 mkl + track_features: + - blas_mkl + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - liblapacke >=3.11.0,<3.12.0a0 + size: 18141 + timestamp: 1786059016380 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libllvm22-22.1.8-h474f4eb_2.conda + sha256: cfc90781f703b8b8cc35694d46c9a200e3cf66657f55517f8b1ec1f672c42752 + md5: d70196b03134e3bab985d9f5554fb38b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libstdcxx >=15 + - libxml2 + - libxml2-16 >=2.15.3 + - libzlib >=1.3.2,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: + weak: + - libllvm22 >=22.1.8,<22.2.0a0 + size: 44652037 + timestamp: 1787288437518 +- conda: https://conda.anaconda.org/conda-forge/linux-64/liblzma-5.8.3-hb03c661_1.conda + sha256: 9787df8c22a59c9a70d3e5a10db9ad663485e75e9ccc3f09bd092cb7b95e0dab + md5: 1390b7c5ac0b1d8e447bc5efa6d3c8c2 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - xz 5.8.3.* + license: 0BSD + purls: [] + run_exports: + weak: + - liblzma >=5.8.3,<6.0a0 + size: 112995 + timestamp: 1786348617826 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libmpdec-4.0.0-hb03c661_2.conda + sha256: 46820b4a835e175940ae20bec00fdddaff804cda27a1408ab1b95e77a2196437 + md5: fcfed1dc5053eb1901b66e7b1fc32588 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 92759 + timestamp: 1786650399772 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libntlm-1.8-hb9d3cd8_0.conda + sha256: 3b3f19ced060013c2dd99d9d46403be6d319d4601814c772a3472fe2955612b0 + md5: 7c7927b404672409d9917d49bff5f2d6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - libntlm >=1.8,<2.0a0 + size: 33418 + timestamp: 1734670021371 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libopengl-1.7.0-ha4b6fd6_5.conda + sha256: 7bdca717c840e17d7dd050f925dd964da61086c2612b8579a4ff4147105f291f + md5: d207a2e9bae9da476bc0a603215d5167 + depends: + - __glibc >=2.17,<3.0.a0 + - libglvnd 1.7.0 ha4b6fd6_5 + license: LicenseRef-libglvnd + purls: [] + run_exports: {} + size: 50627 + timestamp: 1787310045795 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpciaccess-0.19-hb03c661_1.conda + sha256: addc80c69d362a9e6c40305c493139a8e9ee504b2f45a6687dbdaa9da3c6183c + md5: 35fa2b34bbced424e6976d30f5fde576 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libpciaccess >=0.19,<0.20.0a0 + size: 30070 + timestamp: 1785971678815 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpng-1.6.58-h922cc85_1.conda + sha256: c19eefb87d70d9b4b0629fa48414a155a47a1be4f04583ae71ed8c5a9a32fdcb + md5: fcf71c8d979148873f6f8ad4cfc73d86 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libzlib >=1.3.2,<2.0a0 + license: zlib-acknowledgement + purls: [] + run_exports: + weak: + - libpng >=1.6.58,<1.7.0a0 + size: 316643 + timestamp: 1786616563127 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libpq-18.6-h9d76c99_0.conda + sha256: b1eb210c180fda9c445b11cba1286ec250ce2c2a85ccb0551a4ed5423df51e68 + md5: 72e019c04ed02a179da38c56965802d1 + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.3,<79.0a0 + - krb5 >=1.22.2,<1.23.0a0 + - libgcc >=15 + - openldap >=2.6.13,<2.7.0a0 + - openssl >=3.5.7,<4.0a0 + license: PostgreSQL + purls: [] + run_exports: + weak: + - libpq >=18.6,<19.0a0 + size: 2719680 + timestamp: 1786641133110 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libraqm-0.11.0-h6406941_0.conda + sha256: ab3ccb9fd5816f76b18ee8974d359cd2605ca87d8ddef55369dc461b9986f95c + md5: 3ac89a48d224409739dbf6200e524373 + depends: + - libgcc >=14 + - __glibc >=2.28,<3.0.a0 + - fribidi >=1.0.16,<2.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libharfbuzz >=14.2.1 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libraqm >=0.11.0,<0.12.0a0 + size: 33983 + timestamp: 1784850267262 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsanitizer-16.2.0-h3048135_4.conda + sha256: d08ce38542e0b7782572dbed7eac9d648aad8fd951a82346d93a5d5a78e50232 + md5: fbc8b8b630d4dfa30a7a0b673367cf37 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=16.2.0 + - libstdcxx >=16.2.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: + weak: + - libsanitizer 16.2.0 + size: 8194231 + timestamp: 1787618720100 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsodium-1.0.22-hebe6cf0_2.conda + sha256: 7454c6a7cf27033757f2895bc5e3941fe27604506edd62cf6bc00e50ab015a43 + md5: 42c585f153c17b790cd01f01553d24a1 + depends: + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + license: ISC + purls: [] + run_exports: + weak: + - libsodium >=1.0.22,<1.0.23.0a0 + size: 269985 + timestamp: 1787225747011 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libsqlite-3.53.4-h13e7031_1.conda + sha256: f20d70da54e5b31dd4a51fb1efeaafafd5ab6dd8d7ac9d1438eaa2526ac4ed3d + md5: e72bbec309c2b0f37823ee7d4fabfcd3 + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.3,<79.0a0 + - libgcc >=15 + - libzlib >=1.3.2,<2.0a0 + license: blessing + purls: [] + run_exports: + weak: + - libsqlite >=3.53.4,<4.0a0 + size: 974348 + timestamp: 1787051145557 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-16.2.0-h934c35e_4.conda + sha256: 40b792b0186c1e8859280a1f6f19a54fc50a11b32724fc7b637009c1a9bd302b + md5: 2f2ef0d96de5bdd8c1270ff22fdf9352 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc 16.2.0 ha9f2e26_4 + constrains: + - libstdcxx-ng ==16.2.0=*_4 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: {} + size: 6613148 + timestamp: 1787618704262 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libstdcxx-ng-16.2.0-hdf11a46_4.conda + sha256: 4cdebd87b76cf53a58a08ebd6d15336daa79c5f0aa34d83a895ac503c0203632 + md5: de0dceacf3e33c5fc88e167885dc8274 + depends: + - libstdcxx 16.2.0 h934c35e_4 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: + strong: + - libstdcxx + size: 28459 + timestamp: 1787618737021 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libtiff-4.7.2-h9d88235_0.conda + sha256: b31346e1c01ab40a170e91147092ee8fd92b1dee3c66ee47ef025571c879b159 + md5: c1fcb4a88bc15a9f77ad8d27d7af1df9 + depends: + - __glibc >=2.17,<3.0.a0 + - lerc >=4.1.0,<5.0a0 + - libdeflate >=1.25,<1.26.0a0 + - libgcc >=14 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - liblzma >=5.8.3,<6.0a0 + - libstdcxx >=14 + - libwebp-base >=1.6.0,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: HPND + purls: [] + run_exports: + weak: + - libtiff >=4.7.2,<4.8.0a0 + size: 452337 + timestamp: 1783084902636 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libuuid-2.42.2-h5347b49_0.conda + sha256: 9b1bdce27a7e31f7d241aeecff67a1f3101d52a2b1e33ccc2cdf2613072bf81f + md5: 01bb81d12c957de066ea7362007df642 + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libuuid >=2.42.2,<3.0a0 + size: 40017 + timestamp: 1781625522462 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libvulkan-loader-1.4.357.0-h0e34353_2.conda + sha256: a70c25b66321ee77f9892b205e3247263c400803f543dc8aca53d569a59c5a77 + md5: c5f5f159b66332152197893427071695 + depends: + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=15 + - libgcc >=15 + - xorg-libxrandr >=1.5.5,<2.0a0 + - xorg-libx11 >=1.8.13,<2.0a0 + constrains: + - libvulkan-headers 1.4.357.0.* + license: Apache-2.0 + purls: [] + run_exports: + weak: + - libvulkan-loader >=1.4.357.0,<2.0a0 + size: 206957 + timestamp: 1787491938583 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libwebp-base-1.6.0-hd42ef1d_1.conda + sha256: 8415001414f488c85b72b9d8cc2071dfb3981a47bc3c8eb56ef91a57d12eae7f + md5: 9332b53d0ea93c5d39e33be03a0c611a + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + constrains: + - libwebp 1.6.0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libwebp-base >=1.6.0,<2.0a0 + size: 428430 + timestamp: 1785954557217 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.17.0-hb83e432_1.conda + sha256: ce25a1efa85a78a3ce3b16721d9c36153814adbf2fb004a15f72ec69894b4cb1 + md5: 64e856c420205b009da26471d3ac161d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - pthread-stubs + - xorg-libxau >=1.0.12,<2.0a0 + - xorg-libxdmcp >=1.1.5,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libxcb >=1.17.0,<2.0a0 + size: 397355 + timestamp: 1787077466600 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.38-h280c20c_0.conda + sha256: f7e9292dd219a6435bbb1223da9586c3e70d66d169c5a92f08db3f2127df04e9 + md5: f7a7ff5a6ab331e037abd34f379a631d + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - libxcrypt >=4.4.38 + size: 101957 + timestamp: 1785887123445 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxkbcommon-1.13.2-h51789e4_1.conda + sha256: d44878d396713ccf3b355df617f61c40a1442fffcf134392bc6ce0e6c2219369 + md5: f888787e0eab7a8076a67d197e155ffc + depends: + - xkeyboard-config + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libxcb >=1.17.0,<2.0a0 + - xorg-libxau >=1.0.12,<2.0a0 + - libxml2 + - libxml2-16 >=2.14.6 + license: MIT AND MIT-open-group AND HPND AND HPND-sell-variant AND ISC + purls: [] + run_exports: + weak: + - libxkbcommon >=1.13.2,<2.0a0 + size: 942571 + timestamp: 1787178780572 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-16-2.15.3-hca6bf5a_1.conda + sha256: 087d4de023f22d6a28b9e1b818961dd41e9bda6e9793590600ff16ca150bf9cb + md5: 20e4d67ec908f0405124f11612c48305 + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.3,<79.0a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - libxml2 2.15.3 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 559721 + timestamp: 1787237579170 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.15.3-h49c6c72_1.conda + sha256: a16a576a5844a3a0e1cdfc5e162b9fe9c64dccf6a93f44c293bb42851aebe2b4 + md5: 2d34cdb31014cbc8f1e9384c89ede0a5 + depends: + - __glibc >=2.17,<3.0.a0 + - icu >=78.3,<79.0a0 + - libgcc >=14 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libxml2-16 2.15.3 hca6bf5a_1 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libxml2 + - libxml2-16 >=2.15.3 + size: 46203 + timestamp: 1787237584107 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libxslt-1.1.43-h711ed8c_1.conda + sha256: 0694760a3e62bdc659d90a14ae9c6e132b525a7900e59785b18a08bb52a5d7e5 + md5: 87e6096ec6d542d1c1f8b33245fe8300 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libxml2 + - libxml2-16 >=2.14.6 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libxslt >=1.1.43,<2.0a0 + size: 245434 + timestamp: 1757963724977 +- conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.2-h25fd6f3_3.conda + sha256: eb8a0db0aa570124f7d2a93d7c7f596e3390df5e047818d873baad32985fc736 + md5: 0de0122d9570a8ab637c6b73db268389 + depends: + - __glibc >=2.17,<3.0.a0 + constrains: + - zlib 1.3.2 *_3 + license: Zlib + license_family: Other + purls: [] + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 63713 + timestamp: 1785362952714 +- conda: https://conda.anaconda.org/conda-forge/linux-64/llvm-openmp-22.1.8-h3206bd6_1.conda + sha256: cf74ea3b6c8f0c01ae3cea10ff9451e4475eb39ee99a2c0f1f1627630ef888e5 + md5: dc2ace17c0da02fbb8273e139f84e7ce + depends: + - __glibc >=2.17,<3.0.a0 + constrains: + - intel-openmp <0.0a0 + - openmp 22.1.8|22.1.8.* + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: + strong: + - llvm-openmp >=22.1.8 + - _openmp_mutex >=4.5 + - _openmp_mutex * *_llvm + size: 6112252 + timestamp: 1787293213024 +- conda: https://conda.anaconda.org/conda-forge/linux-64/llvmlite-0.48.0-py314h8f0570d_1.conda + sha256: f6ef7e67ad5fa82ab6697caa69b6b76df90264b43a6ac5e23b695156d514e7a8 + md5: 869536d88f55c6235d75805533e30868 + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - libgcc >=14 + - python_abi 3.14.* *_cp314 + - libzlib >=1.3.2,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/llvmlite?source=hash-mapping + run_exports: {} + size: 40440148 + timestamp: 1784043436170 +- conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.3-py314h67df5f8_1.conda + sha256: c279be85b59a62d5c52f5dd9a4cd43ebd08933809a8416c22c3131595607d4cf + md5: 9a17c4307d23318476d7fbf0fedc0cde + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=hash-mapping + run_exports: {} + size: 27424 + timestamp: 1772445227915 +- conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-3.11.1-py314h815e797_2.conda + sha256: 5dc6c469ed94cba223272ba70be250b5820e88640c8fdfe4ae85b2966b8d4b67 + md5: 19db05d03d18c1746c2212bc83e9c4c2 + depends: + - matplotlib-base >=3.11.1,<3.11.2.0a0 + - pyside6 >=6.7.2 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - tornado >=5 + license: PSF-2.0 + license_family: PSF + purls: [] + run_exports: {} + size: 15007 + timestamp: 1785211130117 +- conda: https://conda.anaconda.org/conda-forge/linux-64/matplotlib-base-3.11.1-py314h9a9c090_2.conda + sha256: d76657ece6fef30e44fca988a0a884cf5744f2fb1611814c5c993700148fbefa + md5: 57f668cbc4b70c11ea9d19ebed67295e + depends: + - __glibc >=2.17,<3.0.a0 + - contourpy >=1.0.1 + - cycler >=0.10 + - fonttools >=4.28.2 + - freetype + - kiwisolver >=1.3.1 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libgcc >=14 + - libraqm >=0.11.0,<0.12.0a0 + - libstdcxx >=14 + - numpy >=1.25 + - numpy >=1.25,<3 + - packaging >=20.0 + - pillow >=9 + - pyparsing >=3 + - python >=3.14,<3.15.0a0 + - python-dateutil >=2.7 + - python_abi 3.14.* *_cp314 + - qhull >=2020.2,<2020.3.0a0 + - tk >=8.6.13,<8.7.0a0 + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/matplotlib?source=hash-mapping + run_exports: {} + size: 9068707 + timestamp: 1785211110030 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-2026.1.0-hecca717_244.conda + sha256: 0b903cfa21b1a3396b3468d41b8112d49a5bd7ff6642305791caca3af33fbaf2 + md5: 3575c034d908c0567c53ed6a33e9dfd9 + depends: + - __glibc >=2.17,<3.0.a0 + - _openmp_mutex * *_llvm + - _openmp_mutex >=4.5 + - libgcc >=14 + - libstdcxx >=14 + - llvm-openmp >=22.1.8 + - tbb >=2023.0.0 + license: LicenseRef-IntelSimplifiedSoftwareOct2022 + license_family: Proprietary + purls: [] + run_exports: {} + size: 143113179 + timestamp: 1786085943810 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-devel-2026.1.0-ha770c72_244.conda + sha256: 89b3d2e21c0ac6bb4a0b0495473a8fdda939bf9a50871091cd64e52d640a67ec + md5: b87e47f06086656b84565cb1d72ea244 + depends: + - mkl 2026.1.0 hecca717_244 + - mkl-include 2026.1.0 ha770c72_244 + license: LicenseRef-IntelSimplifiedSoftwareOct2022 + license_family: Proprietary + purls: [] + run_exports: + weak: + - mkl >=2026.1.0,<2027.0a0 + size: 40085 + timestamp: 1786086192776 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-include-2026.1.0-ha770c72_244.conda + sha256: 71cd5db20b5f75183614b814473af582db9dcc4e6651144bbce5517ccfbd9e42 + md5: 653b438353669616adf6e78ef62ef6fe + license: LicenseRef-IntelSimplifiedSoftwareOct2022 + license_family: Proprietary + purls: [] + run_exports: {} + size: 773503 + timestamp: 1786085986536 +- conda: https://conda.anaconda.org/conda-forge/linux-64/mkl-service-2.8.0-py314h3e0429d_0.conda + sha256: b3e279d095f9a244cb468fd90d9a9e53a3d6a29655dac746e4f08cfe146097e0 + md5: 3ad114ea01ade2d7a4f16ee7bcf9b15f + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - mkl >=2026.1.0,<2027.0a0 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/mkl-service?source=hash-mapping + run_exports: {} + size: 72541 + timestamp: 1785145637684 +- conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.6-hdb14827_1.conda + sha256: 5d46557214ed184381dafe835b7c94a474a1c3b307a08a250b1ea4779b44ffb3 + md5: ee6c0cd80a60961a1f48aa3e0b91f986 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: X11 AND BSD-3-Clause + purls: [] + run_exports: + weak: + - ncurses >=6.6,<7.0a0 + size: 911196 + timestamp: 1786355078102 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numba-0.66.0-py314h42812f9_1.conda + sha256: d8ca2096544bcff0aabc44468e3ad8c160222814704af77510bb97d00556c845 + md5: b2356f05812a7e3a724baa21617035f1 + depends: + - python + - llvmlite >=0.48.0,<0.49.0a0 + - numpy >=1.22.3,<2.5 + - libgcc >=14 + - _openmp_mutex >=4.5 + - libstdcxx >=14 + - __glibc >=2.17,<3.0.a0 + - numpy >=1.23,<3 + - python_abi 3.14.* *_cp314 + constrains: + - tbb >=2021.6.0 + - libopenblas !=0.3.6 + - cuda-version >=11.2 + - cudatoolkit >=11.2 + - scipy >=1.0 + - cuda-python >=11.6 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/numba?source=hash-mapping + run_exports: {} + size: 6182231 + timestamp: 1785940728959 +- conda: https://conda.anaconda.org/conda-forge/linux-64/numpy-2.4.6-py314h2b28147_0.conda + sha256: bc61ae892973751a6b0e6ecea57ed6d7053224bddcb007165d6ceb1d7344ad47 + md5: f49b5f950379e0b97c35ca97682f7c6a + depends: + - python + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - liblapack >=3.9.0,<4.0a0 + - python_abi 3.14.* *_cp314 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=hash-mapping + run_exports: + weak: + - numpy >=1.23,<3 + size: 8928909 + timestamp: 1779169198391 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openjpeg-2.5.4-h55fea9a_0.conda + sha256: 3900f9f2dbbf4129cf3ad6acf4e4b6f7101390b53843591c53b00f034343bc4d + md5: 11b3379b191f63139e29c0d19dee24cd + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libpng >=1.6.50,<1.7.0a0 + - libstdcxx >=14 + - libtiff >=4.7.1,<4.8.0a0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - openjpeg >=2.5.4,<3.0a0 + size: 355400 + timestamp: 1758489294972 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openldap-2.6.13-hbde042b_0.conda + sha256: 21c4f6c7f41dc9bec2ea2f9c80440d9a4d45a6f2ac13243e658f10dcf1044146 + md5: 680608784722880fbfe1745067570b00 + depends: + - __glibc >=2.17,<3.0.a0 + - cyrus-sasl >=2.1.28,<3.0a0 + - krb5 >=1.22.2,<1.23.0a0 + - libgcc >=14 + - libstdcxx >=14 + - openssl >=3.5.6,<4.0a0 + license: OLDAP-2.8 + license_family: BSD + purls: [] + run_exports: + weak: + - openldap >=2.6.13,<2.7.0a0 + size: 786149 + timestamp: 1775741359582 +- conda: https://conda.anaconda.org/conda-forge/linux-64/openssl-3.6.3-h35e630c_1.conda + sha256: 012096056b97abf1f68c46b7146bd2cbd68c1be762340b4f5dad4fbbe99177bc + md5: c5955c27917ff2234def47f075e71e02 + depends: + - __glibc >=2.17,<3.0.a0 + - ca-certificates + - libgcc >=14 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - openssl >=3.6.3,<4.0a0 + size: 3182423 + timestamp: 1785913583650 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pandoc-3.10.2-ha770c72_0.conda + sha256: 509d03571e126232f9eb38f1988ac9116f8c51df4a151071c4c6f3761d3fcb44 + md5: 80b992667dc547968afb633772891f71 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + run_exports: {} + size: 22618408 + timestamp: 1786704311096 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pcre2-10.47-h8b3dc9c_1.conda + sha256: 9ebb10a4eb3be51ae67d85c679f5a7a50cb040ed25c783e6331a225ea09991d4 + md5: 06f6113cf1ff4a54b65f87ead132a5f1 + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - libgcc >=15 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - pcre2 >=10.47,<10.48.0a0 + size: 1218833 + timestamp: 1787294571916 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pillow-12.3.0-py314h8ec4b1a_0.conda + sha256: bcab128df8980514061a78b9c67f5004954048f584da20df8c5a78de9e3f5abb + md5: 233e62a8eb894b79b5c93f4f8dec4dcd + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libtiff >=4.7.1,<4.8.0a0 + - openjpeg >=2.5.4,<3.0a0 + - libxcb >=1.17.0,<2.0a0 + - zlib-ng >=2.3.3,<2.4.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - python_abi 3.14.* *_cp314 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - lcms2 >=2.19.1,<3.0a0 + - tk >=8.6.13,<8.7.0a0 + license: HPND + purls: + - pkg:pypi/pillow?source=hash-mapping + run_exports: {} + size: 1108174 + timestamp: 1782912080163 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pixman-0.46.4-h54a6638_3.conda + sha256: 829d8288764282de5a9f7b9169acb75cc7dc0b6c3fe2535cfe87dea3436bbc5d + md5: 0ee5bb30034b081a1386c1e2c98ab0a7 + depends: + - libstdcxx >=14 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - pixman >=0.46.4,<1.0a0 + size: 376704 + timestamp: 1786106621354 +- conda: https://conda.anaconda.org/conda-forge/linux-64/psutil-7.2.2-py314hfe1a184_1.conda + sha256: d4c4c9e1bcaeb38e4c4b6a17e8e0b25f8083e03d11813b872427bd47c9bfe1ca + md5: 1dadeb3cb86afd90e106395730cd87aa + depends: + - python + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + purls: + - pkg:pypi/psutil?source=hash-mapping + run_exports: {} + size: 231304 + timestamp: 1787417370367 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pthread-stubs-0.4-hb03c661_1003.conda + sha256: afc3b27b2cbb0487c1d0e963f96e71181ecfb623a24fb393bb19ff974a6382a1 + md5: df2c27f36bdb0dde779f55b5df76a352 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 9115 + timestamp: 1786067714761 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyside6-6.11.2-py314h5d85b37_0.conda + sha256: ac013bb9f32a448468fad4269659fc772cf9eac7e5c9143c46bbe4f4a5a8922d + md5: 78780ea7feb32f4b6e0c9aae536081dd + depends: + - python + - qt6-main 6.11.2.* + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=15 + - libgl >=1.7.0,<2.0a0 + - libclang13 >=22.1.8 + - qt6-main >=6.11.2,<7.0a0 + - libxslt >=1.1.43,<2.0a0 + - libvulkan-loader >=1.4.357.0,<2.0a0 + - libegl >=1.7.0,<2.0a0 + - python_abi 3.14.* *_cp314 + - libxml2 + - libxml2-16 >=2.14.6 + - libopengl >=1.7.0,<2.0a0 + license: LGPL-3.0-only + license_family: LGPL + purls: + - pkg:pypi/pyside6?source=compressed-mapping + run_exports: {} + size: 13950298 + timestamp: 1787219457013 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pytensor-3.3.0-py314ha04e0d8_0.conda + sha256: 422882556b0136fd6939ae8be095e33b187712e642a485e0377a8332f33b23b5 + md5: 9ad1bfa1210ab7383edb3e88dfcc30a8 + depends: + - python + - pytensor-base ==3.3.0 np2py314h6477eea_0 + - gxx + - blas * mkl + - mkl-service + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 10732 + timestamp: 1786614356755 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pytensor-base-3.3.0-np2py314h6477eea_0.conda + sha256: d8bb7357a11263c6a8e7c89bafbb428ce7ba2e00703252553962a1985f5e7fda + md5: 139602b6549d6c1df664ca38324826f2 + depends: + - python + - setuptools >=59.0.0 + - scipy >=1,<2 + - numpy >=2.0 + - numba >=0.58,<=0.66.0 + - filelock >=3.15 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - python_abi 3.14.* *_cp314 + - numpy >=1.25,<3 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pytensor?source=hash-mapping + run_exports: {} + size: 3221979 + timestamp: 1786614356755 +- conda: https://conda.anaconda.org/conda-forge/linux-64/python-3.14.7-h399a421_100_cp314.conda + build_number: 100 + sha256: ee59fa05898243b9a068476f4bed9461abef4487ebcdc094f569c4c47dc4a732 + md5: a9d6f626c591a56d7b7b36d2465f646d + depends: + - __glibc >=2.17,<3.0.a0 + - bzip2 >=1.0.8,<2.0a0 + - ld_impl_linux-64 >=2.36.1 + - libexpat >=2.8.1,<3.0a0 + - libffi >=3.7.0,<3.8.0a0 + - libgcc >=15 + - liblzma >=5.8.3,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.53.4,<4.0a0 + - libuuid >=2.42.2,<3.0a0 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.6,<7.0a0 + - openssl >=3.5.7,<4.0a0 + - python_abi 3.14.* *_cp314 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - zstd >=1.5.7,<1.6.0a0 + license: Python-2.0 + purls: [] + run_exports: + weak: + - python_abi 3.14.* *_cp314 + noarch: + - python + size: 37028007 + timestamp: 1787154417160 + python_site_packages_path: lib/python3.14/site-packages +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyyaml-6.0.3-py314h67df5f8_1.conda + sha256: b318fb070c7a1f89980ef124b80a0b5ccf3928143708a85e0053cde0169c699d + md5: 2035f68f96be30dc60a5dfd7452c7941 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + run_exports: {} + size: 202391 + timestamp: 1770223462836 +- conda: https://conda.anaconda.org/conda-forge/linux-64/pyzmq-27.2.0-py312h8a5ba0d_0.conda + noarch: python + sha256: 9e8b94a2c9b4479cac80def117178a1658b089b9e5d4ee64408d2e4ff89fdaba + md5: ceffbc006d87e8f81e750cebd63fd8c4 + depends: + - python + - libstdcxx >=15 + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - zeromq >=4.3.5,<4.4.0a0 + - _python_abi3_support 1.* + - cpython >=3.12 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pyzmq?source=compressed-mapping + run_exports: {} + size: 214050 + timestamp: 1787300896477 +- conda: https://conda.anaconda.org/conda-forge/linux-64/qhull-2020.2-h434a139_5.conda + sha256: 776363493bad83308ba30bcb88c2552632581b143e8ee25b1982c8c743e73abc + md5: 353823361b1d27eb3960efb076dfcaf6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc-ng >=12 + - libstdcxx-ng >=12 + license: LicenseRef-Qhull + purls: [] + run_exports: + weak: + - qhull >=2020.2,<2020.3.0a0 + size: 552937 + timestamp: 1720813982144 +- conda: https://conda.anaconda.org/conda-forge/linux-64/qt6-main-6.11.2-pl5321h9df5c37_0.conda + sha256: 00d9bc98d04aeeab6f698ca7fe9d666b157ab39b52cad1af0fa346522705668d + md5: 2ff98660281d1c6bfebda5bffa52cc89 + depends: + - libxcb + - xcb-util + - xcb-util-wm + - xcb-util-keysyms + - xcb-util-image + - xcb-util-renderutil + - xcb-util-cursor + - libgl-devel + - libegl-devel + - libgcc >=15 + - libstdcxx >=15 + - __glibc >=2.17,<3.0.a0 + - libglib >=2.88.3,<3.0a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libvulkan-loader >=1.4.357.0,<2.0a0 + - wayland >=1.26.0,<2.0a0 + - xorg-libx11 >=1.8.13,<2.0a0 + - libcups >=2.3.3,<2.4.0a0 + - alsa-lib >=1.2.16.1,<1.3.0a0 + - zstd >=1.5.7,<1.6.0a0 + - libtiff >=4.7.2,<4.8.0a0 + - libxml2 + - libxml2-16 >=2.14.6 + - dbus >=1.16.2,<2.0a0 + - libegl >=1.7.0,<2.0a0 + - xorg-libsm >=1.2.6,<2.0a0 + - xcb-util-wm >=0.4.2,<0.5.0a0 + - xorg-libice >=1.1.2,<2.0a0 + - xorg-libxxf86vm >=1.1.7,<2.0a0 + - double-conversion >=3.4.0,<3.5.0a0 + - icu >=78.3,<79.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - krb5 >=1.22.2,<1.23.0a0 + - xorg-libxcursor >=1.2.3,<2.0a0 + - pcre2 >=10.47,<10.48.0a0 + - openssl >=3.5.7,<4.0a0 + - xorg-libxcomposite >=0.4.7,<1.0a0 + - libpng >=1.6.58,<1.7.0a0 + - libjpeg-turbo >=3.2.0,<4.0a0 + - libpq >=18.6,<19.0a0 + - libxcb >=1.17.0,<2.0a0 + - xcb-util >=0.4.1,<0.5.0a0 + - xcb-util-keysyms >=0.4.1,<0.5.0a0 + - xcb-util-renderutil >=0.3.10,<0.4.0a0 + - xcb-util-image >=0.4.0,<0.5.0a0 + - libharfbuzz >=14.3.1 + - fontconfig >=2.18.3,<3.0a0 + - fonts-conda-ecosystem + - xorg-libxdamage >=1.1.6,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - libgl >=1.7.0,<2.0a0 + - xorg-libxrandr >=1.5.5,<2.0a0 + - libsqlite >=3.53.4,<4.0a0 + - xcb-util-cursor >=0.1.6,<0.2.0a0 + - libxkbcommon >=1.13.2,<2.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libdrm >=2.4.129,<2.5.0a0 + - xorg-libxext >=1.3.7,<2.0a0 + - xorg-libxtst >=1.2.5,<2.0a0 + constrains: + - qt ==6.11.2 + license: LGPL-3.0-only + license_family: LGPL + purls: [] + run_exports: + weak: + - qt6-main >=6.11.2,<7.0a0 + size: 60888449 + timestamp: 1787048975419 +- conda: https://conda.anaconda.org/conda-forge/linux-64/readline-8.3-hd6e31c0_1.conda + sha256: 01b3fe073a66e321970d09e04b388708f8cbdca5cdfbfcb7c9eeb470ad10383d + md5: 69c01c781e7c8190bbdaf79e3848c5ca + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - ncurses >=6.6,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + run_exports: + weak: + - readline >=8.3,<9.0a0 + size: 348899 + timestamp: 1787033801506 +- conda: https://conda.anaconda.org/conda-forge/linux-64/rpds-py-2026.6.3-py314h7e8cd81_0.conda + sha256: 3425007379dc5b709b02699fa4329f1b82880a42f8dec40003cad7e7b6e2adac + md5: aefc39100f5d6c043d02a38c3b90ff47 + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - python_abi 3.14.* *_cp314 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/rpds-py?source=compressed-mapping + run_exports: {} + size: 300155 + timestamp: 1787344359780 +- conda: https://conda.anaconda.org/conda-forge/linux-64/safetensors-0.8.0-py314h2e6c369_0.conda + sha256: 95c8621b6ce98ccb1c04549e8abcba58d69748ba98c0f132751356fa4be495f3 + md5: ab031a76083dab7ea346b6f68c3cd48f + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.14.* *_cp314 + constrains: + - __glibc >=2.17 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/safetensors?source=hash-mapping + run_exports: {} + size: 503263 + timestamp: 1781179688475 +- conda: https://conda.anaconda.org/conda-forge/linux-64/scipy-1.18.0-py314hf07bd8e_0.conda + sha256: 85503102237f8515ab92319fc14609e894ac9e95e3a1398b0c49db1f9ee50877 + md5: 62c390c1f8f51240f1ebc7ba782669ad + depends: + - __glibc >=2.17,<3.0.a0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libgcc >=14 + - libgfortran + - libgfortran5 >=14.3.0 + - liblapack >=3.9.0,<4.0a0 + - libstdcxx >=14 + - numpy <2.7 + - numpy >=1.23,<3 + - numpy >=2.0.0 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/scipy?source=hash-mapping + run_exports: {} + size: 17260022 + timestamp: 1781912924009 +- conda: https://conda.anaconda.org/conda-forge/linux-64/sqlalchemy-2.0.52-py314h0f05182_0.conda + sha256: 0a3d82fb1650d364f8c2427d870a6c6684e02c9e5c4b0cccb6def83957f007fb + md5: c54cb6754823fe6710369511aab5c29c + depends: + - python + - greenlet !=0.4.17 + - typing-extensions >=4.6.0 + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/sqlalchemy?source=compressed-mapping + run_exports: {} + size: 4047549 + timestamp: 1786535232010 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tbb-2023.0.0-hab88423_2.conda + sha256: 30cb9355c2fefc20ff1a3d6566b9714d5614086a2524c07721fc344eb20515ae + md5: 7073b15f9364ebc118998601ac6ca6a6 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libhwloc >=2.13.0,<2.13.1.0a0 + - libstdcxx >=14 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 182331 + timestamp: 1778673758649 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tk-8.6.13-noxft_h1df4ec4_4.conda + build_number: 104 + sha256: a1a241d172c1ccab067ba245206dd048bc3c2d1b84504b53c9468e99adfc16a1 + md5: 676a2f9b1c4fcf57b70aa5c65f6c60d0 + depends: + - libgcc >=15 + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - xorg-libx11 >=1.8.13,<2.0a0 + license: TCL + purls: [] + run_exports: + weak: + - tk >=8.6.13,<8.7.0a0 + size: 3566806 + timestamp: 1787272857910 +- conda: https://conda.anaconda.org/conda-forge/linux-64/tornado-6.5.8-py314h5bd0f2a_0.conda + sha256: ebec0d90f99fa7bbe91eabab41ee77d3f36dbd58ec2f08aa4220fdd713610b6d + md5: faea84d2f2ccadf70cf9e55381d75b3e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/tornado?source=compressed-mapping + run_exports: {} + size: 923237 + timestamp: 1786226770518 +- conda: https://conda.anaconda.org/conda-forge/linux-64/unicodedata2-17.0.1-py314h5bd0f2a_0.conda + sha256: ff1c1d7c23b91c9b0eb93a3e1380f4e2ac6c37ea2bba4f932a5484e9a55bba30 + md5: 494fdf358c152f9fdd0673c128c2f3dd + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/unicodedata2?source=hash-mapping + run_exports: {} + size: 409562 + timestamp: 1770909102180 +- conda: https://conda.anaconda.org/conda-forge/linux-64/watchfiles-1.2.0-py314h1bee95f_1.conda + sha256: 608e2de2bc61b3f271da82c30948074517c624c508ba5a7921ba742c0a4a013e + md5: 18dce090a0773324393c07048fdd6866 + depends: + - python + - anyio >=3.0.0 + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.14.* *_cp314 + constrains: + - __glibc >=2.17 + license: MIT + license_family: MIT + purls: + - pkg:pypi/watchfiles?source=hash-mapping + run_exports: {} + size: 393293 + timestamp: 1781180266552 +- conda: https://conda.anaconda.org/conda-forge/linux-64/wayland-1.26.0-h1964d1d_1.conda + sha256: a8f1333274382d23721d6f72483ece364631231f8ac3f74389951b1ff2820148 + md5: 47e3c5d0b1e968ee49efc7c3fa8ebc0c + depends: + - __glibc >=2.17,<3.0.a0 + - libexpat >=2.8.1,<3.0a0 + - libffi >=3.7.0,<3.8.0a0 + - libgcc >=14 + - libstdcxx >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - wayland >=1.26.0,<2.0a0 + size: 339462 + timestamp: 1786151675802 +- conda: https://conda.anaconda.org/conda-forge/linux-64/websockets-17.0.1-py314h518bba1_0.conda + sha256: c72d823ff108a0e0886ca5dc6a6d8c37effc5d02cf1c553138d607626a24f29f + md5: 399b4170029594ff7845022a519988fb + depends: + - python + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/websockets?source=hash-mapping + run_exports: {} + size: 431439 + timestamp: 1785597284741 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-0.4.1-h4f16b4b_2.conda + sha256: ad8cab7e07e2af268449c2ce855cbb51f43f4664936eff679b1f3862e6e4b01d + md5: fdc27cb255a7a2cc73b7919a968b48f0 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libxcb >=1.17.0,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xcb-util >=0.4.1,<0.5.0a0 + size: 20772 + timestamp: 1750436796633 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-cursor-0.1.6-hb03c661_0.conda + sha256: c2be9cae786fdb2df7c2387d2db31b285cf90ab3bfabda8fa75a596c3d20fc67 + md5: 4d1fc190b99912ed557a8236e958c559 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libxcb >=1.13 + - libxcb >=1.17.0,<2.0a0 + - xcb-util-image >=0.4.0,<0.5.0a0 + - xcb-util-renderutil >=0.3.10,<0.4.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xcb-util-cursor >=0.1.6,<0.2.0a0 + size: 20829 + timestamp: 1763366954390 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-image-0.4.0-hb711507_2.conda + sha256: 94b12ff8b30260d9de4fd7a28cca12e028e572cbc504fd42aa2646ec4a5bded7 + md5: a0901183f08b6c7107aab109733a3c91 + depends: + - libgcc-ng >=12 + - libxcb >=1.16,<2.0.0a0 + - xcb-util >=0.4.1,<0.5.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xcb-util-image >=0.4.0,<0.5.0a0 + size: 24551 + timestamp: 1718880534789 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-keysyms-0.4.1-hb711507_0.conda + sha256: 546e3ee01e95a4c884b6401284bb22da449a2f4daf508d038fdfa0712fe4cc69 + md5: ad748ccca349aec3e91743e08b5e2b50 + depends: + - libgcc-ng >=12 + - libxcb >=1.16,<2.0.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xcb-util-keysyms >=0.4.1,<0.5.0a0 + size: 14314 + timestamp: 1718846569232 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-renderutil-0.3.10-hb711507_0.conda + sha256: 2d401dadc43855971ce008344a4b5bd804aca9487d8ebd83328592217daca3df + md5: 0e0cbe0564d03a99afd5fd7b362feecd + depends: + - libgcc-ng >=12 + - libxcb >=1.16,<2.0.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xcb-util-renderutil >=0.3.10,<0.4.0a0 + size: 16978 + timestamp: 1718848865819 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xcb-util-wm-0.4.2-hb711507_0.conda + sha256: 31d44f297ad87a1e6510895740325a635dd204556aa7e079194a0034cdd7e66a + md5: 608e0ef8256b81d04456e8d211eee3e8 + depends: + - libgcc-ng >=12 + - libxcb >=1.16,<2.0.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xcb-util-wm >=0.4.2,<0.5.0a0 + size: 51689 + timestamp: 1718844051451 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xkeyboard-config-2.48-h280c20c_0.conda + sha256: 3b04afd5d1a65d2d27ac2d49a63b01ab8bcd875776779ec63e337370ed38afdc + md5: b233b41be0bf210989d57160ed39b394 + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - xorg-libx11 >=1.8.13,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 441670 + timestamp: 1782027360439 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libice-1.1.2-h280c20c_0.conda + sha256: 49b532d1df875c6749d9078b56a76f3f5db49a5abe0ca620b593ed474ef0ebf1 + md5: 85c9442aec283b4e464fa9ecc484a2f3 + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libice >=1.1.2,<2.0a0 + size: 62517 + timestamp: 1786474410404 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libsm-1.2.6-h0d788c3_1.conda + sha256: ef907caee0665cf3b0f775602cc50e388e3bade8ce22ecade0873ff26604b2fd + md5: aa7459ed9ad086ba11dda843d764b33d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - xorg-libice >=1.1.2,<2.0a0 + - libuuid >=2.42.2,<3.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libsm >=1.2.6,<2.0a0 + size: 30739 + timestamp: 1786545374265 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libx11-1.8.13-he1eb515_1.conda + sha256: 68053eebfa9f0d91666786c8fb5839d989aa9b869add92cb8815228bb2d7302c + md5: 8c282bbe4808a3cc80a5c98e9aec1cfc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - libxcb >=1.17.0,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libx11 >=1.8.13,<2.0a0 + size: 839578 + timestamp: 1787087012372 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxau-1.0.12-hb03c661_2.conda + sha256: cbf891f6cc1a859347680af1c8562bc6b033bf182a2a3bb536016932be3206de + md5: f06ef439c280a5f90b8bf62355008dbc + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxau >=1.0.12,<2.0a0 + size: 16419 + timestamp: 1786381001122 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcomposite-0.4.7-hb03c661_0.conda + sha256: 048c103000af9541c919deef03ae7c5e9c570ffb4024b42ecb58dbde402e373a + md5: f2ba4192d38b6cef2bb2c25029071d90 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - xorg-libx11 >=1.8.12,<2.0a0 + - xorg-libxfixes >=6.0.2,<7.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxcomposite >=0.4.7,<1.0a0 + size: 14415 + timestamp: 1770044404696 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxcursor-1.2.3-hb9d3cd8_0.conda + sha256: 832f538ade441b1eee863c8c91af9e69b356cd3e9e1350fff4fe36cc573fc91a + md5: 2ccd714aa2242315acaf0a67faea780b + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-libx11 >=1.8.10,<2.0a0 + - xorg-libxfixes >=6.0.1,<7.0a0 + - xorg-libxrender >=0.9.11,<0.10.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxcursor >=1.2.3,<2.0a0 + size: 32533 + timestamp: 1730908305254 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdamage-1.1.6-hb9d3cd8_0.conda + sha256: 43b9772fd6582bf401846642c4635c47a9b0e36ca08116b3ec3df36ab96e0ec0 + md5: b5fcc7172d22516e1f965490e65e33a4 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - xorg-libx11 >=1.8.10,<2.0a0 + - xorg-libxext >=1.3.6,<2.0a0 + - xorg-libxfixes >=6.0.1,<7.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxdamage >=1.1.6,<2.0a0 + size: 13217 + timestamp: 1727891438799 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxdmcp-1.1.5-hb03c661_2.conda + sha256: c50a16c05ccd7fe7dd6d6cfb539f4e9a491d50f9ed7a5c902fec638f7d0d27be + md5: 2e66c929f3d879708335b6ea4557c838 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxdmcp >=1.1.5,<2.0a0 + size: 21120 + timestamp: 1786381006369 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxext-1.3.7-h7cc23a3_1.conda + sha256: aa9bbe8b278aacc194e280ff5037f9f9a1f2c5b33ed97de8e7f01cfbe90dda43 + md5: e5b6b28536b81b3f4cb20db4668a4642 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - xorg-libx11 >=1.8.13,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxext >=1.3.7,<2.0a0 + size: 53124 + timestamp: 1787100841900 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxfixes-6.0.2-h7cc23a3_1.conda + sha256: aec84f554fc897bf4085c7bc8b8f0740e4c224e13bca3cc51c93e7699d56f83a + md5: 09132e874fe0e1f5e4492b0b6b904b6e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - xorg-libx11 >=1.8.13,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxfixes >=6.0.2,<7.0a0 + size: 21440 + timestamp: 1787248059682 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxi-1.8.3-h7cc23a3_1.conda + sha256: a523d71344a2f640efe6fc42b88fc261d9cd389d4f9838a5d42dcdb120c2b0c8 + md5: a1412b2b1184dacda45f8476fef2cc25 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - xorg-libx11 >=1.8.13,<2.0a0 + - xorg-libxext >=1.3.7,<2.0a0 + - xorg-libxfixes >=6.0.2,<7.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxi >=1.8.3,<2.0a0 + size: 49165 + timestamp: 1787257060460 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrandr-1.5.5-h7cc23a3_1.conda + sha256: 05a7f25d7f7f5cd32b27a019233ece97167fd8ade255bc13a0e49e53387f4c30 + md5: 798a8c9d171859a022e1cb89e7d0eb10 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - xorg-libx11 >=1.8.13,<2.0a0 + - xorg-libxext >=1.3.7,<2.0a0 + - xorg-libxrender >=0.9.12,<0.10.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxrandr >=1.5.5,<2.0a0 + size: 31106 + timestamp: 1787246614086 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxrender-0.9.12-hb03c661_1.conda + sha256: 6901f91d398811e4ec89d7e20a69abac02a7bfebfaf073338b7ea3d1a99685b7 + md5: e470d224a7a5be1b1d021bded7abb536 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - xorg-libx11 >=1.8.13,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxrender >=0.9.12,<0.10.0a0 + size: 34645 + timestamp: 1787100191192 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxtst-1.2.5-h7cc23a3_4.conda + sha256: d66525e7b1c492aa179c6c55610fc8dfb0a9a62ea7d4fb9f904fa6af62127f61 + md5: 752a5ac9322e0fbbc24146c1ce3ae44e + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - xorg-libx11 >=1.8.13,<2.0a0 + - xorg-libxext >=1.3.7,<2.0a0 + - xorg-libxi >=1.8.3,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxtst >=1.2.5,<2.0a0 + size: 35052 + timestamp: 1787360025506 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-libxxf86vm-1.1.7-hb03c661_0.conda + sha256: 64db17baaf36fa03ed8fae105e2e671a7383e22df4077486646f7dbf12842c9f + md5: 665d152b9c6e78da404086088077c844 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + - xorg-libx11 >=1.8.12,<2.0a0 + - xorg-libxext >=1.3.6,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxxf86vm >=1.1.7,<2.0a0 + size: 18701 + timestamp: 1769434732453 +- conda: https://conda.anaconda.org/conda-forge/linux-64/xorg-xorgproto-2025.1-h280c20c_1.conda + sha256: 051c6088bf2381840fcf8764737b829cc6c5f793718d2417d097d6e3b153eba9 + md5: 3b51576511038b50fdbd05245e22e4b1 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=14 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 594844 + timestamp: 1786114408394 +- conda: https://conda.anaconda.org/conda-forge/linux-64/yaml-0.2.5-hebe6cf0_3.conda + sha256: d164dfa75ecd538f6fd68765defcc06aa875bc697b9b215362d79a2a73125dd0 + md5: e741576fb8f89821ac7c1c537322a33d + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - yaml >=0.2.5,<0.3.0a0 + size: 84936 + timestamp: 1787228426393 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zeromq-4.3.5-h09e67af_11.conda + sha256: dc9f28dedcb5f35a127fad2d847674d2833369dd616d294e423b8997df31d8a8 + md5: 96b08867e21d4694fa5c2c226e6581b0 + depends: + - libgcc >=14 + - __glibc >=2.17,<3.0.a0 + - libstdcxx >=14 + - krb5 >=1.22.2,<1.23.0a0 + - libsodium >=1.0.22,<1.0.23.0a0 + license: MPL-2.0 + license_family: MOZILLA + purls: [] + run_exports: + weak: + - zeromq >=4.3.5,<4.4.0a0 + size: 311184 + timestamp: 1779123989774 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zlib-ng-2.3.3-hce19668_1.conda + sha256: 8b786bb4380fa69a718b08a400040b865bc9207eda337e0a1955717c3c3a9403 + md5: 1726acec19beeed1c764385cd8622405 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=15 + - libstdcxx >=15 + license: Zlib + license_family: Other + purls: [] + run_exports: + weak: + - zlib-ng >=2.3.3,<2.4.0a0 + size: 123959 + timestamp: 1786736890973 +- conda: https://conda.anaconda.org/conda-forge/linux-64/zstd-1.5.7-hb78ec9c_7.conda + sha256: 47d682b9f6d6ec9eb1a6e6c3e75ea6273e899e78fb7fc59f81d39745009fbc60 + md5: aa459086047c0e5e27023ab19f8cb86a + depends: + - __glibc >=2.17,<3.0.a0 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - zstd >=1.5.7,<1.6.0a0 + size: 601301 + timestamp: 1786599621503 +- conda: https://conda.anaconda.org/conda-forge/noarch/_python_abi3_support-1.0-hd8ed1ab_3.conda + sha256: 2a7204314663eeda5dec482a956f0e2eaf289bd5b9953eaaaad0e81aa64638f2 + md5: 3845f3d75991bae0fb90884662f4327c + depends: + - cpython + - python-gil + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 8144 + timestamp: 1784221492234 +- conda: https://conda.anaconda.org/conda-forge/noarch/accessible-pygments-0.0.5-pyhd8ed1ab_1.conda + sha256: 1307719f0d8ee694fc923579a39c0621c23fdaa14ccdf9278a5aac5665ac58e9 + md5: 74ac5069774cdbc53910ec4d631a3999 + depends: + - pygments + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/accessible-pygments?source=hash-mapping + run_exports: {} + size: 1326096 + timestamp: 1734956217254 +- conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-1.0.0-pyhd8ed1ab_1.conda + sha256: 6c4456a138919dae9edd3ac1a74b6fbe5fd66c05675f54df2f8ab8c8d0cc6cea + md5: 1fd9696649f65fd6611fcdb4ffec738a + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/alabaster?source=hash-mapping + run_exports: {} + size: 18684 + timestamp: 1733750512696 +- conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.14.2-pyhcf101f3_0.conda + sha256: e36998c5e860e26b22e5dbcd5726dd0c4eabad949c84d383cad8512757bbf6a1 + md5: fb568fbae6908ba86a090a85a089d11f + depends: + - exceptiongroup >=1.0.2 + - idna >=2.8 + - python >=3.10 + - typing_extensions >=4.5 + - python + constrains: + - trio >=0.32.0 + - uvloop >=0.22.1 + - winloop >=0.2.3 + license: MIT + license_family: MIT + purls: + - pkg:pypi/anyio?source=hash-mapping + run_exports: {} + size: 164465 + timestamp: 1783889660383 +- conda: https://conda.anaconda.org/conda-forge/noarch/appnope-1.0.0-pyhcf101f3_0.conda + sha256: 52bc705ba773214cb2f8f884ee0128d007fa7dfdcf19218c910465381b91cd6e + md5: 56d53a5e9740367f5f8cf83f995263c2 + depends: + - python >=3.10 + - python + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/appnope?source=compressed-mapping + run_exports: {} + size: 12232 + timestamp: 1787337124448 +- conda: https://conda.anaconda.org/conda-forge/noarch/argon2-cffi-25.1.0-pyhd8ed1ab_0.conda + sha256: bea62005badcb98b1ae1796ec5d70ea0fc9539e7d59708ac4e7d41e2f4bb0bad + md5: 8ac12aff0860280ee0cff7fa2cf63f3b + depends: + - argon2-cffi-bindings + - python >=3.9 + - typing-extensions + constrains: + - argon2_cffi ==999 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi?source=hash-mapping + run_exports: {} + size: 18715 + timestamp: 1749017288144 +- conda: https://conda.anaconda.org/conda-forge/noarch/arrow-1.4.0-pyhcf101f3_0.conda + sha256: 792da8131b1b53ff667bd6fc617ea9087b570305ccb9913deb36b8e12b3b5141 + md5: 85c4f19f377424eafc4ed7911b291642 + depends: + - python >=3.10 + - python-dateutil >=2.7.0 + - python-tzdata + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/arrow?source=hash-mapping + run_exports: {} + size: 113854 + timestamp: 1760831179410 +- conda: https://conda.anaconda.org/conda-forge/noarch/asttokens-3.0.2-pyhd8ed1ab_0.conda + sha256: fbbd8ce60cbd5c16f3fe559eb644551f94285caff30985ea961ff851c1cf25ac + md5: 89d495168582cb00428dad699d149624 + depends: + - python >=3.10 + constrains: + - astroid >=2,<5 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/asttokens?source=hash-mapping + run_exports: {} + size: 34639 + timestamp: 1783975742052 +- conda: https://conda.anaconda.org/conda-forge/noarch/async-lru-2.3.0-pyhcf101f3_0.conda + sha256: ea8486637cfb89dc26dc9559921640cd1d5fd37e5e02c33d85c94572139f2efe + md5: b85e84cb64c762569cc1a760c2327e0a + depends: + - python >=3.10 + - typing_extensions >=4.0.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/async-lru?source=hash-mapping + run_exports: {} + size: 22949 + timestamp: 1773926359134 +- conda: https://conda.anaconda.org/conda-forge/noarch/attrs-26.1.0-pyhcf101f3_0.conda + sha256: 1b6124230bb4e571b1b9401537ecff575b7b109cc3a21ee019f65e083b8399ab + md5: c6b0543676ecb1fb2d7643941fe375f2 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/attrs?source=hash-mapping + run_exports: {} + size: 64927 + timestamp: 1773935801332 +- conda: https://conda.anaconda.org/conda-forge/noarch/babel-2.18.0-pyhcf101f3_1.conda + sha256: a14a9ad02101aab25570543a59c5193043b73dc311a25650134ed9e6cb691770 + md5: f1976ce927373500cc19d3c0b2c85177 + depends: + - python >=3.10 + - python + constrains: + - pytz >=2015.7 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/babel?source=hash-mapping + run_exports: {} + size: 7684321 + timestamp: 1772555330347 +- conda: https://conda.anaconda.org/conda-forge/noarch/backports.zstd-1.7.0-py314h680f03e_0.conda + noarch: generic + sha256: 19514d89d1e725e44b0650d31a4d43da8e070e4e93e4131e3b16bd139404f2b2 + md5: 92adf685875ab717f68ad4172ef6de27 + depends: + - python >=3.14 + license: BSD-3-Clause AND MIT AND EPL-2.0 + purls: [] + run_exports: {} + size: 7541 + timestamp: 1786861415851 +- conda: https://conda.anaconda.org/conda-forge/noarch/beautifulsoup4-4.15.0-pyha770c72_0.conda + sha256: aed4b9dcf68ec2a75e5645fed14d77fd884d38d2e52bfa6ef4b278d90cd88781 + md5: 3b261da3fe9b4168738712832410b022 + depends: + - python >=3.10 + - soupsieve >=1.2 + - typing-extensions + license: MIT + license_family: MIT + purls: + - pkg:pypi/beautifulsoup4?source=hash-mapping + run_exports: {} + size: 92704 + timestamp: 1780853175566 +- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-6.4.0-pyhcf101f3_0.conda + sha256: 0c786f3e571bd58ac73d730d06314716663884d848ae320de0b438fae5e0bea9 + md5: 93009c29cdd6f2500468f2502fff9209 + depends: + - python >=3.10 + - webencodings + - python + constrains: + - tinycss2 >=1.1.0,<1.5 + license: Apache-2.0 AND MIT + purls: + - pkg:pypi/bleach?source=hash-mapping + run_exports: {} + size: 142246 + timestamp: 1780675823953 +- conda: https://conda.anaconda.org/conda-forge/noarch/bleach-with-css-6.4.0-hac0b51c_0.conda + sha256: ede77e412304cd080e23967352a7904932207d0167ecdccd6a9e210530942be6 + md5: 5f710eab1f3c4e773c75686f5e8e6481 + depends: + - bleach ==6.4.0 pyhcf101f3_0 + - tinycss2 + license: Apache-2.0 AND MIT + purls: [] + run_exports: {} + size: 4406 + timestamp: 1780675823953 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-h4c7d964_0.conda + sha256: 95e8e74062a5fe5f870ac8c90302b6e89945165fdaed7810606e84ddee6aac12 + md5: e27d2ac27b096dc51fedfcf775a53f9b + depends: + - __win + license: ISC + purls: [] + run_exports: {} + size: 132136 + timestamp: 1784754918886 +- conda: https://conda.anaconda.org/conda-forge/noarch/ca-certificates-2026.7.22-hbd8a1cb_0.conda + sha256: 0a0544cf95f64394fe4959286f5c71f5444ad58feb0602e53becb27448d24da6 + md5: 0f51e2391ade309db462a55611263e9c + depends: + - __unix + license: ISC + purls: [] + run_exports: {} + size: 131780 + timestamp: 1784754889428 +- conda: https://conda.anaconda.org/conda-forge/noarch/cached-property-1.5.2-hd8ed1ab_2.conda + noarch: python + sha256: 0d00dd61cb91b1bd1536600c64c9db4f94f3c699fec42864d0a2f4bc2ad8c3e8 + md5: 1990eb3f49022846fbc7fc9624a0ee43 + depends: + - cached_property >=1.5.2,<1.5.3.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 6836 + timestamp: 1783242914545 +- conda: https://conda.anaconda.org/conda-forge/noarch/cached_property-1.5.2-pyha770c72_2.conda + sha256: b1808a7811b5688d045b204425bb7ee824b340b40025b4ad0a39b4db1f4f1c98 + md5: f71e6840332854fd2eac6c3229768a9c + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/cached-property?source=hash-mapping + run_exports: {} + size: 14752 + timestamp: 1783242913845 +- conda: https://conda.anaconda.org/conda-forge/noarch/certifi-2026.7.22-pyhd8ed1ab_0.conda + sha256: fb167de4388e64e52aa3907ed099afab944c1fa6e5f74b281a312dae1bcf7f3b + md5: 37e13edbe3b48f1095a9d085ef9cd83b + depends: + - python >=3.10 + license: ISC + purls: + - pkg:pypi/certifi?source=hash-mapping + run_exports: {} + size: 137015 + timestamp: 1784717699092 +- conda: https://conda.anaconda.org/conda-forge/noarch/charset-normalizer-3.5.1-pyhd8ed1ab_0.conda + sha256: cb60ef3e0631c8bacb4f7057196dee4496091a22baa3bb4b9bccb12c7e1c921b + md5: e0ac3accc64e23e40969d660e5f58ac8 + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/charset-normalizer?source=compressed-mapping + run_exports: {} + size: 64487 + timestamp: 1786835648298 +- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.4.2-pyh6dadd2b_0.conda + sha256: 5b5c96afdd801dd9c3b78ebc2cd9a9f3ce34186257415d394dde1aa8468aa3c0 + md5: 8a0d65027e25e367f9f1754f0604e8de + depends: + - __win + - colorama + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/click?source=hash-mapping + run_exports: {} + size: 106227 + timestamp: 1783085395110 +- conda: https://conda.anaconda.org/conda-forge/noarch/click-8.4.2-pyhc90fa1f_0.conda + sha256: ccc4787f511964f9a1f2d2d2859c91c5d571fb60f7f09d4c4e092c9b7a94e671 + md5: 2c4bd6aeb90bb157456841c3270a0d92 + depends: + - __unix + - python + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/click?source=hash-mapping + run_exports: {} + size: 107155 + timestamp: 1783085363526 +- conda: https://conda.anaconda.org/conda-forge/noarch/colorama-0.4.6-pyhd8ed1ab_1.conda + sha256: ab29d57dc70786c1269633ba3dff20288b81664d3ff8d21af995742e2bb03287 + md5: 962b9857ee8e7018c22f2776ffa0b2d7 + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/colorama?source=hash-mapping + run_exports: {} + size: 27011 + timestamp: 1733218222191 +- conda: https://conda.anaconda.org/conda-forge/noarch/comm-0.2.3-pyhe01879c_0.conda + sha256: 576a44729314ad9e4e5ebe055fbf48beb8116b60e58f9070278985b2b634f212 + md5: 2da13f2b299d8e1995bafbbe9689a2f7 + depends: + - python >=3.9 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/comm?source=hash-mapping + run_exports: {} + size: 14690 + timestamp: 1753453984907 +- conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt22_osx-arm64-22.1.8-hb8825d9_2.conda + sha256: 016121f654e8052edf0e9912ab2f430c2d6ac93adc8cfd484318de6a656cd0c9 + md5: e603f604aa1e281db38ff849cc4e4c5d + constrains: + - compiler-rt >=9.0.1 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: {} + size: 10565200 + timestamp: 1787293602816 +- conda: https://conda.anaconda.org/conda-forge/noarch/compiler-rt_osx-arm64-22.1.8-hce30654_2.conda + sha256: 35a0429b875ff8252b173e4c0a3b028f58ea837da56b04e052ffc2c68c253633 + md5: 79874270cd835b658d889f4cd054677b + depends: + - compiler-rt22_osx-arm64 22.1.8 hb8825d9_2 + constrains: + - clang 22.1.8 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: {} + size: 16768 + timestamp: 1787293624614 +- conda: https://conda.anaconda.org/conda-forge/noarch/cpython-3.14.7-py314hd8ed1ab_100.conda + noarch: generic + sha256: 2a58aa937a36623e97d23fe595f26f56e0bfe9ecbac7418699f8b5744de15de1 + md5: 43ee8b10fa6955115c1969d04caa49a3 + depends: + - python >=3.14,<3.15.0a0 + - python_abi * *_cp314 + license: Python-2.0 + purls: [] + run_exports: {} + size: 50365 + timestamp: 1787153603657 +- conda: https://conda.anaconda.org/conda-forge/noarch/cycler-0.12.1-pyhcf101f3_2.conda + sha256: bb47aec5338695ff8efbddbc669064a3b10fe34ad881fb8ad5d64fbfa6910ed1 + md5: 4c2a8fef270f6c69591889b93f9f55c1 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/cycler?source=hash-mapping + run_exports: {} + size: 14778 + timestamp: 1764466758386 +- conda: https://conda.anaconda.org/conda-forge/noarch/defusedxml-0.7.1-pyhd8ed1ab_0.tar.bz2 + sha256: 9717a059677553562a8f38ff07f3b9f61727bd614f505658b0a5ecbcf8df89be + md5: 961b3a227b437d82ad7054484cfa71b2 + depends: + - python >=3.6 + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/defusedxml?source=hash-mapping + run_exports: {} + size: 24062 + timestamp: 1615232388757 +- conda: https://conda.anaconda.org/conda-forge/noarch/docutils-0.22.4-pyhd8ed1ab_0.conda + sha256: 0d605569a77350fb681f9ed8d357cc71649b59a304099dc9d09fbeec5e84a65e + md5: d6bd3cd217e62bbd7efe67ff224cd667 + depends: + - python >=3.10 + license: CC-PDDC AND BSD-3-Clause AND BSD-2-Clause AND ZPL-2.1 + purls: + - pkg:pypi/docutils?source=hash-mapping + run_exports: {} + size: 438002 + timestamp: 1766092633160 +- conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.3.1-pyhd8ed1ab_0.conda + sha256: ee6cf346d017d954255bbcbdb424cddea4d14e4ed7e9813e429db1d795d01144 + md5: 8e662bd460bda79b1ea39194e3c4c9ab + depends: + - python >=3.10 + - typing_extensions >=4.6.0 + license: MIT and PSF-2.0 + purls: + - pkg:pypi/exceptiongroup?source=hash-mapping + run_exports: {} + size: 21333 + timestamp: 1763918099466 +- conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.2.1-pyhd8ed1ab_0.conda + sha256: 210c8165a58fdbf16e626aac93cc4c14dbd551a01d1516be5ecad795d2422cad + md5: ff9efb7f7469aed3c4a8106ffa29593c + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/executing?source=hash-mapping + run_exports: {} + size: 30753 + timestamp: 1756729456476 +- conda: https://conda.anaconda.org/conda-forge/noarch/filelock-3.32.4-pyhd8ed1ab_0.conda + sha256: c2c2527101fea8d2fbae3883d3328a4cd225e8fc3f133b49504acb7a8cecf6fe + md5: 0171dc5d54fdbb3f6e55f285f805e0fe + depends: + - python >=3.10 + license: Unlicense + purls: + - pkg:pypi/filelock?source=compressed-mapping + run_exports: {} + size: 78622 + timestamp: 1787521663311 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 + sha256: 58d7f40d2940dd0a8aa28651239adbf5613254df0f75789919c4e6762054403b + md5: 0c96522c6bdaed4b1566d11387caaf45 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 397370 + timestamp: 1566932522327 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 + sha256: c52a29fdac682c20d252facc50f01e7c2e7ceac52aa9817aaf0bb83f7559ec5c + md5: 34893075a5c9e55cdafac56607368fc6 + license: OFL-1.1 + license_family: Other + purls: [] + run_exports: {} + size: 96530 + timestamp: 1620479909603 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-source-code-pro-2.038-h77eed37_0.tar.bz2 + sha256: 00925c8c055a2275614b4d983e1df637245e19058d79fc7dd1a93b8d9fb4b139 + md5: 4d59c254e01d9cde7957100457e2d5fb + license: OFL-1.1 + license_family: Other + purls: [] + run_exports: {} + size: 700814 + timestamp: 1620479612257 +- conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-ubuntu-0.83-h77eed37_3.conda + sha256: 2821ec1dc454bd8b9a31d0ed22a7ce22422c0aef163c59f49dfdf915d0f0ca14 + md5: 49023d73832ef61042f6a237cb2687e7 + license: LicenseRef-Ubuntu-Font-Licence-Version-1.0 + license_family: Other + purls: [] + run_exports: {} + size: 1620504 + timestamp: 1727511233259 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-ecosystem-1-0.tar.bz2 + sha256: a997f2f1921bb9c9d76e6fa2f6b408b7fa549edd349a77639c9fe7a23ea93e61 + md5: fee5683a3f04bd15cbd8318b096a27ab + depends: + - fonts-conda-forge + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 3667 + timestamp: 1566974674465 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-hc364b38_1.conda + sha256: 54eea8469786bc2291cc40bca5f46438d3e062a399e8f53f013b6a9f50e98333 + md5: a7970cd949a077b7cb9696379d338681 + depends: + - font-ttf-ubuntu + - font-ttf-inconsolata + - font-ttf-dejavu-sans-mono + - font-ttf-source-code-pro + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 4059 + timestamp: 1762351264405 +- conda: https://conda.anaconda.org/conda-forge/noarch/fonttools-4.63.0-pyh7db6752_0.conda + sha256: c9752235f1ff7061d834e5e4a3d0adf71ebeeff2b3fad82dab607edce7f70c91 + md5: 0509ee74d95e5b98eb6fe2a47760e399 + depends: + - brotli + - munkres + - python >=3.10 + - unicodedata2 >=15.1.0 + track_features: + - fonttools_no_compile + license: MIT + license_family: MIT + purls: + - pkg:pypi/fonttools?source=hash-mapping + run_exports: {} + size: 846038 + timestamp: 1778770337113 +- conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_1.conda + sha256: 2509992ec2fd38ab27c7cdb42cf6cadc566a1cc0d1021a2673475d9fa87c6276 + md5: d3549fd50d450b6d9e7dddff25dd2110 + depends: + - cached-property >=1.3.0 + - python >=3.9,<4 + license: MPL-2.0 + license_family: MOZILLA + purls: + - pkg:pypi/fqdn?source=hash-mapping + run_exports: {} + size: 16705 + timestamp: 1733327494780 +- conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.16.0-pyhcf101f3_1.conda + sha256: 96cac6573fd35ae151f4d6979bab6fbc90cb6b1fb99054ba19eb075da9822fcb + md5: b8993c19b0c32a2f7b66cbb58ca27069 + depends: + - python >=3.10 + - typing_extensions + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/h11?source=hash-mapping + run_exports: {} + size: 39069 + timestamp: 1767729720872 +- conda: https://conda.anaconda.org/conda-forge/noarch/h2-4.4.1-pyhcf101f3_0.conda + sha256: 307dd6ec90140c3cf4171071b0e5e870abec314f4565c1edd5bc433e942cdcc0 + md5: e652ac7756069c456d0da2a922cd7df5 + depends: + - python >=3.10 + - hyperframe >=6.1,<7 + - hpack >=4.2,<5 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/h2?source=hash-mapping + run_exports: {} + size: 100789 + timestamp: 1785796355216 +- conda: https://conda.anaconda.org/conda-forge/noarch/hpack-4.2.0-pyhd8ed1ab_0.conda + sha256: fdcea5d7cb314485d3907192ef024c704311548c5b0cbeb390cd1951051e29d2 + md5: b395909221b9bd1df066e5930e18855b + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hpack?source=hash-mapping + run_exports: {} + size: 32884 + timestamp: 1782283986153 +- conda: https://conda.anaconda.org/conda-forge/noarch/httpcore-1.0.9-pyh29332c3_0.conda + sha256: 04d49cb3c42714ce533a8553986e1642d0549a05dc5cc48e0d43ff5be6679a5b + md5: 4f14640d58e2cc0aa0819d9d8ba125bb + depends: + - python >=3.9 + - h11 >=0.16 + - h2 >=3,<5 + - sniffio 1.* + - anyio >=4.0,<5.0 + - certifi + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/httpcore?source=hash-mapping + run_exports: {} + size: 49483 + timestamp: 1745602916758 +- conda: https://conda.anaconda.org/conda-forge/noarch/httpx-0.28.1-pyhd8ed1ab_0.conda + sha256: cd0f1de3697b252df95f98383e9edb1d00386bfdd03fdf607fa42fe5fcb09950 + md5: d6989ead454181f4f9bc987d3dc4e285 + depends: + - anyio + - certifi + - httpcore 1.* + - idna + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/httpx?source=hash-mapping + run_exports: {} + size: 63082 + timestamp: 1733663449209 +- conda: https://conda.anaconda.org/conda-forge/noarch/hyperframe-6.1.0-pyhd8ed1ab_0.conda + sha256: 77af6f5fe8b62ca07d09ac60127a30d9069fdc3c68d6b256754d0ffb1f7779f8 + md5: 8e6923fc12f1fe8f8c4e5c9f343256ac + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/hyperframe?source=hash-mapping + run_exports: {} + size: 17397 + timestamp: 1737618427549 +- conda: https://conda.anaconda.org/conda-forge/noarch/idna-3.19-pyhcf101f3_0.conda + sha256: 1c35a59c1545ad0fdaddf1fbde7bcfa6ef41a8d68c3a2b0b4a291be00676163e + md5: a39ae05027e9b707742e41b30d296b75 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/idna?source=compressed-mapping + run_exports: {} + size: 177433 + timestamp: 1787059857580 +- conda: https://conda.anaconda.org/conda-forge/noarch/imagesize-2.0.0-pyhd8ed1ab_0.conda + sha256: 5a047f9eac290e679b4e6f6f4cbfcc5acdfbf031a4f06824d4ddb590cdbb850b + md5: 92617c2ba2847cca7a6ed813b6f4ab79 + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/imagesize?source=hash-mapping + run_exports: {} + size: 15729 + timestamp: 1773752188889 +- conda: https://conda.anaconda.org/conda-forge/noarch/importlib-metadata-9.0.0-pyhcf101f3_0.conda + sha256: 43e2a5497cad1598ff88a3e69f69bc88b7b8f141fa63c60eab5db296317318b8 + md5: ffc17e785d64e12fc311af9184221839 + depends: + - python >=3.10 + - zipp >=3.20 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/importlib-metadata?source=hash-mapping + run_exports: {} + size: 34766 + timestamp: 1779714582554 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.3.0-pyh01cf8df_0.conda + sha256: 994d3cb6b9b88a6533f567c50d20f2f6edc40ae3540ce2ee9629492182ab3403 + md5: a1ddab91145f7f06eee769d2f3ac69cd + depends: + - appnope + - __osx + - comm >=0.1.1 + - debugpy >=1.6.5 + - ipython >=7.23.1 + - jupyter_client >=8.9.0 + - jupyter_core >=5.1,!=6.0.* + - matplotlib-inline >=0.1 + - nest-asyncio2 >=1.7.0 + - packaging >=22 + - psutil >=5.7 + - python >=3.10 + - pyzmq >=25 + - tornado >=6.4.1 + - traitlets >=5.4.0 + - python + constrains: + - appnope >=0.1.2 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipykernel?source=hash-mapping + run_exports: {} + size: 137725 + timestamp: 1781101860049 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.3.0-pyh6dadd2b_0.conda + sha256: e3ff0b3d5db5c31830030406f50ac2c9a5c31b86f1c2cef87a6042f0a4c77eb7 + md5: dd5c51d5c42381ba4a2e0ce32e02ba17 + depends: + - __win + - comm >=0.1.1 + - debugpy >=1.6.5 + - ipython >=7.23.1 + - jupyter_client >=8.9.0 + - jupyter_core >=5.1,!=6.0.* + - matplotlib-inline >=0.1 + - nest-asyncio2 >=1.7.0 + - packaging >=22 + - psutil >=5.7 + - python >=3.10 + - pyzmq >=25 + - tornado >=6.4.1 + - traitlets >=5.4.0 + - python + constrains: + - appnope >=0.1.2 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipykernel?source=hash-mapping + run_exports: {} + size: 138046 + timestamp: 1781101760172 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-7.3.0-pyha191276_0.conda + sha256: 305ad9226363ff5f259c404dd9a7508183a2e150739b2adc43db7d817234da66 + md5: 2b47a10e4d98334f8171ff60aea05ff3 + depends: + - __linux + - comm >=0.1.1 + - debugpy >=1.6.5 + - ipython >=7.23.1 + - jupyter_client >=8.9.0 + - jupyter_core >=5.1,!=6.0.* + - matplotlib-inline >=0.1 + - nest-asyncio2 >=1.7.0 + - packaging >=22 + - psutil >=5.7 + - python >=3.10 + - pyzmq >=25 + - tornado >=6.4.1 + - traitlets >=5.4.0 + - python + constrains: + - appnope >=0.1.2 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipykernel?source=hash-mapping + run_exports: {} + size: 138635 + timestamp: 1781101665847 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.16.1-pyh53cf698_0.conda + sha256: 8470648b5e790d1881c09c02068d53e1bf0126f020db02a6dc2e73400cf1eeeb + md5: 23564ed27c9c7714905be96ac5786500 + depends: + - __unix + - ipython_pygments_lexers >=1.0.0 + - jedi >=0.18.2 + - matplotlib-inline >=0.1.6 + - prompt-toolkit >=3.0.41,<3.1.0 + - psutil >=7 + - pygments >=2.14.0 + - python >=3.11 + - stack_data >=0.6.0 + - traitlets >=5.13.0 + - typing_extensions >=4.6 + - pexpect >4.6 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipython?source=hash-mapping + run_exports: {} + size: 716078 + timestamp: 1785754203352 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipython-9.16.1-pyhe2676ad_0.conda + sha256: 2a0cd24e5c0e1eb8b7baf06346906673f6b8abea151ce302d7d978a3c416aeec + md5: d7c95d926befcfa22bee69bb1980e2ce + depends: + - __win + - ipython_pygments_lexers >=1.0.0 + - jedi >=0.18.2 + - matplotlib-inline >=0.1.6 + - prompt-toolkit >=3.0.41,<3.1.0 + - psutil >=7 + - pygments >=2.14.0 + - python >=3.11 + - stack_data >=0.6.0 + - traitlets >=5.13.0 + - typing_extensions >=4.6 + - colorama >=0.4.4 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipython?source=hash-mapping + run_exports: {} + size: 715162 + timestamp: 1785754256301 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipython_pygments_lexers-1.1.1-pyhd8ed1ab_0.conda + sha256: 894682a42a7d659ae12878dbcb274516a7031bbea9104e92f8e88c1f2765a104 + md5: bd80ba060603cc228d9d81c257093119 + depends: + - pygments + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipython-pygments-lexers?source=hash-mapping + run_exports: {} + size: 13993 + timestamp: 1737123723464 +- conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.9-pyhd8ed1ab_0.conda + sha256: c391fe12c24fce4f8380f44ebdfb62a5ecdbf0ee168e7db7ea9baf3527eec003 + md5: 1efaf89bb5d9a6ff2ce5f84d25678f53 + depends: + - comm >=0.1.3 + - ipython >=6.1.0 + - jupyterlab_widgets >=3.0.17,<3.1.0 + - python >=3.10 + - traitlets >=4.3.1 + - widgetsnbextension >=4.0.16,<4.1.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/ipywidgets?source=compressed-mapping + run_exports: {} + size: 115707 + timestamp: 1787052940568 +- conda: https://conda.anaconda.org/conda-forge/noarch/isoduration-20.11.0-pyhd8ed1ab_1.conda + sha256: 08e838d29c134a7684bca0468401d26840f41c92267c4126d7b43a6b533b0aed + md5: 0b0154421989637d424ccf0f104be51a + depends: + - arrow >=0.15.0 + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/isoduration?source=hash-mapping + run_exports: {} + size: 19832 + timestamp: 1733493720346 +- conda: https://conda.anaconda.org/conda-forge/noarch/jedi-0.20.0-pyhcf101f3_0.conda + sha256: 744143551c1c7b528b82533fb641b9d7db20b2203abc4c2635c387fa6c089fc3 + md5: c2b3d37aa1411031126036ee76a8a861 + depends: + - python >=3.10 + - parso >=0.8.6,<0.9.0 + - python + license: Apache-2.0 AND MIT + purls: + - pkg:pypi/jedi?source=hash-mapping + run_exports: {} + size: 2715215 + timestamp: 1782251948616 +- conda: https://conda.anaconda.org/conda-forge/noarch/jinja2-3.1.6-pyhcf101f3_1.conda + sha256: fc9ca7348a4f25fed2079f2153ecdcf5f9cf2a0bc36c4172420ca09e1849df7b + md5: 04558c96691bed63104678757beb4f8d + depends: + - markupsafe >=2.0 + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jinja2?source=hash-mapping + run_exports: {} + size: 120685 + timestamp: 1764517220861 +- conda: https://conda.anaconda.org/conda-forge/noarch/json5-0.15.0-pyhd8ed1ab_0.conda + sha256: 637400a4174880463985c7a5b6e3e0bea0aa9d0892a6c06a3a8aa2cf1208c35a + md5: 761a4a6b9cba303c66a97ca642447171 + depends: + - python >=3.10 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/json5?source=hash-mapping + run_exports: {} + size: 39373 + timestamp: 1781926894833 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonpointer-3.1.1-pyhcf101f3_0.conda + sha256: a3d10301b6ff399ba1f3d39e443664804a3d28315a4fb81e745b6817845f70ae + md5: 89bf346df77603055d3c8fe5811691e6 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jsonpointer?source=hash-mapping + run_exports: {} + size: 14190 + timestamp: 1774311356147 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-4.26.0-pyhcf101f3_1.conda + sha256: 328756a4555941d57af4a5f553e5d8598e9f3b37db028f557e30f9f0b3086db6 + md5: 204b5ca91eba7738790ecc333facbbbe + depends: + - attrs >=22.2.0 + - jsonschema-specifications >=2023.3.6 + - python >=3.10 + - referencing >=0.28.4 + - rpds-py >=0.25.0 + - python + license: MIT + purls: + - pkg:pypi/jsonschema?source=compressed-mapping + run_exports: {} + size: 82084 + timestamp: 1787579299493 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-specifications-2025.9.1-pyhcf101f3_0.conda + sha256: 0a4f3b132f0faca10c89fdf3b60e15abb62ded6fa80aebfc007d05965192aa04 + md5: 439cd0f567d697b20a8f45cb70a1005a + depends: + - python >=3.10 + - referencing >=0.31.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/jsonschema-specifications?source=hash-mapping + run_exports: {} + size: 19236 + timestamp: 1757335715225 +- conda: https://conda.anaconda.org/conda-forge/noarch/jsonschema-with-format-nongpl-4.26.0-hcf101f3_1.conda + sha256: 1b8ed25276ceae973e21d269fbe11f08d0b5f73c7ac9584e2d6a9dd763b8707a + md5: ae36c895b9e25a127d2acbdbaa87e301 + depends: + - jsonschema >=4.26.0,<4.26.1.0a0 + - fqdn + - idna + - isoduration + - jsonpointer >1.13 + - rfc3339-validator + - rfc3986-validator >0.1.0 + - rfc3987-syntax >=1.1.0 + - uri-template + - webcolors >=24.6.0 + license: MIT + purls: [] + run_exports: {} + size: 4809 + timestamp: 1787579299493 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-1.1.1-pyhd8ed1ab_1.conda + sha256: b538e15067d05768d1c0532a6d9b0625922a1cce751dd6a2af04f7233a1a70e9 + md5: 9453512288d20847de4356327d0e1282 + depends: + - ipykernel + - ipywidgets + - jupyter_console + - jupyterlab + - nbconvert-core + - notebook + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter?source=hash-mapping + run_exports: {} + size: 8891 + timestamp: 1733818677113 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-builder-1.2.2-pyhcf101f3_0.conda + sha256: 46777dd25ae623f960ec9ed9eae44cc7f96f371f3bc96f85d3ebf95737bd4e88 + md5: 9672806e67ea8b09a61408597a418e20 + depends: + - jupyter_core + - python >=3.10 + - tomli + - traitlets + - python + constrains: + - nodejs >=22 + license: BSD-3-Clause AND MIT AND ISC + purls: + - pkg:pypi/jupyter-builder?source=compressed-mapping + run_exports: {} + size: 862197 + timestamp: 1786385602872 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-cache-1.0.1-pyhff2d567_0.conda + sha256: 054d397dd45ed08bffb0976702e553dfb0d0b0a477da9cff36e2ea702e928f48 + md5: b0ee650829b8974202a7abe7f8b81e5a + depends: + - attrs + - click + - importlib-metadata + - nbclient >=0.2 + - nbformat + - python >=3.9 + - pyyaml + - sqlalchemy >=1.3.12,<3 + - tabulate + license: MIT + license_family: MIT + purls: + - pkg:pypi/jupyter-cache?source=hash-mapping + run_exports: {} + size: 31236 + timestamp: 1731777189586 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-lsp-2.3.1-pyhcf101f3_0.conda + sha256: 3766e2ae59641c172cec8a821528bfa6bf9543ffaaeb8b358bfd5259dcf18e4e + md5: 0c3b465ceee138b9c39279cc02e5c4a0 + depends: + - importlib-metadata >=4.8.3 + - jupyter_server >=1.1.2 + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-lsp?source=hash-mapping + run_exports: {} + size: 61633 + timestamp: 1775136333147 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter-sphinx-0.5.3-pyha770c72_5.conda + sha256: f001c315740c26ffb5591db4cf0131a4de4017804049f8817c11ed3dae18925d + md5: 868d6d1da5fefbd902f6f2a74a02ff7e + depends: + - ipykernel >=4.5.1 + - ipython + - ipywidgets >=7.0.0 + - nbconvert >=5.5 + - nbformat + - python >=3.9 + - sphinx >=7 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-sphinx?source=hash-mapping + run_exports: {} + size: 24112 + timestamp: 1734429364515 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_client-8.9.1-pyhcf101f3_0.conda + sha256: 48b18974cc93b2c0d2681563237034e521f51d1878f0bbc6a5a67ca31b1608a6 + md5: 49440e66df843bee2273937e8032ec43 + depends: + - jupyter_core >=5.1 + - python >=3.10 + - python-dateutil >=2.8.2 + - pyzmq >=25.0 + - tornado >=6.4.1 + - traitlets >=5.3 + - typing_extensions >=4.13.0 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-client?source=hash-mapping + run_exports: {} + size: 117954 + timestamp: 1781019994076 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_console-6.6.3-pyhd8ed1ab_1.conda + sha256: aee0cdd0cb2b9321d28450aec4e0fd43566efcd79e862d70ce49a68bf0539bcd + md5: 801dbf535ec26508fac6d4b24adfb76e + depends: + - ipykernel >=6.14 + - ipython + - jupyter_client >=7.0.0 + - jupyter_core >=4.12,!=5.0.* + - prompt_toolkit >=3.0.30 + - pygments + - python >=3.9 + - pyzmq >=17 + - traitlets >=5.4 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-console?source=hash-mapping + run_exports: {} + size: 26874 + timestamp: 1733818130068 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyh6dadd2b_0.conda + sha256: ed709a6c25b731e01563521ef338b93986cd14b5bc17f35e9382000864872ccc + md5: a8db462b01221e9f5135be466faeb3e0 + depends: + - __win + - pywin32 + - platformdirs >=2.5 + - python >=3.10 + - traitlets >=5.3 + - python + constrains: + - pywin32 >=300 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-core?source=hash-mapping + run_exports: {} + size: 64679 + timestamp: 1760643889625 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_core-5.9.1-pyhc90fa1f_0.conda + sha256: 1d34b80e5bfcd5323f104dbf99a2aafc0e5d823019d626d0dce5d3d356a2a52a + md5: b38fe4e78ee75def7e599843ef4c1ab0 + depends: + - __unix + - python + - platformdirs >=2.5 + - python >=3.10 + - traitlets >=5.3 + - python + constrains: + - pywin32 >=300 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-core?source=hash-mapping + run_exports: {} + size: 65503 + timestamp: 1760643864586 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_events-0.12.1-pyhcf101f3_0.conda + sha256: c7edb5682c6316a95ad781dccb1b6589cd2ec0bf94f23c21152974eb0363b5d7 + md5: bf42ee94c750c0b2e7e998b79ac299ea + depends: + - jsonschema-with-format-nongpl >=4.18.0 + - packaging + - python >=3.10 + - python-json-logger >=2.0.4 + - pyyaml >=5.3 + - referencing + - rfc3339-validator + - rfc3986-validator >=0.1.1 + - traitlets >=5.3 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-events?source=hash-mapping + run_exports: {} + size: 24002 + timestamp: 1776861872237 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server-2.20.0-pyhcf101f3_0.conda + sha256: 3e8759fdc404f149e7e722e0af472044a0ef9e70d0a3a7690ddcfe1232a0e868 + md5: b2ddb0e13b5600070c4019c4db8a78e6 + depends: + - anyio >=3.1.0 + - argon2-cffi >=21.1 + - jinja2 >=3.0.3 + - jupyter_client >=7.4.4 + - jupyter_core >=4.12,!=5.0.* + - jupyter_events >=0.11.0 + - jupyter_server_terminals >=0.4.4 + - nbconvert-core >=6.4.4 + - nbformat >=5.3.0 + - overrides >=5.0 + - packaging >=22.0 + - prometheus_client >=0.9 + - python >=3.10 + - pyzmq >=24 + - send2trash >=1.8.2 + - terminado >=0.8.3 + - tornado >=6.2.0 + - traitlets >=5.6.0 + - websocket-client >=1.7 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-server?source=hash-mapping + run_exports: {} + size: 363068 + timestamp: 1781713810089 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyter_server_terminals-0.5.4-pyhcf101f3_0.conda + sha256: 5eda79ed9f53f590031d29346abd183051263227dd9ee667b5ca1133ce297654 + md5: 7b8bace4943e0dc345fc45938826f2b8 + depends: + - python >=3.10 + - terminado >=0.8.3 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyter-server-terminals?source=hash-mapping + run_exports: {} + size: 22052 + timestamp: 1768574057200 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab-4.6.3-pyhd8ed1ab_0.conda + sha256: dabfff705b000188a9f67c9af68bb607cdb2e46fd7c6283307a502994c2af46f + md5: e5527f195a1a1925b9207e9d47752480 + depends: + - async-lru >=1.0.0 + - httpx >=0.25.0,<1 + - ipykernel >=6.5.0,!=6.30.0 + - jinja2 >=3.0.3 + - jupyter-builder >=1.0.2 + - jupyter-lsp >=2.0.0 + - jupyter_core + - jupyter_server >=2.19.0,<3 + - jupyterlab_server >=2.28.0,<3 + - notebook-shim >=0.2 + - packaging >=23.2 + - python >=3.10 + - tomli >=1.2.2 + - tornado >=6.2.0 + - traitlets + - typing_extensions >=4.4.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyterlab?source=compressed-mapping + run_exports: {} + size: 13178193 + timestamp: 1786398793317 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_pygments-0.3.0-pyhd8ed1ab_2.conda + sha256: dc24b900742fdaf1e077d9a3458fd865711de80bca95fe3c6d46610c532c6ef0 + md5: fd312693df06da3578383232528c468d + depends: + - pygments >=2.4.1,<3 + - python >=3.9 + constrains: + - jupyterlab >=4.0.8,<5.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyterlab-pygments?source=hash-mapping + run_exports: {} + size: 18711 + timestamp: 1733328194037 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_server-2.28.0-pyhcf101f3_0.conda + sha256: 381d2d6a259a3be5f38a69463e0f6c5dcf1844ae113058007b51c3bef13a7cee + md5: a63877cb23de826b1620d3adfccc4014 + depends: + - babel >=2.10 + - jinja2 >=3.0.3 + - json5 >=0.9.0 + - jsonschema >=4.18 + - jupyter_server >=1.21,<3 + - packaging >=21.3 + - python >=3.10 + - requests >=2.31 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyterlab-server?source=hash-mapping + run_exports: {} + size: 51621 + timestamp: 1761145478692 +- conda: https://conda.anaconda.org/conda-forge/noarch/jupyterlab_widgets-3.0.17-pyhcf101f3_0.conda + sha256: 697c4a5b4771e8fbd5a9bbb614899982c7e586eafaf123c210f9d1794436f00a + md5: 1fb3f0d175d6e92f56139fb46a17aa2d + depends: + - python >=3.10 + - python + constrains: + - jupyterlab >=4,<5 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/jupyterlab-widgets?source=compressed-mapping + run_exports: {} + size: 219589 + timestamp: 1787045328396 +- conda: https://conda.anaconda.org/conda-forge/noarch/kernel-headers_linux-64-4.18.0-he073ed8_9.conda + sha256: 41557eeadf641de6aeae49486cef30d02a6912d8da98585d687894afd65b356a + md5: 86d9cba083cd041bfbf242a01a7a1999 + constrains: + - sysroot_linux-64 ==2.28 + license: LGPL-2.0-or-later AND LGPL-2.0-or-later WITH exceptions AND GPL-2.0-or-later + license_family: GPL + purls: [] + run_exports: {} + size: 1278712 + timestamp: 1765578681495 +- conda: https://conda.anaconda.org/conda-forge/noarch/lark-1.3.1-pyhd8ed1ab_0.conda + sha256: 49570840fb15f5df5d4b4464db8ee43a6d643031a2bc70ef52120a52e3809699 + md5: 9b965c999135d43a3d0f7bd7d024e26a + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/lark?source=hash-mapping + run_exports: {} + size: 94312 + timestamp: 1761596921009 +- conda: https://conda.anaconda.org/conda-forge/noarch/latexcodec-2.0.1-pyh9f0ad1d_0.tar.bz2 + sha256: 5210d31c8f2402dd1ad1b3edcf7a53292b9da5de20cd14d9c243dbf9278b1c4f + md5: 8d67904973263afd2985ba56aa2d6bb4 + depends: + - python + - six + license: MIT + license_family: MIT + purls: + - pkg:pypi/latexcodec?source=hash-mapping + run_exports: {} + size: 18212 + timestamp: 1592937373647 +- conda: https://conda.anaconda.org/conda-forge/noarch/libcxx-headers-22.1.8-h707e725_0.conda + sha256: 0b9853ca0d29729488519ab5c61401b1ade22b15841dae8bf299e6980fe1c964 + md5: 2306682595f6d2a5e67fab177427e139 + depends: + - __unix + constrains: + - clangxx >=19 + - gxx_osx-64 >=14 + - libcxx-devel 22.1.8 + - gxx_osx-arm64 >=14 + - gxx_linux-64 >=14 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: {} + size: 1149924 + timestamp: 1781670214284 +- conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_linux-64-16.2.0-he3ce08f_104.conda + sha256: 5365adce4b7564ba3773d037d84070d68ec1269d4b35f3ab92536ee1087a99a5 + md5: f2cf63d33e7be7f7722479fb30d9e332 + depends: + - __unix + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: {} + size: 3095149 + timestamp: 1787618545895 +- conda: https://conda.anaconda.org/conda-forge/noarch/libgcc-devel_win-64-16.1.0-hecf7705_103.conda + sha256: 104d0b85ea2caaa6bb1dd10f0eedf07646655e22d0087994b12251b786788dbc + md5: 054e1d6be93f2803ce5a4e957f8f107a + depends: + - m2-conda-epoch + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: {} + size: 2418775 + timestamp: 1787332962237 +- conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_linux-64-16.2.0-h86e191b_104.conda + sha256: 435877f29650022730300859dec5c0dee162d10536e1b6fd01cf93ca33a71fde + md5: 83cdebeadbdacc2692cb4797d188c77a + depends: + - __unix + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: {} + size: 22447939 + timestamp: 1787618628584 +- conda: https://conda.anaconda.org/conda-forge/noarch/libstdcxx-devel_win-64-16.1.0-hc76ffd0_103.conda + sha256: e6a43af00adc3256fb369c58b8bc7d3b45e6e681365601389a28c65b2152bb84 + md5: 5f33ef8965affac2c15a962911c933dd + depends: + - m2-conda-epoch + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: {} + size: 13130838 + timestamp: 1787332984486 +- conda: https://conda.anaconda.org/conda-forge/noarch/m2w64-sysroot_win-64-12.0.0.r4.gg4f2fc60ca-hd8ed1ab_10.conda + sha256: fb0ffe6b3c25189038c29abbd1fac2522d87fe2775a09e5f5088e5542dc3309b + md5: 9676d2a30fa3ffa4e5350041d0993758 + depends: + - m2-conda-epoch + - mingw-w64-ucrt-x86_64-crt-git 12.0.0.r4.gg4f2fc60ca hd8ed1ab_10 + - mingw-w64-ucrt-x86_64-headers-git 12.0.0.r4.gg4f2fc60ca hd8ed1ab_10 + - mingw-w64-ucrt-x86_64-windows-default-manifest + - mingw-w64-ucrt-x86_64-winpthreads-git 12.0.0.r4.gg4f2fc60ca hd8ed1ab_10 + - ucrt + purls: [] + run_exports: + strong: + - libwinpthread >=12.0.0.r4.gg4f2fc60ca + size: 8421 + timestamp: 1759768559974 +- conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-4.2.0-pyhd8ed1ab_0.conda + sha256: 0c4c35376fe920714390d46e4b8d31c876d65f18e1655899e0763ec25f2a902f + md5: 6d03368f2b2b0a5fb6839df53b2eb5e0 + depends: + - mdurl >=0.1,<1 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/markdown-it-py?source=hash-mapping + run_exports: {} + size: 69017 + timestamp: 1778169663339 +- conda: https://conda.anaconda.org/conda-forge/noarch/matplotlib-inline-0.2.2-pyhd8ed1ab_0.conda + sha256: 35b43d7343f74452307fd018a1cca92b8f68961ff8e2ab6a81ce0a703c9a3764 + md5: 9acc1c385be401d533ff70ef5b50dae6 + depends: + - python >=3.10 + - traitlets + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/matplotlib-inline?source=hash-mapping + run_exports: {} + size: 15725 + timestamp: 1778264403247 +- conda: https://conda.anaconda.org/conda-forge/noarch/mdit-py-plugins-0.6.1-pyhd8ed1ab_0.conda + sha256: 49db23cbfb1c1d414a14d7540195208b994ebd747beba0f15c903f3a0a2dc446 + md5: ad6821df7a98510117db06e9a833281f + depends: + - markdown-it-py >=2.0.0,<5.0.0 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/mdit-py-plugins?source=hash-mapping + run_exports: {} + size: 50460 + timestamp: 1778692223625 +- conda: https://conda.anaconda.org/conda-forge/noarch/mdurl-0.1.2-pyhd8ed1ab_1.conda + sha256: 78c1bbe1723449c52b7a9df1af2ee5f005209f67e40b6e1d3c7619127c43b1c7 + md5: 592132998493b3ff25fd7479396e8351 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/mdurl?source=hash-mapping + run_exports: {} + size: 14465 + timestamp: 1733255681319 +- conda: https://conda.anaconda.org/conda-forge/noarch/mingw-w64-ucrt-x86_64-crt-git-12.0.0.r4.gg4f2fc60ca-hd8ed1ab_10.conda + sha256: de3e42149b498c16bfb485b7729f4ca0fe392be576a2a10ff702d661799b1df3 + md5: 44ffa6d68699ec9321f6d48d75bdc726 + depends: + - m2-conda-epoch + - mingw-w64-ucrt-x86_64-headers-git 12.0.0.r4.gg4f2fc60ca hd8ed1ab_10 + constrains: + - mingw-w64-ucrt-x86_64-winpthreads-git 12.0.0.r4.gg4f2fc60ca.* + license: ZPL-2.1 + purls: [] + run_exports: {} + size: 5663635 + timestamp: 1759768458961 +- conda: https://conda.anaconda.org/conda-forge/noarch/mingw-w64-ucrt-x86_64-headers-git-12.0.0.r4.gg4f2fc60ca-hd8ed1ab_10.conda + sha256: 1add86481f35163215e7076e6f06f22aa9f1f9345a5fff5cb07bc846c13fbec7 + md5: cab7b807024204893ef5bb1860d91408 + depends: + - m2-conda-epoch + constrains: + - mingw-w64-ucrt-x86_64-crt-git 12.0.0.r4.gg4f2fc60ca.* + - mingw-w64-ucrt-x86_64-winpthreads-git 12.0.0.r4.gg4f2fc60ca.* + license: ZPL-2.1 AND LGPL-2.1-or-later + purls: [] + run_exports: {} + size: 7089846 + timestamp: 1759768412123 +- conda: https://conda.anaconda.org/conda-forge/noarch/mingw-w64-ucrt-x86_64-windows-default-manifest-6.4-he206cdd_7.conda + sha256: 5b0df4e0ba8487ffd59f60c34c5dbb9e001ecd2c5d2c66ba88eada40bfa3ecb8 + md5: 1d6b5c96d7e3cce773519d7d1a4482f0 + depends: + - __win + constrains: + - m2w64-sysroot_win-64 >=12.0.0.r0 + license: FSFAP + purls: [] + run_exports: {} + size: 7412 + timestamp: 1717486007140 +- conda: https://conda.anaconda.org/conda-forge/noarch/mingw-w64-ucrt-x86_64-winpthreads-git-12.0.0.r4.gg4f2fc60ca-hd8ed1ab_10.conda + sha256: 828abb111286940473c4c665fc8ab300d28920f5af83b32295e8bf2256a8f342 + md5: ba0eeff6a5c62b83c771bb392e22dbb4 + depends: + - m2-conda-epoch + - mingw-w64-ucrt-x86_64-headers-git 12.0.0.r4.gg4f2fc60ca hd8ed1ab_10 + constrains: + - mingw-w64-ucrt-x86_64-crt-git 12.0.0.r4.gg4f2fc60ca.* + license: MIT AND BSD-3-Clause-Clear + purls: [] + run_exports: {} + size: 123916 + timestamp: 1759768539535 +- conda: https://conda.anaconda.org/conda-forge/noarch/mistune-3.3.4-pyhcf101f3_0.conda + sha256: 306af633cb4ac85d04024d7c243ac2031c488bf5b0912207e7304c27a5d65a85 + md5: 1c0ebec41ef9214160da1ee6a0880fce + depends: + - python >=3.10 + - typing_extensions + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/mistune?source=hash-mapping + run_exports: {} + size: 93811 + timestamp: 1784722859728 +- conda: https://conda.anaconda.org/conda-forge/noarch/munkres-1.1.4-pyhd8ed1ab_1.conda + sha256: d09c47c2cf456de5c09fa66d2c3c5035aa1fa228a1983a433c47b876aa16ce90 + md5: 37293a85a0f4f77bbd9cf7aaefc62609 + depends: + - python >=3.9 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/munkres?source=hash-mapping + run_exports: {} + size: 15851 + timestamp: 1749895533014 +- conda: https://conda.anaconda.org/conda-forge/noarch/myst-nb-1.4.0-pyhcf101f3_0.conda + sha256: c81d0c8c74c3da66808f8da09d8e48f2af2d173d357d45239defaf466838edba + md5: da07c7b1588ad0a44118d28aeb31b6a6 + depends: + - importlib-metadata + - ipykernel + - ipython + - jupyter-cache >=0.5 + - myst-parser >=1.0.0 + - nbclient + - nbformat >=5.0 + - python >=3.10 + - pyyaml + - sphinx >=5 + - typing_extensions + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/myst-nb?source=hash-mapping + run_exports: {} + size: 68766 + timestamp: 1772587444587 +- conda: https://conda.anaconda.org/conda-forge/noarch/myst-parser-5.1.0-pyhd8ed1ab_0.conda + sha256: 94235bc1f769cf35029942ecb2ca796f18e730c1bf5aeef95e72680ebcfacfef + md5: 580615e59fc7c07741e4d2ab052cfc8b + depends: + - docutils >=0.20,<0.23 + - jinja2 + - markdown-it-py >=4.2.0,<4.3.0 + - mdit-py-plugins >=0.6.1,<0.7 + - python >=3.11 + - pyyaml + - sphinx >=8,<10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/myst-parser?source=hash-mapping + run_exports: {} + size: 74888 + timestamp: 1778696564508 +- conda: https://conda.anaconda.org/conda-forge/noarch/nbclient-0.11.0-pyhd8ed1ab_0.conda + sha256: eceb424236fbbb9b337a857fe5448307b57a2a3fb2db389ae37e7a8b8cdca2ab + md5: cf01a81d7960ad9c829bf2e794fcee9a + depends: + - jupyter_client >=7.0.0 + - jupyter_core >=5.4 + - nbformat >=5.2.0 + - python >=3.10 + - traitlets >=5.13 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/nbclient?source=hash-mapping + run_exports: {} + size: 29138 + timestamp: 1780661039538 +- conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-7.17.1-hb502eef_0.conda + sha256: 6b6d96cca8e9dd34e0735b59534831e1f8206b7366c79c71e08da6ee55c50683 + md5: 02669c36935d23e5258c5dd1a5e601ab + depends: + - nbconvert-core ==7.17.1 pyhcf101f3_0 + - nbconvert-pandoc ==7.17.1 h08b4883_0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 5364 + timestamp: 1775615493260 +- conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.17.1-pyhcf101f3_0.conda + sha256: ab2ac79c5892c5434d50b3542d96645bdaa06d025b6e03734be29200de248ac2 + md5: 2bce0d047658a91b99441390b9b27045 + depends: + - beautifulsoup4 + - bleach-with-css !=5.0.0 + - defusedxml + - importlib-metadata >=3.6 + - jinja2 >=3.0 + - jupyter_core >=4.7 + - jupyterlab_pygments + - markupsafe >=2.0 + - mistune >=2.0.3,<4 + - nbclient >=0.5.0 + - nbformat >=5.7 + - packaging + - pandocfilters >=1.4.1 + - pygments >=2.4.1 + - python >=3.10 + - traitlets >=5.1 + - python + constrains: + - pandoc >=2.9.2,<4.0.0 + - nbconvert ==7.17.1 *_0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/nbconvert?source=hash-mapping + run_exports: {} + size: 202229 + timestamp: 1775615493260 +- conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.17.1-h08b4883_0.conda + sha256: b576268b5b3da13b703a15d28d218fd1b552511726253575025930091e6206ae + md5: f635af333701d2ed89d70ba9adb8e2ee + depends: + - nbconvert-core ==7.17.1 pyhcf101f3_0 + - pandoc + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 5840 + timestamp: 1775615493260 +- conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.11.1-pyhcf101f3_0.conda + sha256: d85d76827ff732e1639f50f45e4d7d3387a27abd857b194dcbb5bb4166c2a7c2 + md5: 2fbbf92e1173ae024f0b12759c1e64a1 + depends: + - jsonschema >=2.6 + - jupyter_core >=4.12,!=5.0.* + - python >=3.10 + - python-fastjsonschema >=2.15 + - traitlets >=5.1 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/nbformat?source=compressed-mapping + run_exports: {} + size: 107750 + timestamp: 1787133512599 +- conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio2-1.7.2-pyhcf101f3_0.conda + sha256: e6768ceef038f4d7e083de7e393f5dd7d672b937e2bda570b740f6399b686689 + md5: fcd832bfd4749e9b246112b6894f97fc + depends: + - python >=3.10 + - python + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/nest-asyncio2?source=hash-mapping + run_exports: {} + size: 15903 + timestamp: 1770973502283 +- conda: https://conda.anaconda.org/conda-forge/noarch/notebook-7.6.2-pyhcf101f3_0.conda + sha256: fb2fed7ff3ac2a94225f444984b6316cd2bf9419db0f45057334c57df3c20d0f + md5: ca3a76b7ae886c30f2ef6e0ca851b2ab + depends: + - jupyter_server >=2.19.0,<3 + - jupyter-builder >=1.0.2,<2 + - jupyterlab >=4.6.3,<4.7 + - jupyterlab_server >=2.28.0,<3 + - notebook-shim >=0.2,<0.3 + - python >=3.10 + - tornado >=6.2.0 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/notebook?source=compressed-mapping + run_exports: {} + size: 4631065 + timestamp: 1786455253264 +- conda: https://conda.anaconda.org/conda-forge/noarch/notebook-shim-0.2.4-pyhd8ed1ab_1.conda + sha256: 7b920e46b9f7a2d2aa6434222e5c8d739021dbc5cc75f32d124a8191d86f9056 + md5: e7f89ea5f7ea9401642758ff50a2d9c1 + depends: + - jupyter_server >=1.8,<3 + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/notebook-shim?source=hash-mapping + run_exports: {} + size: 16817 + timestamp: 1733408419340 +- conda: https://conda.anaconda.org/conda-forge/noarch/numpydoc-1.10.0-pyhcf101f3_0.conda + sha256: 482d94fce136c4352b18c6397b9faf0a3149bfb12499ab1ffebad8db0cb6678f + md5: 3aa4b625f20f55cf68e92df5e5bf3c39 + depends: + - python >=3.10 + - sphinx >=6 + - tomli >=1.1.0 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpydoc?source=hash-mapping + run_exports: {} + size: 65801 + timestamp: 1764715638266 +- conda: https://conda.anaconda.org/conda-forge/noarch/overrides-7.7.0-pyhd8ed1ab_1.conda + sha256: 1840bd90d25d4930d60f57b4f38d4e0ae3f5b8db2819638709c36098c6ba770c + md5: e51f1e4089cad105b6cac64bd8166587 + depends: + - python >=3.9 + - typing_utils + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/overrides?source=hash-mapping + run_exports: {} + size: 30139 + timestamp: 1734587755455 +- conda: https://conda.anaconda.org/conda-forge/noarch/packaging-26.3-pyhc364b38_0.conda + sha256: c432626b16768b8dab228bfb706f7060c2d462a21c516d240f68f2f902b5a044 + md5: 936687ed80f295a1f5dbcf8bd34c252c + depends: + - python >=3.9 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/packaging?source=hash-mapping + run_exports: {} + size: 116363 + timestamp: 1785888127370 +- conda: https://conda.anaconda.org/conda-forge/noarch/pandocfilters-1.5.0-pyhd8ed1ab_0.tar.bz2 + sha256: 2bb9ba9857f4774b85900c2562f7e711d08dd48e2add9bee4e1612fbee27e16f + md5: 457c2c8c08e54905d6954e79cb5b5db9 + depends: + - python !=3.0,!=3.1,!=3.2,!=3.3 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pandocfilters?source=hash-mapping + run_exports: {} + size: 11627 + timestamp: 1631603397334 +- conda: https://conda.anaconda.org/conda-forge/noarch/parso-0.8.7-pyhcf101f3_0.conda + sha256: 611882f7944b467281c46644ffde6c5145d1a7730388bcde26e7e86819b0998e + md5: 39894c952938276405a1bd30e4ce2caf + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/parso?source=hash-mapping + run_exports: {} + size: 82472 + timestamp: 1777722955579 +- conda: https://conda.anaconda.org/conda-forge/noarch/pexpect-4.9.0-pyhd8ed1ab_1.conda + sha256: 202af1de83b585d36445dc1fda94266697341994d1a3328fabde4989e1b3d07a + md5: d0d408b1f18883a944376da5cf8101ea + depends: + - ptyprocess >=0.5 + - python >=3.9 + license: ISC + purls: + - pkg:pypi/pexpect?source=hash-mapping + run_exports: {} + size: 53561 + timestamp: 1733302019362 +- conda: https://conda.anaconda.org/conda-forge/noarch/platformdirs-4.11.4-pyhcf101f3_0.conda + sha256: 36596d60d6bc49c4c27f009ab3128c63ea50ac5d67dffc44df05ab3d78bc4669 + md5: 840f27e0254bcdc8382ac2ca32782a22 + depends: + - python >=3.10 + - python + license: MIT + purls: + - pkg:pypi/platformdirs?source=compressed-mapping + run_exports: {} + size: 27321 + timestamp: 1787603822584 +- conda: https://conda.anaconda.org/conda-forge/noarch/prometheus_client-0.26.0-pyhd8ed1ab_0.conda + sha256: 794eec057361b41db1b06a9677eb8632adc0de81f7dcfe113bca8f0b04a23553 + md5: 3aa7e2d85645e61627c98082747dfdfe + depends: + - python >=3.10 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/prometheus-client?source=hash-mapping + run_exports: {} + size: 61554 + timestamp: 1785016068982 +- conda: https://conda.anaconda.org/conda-forge/noarch/prompt-toolkit-3.0.53-pyha770c72_0.conda + sha256: efe8def2c93aa34cd8d3c9af1dc4c7d312791cf769d8b2b615e32733e6df6051 + md5: 39c92a39517316e5001d645ae63d9ab9 + depends: + - python >=3.10 + - wcwidth + constrains: + - prompt_toolkit 3.0.53 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/prompt-toolkit?source=hash-mapping + run_exports: {} + size: 276081 + timestamp: 1785160613307 +- conda: https://conda.anaconda.org/conda-forge/noarch/prompt_toolkit-3.0.53-hd8ed1ab_0.conda + sha256: 59628c765189e99ca5d3c51f0758a325bc020dfafb8fef6068045595aaae1baf + md5: 4c7171dde29a2f2b1dac681c1154a291 + depends: + - prompt-toolkit >=3.0.53,<3.0.54.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 7083 + timestamp: 1785160614678 +- conda: https://conda.anaconda.org/conda-forge/noarch/ptyprocess-0.7.0-pyhd8ed1ab_1.conda + sha256: a7713dfe30faf17508ec359e0bc7e0983f5d94682492469bd462cdaae9c64d83 + md5: 7d9daffbb8d8e0af0f769dbbcd173a54 + depends: + - python >=3.9 + license: ISC + purls: + - pkg:pypi/ptyprocess?source=hash-mapping + run_exports: {} + size: 19457 + timestamp: 1733302371990 +- conda: https://conda.anaconda.org/conda-forge/noarch/pure_eval-0.2.3-pyhd8ed1ab_1.conda + sha256: 71bd24600d14bb171a6321d523486f6a06f855e75e547fa0cb2a0953b02047f0 + md5: 3bfdfb8dbcdc4af1ae3f9a8eb3948f04 + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pure-eval?source=hash-mapping + run_exports: {} + size: 16668 + timestamp: 1733569518868 +- conda: https://conda.anaconda.org/conda-forge/noarch/pybtex-0.26.1-pyhcf101f3_0.conda + sha256: fd9b0a389b21dc2ccb11925db83a5e9e32652e87971f93c23358d51c3632d96d + md5: b0969b1326b5b9d6094dd1831eb48919 + depends: + - python >=3.10 + - pyyaml >=3.1 + - latexcodec >=1.0.4 + - importlib-metadata + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/pybtex?source=hash-mapping + run_exports: {} + size: 78091 + timestamp: 1775231969598 +- conda: https://conda.anaconda.org/conda-forge/noarch/pybtex-docutils-1.0.3-pyhcf101f3_4.conda + sha256: a0397b8fc65eabd773fe33affb726fe9d16c8f0a8ab7c3493d80c412ef2539a6 + md5: 75f19dd4b0b95ce928286e18c561cb13 + depends: + - python >=3.10 + - setuptools + - docutils >=0.14 + - pybtex >=0.16 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/pybtex-docutils?source=hash-mapping + run_exports: {} + size: 14980 + timestamp: 1765317730499 +- conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-3.0-pyhcf101f3_0.conda + sha256: e27e0473fc6723311a0bd48b89b616fa1b996a2f7a2b555338cbbcfb9c640568 + md5: 9c5491066224083c41b6d5635ed7107b + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pycparser?source=hash-mapping + run_exports: {} + size: 55886 + timestamp: 1779293633166 +- conda: https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.20.0-pyhc364b38_0.conda + sha256: 40664599ee4b5667d31b38538a06a9daad97594d4d2af8084e80cfc35ac86d6d + md5: 4539a6224d84b50aea1bcdcc01ee9802 + depends: + - accessible-pygments + - babel + - beautifulsoup4 + - docutils !=0.17.0 + - jinja2 + - pygments >=2.7 + - python >=3.11 + - requests + - sphinx >=8.2 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pydata-sphinx-theme?source=hash-mapping + run_exports: {} + size: 1312216 + timestamp: 1783604241703 +- conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.21.0-pyhcf101f3_0.conda + sha256: f5f015ff1bc3e1b7fc08eee096b1865234189ae0b82bebdb86a5369116e42fa0 + md5: 2882dee445dfa45b0c5afce3ffb7730a + depends: + - python >=3.10 + - python + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/pygments?source=compressed-mapping + run_exports: {} + size: 959376 + timestamp: 1786995678795 +- conda: https://conda.anaconda.org/conda-forge/noarch/pyparsing-3.3.2-pyhcf101f3_0.conda + sha256: 417fba4783e528ee732afa82999300859b065dc59927344b4859c64aae7182de + md5: 3687cc0b82a8b4c17e1f0eb7e47163d5 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyparsing?source=hash-mapping + run_exports: {} + size: 110893 + timestamp: 1769003998136 +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh09c184e_7.conda + sha256: d016e04b0e12063fbee4a2d5fbb9b39a8d191b5a0042f0b8459188aedeabb0ca + md5: e2fd202833c4a981ce8a65974fe4abd1 + depends: + - __win + - python >=3.9 + - win_inet_pton + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pysocks?source=hash-mapping + run_exports: {} + size: 21784 + timestamp: 1733217448189 +- conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha55dd90_7.conda + sha256: ba3b032fa52709ce0d9fd388f63d330a026754587a2f461117cac9ab73d8d0d8 + md5: 461219d1a5bd61342293efa2c0c90eac + depends: + - __unix + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pysocks?source=hash-mapping + run_exports: {} + size: 21085 + timestamp: 1733217331982 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-dateutil-2.9.0.post0-pyhe01879c_2.conda + sha256: d6a17ece93bbd5139e02d2bd7dbfa80bee1a4261dced63f65f679121686bf664 + md5: 5b8d21249ff20967101ffa321cab24e8 + depends: + - python >=3.9 + - six >=1.5 + - python + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/python-dateutil?source=hash-mapping + run_exports: {} + size: 233310 + timestamp: 1751104122689 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-fastjsonschema-2.22.2-pyhcf101f3_0.conda + sha256: fc4a704822df22defce49d0fb811fdc036a1fd3b579aeaa601228e9cfd198b3d + md5: aa75b7f096d17621bc307b3025b29461 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/fastjsonschema?source=compressed-mapping + run_exports: {} + size: 254446 + timestamp: 1786892280524 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-gil-3.14.7-h4df99d1_100.conda + sha256: 05d8d9f928cf1ba1217b3cde7fb41dfe2995feb4e6b4ef5b1a9f19ce44e18f66 + md5: c56da48fd8d5d1feb1829d0241fd338c + depends: + - cpython 3.14.7.* + - python_abi * *_cp314 + license: Python-2.0 + purls: [] + run_exports: {} + size: 50369 + timestamp: 1787153622440 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-json-logger-4.2.0-pyhd8ed1ab_0.conda + sha256: 4f8ecadbd9d282b0208d6e84640573fcb6ab462307d737eedd28341964e18cc6 + md5: e5407c82510aab6a4baa31fcd4249655 + depends: + - python >=3.10 + - typing_extensions + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/python-json-logger?source=compressed-mapping + run_exports: {} + size: 19367 + timestamp: 1786872772907 +- conda: https://conda.anaconda.org/conda-forge/noarch/python-tzdata-2026.3-pyhd8ed1ab_0.conda + sha256: 3f05db78cf8be33cf6dbc469664b8e3a01f3980d61d6d6bef48669b171896d8a + md5: eefc8d916bd2e708d76d40398ef9a1ee + depends: + - python >=3.10 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/tzdata?source=hash-mapping + run_exports: {} + size: 146862 + timestamp: 1783704822814 +- conda: https://conda.anaconda.org/conda-forge/noarch/python_abi-3.14-8_cp314.conda + build_number: 8 + sha256: ad6d2e9ac39751cc0529dd1566a26751a0bf2542adb0c232533d32e176e21db5 + md5: 0539938c55b6b1a59b560e843ad864a4 + constrains: + - python 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 6989 + timestamp: 1752805904792 +- conda: https://conda.anaconda.org/conda-forge/noarch/referencing-0.37.0-pyhcf101f3_0.conda + sha256: 0577eedfb347ff94d0f2fa6c052c502989b028216996b45c7f21236f25864414 + md5: 870293df500ca7e18bedefa5838a22ab + depends: + - attrs >=22.2.0 + - python >=3.10 + - rpds-py >=0.7.0 + - typing_extensions >=4.4.0 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/referencing?source=hash-mapping + run_exports: {} + size: 51788 + timestamp: 1760379115194 +- conda: https://conda.anaconda.org/conda-forge/noarch/requests-2.34.2-pyhcf101f3_0.conda + sha256: 1715246b19c9f85ee022933b4845f2fc14ac9184981b7b7d9b728bec8e9588da + md5: 4a85203c1d80c1059086ae860836ffb9 + depends: + - python >=3.10 + - certifi >=2023.5.7 + - charset-normalizer >=2,<4 + - idna >=2.5,<4 + - urllib3 >=1.26,<3 + - python + constrains: + - chardet >=3.0.2,<8 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/requests?source=hash-mapping + run_exports: {} + size: 68709 + timestamp: 1778851103479 +- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3339-validator-0.1.4-pyhd8ed1ab_1.conda + sha256: 2e4372f600490a6e0b3bac60717278448e323cab1c0fecd5f43f7c56535a99c5 + md5: 36de09a8d3e5d5e6f4ee63af49e59706 + depends: + - python >=3.9 + - six + license: MIT + license_family: MIT + purls: + - pkg:pypi/rfc3339-validator?source=hash-mapping + run_exports: {} + size: 10209 + timestamp: 1733600040800 +- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3986-validator-0.1.1-pyh9f0ad1d_0.tar.bz2 + sha256: 2a5b495a1de0f60f24d8a74578ebc23b24aa53279b1ad583755f223097c41c37 + md5: 912a71cc01012ee38e6b90ddd561e36f + depends: + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/rfc3986-validator?source=hash-mapping + run_exports: {} + size: 7818 + timestamp: 1598024297745 +- conda: https://conda.anaconda.org/conda-forge/noarch/rfc3987-syntax-1.1.0-pyhe01879c_1.conda + sha256: 70001ac24ee62058557783d9c5a7bbcfd97bd4911ef5440e3f7a576f9e43bc92 + md5: 7234f99325263a5af6d4cd195035e8f2 + depends: + - python >=3.9 + - lark >=1.2.2 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/rfc3987-syntax?source=hash-mapping + run_exports: {} + size: 22913 + timestamp: 1752876729969 +- conda: https://conda.anaconda.org/conda-forge/noarch/roman-numerals-4.1.0-pyhd8ed1ab_0.conda + sha256: 30f3c04fcfb64c44d821d392a4a0b8915650dbd900c8befc20ade8fde8ec6aa2 + md5: 0dc48b4b570931adc8641e55c6c17fe4 + depends: + - python >=3.10 + license: 0BSD OR CC0-1.0 + purls: + - pkg:pypi/roman-numerals?source=hash-mapping + run_exports: {} + size: 13814 + timestamp: 1766003022813 +- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh5552912_1.conda + sha256: 8fc024bf1a7b99fc833b131ceef4bef8c235ad61ecb95a71a6108be2ccda63e8 + md5: b70e2d44e6aa2beb69ba64206a16e4c6 + depends: + - __osx + - pyobjc-framework-cocoa + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/send2trash?source=hash-mapping + run_exports: {} + size: 22519 + timestamp: 1770937603551 +- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyh6dadd2b_1.conda + sha256: 305446a0b018f285351300463653d3d3457687270e20eda37417b12ee386ef76 + md5: 6ac53f3fff2c416d63511843a04646fa + depends: + - __win + - pywin32 + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/send2trash?source=hash-mapping + run_exports: {} + size: 22864 + timestamp: 1770937641143 +- conda: https://conda.anaconda.org/conda-forge/noarch/send2trash-2.1.0-pyha191276_1.conda + sha256: 59656f6b2db07229351dfb3a859c35e57cc8e8bcbc86d4e501bff881a6f771f1 + md5: 28eb91468df04f655a57bcfbb35fc5c5 + depends: + - __linux + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/send2trash?source=hash-mapping + run_exports: {} + size: 24108 + timestamp: 1770937597662 +- conda: https://conda.anaconda.org/conda-forge/noarch/setuptools-84.0.0-pyh332efcf_0.conda + sha256: 9e200ee5f9ff19a4d94e4b51c4856d53dec849f91032f345cf0c6bc3d51a7183 + md5: 62ac906f1cd582c6c264c95625cb9d6f + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/setuptools?source=compressed-mapping + run_exports: {} + size: 524488 + timestamp: 1786282924579 +- conda: https://conda.anaconda.org/conda-forge/noarch/six-1.17.0-pyhe01879c_1.conda + sha256: 458227f759d5e3fcec5d9b7acce54e10c9e1f4f4b7ec978f3bfd54ce4ee9853d + md5: 3339e3b65d58accf4ca4fb8748ab16b3 + depends: + - python >=3.9 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/six?source=hash-mapping + run_exports: {} + size: 18455 + timestamp: 1753199211006 +- conda: https://conda.anaconda.org/conda-forge/noarch/sniffio-1.3.1-pyhd8ed1ab_2.conda + sha256: dce518f45e24cd03f401cb0616917773159a210c19d601c5f2d4e0e5879d30ad + md5: 03fe290994c5e4ec17293cfb6bdce520 + depends: + - python >=3.10 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/sniffio?source=hash-mapping + run_exports: {} + size: 15698 + timestamp: 1762941572482 +- conda: https://conda.anaconda.org/conda-forge/noarch/snowballstemmer-3.1.1-pyhd8ed1ab_0.conda + sha256: ad89284ea94821c20ff87e64b948e4afc690cf5202d14c009355b0594cf23aea + md5: 46b6abe31482f6bca064b965696ae807 + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/snowballstemmer?source=hash-mapping + run_exports: {} + size: 74456 + timestamp: 1780468201547 +- conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.9.2-pyhd8ed1ab_0.conda + sha256: 056d7a2e91e303a9ee37e580f6dde0511fc3fb72476581cc337aacf2cc747613 + md5: ba33e6c8a46ee373fdf6dd8665212778 + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/soupsieve?source=compressed-mapping + run_exports: {} + size: 39439 + timestamp: 1786202135509 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-9.1.0-pyhd8ed1ab_0.conda + sha256: 035ca4b17afca3d53650380dd94c564555b7ec2b4f8818111f98c15c7a991b7b + md5: aabfbc2813712b71ba8beb217a978498 + depends: + - alabaster >=0.7.14 + - babel >=2.13 + - colorama >=0.4.6 + - docutils >=0.21,<0.23 + - imagesize >=1.3 + - jinja2 >=3.1 + - packaging >=23.0 + - pygments >=2.17 + - python >=3.12 + - requests >=2.30.0 + - roman-numerals >=1.0.0 + - snowballstemmer >=2.2 + - sphinxcontrib-applehelp >=1.0.7 + - sphinxcontrib-devhelp >=1.0.6 + - sphinxcontrib-htmlhelp >=2.0.6 + - sphinxcontrib-jsmath >=1.0.1 + - sphinxcontrib-qthelp >=1.0.6 + - sphinxcontrib-serializinghtml >=1.1.9 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/sphinx?source=hash-mapping + run_exports: {} + size: 1584836 + timestamp: 1767271941650 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-autobuild-2025.8.25-pyhcf101f3_0.conda + sha256: ad56a36c575f4ccec429e070dd36538cb6cb25f8d8b174a94bb9622858d9e4a4 + md5: 26d9d9a48ff32bca94581d7c91684ab8 + depends: + - colorama >=0.4.6 + - python >=3.11 + - sphinx + - starlette >=0.35 + - uvicorn >=0.25 + - watchfiles >=0.20 + - websockets >=11 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/sphinx-autobuild?source=hash-mapping + run_exports: {} + size: 19892 + timestamp: 1762270046787 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-codeautolink-0.19.0-pyhd8ed1ab_0.conda + sha256: cf0c20aee37db6a52db1be576bff550aea8eda8190e44b45a6aae2e2ae86b01f + md5: 1ee2b08263a77c64f14c2e1a13a99715 + depends: + - beautifulsoup4 >=4.8.1 + - python >=3.10 + - sphinx >=3.2.0 + constrains: + - ipython !=8.7.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/sphinx-codeautolink?source=hash-mapping + run_exports: {} + size: 51470 + timestamp: 1785419092000 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.2-pyhd8ed1ab_1.conda + sha256: 8cd892e49cb4d00501bc4439fb0c73ca44905f01a65b2b7fa05ba0e8f3924f19 + md5: bf22cb9c439572760316ce0748af3713 + depends: + - python >=3.9 + - sphinx >=1.8 + license: MIT + license_family: MIT + purls: + - pkg:pypi/sphinx-copybutton?source=hash-mapping + run_exports: {} + size: 17893 + timestamp: 1734573117732 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-design-0.7.0-pyhd8ed1ab_0.conda + sha256: 7f8437a97e6311bebf230cfd2ae3c5bdb2230e681c41daebdb894280bf8b4ab6 + md5: 28eddfb8b9ecdd044a6f609f985398a7 + depends: + - python >=3.11 + - sphinx >=7,<10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/sphinx-design?source=hash-mapping + run_exports: {} + size: 931118 + timestamp: 1769032711360 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-last-updated-by-git-0.3.8-pyhe01879c_0.conda + sha256: f761670b793dcc10a4a2d855de163d9dfd4016636ef093fb3e3d83ac25ed6e97 + md5: 405a232fb900fc631d2f1b5cdf01dea9 + depends: + - python >=3.9 + - sphinx >=1.8 + - python + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/sphinx-last-updated-by-git?source=hash-mapping + run_exports: {} + size: 17546 + timestamp: 1750694360605 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-notfound-page-1.1.0-pyhd8ed1ab_0.conda + sha256: 227e87c72ff1e20833009f6ee6f9c1067534a92a14d8484619953d50f3c90f13 + md5: 08f13b15791e1b82ad8ad7d5b373ab54 + depends: + - docutils + - python >=3.9 + - sphinx + license: MIT + license_family: MIT + purls: + - pkg:pypi/sphinx-notfound-page?source=hash-mapping + run_exports: {} + size: 14204 + timestamp: 1749276617752 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-sitemap-2.9.0-pyhcf101f3_0.conda + sha256: 1be6289124207256df5dfbfe6ff0a652e313ac5c3e50560c9e510afa76eb702b + md5: 3baeff262222dc87e978a68702bc5797 + depends: + - python >=3.10 + - sphinx-last-updated-by-git + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/sphinx-sitemap?source=hash-mapping + run_exports: {} + size: 13441 + timestamp: 1759753011102 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_1.conda + sha256: d7433a344a9ad32a680b881c81b0034bc61618d12c39dd6e3309abeffa9577ba + md5: 16e3f039c0aa6446513e94ab18a8784b + depends: + - python >=3.9 + - sphinx >=5 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/sphinxcontrib-applehelp?source=hash-mapping + run_exports: {} + size: 29752 + timestamp: 1733754216334 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-bibtex-2.7.0-pyhd8ed1ab_0.conda + sha256: 3b9c6273f95302df966a48a267b33ddf78ed6e662c7d578d3e129e259e049314 + md5: 7f562e153eedbdc92f1c3adb019a8792 + depends: + - docutils >=0.20 + - pybtex >=0.25 + - pybtex-docutils >=1.0.2 + - python >=3.10 + - sphinx >=7.4 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/sphinxcontrib-bibtex?source=hash-mapping + run_exports: {} + size: 33489 + timestamp: 1778364472446 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-devhelp-2.0.0-pyhd8ed1ab_1.conda + sha256: 55d5076005d20b84b20bee7844e686b7e60eb9f683af04492e598a622b12d53d + md5: 910f28a05c178feba832f842155cbfff + depends: + - python >=3.9 + - sphinx >=5 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/sphinxcontrib-devhelp?source=hash-mapping + run_exports: {} + size: 24536 + timestamp: 1733754232002 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-htmlhelp-2.1.0-pyhd8ed1ab_1.conda + sha256: c1492c0262ccf16694bdcd3bb62aa4627878ea8782d5cd3876614ffeb62b3996 + md5: e9fb3fe8a5b758b4aff187d434f94f03 + depends: + - python >=3.9 + - sphinx >=5 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/sphinxcontrib-htmlhelp?source=hash-mapping + run_exports: {} + size: 32895 + timestamp: 1733754385092 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-jsmath-1.0.1-pyhd8ed1ab_1.conda + sha256: 578bef5ec630e5b2b8810d898bbbf79b9ae66d49b7938bcc3efc364e679f2a62 + md5: fa839b5ff59e192f411ccc7dae6588bb + depends: + - python >=3.9 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/sphinxcontrib-jsmath?source=hash-mapping + run_exports: {} + size: 10462 + timestamp: 1733753857224 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-qthelp-2.0.0-pyhd8ed1ab_1.conda + sha256: c664fefae4acdb5fae973bdde25836faf451f41d04342b64a358f9a7753c92ca + md5: 00534ebcc0375929b45c3039b5ba7636 + depends: + - python >=3.9 + - sphinx >=5 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/sphinxcontrib-qthelp?source=hash-mapping + run_exports: {} + size: 26959 + timestamp: 1733753505008 +- conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-serializinghtml-2.0.0-pyhd8ed1ab_0.conda + sha256: 20b49741065fd7d3fabf98caf6d19b6436badb06b6d41f66b58f1fc2b52f37a1 + md5: f77df1fcf9af03b7287342638befca77 + depends: + - python >=3.10 + - sphinx >=5 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/sphinxcontrib-serializinghtml?source=hash-mapping + run_exports: {} + size: 30640 + timestamp: 1781260357443 +- conda: https://conda.anaconda.org/conda-forge/noarch/stack_data-0.6.3-pyhd8ed1ab_1.conda + sha256: 570da295d421661af487f1595045760526964f41471021056e993e73089e9c41 + md5: b1b505328da7a6b246787df4b5a49fbc + depends: + - asttokens + - executing + - pure_eval + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/stack-data?source=hash-mapping + run_exports: {} + size: 26988 + timestamp: 1733569565672 +- conda: https://conda.anaconda.org/conda-forge/noarch/starlette-1.6.0-pyhcf101f3_0.conda + sha256: 6074c6d74e86b156a61ff37f2c3f7a29bd188f05d5f490d88db96131eb945e95 + md5: 7b7d3f2bcca2be53635bd4f1166b04a7 + depends: + - anyio >=3.6.2,<5 + - python >=3.10 + - typing_extensions >=4.10.0 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/starlette?source=compressed-mapping + run_exports: {} + size: 65974 + timestamp: 1786315934415 +- conda: https://conda.anaconda.org/conda-forge/noarch/sysroot_linux-64-2.28-h4ee821c_9.conda + sha256: c47299fe37aebb0fcf674b3be588e67e4afb86225be4b0d452c7eb75c086b851 + md5: 13dc3adbc692664cd3beabd216434749 + depends: + - __glibc >=2.28 + - kernel-headers_linux-64 4.18.0 he073ed8_9 + - tzdata + license: LGPL-2.0-or-later AND LGPL-2.0-or-later WITH exceptions AND GPL-2.0-or-later + license_family: GPL + purls: [] + run_exports: + strong: + - __glibc >=2.28,<3.0.a0 + size: 24008591 + timestamp: 1765578833462 +- conda: https://conda.anaconda.org/conda-forge/noarch/tabulate-0.10.0-pyhcf101f3_0.conda + sha256: 3f661e98a09f976775a494488beb3d35ebb00f535b169c6bd891f2e280d55783 + md5: 3b887b7b3468b0f494b4fad40178b043 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/tabulate?source=hash-mapping + run_exports: {} + size: 43964 + timestamp: 1772732795746 +- conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyh6dadd2b_1.conda + sha256: b375e8df0d5710717c31e7c8e93c025c37fa3504aea325c7a55509f64e5d4340 + md5: e43ca10d61e55d0a8ec5d8c62474ec9e + depends: + - __win + - pywinpty >=1.1.0 + - python >=3.10 + - tornado >=6.1.0 + - python + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/terminado?source=hash-mapping + run_exports: {} + size: 23665 + timestamp: 1766513806974 +- conda: https://conda.anaconda.org/conda-forge/noarch/terminado-0.18.1-pyhc90fa1f_1.conda + sha256: 6b6727a13d1ca6a23de5e6686500d0669081a117736a87c8abf444d60c1e40eb + md5: 17b43cee5cc84969529d5d0b0309b2cb + depends: + - __unix + - ptyprocess + - python >=3.10 + - tornado >=6.1.0 + - python + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/terminado?source=hash-mapping + run_exports: {} + size: 24749 + timestamp: 1766513766867 +- conda: https://conda.anaconda.org/conda-forge/noarch/tinycss2-1.4.0-pyhd8ed1ab_0.conda + sha256: cad582d6f978276522f84bd209a5ddac824742fe2d452af6acf900f8650a73a2 + md5: f1acf5fdefa8300de697982bcb1761c9 + depends: + - python >=3.5 + - webencodings >=0.4 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/tinycss2?source=hash-mapping + run_exports: {} + size: 28285 + timestamp: 1729802975370 +- conda: https://conda.anaconda.org/conda-forge/noarch/tomli-2.4.1-pyhcf101f3_0.conda + sha256: 91cafdb64268e43e0e10d30bd1bef5af392e69f00edd34dfaf909f69ab2da6bd + md5: b5325cf06a000c5b14970462ff5e4d58 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/tomli?source=hash-mapping + run_exports: {} + size: 21561 + timestamp: 1774492402955 +- conda: https://conda.anaconda.org/conda-forge/noarch/traitlets-5.16.1-pyhcf101f3_0.conda + sha256: 03dba5917f944c6684ab44c81daacac1624cd148e4b2cae215dcec594a210c48 + md5: a79bf97561232a31447b6246c2153ab5 + depends: + - python >=3.10 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/traitlets?source=hash-mapping + run_exports: {} + size: 116935 + timestamp: 1785761789772 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing-extensions-4.16.0-h69aa097_0.conda + sha256: b141933ece3518f6d7b75dfb59451e2f26b405a44c18e2518a83e9a02e09315c + md5: c680b5747e8c4c8f23dca0bb7042a8fc + depends: + - typing_extensions ==4.16.0 pyhcf101f3_0 + license: PSF-2.0 + license_family: PSF + purls: [] + run_exports: {} + size: 94080 + timestamp: 1783002732887 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_extensions-4.16.0-pyhcf101f3_0.conda + sha256: 2d888f90af0686044882c74193ec80a90ec1943145d94a7b1b048958acda1848 + md5: c70ad746c22219b9700931707482992c + depends: + - python >=3.10 + - python + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/typing-extensions?source=hash-mapping + run_exports: {} + size: 52631 + timestamp: 1783002732887 +- conda: https://conda.anaconda.org/conda-forge/noarch/typing_utils-0.1.0-pyhd8ed1ab_1.conda + sha256: 3088d5d873411a56bf988eee774559335749aed6f6c28e07bf933256afb9eb6c + md5: f6d7aa696c67756a650e91e15e88223c + depends: + - python >=3.9 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/typing-utils?source=hash-mapping + run_exports: {} + size: 15183 + timestamp: 1733331395943 +- conda: https://conda.anaconda.org/conda-forge/noarch/tzdata-2026c-h151e31d_0.conda + sha256: b928c30ddcb0e3f544c6eade8352737e6e610e263276b90232db6a578ef899d8 + md5: fcb489df604d100968b737f2cb6076c6 + license: LicenseRef-Public-Domain + purls: [] + run_exports: {} + size: 118849 + timestamp: 1784250406640 +- conda: https://conda.anaconda.org/conda-forge/noarch/uri-template-1.3.0-pyhd8ed1ab_1.conda + sha256: e0eb6c8daf892b3056f08416a96d68b0a358b7c46b99c8a50481b22631a4dfc0 + md5: e7cb0f5745e4c5035a460248334af7eb + depends: + - python >=3.9 + license: MIT + license_family: MIT + purls: + - pkg:pypi/uri-template?source=hash-mapping + run_exports: {} + size: 23990 + timestamp: 1733323714454 +- conda: https://conda.anaconda.org/conda-forge/noarch/urllib3-2.7.0-pyhd8ed1ab_0.conda + sha256: feff959a816f7988a0893201aa9727bbb7ee1e9cec2c4f0428269b489eb93fb4 + md5: cbb88288f74dbe6ada1c6c7d0a97223e + depends: + - backports.zstd >=1.0.0 + - brotli-python >=1.2.0 + - h2 >=4,<5 + - pysocks >=1.5.6,<2.0,!=1.5.7 + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/urllib3?source=hash-mapping + run_exports: {} + size: 103560 + timestamp: 1778188657149 +- conda: https://conda.anaconda.org/conda-forge/noarch/uvicorn-0.52.4-pyh6dadd2b_0.conda + sha256: a30e60cfe04ce3e56aa87f06b61f28a893f3c1de3aacd83f45b7fe41eb684519 + md5: ebcd387e448496528a8f7af3e661861f + depends: + - __win + - click >=7.0 + - h11 >=0.8 + - python >=3.10 + - typing_extensions >=4.0 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/uvicorn?source=hash-mapping + run_exports: {} + size: 59109 + timestamp: 1787133011554 +- conda: https://conda.anaconda.org/conda-forge/noarch/uvicorn-0.52.4-pyhc90fa1f_0.conda + sha256: b74765ebebe0da43b9e0ec1d9bedc881125304ffbebd86018dcb6d7a4e2d2ee4 + md5: 2bf4d228f4df09d3e2ebee46f3a9129e + depends: + - __unix + - click >=7.0 + - h11 >=0.8 + - python >=3.10 + - typing_extensions >=4.0 + - python + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/uvicorn?source=compressed-mapping + run_exports: {} + size: 60189 + timestamp: 1787133003107 +- conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.8.2-pyhd8ed1ab_0.conda + sha256: 4acf845da404e84cef1acccc66cc0156af1b83a5b5d7077b2ca19b705c561e57 + md5: 99f7755ec8648a042b0dbe906234f888 + depends: + - python >=3.10 + license: MIT + license_family: MIT + purls: + - pkg:pypi/wcwidth?source=hash-mapping + run_exports: {} + size: 132415 + timestamp: 1782771807703 +- conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-25.10.0-pyhd8ed1ab_0.conda + sha256: 21f6c8a20fe050d09bfda3fb0a9c3493936ce7d6e1b3b5f8b01319ee46d6c6f6 + md5: 6639b6b0d8b5a284f027a2003669aa65 + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/webcolors?source=hash-mapping + run_exports: {} + size: 18987 + timestamp: 1761899393153 +- conda: https://conda.anaconda.org/conda-forge/noarch/webencodings-0.5.1-pyhd8ed1ab_3.conda + sha256: 19ff205e138bb056a46f9e3839935a2e60bd1cf01c8241a5e172a422fed4f9c6 + md5: 2841eb5bfc75ce15e9a0054b98dcd64d + depends: + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/webencodings?source=hash-mapping + run_exports: {} + size: 15496 + timestamp: 1733236131358 +- conda: https://conda.anaconda.org/conda-forge/noarch/websocket-client-1.9.0-pyhd8ed1ab_0.conda + sha256: 42a2b61e393e61cdf75ced1f5f324a64af25f347d16c60b14117393a98656397 + md5: 2f1ed718fcd829c184a6d4f0f2e07409 + depends: + - python >=3.10 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/websocket-client?source=hash-mapping + run_exports: {} + size: 61391 + timestamp: 1759928175142 +- conda: https://conda.anaconda.org/conda-forge/noarch/widgetsnbextension-4.0.16-pyhd8ed1ab_0.conda + sha256: bd909d9845ff269f5487a83654d6b8fe220c73148c47d602f4ee9e1283829212 + md5: 4e3aff9f40afb392477584e6a1a45b6f + depends: + - python >=3.10 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/widgetsnbextension?source=compressed-mapping + run_exports: {} + size: 901988 + timestamp: 1787044664327 +- conda: https://conda.anaconda.org/conda-forge/noarch/win_inet_pton-1.1.0-pyh7428d3b_8.conda + sha256: 93807369ab91f230cf9e6e2a237eaa812492fe00face5b38068735858fba954f + md5: 46e441ba871f524e2b067929da3051c2 + depends: + - __win + - python >=3.9 + license: LicenseRef-Public-Domain + purls: + - pkg:pypi/win-inet-pton?source=hash-mapping + run_exports: {} + size: 9555 + timestamp: 1733130678956 +- conda: https://conda.anaconda.org/conda-forge/noarch/zipp-4.1.0-pyhcf101f3_0.conda + sha256: 210bd31c22bb88f5e2a167df24c95bb5f152b2ada7502f9b8c49d1f5366db423 + md5: ba3dcdc8584155c97c648ae9c044b7a3 + depends: + - python >=3.10 + - python + license: MIT + license_family: MIT + purls: + - pkg:pypi/zipp?source=hash-mapping + run_exports: {} + size: 24190 + timestamp: 1779159948016 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/_openmp_mutex-4.5-7_kmp_llvm.conda + build_number: 7 + sha256: 7acaa2e0782cad032bdaf756b536874346ac1375745fb250e9bdd6a48a7ab3cd + md5: a44032f282e7d2acdeb1c240308052dd + depends: + - llvm-openmp >=9.0.1 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - _openmp_mutex >=4.5 + size: 8325 + timestamp: 1764092507920 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/argon2-cffi-bindings-26.1.0-py314h61c6340_0.conda + sha256: ca954d29945fc070549656374d2d093370513c7b5b03ed6dd90e3e28040a5ae6 + md5: f851200bba7198af60f999578267cdf4 + depends: + - __osx >=11.0 + - cffi >=2.0.0b1 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi-bindings?source=hash-mapping + run_exports: {} + size: 32051 + timestamp: 1787248673403 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/blas-2.309-accelerate.conda + build_number: 9 + sha256: 5672e4772e459d6bd1a0138e2146ae5c52dfbdeb53ad7b881af2ca31b80f4250 + md5: 9871fe8f2c28448c83dff3b0f682b2f5 + depends: + - blas-devel 3.11.0 9*_accelerate + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 18398 + timestamp: 1786058878302 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/blas-devel-3.11.0-9_h55bc449_accelerate.conda + build_number: 9 + sha256: cffd54200d9a33820d123b1f29c7b243ff29c47473833a59860ba2c984ecc1aa + md5: 0fe2c92600d1483811316c2090233161 + depends: + - libblas 3.11.0 9_h3d1d584_accelerate + - libcblas 3.11.0 9_h752f6bc_accelerate + - liblapack 3.11.0 9_hcb0d94e_accelerate + - liblapacke 3.11.0 9_hbdd07e9_accelerate + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 17703 + timestamp: 1786058859766 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-1.2.0-hf00d406_3.conda + sha256: 7050e1b11876219bd0df375ef3d83fd00f94fb4e13c1ffb21dede0c506893079 + md5: aa7df8174ee3b34a5ad8a3160429db68 + depends: + - __osx >=11.0 + - brotli-bin 1.2.0 he4a93e4_3 + - libbrotlidec 1.2.0 h5295a6a_3 + - libbrotlienc 1.2.0 h2ddc9cb_3 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + size: 20767 + timestamp: 1786622887445 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-bin-1.2.0-he4a93e4_3.conda + sha256: 1e92cea587c2eaab169e45dae01c149b9889a8249eda296b8b6db39aee708531 + md5: d9de6c0ad7e008afdf62fb2ffd450b4a + depends: + - __osx >=11.0 + - libbrotlidec 1.2.0 h5295a6a_3 + - libbrotlienc 1.2.0 h2ddc9cb_3 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 18962 + timestamp: 1786622878369 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/brotli-python-1.2.0-py314hee34562_3.conda + sha256: 3cdfc0a96de4717fc309e39133e2a192f4e1df96680577e1d48804021d1726eb + md5: d3a28add84f2412a562355f89ef5e0f5 + depends: + - __osx >=11.0 + - libcxx >=21 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + constrains: + - libbrotlicommon 1.2.0 h1dcdb26_3 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=compressed-mapping + run_exports: {} + size: 365272 + timestamp: 1786623009364 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/bzip2-1.0.8-h4e30115_10.conda + sha256: 8ec22f0ba25cbfc2e64d70cf29459eccd7ffdf6436f6a6ff15bbfef799f7d4f6 + md5: b50612e7d190b8061ab4e7dc119cf4d5 + depends: + - __osx >=11.0 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + run_exports: + weak: + - bzip2 >=1.0.8,<2.0a0 + size: 124965 + timestamp: 1785906749812 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cairo-1.18.4-he0f2337_1.conda + sha256: cde9b79ee206fe3ba6ca2dc5906593fb7a1350515f85b2a1135a4ce8ec1539e3 + md5: 36200ecfbbfbcb82063c87725434161f + depends: + - __osx >=11.0 + - fontconfig >=2.15.0,<3.0a0 + - fonts-conda-ecosystem + - icu >=78.1,<79.0a0 + - libcxx >=19 + - libexpat >=2.7.3,<3.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libglib >=2.86.3,<3.0a0 + - libpng >=1.6.53,<1.7.0a0 + - libzlib >=1.3.1,<2.0a0 + - pixman >=0.46.4,<1.0a0 + license: LGPL-2.1-only or MPL-1.1 + purls: [] + run_exports: + weak: + - cairo >=1.18.4,<2.0a0 + size: 900035 + timestamp: 1766416416791 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cctools-1030.6.3-llvm22_1_hbe26303_5.conda + sha256: c2431becc11a32e157a1c22c44f368773c009e298ba0127c89e614c8d3493a36 + md5: ee35bbae0edb9cc3b69445b0e8f90773 + depends: + - cctools_impl_osx-arm64 1030.6.3 llvm22_1_h7bf7afb_5 + - ld64 956.6 llvm22_1_h5b97f1b_5 + - libllvm22 >=22.1.8,<22.2.0a0 + license: APSL-2.0 + license_family: Other + purls: [] + run_exports: {} + size: 24351 + timestamp: 1785270934554 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cctools_impl_osx-arm64-1030.6.3-llvm22_1_h7bf7afb_5.conda + sha256: 4e973a6236849d4f52b4463609654980ce60e6cd3cad71de9480f63ff9767548 + md5: 65296f920674a115310cc3f3ce1bc8fc + depends: + - __osx >=11.0 + - ld64_osx-arm64 >=956.6,<956.7.0a0 + - libcxx + - libllvm22 >=22.1.8,<22.2.0a0 + - libzlib >=1.3.2,<2.0a0 + - llvm-tools 22.1.* + - sigtool-codesign + constrains: + - ld64 956.6.* + - cctools 1030.6.3.* + - clang 22.1.* + license: APSL-2.0 + license_family: Other + purls: [] + run_exports: {} + size: 752126 + timestamp: 1785270918177 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/cffi-2.1.1-py314h618e29d_2.conda + sha256: 77b0bb0f3fd2d28b6bbd216d370d98320835b8e1df4c86a478a1d0391d1913fe + md5: 1572fc59fc2b1461f47b0d6907acef30 + depends: + - __osx >=11.0 + - libffi >=3.7.0,<3.8.0a0 + - pycparser + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=hash-mapping + run_exports: {} + size: 292307 + timestamp: 1786775143512 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang-22-22.1.8-default_h54a73ef_9.conda + sha256: 9a6cec88635856d7f62ca615c95e37f4674d8f0bf4ef8e7c39ffcf513aa1f76c + md5: 46a43a8b768f4abd45995a0ec92581f3 + depends: + - compiler-rt22 ==22.1.8 + - libclang-cpp22.1 ==22.1.8 default_h79071a4_9 + - libcxx >=22.1.8 + - __osx >=11.0 + - zstd >=1.5.7,<1.6.0a0 + - libxml2 + - libxml2-16 >=2.15.3 + - libzlib >=1.3.2,<2.0a0 + - libllvm22 >=22.1.8,<22.2.0a0 + - libclang-cpp22.1 >=22.1.8,<22.2.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: {} + size: 924796 + timestamp: 1787349779831 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang-22.1.8-default_cfg_ha85103e_9.conda + sha256: 16f156f01b08812632a7cbab32b89744fc67049cd8a304c5c18b7e33137c55ce + md5: a43e75e98ff89a12eb00c4ff8c129708 + depends: + - clang-22 ==22.1.8 default_* + - llvm-openmp >=22.1.8 + - clang_impl_osx-arm64 ==22.1.8 default_h79071a4_9 + - cctools + - ld64 + - ld64_osx-arm64 * llvm22_1_* + - llvm-tools ==22.1.8 + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: {} + size: 32197 + timestamp: 1787349779831 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang-scan-deps-22.1.8-default_h79071a4_9.conda + sha256: bbb3e4e7e09b43880b436b0d000612cf3ab4e9f6344a2e9e3cc77e734b2fa62c + md5: d61a52eada41177a1734e2fae4715878 + depends: + - libclang13 >=22.1.8 + - libclang-cpp22.1 >=22.1.8,<22.2.0a0 + - libcxx >=22.1.8 + - __osx >=11.0 + - zstd >=1.5.7,<1.6.0a0 + - libxml2 + - libxml2-16 >=2.15.3 + - libzlib >=1.3.2,<2.0a0 + - libllvm22 >=22.1.8,<22.2.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: {} + size: 116207 + timestamp: 1787349779831 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/clang_impl_osx-arm64-22.1.8-default_h79071a4_9.conda + sha256: 36ac0e4d0430832b68f36e255fe53c9d07180d0cf756d14a600a7c377c007e79 + md5: 2aa1656f46395a10bb9e666b7398a368 + depends: + - clang-22 ==22.1.8 default_* + - compiler-rt_osx-arm64 ==22.1.8 + - compiler-rt ==22.1.8 + - cctools_impl_osx-arm64 + - ld64_osx-arm64 * llvm22_1_* + - __osx >=11.0 + - zstd >=1.5.7,<1.6.0a0 + - libxml2 + - libxml2-16 >=2.15.3 + - libzlib >=1.3.2,<2.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: {} + size: 31777 + timestamp: 1787349779831 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/clangxx-22.1.8-default_cfg_h9e28d6e_9.conda + sha256: 02ed1159d3cf1a025003fc2db6a87c4a56022e47137ee9f2d6c35d7868ba7d68 + md5: 8ddfb874a270db5195d80ed3518b09af + depends: + - clang ==22.1.8 default_cfg_ha85103e_9 + - clangxx_impl_osx-arm64 ==22.1.8 default_* + - libcxx-devel 22.1.* + - __osx >=11.0 + - zstd >=1.5.7,<1.6.0a0 + - libxml2 + - libxml2-16 >=2.15.3 + - libzlib >=1.3.2,<2.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: {} + size: 32255 + timestamp: 1787349779831 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/clangxx_impl_osx-arm64-22.1.8-default_h350d358_9.conda + sha256: 8a4e922fd277e36b7392fe9cbfb7ac3ae9e4743f6f75581421e83088cb7f6b71 + md5: cf27f388b4cac48c7c9a79434975ba82 + depends: + - clang-22 ==22.1.8 default_* + - clang_impl_osx-arm64 ==22.1.8 default_h79071a4_9 + - clang-scan-deps ==22.1.8 default_h79071a4_9 + - libcxx-devel 22.1.* + - __osx >=11.0 + - zstd >=1.5.7,<1.6.0a0 + - libxml2 + - libxml2-16 >=2.15.3 + - libzlib >=1.3.2,<2.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: {} + size: 31898 + timestamp: 1787349779831 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/compiler-rt-22.1.8-hce30654_2.conda + sha256: 28c01956aefbe38a8bddde58bb2398789fd580ffb341a12c54f44a542fa1a358 + md5: fa14e650717aac340bcc3251bab8117b + depends: + - compiler-rt22 22.1.8 hdb3d66b_2 + - libcompiler-rt 22.1.8 hdb3d66b_2 + constrains: + - clang 22.1.8 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: {} + size: 16634 + timestamp: 1787293624982 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/compiler-rt22-22.1.8-hdb3d66b_2.conda + sha256: cfa490c27082b9aca7a087548c2608daf5af2644f0eb8345fba7d4baa45fd03d + md5: da9362e9de3e1f80c9786a268ff22bfa + depends: + - __osx >=11.0 + - compiler-rt22_osx-arm64 22.1.8.* + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: {} + size: 99807 + timestamp: 1787293624048 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/contourpy-1.3.3-py314hf8a3a22_4.conda + sha256: 754ab72f1c1ae99ef7c57995f59224dc9632cbd6731fe7e6277437fd01d43156 + md5: cddc851000ce131d757678c2f329eaad + depends: + - numpy >=1.25 + - python + - python 3.14.* *_cp314 + - __osx >=11.0 + - libcxx >=19 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/contourpy?source=hash-mapping + run_exports: {} + size: 290405 + timestamp: 1769156069514 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/debugpy-1.8.21-py314he609de1_0.conda + sha256: fd6df772cdb30d01fa0d405ed637f420a9f2194fe931f967e8c67d95b504f8b4 + md5: da29307f59fb7a63f686ec20724fecf9 + depends: + - python + - libcxx >=19 + - __osx >=11.0 + - python 3.14.* *_cp314 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/debugpy?source=hash-mapping + run_exports: {} + size: 2776045 + timestamp: 1780390212997 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/fontconfig-2.18.3-h81aa574_1.conda + sha256: 004570d35fb0eff73ce3ae49b209a47622d0c2e580dd0dc4c61d59626b386a09 + md5: 8ac8cc3fe744b484de4387703134d8b2 + depends: + - __osx >=11.0 + - libexpat >=2.8.1,<3.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libintl >=0.25.1,<1.0a0 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - fontconfig >=2.18.3,<3.0a0 + - fonts-conda-ecosystem + size: 265659 + timestamp: 1786667932256 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.14.3-hce30654_2.conda + sha256: 90681f1d0658cb2fd49a074f644eb4774272499290487a4bebb1c4df01d6997b + md5: 2f4cc7dc2e6622591735c49dda1b92aa + depends: + - libfreetype 2.14.3 hce30654_2 + - libfreetype6 2.14.3 h2ed5691_2 + license: GPL-2.0-only OR FTL + purls: [] + run_exports: + weak: + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + size: 175388 + timestamp: 1786641016583 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/fribidi-1.0.16-h84a0fba_1.conda + sha256: 6dd18694340b84290bb1906cc1359502b974aada6a8476cfe6dec3ce0e860af8 + md5: 2bb7d7dd91116b8c85e805b0e08cc67b + depends: + - __osx >=11.0 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - fribidi >=1.0.16,<2.0a0 + size: 60230 + timestamp: 1785912572097 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/graphite2-1.3.15-h784d473_1.conda + sha256: 471f34a187fdb4f2df33e26f2e471b16c239a5b277903f643d9c3a8c9a9f44ec + md5: 0c7b78d9ffff4f5a6ca28d346f734d8f + depends: + - libcxx >=19 + - __osx >=11.0 + license: LGPL-2.0-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - graphite2 >=1.3.15,<2.0a0 + size: 86493 + timestamp: 1786118637573 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/greenlet-3.5.5-py314he609de1_0.conda + sha256: d960aefe961553b75450b7584f2fad7693ed1fac423da0fc5a9d649c48d2a019 + md5: ae860c6208235503f82ef3019461d410 + depends: + - python + - __osx >=11.0 + - libcxx >=19 + - python 3.14.* *_cp314 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/greenlet?source=compressed-mapping + run_exports: {} + size: 274201 + timestamp: 1786384265137 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/icu-78.3-py310h579977c_2.conda + sha256: 6cdb5dee54c72e56ab189fb3ad33cb28533553d42590e7e831160248f4416a43 + md5: a5efc0b42bb8b42e97d0a29ae3e3c187 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - icu >=78.3,<79.0a0 + size: 14070242 + timestamp: 1786545847761 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/kiwisolver-1.5.0-py314hf8a3a22_0.conda + sha256: 840de1b0ba2fa646475bc53ba0f723c8a13e66139633a070831b8279deaa7c64 + md5: eb1465d8a644ef290d18fb86af6e9bc4 + depends: + - python + - python 3.14.* *_cp314 + - libcxx >=19 + - __osx >=11.0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/kiwisolver?source=hash-mapping + run_exports: {} + size: 69284 + timestamp: 1773067285911 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/krb5-1.22.2-h34f8a20_2.conda + sha256: aaea1d42b07769920db2af7471ece9399d2613448863da64ad8272998db5db34 + md5: 15235dd10450d67bc25ccafd5b46d2bc + depends: + - __osx >=11.0 + - libcxx >=21 + - libedit >=3.1.20250104,<3.2.0a0 + - libedit >=3.1.20250104,<4.0a0 + - openssl >=3.5.7,<4.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - krb5 >=1.22.2,<1.23.0a0 + size: 1165740 + timestamp: 1786762145768 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lcms2-2.19.1-hdfa7624_1.conda + sha256: ccb5598fad3694e79bf54f0eb812e3b3c3dd63d1497e631f5978800eadb9bcc4 + md5: d2f2c7c10e2957647d45589b7701a453 + depends: + - __osx >=11.0 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - libtiff >=4.7.1,<4.8.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - lcms2 >=2.19.1,<3.0a0 + size: 213747 + timestamp: 1780212240694 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ld64-956.6-llvm22_1_h5b97f1b_5.conda + sha256: 649aed47b17fb6dd0036972eb67cda49d930edb33bfbd42080d0f56e689927cc + md5: e36ce4ef652fd1d571d32df59e6aafe6 + depends: + - ld64_osx-arm64 956.6 llvm22_1_h692d5aa_5 + - libllvm22 >=22.1.8,<22.2.0a0 + constrains: + - cctools_osx-arm64 1030.6.3.* + - cctools 1030.6.3.* + license: APSL-2.0 + license_family: Other + purls: [] + run_exports: {} + size: 21744 + timestamp: 1785270927117 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ld64_osx-arm64-956.6-llvm22_1_h692d5aa_5.conda + sha256: 0ae96297b92a8148219d3450b989caaa37c6a8f03c3b891d204946e9b2a8f69d + md5: b84ec86a7de90fd23133d5f527943329 + depends: + - __osx >=11.0 + - libcxx + - libllvm22 >=22.1.8,<22.2.0a0 + - sigtool-codesign + - tapi >=1600.0.11.8,<1601.0a0 + constrains: + - cctools_impl_osx-arm64 1030.6.3.* + - ld64 956.6.* + - cctools 1030.6.3.* + - clang 22.1.* + license: APSL-2.0 + license_family: Other + purls: [] + run_exports: {} + size: 1038789 + timestamp: 1785270893798 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/lerc-4.2.0-h1eee2c3_0.conda + sha256: c97aa17d16d2ac332ba9f184e82ce8f72dcb10e9a10c5f299030be2d44e191b9 + md5: e429aec4037d5cb8fd34ded9f5dadd39 + depends: + - __osx >=11.0 + - libcxx >=19 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - lerc >=4.2.0,<5.0a0 + size: 166477 + timestamp: 1785036480092 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libblas-3.11.0-9_h3d1d584_accelerate.conda + build_number: 9 + sha256: 700b9977c9204195efa3ee0913895e344f264fec7a9a5ec0e886e7b2cb4de9b2 + md5: 33f464f01650d5be2d9c2d05533e3d97 + depends: + - __osx >=11.0 + - libgfortran + - libgfortran5 >=14.4.0 + constrains: + - blas 2.309 accelerate + - libcblas 3.11.0 9*_accelerate + - liblapack 3.11.0 9*_accelerate + - liblapacke 3.11.0 9*_accelerate + - mkl <2027 + track_features: + - blas_accelerate + - blas_accelerate_2 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libblas >=3.11.0,<4.0a0 + size: 2822826 + timestamp: 1786058830452 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlicommon-1.2.0-h1dcdb26_3.conda + sha256: 5e62b856b2e77ce98db44133bacab1281b42c1040dcbd69dbacfb80890cff5b0 + md5: b457450ba3f27c4749783c0204bd17b0 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libbrotlicommon >=1.2.0,<1.3.0a0 + size: 80027 + timestamp: 1786622846050 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlidec-1.2.0-h5295a6a_3.conda + sha256: 510c9fce0d9ffcf41741dd96fc05db381672109d5665ef002c2e58f0d4ca0118 + md5: e07a99c6fdd984f4d588060f2f936bf4 + depends: + - __osx >=11.0 + - libbrotlicommon 1.2.0 h1dcdb26_3 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libbrotlidec >=1.2.0,<1.3.0a0 + size: 29935 + timestamp: 1786622857695 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libbrotlienc-1.2.0-h2ddc9cb_3.conda + sha256: eac412417eee2e93c62e9d53559f1f9c14f40b6c41e2c432af8b517596898dd1 + md5: 954c78a9f591bfb12c79beaff7338ec8 + depends: + - __osx >=11.0 + - libbrotlicommon 1.2.0 h1dcdb26_3 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libbrotlienc >=1.2.0,<1.3.0a0 + size: 295650 + timestamp: 1786622868044 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcblas-3.11.0-9_h752f6bc_accelerate.conda + build_number: 9 + sha256: b3afe0b26f1d67e1d74f7a93aca436814f8a508a7ff304c4226e6add45bb36b8 + md5: ca9ab16cae919037f520e8a0e3238857 + depends: + - libblas 3.11.0 9_h3d1d584_accelerate + constrains: + - blas 2.309 accelerate + - liblapack 3.11.0 9*_accelerate + - liblapacke 3.11.0 9*_accelerate + track_features: + - blas_accelerate + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libcblas >=3.11.0,<4.0a0 + size: 18170 + timestamp: 1786058842226 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libclang-cpp22.1-22.1.8-default_h79071a4_9.conda + sha256: dee9afef1b1e45bb2fa7fed485d095e3184a34cc06697ca3df7b46b5cb618f7b + md5: 55ee288cf2d7e6d4581a714f7da59914 + depends: + - libcxx >=22.1.8 + - __osx >=11.0 + - zstd >=1.5.7,<1.6.0a0 + - libxml2 + - libxml2-16 >=2.15.3 + - libzlib >=1.3.2,<2.0a0 + - libllvm22 >=22.1.8,<22.2.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: + weak: + - libclang-cpp22.1 >=22.1.8,<22.2.0a0 + size: 15941619 + timestamp: 1787349779831 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libclang13-22.1.8-default_h54a73ef_9.conda + sha256: 93a3485f7c08dc4f6528913801e60e8bd93742d5058d335aa482548d9a50678b + md5: 349f80d4b7d80b25c88c00cb5eac5f74 + depends: + - libclang-cpp22.1 ==22.1.8 default_h79071a4_9 + - libcxx >=22.1.8 + - __osx >=11.0 + - zstd >=1.5.7,<1.6.0a0 + - libxml2 + - libxml2-16 >=2.15.3 + - libzlib >=1.3.2,<2.0a0 + - libllvm22 >=22.1.8,<22.2.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: + weak: + - libclang13 >=22.1.8 + size: 9988216 + timestamp: 1787349779831 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcompiler-rt-22.1.8-hdb3d66b_2.conda + sha256: ed70856638465fa1e3dbce41b7512425a63789774368e4b9878249517daeecad + md5: 1ea9adef104f7304dc445153f1f19274 + depends: + - __osx >=11.0 + constrains: + - compiler-rt >=9.0.1 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: + weak: + - libcompiler-rt >=22.1.8 + size: 1374400 + timestamp: 1787293619574 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-22.1.8-h55c6f16_0.conda + sha256: a2e7abab5add9750fab064c024394de48e49f97631c605ad5db5c8ac3fc769ef + md5: 89f76a2a21a3ec3ec983b5eb237c4113 + depends: + - __osx >=11.0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: {} + size: 569349 + timestamp: 1781670209146 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libcxx-devel-22.1.8-h6dc3340_0.conda + sha256: e734ac1ed426d09363d242674a286dc004f041ad1167e73912e11d3634cee09d + md5: f7784a43f0ed7158e918fd8ec0843b47 + depends: + - libcxx >=22.1.8 + - libcxx-headers >=22.1.8,<22.1.9.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: {} + size: 21687 + timestamp: 1781670229781 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libdeflate-1.25-he7e0567_1.conda + sha256: d896f4aa4ce4c590c2838678cb1917356fdb461d2a189991c0280c818c362172 + md5: 78650d671cb56909bb3e5c13bce310f9 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libdeflate >=1.25,<1.26.0a0 + size: 55727 + timestamp: 1785909153744 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libedit-3.1.20250104-pl5321h26f1114_1.conda + sha256: 257c926f19e32bdb981fc674c76966049b6fc73f706bae58e9fed8757ad1da70 + md5: 843ef89082f368cb889305084d3b483c + depends: + - ncurses + - __osx >=11.0 + - ncurses >=6.6,<7.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libedit >=3.1.20250104,<3.2.0a0 + size: 107742 + timestamp: 1786616721640 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libexpat-2.8.1-hf6b4638_1.conda + sha256: 5af74261101e3c777399c6294b2b5d290e508153268eb2e9ff99c4d69834612f + md5: a915151d5d3c5bf039f5ccc8402a436f + depends: + - __osx >=11.0 + constrains: + - expat 2.8.1.* + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 69362 + timestamp: 1781203631990 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libffi-3.7.0-hcf2aa1b_0.conda + sha256: 2c6ac9a6cd65af89b2bd448518bb1e13b44a2e48c0d469398e37bcfc0092e832 + md5: 92e8690d170d46d768c32553458c0105 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libffi >=3.7.0,<3.8.0a0 + size: 43734 + timestamp: 1783521647536 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype-2.14.3-hce30654_2.conda + sha256: 19ce8bd320414cb6b9889ecbf48a3ded848b0d1068b76ff6bea099bd7387d6f3 + md5: ee1fc5bba400ff0cae27fbf141a1ae0c + depends: + - libfreetype6 >=2.14.3 + license: GPL-2.0-only OR FTL + purls: [] + run_exports: {} + size: 8367 + timestamp: 1786641013393 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libfreetype6-2.14.3-h2ed5691_2.conda + sha256: d9b203d6484ad491b5b5f33d49a9d7a91189d776a9adae53d2b63af18ec11e6a + md5: 881cfb44ea02cc9849f612725a959660 + depends: + - __osx >=11.0 + - libpng >=1.6.58,<1.7.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - freetype >=2.14.3 + license: GPL-2.0-only OR FTL + purls: [] + run_exports: {} + size: 340923 + timestamp: 1786641012794 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgcc-16.2.0-h3cf6597_4.conda + sha256: 00b8d07ea98344c72c6e332a09b8060f4176849d5fd0eb78dd5b30176ad97ca4 + md5: 408af8d9d5226ff45ff04756ff1aa91e + depends: + - _openmp_mutex + constrains: + - libgcc-ng ==16.2.0=*_4 + - libgomp 16.2.0 4 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: {} + size: 365758 + timestamp: 1787617459249 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran-16.2.0-h07b0088_4.conda + sha256: 7582efbbffc6964093afd80e01c2ac60d89aa4e404cac664544675b9d4d1c5e7 + md5: aa6c627acd0f5709d9c79bf708a7b81b + depends: + - libgfortran5 16.2.0 hdb7a957_4 + constrains: + - libgfortran-ng ==16.2.0=*_4 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: {} + size: 99624 + timestamp: 1787617544212 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libgfortran5-16.2.0-hdb7a957_4.conda + sha256: 902719c38c7a390d305435a362dc83893754c3f933569a38347c434cd1c12540 + md5: d54390644d893d50e739b19145aae45f + depends: + - libgcc >=16.2.0 + constrains: + - libgfortran 16.2.0 + license: GPL-3.0-only WITH GCC-exception-3.1 + purls: [] + run_exports: {} + size: 554806 + timestamp: 1787617465010 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libglib-2.88.3-ha531971_1.conda + sha256: c9f7273bbe06d1693ff3a2b6f8b49b74e2ef6cbac2cead7aae767f2f5191b7aa + md5: 70a6bcf13dc0dd00fe3fe91190d38771 + depends: + - __osx >=11.0 + - pcre2 >=10.47,<10.48.0a0 + - libffi >=3.7.0,<3.8.0a0 + - libintl >=0.25.1,<1.0a0 + - libiconv >=1.18,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - glib >2.66 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - libglib >=2.88.3,<3.0a0 + size: 4447961 + timestamp: 1786457780347 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libharfbuzz-14.3.1-hcda0f7c_0.conda + sha256: 67af14f8fbf9b9725890f728179d933ac35a95a6b50f37b7b568a11bfa8a4c1d + md5: 1631ca9ffe7d368850904baa1df1abf4 + depends: + - __osx >=11.0 + - cairo >=1.18.4,<2.0a0 + - graphite2 >=1.3.15,<2.0a0 + - icu >=78.3,<79.0a0 + - libcxx >=21 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libglib >=2.88.3,<3.0a0 + - libpng >=1.6.58,<1.7.0a0 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 953252 + timestamp: 1786970947180 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libiconv-1.18-he4c29f2_3.conda + sha256: 689a14968267f2f97c07112fca5636e7d756036b9aee969911267c20bd3eea1f + md5: 17f4744c0873e9f77ac9b5cd0a27c187 + depends: + - __osx >=11.0 + license: LGPL-2.1-only + purls: [] + run_exports: + weak: + - libiconv >=1.18,<2.0a0 + size: 750816 + timestamp: 1787033961086 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libintl-0.25.1-h493aca8_0.conda + sha256: 99d2cebcd8f84961b86784451b010f5f0a795ed1c08f1e7c76fbb3c22abf021a + md5: 5103f6a6b210a3912faf8d7db516918c + depends: + - __osx >=11.0 + - libiconv >=1.18,<2.0a0 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - libintl >=0.25.1,<1.0a0 + size: 90957 + timestamp: 1751558394144 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libjpeg-turbo-3.2.0-h84a0fba_1.conda + sha256: 05006418f9392c9b723e8428808db106de0350a497832f95f921b85ff1072310 + md5: b2f8c8e5a7651a1d7c05404c3a159517 + depends: + - __osx >=11.0 + constrains: + - jpeg <0.0.0a + license: IJG AND BSD-3-Clause AND Zlib + purls: [] + run_exports: + weak: + - libjpeg-turbo >=3.2.0,<4.0a0 + size: 558459 + timestamp: 1785896382474 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapack-3.11.0-9_hcb0d94e_accelerate.conda + build_number: 9 + sha256: 181c3fc8ed3fa5728ad8e418aee37c94c556b169ab6fbc620b448f1b5cf0448e + md5: 31673241e570e2a408576fa62f55215b + depends: + - libblas 3.11.0 9_h3d1d584_accelerate + constrains: + - blas 2.309 accelerate + - libcblas 3.11.0 9*_accelerate + - liblapacke 3.11.0 9*_accelerate + track_features: + - blas_accelerate + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - liblapack >=3.11.0,<3.12.0a0 + size: 18148 + timestamp: 1786058849920 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblapacke-3.11.0-9_hbdd07e9_accelerate.conda + build_number: 9 + sha256: 8cb75e86c882585437003b2be6a5bac9418678cb934e5f154babb9a4b711ead8 + md5: d2dbdc0cbf29754e1575c0a78f9e66dd + depends: + - libblas 3.11.0 9_h3d1d584_accelerate + - libcblas 3.11.0 9_h752f6bc_accelerate + - liblapack 3.11.0 9_hcb0d94e_accelerate + constrains: + - blas 2.309 accelerate + track_features: + - blas_accelerate + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - liblapacke >=3.11.0,<3.12.0a0 + size: 18188 + timestamp: 1786058856697 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libllvm22-22.1.8-h759d1ac_2.conda + sha256: 0d9f5b0d13e3b6b94132ebe5ce79edbd7790e089249852ce3e061888f1b0a0c5 + md5: 6f1791ed2becb128eb25d6819fea99b6 + depends: + - __osx >=11.0 + - libcxx >=21 + - libxml2 + - libxml2-16 >=2.15.3 + - libzlib >=1.3.2,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: + weak: + - libllvm22 >=22.1.8,<22.2.0a0 + size: 30239322 + timestamp: 1787282732568 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/liblzma-5.8.3-h8088a28_1.conda + sha256: 23d0630046a3e8b164d8f80f2b74ed2605af2e7050ab9913018056402fae4311 + md5: 8ab10323068b107661a4b9a4af84f3b5 + depends: + - __osx >=11.0 + constrains: + - xz 5.8.3.* + license: 0BSD + purls: [] + run_exports: + weak: + - liblzma >=5.8.3,<6.0a0 + size: 91720 + timestamp: 1786348695846 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libmpdec-4.0.0-h84a0fba_2.conda + sha256: 04cc136c5a956a73aa14e0a160b5822b0b29714e67b299fa1b1fd16dbcba5366 + md5: ff33a4dbd93abc8a798cc4e0e7c8136d + depends: + - __osx >=11.0 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 73289 + timestamp: 1786651074391 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libpng-1.6.58-hf5e6511_1.conda + sha256: f88da60b348ee1f3e1a9c20fe02142fdafe889f08da0b1cc33c1f3f14460239d + md5: e0466c58fd07db190b9a81b5e58539f2 + depends: + - __osx >=11.0 + - libzlib >=1.3.2,<2.0a0 + license: zlib-acknowledgement + purls: [] + run_exports: + weak: + - libpng >=1.6.58,<1.7.0a0 + size: 290219 + timestamp: 1786616561185 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libraqm-0.11.0-h45af499_0.conda + sha256: b110f7f9b2cec61ea793c521950414e00cd64826e18aeb4ab40369acf05c0039 + md5: 46ea0eb4e71bffa35ab7070678bcb053 + depends: + - __osx >=11.0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libharfbuzz >=14.2.1 + - fribidi >=1.0.16,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libraqm >=0.11.0,<0.12.0a0 + size: 31333 + timestamp: 1784850348342 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsigtool-0.1.3-h98dc951_1.conda + sha256: fcfa03afe31f40dfabf011629d99836adbebbc3ed1b38c7e9eee9ffc7581975b + md5: c70eb797aa92a294b09ef8f41f6bd578 + depends: + - __osx >=11.0 + - openssl >=3.5.7,<4.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 36651 + timestamp: 1786115069018 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsodium-1.0.22-h74c22ad_2.conda + sha256: 1cc97c217b103145e67ae6f928d0ae11ebc56879dfd10d739539624f0de80f86 + md5: ea78b9246e47aade6c94db52b1c9b5a4 + depends: + - __osx >=11.0 + license: ISC + purls: [] + run_exports: + weak: + - libsodium >=1.0.22,<1.0.23.0a0 + size: 249624 + timestamp: 1787225816699 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libsqlite-3.53.4-hca69786_1.conda + sha256: 839b31d4830e896b4d315b551d27f2bb08026bc946df04bcc360d8627c3ba2cd + md5: fde96d40ebe9a9cb34e4122380189ddb + depends: + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - libzlib >=1.3.2,<2.0a0 + license: blessing + purls: [] + run_exports: + weak: + - libsqlite >=3.53.4,<4.0a0 + size: 942754 + timestamp: 1787051243846 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libtiff-4.7.2-h282da08_0.conda + sha256: 253153cabf9469170e02b21a6bc9aa598048e7c34966b1103af52d27d2a708a4 + md5: 6fa87106b3c041ad6d965a9411e79b94 + depends: + - __osx >=11.0 + - lerc >=4.1.0,<5.0a0 + - libcxx >=19 + - libdeflate >=1.25,<1.26.0a0 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - liblzma >=5.8.3,<6.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: HPND + purls: [] + run_exports: + weak: + - libtiff >=4.7.2,<4.8.0a0 + size: 387825 + timestamp: 1783085754081 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.6.0-h202fb40_1.conda + sha256: 0ff54650d470c7e54cbeffdd53a8c063e055a7bcf784388c0385ce5c4741b0f4 + md5: 168a13e329259710b28277abc1395b8e + depends: + - __osx >=11.0 + constrains: + - libwebp 1.6.0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libwebp-base >=1.6.0,<2.0a0 + size: 294522 + timestamp: 1785955350410 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxcb-1.17.0-hbffa61f_1.conda + sha256: 81f9b4fb64d977c19474d1b5616974757aaac21d55401ea105f21022b386bd28 + md5: 923bf656578743d064f352f0b56ee658 + depends: + - __osx >=11.0 + - pthread-stubs + - xorg-libxau >=1.0.12,<2.0a0 + - xorg-libxdmcp >=1.1.5,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libxcb >=1.17.0,<2.0a0 + size: 324264 + timestamp: 1787077544793 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-16-2.15.3-h5ef1a60_1.conda + sha256: 2ca61d8287339c726b151fa612e83d2afa1418b89fa17694c873dc231bc9b077 + md5: f86c0b8ac5c866049717b55df1049c2c + depends: + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - libxml2 2.15.3 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 466188 + timestamp: 1787237580766 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.15.3-h5654f7c_1.conda + sha256: ff1b70683ccb82f8aa7f3eda53405a2036d849daba4e4e622f330dcd49d37700 + md5: 06a3f8eb5cdde56b2d10b49ae3337954 + depends: + - __osx >=11.0 + - icu >=78.3,<79.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libxml2-16 2.15.3 h5ef1a60_1 + - libzlib >=1.3.2,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libxml2 + - libxml2-16 >=2.15.3 + size: 41264 + timestamp: 1787237585903 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.2-h8088a28_3.conda + sha256: a18fa5d5bac452401459f966cf0d872224e8080c4ff93c77e168d43ab42ef9d7 + md5: f39288f0ea63ae962e1a2e4f355a0d75 + depends: + - __osx >=11.0 + constrains: + - zlib 1.3.2 *_3 + license: Zlib + license_family: Other + purls: [] + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 47822 + timestamp: 1785277049190 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-22.1.8-hc225544_1.conda + sha256: aa95fe355ec9fbf5d9816e155b9678cc98b749d3245bff56ca02f2a01239f5ac + md5: 4d317ff37f520b0d25d634cb996823cd + depends: + - __osx >=11.0 + constrains: + - intel-openmp <0.0a0 + - openmp 22.1.8|22.1.8.* + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: + strong: + - llvm-openmp >=22.1.8 + size: 287782 + timestamp: 1787293144681 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-tools-22-22.1.8-h79441dc_2.conda + sha256: 7034bea200b67b6a1effbf350cb34518786715a2640247f85173d9ee4159ad11 + md5: dffb5b6ba46221e9c287d09721e8ac9f + depends: + - __osx >=11.0 + - libcxx >=21 + - libllvm22 22.1.8 h759d1ac_2 + - libzlib >=1.3.2,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: {} + size: 17968840 + timestamp: 1787282818022 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-tools-22.1.8-hdb3d66b_2.conda + sha256: cf82447e24d3d46d7d17521ee8875eef6132313feda4f8dd2cfac989d5184189 + md5: 0b33be5a03e3f7ac75fdbbde668d8185 + depends: + - __osx >=11.0 + - libllvm22 22.1.8 h759d1ac_2 + - llvm-tools-22 22.1.8 h79441dc_2 + constrains: + - clang 22.1.8 + - clang-tools 22.1.8 + - llvm 22.1.8 + - llvmdev 22.1.8 + license: Apache-2.0 WITH LLVM-exception + license_family: Apache + purls: [] + run_exports: {} + size: 51998 + timestamp: 1787282867589 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvmlite-0.48.0-py314h582f951_1.conda + sha256: 2e4b585c7612892ea2f6328a32c8c8a7b231cada67a7b62124043827f849718f + md5: 6441a7f3066baffbfe9952d1cf1ee8ad + depends: + - python + - libcxx >=19 + - __osx >=11.0 + - libzlib >=1.3.2,<2.0a0 + - zstd >=1.5.7,<1.6.0a0 + - python_abi 3.14.* *_cp314 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/llvmlite?source=hash-mapping + run_exports: {} + size: 30238668 + timestamp: 1784043454464 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.3-py314h6e9b3f0_1.conda + sha256: 411153d14ee0d98be6e3751cf5cc0502db17bce2deebebb8779e33d29d0e525f + md5: d33c0a15882b70255abdd54711b06a45 + depends: + - __osx >=11.0 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=hash-mapping + run_exports: {} + size: 27256 + timestamp: 1772445397216 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-3.11.1-py314h314fc0d_2.conda + sha256: ea350f06df2581e68b9f5e1d029ad603c4044d9afff1294ca9315ac986b5d05f + md5: 710b3448473bb1bff68d2ba19cbc6c58 + depends: + - matplotlib-base >=3.11.1,<3.11.2.0a0 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - tornado >=5 + license: PSF-2.0 + license_family: PSF + purls: [] + run_exports: {} + size: 14958 + timestamp: 1785211126869 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/matplotlib-base-3.11.1-py314h27b0870_2.conda + sha256: 363b319e21cf4782fe264b96494c6be949db79d0d0ba126be1a1a94a5e96aae6 + md5: 41fb78906bf657af4fbdcf8c67f41c11 + depends: + - __osx >=11.0 + - contourpy >=1.0.1 + - cycler >=0.10 + - fonttools >=4.28.2 + - freetype + - kiwisolver >=1.3.1 + - libcxx >=19 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libraqm >=0.11.0,<0.12.0a0 + - numpy >=1.25 + - numpy >=1.25,<3 + - packaging >=20.0 + - pillow >=9 + - pyparsing >=3 + - python >=3.14,<3.15.0a0 + - python-dateutil >=2.7 + - python_abi 3.14.* *_cp314 + - qhull >=2020.2,<2020.3.0a0 + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/matplotlib?source=hash-mapping + run_exports: {} + size: 8776291 + timestamp: 1785211103855 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.6-he64c551_1.conda + sha256: 7024a48c8c0d0114ed4ab53c76bf9275d50e91ba7cea367a9aead638d3c29c68 + md5: 3dfa0d0316dc246cd44937a557de4501 + depends: + - __osx >=11.0 + license: X11 AND BSD-3-Clause + purls: [] + run_exports: + weak: + - ncurses >=6.6,<7.0a0 + size: 804298 + timestamp: 1786355189145 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numba-0.66.0-py314h705d3de_1.conda + sha256: 891dc017080e256abf5d71ff3b2a76d1440c41ef857303437cbee4d163be842a + md5: 36963987dbd28a0522a99625f54012f8 + depends: + - python + - llvmlite >=0.48.0,<0.49.0a0 + - numpy >=1.22.3,<2.5 + - llvm-openmp >=19.1.7 + - __osx >=11.0 + - libcxx >=19 + - python_abi 3.14.* *_cp314 + - numpy >=1.23,<3 + constrains: + - tbb >=2021.6.0 + - libopenblas >=0.3.18,!=0.3.20 + - cuda-version >=11.2 + - cudatoolkit >=11.2 + - scipy >=1.0 + - cuda-python >=11.6 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/numba?source=hash-mapping + run_exports: {} + size: 6168367 + timestamp: 1785940733623 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/numpy-2.4.6-py314hb79c6fa_0.conda + sha256: 538064b78042cd2751664f00c6255ecce81b38e9fa6dd9c1863327e6c759ed4a + md5: e64e47cb372d92e3425816a2918f4605 + depends: + - python + - __osx >=11.0 + - libcxx >=19 + - libblas >=3.9.0,<4.0a0 + - python_abi 3.14.* *_cp314 + - liblapack >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=hash-mapping + run_exports: + weak: + - numpy >=1.23,<3 + size: 6995531 + timestamp: 1779169217034 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openjpeg-2.5.4-hd9e9057_0.conda + sha256: 60aca8b9f94d06b852b296c276b3cf0efba5a6eb9f25feb8708570d3a74f00e4 + md5: 4b5d3a91320976eec71678fad1e3569b + depends: + - __osx >=11.0 + - libcxx >=19 + - libpng >=1.6.55,<1.7.0a0 + - libtiff >=4.7.1,<4.8.0a0 + - libzlib >=1.3.1,<2.0a0 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - openjpeg >=2.5.4,<3.0a0 + size: 319697 + timestamp: 1772625397692 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/openssl-3.6.3-hd24854e_1.conda + sha256: 66be2283b5b37dcda1332b5e74c1782a8cb14fd2e62e0d38017c2d35bf73c119 + md5: 65d1906712b85d1679263c518d011b5b + depends: + - __osx >=11.0 + - ca-certificates + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - openssl >=3.6.3,<4.0a0 + size: 3109132 + timestamp: 1785913735357 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pandoc-3.10.2-hce30654_0.conda + sha256: 086c2d905c861de43e8fefe128f68ca94ea9b9e8a476c310f9b2f12ffd713827 + md5: d17619f554a5892162869bec6f97a0c4 + license: GPL-2.0-or-later + license_family: GPL + purls: [] + run_exports: {} + size: 27215826 + timestamp: 1786704617167 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pcre2-10.47-he63d830_1.conda + sha256: f8c415329b542e1fca1ff2917b80e687c18302dfa0353530668b90cb225b494f + md5: 5ab90033816cbe4097e8a297e5179f67 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - pcre2 >=10.47,<10.48.0a0 + size: 851704 + timestamp: 1787294559157 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pillow-12.3.0-py314hab283cf_0.conda + sha256: dd7303ea135116cc1182c109825459a9eeb77d244d3899f03dd8d83a8db956f9 + md5: 4f25498ea1962ab24097fed8700e91ae + depends: + - python + - python 3.14.* *_cp314 + - __osx >=11.0 + - zlib-ng >=2.3.3,<2.4.0a0 + - openjpeg >=2.5.4,<3.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libxcb >=1.17.0,<2.0a0 + - libtiff >=4.7.1,<4.8.0a0 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - libwebp-base >=1.6.0,<2.0a0 + - python_abi 3.14.* *_cp314 + - tk >=8.6.13,<8.7.0a0 + - lcms2 >=2.19.1,<3.0a0 + license: HPND + purls: + - pkg:pypi/pillow?source=hash-mapping + run_exports: {} + size: 1019767 + timestamp: 1782912213683 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pixman-0.46.4-h784d473_3.conda + sha256: 4779cd57231ce2e96fac643fcbdd4a6f51a57986264545445574e9c4acf526d3 + md5: 9a99c0b60efe41c194d01c182d000733 + depends: + - __osx >=11.0 + - libcxx >=19 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - pixman >=0.46.4,<1.0a0 + size: 198717 + timestamp: 1786106922508 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/psutil-7.2.2-py314hd98292b_1.conda + sha256: 1cb01e783a9bcc350ecc1d80cbd207c0b0e1f60dc0db6ded8650e318796ba4bf + md5: bea6a028c9205ea3789bb38409cbfe43 + depends: + - python + - __osx >=11.0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + purls: + - pkg:pypi/psutil?source=compressed-mapping + run_exports: {} + size: 242631 + timestamp: 1787417424394 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pthread-stubs-0.4-h84a0fba_1003.conda + sha256: 1c2338b9b0486af86883b9593d2f3e2845bbf216ea453dfe5d9a228e8dbe4057 + md5: d4852e6054b74645cf49ca10f6349191 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 9238 + timestamp: 1786068031100 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-12.2.2-py314h63b12ec_0.conda + sha256: 18577d2164b23888bc4a6b78f1bdfa12efa7253bf0e3f871ccf49fda8d18acd8 + md5: a34643ba983b0d8aed611d7aab0233db + depends: + - __osx >=11.3 + - libffi >=3.7.0,<3.8.0a0 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - setuptools + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyobjc-core?source=hash-mapping + run_exports: {} + size: 2183843 + timestamp: 1786667130341 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-12.2.2-py314hddd3963_0.conda + sha256: 4be5be6b7d204c4a18fa4eb679a0b2bc5136632df145ca4e8c22c3fae2f97b2e + md5: ac045b8c9dd989c9fcc86199fc68f7b1 + depends: + - __osx >=11.3 + - libffi >=3.7.0,<3.8.0a0 + - pyobjc-core 12.2.2.* + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyobjc-framework-cocoa?source=compressed-mapping + run_exports: {} + size: 382991 + timestamp: 1786682078806 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytensor-3.3.0-py314h0ac4119_0.conda + sha256: 884da424d2d24f2f814422fb6da3904d8496772c77252f3e28ee05f0bfd02684 + md5: c0d7e06ae86b1159bdb14a5776dba730 + depends: + - python + - pytensor-base ==3.3.0 np2py314hdd732f0_0 + - clangxx + - blas * *accelerate + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 9843 + timestamp: 1786614456777 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pytensor-base-3.3.0-np2py314hdd732f0_0.conda + sha256: 3e1785ef7de319bde45ef29206ea416de87b958677ad45d919a70d5feecbed48 + md5: 409c5aebeacd76f31045c11dc4fd0e00 + depends: + - python + - setuptools >=59.0.0 + - scipy >=1,<2 + - numpy >=2.0 + - numba >=0.58,<=0.66.0 + - filelock >=3.15 + - python 3.14.* *_cp314 + - libcxx >=19 + - __osx >=11.0 + - numpy >=1.25,<3 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pytensor?source=hash-mapping + run_exports: {} + size: 3221853 + timestamp: 1786614456777 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/python-3.14.7-hf4d206d_100_cp314.conda + build_number: 100 + sha256: 2f07838e44942236471d84e2a99b603badde96e58f86cf78c38488d4da64353a + md5: 47bd7a90ff0aef0717323a94ba3a04a0 + depends: + - __osx >=11.0 + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.8.1,<3.0a0 + - libffi >=3.7.0,<3.8.0a0 + - liblzma >=5.8.3,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.53.4,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - ncurses >=6.6,<7.0a0 + - openssl >=3.5.7,<4.0a0 + - python_abi 3.14.* *_cp314 + - readline >=8.3,<9.0a0 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - zstd >=1.5.7,<1.6.0a0 + license: Python-2.0 + purls: [] + run_exports: + weak: + - python_abi 3.14.* *_cp314 + noarch: + - python + size: 14159560 + timestamp: 1787154022633 + python_site_packages_path: lib/python3.14/site-packages +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyyaml-6.0.3-py314h6e9b3f0_1.conda + sha256: 95f385f9606e30137cf0b5295f63855fd22223a4cf024d306cf9098ea1c4a252 + md5: dcf51e564317816cb8d546891019b3ab + depends: + - __osx >=11.0 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + run_exports: {} + size: 189475 + timestamp: 1770223788648 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyzmq-27.2.0-py312hcedbef1_0.conda + noarch: python + sha256: de17d16785e44ea075ba0912ad4618db3438a3cf79c13249b1646ac4e616ea35 + md5: 52d1e1fe1463e21e34be9b8c2bf6c96c + depends: + - python + - __osx >=11.0 + - libcxx >=21 + - _python_abi3_support 1.* + - cpython >=3.12 + - zeromq >=4.3.5,<4.4.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pyzmq?source=compressed-mapping + run_exports: {} + size: 195379 + timestamp: 1787301084171 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/qhull-2020.2-h420ef59_5.conda + sha256: 873ac689484262a51fd79bc6103c1a1bedbf524924d7f0088fb80703042805e4 + md5: 6483b1f59526e05d7d894e466b5b6924 + depends: + - __osx >=11.0 + - libcxx >=16 + license: LicenseRef-Qhull + purls: [] + run_exports: + weak: + - qhull >=2020.2,<2020.3.0a0 + size: 516376 + timestamp: 1720814307311 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/readline-8.3-h8b90a29_1.conda + sha256: d6782b430e6dce4fe8c7ac118cd988d5a193eb4a7f60741edfaede58016b6110 + md5: 9347fd56a002e6b58946831d48fe62f6 + depends: + - __osx >=11.0 + - ncurses >=6.6,<7.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + run_exports: + weak: + - readline >=8.3,<9.0a0 + size: 314395 + timestamp: 1787033878899 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/rpds-py-2026.6.3-py314hc05cd11_0.conda + sha256: 7d4b49a7542735c63f0ef3379f83dfed1870a5d62ae4c3e357f859014fb1fcaf + md5: aa2a7e9c0ab5322b9bd6e75a384b48a1 + depends: + - python + - __osx >=11.0 + - python_abi 3.14.* *_cp314 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/rpds-py?source=hash-mapping + run_exports: {} + size: 284937 + timestamp: 1787344325551 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/safetensors-0.8.0-py314h54f3292_0.conda + sha256: 39e617554a4863f954c12f9c73eb8a94d6168b64bcc99cfaa0e14a159a7004fa + md5: 7b6c8590d1cbd703205defdee8c7e8e8 + depends: + - python + - __osx >=11.0 + - python 3.14.* *_cp314 + - python_abi 3.14.* *_cp314 + constrains: + - __osx >=11.0 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/safetensors?source=hash-mapping + run_exports: {} + size: 480148 + timestamp: 1781179759890 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/scipy-1.18.0-py314h18e1515_0.conda + sha256: 7ce218a4e1c55775547835d21a7ead0d50e5ac348fd638ce6ba316c48c8547b7 + md5: e55fe08bb5d43e7120672338dd129030 + depends: + - __osx >=11.0 + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - libcxx >=19 + - libgfortran + - libgfortran5 >=14.3.0 + - liblapack >=3.9.0,<4.0a0 + - numpy <2.7 + - numpy >=1.23,<3 + - numpy >=2.0.0 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/scipy?source=hash-mapping + run_exports: {} + size: 14122215 + timestamp: 1781912992503 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/sigtool-codesign-0.1.3-h98dc951_1.conda + sha256: 7efd6d7d18bf9cc5788bfe1c1e1620d9dbbe00a81c646814929a82ff31944cf3 + md5: a322c0a0d3b5be99ee11f9ccec99b60e + depends: + - __osx >=11.0 + - libsigtool 0.1.3 h98dc951_1 + - openssl >=3.5.7,<4.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 114653 + timestamp: 1786115093248 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/sqlalchemy-2.0.52-py314h5583935_0.conda + sha256: 9c86803397cf137bf92d517b6a4395d059cfb3dbfc1def708ed5963c5848db48 + md5: 61db956083da31d178a6940043d176d2 + depends: + - python + - greenlet !=0.4.17 + - typing-extensions >=4.6.0 + - __osx >=11.0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/sqlalchemy?source=compressed-mapping + run_exports: {} + size: 4044830 + timestamp: 1786535242452 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tapi-1600.0.11.8-hb561403_3.conda + sha256: 9f1cc109e6e57861110e9de6e03beca7fd2b7fbdb46124cccc07990b0844d88a + md5: 0676b8719dd1e9bb1003e59a481c6c3e + depends: + - libcxx >=19.0.0.a0 + - __osx >=11.0 + - ncurses >=6.6,<7.0a0 + license: NCSA + purls: [] + run_exports: {} + size: 200397 + timestamp: 1785906507697 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tk-8.6.13-hbeba79b_4.conda + sha256: 857d89087ae7f2c328cf256728affcb7343031a148b4af847334d248a9cf564c + md5: 90a01bf394d1c594e0eb9258f967d828 + depends: + - __osx >=11.0 + - libzlib >=1.3.2,<2.0a0 + license: TCL + purls: [] + run_exports: + weak: + - tk >=8.6.13,<8.7.0a0 + size: 3342183 + timestamp: 1787272852357 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/tornado-6.5.8-py314h6c2aa35_0.conda + sha256: 4af8e4394d249619b4f1641c82fd6c0a8b97d51991ca2986095bed400c2e5eb9 + md5: ab1bd70ec9c95d199f8ce456823004ab + depends: + - __osx >=11.0 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/tornado?source=hash-mapping + run_exports: {} + size: 922067 + timestamp: 1786227125229 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/unicodedata2-17.0.1-py314h6c2aa35_0.conda + sha256: 09bfbee5a2bcf4df06f21a2aa9eb40a7af97864a569beb5ea85fd6baf6e03ce7 + md5: 4fffb3ba871bb05f34ffb705534dfef5 + depends: + - __osx >=11.0 + - python >=3.14,<3.15.0a0 + - python >=3.14,<3.15.0a0 *_cp314 + - python_abi 3.14.* *_cp314 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/unicodedata2?source=hash-mapping + run_exports: {} + size: 416130 + timestamp: 1770909728445 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/watchfiles-1.2.0-py314he1d1ac0_1.conda + sha256: cd8c788feb2a47551b7f37ca7dfa4b5df1fe732007539d29be53874f03c6d945 + md5: 042bf3289f21e8a5c28551d150704295 + depends: + - python + - anyio >=3.0.0 + - __osx >=11.0 + - python_abi 3.14.* *_cp314 + constrains: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/watchfiles?source=hash-mapping + run_exports: {} + size: 352931 + timestamp: 1781180392615 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/websockets-17.0.1-py314h2fbedac_0.conda + sha256: 1c36aaa95a3fd57c3abd61903eac980403d944c0eee5c633206e1e9f49477fcf + md5: 27d17e43642cc45b5c771169cb386352 + depends: + - python + - __osx >=11.0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/websockets?source=hash-mapping + run_exports: {} + size: 432578 + timestamp: 1785599301757 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxau-1.0.12-h84a0fba_2.conda + sha256: 5c5ea38ceec008e4ff40111fc166b04086fca310b0aa9e5bd46f65e6b0dcb71d + md5: 31d71ce1b056f69b6ec67ee0712bdf97 + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxau >=1.0.12,<2.0a0 + size: 15124 + timestamp: 1786381585975 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/xorg-libxdmcp-1.1.5-h84a0fba_2.conda + sha256: 2f6915f0897ace4a1b5d66d0463079f7bc9819b0048c03677e47764f0a743499 + md5: f51e9414bf1b7bb556aac1a0b74a75fe + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxdmcp >=1.1.5,<2.0a0 + size: 19486 + timestamp: 1786381546642 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/yaml-0.2.5-h74c22ad_3.conda + sha256: 3e78c43207502418fc5fb2317bbbefe5df8b6fc1051d90bdcd1c881c76bb4193 + md5: 31cf6dcc138abe673c9440cd6fe6c6fe + depends: + - __osx >=11.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - yaml >=0.2.5,<0.3.0a0 + size: 77530 + timestamp: 1787228556857 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zeromq-4.3.5-h10816f8_11.conda + sha256: 01fd50d2801b23b59fafea6bf704a6c5faf0f5969104400eae0e6572cb2e5304 + md5: d31c0e54c4f9c51100ec8c812ee925d1 + depends: + - libcxx >=19 + - __osx >=11.0 + - krb5 >=1.22.2,<1.23.0a0 + - libsodium >=1.0.22,<1.0.23.0a0 + license: MPL-2.0 + license_family: MOZILLA + purls: [] + run_exports: + weak: + - zeromq >=4.3.5,<4.4.0a0 + size: 245404 + timestamp: 1779124076307 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zlib-ng-2.3.3-h31dac16_1.conda + sha256: 489a29915a3e1b425cff4df10a448f55bbaaf29d777a0f8a9e381444e6a284f1 + md5: 4621ded2fd5b36f51454d5f81bff4ec1 + depends: + - __osx >=11.0 + - libcxx >=21 + license: Zlib + license_family: Other + purls: [] + run_exports: + weak: + - zlib-ng >=2.3.3,<2.4.0a0 + size: 95857 + timestamp: 1786737243497 +- conda: https://conda.anaconda.org/conda-forge/osx-arm64/zstd-1.5.7-hf451053_7.conda + sha256: da867f5092eb0cb746d353694f0098031fd9817a4ce7d5743121209ae0f406ca + md5: 4ec2684c73812cc2c3d78379384a39cc + depends: + - __osx >=11.0 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - zstd >=1.5.7,<1.6.0a0 + size: 433687 + timestamp: 1786599629846 +- conda: https://conda.anaconda.org/conda-forge/win-64/_openmp_mutex-4.5-20_gnu.conda + build_number: 20 + sha256: 8a1cee28bd0ee7451ada1cd50b64720e57e17ff994fc62dd8329bef570d382e4 + md5: 1626967b574d1784b578b52eaeb071e7 + depends: + - libgomp >=7.5.0 + - libwinpthread >=12.0.0.r4.gg4f2fc60ca + constrains: + - openmp_impl <0.0a0 + - msys2-conda-epoch <0.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + strong: + - _openmp_mutex >=4.5 + size: 52252 + timestamp: 1770943776666 +- conda: https://conda.anaconda.org/conda-forge/win-64/argon2-cffi-bindings-26.1.0-py314h5a2d7ad_0.conda + sha256: 47917aa31232191136b40f3055d4b469c0a71475aea396c2413d753be014b7dc + md5: 6c4598c60f92421a0f9926a0f4b36dd0 + depends: + - cffi >=2.0.0b1 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/argon2-cffi-bindings?source=compressed-mapping + run_exports: {} + size: 33526 + timestamp: 1787248062135 +- conda: https://conda.anaconda.org/conda-forge/win-64/binutils_impl_win-64-2.46.1-default_ha84baeb_102.conda + sha256: 83476bc3ed6ee4f1d6e67e6e1360696a0ac3e99679f9c142674003cf721740e3 + md5: 7832bada38267be3333319febcf5e4fc + depends: + - ld_impl_win-64 2.46.1 default_hfd38196_102 + - m2w64-sysroot_win-64 >=12.0.0.r0 + - zstd >=1.5.7,<1.6.0a0 + license: GPL-3.0-only + license_family: GPL + purls: [] + run_exports: {} + size: 6140284 + timestamp: 1784214565466 +- conda: https://conda.anaconda.org/conda-forge/win-64/blas-2.309-mkl.conda + build_number: 9 + sha256: 30951d77d5efea2977f644094fe67faf5f621d30f9aec669c6dd906769e5c637 + md5: df62249c6f2232ec771794699119d52e + depends: + - blas-devel 3.11.0 9*_mkl + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 18620 + timestamp: 1786059303886 +- conda: https://conda.anaconda.org/conda-forge/win-64/blas-devel-3.11.0-9_h85df5b5_mkl.conda + build_number: 9 + sha256: c319cafe77419a9fa9a049da6e132e7f4c90023c91f35c60ccfd254021f11a58 + md5: eaf7e91d72d60a3de4b2f8e218b517fb + depends: + - libblas 3.11.0 9_h8455456_mkl + - libcblas 3.11.0 9_h2a3cdd5_mkl + - liblapack 3.11.0 9_hf9ab0e9_mkl + - liblapacke 3.11.0 9_h3ae206f_mkl + - mkl >=2026.1.0,<2027.0a0 + - mkl-devel 2026.1.* + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 18169 + timestamp: 1786059259827 +- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-1.2.0-hc8c2fe1_3.conda + sha256: 85e12348d058791aabd6eeb6abc1d7ab44b8f6308f91c8475f44f778f2a158af + md5: 9eba56a007c44dc32d0b9d0a820871ea + depends: + - brotli-bin 1.2.0 hd477307_3 + - libbrotlidec 1.2.0 h84f9c24_3 + - libbrotlienc 1.2.0 he2a975b_3 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + size: 20954 + timestamp: 1786622876247 +- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-bin-1.2.0-hd477307_3.conda + sha256: 5328e0576c729813d28efc15613f4be94d029d77c4fe5afda16cab0b8d0c9d05 + md5: dc04f7b3d82c6fb3fdf4d93e45b6ea2a + depends: + - libbrotlidec 1.2.0 h84f9c24_3 + - libbrotlienc 1.2.0 he2a975b_3 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 23119 + timestamp: 1786622866096 +- conda: https://conda.anaconda.org/conda-forge/win-64/brotli-python-1.2.0-py314h85cf176_3.conda + sha256: f96c411313beb92a6a9066b823f7e8ea085f3e3889219b44306c44d59f99e611 + md5: b1ff58c1f0deedd3f25c103e37049cee + depends: + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - libbrotlicommon 1.2.0 hf02afa3_3 + license: MIT + license_family: MIT + purls: + - pkg:pypi/brotli?source=hash-mapping + run_exports: {} + size: 336902 + timestamp: 1786623039339 +- conda: https://conda.anaconda.org/conda-forge/win-64/bzip2-1.0.8-h0ad9c76_10.conda + sha256: 04767466ee9227c9c57ab2c6503e0149177d34111c7418d2f420297acb1eb229 + md5: c3301c058362f340100d91cd8be0393f + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: bzip2-1.0.6 + license_family: BSD + purls: [] + run_exports: + weak: + - bzip2 >=1.0.8,<2.0a0 + size: 55919 + timestamp: 1785906343696 +- conda: https://conda.anaconda.org/conda-forge/win-64/cairo-1.18.4-h477c42c_1.conda + sha256: 9ee4ad706c5d3e1c6c469785d60e3c2b263eec569be0eac7be33fbaef978bccc + md5: 52ea1beba35b69852d210242dd20f97d + depends: + - fontconfig >=2.15.0,<3.0a0 + - fonts-conda-ecosystem + - icu >=78.1,<79.0a0 + - libexpat >=2.7.3,<3.0a0 + - libfreetype >=2.14.1 + - libfreetype6 >=2.14.1 + - libglib >=2.86.3,<3.0a0 + - libpng >=1.6.53,<1.7.0a0 + - libzlib >=1.3.1,<2.0a0 + - pixman >=0.46.4,<1.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: LGPL-2.1-only or MPL-1.1 + purls: [] + run_exports: + weak: + - cairo >=1.18.4,<2.0a0 + size: 1537783 + timestamp: 1766416059188 +- conda: https://conda.anaconda.org/conda-forge/win-64/cffi-2.1.1-py314h5a2d7ad_2.conda + sha256: e0e10d676eb67a8a4c8991cec89fb6f150f919eb1266a1b39253a857b3dc0454 + md5: 672d6ff72c6265b25eeef94ce21e71ac + depends: + - pycparser + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: + - pkg:pypi/cffi?source=compressed-mapping + run_exports: {} + size: 346657 + timestamp: 1786775160153 +- conda: https://conda.anaconda.org/conda-forge/win-64/conda-gcc-specs-16.1.0-h851ee6d_3.conda + sha256: 0dfab7b5adecfbfd4093bf8251043cf71550051caadda228c6ecbfd40e5f42fc + md5: 700f080b26531379d9a8acb0f085352d + depends: + - gcc_impl_win-64 >=16.1.0,<16.1.1.0a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: {} + size: 47294 + timestamp: 1787333238012 +- conda: https://conda.anaconda.org/conda-forge/win-64/contourpy-1.3.3-py314hf309875_4.conda + sha256: f141bcbf8e490b49b2f53f517173d13a64d75e43cfae170e0d931cb0b66f4bce + md5: c26934035616f7d578f9da0491aed3d8 + depends: + - numpy >=1.25 + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/contourpy?source=hash-mapping + run_exports: {} + size: 247437 + timestamp: 1769155978556 +- conda: https://conda.anaconda.org/conda-forge/win-64/debugpy-1.8.21-py314hb98de8c_0.conda + sha256: daa9397ffba6722f27c21b2f0a02b197f3ba9baedb484a155539743ec5ea3ac3 + md5: 945786ac874b8e821a675fbdd885f844 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/debugpy?source=hash-mapping + run_exports: {} + size: 4022782 + timestamp: 1780390190830 +- conda: https://conda.anaconda.org/conda-forge/win-64/double-conversion-3.4.0-hac47afa_0.conda + sha256: 09e30a170e0da3e9847d449b594b5e55e6ae2852edd3a3680e05753a5e015605 + md5: 3d3caf4ccc6415023640af4b1b33060a + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - double-conversion >=3.4.0,<3.5.0a0 + size: 70943 + timestamp: 1765193243911 +- conda: https://conda.anaconda.org/conda-forge/win-64/fontconfig-2.18.3-hd47e2ca_1.conda + sha256: f26139e3c774a6434c8a73381c08e3d2a4c2f9d9ef9587c66aab8836211ee2ec + md5: ed95670fed91b091fe34ba6cae6677d7 + depends: + - libexpat >=2.8.1,<3.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libiconv >=1.18,<2.0a0 + - libintl >=0.22.5,<1.0a0 + - libzlib >=1.3.2,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - fontconfig >=2.18.3,<3.0a0 + - fonts-conda-ecosystem + size: 217988 + timestamp: 1786667461139 +- conda: https://conda.anaconda.org/conda-forge/win-64/freetype-2.14.3-h57928b3_2.conda + sha256: 32f7dd8ffe62fd485e9e6570e871f5be45af74c84fde62241a2417046a373efd + md5: 6fc6c09c05a099d58efd9b2e96598e41 + depends: + - libfreetype 2.14.3 h57928b3_2 + - libfreetype6 2.14.3 hdbac1cb_2 + - zlib + license: GPL-2.0-only OR FTL + purls: [] + run_exports: + weak: + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + size: 186945 + timestamp: 1786641050416 +- conda: https://conda.anaconda.org/conda-forge/win-64/fribidi-1.0.16-hfd05255_1.conda + sha256: 274b3e4ae5dff527062039d1dcb5cfdd9f91fa7d8eaf61358c09450b361385de + md5: 66f5ce9d0d618332023619a899ceb26f + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - fribidi >=1.0.16,<2.0a0 + size: 65318 + timestamp: 1785912637725 +- conda: https://conda.anaconda.org/conda-forge/win-64/gcc-16.1.0-hb5e953d_3.conda + sha256: b0dba3638f794b0f2a737574d5544ba9102b4b542467ab2015beb58bb72a398d + md5: 2e2dd2ff89786b8c2087bb01d6906121 + depends: + - conda-gcc-specs + - gcc_impl_win-64 16.1.0 hf3f8c13_3 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 1351786 + timestamp: 1787333364390 +- conda: https://conda.anaconda.org/conda-forge/win-64/gcc_impl_win-64-16.1.0-hf3f8c13_3.conda + sha256: dc148f6b468b29192af93132e0ae4e5fcd2a71b184c3dc6d74edefa42abba860 + md5: cfe7e37706d5b7f392dd81bb354a84c0 + depends: + - binutils_impl_win-64 >=2.46.1 + - libgcc >=16.1.0 + - libgcc-devel_win-64 16.1.0 hecf7705_103 + - libgomp >=16.1.0 + - libstdcxx >=16.1.0 + - libstdcxx-devel_win-64 16.1.0 hc76ffd0_103 + - m2w64-sysroot_win-64 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: {} + size: 65095791 + timestamp: 1787333099106 +- conda: https://conda.anaconda.org/conda-forge/win-64/graphite2-1.3.15-h5112557_1.conda + sha256: 93a59bdf944fb6f947bd7ad2f293d5d80db3a90f8ff8dfabc591e3752141e46f + md5: 79538e7a7bc024084eda5989f73fde35 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: LGPL-2.0-or-later + license_family: LGPL + purls: [] + run_exports: + weak: + - graphite2 >=1.3.15,<2.0a0 + size: 98064 + timestamp: 1786118492029 +- conda: https://conda.anaconda.org/conda-forge/win-64/greenlet-3.5.5-py314hb98de8c_0.conda + sha256: c1f9d4b92bf255fc5cc92ea10448d812417fd26132856cefe9f41f2d5ccc2f8a + md5: 212518796fa1c60ca92e4d1c8ef80ce3 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/greenlet?source=compressed-mapping + run_exports: {} + size: 259251 + timestamp: 1786384106710 +- conda: https://conda.anaconda.org/conda-forge/win-64/gxx-16.1.0-hb5e953d_3.conda + sha256: cdf3ad6ea0097cff6055ee110e205af38549d4a601d93ad156894474aabd3ed7 + md5: 60ae547baaf558222e923978f81f18b0 + depends: + - conda-gcc-specs + - gcc 16.1.0 hb5e953d_3 + - gxx_impl_win-64 16.1.0 he3d2c83_3 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 936148 + timestamp: 1787333412579 +- conda: https://conda.anaconda.org/conda-forge/win-64/gxx_impl_win-64-16.1.0-he3d2c83_3.conda + sha256: 60e8934bcd323f11af1a0181b193ca3a902d0db3c16b7eebc6db18a9a84ba2c7 + md5: ee6ef5218193fb363adc382738faf6a3 + depends: + - gcc_impl_win-64 16.1.0 hf3f8c13_3 + - libstdcxx-devel_win-64 16.1.0 hc76ffd0_103 + - m2w64-sysroot_win-64 + - tzdata + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: {} + size: 15850994 + timestamp: 1787333323014 +- conda: https://conda.anaconda.org/conda-forge/win-64/icu-78.3-h5112557_2.conda + sha256: 75c549b55b673e15de8785a8e5dd85bca7eb612eee0ff4dc8d7bdaa15eacbdbb + md5: e596942e8ee6ee17fdcf1e6a77757a66 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - icu >=78.3,<79.0a0 + size: 16835644 + timestamp: 1784916416303 +- conda: https://conda.anaconda.org/conda-forge/win-64/kiwisolver-1.5.0-py314hf309875_0.conda + sha256: 37cbc49fd7255532d09fb3bc9cc699554693e632fa90678a9b3d0ed12557d0d7 + md5: 0508c8dabeab91311e5c59b5e3f6d278 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/kiwisolver?source=hash-mapping + run_exports: {} + size: 73330 + timestamp: 1773067062280 +- conda: https://conda.anaconda.org/conda-forge/win-64/krb5-1.22.2-h719d79b_2.conda + sha256: 63ff03324e903eb01a715ccf357df56d66224e61952fd6615d86490ebefb3285 + md5: 93f5a01dec294a2228f757fe2f3432d4 + depends: + - openssl >=3.5.7,<4.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - krb5 >=1.22.2,<1.23.0a0 + size: 753425 + timestamp: 1786762169034 +- conda: https://conda.anaconda.org/conda-forge/win-64/lcms2-2.19.1-hf2c6c5f_1.conda + sha256: 5ed63a32639a130564a870becb679fd52dfb816666a61ed3c023917389010480 + md5: 1df4012c8a2478699d07bc26af66d41e + depends: + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - libtiff >=4.7.1,<4.8.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - lcms2 >=2.19.1,<3.0a0 + size: 523194 + timestamp: 1780211799997 +- conda: https://conda.anaconda.org/conda-forge/win-64/ld_impl_win-64-2.46.1-default_hfd38196_102.conda + sha256: e3cb27be096acab3c8d5ed36961e291e3e6e9c2323dd3fd565623a4c093e931c + md5: 179ed4e90a5c9560bebb28292a726f12 + depends: + - zstd >=1.5.7,<1.6.0a0 + constrains: + - binutils_impl_win-64 2.46.1 + license: GPL-3.0-only + license_family: GPL + purls: [] + run_exports: {} + size: 896485 + timestamp: 1784214548635 +- conda: https://conda.anaconda.org/conda-forge/win-64/lerc-4.2.0-hd936e49_0.conda + sha256: 93d666f63f284ef77b87b0b1f77b70f7d36d315a132f9afa64bc0012d937ba39 + md5: add59e2b60ac9d4299d17c938185c75a + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - lerc >=4.2.0,<5.0a0 + size: 175297 + timestamp: 1785036247761 +- conda: https://conda.anaconda.org/conda-forge/win-64/libblas-3.11.0-9_h8455456_mkl.conda + build_number: 9 + sha256: a99c640f10a3f77efe5c6605676ddc8d365d020eec4fdf1c803d34bdaf49b357 + md5: 17b26a4ad064259983bec1eaa36f40d3 + depends: + - mkl >=2026.1.0,<2027.0a0 + constrains: + - blas 2.309 mkl + - libcblas 3.11.0 9*_mkl + - liblapack 3.11.0 9*_mkl + - liblapacke 3.11.0 9*_mkl + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libblas >=3.11.0,<4.0a0 + size: 67235 + timestamp: 1786059219098 +- conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlicommon-1.2.0-hf02afa3_3.conda + sha256: c739589318a1f8a88cd1b66d385176fd1ec2c4609e9f90d23d748be94b547e4e + md5: 8ef4beb3cb18e1a876b9af9a757cc1a5 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libbrotlicommon >=1.2.0,<1.3.0a0 + size: 82655 + timestamp: 1786622832371 +- conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlidec-1.2.0-h84f9c24_3.conda + sha256: f4bdb7ec97c3122e531fc867efae5ec928b649d047aa70d42893f0d8eb110fd9 + md5: 10c479888ee4e960587967b2d0c8143a + depends: + - libbrotlicommon 1.2.0 hf02afa3_3 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libbrotlidec >=1.2.0,<1.3.0a0 + size: 34788 + timestamp: 1786622843818 +- conda: https://conda.anaconda.org/conda-forge/win-64/libbrotlienc-1.2.0-he2a975b_3.conda + sha256: c5e638ea9704c94b316238b425a3439ba38d4d8fba81682842e6d7d464472848 + md5: cb5d08dc81f52e87c27914b5636cd995 + depends: + - libbrotlicommon 1.2.0 hf02afa3_3 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libbrotlienc >=1.2.0,<1.3.0a0 + size: 253894 + timestamp: 1786622854214 +- conda: https://conda.anaconda.org/conda-forge/win-64/libcblas-3.11.0-9_h2a3cdd5_mkl.conda + build_number: 9 + sha256: 8c20714bbb85c8a109e9002e76c32be64a82d9867beb1a35a20c85258cc2b535 + md5: 9d1d0c22e9ed8c31ec2efc0d063d6f48 + depends: + - libblas 3.11.0 9_h8455456_mkl + constrains: + - blas 2.309 mkl + - liblapack 3.11.0 9*_mkl + - liblapacke 3.11.0 9*_mkl + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libcblas >=3.11.0,<4.0a0 + size: 67587 + timestamp: 1786059232952 +- conda: https://conda.anaconda.org/conda-forge/win-64/libclang13-22.1.8-default_hacd6ee9_9.conda + sha256: 4e6169971eaf472bfbdd9143202ccd5eed3a725c548155f42ebf3d19c9afea13 + md5: 20ec1bc1a4ff03f3a9098ad7b4d469b6 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - llvm-openmp >=22.1.8 + - zstd >=1.5.7,<1.6.0a0 + - libzlib >=1.3.2,<2.0a0 + - libxml2 + - libxml2-16 >=2.15.3 + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: + weak: + - libclang13 >=22.1.8 + size: 34915563 + timestamp: 1787349750337 +- conda: https://conda.anaconda.org/conda-forge/win-64/libdeflate-1.25-h1a1d4e4_1.conda + sha256: af1cda21d4653f594fbef20aa4e1ff158a546902b3307ef8af9a9b44b43862d2 + md5: e4e122e124676a49eebf241399ad8393 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libdeflate >=1.25,<1.26.0a0 + size: 157828 + timestamp: 1785908793271 +- conda: https://conda.anaconda.org/conda-forge/win-64/libexpat-2.8.1-hac47afa_1.conda + sha256: 1a54d874addda73b6f7164d5f3905821277a1831bcc05edd74b3085391688571 + md5: ccc490c81ffe14181861beac0e8f3169 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - expat 2.8.1.* + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 71631 + timestamp: 1781203724164 +- conda: https://conda.anaconda.org/conda-forge/win-64/libffi-3.7.0-h3d046cb_0.conda + sha256: 2ea8d2fe7b84ca37653777e15ac1e7abd35f0c90d3efbe7f6c4de9b489606369 + md5: 92bdfc0e5012660892b0e0eaf3069a5c + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libffi >=3.7.0,<3.8.0a0 + size: 50247 + timestamp: 1783521107166 +- conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype-2.14.3-h57928b3_2.conda + sha256: d794d7fddb6eea50e18a62fa27ab3819f40d51bad00b90cf4ca591fb0d4006c2 + md5: 740f99c3c91079c03874b809fedace1b + depends: + - libfreetype6 >=2.14.3 + license: GPL-2.0-only OR FTL + purls: [] + run_exports: {} + size: 8742 + timestamp: 1786641045882 +- conda: https://conda.anaconda.org/conda-forge/win-64/libfreetype6-2.14.3-hdbac1cb_2.conda + sha256: cbc650854003e434d4ff6c7b1a2667e38a4242ad8a391a1c5ff89721624065ce + md5: 8157483eb7ed3fcba71aad3b50a97131 + depends: + - libpng >=1.6.58,<1.7.0a0 + - libzlib >=1.3.2,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - freetype >=2.14.3 + license: GPL-2.0-only OR FTL + purls: [] + run_exports: {} + size: 340385 + timestamp: 1786641044865 +- conda: https://conda.anaconda.org/conda-forge/win-64/libgcc-16.1.0-h110b43a_3.conda + sha256: 125a3ea39e7308680a494416c5b8868bafefafc5f82987fae282c300614d979e + md5: e8d20d3561601e827dc754481294f40c + depends: + - _openmp_mutex >=4.5 + - libwinpthread >=12.0.0.r4.gg4f2fc60ca + constrains: + - libgcc-ng ==16.1.0=*_3 + - libgomp 16.1.0 h8ee18e1_3 + - msys2-conda-epoch <0.0a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: {} + size: 792903 + timestamp: 1787333031804 +- conda: https://conda.anaconda.org/conda-forge/win-64/libglib-2.88.3-he810d59_1.conda + sha256: fd94edec4f945c82ba6b983aae2bec21dd08209a5b387fb7a713534bb3db51af + md5: d8b3236ab45b58a0c4f4aa0a286cee13 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - pcre2 >=10.47,<10.48.0a0 + - libffi >=3.7.0,<3.8.0a0 + - libintl >=0.22.5,<1.0a0 + - libiconv >=1.18,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + constrains: + - glib >2.66 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - libglib >=2.88.3,<3.0a0 + size: 4518977 + timestamp: 1786457672418 +- conda: https://conda.anaconda.org/conda-forge/win-64/libgomp-16.1.0-h8ee18e1_3.conda + sha256: 6ede93ef60404209453cddf8f1cae5b8d05dcc0c39e1021db1dace88db7543cf + md5: fa88e8a67d2f6370f7b3cd45e8861dfd + depends: + - libwinpthread >=12.0.0.r4.gg4f2fc60ca + constrains: + - msys2-conda-epoch <0.0a0 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: + strong: + - _openmp_mutex >=4.5 + - libgomp >=16.1.0 + size: 627339 + timestamp: 1787332971056 +- conda: https://conda.anaconda.org/conda-forge/win-64/libharfbuzz-14.3.1-h03b5201_0.conda + sha256: 79fa7b957cb1fa539b5be09a67872360e507a3f25501a0ceb26cda2b5091686e + md5: 72c117cd3215779e10bf16266db1d70e + depends: + - cairo >=1.18.4,<2.0a0 + - graphite2 >=1.3.15,<2.0a0 + - icu >=78.3,<79.0a0 + - libexpat >=2.8.1,<3.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libglib >=2.88.3,<3.0a0 + - libpng >=1.6.58,<1.7.0a0 + - libzlib >=1.3.2,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 1042074 + timestamp: 1786971066342 +- conda: https://conda.anaconda.org/conda-forge/win-64/libhwloc-2.13.0-default_h049141e_1000.conda + sha256: 2ee12e37223dfcd0acd050c80a91150c482b6e2899198521e1800dce66662467 + md5: 6a01c986e30292c715038d2788aa1385 + depends: + - libwinpthread >=12.0.0.r4.gg4f2fc60ca + - libxml2 + - libxml2-16 >=2.14.6 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libhwloc >=2.13.0,<2.13.1.0a0 + size: 2396128 + timestamp: 1770954127918 +- conda: https://conda.anaconda.org/conda-forge/win-64/libiconv-1.18-hc1393d2_3.conda + sha256: 35e04e3ddac7720fc7c550a1c9f998604299d6a7bd1f3ec9b2825d825061daa2 + md5: a8a2abdf0f901bc4779d9b7be0845921 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: LGPL-2.1-only + purls: [] + run_exports: + weak: + - libiconv >=1.18,<2.0a0 + size: 694899 + timestamp: 1787033851701 +- conda: https://conda.anaconda.org/conda-forge/win-64/libintl-0.22.5-h5728263_3.conda + sha256: c7e4600f28bcada8ea81456a6530c2329312519efcf0c886030ada38976b0511 + md5: 2cf0cf76cc15d360dfa2f17fd6cf9772 + depends: + - libiconv >=1.17,<2.0a0 + license: LGPL-2.1-or-later + purls: [] + run_exports: + weak: + - libintl >=0.22.5,<1.0a0 + size: 95568 + timestamp: 1723629479451 +- conda: https://conda.anaconda.org/conda-forge/win-64/libjpeg-turbo-3.2.0-hfd05255_1.conda + sha256: df78ab4c0eecb3dd9331898f96baeed8e5ca1c363346332517abeb0b614b9a53 + md5: fc2c23bacefd1e733f1e1d6cd3b2aaeb + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - jpeg <0.0.0a + license: IJG AND BSD-3-Clause AND Zlib + purls: [] + run_exports: + weak: + - libjpeg-turbo >=3.2.0,<4.0a0 + size: 990125 + timestamp: 1785896494014 +- conda: https://conda.anaconda.org/conda-forge/win-64/liblapack-3.11.0-9_hf9ab0e9_mkl.conda + build_number: 9 + sha256: 62d0a7a70ee13c554d5d7ff94a2ba758c4111439822dcc81324dd4cfaf576071 + md5: bee6f13ab945c4034bdd0f722ad14dd5 + depends: + - libblas 3.11.0 9_h8455456_mkl + constrains: + - blas 2.309 mkl + - libcblas 3.11.0 9*_mkl + - liblapacke 3.11.0 9*_mkl + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - liblapack >=3.11.0,<3.12.0a0 + size: 79963 + timestamp: 1786059243440 +- conda: https://conda.anaconda.org/conda-forge/win-64/liblapacke-3.11.0-9_h3ae206f_mkl.conda + build_number: 9 + sha256: 888dec7ff7b99ab912056090efc956d9042f6440069353f43dbbaadd26aa4cd2 + md5: 9f4598c34a84e78d4d6d5db97e7350f7 + depends: + - libblas 3.11.0 9_h8455456_mkl + - libcblas 3.11.0 9_h2a3cdd5_mkl + - liblapack 3.11.0 9_hf9ab0e9_mkl + constrains: + - blas 2.309 mkl + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - liblapacke >=3.11.0,<3.12.0a0 + size: 84178 + timestamp: 1786059255731 +- conda: https://conda.anaconda.org/conda-forge/win-64/liblzma-5.8.3-hfd05255_1.conda + sha256: d36c4a1e1f80fd08e18a407e03622ff2f34dfdd022da6488ad19603dea19e6d5 + md5: 880a0c8549479b198af21ba5dc49b109 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - xz 5.8.3.* + license: 0BSD + purls: [] + run_exports: + weak: + - liblzma >=5.8.3,<6.0a0 + size: 105809 + timestamp: 1786348717883 +- conda: https://conda.anaconda.org/conda-forge/win-64/libmpdec-4.0.0-hfd05255_2.conda + sha256: f07e451de3db1836b87f7aedf95c8e65cdb06c0e6105329ba24bb5f7b5c75e2a + md5: 5ae92fd6614edd024576e14069d7ad4c + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 89109 + timestamp: 1786650384519 +- conda: https://conda.anaconda.org/conda-forge/win-64/libpng-1.6.58-hdc8cecf_1.conda + sha256: 8c49c32adf3ba2c59783630b82377f54ab72204ea99e2bafc90a47a8e25c1032 + md5: 5aa7348e73691187c81d51616f17e48a + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libzlib >=1.3.2,<2.0a0 + license: zlib-acknowledgement + purls: [] + run_exports: + weak: + - libpng >=1.6.58,<1.7.0a0 + size: 385462 + timestamp: 1786616543374 +- conda: https://conda.anaconda.org/conda-forge/win-64/libraqm-0.11.0-h50d6d30_0.conda + sha256: 1bc52f781355e233411a7aaebbb155d9fd53efa2a1fc6c621250833d80cc5ab1 + md5: df92be5685f712989830bdb7ee39383a + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libharfbuzz >=14.2.1 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - fribidi >=1.0.16,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libraqm >=0.11.0,<0.12.0a0 + size: 33471 + timestamp: 1784850324004 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsodium-1.0.22-h6a83c73_2.conda + sha256: 7dc8d243cc8bcd12d1666640664288822eda5d30411a0b22ff05090bbac4c32b + md5: 2a001157df8205a7785ea69dd3270a8f + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: ISC + purls: [] + run_exports: + weak: + - libsodium >=1.0.22,<1.0.23.0a0 + size: 280204 + timestamp: 1787225747847 +- conda: https://conda.anaconda.org/conda-forge/win-64/libsqlite-3.53.4-hf5d6505_1.conda + sha256: 0a45d7c0f20146fff787a106f8fa187872e309c70975ff8f0936e188914c26ad + md5: a72d495965b144bb7da033642d389047 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: blessing + purls: [] + run_exports: + weak: + - libsqlite >=3.53.4,<4.0a0 + size: 1314919 + timestamp: 1787051140039 +- conda: https://conda.anaconda.org/conda-forge/win-64/libstdcxx-16.1.0-hae5796f_3.conda + sha256: 9beca52beac60dd9d3772a2ba73e78502c634b627e739560db54127aa34dd264 + md5: d830ed49961553edfb0445966d8334d5 + depends: + - libgcc 16.1.0 h110b43a_3 + - libwinpthread >=12.0.0.r4.gg4f2fc60ca + constrains: + - libstdcxx-ng ==16.1.0=*_3 + license: GPL-3.0-only WITH GCC-exception-3.1 + license_family: GPL + purls: [] + run_exports: {} + size: 7153890 + timestamp: 1787333055619 +- conda: https://conda.anaconda.org/conda-forge/win-64/libtiff-4.7.2-h8f73337_0.conda + sha256: ec6d66308a6d6abaf3225f2f185113e6172e77eb0fa8622af982d7a5d6d47a2c + md5: e83f459471905a04ebe15e21d063c49d + depends: + - lerc >=4.1.0,<5.0a0 + - libdeflate >=1.25,<1.26.0a0 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - liblzma >=5.8.3,<6.0a0 + - libzlib >=1.3.2,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - zstd >=1.5.7,<1.6.0a0 + license: HPND + purls: [] + run_exports: + weak: + - libtiff >=4.7.2,<4.8.0a0 + size: 1014598 + timestamp: 1783085017197 +- conda: https://conda.anaconda.org/conda-forge/win-64/libvulkan-loader-1.4.357.0-h477610d_2.conda + sha256: 4b094443126c5fc38d200eb0ea335b67e64d966d7aa737bb3141217a787b0c73 + md5: aaeed7feddbfd7454f8853a5eae8d902 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + constrains: + - libvulkan-headers 1.4.357.0.* + license: Apache-2.0 + purls: [] + run_exports: + weak: + - libvulkan-loader >=1.4.357.0,<2.0a0 + size: 288787 + timestamp: 1787491929908 +- conda: https://conda.anaconda.org/conda-forge/win-64/libwebp-base-1.6.0-h4d5522a_1.conda + sha256: 4470d98d3178b0d45492eed4808afe421d2e7808352b8d06c2dc29166b75d94d + md5: 35a9475e4cc999d52921f57c181979fc + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - libwebp 1.6.0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - libwebp-base >=1.6.0,<2.0a0 + size: 278886 + timestamp: 1785954716703 +- conda: https://conda.anaconda.org/conda-forge/win-64/libwinpthread-12.0.0.r4.gg4f2fc60ca-h57928b3_10.conda + sha256: 0fccf2d17026255b6e10ace1f191d0a2a18f2d65088fd02430be17c701f8ffe0 + md5: 8a86073cf3b343b87d03f41790d8b4e5 + depends: + - ucrt + constrains: + - pthreads-win32 <0.0a0 + - msys2-conda-epoch <0.0a0 + license: MIT AND BSD-3-Clause-Clear + purls: [] + run_exports: {} + size: 36621 + timestamp: 1759768399557 +- conda: https://conda.anaconda.org/conda-forge/win-64/libxcb-1.17.0-h874e120_1.conda + sha256: a99f266ade1140c3106cca0f71ae2b5627ff77e1e791db3837129d28d596800e + md5: 98046512ad7f2c44e48ff77bce854052 + depends: + - libgcc >=13 + - libwinpthread >=12.0.0.r4.gg4f2fc60ca + - pthread-stubs + - ucrt >=10.0.20348.0 + - xorg-libxau >=1.0.12,<2.0a0 + - xorg-libxdmcp >=1.1.5,<2.0a0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libxcb >=1.17.0,<2.0a0 + size: 1218652 + timestamp: 1787077697863 +- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-16-2.15.3-h3cfd58e_1.conda + sha256: 8163de24a7ddb4ebf9587c3a45ba950caf3690b9646b76f007ca15e6e52b5881 + md5: 6e7dadff3f3bde2d29d29a3ec34f2d58 + depends: + - icu >=78.3,<79.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libzlib >=1.3.2,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - libxml2 2.15.3 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 519962 + timestamp: 1787237653321 +- conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.15.3-h8ef44ab_1.conda + sha256: a83e9adcf7c50f20289dc6890707c8e4e84151b4204b0f7344998138807458d1 + md5: 45a5a1c709a69f14cf176220788d18a9 + depends: + - icu >=78.3,<79.0a0 + - libiconv >=1.18,<2.0a0 + - liblzma >=5.8.3,<6.0a0 + - libxml2-16 2.15.3 h3cfd58e_1 + - libzlib >=1.3.2,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libxml2 + - libxml2-16 >=2.15.3 + size: 43610 + timestamp: 1787237661577 +- conda: https://conda.anaconda.org/conda-forge/win-64/libxslt-1.1.43-h0fbe4c1_1.conda + sha256: 13da38939c2c20e7112d683ab6c9f304bfaf06230a2c6a7cf00359da1a003ec7 + md5: 46034d9d983edc21e84c0b36f1b4ba61 + depends: + - libxml2 + - libxml2-16 >=2.14.6 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - libxslt >=1.1.43,<2.0a0 + size: 420223 + timestamp: 1757963935611 +- conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.2-hfd05255_3.conda + sha256: 0629c2cc0404d3bb29d6baa7b4ba62da80797015e86de050db81ea5a07050527 + md5: 5d2ff29d465097458cc3ff6569151991 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - zlib 1.3.2 *_3 + license: Zlib + license_family: Other + purls: [] + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 58529 + timestamp: 1785276664143 +- conda: https://conda.anaconda.org/conda-forge/win-64/llvm-openmp-22.1.8-h4fa8253_1.conda + sha256: 41a804136fdade8cfa9db006f3c41cac1199a387e609c465d779cef271fda85e + md5: 8135c070cad2ee5bb28ea50c12e81ce9 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - intel-openmp <0.0a0 + - openmp 22.1.8|22.1.8.* + license: Apache-2.0 WITH LLVM-exception + license_family: APACHE + purls: [] + run_exports: + strong: + - llvm-openmp >=22.1.8 + size: 348946 + timestamp: 1787293654028 +- conda: https://conda.anaconda.org/conda-forge/win-64/llvmlite-0.48.0-py314h46b4103_1.conda + sha256: 62f997491572f3b83301b64a0056bba0e77db5a6612a3f9acf3876c3c830d963 + md5: b4006e5a63ca3f2d825a079712ff067e + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + - zstd >=1.5.7,<1.6.0a0 + - libzlib >=1.3.2,<2.0a0 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/llvmlite?source=hash-mapping + run_exports: {} + size: 28574162 + timestamp: 1784043479652 +- conda: https://conda.anaconda.org/conda-forge/win-64/m2-conda-epoch-20250515-0_x86_64.conda + build_number: 0 + sha256: 51e9214548f177db9c3fe70424e3774c95bf19cd69e0e56e83abe2e393228ba1 + md5: 7d60fb16df2cd07fbc3dbff1c9df4244 + constrains: + - msys2-conda-epoch <0.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - m2-conda-epoch 20250515 *_x86_64 + noarch: + - m2-conda-epoch 20250515 *_x86_64 + size: 7539 + timestamp: 1747330852019 +- conda: https://conda.anaconda.org/conda-forge/win-64/markupsafe-3.0.3-py314h2359020_1.conda + sha256: 02805a0f3cd168dbf13afc5e4aed75cc00fe538ce143527a6471485b36f5887c + md5: 8de7b40f8b30a8fcaa423c2537fe4199 + depends: + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + constrains: + - jinja2 >=3.0.0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/markupsafe?source=hash-mapping + run_exports: {} + size: 30022 + timestamp: 1772445159549 +- conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-3.11.1-py314h30c6bc1_2.conda + sha256: 3599b3ae70c4fd210e83d3e04920b70e763a3c3cc6a589f7a46776a339cf79c0 + md5: d8f7f4bb221698dda04dd955eb6f7e52 + depends: + - matplotlib-base >=3.11.1,<3.11.2.0a0 + - pyside6 >=6.7.2 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - tornado >=5 + license: PSF-2.0 + license_family: PSF + purls: [] + run_exports: {} + size: 15333 + timestamp: 1785211311397 +- conda: https://conda.anaconda.org/conda-forge/win-64/matplotlib-base-3.11.1-py314h2061cd4_2.conda + sha256: b3f19b71ea556c79b67a599929a4c05618fd4ae03d7412e65adc95ec627670c5 + md5: 88c095cee1bcd48178328c93920870a2 + depends: + - contourpy >=1.0.1 + - cycler >=0.10 + - fonttools >=4.28.2 + - freetype + - kiwisolver >=1.3.1 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libraqm >=0.11.0,<0.12.0a0 + - numpy >=1.25 + - numpy >=1.25,<3 + - packaging >=20.0 + - pillow >=9 + - pyparsing >=3 + - python >=3.14,<3.15.0a0 + - python-dateutil >=2.7 + - python_abi 3.14.* *_cp314 + - qhull >=2020.2,<2020.3.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/matplotlib?source=hash-mapping + run_exports: {} + size: 8810362 + timestamp: 1785211292854 +- conda: https://conda.anaconda.org/conda-forge/win-64/mkl-2026.1.0-hac47afa_234.conda + sha256: f7568a5ebf9f4a401a6d9da7be4f82a8794cf305e870e77923a5712ba7f4b964 + md5: f0510f9d5e501462d72a5c0c798dbcc3 + depends: + - llvm-openmp >=22.1.8 + - tbb >=2023.0.0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: LicenseRef-IntelSimplifiedSoftwareOct2022 + license_family: Proprietary + purls: [] + run_exports: {} + size: 114429478 + timestamp: 1786086389935 +- conda: https://conda.anaconda.org/conda-forge/win-64/mkl-devel-2026.1.0-h57928b3_234.conda + sha256: 1a7fcabf40552d35fd9ed5be0759ad129e0db56867fb3ab816beff1263b015bb + md5: 9adca581ae34f78252ca387128707ea3 + depends: + - mkl 2026.1.0 hac47afa_234 + - mkl-include 2026.1.0 h57928b3_234 + license: LicenseRef-IntelSimplifiedSoftwareOct2022 + license_family: Proprietary + purls: [] + run_exports: + weak: + - mkl >=2026.1.0,<2027.0a0 + size: 5448461 + timestamp: 1786086741635 +- conda: https://conda.anaconda.org/conda-forge/win-64/mkl-include-2026.1.0-h57928b3_234.conda + sha256: cf7d6f4cb879d4066c7ddcbc7795a6e6a993d538a417b0f838819b17d048ec61 + md5: 089c701d8617a906df5afd076a1daffa + license: LicenseRef-IntelSimplifiedSoftwareOct2022 + license_family: Proprietary + purls: [] + run_exports: {} + size: 793957 + timestamp: 1786086608942 +- conda: https://conda.anaconda.org/conda-forge/win-64/mkl-service-2.8.0-py314h6d79c4c_0.conda + sha256: e8486ba8a4ae490bbb343c20866b55230aaf07ffa5820e8477d894929daf0299 + md5: 6a0b559d040714f2d90b3bf1ca6c15a0 + depends: + - mkl >=2026.1.0,<2027.0a0 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/mkl-service?source=hash-mapping + run_exports: {} + size: 65912 + timestamp: 1785145741824 +- conda: https://conda.anaconda.org/conda-forge/win-64/numba-0.66.0-py314hb98de8c_1.conda + sha256: aca94d88bdde53f633e4264202c8516d664f4d156e476c7a046cb7bf64ba0957 + md5: 3f1b4917ec0f902bba25c6dd0e5a348a + depends: + - python + - llvmlite >=0.48.0,<0.49.0a0 + - numpy >=1.22.3,<2.5 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - numpy >=1.23,<3 + - python_abi 3.14.* *_cp314 + constrains: + - tbb >=2021.6.0 + - libopenblas !=0.3.6 + - cuda-version >=11.2 + - cudatoolkit >=11.2 + - scipy >=1.0 + - cuda-python >=11.6 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/numba?source=hash-mapping + run_exports: {} + size: 6178278 + timestamp: 1785940762943 +- conda: https://conda.anaconda.org/conda-forge/win-64/numpy-2.4.6-py314h02f10f6_0.conda + sha256: de0eee21d902fb45a58454e3739e04ede7d02bf7575ca0ae9f959f20fa15c76b + md5: df95e6c7325bbae2571e5cef5f9c8096 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libcblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 + - libblas >=3.9.0,<4.0a0 + - python_abi 3.14.* *_cp314 + constrains: + - numpy-base <0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/numpy?source=hash-mapping + run_exports: + weak: + - numpy >=1.23,<3 + size: 7318163 + timestamp: 1779169232086 +- conda: https://conda.anaconda.org/conda-forge/win-64/openjpeg-2.5.4-h0e57b4f_0.conda + sha256: 24342dee891a49a9ba92e2018ec0bde56cc07fdaec95275f7a55b96f03ea4252 + md5: e723ab7cc2794c954e1b22fde51c16e4 + depends: + - libpng >=1.6.55,<1.7.0a0 + - libtiff >=4.7.1,<4.8.0a0 + - libzlib >=1.3.1,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-2-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - openjpeg >=2.5.4,<3.0a0 + size: 245594 + timestamp: 1772624841727 +- conda: https://conda.anaconda.org/conda-forge/win-64/openssl-3.6.3-hf411b9b_1.conda + sha256: 2ebff5a1b5793e82495bf33c91fba040e11ff23333c2385ac66d0c3aee2cc14c + md5: a978392692a910ba1c8920ccb1e784b3 + depends: + - ca-certificates + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: [] + run_exports: + weak: + - openssl >=3.6.3,<4.0a0 + size: 9427535 + timestamp: 1785915614585 +- conda: https://conda.anaconda.org/conda-forge/win-64/pandoc-3.10.2-h57928b3_0.conda + sha256: afcb977d62149f0e0c7f946e50f29a0b7ac24ef04c6d11b29478ea11dd63d825 + md5: 7643f65d7e954c60f69b353ae536730a + license: GPL-2.0-or-later + license_family: GPL + purls: [] + run_exports: {} + size: 26943591 + timestamp: 1786704423753 +- conda: https://conda.anaconda.org/conda-forge/win-64/pcre2-10.47-h8466c1e_1.conda + sha256: d8e9ea26b52a09d880d1e5371830c8dd5b4c099a6db64dbdf8236440744b7499 + md5: 009af999dbe2d1db36a37187a4ca4eb4 + depends: + - bzip2 >=1.0.8,<2.0a0 + - libzlib >=1.3.2,<2.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - pcre2 >=10.47,<10.48.0a0 + size: 993241 + timestamp: 1787294653206 +- conda: https://conda.anaconda.org/conda-forge/win-64/pillow-12.3.0-py314h61b30b5_0.conda + sha256: c9dc3212ed0021974541072ee3765a5097a0721191c34ee485d7d7e94449648f + md5: 09b8e6ac8f4a257b59e5d3025f3c9c1d + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libwebp-base >=1.6.0,<2.0a0 + - libtiff >=4.7.1,<4.8.0a0 + - openjpeg >=2.5.4,<3.0a0 + - lcms2 >=2.19.1,<3.0a0 + - libxcb >=1.17.0,<2.0a0 + - libjpeg-turbo >=3.1.4.1,<4.0a0 + - tk >=8.6.13,<8.7.0a0 + - zlib-ng >=2.3.3,<2.4.0a0 + license: HPND + purls: + - pkg:pypi/pillow?source=hash-mapping + run_exports: {} + size: 989058 + timestamp: 1782912129930 +- conda: https://conda.anaconda.org/conda-forge/win-64/pixman-0.46.4-h5112557_3.conda + sha256: cad8b94c2b00264a15d469eed6dc53aac1c59c6d46f27ad1d91bfaf227cf136a + md5: 27c5be39d9e4d25fe85b89a069360d1e + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - pixman >=0.46.4,<1.0a0 + size: 257473 + timestamp: 1786106677325 +- conda: https://conda.anaconda.org/conda-forge/win-64/psutil-7.2.2-py314hc5dbbe4_1.conda + sha256: c975e08da7627e95d1f0b70ef9787e9153118e67bb5b31f5530c5c3827d9afe8 + md5: deeef9494cfffa58f5fd20bc6eb57664 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + purls: + - pkg:pypi/psutil?source=hash-mapping + run_exports: {} + size: 249966 + timestamp: 1787417377436 +- conda: https://conda.anaconda.org/conda-forge/win-64/pthread-stubs-0.4-hba3369d_1003.conda + sha256: 5546927a2e337d2903d6cdb028fb33723cdbcc45bf9abe1770fbde2c4ea8bce6 + md5: bc97d497656c9c661ffd3043aca90aa4 + depends: + - libgcc >=14 + - libwinpthread >=12.0.0.r4.gg4f2fc60ca + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 10120 + timestamp: 1786067833280 +- conda: https://conda.anaconda.org/conda-forge/win-64/pyside6-6.11.2-py314h447aaf0_0.conda + sha256: bf1553eebc31e8ba04689639e8ce235c3df5317414f0c4a11417f238297f1b17 + md5: 7c3ccff96dbc9f8e3c40142c7594ec34 + depends: + - python + - qt6-main 6.11.2.* + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libxml2 + - libxml2-16 >=2.14.6 + - libclang13 >=22.1.8 + - qt6-main >=6.11.2,<7.0a0 + - python_abi 3.14.* *_cp314 + - libvulkan-loader >=1.4.357.0,<2.0a0 + - libxslt >=1.1.43,<2.0a0 + license: LGPL-3.0-only + license_family: LGPL + purls: + - pkg:pypi/pyside6?source=hash-mapping + - pkg:pypi/shiboken6?source=hash-mapping + run_exports: {} + size: 11552286 + timestamp: 1787219450026 +- conda: https://conda.anaconda.org/conda-forge/win-64/pytensor-3.3.0-py314h443d124_0.conda + sha256: afc5e3836f2886eff9e5035ddb518acd07ef62f89c1e72fa1df8eddfbbe3feab + md5: 731852fdc6b3a561afed885eaa929e57 + depends: + - python + - pytensor-base ==3.3.0 np2py314hb7a55bc_0 + - gxx + - blas * mkl + - mkl-service + - python_abi 3.14.* *_cp314 + constrains: + - libstdcxx !=15.1.0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 10069 + timestamp: 1786614345920 +- conda: https://conda.anaconda.org/conda-forge/win-64/pytensor-base-3.3.0-np2py314hb7a55bc_0.conda + sha256: cd2fae7ea29d751cecfd82eeaa21c83cdb6d83c5ed159ffac98c0522bbd8121b + md5: f77cc243811952111800a5e6b8816324 + depends: + - python + - setuptools >=59.0.0 + - scipy >=1,<2 + - numpy >=2.0 + - numba >=0.58,<=0.66.0 + - filelock >=3.15 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - numpy >=1.25,<3 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pytensor?source=hash-mapping + run_exports: {} + size: 3232733 + timestamp: 1786614345920 +- conda: https://conda.anaconda.org/conda-forge/win-64/python-3.14.7-h53f6dd8_100_cp314.conda + build_number: 100 + sha256: 01cd1d39e5a24f029df0a8db0878dd442d552a82f584e18fd2ed366bc881633f + md5: 99dcbb9c65cb1fb10340bc7f6984ddfa + depends: + - bzip2 >=1.0.8,<2.0a0 + - libexpat >=2.8.1,<3.0a0 + - libffi >=3.7.0,<3.8.0a0 + - liblzma >=5.8.3,<6.0a0 + - libmpdec >=4.0.0,<5.0a0 + - libsqlite >=3.53.4,<4.0a0 + - libzlib >=1.3.2,<2.0a0 + - openssl >=3.5.7,<4.0a0 + - python_abi 3.14.* *_cp314 + - tk >=8.6.13,<8.7.0a0 + - tzdata + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - zstd >=1.5.7,<1.6.0a0 + license: Python-2.0 + purls: [] + run_exports: + weak: + - python_abi 3.14.* *_cp314 + noarch: + - python + size: 18052663 + timestamp: 1787154783216 + python_site_packages_path: Lib/site-packages +- conda: https://conda.anaconda.org/conda-forge/win-64/pywin32-312-py314hf700ef7_1.conda + sha256: 5a6ddd03a8441c2312a09f998dccb0bdeb1b906b45c9b8cd5e9f5a7965b0a604 + md5: 57992821d70e28562192fa29cff08260 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + license: PSF-2.0 + license_family: PSF + purls: + - pkg:pypi/pywin32?source=hash-mapping + run_exports: {} + size: 4473050 + timestamp: 1787374336870 +- conda: https://conda.anaconda.org/conda-forge/win-64/pywinpty-3.0.5-py314h51f0985_0.conda + sha256: 025a42fa3df042590f9f5efdf8473e98e654672871c9db029436f32e021cb900 + md5: 9f34ac4f934a5e6bdffa81db44b3ce82 + depends: + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + - winpty + license: MIT + license_family: MIT + purls: + - pkg:pypi/pywinpty?source=hash-mapping + run_exports: {} + size: 731340 + timestamp: 1785742446062 +- conda: https://conda.anaconda.org/conda-forge/win-64/pyyaml-6.0.3-py314h2359020_1.conda + sha256: a2aff34027aa810ff36a190b75002d2ff6f9fbef71ec66e567616ac3a679d997 + md5: 0cd9b88826d0f8db142071eb830bce56 + depends: + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - yaml >=0.2.5,<0.3.0a0 + license: MIT + license_family: MIT + purls: + - pkg:pypi/pyyaml?source=hash-mapping + run_exports: {} + size: 181257 + timestamp: 1770223460931 +- conda: https://conda.anaconda.org/conda-forge/win-64/pyzmq-27.2.0-py312h343a6d4_0.conda + noarch: python + sha256: 56d33fdddf6fb7ffb86120b8e2f1398cc406d0d3e85e5647d3a72cad05c17509 + md5: 58bcf20b110e99aa69ab892a6ced10f5 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - _python_abi3_support 1.* + - cpython >=3.12 + - zeromq >=4.3.5,<4.3.6.0a0 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pyzmq?source=compressed-mapping + run_exports: {} + size: 187939 + timestamp: 1787300948668 +- conda: https://conda.anaconda.org/conda-forge/win-64/qhull-2020.2-hc790b64_5.conda + sha256: 887d53486a37bd870da62b8fa2ebe3993f912ad04bd755e7ed7c47ced97cbaa8 + md5: 854fbdff64b572b5c0b470f334d34c11 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: LicenseRef-Qhull + purls: [] + run_exports: + weak: + - qhull >=2020.2,<2020.3.0a0 + size: 1377020 + timestamp: 1720814433486 +- conda: https://conda.anaconda.org/conda-forge/win-64/qt6-main-6.11.2-pl5321hfcac499_0.conda + sha256: 042c94a5a7a89b0b28a24f6ae6a2d3d609751edab78454057973ec5cc3e77d7d + md5: d98a163070d05c6ed4421432ee1bafb8 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libvulkan-loader >=1.4.357.0,<2.0a0 + - icu >=78.3,<79.0a0 + - libfreetype >=2.14.3 + - libfreetype6 >=2.14.3 + - libharfbuzz >=14.3.1 + - libzlib >=1.3.2,<2.0a0 + - double-conversion >=3.4.0,<3.5.0a0 + - libglib >=2.88.3,<3.0a0 + - krb5 >=1.22.2,<1.23.0a0 + - pcre2 >=10.47,<10.48.0a0 + - libpng >=1.6.58,<1.7.0a0 + - zstd >=1.5.7,<1.6.0a0 + - libsqlite >=3.53.4,<4.0a0 + - openssl >=3.5.7,<4.0a0 + - libjpeg-turbo >=3.2.0,<4.0a0 + - libtiff >=4.7.2,<4.8.0a0 + - libbrotlicommon >=1.2.0,<1.3.0a0 + - libbrotlienc >=1.2.0,<1.3.0a0 + - libbrotlidec >=1.2.0,<1.3.0a0 + - libwebp-base >=1.6.0,<2.0a0 + constrains: + - qt ==6.11.2 + license: LGPL-3.0-only + license_family: LGPL + purls: [] + run_exports: + weak: + - qt6-main >=6.11.2,<7.0a0 + size: 89682645 + timestamp: 1787049046883 +- conda: https://conda.anaconda.org/conda-forge/win-64/rpds-py-2026.6.3-py314h9f07db2_0.conda + sha256: d297e7ef5f062194cf76dcf7a2e0449af24012b895a06aa942805f2933a0256c + md5: 07602f253d1ad9436bcac564c9fe183f + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/rpds-py?source=compressed-mapping + run_exports: {} + size: 218916 + timestamp: 1787344535083 +- conda: https://conda.anaconda.org/conda-forge/win-64/safetensors-0.8.0-py314h9f07db2_0.conda + sha256: 096520c49c0ab0aa78935e54d8e5141bc01cc4a37eacb4d72e21ac5755e33631 + md5: 327246b3f9b6d7f95a0713b4d68b7cd4 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/safetensors?source=hash-mapping + run_exports: {} + size: 359273 + timestamp: 1781179723506 +- conda: https://conda.anaconda.org/conda-forge/win-64/scipy-1.18.0-py314h221f224_0.conda + sha256: 952db1642d707d2572b511b1207bfd8e8c114fdc99930c400b547eb8ab92ac19 + md5: a925cfb1429da2b1312c86516ae7c418 + depends: + - libblas >=3.9.0,<4.0a0 + - libcblas >=3.9.0,<4.0a0 + - liblapack >=3.9.0,<4.0a0 + - numpy <2.7 + - numpy >=1.23,<3 + - numpy >=2.0.0 + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/scipy?source=hash-mapping + run_exports: {} + size: 15353018 + timestamp: 1781914001107 +- conda: https://conda.anaconda.org/conda-forge/win-64/sqlalchemy-2.0.52-py314hc5dbbe4_0.conda + sha256: d3ab49c60febcc9edd55ec7f65a9e02b631e137fe609643c590e8151becacca7 + md5: caa67c9335b6f97b708186a6474a306b + depends: + - python + - greenlet !=0.4.17 + - typing-extensions >=4.6.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/sqlalchemy?source=hash-mapping + run_exports: {} + size: 3998837 + timestamp: 1786535220662 +- conda: https://conda.anaconda.org/conda-forge/win-64/tbb-2023.0.0-hd3d4ead_2.conda + sha256: 8a4053839b8e997a5965e2dff7d6cf3c77be62d82c0e48c8a04a5ed2d2e73035 + md5: 8ee01a693aecff5432069eaaf1183c45 + depends: + - libhwloc >=2.13.0,<2.13.1.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: APACHE + purls: [] + run_exports: {} + size: 156515 + timestamp: 1778673901757 +- conda: https://conda.anaconda.org/conda-forge/win-64/tk-8.6.13-h967ab96_4.conda + sha256: f19618a3a82cc483dacf1c70a30367ee7b0c7e71b90f1f80e14feff44fbd3688 + md5: dd9eb33c99d352b6356f86964d6040f7 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: TCL + purls: [] + run_exports: + weak: + - tk >=8.6.13,<8.7.0a0 + size: 3782376 + timestamp: 1787272860855 +- conda: https://conda.anaconda.org/conda-forge/win-64/tornado-6.5.8-py314h5a2d7ad_0.conda + sha256: 1cac25c190b60a4b6fbe2853c4989a4ce086564ee5f995c17d4b32885993068d + md5: 94dfb208db7d79437cfe9b4390706b5c + depends: + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/tornado?source=hash-mapping + run_exports: {} + size: 925990 + timestamp: 1786226866739 +- conda: https://conda.anaconda.org/conda-forge/win-64/ucrt-10.0.26100.0-h57928b3_0.conda + sha256: 3005729dce6f3d3f5ec91dfc49fc75a0095f9cd23bab49efb899657297ac91a5 + md5: 71b24316859acd00bdb8b38f5e2ce328 + constrains: + - vc14_runtime >=14.29.30037 + - vs2015_runtime >=14.29.30037 + license: LicenseRef-MicrosoftWindowsSDK10 + purls: [] + run_exports: {} + size: 694692 + timestamp: 1756385147981 +- conda: https://conda.anaconda.org/conda-forge/win-64/unicodedata2-17.0.1-py314h5a2d7ad_0.conda + sha256: 9041e463044944460f73f9528f2ec491180f0ffe857e3555aa8160b81050b8d9 + md5: d6b580a13384df5155c6ca19ee66854e + depends: + - python >=3.14,<3.15.0a0 + - python_abi 3.14.* *_cp314 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Apache-2.0 + license_family: Apache + purls: + - pkg:pypi/unicodedata2?source=hash-mapping + run_exports: {} + size: 406126 + timestamp: 1770909191618 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc-14.5-ha367084_41.conda + sha256: 35444c55a92e2f7f7ba26bc70f81e56e52344f7d064c0fd4b40a46a58517b79c + md5: aa805b5522c2a98fa286e551a1f48546 + depends: + - vc14_runtime >=14.51.36247 + track_features: + - vc14 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: {} + size: 21383 + timestamp: 1785359368566 +- conda: https://conda.anaconda.org/conda-forge/win-64/vc14_runtime-14.51.36247-habf1de7_41.conda + sha256: 4e4cb599cdc41bf2109d1464c127b5bcbddf548ce3e322e612afb691338b48f8 + md5: ac5333bb3d429361f23adf704cc49a78 + depends: + - ucrt >=10.0.20348.0 + - vcomp14 14.51.36247 habf1de7_41 + constrains: + - vs2015_runtime 14.51.36247.* *_41 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + run_exports: {} + size: 767955 + timestamp: 1785359364369 +- conda: https://conda.anaconda.org/conda-forge/win-64/vcomp14-14.51.36247-habf1de7_41.conda + sha256: 731e043390c9457299484d39e427221fc868a9249540a498a5a4f6456c7744d1 + md5: 350bb67a5c8e5f1c53347ac544ab6600 + depends: + - ucrt >=10.0.20348.0 + constrains: + - vs2015_runtime 14.51.36247.* *_41 + license: LicenseRef-MicrosoftVisualCpp2015-2022Runtime + license_family: Proprietary + purls: [] + run_exports: + strong: + - vcomp14 >=14.51.36247 + size: 155910 + timestamp: 1785359349999 +- conda: https://conda.anaconda.org/conda-forge/win-64/watchfiles-1.2.0-py314hc980628_1.conda + sha256: 262150bdbbecbe49096ef90b8233412d6134f4d43168e7e5c530d8b3252f6cb4 + md5: 5654b0a18935e2baecb89e33e2d6a530 + depends: + - python + - anyio >=3.0.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + license: MIT + license_family: MIT + purls: + - pkg:pypi/watchfiles?source=hash-mapping + run_exports: {} + size: 351309 + timestamp: 1781180337205 +- conda: https://conda.anaconda.org/conda-forge/win-64/websockets-17.0.1-py314h13f4da2_0.conda + sha256: 67ed3e8abf94be2a1447e19d490afdba9037aa84f4f29930d791888d3ec6bb35 + md5: 8cf9a416e9e7c87b9687508a30245398 + depends: + - python + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - python_abi 3.14.* *_cp314 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/websockets?source=hash-mapping + run_exports: {} + size: 487341 + timestamp: 1785597313208 +- conda: https://conda.anaconda.org/conda-forge/win-64/winpty-0.4.3-4.tar.bz2 + sha256: 9df10c5b607dd30e05ba08cbd940009305c75db242476f4e845ea06008b0a283 + md5: 1cee351bf20b830d991dbe0bc8cd7dfe + license: MIT + license_family: MIT + purls: [] + run_exports: {} + size: 1176306 +- conda: https://conda.anaconda.org/conda-forge/win-64/xorg-libxau-1.0.12-hba3369d_2.conda + sha256: 892ad69b1a9ecc3903ed5a1b18fa097013eaf462eeed3c6ff31bf5c12e71e84b + md5: 87aee04978ab77bf4c0f42b983c216cc + depends: + - libgcc >=14 + - libwinpthread >=12.0.0.r4.gg4f2fc60ca + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxau >=1.0.12,<2.0a0 + size: 110446 + timestamp: 1786381242485 +- conda: https://conda.anaconda.org/conda-forge/win-64/xorg-libxdmcp-1.1.5-hba3369d_2.conda + sha256: 21b11bb98c41ece4ce6069d86d826b50b3d73020fb631b97e59a0521dd9d08a1 + md5: 0e21a45f1bb429b6e35e82631e60554a + depends: + - libgcc >=14 + - libwinpthread >=12.0.0.r4.gg4f2fc60ca + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - xorg-libxdmcp >=1.1.5,<2.0a0 + size: 71362 + timestamp: 1786381239727 +- conda: https://conda.anaconda.org/conda-forge/win-64/yaml-0.2.5-h6a83c73_3.conda + sha256: 80ee68c1e7683a35295232ea79bcc87279d31ffeda04a1665efdb43cbd50a309 + md5: 433699cba6602098ae8957a323da2664 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + license: MIT + license_family: MIT + purls: [] + run_exports: + weak: + - yaml >=0.2.5,<0.3.0a0 + size: 63944 + timestamp: 1753484092156 +- conda: https://conda.anaconda.org/conda-forge/win-64/zeromq-4.3.5-h3a581c9_11.conda + sha256: c3e279cb309b153152fcdd6ee6d039ad996d563c849f06be39d85b8e3351df25 + md5: f016c0c5f9c01549b259146614786192 + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libsodium >=1.0.22,<1.0.23.0a0 + - krb5 >=1.22.2,<1.23.0a0 + license: MPL-2.0 + license_family: MOZILLA + purls: [] + run_exports: + weak: + - zeromq >=4.3.5,<4.3.6.0a0 + size: 265717 + timestamp: 1779124031378 +- conda: https://conda.anaconda.org/conda-forge/win-64/zlib-1.3.2-hfd05255_3.conda + sha256: 5a0b55df66ff07b4342e04967cef69f8bf348f6a0fb1cc1eca741c7b015dfe77 + md5: 945092a9bc1d0f250f7d5ecf51ecd471 + depends: + - libzlib 1.3.2 hfd05255_3 + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Zlib + license_family: Other + purls: [] + run_exports: + weak: + - libzlib >=1.3.2,<2.0a0 + size: 851288 + timestamp: 1785276674755 +- conda: https://conda.anaconda.org/conda-forge/win-64/zlib-ng-2.3.3-h0261ad2_1.conda + sha256: 71332532332d13b5dbe57074ddcf82ae711bdc132affa5a2982a29ffa06dc234 + md5: 46a21c0a4e65f1a135251fc7c8663f83 + depends: + - ucrt >=10.0.20348.0 + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + license: Zlib + license_family: Other + purls: [] + run_exports: + weak: + - zlib-ng >=2.3.3,<2.4.0a0 + size: 124542 + timestamp: 1770167984883 +- conda: https://conda.anaconda.org/conda-forge/win-64/zstd-1.5.7-h534d264_7.conda + sha256: ca7daae4f218a11fab82cc2857f0ea518ec3f46acec60490485347a4c22c6b3e + md5: e4ac308c39d6d0e131154976da67cf3b + depends: + - vc >=14.3,<15 + - vc14_runtime >=14.44.35208 + - ucrt >=10.0.20348.0 + - libzlib >=1.3.2,<2.0a0 + license: BSD-3-Clause + license_family: BSD + purls: [] + run_exports: + weak: + - zstd >=1.5.7,<1.6.0a0 + size: 387535 + timestamp: 1786599623274 +- pypi: . + name: pytensor-ml + requires_dist: + - numpy + - pytensor>=3.2.3,<4.0.0 + - safetensors + - jax ; extra == 'dev' + - mypy ; extra == 'dev' + - pre-commit ; extra == 'dev' + - pytest ; extra == 'dev' + - pytest-cov ; extra == 'dev' + - pyyaml ; extra == 'dev' + - scikit-learn ; extra == 'dev' + - matplotlib ; extra == 'examples' + - tqdm ; extra == 'examples' + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/01/a4/9b63d595d748e3aff8812b65eacc1a2c4bd90b7c2012e08e72373b4835eb/filelock-3.32.4-py3-none-any.whl + name: filelock + version: 3.32.4 + sha256: 22e58ca3b1ae3b98993b762d7338367ae64fe50252bf78d59da3bfebcdf1cedd + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/0f/26/885774c006de6620ed3d10f45d8e20fe0b8e6aad6d573211a2cbc8b3e528/numba-0.66.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: numba + version: 0.66.0 + sha256: e2b101f23b8b63978d334574d2039f27f0dccfe1d891756f33a2e2f3e4c88cf4 + requires_dist: + - llvmlite>=0.48.0.dev0,<0.49 + - numpy>=1.22,<2.5 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/15/4d/0eae213c5197c6a0cb4080f984c7295cb53af6d0ecba9ece190a6ae9560f/pytensor-3.3.0-cp314-cp314-win_amd64.whl + name: pytensor + version: 3.3.0 + sha256: 115ce884c378a476397071c07c3de228b6defbf064de0ccca8a8ebf369093b74 + requires_dist: + - setuptools>=59.0.0 + - scipy>=1,<2 + - numpy>=2.0 + - numba>=0.58,<=0.66.0 + - filelock>=3.15 + - pytensor[jax] ; extra == 'complete' + - pytensor[numba] ; extra == 'complete' + - pytensor[complete] ; extra == 'development' + - pytensor[tests] ; extra == 'development' + - pytensor[rtd] ; extra == 'development' + - pytest ; extra == 'tests' + - pre-commit ; extra == 'tests' + - pytest-cov>=2.6.1 ; extra == 'tests' + - coverage>=5.1 ; extra == 'tests' + - pytest-benchmark ; extra == 'tests' + - pytest-mock ; extra == 'tests' + - pytest-sphinx ; extra == 'tests' + - pytensor[kanren] ; extra == 'tests' + - sphinx>=5.1.0,<6 ; extra == 'rtd' + - pygments ; extra == 'rtd' + - pydot ; extra == 'rtd' + - jax ; extra == 'jax' + - jaxlib ; extra == 'jax' + - numba>=0.58,<=0.66.0 ; extra == 'numba' + - llvmlite ; extra == 'numba' + - etuples ; extra == 'kanren' + - logical-unification ; extra == 'kanren' + - minikanren ; extra == 'kanren' + - cons ; extra == 'kanren' + requires_python: '>=3.12,<3.15' +- pypi: https://files.pythonhosted.org/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl + name: safetensors + version: 0.8.0 + sha256: 096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f + requires_dist: + - safetensors[torch] ; extra == 'all' + - safetensors[numpy] ; extra == 'all' + - safetensors[jax] ; extra == 'all' + - safetensors[paddlepaddle] ; extra == 'all' + - safetensors[convert] ; extra == 'all' + - safetensors[quality] ; extra == 'all' + - safetensors[testing] ; extra == 'all' + - safetensors[torch] ; extra == 'convert' + - huggingface-hub>=1.4 ; extra == 'convert' + - safetensors[all] ; extra == 'dev' + - safetensors[pinned-tf] ; extra == 'dev' + - safetensors[numpy] ; extra == 'jax' + - flax>=0.6.3 ; extra == 'jax' + - jax>=0.3.25 ; extra == 'jax' + - jaxlib>=0.3.25 ; extra == 'jax' + - mlx>=0.0.9 ; extra == 'mlx' + - numpy>=1.24.6 ; extra == 'numpy' + - safetensors[numpy] ; extra == 'paddlepaddle' + - paddlepaddle>=2.4.1 ; extra == 'paddlepaddle' + - safetensors[numpy] ; extra == 'pinned-tf' + - tensorflow==2.18.0 ; extra == 'pinned-tf' + - ruff ; extra == 'quality' + - safetensors[numpy] ; extra == 'tensorflow' + - tensorflow>=2.11.0 ; extra == 'tensorflow' + - safetensors[numpy] ; extra == 'testing' + - h5py>=3.7.0 ; extra == 'testing' + - setuptools-rust>=1.12.0 ; extra == 'testing' + - pytest>=9.0 ; extra == 'testing' + - pytest-benchmark>=5.2 ; extra == 'testing' + - hypothesis>=6.70.2 ; extra == 'testing' + - fsspec>=2024.6.0 ; extra == 'testing' + - s3fs>=2024.6.0 ; extra == 'testing' + - safetensors[numpy] ; extra == 'tf-nightly' + - tf-nightly ; extra == 'tf-nightly' + - safetensors[numpy] ; extra == 'torch' + - torch>=2.4 ; extra == 'torch' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl + name: safetensors + version: 0.8.0 + sha256: fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774 + requires_dist: + - safetensors[torch] ; extra == 'all' + - safetensors[numpy] ; extra == 'all' + - safetensors[jax] ; extra == 'all' + - safetensors[paddlepaddle] ; extra == 'all' + - safetensors[convert] ; extra == 'all' + - safetensors[quality] ; extra == 'all' + - safetensors[testing] ; extra == 'all' + - safetensors[torch] ; extra == 'convert' + - huggingface-hub>=1.4 ; extra == 'convert' + - safetensors[all] ; extra == 'dev' + - safetensors[pinned-tf] ; extra == 'dev' + - safetensors[numpy] ; extra == 'jax' + - flax>=0.6.3 ; extra == 'jax' + - jax>=0.3.25 ; extra == 'jax' + - jaxlib>=0.3.25 ; extra == 'jax' + - mlx>=0.0.9 ; extra == 'mlx' + - numpy>=1.24.6 ; extra == 'numpy' + - safetensors[numpy] ; extra == 'paddlepaddle' + - paddlepaddle>=2.4.1 ; extra == 'paddlepaddle' + - safetensors[numpy] ; extra == 'pinned-tf' + - tensorflow==2.18.0 ; extra == 'pinned-tf' + - ruff ; extra == 'quality' + - safetensors[numpy] ; extra == 'tensorflow' + - tensorflow>=2.11.0 ; extra == 'tensorflow' + - safetensors[numpy] ; extra == 'testing' + - h5py>=3.7.0 ; extra == 'testing' + - setuptools-rust>=1.12.0 ; extra == 'testing' + - pytest>=9.0 ; extra == 'testing' + - pytest-benchmark>=5.2 ; extra == 'testing' + - hypothesis>=6.70.2 ; extra == 'testing' + - fsspec>=2024.6.0 ; extra == 'testing' + - s3fs>=2024.6.0 ; extra == 'testing' + - safetensors[numpy] ; extra == 'tf-nightly' + - tf-nightly ; extra == 'tf-nightly' + - safetensors[numpy] ; extra == 'torch' + - torch>=2.4 ; extra == 'torch' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/2a/49/59ea385dc3a62ff498ddf3cfff7c2b41b0f9f9d3c4122b3f1dcb6d6327fe/scipy-1.18.1-cp314-cp314-macosx_12_0_arm64.whl + name: scipy + version: 1.18.1 + sha256: 9554bcc6d715ee87a633a3cc8e7703c6628b100dd29cb8a2efc4c0533c7ff729 + requires_dist: + - numpy>=2.0.0,<2.8 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - scipy-doctest>=2.0.0 ; extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.19.1 ; extra == 'dev' + - pyrefly==0.63.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/4d/da/007be278565871eadd1ce7abda66532cecda68b63363be44f321f3600c43/pytensor-3.3.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl + name: pytensor + version: 3.3.0 + sha256: 76879ea107d2e0a2c775cf2b5f7edb63e8118017b04d62468a6cb2219b0805ca + requires_dist: + - setuptools>=59.0.0 + - scipy>=1,<2 + - numpy>=2.0 + - numba>=0.58,<=0.66.0 + - filelock>=3.15 + - pytensor[jax] ; extra == 'complete' + - pytensor[numba] ; extra == 'complete' + - pytensor[complete] ; extra == 'development' + - pytensor[tests] ; extra == 'development' + - pytensor[rtd] ; extra == 'development' + - pytest ; extra == 'tests' + - pre-commit ; extra == 'tests' + - pytest-cov>=2.6.1 ; extra == 'tests' + - coverage>=5.1 ; extra == 'tests' + - pytest-benchmark ; extra == 'tests' + - pytest-mock ; extra == 'tests' + - pytest-sphinx ; extra == 'tests' + - pytensor[kanren] ; extra == 'tests' + - sphinx>=5.1.0,<6 ; extra == 'rtd' + - pygments ; extra == 'rtd' + - pydot ; extra == 'rtd' + - jax ; extra == 'jax' + - jaxlib ; extra == 'jax' + - numba>=0.58,<=0.66.0 ; extra == 'numba' + - llvmlite ; extra == 'numba' + - etuples ; extra == 'kanren' + - logical-unification ; extra == 'kanren' + - minikanren ; extra == 'kanren' + - cons ; extra == 'kanren' + requires_python: '>=3.12,<3.15' +- pypi: https://files.pythonhosted.org/packages/52/94/d73da0d28f16c45bb9b0a5691b91610b0275c5ef0eb5e43c87cf2dc1bf31/scipy-1.18.1-cp314-cp314-win_amd64.whl + name: scipy + version: 1.18.1 + sha256: 78a0d7c918e74a232394117160e7e3db503377572a45bcef8826e4ab8a35feba + requires_dist: + - numpy>=2.0.0,<2.8 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - scipy-doctest>=2.0.0 ; extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.19.1 ; extra == 'dev' + - pyrefly==0.63.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/66/c5/b46ad28ac3681d035ea21365c5e052149062e1a0a9affd0563d2760ea6ff/numba-0.66.0-cp314-cp314-win_amd64.whl + name: numba + version: 0.66.0 + sha256: bd57790acd20f6a468e0ad333ef6b82355e309a92310fb7dff80e919f01a21a9 + requires_dist: + - llvmlite>=0.48.0.dev0,<0.49 + - numpy>=1.22,<2.5 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/6b/89/2a844506d49651e9aa1af6ef95b6bd8031cb1d5a4375edec6155037e04cf/scipy-1.18.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: scipy + version: 1.18.1 + sha256: ac0333bdf38309aa3dcbe7e3fa7ea29e7a2c37c6ea306a757b700ded8e4596ad + requires_dist: + - numpy>=2.0.0,<2.8 + - pytest>=8.0.0 ; extra == 'test' + - pytest-cov ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-xdist ; extra == 'test' + - asv ; extra == 'test' + - mpmath ; extra == 'test' + - gmpy2 ; extra == 'test' + - threadpoolctl ; extra == 'test' + - scikit-umfpack ; extra == 'test' + - pooch ; extra == 'test' + - hypothesis>=6.30 ; extra == 'test' + - array-api-strict>=2.3.1 ; extra == 'test' + - cython ; extra == 'test' + - meson ; extra == 'test' + - ninja ; sys_platform != 'emscripten' and extra == 'test' + - scipy-doctest>=2.0.0 ; extra == 'test' + - sphinx>=5.0.0,<8.2.0 ; extra == 'doc' + - intersphinx-registry ; extra == 'doc' + - pydata-sphinx-theme>=0.15.2 ; extra == 'doc' + - sphinx-copybutton ; extra == 'doc' + - sphinx-design>=0.4.0 ; extra == 'doc' + - matplotlib>=3.5 ; extra == 'doc' + - numpydoc ; extra == 'doc' + - jupytext ; extra == 'doc' + - myst-nb>=1.2.0 ; extra == 'doc' + - pooch ; extra == 'doc' + - jupyterlite-sphinx>=0.19.1 ; extra == 'doc' + - jupyterlite-pyodide-kernel ; extra == 'doc' + - linkify-it-py ; extra == 'doc' + - tabulate ; extra == 'doc' + - click<8.3.0 ; extra == 'dev' + - spin ; extra == 'dev' + - mypy==1.19.1 ; extra == 'dev' + - pyrefly==0.63.0 ; extra == 'dev' + - typing-extensions ; extra == 'dev' + - types-psutil ; extra == 'dev' + - pycodestyle ; extra == 'dev' + - ruff>=0.12.0 ; extra == 'dev' + - cython-lint>=0.12.2 ; extra == 'dev' + requires_python: '>=3.12' +- pypi: https://files.pythonhosted.org/packages/8d/8e/8170f2e0c217f88069c333d85bb976e536b332aecfcce606ddbdb249385f/llvmlite-0.48.0-cp314-cp314-macosx_12_0_arm64.whl + name: llvmlite + version: 0.48.0 + sha256: 321f1ac39b462603f0b589751aecf2d237d056f6d005749c1752b6f23ec3f074 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/8e/62/764ce66fa4147ae6d73071a3abf804ffe606f174618697c571acdf26a7c9/numpy-2.4.6-cp314-cp314-macosx_11_0_arm64.whl + name: numpy + version: 2.4.6 + sha256: 38efbc8de75c7a0fc1ac190162d892787f3f47b57cc291231aafee36b80982b7 + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/95/9c/c510029fc6ef33a6275cd2c5d3cecd6613dfd6aa401d57c54f1c18852ccf/setuptools-84.0.0-py3-none-any.whl + name: setuptools + version: 84.0.0 + sha256: 51a52592b3b99e102b609654876bd65f19f999935166d1352678931132b0c670 + requires_dist: + - pytest>=6,!=8.1.* ; extra == 'test' + - virtualenv>=13.0.0 ; extra == 'test' + - wheel>=0.44.0 ; extra == 'test' + - pip>=19.1 ; extra == 'test' + - packaging>=24.2 ; extra == 'test' + - jaraco-envs>=2.2 ; extra == 'test' + - pytest-xdist>=3 ; extra == 'test' + - jaraco-path>=3.7.2 ; extra == 'test' + - build[virtualenv]>=1.0.3 ; extra == 'test' + - filelock>=3.4.0 ; extra == 'test' + - ini2toml[lite]>=0.14 ; extra == 'test' + - tomli-w>=1.0.0 ; extra == 'test' + - pytest-timeout ; extra == 'test' + - pytest-perf ; sys_platform != 'cygwin' and extra == 'test' + - jaraco-develop>=7.21 ; python_full_version >= '3.9' and sys_platform != 'cygwin' and extra == 'test' + - pytest-home>=0.5 ; extra == 'test' + - pytest-subprocess ; extra == 'test' + - pyproject-hooks!=1.1 ; extra == 'test' + - jaraco-test>=5.5 ; extra == 'test' + - sphinx>=3.5 ; extra == 'doc' + - jaraco-packaging>=9.3 ; extra == 'doc' + - rst-linker>=1.9 ; extra == 'doc' + - furo ; extra == 'doc' + - sphinx-lint ; extra == 'doc' + - jaraco-tidelift>=1.4 ; extra == 'doc' + - pygments-github-lexers==0.0.5 ; extra == 'doc' + - sphinx-favicon ; extra == 'doc' + - sphinx-inline-tabs ; extra == 'doc' + - sphinx-reredirects ; extra == 'doc' + - sphinxcontrib-towncrier ; extra == 'doc' + - sphinx-notfound-page>=1,<2 ; extra == 'doc' + - pyproject-hooks!=1.1 ; extra == 'doc' + - towncrier<24.7 ; extra == 'doc' + - packaging>=24.2 ; extra == 'core' + - more-itertools>=8.8 ; extra == 'core' + - jaraco-text>=3.7 ; extra == 'core' + - importlib-metadata>=6 ; python_full_version < '3.10' and extra == 'core' + - tomli>=2.0.1 ; python_full_version < '3.11' and extra == 'core' + - wheel>=0.43.0 ; extra == 'core' + - jaraco-functools>=4 ; extra == 'core' + - more-itertools ; extra == 'core' + - pytest-checkdocs>=2.14 ; extra == 'check' + - pytest-ruff>=0.2.1 ; sys_platform != 'cygwin' and extra == 'check' + - ruff>=0.13.0 ; sys_platform != 'cygwin' and extra == 'check' + - pytest-cov ; extra == 'cover' + - pytest-enabler>=3.4 ; extra == 'enabler' + - pytest-mypy>=1.0.1 ; platform_python_implementation != 'PyPy' and extra == 'type' + - mypy==1.18.* ; extra == 'type' + - importlib-metadata>=7.0.2 ; python_full_version < '3.10' and extra == 'type' + - jaraco-develop>=7.21 ; sys_platform != 'cygwin' and extra == 'type' + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/96/7a/7e0e73550eb4e41ede6e72fb5371f4539537a4d770a3b73fa9b61aea0622/numba-0.66.0-cp314-cp314-macosx_12_0_arm64.whl + name: numba + version: 0.66.0 + sha256: 46ae5f2b19e2af3c33c2df100306a90ea2f981c8158b0390f8bf6c20eee7357e + requires_dist: + - llvmlite>=0.48.0.dev0,<0.49 + - numpy>=1.22,<2.5 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/9e/74/3b17e08f1d9facfa7772ed32e1614f2e676f44093b2b717dae89a0c44161/pytensor-3.3.0-cp314-cp314-macosx_11_0_arm64.whl + name: pytensor + version: 3.3.0 + sha256: 5728796ba3bbefb196311495315ebcd7db39f19564674ab958d5e92e02df5ead + requires_dist: + - setuptools>=59.0.0 + - scipy>=1,<2 + - numpy>=2.0 + - numba>=0.58,<=0.66.0 + - filelock>=3.15 + - pytensor[jax] ; extra == 'complete' + - pytensor[numba] ; extra == 'complete' + - pytensor[complete] ; extra == 'development' + - pytensor[tests] ; extra == 'development' + - pytensor[rtd] ; extra == 'development' + - pytest ; extra == 'tests' + - pre-commit ; extra == 'tests' + - pytest-cov>=2.6.1 ; extra == 'tests' + - coverage>=5.1 ; extra == 'tests' + - pytest-benchmark ; extra == 'tests' + - pytest-mock ; extra == 'tests' + - pytest-sphinx ; extra == 'tests' + - pytensor[kanren] ; extra == 'tests' + - sphinx>=5.1.0,<6 ; extra == 'rtd' + - pygments ; extra == 'rtd' + - pydot ; extra == 'rtd' + - jax ; extra == 'jax' + - jaxlib ; extra == 'jax' + - numba>=0.58,<=0.66.0 ; extra == 'numba' + - llvmlite ; extra == 'numba' + - etuples ; extra == 'kanren' + - logical-unification ; extra == 'kanren' + - minikanren ; extra == 'kanren' + - cons ; extra == 'kanren' + requires_python: '>=3.12,<3.15' +- pypi: https://files.pythonhosted.org/packages/c9/2d/6a5171fb7236ac0895e1a02ccba3735bf291e8597239aa6421894d3c0ba8/llvmlite-0.48.0-cp314-cp314-win_amd64.whl + name: llvmlite + version: 0.48.0 + sha256: 966dcab0a598e2bd8fb5f2cc082cf7b07bae564fc485a3a8692393caf986facf + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/d6/e1/05b50692b647cac3c18200ac485b04f342f00ed173c9cc46767274469a15/llvmlite-0.48.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl + name: llvmlite + version: 0.48.0 + sha256: 05f0103c8f2f96a37441337e3643863c01b8e83e530aff38960dcb383c54a065 + requires_python: '>=3.10' +- pypi: https://files.pythonhosted.org/packages/df/ac/46de6dda46478f7942f839e094970be2d4a861e005c4b3bf07c92e291a09/numpy-2.4.6-cp314-cp314-win_amd64.whl + name: numpy + version: 2.4.6 + sha256: b507f5c4c1d508876d1819b6bf9a49d365b96320b5d4993426b33a23ca4b8261 + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/f3/eb/ebffaa97dc55502df69584a8f0dcf07f69a3e0b3e2323670a2722db9aa39/numpy-2.4.6-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl + name: numpy + version: 2.4.6 + sha256: a2c306dea656c12c68f51f4cea133cbe78ca7435eb28c735eac1d3ebe73be6e8 + requires_python: '>=3.11' +- pypi: https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl + name: safetensors + version: 0.8.0 + sha256: c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25 + requires_dist: + - safetensors[torch] ; extra == 'all' + - safetensors[numpy] ; extra == 'all' + - safetensors[jax] ; extra == 'all' + - safetensors[paddlepaddle] ; extra == 'all' + - safetensors[convert] ; extra == 'all' + - safetensors[quality] ; extra == 'all' + - safetensors[testing] ; extra == 'all' + - safetensors[torch] ; extra == 'convert' + - huggingface-hub>=1.4 ; extra == 'convert' + - safetensors[all] ; extra == 'dev' + - safetensors[pinned-tf] ; extra == 'dev' + - safetensors[numpy] ; extra == 'jax' + - flax>=0.6.3 ; extra == 'jax' + - jax>=0.3.25 ; extra == 'jax' + - jaxlib>=0.3.25 ; extra == 'jax' + - mlx>=0.0.9 ; extra == 'mlx' + - numpy>=1.24.6 ; extra == 'numpy' + - safetensors[numpy] ; extra == 'paddlepaddle' + - paddlepaddle>=2.4.1 ; extra == 'paddlepaddle' + - safetensors[numpy] ; extra == 'pinned-tf' + - tensorflow==2.18.0 ; extra == 'pinned-tf' + - ruff ; extra == 'quality' + - safetensors[numpy] ; extra == 'tensorflow' + - tensorflow>=2.11.0 ; extra == 'tensorflow' + - safetensors[numpy] ; extra == 'testing' + - h5py>=3.7.0 ; extra == 'testing' + - setuptools-rust>=1.12.0 ; extra == 'testing' + - pytest>=9.0 ; extra == 'testing' + - pytest-benchmark>=5.2 ; extra == 'testing' + - hypothesis>=6.70.2 ; extra == 'testing' + - fsspec>=2024.6.0 ; extra == 'testing' + - s3fs>=2024.6.0 ; extra == 'testing' + - safetensors[numpy] ; extra == 'tf-nightly' + - tf-nightly ; extra == 'tf-nightly' + - safetensors[numpy] ; extra == 'torch' + - torch>=2.4 ; extra == 'torch' + requires_python: '>=3.10' diff --git a/pyproject.toml b/pyproject.toml index 7210396..d00f94e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -146,3 +146,43 @@ warn_unused_ignores = true warn_return_any = false warn_unreachable = true files = ["pytensor_ml"] + +[tool.pixi.workspace] +channels = ["conda-forge"] +platforms = ["osx-arm64", "linux-64", "win-64"] + +# Autodoc imports pytensor_ml, so the docs environment needs the runtime stack alongside sphinx. +# Read the Docs cannot read a pixi environment, so it builds from conda_envs/environment-docs.yml; +# the two lists have to move together. +[tool.pixi.feature.docs.dependencies] +python = ">=3.12" +pytensor = ">=3.3.0,<3.4.0" +numpy = "*" +safetensors = "*" +# The gallery extension renders notebook thumbnails with matplotlib. +matplotlib = "*" +ipython = "*" +jupyter = "*" +sphinx = ">=7" +pydata-sphinx-theme = "*" +myst-nb = "*" +numpydoc = "*" +sphinx-copybutton = "*" +sphinx-design = "*" +sphinx-codeautolink = "*" +sphinx-sitemap = "*" +sphinx-notfound-page = "*" +sphinx-autobuild = "*" +jupyter-sphinx = "*" +sphinxcontrib-bibtex = "*" + +[tool.pixi.feature.docs.pypi-dependencies] +pytensor_ml = { path = ".", editable = true } + +[tool.pixi.feature.docs.tasks] +docs-build = { cmd = "sphinx-build -b html source build/html", cwd = "docs" } +# Rebuilds on save and serves the result at http://localhost:8000. +docs-serve = { cmd = "sphinx-autobuild source build/html --port 8000 --open-browser", cwd = "docs" } + +[tool.pixi.environments] +docs = { features = ["docs"] } From 29f7a5265a319629b8c7ffcd8ff09d7bb12af393 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 22:07:43 -0500 Subject: [PATCH 07/26] Rewrite the README around installation, docs and contributing --- README.md | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c157c69..b97dae2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,92 @@ # pytensor_ml -Neural network package built on Pytensor + +A(nother) deep learning library, built on top of [PyTensor](https://github.com/pymc-devs/pytensor). + +Networks are ordinary PyTensor graphs. You build one out of layers, and everything PyTensor already does — +symbolic differentiation, graph rewrites, and compilation to C, Numba, JAX, PyTorch, or MLX — applies to it +unchanged. Training is a compiled function that takes a batch and returns a loss; there is no separate runtime +or tape. + +> **Status: pre-alpha.** The API is still moving, and there is no release-to-release compatibility guarantee yet. + +## Installation + +```bash +pip install pytensor-ml +``` + +The only hard dependencies are `pytensor`, `numpy`, and `safetensors`. A backend beyond the default (`numba`, +`jax`, `torch`, `mlx`) is installed separately, and only loads when you actually compile against it. + +## Quickstart + +Train a classifier on scikit-learn's digits, then run inference: + +```python +import numpy as np +import pytensor + +pytensor.config.floatX = "float32" + +from sklearn.datasets import load_digits + +from pytensor_ml.activations import ReLU +from pytensor_ml.layers import Input, Linear, Sequential +from pytensor_ml.loss import CrossEntropy +from pytensor_ml.model import Model +from pytensor_ml.optim import adam, chain, clip_by_global_norm, cosine_schedule +from pytensor_ml.util import DataLoader + +X, y = load_digits(return_X_y=True) +X = (X / 16.0).astype("float32") +y_onehot = np.eye(10, dtype="float32")[y] + +X_in = Input("X_in", shape=(None, 64)) +network = Sequential( + Linear("fc1", n_in=64, n_out=128), + ReLU(), + Linear("logits", n_in=128, n_out=10), +) +model = Model(X_in, network(X_in)).initialize(seed=0) + +rule = chain(adam(learning_rate=cosine_schedule(1e-3, total_steps=500)), clip_by_global_norm(1.0)) +loss_fn = CrossEntropy(expect_onehot_labels=True, expect_logits=True, reduction="mean") +step = model.compile_train(rule, loss_fn, ndim_out=2) + +loader = DataLoader(X, y_onehot, batch_size=64, random_state=0) +for _ in range(500): + loss_value = step(*loader()) + +accuracy = (model.predict(X).argmax(axis=-1) == y).mean() +``` + +`compile_train` builds the loss against a target placeholder, differentiates it, folds in any stateful layer +updates (batch norm running statistics, RNG advances, the training clock a schedule reads), and compiles a +one-step function. `predict` compiles a separate inference pass, with dropout removed and batch norm reading +its running statistics. + +## Documentation + +The full API reference and user guide live at +[pytensor-ml.readthedocs.io](https://pytensor-ml.readthedocs.io). + +For worked models end to end — training loops, convolutional and recurrent networks, transformers, saving and +reloading — see the [examples gallery](https://pytensor-ml.readthedocs.io/en/latest/examples/gallery.html). + +## Contributing + +Contributions are welcome. To get set up: + +```bash +pip install -e ".[dev]" +pre-commit install +pytest +``` + +Formatting and linting run through `ruff` under pre-commit, and `mypy` checks `pytensor_ml/`; both also run in +CI. Bug reports and feature requests belong in the +[issue tracker](https://github.com/pymc-devs/pytensor-ml/issues). + +## License + +Apache 2.0. See [LICENSE](LICENSE). From 8cb4b527f8f37cadb5185841587b2ac13e8ebbb0 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 22:50:04 -0500 Subject: [PATCH 08/26] Add a test that runs every docstring example Each block executes in a fresh namespace and its own directory, so an example that omits an import or leans on another one fails rather than shipping broken. --- .github/workflows/run_tests.yml | 2 + tests/public_api.py | 79 ++++++++++++++++++++++++++++++++ tests/test_docstring_examples.py | 57 +++++++++++++++++++++++ tests/test_docstrings.py | 27 +---------- 4 files changed, 140 insertions(+), 25 deletions(-) create mode 100644 tests/public_api.py create mode 100644 tests/test_docstring_examples.py diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 4dd5960..a1f190e 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -58,6 +58,8 @@ jobs: tests/test_collect.py tests/test_util.py tests/test_workflow_groups.py + tests/test_docstrings.py + tests/test_docstring_examples.py # Windows runners are roughly twice as slow, and almost everything here is platform independent. # It runs one job over the parts that are not: file IO, paths, and compiling a graph to train. # One job per backend, each installing only that backend. They live here rather than in diff --git a/tests/public_api.py b/tests/public_api.py new file mode 100644 index 0000000..7527d3f --- /dev/null +++ b/tests/public_api.py @@ -0,0 +1,79 @@ +import ast +import importlib +import inspect +import itertools +import pkgutil +import types + +import pytensor_ml + +# Importing a backend dispatch module pulls in the backend itself, and the core test jobs deliberately +# install none of them. +BACKEND_DISPATCH = "pytensor_ml.dispatch." + + +def public_objects() -> list[tuple[str, object]]: + """ + Collect every public object the package defines, for tests that sweep the whole API. + + Returns + ------- + objects : list of tuple of str and object + Each object paired with its qualified name, listed under the module that defines it rather than + every module that re-exports it. + """ + objects: list[tuple[str, object]] = [] + for module_info in pkgutil.walk_packages(pytensor_ml.__path__, f"{pytensor_ml.__name__}."): + if module_info.name.startswith(BACKEND_DISPATCH): + continue + module = importlib.import_module(module_info.name) + objects.append((module_info.name, module)) + for name, obj in vars(module).items(): + if not name.startswith("_") and getattr(obj, "__module__", None) == module_info.name: + objects.append((f"{module_info.name}.{name}", obj)) + return objects + + +def _assigned_name(node: ast.stmt) -> str | None: + if ( + isinstance(node, ast.Assign) + and len(node.targets) == 1 + and isinstance(node.targets[0], ast.Name) + ): + return node.targets[0].id + if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name): + return node.target.id + if isinstance(node, ast.TypeAlias) and isinstance(node.name, ast.Name): + return node.name.id + return None + + +def attribute_docstrings(module: types.ModuleType) -> list[tuple[str, str]]: + """ + Collect the docstrings written under a module's public assignments. + + A type alias cannot carry ``__doc__``, so the string literal below its assignment is the only + documentation it has, and the only place Sphinx looks. + + Parameters + ---------- + module : module + The module to read. + + Returns + ------- + documented : list of tuple of str and str + Each qualified attribute name paired with the docstring written beneath it. + """ + body = ast.parse(inspect.getsource(module)).body + documented = [] + for assignment, following in itertools.pairwise(body): + name = _assigned_name(assignment) + is_docstring = ( + isinstance(following, ast.Expr) + and isinstance(following.value, ast.Constant) + and isinstance(following.value.value, str) + ) + if name and not name.startswith("_") and is_docstring: + documented.append((f"{module.__name__}.{name}", following.value.value)) + return documented diff --git a/tests/test_docstring_examples.py b/tests/test_docstring_examples.py new file mode 100644 index 0000000..3f909b4 --- /dev/null +++ b/tests/test_docstring_examples.py @@ -0,0 +1,57 @@ +import textwrap +import types + +import pytest + +from tests.public_api import attribute_docstrings, public_objects + +CODE_BLOCK_DIRECTIVE = ".. code-block:: python" + + +def _example_blocks(docstring: str) -> list[str]: + lines = docstring.splitlines() + start = next((i for i, line in enumerate(lines) if line.strip() == "Examples"), None) + if start is None: + return [] + + blocks = [] + for index, line in enumerate(lines[start:], start=start): + if line.strip() != CODE_BLOCK_DIRECTIVE: + continue + directive_indent = len(line) - len(line.lstrip()) + body = [] + for candidate in lines[index + 1 :]: + indent = len(candidate) - len(candidate.lstrip()) + if candidate.strip() and indent <= directive_indent: + break + body.append(candidate) + code = textwrap.dedent("\n".join(body)).strip() + # An empty block means the directive is there but its body is not indented under it, which used to + # pass as a trivially successful exec. + assert code, f"code-block at line {index} has no indented body" + blocks.append(code) + return blocks + + +def _collect_examples() -> list[tuple[str, str]]: + examples = [] + for qualified_name, obj in public_objects(): + docstrings = [(qualified_name, obj.__doc__ or "")] + if isinstance(obj, types.ModuleType): + docstrings.extend(attribute_docstrings(obj)) + for name, docstring in docstrings: + for position, block in enumerate(_example_blocks(docstring)): + examples.append((f"{name}[{position}]", block)) + return examples + + +EXAMPLES = _collect_examples() + + +@pytest.mark.parametrize("qualified_name, source", EXAMPLES, ids=[name for name, _ in EXAMPLES]) +def test_docstring_example_runs(qualified_name, source, tmp_path, monkeypatch): + # A fresh namespace per block: an example that leans on a name another example imported is not the + # self-contained snippet a reader is invited to paste into a script. Each runs in its own directory + # so an example that writes a checkpoint can use the plain relative path a reader would. + monkeypatch.chdir(tmp_path) + exec(compile(source, f"<{qualified_name}>", "exec"), {"__name__": "__main__"}) diff --git a/tests/test_docstrings.py b/tests/test_docstrings.py index cda90df..cfadfb9 100644 --- a/tests/test_docstrings.py +++ b/tests/test_docstrings.py @@ -1,36 +1,13 @@ -import importlib -import pkgutil - import pytest -import pytensor_ml +from tests.public_api import public_objects # A LaTeX macro in a docstring that forgot its `r` prefix silently becomes a control character: # `\text` is a tab, `\rceil` a carriage return, `\b` a backspace. The docstring still renders, just # wrong, so nothing but a scan like this catches it. CONTROL_CHARACTERS = {"\t": r"\t", "\r": r"\r", "\x08": r"\b", "\x0c": r"\f", "\x0b": r"\v"} - -# Importing a backend dispatch module pulls in the backend itself, and the core test jobs deliberately -# install none of them. -BACKEND_DISPATCH = "pytensor_ml.dispatch." - - -def _public_objects() -> list[tuple[str, object]]: - objects: list[tuple[str, object]] = [] - for module_info in pkgutil.walk_packages(pytensor_ml.__path__, f"{pytensor_ml.__name__}."): - if module_info.name.startswith(BACKEND_DISPATCH): - continue - module = importlib.import_module(module_info.name) - objects.append((module_info.name, module)) - for name, obj in vars(module).items(): - # Skip re-exports so each object is checked once, under the module that defines it. - if not name.startswith("_") and getattr(obj, "__module__", None) == module_info.name: - objects.append((f"{module_info.name}.{name}", obj)) - return objects - - -PUBLIC_OBJECTS = _public_objects() +PUBLIC_OBJECTS = public_objects() @pytest.mark.parametrize( From cc528bf00d54f6652a3ac88be3ec09dc827ae33b Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 22:50:17 -0500 Subject: [PATCH 09/26] Add examples to the model, loss and activation docstrings --- pytensor_ml/activations.py | 156 ++++++++++++++++++++++++++++++++++++- pytensor_ml/loss.py | 105 +++++++++++++++++++++++++ pytensor_ml/model.py | 33 +++++++- pytensor_ml/util.py | 17 ++++ 4 files changed, 309 insertions(+), 2 deletions(-) diff --git a/pytensor_ml/activations.py b/pytensor_ml/activations.py index f1cf253..a91f9d6 100644 --- a/pytensor_ml/activations.py +++ b/pytensor_ml/activations.py @@ -20,7 +20,29 @@ def _constant_like(value: float, x: pt.TensorVariable) -> pt.TensorVariable: return pt.constant(np.asarray(value, dtype=dtype)) -class Activation(Layer): ... +class Activation(Layer): + """ + Base class for the elementwise nonlinearities, each of which is called on an activation. + + Examples + -------- + Subclass it to add a nonlinearity of your own; the body builds a graph from its input: + + .. code-block:: python + + import pytensor.tensor as pt + + from pytensor_ml.activations import Activation + from pytensor_ml.layers import Input + + + class HardTanh(Activation): + def __call__(self, X): + return pt.clip(X, -1.0, 1.0) + + + activations = HardTanh()(Input("X", shape=(None, 4))) + """ class ReLU(Activation): @@ -32,6 +54,22 @@ class ReLU(Activation): .. math:: \mathrm{ReLU}(x) = \max(0, x). + + Examples + -------- + Drop it into a stack wherever a nonlinearity belongs, usually straight after a linear layer: + + .. code-block:: python + + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import Input, Linear, Sequential + + X = Input("X", shape=(None, 4)) + network = Sequential( + Linear("fc", n_in=4, n_out=8), + ReLU(), + ) + activations = network(X) """ def __call__(self, x: pt.TensorLike) -> pt.TensorVariable: @@ -55,6 +93,23 @@ class LeakyReLU(Activation): ---------- negative_slope : float, optional The slope :math:`\alpha` applied to negative inputs. Default is 0.01. + + Examples + -------- + The slope below zero is what separates it from :class:`ReLU`, and a wider one keeps more gradient + flowing through units that would otherwise be dead: + + .. code-block:: python + + from pytensor_ml.activations import LeakyReLU + from pytensor_ml.layers import Input, Linear, Sequential + + X = Input("X", shape=(None, 4)) + network = Sequential( + Linear("fc", n_in=4, n_out=8), + LeakyReLU(negative_slope=0.2), + ) + activations = network(X) """ def __init__(self, negative_slope: float = 0.01): @@ -76,6 +131,22 @@ class Tanh(Activation): .. math:: \tanh(x) = \frac{e^x - e^{-x}}{e^x + e^{-x}}. + + Examples + -------- + Squash an activation into ``(-1, 1)``, keeping it centred on zero: + + .. code-block:: python + + from pytensor_ml.activations import Tanh + from pytensor_ml.layers import Input, Linear, Sequential + + X = Input("X", shape=(None, 4)) + network = Sequential( + Linear("fc", n_in=4, n_out=8), + Tanh(), + ) + activations = network(X) """ def __call__(self, x: pt.TensorLike) -> pt.TensorVariable: @@ -93,6 +164,22 @@ class Sigmoid(Activation): .. math:: \sigma(x) = \frac{1}{1 + e^{-x}}. + + Examples + -------- + Squash an activation into ``(0, 1)``, which is what a binary output head wants: + + .. code-block:: python + + from pytensor_ml.activations import Sigmoid + from pytensor_ml.layers import Input, Linear, Sequential + + X = Input("X", shape=(None, 4)) + network = Sequential( + Linear("fc", n_in=4, n_out=8), + Sigmoid(), + ) + activations = network(X) """ def __call__(self, x: pt.TensorLike) -> pt.TensorVariable: @@ -110,6 +197,22 @@ class SoftPlus(Activation): .. math:: \mathrm{softplus}(x) = \log(1 + e^x). + + Examples + -------- + Reach for it where an output has to stay strictly positive, such as a predicted scale: + + .. code-block:: python + + from pytensor_ml.activations import SoftPlus + from pytensor_ml.layers import Input, Linear, Sequential + + X = Input("X", shape=(None, 4)) + network = Sequential( + Linear("fc", n_in=4, n_out=8), + SoftPlus(), + ) + activations = network(X) """ def __call__(self, x: pt.TensorLike) -> pt.TensorVariable: @@ -142,6 +245,23 @@ class GELU(Activation): This is the variant HuggingFace calls ``"gelu_new"`` / ``"gelu_pytorch_tanh"``, PyTorch exposes as ``nn.GELU(approximate="tanh")``, and Flax as ``gelu(approximate=True)``; GPT-2 uses it. It is cheaper to evaluate than the exact :math:`\operatorname{erf}` form. Default is True. + + Examples + -------- + The default takes the tanh approximation. Pass ``approximate=False`` for the exact error-function + form, which costs more to evaluate: + + .. code-block:: python + + from pytensor_ml.activations import GELU + from pytensor_ml.layers import Input, Linear, Sequential + + X = Input("X", shape=(None, 4)) + network = Sequential( + Linear("fc", n_in=4, n_out=8), + GELU(approximate=False), + ) + activations = network(X) """ def __init__(self, approximate: bool = True): @@ -175,6 +295,22 @@ class Swish(Activation): beta : float, optional Slope of the sigmoid gate. Larger :math:`\beta` sharpens the gate toward a ReLU; :math:`\beta \to 0` collapses it toward the linear map :math:`x/2`. Default is 1.0. + + Examples + -------- + Raise ``beta`` to sharpen the gate towards ReLU's hinge, or lower it to soften towards a linear unit: + + .. code-block:: python + + from pytensor_ml.activations import Swish + from pytensor_ml.layers import Input, Linear, Sequential + + X = Input("X", shape=(None, 4)) + network = Sequential( + Linear("fc", n_in=4, n_out=8), + Swish(beta=1.5), + ) + activations = network(X) """ def __init__(self, beta: float = 1.0): @@ -201,6 +337,24 @@ class Softmax(Activation): ---------- axis : int, optional The axis along which the values sum to one. Default is -1. + + Examples + -------- + Normalizes over the last axis by default, which is the class axis of a ``(batch, classes)`` logit + matrix. A loss built with ``expect_logits=True`` wants the logits themselves, so reach for this only + when you need the probabilities: + + .. code-block:: python + + from pytensor_ml.activations import Softmax + from pytensor_ml.layers import Input, Linear, Sequential + + X = Input("X", shape=(None, 4)) + network = Sequential( + Linear("logits", n_in=4, n_out=3), + Softmax(axis=-1), + ) + probabilities = network(X) """ def __init__(self, axis: int = -1): diff --git a/pytensor_ml/loss.py b/pytensor_ml/loss.py index 8ecf9ad..3365742 100644 --- a/pytensor_ml/loss.py +++ b/pytensor_ml/loss.py @@ -20,6 +20,28 @@ def _as_reduction(reduction: ReductionLike) -> ReductionFunction: class Loss(ABC): + """ + Scalar objective a training step differentiates, called as ``loss(y_true, y_pred)``. + + Examples + -------- + Subclass it by implementing :meth:`loss`; the base class makes the instance callable: + + .. code-block:: python + + import pytensor.tensor as pt + + from pytensor_ml.loss import Loss + + + class MeanAbsoluteError(Loss): + def loss(self, y_true, y_pred): + return pt.abs(y_true - y_pred).mean() + + + objective = MeanAbsoluteError()(pt.vector("y_true"), pt.vector("y_pred")) + """ + @abstractmethod def loss(self, y_true, y_pred) -> pt.TensorVariable: ... @@ -28,6 +50,28 @@ def __call__(self, y_true, y_pred) -> pt.TensorVariable: class SquaredError(Loss): + """ + Mean or summed squared deviation between prediction and target, for regression. + + Examples + -------- + Call the loss on a target and a prediction to get the scalar a training step differentiates: + + .. code-block:: python + + import numpy as np + import pytensor + import pytensor.tensor as pt + + from pytensor_ml.loss import SquaredError + + y_true = pt.matrix("y_true") + y_pred = pt.matrix("y_pred") + + objective = SquaredError()(y_true, y_pred) + value = pytensor.function([y_true, y_pred], objective)(np.ones((4, 1)), np.zeros((4, 1))) + """ + def __init__(self, reduction: ReductionLike = "mean"): self.reduction = _as_reduction(reduction) @@ -38,6 +82,49 @@ def loss(self, y_true, y_pred) -> pt.TensorVariable: class CrossEntropy(Loss): + """ + Negative log likelihood of the true class under the predicted distribution, for classification. + + Examples + -------- + Integer labels against predicted probabilities, the default: + + .. code-block:: python + + import numpy as np + import pytensor + import pytensor.tensor as pt + + from pytensor_ml.loss import CrossEntropy + + labels = pt.vector("labels", dtype="int64") + probabilities = pt.matrix("probabilities") + + objective = CrossEntropy()(labels, probabilities) + value = pytensor.function([labels, probabilities], objective)( + np.array([0, 2]), np.array([[0.7, 0.2, 0.1], [0.1, 0.2, 0.7]]) + ) + + A classifier head emits logits, and one-hot labels are what an encoder produces, so both flags are + usually set together. Passing logits keeps the loss on the numerically stable log-softmax path: + + .. code-block:: python + + import numpy as np + import pytensor + import pytensor.tensor as pt + + from pytensor_ml.loss import CrossEntropy + + onehot = pt.matrix("onehot") + logits = pt.matrix("logits") + + objective = CrossEntropy(expect_logits=True, expect_onehot_labels=True)(onehot, logits) + value = pytensor.function([onehot, logits], objective)( + np.eye(3)[[0, 2]], np.array([[2.0, 0.5, 0.1], [0.1, 0.5, 2.0]]) + ) + """ + def __init__( self, reduction: ReductionLike = "mean", @@ -106,6 +193,24 @@ def supervised_loss( Scalar training loss. target : TensorVariable Input placeholder for the ground-truth labels, to be supplied at call time. + + Examples + -------- + Point it at a model's output and it hands back the target placeholder to feed alongside each batch: + + .. code-block:: python + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import CrossEntropy, supervised_loss + from pytensor_ml.optim import adam, compile_train + + X = Input("X", shape=(None, 4)) + logits = Linear("logits", n_in=4, n_out=3)(X) + + loss_fn = CrossEntropy(expect_logits=True, expect_onehot_labels=True) + objective, target = supervised_loss(logits, loss_fn, ndim_out=2) + + step = compile_train(objective, adam(1e-3), inputs=[X, target]) """ label_slice = (slice(None),) * ndim_out + (0,) * (prediction.ndim - ndim_out) target = prediction[label_slice].type() diff --git a/pytensor_ml/model.py b/pytensor_ml/model.py index 7e63257..d831998 100644 --- a/pytensor_ml/model.py +++ b/pytensor_ml/model.py @@ -16,7 +16,38 @@ class Model: - """A network's input and output, with conveniences to initialize its weights, train, and run inference.""" + """ + A network's input and output, with conveniences to initialize its weights, train, and run inference. + + Examples + -------- + Build the network, wrap it, and the model carries the graph through initialization, a training + step, and inference: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import Input, Linear, Sequential + from pytensor_ml.loss import SquaredError + from pytensor_ml.model import Model + from pytensor_ml.optim import adam + + X = Input("X", shape=(None, 4)) + network = Sequential( + Linear("fc1", n_in=4, n_out=8), + ReLU(), + Linear("fc2", n_in=8, n_out=1), + ) + + model = Model(X, network(X)).initialize(seed=0) + step = model.compile_train(adam(1e-2), SquaredError(), ndim_out=2) + + batch = np.zeros((16, 4)) + loss_value = step(batch, np.zeros((16, 1))) + predictions = model.predict(batch) + """ def __init__(self, X: TensorVariable, y: TensorVariable, compile_kwargs: dict | None = None): self.X = X diff --git a/pytensor_ml/util.py b/pytensor_ml/util.py index eb75d27..b2efe86 100644 --- a/pytensor_ml/util.py +++ b/pytensor_ml/util.py @@ -23,6 +23,23 @@ class DataLoader: Dtype to cast ``X`` and ``y`` to. Defaults to ``floatX``. random_state : int or numpy Generator, optional Seed for the shuffling generator, for reproducible batch sequences. + + Examples + -------- + Call the loader once per training step; it reshuffles and wraps around on its own, so the loop + never has to track epoch boundaries: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.util import DataLoader + + X = np.random.default_rng(0).normal(size=(500, 8)) + y = np.random.default_rng(1).normal(size=(500, 1)) + + loader = DataLoader(X, y, batch_size=32, random_state=0) + X_batch, y_batch = loader() """ def __init__(self, X, y, batch_size=64, dtype=None, random_state=None): From 9b1de5d764e2e0b0106e4b9718aa8333675bc320 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 22:50:24 -0500 Subject: [PATCH 10/26] Add examples to the optim docstrings The type aliases move to their defining modules on the API page because autodoc reads an attribute docstring from the module that defines it, not from the package re-exporting it. --- docs/source/api/optim.rst | 22 +++- pytensor_ml/optim/alias.py | 171 ++++++++++++++++++++++++++++ pytensor_ml/optim/base.py | 190 ++++++++++++++++++++++++++++++- pytensor_ml/optim/clipping.py | 38 +++++++ pytensor_ml/optim/guards.py | 132 +++++++++++++++++++++- pytensor_ml/optim/policy.py | 23 ++++ pytensor_ml/optim/rules.py | 198 +++++++++++++++++++++++++++++++++ pytensor_ml/optim/schedules.py | 68 +++++++++++ pytensor_ml/optim/train.py | 21 ++++ pytensor_ml/optim/transform.py | 75 +++++++++++++ 10 files changed, 927 insertions(+), 11 deletions(-) diff --git a/docs/source/api/optim.rst b/docs/source/api/optim.rst index 3807d31..ab4fc56 100644 --- a/docs/source/api/optim.rst +++ b/docs/source/api/optim.rst @@ -52,7 +52,6 @@ Guards and policies nonfinite large_step reduce_on_plateau - Decision SkipCondition Schedules @@ -72,6 +71,15 @@ Schedules Building blocks --------------- +.. autosummary:: + :toctree: generated/ + + get_gradients + scalar_state + to_floatx + +.. currentmodule:: pytensor_ml.optim.base + .. autosummary:: :toctree: generated/ @@ -80,9 +88,15 @@ Building blocks Schedule Rate LearningRate - get_gradients - scalar_state - to_floatx + +.. currentmodule:: pytensor_ml.optim.guards + +.. autosummary:: + :toctree: generated/ + + Decision + +.. currentmodule:: pytensor_ml.optim Low-level update functions -------------------------- diff --git a/pytensor_ml/optim/alias.py b/pytensor_ml/optim/alias.py index 2cf9229..2e168fc 100644 --- a/pytensor_ml/optim/alias.py +++ b/pytensor_ml/optim/alias.py @@ -56,6 +56,25 @@ def sgd( Momentum coefficient. A value of 0 (the default) gives plain SGD. nesterov : bool Use Nesterov momentum. Ignored when ``momentum`` is 0. Default False. + + Examples + -------- + Plain gradient descent by default. Momentum carries a running average of past steps, and + ``nesterov`` measures the gradient after that carry rather than before: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import compile_train, sgd + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, sgd(learning_rate=0.1, momentum=0.9, nesterov=True)) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ @reuses_state @@ -84,6 +103,25 @@ def adam( ``learning_rate`` accepts a float, a scalar shared variable, any scalar graph, or a schedule; see :func:`sgd`. + + Examples + -------- + The usual first choice: a per-parameter rate adapted from the first and second gradient moments, + both bias-corrected, so the earliest steps are not damped towards zero: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam, compile_train + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, adam(learning_rate=1e-3)) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ @reuses_state @@ -120,6 +158,25 @@ def adamw( ``learning_rate`` accepts a float, a scalar shared variable, any scalar graph, or a schedule; see :func:`sgd`. + + Examples + -------- + Adam entangles weight decay with its adaptive rate; this one subtracts the decay from the weights + directly. A ``mask`` keeps biases and norm scales out of it, which is almost always what you want: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adamw, compile_train + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, adamw(learning_rate=1e-3, mask=lambda parameter: parameter.ndim > 1)) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ @reuses_state @@ -155,6 +212,25 @@ def nadam( ``learning_rate`` accepts a float, a scalar shared variable, any scalar graph, or a schedule; see :func:`sgd`. + + Examples + -------- + Adam with Nesterov's look-ahead folded into the first moment, which turns corners a little faster + than plain Adam on the same rate: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import compile_train, nadam + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, nadam(learning_rate=2e-3)) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ @reuses_state @@ -187,6 +263,25 @@ def adamax( ``learning_rate`` accepts a float, a scalar shared variable, any scalar graph, or a schedule; see :func:`sgd`. + + Examples + -------- + Adam's second moment replaced by a running infinity norm, so one outsized gradient cannot shrink + every step for many iterations afterwards: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adamax, compile_train + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, adamax(learning_rate=2e-3)) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ @reuses_state @@ -219,6 +314,25 @@ def rprop( Unlike the other rules, ``learning_rate`` must be a plain number: it initializes the per-parameter step sizes Rprop then adapts, so it never enters the graph and cannot be scheduled or steered. + + Examples + -------- + Steps by the sign of the gradient alone, growing or shrinking a per-parameter step size. It reads a + sign change as overshoot, so minibatch noise misleads it -- keep it to full-batch objectives: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import compile_train, rprop + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, rprop(learning_rate=1e-2, eta_plus=1.2, eta_minus=0.5)) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ _require_numeric_learning_rate(learning_rate) @@ -249,6 +363,25 @@ def rmsprop( ``learning_rate`` accepts a float, a scalar shared variable, any scalar graph, or a schedule; see :func:`sgd`. + + Examples + -------- + Scales each step by a decaying average of squared gradients. Setting ``centered`` subtracts the + mean gradient first, which estimates variance rather than raw magnitude: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import compile_train, rmsprop + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, rmsprop(learning_rate=1e-2, centered=True)) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ @reuses_state @@ -276,6 +409,25 @@ def adagrad(learning_rate: LearningRate = 0.01, epsilon: float = 1e-8) -> Update ``learning_rate`` accepts a float, a scalar shared variable, any scalar graph, or a schedule; see :func:`sgd`. + + Examples + -------- + Accumulates every squared gradient it has seen, so the effective rate only ever decreases. That + suits sparse features and stalls on long runs: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adagrad, compile_train + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, adagrad(learning_rate=1e-2)) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ @reuses_state @@ -299,6 +451,25 @@ def adadelta( ``learning_rate`` accepts a float, a scalar shared variable, any scalar graph, or a schedule; see :func:`sgd`. + + Examples + -------- + Tracks a window of squared updates alongside squared gradients, so their ratio sets the scale and + the learning rate stays at its default of 1.0: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adadelta, compile_train + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, adadelta()) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ @reuses_state diff --git a/pytensor_ml/optim/base.py b/pytensor_ml/optim/base.py index 18425a7..a8b3a9d 100644 --- a/pytensor_ml/optim/base.py +++ b/pytensor_ml/optim/base.py @@ -25,21 +25,140 @@ # carries the next parameter values *and* the next optimizer-state values in one identity-keyed dict. Updates = dict[SharedVariable, TensorVariable] -# A chainable transform reads an updates dict and returns a new one, working in "step space" (updates[p] - p). Transform = Callable[[Updates, Sequence[Parameter]], Updates] +""" +A chainable step transformer, reading an updates dict and returning a new one. + +Transforms work in step space -- ``updates[parameter] - parameter`` -- so one can be written without +knowing which rule produced the step it is adjusting. + +Examples +-------- +Write one as a plain function and :func:`chain` accepts it wherever a built-in transform goes. The +updates dict also carries optimizer state and training clocks, so touch only the entries for +``parameters`` -- rewriting the rest would halve a clock's advance as readily as a step: + +.. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam, chain, compile_train + + + def halve_every_step(updates, parameters): + halved = dict(updates) + for parameter in parameters: + halved[parameter] = parameter + 0.5 * (updates[parameter] - parameter) + return halved + + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, chain(adam(1e-3), halve_every_step)) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) +""" UpdateRule = Callable[[LossOrGradients, Sequence[Parameter]], Updates] +""" +What every optimizer is: a callable taking a loss (or gradients) and the parameters, returning the +updates dict that moves them. + +Examples +-------- +Anything matching the signature is a rule, so a hand-written one composes with the rest of the module: + +.. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import chain, clip_by_global_norm, compile_train, get_gradients + + + def plain_descent(loss_or_gradients, parameters): + gradients = get_gradients(loss_or_gradients, parameters) + return {p: p - 0.01 * gradient for p, gradient in zip(parameters, gradients)} + + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, chain(plain_descent, clip_by_global_norm(1.0))) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) +""" -# A learning-rate schedule: symbolic step count in, scalar learning rate out. type Schedule = Callable[[TensorVariable], TensorVariable] +""" +A learning-rate schedule: symbolic step count in, scalar learning rate out. + +Examples +-------- +The built-in schedules return one, and any callable of the same shape works in their place: + +.. code-block:: python + + import pytensor.tensor as pt + + from pytensor_ml.optim import adam + + + def inverse_square_root(step_count): + return 3e-4 / pt.sqrt(pt.maximum(step_count, 1)) + + + rule = adam(learning_rate=inverse_square_root) +""" -# A rate a rule multiplies into its step: a baked-in constant, a shared variable to steer from Python with -# `set_value` or to substitute a schedule into, or any scalar graph, which is what a schedule reading a -# training clock produces. type Rate = float | Parameter | TensorVariable +""" +A rate a rule multiplies into its step. + +Either a baked-in constant, a shared variable to steer from Python with ``set_value`` or to substitute a +schedule into, or any scalar graph, which is what a schedule reading a training clock produces. + +Examples +-------- +A shared scalar is the form to reach for when the rate has to change mid-run without recompiling: + +.. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import compile_train, scalar_state, sgd + + rate = scalar_state("rate", fill_value=0.1) + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, sgd(learning_rate=rate)) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) + + rate.set_value(np.array(0.01, dtype=rate.dtype)) +""" -# What an optimizer alias accepts as its rate, adding a schedule that drives it on-graph. type LearningRate = Rate | Schedule +""" +What an optimizer alias accepts as its rate, adding a schedule that drives it on-graph. + +Examples +-------- +Every alias takes either form, so a constant can be swapped for a schedule without touching anything +else: + +.. code-block:: python + + from pytensor_ml.optim import adam, cosine_schedule + + fixed = adam(learning_rate=3e-4) + scheduled = adam(learning_rate=cosine_schedule(3e-4, total_steps=10_000)) +""" def to_floatx(value: Rate) -> Rate: @@ -59,6 +178,17 @@ def to_floatx(value: Rate) -> Rate: ---------- value : float or TensorVariable A scalar a rule is about to build into its step. + + Examples + -------- + Cast a rate to ``floatX`` so it cannot silently upcast a float32 graph to float64. A symbolic rate, such + as one a schedule produced, passes through unchanged: + + .. code-block:: python + + from pytensor_ml.optim import to_floatx + + rate = to_floatx(1e-3) """ return value.astype(pytensor.config.floatX) if isinstance(value, Variable) else value @@ -81,6 +211,24 @@ def get_gradients( ------- gradients : list of TensorVariable One gradient per parameter, in the order of ``parameters``. + + Examples + -------- + Take gradients of a loss with respect to the parameters, or pass gradients straight through. Every rule + calls it first, so a rule can be handed gradients you computed yourself: + + .. code-block:: python + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import get_gradients + from pytensor_ml.pytensorf import collect_trainable_params + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + parameters = collect_trainable_params(loss) + gradients = get_gradients(loss, parameters) """ if isinstance(loss_or_gradients, list | tuple): gradients = list(loss_or_gradients) @@ -220,6 +368,17 @@ def scalar_state(name: str, fill_value: float = 0.0) -> Parameter: Name of the variable, used to match it at serialization boundaries. fill_value : float Value to initialize it with. Default 0.0. + + Examples + -------- + Build the scalar a rule or policy keeps between steps. Naming it makes it findable in a checkpoint and + in a printed graph: + + .. code-block:: python + + from pytensor_ml.optim import scalar_state + + scale = scalar_state("plateau/scale", fill_value=1.0) """ return _reuse_or_allocate( name, @@ -293,6 +452,25 @@ def chain(head, *rest: Transform): ------- chained : UpdateRule or Transform A callable matching the head, applying every argument in sequence. + + Examples + -------- + Compose a rule with the transforms that follow it. The head decides the result: given a rule, the + whole chain is a rule, applied left to right: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam, chain, clip_by_global_norm, compile_train, scale + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, chain(adam(1e-3), clip_by_global_norm(1.0), scale(0.5))) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ @reuses_state diff --git a/pytensor_ml/optim/clipping.py b/pytensor_ml/optim/clipping.py index a17c01c..2236137 100644 --- a/pytensor_ml/optim/clipping.py +++ b/pytensor_ml/optim/clipping.py @@ -22,6 +22,25 @@ def clip_by_global_norm(max_norm: float = 1.0) -> Transform: ------- transform : Transform A transform that clips the updates dict by global norm. + + Examples + -------- + Bound the whole update rather than each coordinate, so the direction of the step survives and only + its magnitude is capped: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam, chain, clip_by_global_norm, compile_train + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, chain(adam(1e-3), clip_by_global_norm(1.0))) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ def transform(updates: Updates, parameters: Sequence[Parameter]) -> Updates: @@ -51,6 +70,25 @@ def clip_by_value(min_value: float = -1.0, max_value: float = 1.0) -> Transform: ------- transform : Transform A transform that clips the updates dict element-wise. + + Examples + -------- + Clip each coordinate on its own, which bounds the step but tilts its direction whenever only some + coordinates are clipped: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam, chain, clip_by_value, compile_train + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, chain(adam(1e-3), clip_by_value(-0.1, 0.1))) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ def transform(updates: Updates, parameters: Sequence[Parameter]) -> Updates: diff --git a/pytensor_ml/optim/guards.py b/pytensor_ml/optim/guards.py index b9b5e15..16aa1c7 100644 --- a/pytensor_ml/optim/guards.py +++ b/pytensor_ml/optim/guards.py @@ -16,8 +16,35 @@ ) from pytensor_ml.params import StepCounter -# Reads the step a rule has proposed and returns a scalar boolean graph: True to throw the step away. type Decision = Callable[[Updates, Sequence[Parameter]], TensorVariable] +""" +Reads the step a rule has proposed and returns a scalar boolean graph: True to throw the step away. + +Examples +-------- +Pass one straight to :func:`skip_if` when the reason a raised error names does not matter, or wrap it in +a :class:`SkipCondition` when it does: + +.. code-block:: python + + import numpy as np + import pytensor.tensor as pt + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam, compile_train, skip_if + + + def loss_is_huge(updates, parameters): + return pt.max([pt.abs(updates[parameter]).max() for parameter in parameters]) > 1e6 + + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, skip_if(adam(1e-3), loss_is_huge)) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) +""" @dataclass(frozen=True) @@ -32,6 +59,30 @@ class SkipCondition: reason : str Phrase naming why this condition skips, completing the sentence "the optimizer ..." in the error a run of consecutive skips raises. Default names no particular cause. + + Examples + -------- + Pair a decision graph with the reason a raised error should name, for a condition of your own: + + .. code-block:: python + + import numpy as np + import pytensor.tensor as pt + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import SkipCondition, adam, compile_train, skip_if + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + any_step_huge = SkipCondition( + decide=lambda updates, parameters: pt.max([pt.abs(step).max() for step in updates.values()]) > 1e3, + reason="took a step with an implausibly large coordinate", + ) + + step = compile_train(loss, skip_if(adam(1e-3), any_step_huge)) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ decide: Decision @@ -53,6 +104,25 @@ def nonfinite() -> SkipCondition: :func:`~pytensor_ml.optim.policy.reduce_on_plateau` holds an infinite best-loss until it has seen a full window -- and that is not a step to throw away. Optimizer state cannot hide a NaN for long in any case, since a poisoned moment reaches its parameter on the very next step. + + Examples + -------- + The condition behind :func:`apply_if_finite`, useful when you want it alongside a different skip budget + than that helper's default: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam, compile_train, nonfinite, skip_if + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, skip_if(adam(1e-3), nonfinite(), max_consecutive_skips=None)) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ def decide(updates: Updates, parameters: Sequence[Parameter]) -> TensorVariable: @@ -88,6 +158,25 @@ def large_step(max_norm: float) -> SkipCondition: ---------- max_norm : float Norm at which a step is thrown away rather than applied. + + Examples + -------- + Skip a step whose global norm exceeds a bound. Unlike :func:`clip_by_global_norm`, which rescales an + outsized step and applies it, this one discards it entirely: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam, compile_train, large_step, skip_if + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, skip_if(adam(1e-3), large_step(10.0))) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ if max_norm <= 0.0: raise ValueError( @@ -162,6 +251,28 @@ def skip_if( ------- guarded_rule : UpdateRule The guarded rule, which also writes both skip counters. + + Examples + -------- + Wrap a rule to throw away any step meeting the condition, leaving the parameters where they were. It + raises once too many steps in a row are skipped, so a divergence surfaces instead of looking like + training that quietly stopped learning: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam, compile_train, large_step, skip_if + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + rule = skip_if(adam(1e-3), large_step(10.0), max_consecutive_skips=3) + + step = compile_train(loss, rule) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ if max_consecutive_skips is not None and max_consecutive_skips < 1: raise ValueError( @@ -227,6 +338,25 @@ def apply_if_finite( :func:`skip_if` under :func:`nonfinite`, which is the common case and the name optax gives it. Takes the same keyword arguments; see :func:`skip_if` for what they mean and what the guard does and does not cover. + + Examples + -------- + The common case of :func:`skip_if`: drop any step carrying a NaN or an infinity, which would otherwise + poison every parameter it touches and every step after it: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam, apply_if_finite, compile_train + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, apply_if_finite(adam(1e-3))) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ return skip_if( rule, diff --git a/pytensor_ml/optim/policy.py b/pytensor_ml/optim/policy.py index dc4fab8..6c4ec52 100644 --- a/pytensor_ml/optim/policy.py +++ b/pytensor_ml/optim/policy.py @@ -69,6 +69,29 @@ def reduce_on_plateau( ------- wrapped_rule : UpdateRule The wrapped rule, which also writes the scale and the policy's own history. + + Examples + -------- + Own a scale the rule's rate is built from, and the policy cuts it once the loss stops improving. It + decides once per step rather than once per epoch, so widen ``accumulation_size`` to judge on a window + of batches rather than on a single noisy one: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam, compile_train, reduce_on_plateau, scalar_state + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + scale = scalar_state("plateau/scale", fill_value=1.0) + rule = reduce_on_plateau(adam(learning_rate=scale * 1e-3), scale, patience=5, accumulation_size=50) + + step = compile_train(loss, rule) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ if not 0.0 < factor < 1.0: raise ValueError(f"factor must lie in (0, 1), got {factor}.") diff --git a/pytensor_ml/optim/rules.py b/pytensor_ml/optim/rules.py index 8dcba27..b476189 100644 --- a/pytensor_ml/optim/rules.py +++ b/pytensor_ml/optim/rules.py @@ -43,6 +43,28 @@ def sgd_updates( ------- updates : Updates Mapping from each parameter to its next value. + + Examples + -------- + The update function behind :func:`sgd`, for driving a bare ``pytensor.function`` yourself rather + than going through :func:`~pytensor_ml.optim.train.compile_train`. It returns the updates dict directly, and its rate defaults to 1.0 rather than the alias's 0.01: + + .. code-block:: python + + import numpy as np + import pytensor + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import sgd_updates + from pytensor_ml.pytensorf import collect_trainable_params + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + updates = sgd_updates(loss, collect_trainable_params(loss), learning_rate=0.1) + step = pytensor.function([X, target], loss, updates=updates) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ gradients = get_gradients(loss_or_gradients, parameters) learning_rate = to_floatx(learning_rate) @@ -92,6 +114,28 @@ def adam_updates( ------- updates : Updates Mapping from each parameter and its moment buffers to their next values. + + Examples + -------- + The update function behind :func:`adam`, for driving a bare ``pytensor.function`` yourself rather + than going through :func:`~pytensor_ml.optim.train.compile_train`. It returns the updates dict directly: + + .. code-block:: python + + import numpy as np + import pytensor + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam_updates + from pytensor_ml.pytensorf import collect_trainable_params + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + updates = adam_updates(loss, collect_trainable_params(loss), learning_rate=1e-3) + step = pytensor.function([X, target], loss, updates=updates) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ return _adam_family_updates( loss_or_gradients, @@ -241,6 +285,28 @@ def adamw_updates( ------- updates : Updates Mapping from each parameter and its moment buffers to their next values. + + Examples + -------- + The update function behind :func:`adamw`, for driving a bare ``pytensor.function`` yourself rather + than going through :func:`~pytensor_ml.optim.train.compile_train`. It returns the updates dict directly, decoupling the weight decay from the adaptive rate: + + .. code-block:: python + + import numpy as np + import pytensor + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adamw_updates + from pytensor_ml.pytensorf import collect_trainable_params + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + updates = adamw_updates(loss, collect_trainable_params(loss), learning_rate=1e-3, weight_decay=0.01) + step = pytensor.function([X, target], loss, updates=updates) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ return _adam_family_updates( loss_or_gradients, @@ -294,6 +360,28 @@ def nadam_updates( ------- updates : Updates Mapping from each parameter and its moment buffers to their next values. + + Examples + -------- + The update function behind :func:`nadam`, for driving a bare ``pytensor.function`` yourself rather + than going through :func:`~pytensor_ml.optim.train.compile_train`. It returns the updates dict directly: + + .. code-block:: python + + import numpy as np + import pytensor + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import nadam_updates + from pytensor_ml.pytensorf import collect_trainable_params + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + updates = nadam_updates(loss, collect_trainable_params(loss), learning_rate=2e-3) + step = pytensor.function([X, target], loss, updates=updates) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ gradients = get_gradients(loss_or_gradients, parameters) learning_rate = to_floatx(learning_rate) @@ -363,6 +451,28 @@ def adamax_updates( ------- updates : Updates Mapping from each parameter and its state buffers to their next values. + + Examples + -------- + The update function behind :func:`adamax`, for driving a bare ``pytensor.function`` yourself rather + than going through :func:`~pytensor_ml.optim.train.compile_train`. It returns the updates dict directly: + + .. code-block:: python + + import numpy as np + import pytensor + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adamax_updates + from pytensor_ml.pytensorf import collect_trainable_params + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + updates = adamax_updates(loss, collect_trainable_params(loss), learning_rate=2e-3) + step = pytensor.function([X, target], loss, updates=updates) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ gradients = get_gradients(loss_or_gradients, parameters) learning_rate = to_floatx(learning_rate) @@ -418,6 +528,28 @@ def adagrad_updates( ------- updates : Updates Mapping from each parameter and its accumulator to their next values. + + Examples + -------- + The update function behind :func:`adagrad`, for driving a bare ``pytensor.function`` yourself rather + than going through :func:`~pytensor_ml.optim.train.compile_train`. It returns the updates dict directly: + + .. code-block:: python + + import numpy as np + import pytensor + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adagrad_updates + from pytensor_ml.pytensorf import collect_trainable_params + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + updates = adagrad_updates(loss, collect_trainable_params(loss), learning_rate=1e-2) + step = pytensor.function([X, target], loss, updates=updates) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ gradients = get_gradients(loss_or_gradients, parameters) learning_rate = to_floatx(learning_rate) @@ -476,6 +608,28 @@ def rmsprop_updates( ------- updates : Updates Mapping from each parameter and its state buffers to their next values. + + Examples + -------- + The update function behind :func:`rmsprop`, for driving a bare ``pytensor.function`` yourself rather + than going through :func:`~pytensor_ml.optim.train.compile_train`. It returns the updates dict directly: + + .. code-block:: python + + import numpy as np + import pytensor + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import rmsprop_updates + from pytensor_ml.pytensorf import collect_trainable_params + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + updates = rmsprop_updates(loss, collect_trainable_params(loss), learning_rate=1e-2) + step = pytensor.function([X, target], loss, updates=updates) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ gradients = get_gradients(loss_or_gradients, parameters) learning_rate = to_floatx(learning_rate) @@ -540,6 +694,28 @@ def adadelta_updates( ------- updates : Updates Mapping from each parameter and its two accumulators to their next values. + + Examples + -------- + The update function behind :func:`adadelta`, for driving a bare ``pytensor.function`` yourself rather + than going through :func:`~pytensor_ml.optim.train.compile_train`. It returns the updates dict directly, needing no learning rate of its own: + + .. code-block:: python + + import numpy as np + import pytensor + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adadelta_updates + from pytensor_ml.pytensorf import collect_trainable_params + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + updates = adadelta_updates(loss, collect_trainable_params(loss)) + step = pytensor.function([X, target], loss, updates=updates) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ gradients = get_gradients(loss_or_gradients, parameters) learning_rate = to_floatx(learning_rate) @@ -627,6 +803,28 @@ def rprop_updates( ------- updates : Updates Mapping from each parameter and its state buffers to their next values. + + Examples + -------- + The update function behind :func:`rprop`, for driving a bare ``pytensor.function`` yourself rather + than going through :func:`~pytensor_ml.optim.train.compile_train`. It returns the updates dict directly, stepping by gradient sign alone: + + .. code-block:: python + + import numpy as np + import pytensor + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import rprop_updates + from pytensor_ml.pytensorf import collect_trainable_params + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + updates = rprop_updates(loss, collect_trainable_params(loss), learning_rate=1e-2) + step = pytensor.function([X, target], loss, updates=updates) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ _require_numeric_learning_rate(learning_rate) diff --git a/pytensor_ml/optim/schedules.py b/pytensor_ml/optim/schedules.py index 9fed3a0..ab7b13d 100644 --- a/pytensor_ml/optim/schedules.py +++ b/pytensor_ml/optim/schedules.py @@ -127,6 +127,16 @@ def linear_schedule( schedule : Schedule A callable mapping a symbolic step count to a scalar learning rate, ready to hand to a rule as its ``learning_rate``. + + Examples + -------- + Move the rate along a straight line, the usual choice for a warmup or a linear decay to zero: + + .. code-block:: python + + from pytensor_ml.optim import adam, linear_schedule + + rule = adam(learning_rate=linear_schedule(3e-4, total_steps=10_000, final_learning_rate=1e-5)) """ _validate_horizon(total_steps, transition_begin) @@ -181,6 +191,17 @@ def exponential_schedule( schedule : Schedule A callable mapping a symbolic step count to a scalar learning rate, ready to hand to a rule as its ``learning_rate``. + + Examples + -------- + Decay by a constant factor per step, so the rate falls fast early and flattens out. Both endpoints + must be positive, since no finite number of multiplications reaches zero: + + .. code-block:: python + + from pytensor_ml.optim import adam, exponential_schedule + + rule = adam(learning_rate=exponential_schedule(3e-4, total_steps=10_000, final_learning_rate=1e-6)) """ _validate_horizon(total_steps, transition_begin) if final_learning_rate <= 0.0 or learning_rate <= 0.0: @@ -244,6 +265,17 @@ def polynomial_schedule( schedule : Schedule A callable mapping a symbolic step count to a scalar learning rate, ready to hand to a rule as its ``learning_rate``. + + Examples + -------- + Bend the path between the endpoints: ``power=1.0`` is linear, higher powers hold the initial rate + longer before dropping away: + + .. code-block:: python + + from pytensor_ml.optim import adam, polynomial_schedule + + rule = adam(learning_rate=polynomial_schedule(3e-4, total_steps=10_000, power=2.0)) """ _validate_horizon(total_steps, transition_begin) if power <= 0.0: @@ -302,6 +334,17 @@ def step_decay( schedule : Schedule A callable mapping a symbolic step count to a scalar learning rate, ready to hand to a rule as its ``learning_rate``. + + Examples + -------- + Cut the rate by a factor on a fixed cadence, holding it flat in between -- the staircase familiar + from torch's ``StepLR``: + + .. code-block:: python + + from pytensor_ml.optim import adam, step_decay + + rule = adam(learning_rate=step_decay(3e-4, decay_every=2_000, decay_factor=0.5)) """ if decay_every < 1: raise ValueError(f"decay_every must be at least 1, got {decay_every}.") @@ -339,6 +382,17 @@ def constant_schedule(learning_rate: float) -> Schedule: schedule : Schedule A callable mapping a symbolic step count to a scalar learning rate, ready to hand to a rule as its ``learning_rate``. + + Examples + -------- + Hold one rate for the whole run. Useful as a segment of :func:`join_schedules`, where every other + segment is a schedule too: + + .. code-block:: python + + from pytensor_ml.optim import adam, constant_schedule + + rule = adam(learning_rate=constant_schedule(3e-4)) """ def schedule(step_count: TensorVariable) -> TensorVariable: @@ -376,6 +430,20 @@ def join_schedules(schedules: Sequence[Schedule], boundaries: Sequence[int]) -> schedule : Schedule A callable mapping a symbolic step count to a scalar learning rate, ready to hand to a rule as its ``learning_rate``. + + Examples + -------- + Run schedules back to back, switching at each boundary step. A linear warmup into a cosine decay is + the standard recipe for a transformer: + + .. code-block:: python + + from pytensor_ml.optim import adam, cosine_schedule, join_schedules, linear_schedule + + warmup = linear_schedule(0.0, total_steps=1_000, final_learning_rate=3e-4) + decay = cosine_schedule(3e-4, total_steps=9_000) + + rule = adam(learning_rate=join_schedules([warmup, decay], boundaries=[1_000])) """ if not schedules: raise ValueError("join_schedules needs at least one schedule.") diff --git a/pytensor_ml/optim/train.py b/pytensor_ml/optim/train.py index 53c750a..86b157e 100644 --- a/pytensor_ml/optim/train.py +++ b/pytensor_ml/optim/train.py @@ -82,6 +82,27 @@ def compile_train( step : Function The compiled one-step training function, applying every update in place. Returns the loss alone, or ``(loss, *extra_outputs)`` when diagnostics were requested. + + Examples + -------- + Hand it any scalar loss graph and a rule, and it differentiates the loss, applies the rule, and folds in + every update the graph carries -- optimizer state, batch-norm statistics, RNGs, training clocks. Pass + ``extra_outputs`` to read a value out alongside the loss: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam, compile_train + + X = Input("X", shape=(None, 4)) + prediction = Linear("fc", n_in=4, n_out=1)(X) + loss, target = supervised_loss(prediction, SquaredError(), ndim_out=2) + + step = compile_train(loss, adam(1e-3), inputs=[X, target], extra_outputs=[prediction]) + loss_value, predictions = step(np.zeros((8, 4)), np.zeros((8, 1))) """ extra_outputs = list(extra_outputs or []) extra_updates = dict(extra_updates or {}) diff --git a/pytensor_ml/optim/transform.py b/pytensor_ml/optim/transform.py index d84b354..3e79248 100644 --- a/pytensor_ml/optim/transform.py +++ b/pytensor_ml/optim/transform.py @@ -31,6 +31,24 @@ def trace(decay: float = 0.9, nesterov: bool = False) -> Transform: ------- transform : Transform A transform that folds momentum into the updates dict. + + Examples + -------- + Fold momentum into whatever produced the steps, giving a rule that has none of its own: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import chain, compile_train, sgd, trace + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, chain(sgd(0.1), trace(decay=0.9, nesterov=True))) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ def transform(updates: Updates, parameters: Sequence[Parameter]) -> Updates: @@ -63,6 +81,25 @@ def scale(factor: Rate) -> Transform: ------- transform : Transform A transform that rescales the updates dict. + + Examples + -------- + Multiply every step by a constant, most often to shrink a rule's steps without rebuilding it at a + different rate: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam, chain, compile_train, scale + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, chain(adam(1e-3), scale(0.5))) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ def transform(updates: Updates, parameters: Sequence[Parameter]) -> Updates: @@ -106,6 +143,25 @@ def scale_by_schedule(schedule: Schedule, *, namespace: str = "scale_by_schedule ------- transform : Transform A transform that rescales the updates dict by the rate its clock currently reads. + + Examples + -------- + Apply the rate at the end of a chain rather than inside the rule, so a clip placed before it bounds + the step in gradient units instead of units that move with the rate: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam, chain, clip_by_global_norm, compile_train, cosine_schedule, scale_by_schedule + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, chain(adam(1.0), clip_by_global_norm(1.0), scale_by_schedule(cosine_schedule(3e-4, 10_000)))) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ @reuses_state @@ -138,6 +194,25 @@ def add_weight_decay( ------- transform : Transform A transform that folds weight decay into the updates dict. + + Examples + -------- + Pull weights towards zero by a fixed fraction of themselves each step, independently of the + gradient. The mask keeps biases and norm scales out of it: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam, add_weight_decay, chain, compile_train + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, chain(adam(1e-3), add_weight_decay(0.01, mask=lambda parameter: parameter.ndim > 1))) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) """ def transform(updates: Updates, parameters: Sequence[Parameter]) -> Updates: From a1fd88da471fefac04671dc0ba0e91fa921c6292 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 22:50:25 -0500 Subject: [PATCH 11/26] Add examples to the layer docstrings --- pytensor_ml/base.py | 31 ++++++- pytensor_ml/layers/attention.py | 43 +++++++++ pytensor_ml/layers/combinators.py | 84 +++++++++++++++++- pytensor_ml/layers/conv.py | 119 ++++++++++++++++++++++++- pytensor_ml/layers/dropout.py | 19 ++++ pytensor_ml/layers/embedding.py | 12 +++ pytensor_ml/layers/linear.py | 17 ++++ pytensor_ml/layers/norm.py | 37 ++++++++ pytensor_ml/layers/padding.py | 133 ++++++++++++++++++++++++++++ pytensor_ml/layers/recurrent.py | 139 ++++++++++++++++++++++++++++++ pytensor_ml/layers/transformer.py | 31 +++++++ 11 files changed, 660 insertions(+), 5 deletions(-) diff --git a/pytensor_ml/base.py b/pytensor_ml/base.py index 03bff42..abf3ba8 100644 --- a/pytensor_ml/base.py +++ b/pytensor_ml/base.py @@ -17,8 +17,35 @@ def _check_input_rank(X: TensorVariable, name: str, n_spatial: int) -> None: class Layer(ABC): - """Base class for the objects that build layer graphs. Defined here, not in ``pytensor_ml.layers``, so - that ``pytensor_ml.activations`` can subclass it without a circular import.""" + """ + Base class for the objects that build layer graphs. Defined here, not in ``pytensor_ml.layers``, so + that ``pytensor_ml.activations`` can subclass it without a circular import. + + Examples + -------- + Subclass it when a layer owns parameters or needs a marker op of its own; for anything stateless a + plain function is enough. The constructor builds the parameters once, and ``__call__`` builds the graph: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.base import Layer + from pytensor_ml.layers import Input + from pytensor_ml.params import trainable + from pytensor_ml.state import ZeroInitializer + + + class Bias(Layer): + def __init__(self, name, n_in): + self.b = trainable(np.zeros(n_in), f"{name}_b", initializer=ZeroInitializer()) + + def __call__(self, X): + return X + self.b + + + activations = Bias("bias", n_in=4)(Input("X", shape=(None, 4))) + """ @abstractmethod def __call__(self, x: pt.TensorLike) -> pt.TensorVariable: ... diff --git a/pytensor_ml/layers/attention.py b/pytensor_ml/layers/attention.py index 6fae51c..ad8dc69 100644 --- a/pytensor_ml/layers/attention.py +++ b/pytensor_ml/layers/attention.py @@ -112,6 +112,24 @@ def scaled_dot_product_attention( ------- output : TensorVariable Attention output, shape ``(..., n_head, q_len, v_dim)``. + + Examples + -------- + The bare attention kernel, for building an attention variant of your own. It takes heads as an explicit + axis -- ``(batch, n_head, time, head_dim)`` -- and knows nothing about positions, so rotary or other + positional schemes are applied to ``q`` and ``k`` before the call: + + .. code-block:: python + + import pytensor.tensor as pt + + from pytensor_ml.layers import scaled_dot_product_attention + + q = pt.tensor("q", shape=(None, 8, 128, 32)) + k = pt.tensor("k", shape=(None, 8, 128, 32)) + v = pt.tensor("v", shape=(None, 8, 128, 32)) + + attended = scaled_dot_product_attention(q, k, v, is_causal=True) """ q, k, v = (pt.as_tensor(t).copy() for t in (q, k, v)) inputs = [q, k, v] @@ -157,6 +175,19 @@ class MultiheadAttention(Layer): How the output projection's weight is drawn. The three input projections are unaffected, which is what a scaling applied only to the projection writing back into a residual stream needs. Xavier normal when omitted, as for any other weight. + + Examples + -------- + Attend over a sequence with several heads at once, taking ``(batch, time, n_embd)``. Set + ``n_kv_head`` below ``n_head`` for grouped-query attention, which shrinks the key/value cache that + dominates memory at inference: + + .. code-block:: python + + from pytensor_ml.layers import Input, MultiheadAttention + + X = Input("X", shape=(None, 128, 256)) + attended = MultiheadAttention("attn", n_embd=256, n_head=8, n_kv_head=2)(X) """ def __init__( @@ -240,6 +271,18 @@ class CausalSelfAttention(MultiheadAttention): Include bias terms in the projections. Default is True. out_proj_initializer : Initializer, optional How the output projection's weight is drawn. See :class:`MultiheadAttention`. + + Examples + -------- + :class:`MultiheadAttention` with the causal mask always on, so no position sees a later one. This is + the decoder-side layer a language model stacks: + + .. code-block:: python + + from pytensor_ml.layers import CausalSelfAttention, Input + + X = Input("X", shape=(None, 128, 256)) + attended = CausalSelfAttention("attn", n_embd=256, n_head=8)(X) """ def __init__( diff --git a/pytensor_ml/layers/combinators.py b/pytensor_ml/layers/combinators.py index ef86316..009660b 100644 --- a/pytensor_ml/layers/combinators.py +++ b/pytensor_ml/layers/combinators.py @@ -15,12 +15,48 @@ def Input(name: str, shape: tuple[int | None, ...], dtype: str | None = None) -> Size of each dimension. Use None wherever the size varies between calls, such as a batch axis. dtype : str, optional Data type of the input. Default ``floatX``. + + Examples + -------- + Start every network with one: it names the placeholder a batch is fed to, with ``None`` wherever the + size varies from call to call: + + .. code-block:: python + + from pytensor_ml.layers import Input, Linear + + X = Input("X", shape=(None, 64)) + activations = Linear("fc", n_in=64, n_out=10)(X) """ return pt.tensor(name=name, shape=shape, dtype=dtype) def Sequential(*layers: Callable) -> Callable: - """Compose layers left to right into a single callable that threads its input through each in turn.""" + """ + Compose layers left to right into a single callable that threads its input through each in turn. + + Examples + -------- + Thread an input through several layers in order. The result is itself callable, so it nests inside + another ``Sequential`` wherever a block repeats: + + .. code-block:: python + + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import BatchNorm, Input, Linear, Sequential + + block = Sequential( + Linear("fc1", n_in=64, n_out=32), + BatchNorm("bn1", n_in=32), + ReLU(), + ) + network = Sequential( + block, + Linear("logits", n_in=32, n_out=10), + ) + + logits = network(Input("X", shape=(None, 64))) + """ def forward(x: pt.TensorLike) -> pt.TensorLike: for layer in layers: @@ -43,6 +79,23 @@ def Flatten(X: pt.TensorLike) -> pt.TensorVariable: ------- flattened : TensorVariable Shape ``(batch, features)``, with ``features`` the product of every remaining axis. + + Examples + -------- + Collapse everything after the batch axis, which is how a convolutional stack hands off to a dense head: + + .. code-block:: python + + from pytensor_ml.layers import Conv2D, Flatten, Input, Linear, MaxPool2D, Sequential + + X = Input("X", shape=(None, 28, 28, 1)) + network = Sequential( + Conv2D("conv", in_channels=1, out_channels=8, kernel_size=3), + MaxPool2D(), + ) + features = network(X) + + logits = Linear("logits", n_in=8 * 13 * 13, n_out=10)(Flatten(features)) """ return pt.join_dims(X, start_axis=1) @@ -64,6 +117,20 @@ def Squeeze(X: pt.TensorLike, axis: int | Sequence[int] | None = None) -> pt.Ten ------- squeezed : TensorVariable ``X`` with the selected axes removed. + + Examples + -------- + Drop a length-1 axis a layer left behind, such as the trailing feature axis of a single-output + regression head: + + .. code-block:: python + + from pytensor_ml.layers import Input, Linear, Squeeze + + X = Input("X", shape=(None, 64)) + prediction = Linear("fc", n_in=64, n_out=1)(X) + + per_row = Squeeze(prediction, axis=-1) """ return pt.squeeze(X, axis=axis) @@ -85,5 +152,20 @@ def Concatenate(tensors: Sequence[pt.TensorLike], axis: int = 0) -> pt.TensorVar ------- joined : TensorVariable The inputs joined, with extent along ``axis`` equal to the sum of the inputs' extents. + + Examples + -------- + Merge parallel branches back into one tensor. Pass ``axis=-1`` to join along features, since the + default of 0 joins along the batch: + + .. code-block:: python + + from pytensor_ml.layers import Concatenate, Input, Linear + + X = Input("X", shape=(None, 64)) + wide = Linear("wide", n_in=64, n_out=8)(X) + deep = Linear("deep", n_in=64, n_out=4)(X) + + merged = Concatenate([wide, deep], axis=-1) """ return pt.concatenate(tensors, axis=axis) diff --git a/pytensor_ml/layers/conv.py b/pytensor_ml/layers/conv.py index 382f975..322a2ea 100644 --- a/pytensor_ml/layers/conv.py +++ b/pytensor_ml/layers/conv.py @@ -830,6 +830,18 @@ class Conv1D(_ConvNd): How :math:`W` is drawn. Xavier normal when omitted, whose fans count the receptive field. bias_initializer : Initializer, optional How :math:`b` is drawn. Zeros when omitted. + + Examples + -------- + Slide a learned kernel along one spatial axis. The layout is channels-last, so a batch of sequences is + ``(batch, time, channels)``: + + .. code-block:: python + + from pytensor_ml.layers import Conv1D, Input + + X = Input("X", shape=(None, 128, 16)) + features = Conv1D("conv", in_channels=16, out_channels=32, kernel_size=5, padding="same")(X) """ n_spatial = 1 @@ -879,6 +891,25 @@ class Conv2D(_ConvNd): How :math:`W` is drawn. Xavier normal when omitted, whose fans count the receptive field. bias_initializer : Initializer, optional How :math:`b` is drawn. Zeros when omitted. + + Examples + -------- + The image workhorse, taking ``(batch, height, width, channels)``. Padding ``"valid"`` shrinks each + spatial extent by ``kernel_size - 1``; ``"same"`` holds it at ``ceil(extent / stride)``: + + .. code-block:: python + + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import Conv2D, Input, Sequential + + X = Input("X", shape=(None, 32, 32, 3)) + network = Sequential( + Conv2D("conv1", in_channels=3, out_channels=16, kernel_size=3, padding="same"), + ReLU(), + Conv2D("conv2", in_channels=16, out_channels=32, kernel_size=3, stride=2), + ) + + features = network(X) """ n_spatial = 2 @@ -1023,6 +1054,18 @@ class ConvTranspose1D(_ConvTransposeNd): How the kernel is drawn. Xavier normal when omitted. bias_initializer : Initializer, optional How the bias is drawn. Zeros when omitted. + + Examples + -------- + Run a convolution's gradient forwards, so the sequence grows rather than shrinks. A stride of 2 roughly + doubles the extent, which is what a decoder wants: + + .. code-block:: python + + from pytensor_ml.layers import ConvTranspose1D, Input + + X = Input("X", shape=(None, 32, 16)) + upsampled = ConvTranspose1D("up", in_channels=16, out_channels=8, kernel_size=4, stride=2)(X) """ n_spatial = 1 @@ -1064,6 +1107,18 @@ class ConvTranspose2D(_ConvTransposeNd): How the kernel is drawn. Xavier normal when omitted. bias_initializer : Initializer, optional How the bias is drawn. Zeros when omitted. + + Examples + -------- + The upsampling half of an autoencoder or a generator: each input position paints a kernel-sized patch + into the output, so a stride of 2 roughly doubles both extents: + + .. code-block:: python + + from pytensor_ml.layers import ConvTranspose2D, Input + + X = Input("X", shape=(None, 8, 8, 64)) + upsampled = ConvTranspose2D("up", in_channels=64, out_channels=32, kernel_size=4, stride=2)(X) """ n_spatial = 2 @@ -1141,6 +1196,23 @@ class MaxPool2D(_PoolNd): padding : {"valid", "same"}, int, or tuple of int, optional No padding, enough to leave each output extent at :math:`\lceil \text{extent} / s \rceil`, or an explicit number of elements on each side. Default is "valid". + + Examples + -------- + Downsample by keeping the largest activation in each window. Stride defaults to ``kernel_size``, so + windows tile without overlapping and a 2x2 pool halves both extents: + + .. code-block:: python + + from pytensor_ml.layers import Conv2D, Input, MaxPool2D, Sequential + + X = Input("X", shape=(None, 32, 32, 3)) + network = Sequential( + Conv2D("conv", in_channels=3, out_channels=16, kernel_size=3, padding="same"), + MaxPool2D(kernel_size=2), + ) + + features = network(X) """ n_spatial = 2 @@ -1148,7 +1220,25 @@ class MaxPool2D(_PoolNd): class MaxPool1D(_PoolNd): - """Downsample a sequence by taking the largest activation in each window; see :class:`MaxPool2D`.""" + """ + Downsample a sequence by taking the largest activation in each window; see :class:`MaxPool2D`. + + Examples + -------- + The one-dimensional pool, keeping the largest activation in each window along the time axis: + + .. code-block:: python + + from pytensor_ml.layers import Conv1D, Input, MaxPool1D, Sequential + + X = Input("X", shape=(None, 128, 16)) + network = Sequential( + Conv1D("conv", in_channels=16, out_channels=32, kernel_size=5, padding="same"), + MaxPool1D(kernel_size=2), + ) + + features = network(X) + """ n_spatial = 1 reduction = "max" @@ -1161,6 +1251,18 @@ class AvgPool2D(_PoolNd): Takes ``(batch, height, width, channels)`` and returns ``(batch, out_height, out_width, channels)``. Padded positions count toward the average as zeros, matching torch's ``count_include_pad``. See :class:`MaxPool2D` for the arguments, which are shared. + + Examples + -------- + Average each window instead of taking its maximum, which keeps every activation in the window rather + than routing the whole gradient to one of them: + + .. code-block:: python + + from pytensor_ml.layers import AvgPool2D, Input + + X = Input("X", shape=(None, 32, 32, 16)) + pooled = AvgPool2D(kernel_size=2)(X) """ n_spatial = 2 @@ -1168,7 +1270,20 @@ class AvgPool2D(_PoolNd): class AvgPool1D(_PoolNd): - """Downsample a sequence by averaging each window; see :class:`AvgPool2D`.""" + """ + Downsample a sequence by averaging each window; see :class:`AvgPool2D`. + + Examples + -------- + The one-dimensional average pool, smoothing along the time axis rather than picking a winner per window: + + .. code-block:: python + + from pytensor_ml.layers import AvgPool1D, Input + + X = Input("X", shape=(None, 128, 16)) + pooled = AvgPool1D(kernel_size=2)(X) + """ n_spatial = 1 reduction = "mean" diff --git a/pytensor_ml/layers/dropout.py b/pytensor_ml/layers/dropout.py index 05701a2..b5ececb 100644 --- a/pytensor_ml/layers/dropout.py +++ b/pytensor_ml/layers/dropout.py @@ -36,6 +36,25 @@ class Dropout(Layer): generators : list of RandomGeneratorSharedVariable One generator per application of the layer, in the order they were applied. Set a value on these to steer or restore the masks. + + Examples + -------- + Zero a random share of activations during training, rescaling the survivors so the mean is unchanged. + :meth:`~pytensor_ml.model.Model.predict` drops the layer entirely, so inference is deterministic: + + .. code-block:: python + + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import Dropout, Input, Linear, Sequential + + X = Input("X", shape=(None, 64)) + network = Sequential( + Linear("fc", n_in=64, n_out=32), + ReLU(), + Dropout(p=0.1, random_state=0), + ) + + activations = network(X) """ def __init__(self, name: str | None = None, p: float = 0.5, random_state: Any | None = None): diff --git a/pytensor_ml/layers/embedding.py b/pytensor_ml/layers/embedding.py index 92f36b9..e4fc034 100644 --- a/pytensor_ml/layers/embedding.py +++ b/pytensor_ml/layers/embedding.py @@ -33,6 +33,18 @@ class Embedding(Layer): the vocabulary size in the denominator -- correct Xavier, and much tighter than the ``NormalInitializer(0.0, 0.02)`` that reference implementations of GPT-2 use, so this is the keyword to reach for when matching one. + + Examples + -------- + Look up a learned vector per integer token, which is how a vocabulary enters a network. The input + carries token ids, so it must be an integer tensor: + + .. code-block:: python + + from pytensor_ml.layers import Embedding, Input + + tokens = Input("tokens", shape=(None, 128), dtype="int64") + embedded = Embedding("embed", n_embeddings=50_000, n_features=256)(tokens) """ def __init__( diff --git a/pytensor_ml/layers/linear.py b/pytensor_ml/layers/linear.py index c78ab2a..0d9b1c2 100644 --- a/pytensor_ml/layers/linear.py +++ b/pytensor_ml/layers/linear.py @@ -45,6 +45,23 @@ class Linear(Layer): Both parameters are drawn when the layer is built, so a network trains without any further call. :meth:`~pytensor_ml.model.Model.initialize` redraws them from a single seed, which is what makes a run reproducible. + + Examples + -------- + The dense layer: multiply by a learned matrix and add a learned bias. Pass ``bias=False`` where a + normalization layer follows and would subtract the bias away again: + + .. code-block:: python + + from pytensor_ml.layers import BatchNorm, Input, Linear, Sequential + + X = Input("X", shape=(None, 64)) + network = Sequential( + Linear("fc", n_in=64, n_out=32, bias=False), + BatchNorm("bn", n_in=32), + ) + + activations = network(X) """ def __init__( diff --git a/pytensor_ml/layers/norm.py b/pytensor_ml/layers/norm.py index a7f6b60..abd2c89 100644 --- a/pytensor_ml/layers/norm.py +++ b/pytensor_ml/layers/norm.py @@ -194,6 +194,26 @@ class BatchNorm(Layer): be batched with. Compile prediction graphs with :func:`compile_predict`, which applies :func:`rewrite_for_prediction` to substitute the accumulated running statistics for the batch statistics. + + Examples + -------- + Normalize each feature over the batch, keeping running statistics so inference does not depend on which + other rows happen to share the batch. :meth:`~pytensor_ml.model.Model.predict` swaps in those running + statistics automatically, so the training and inference graphs differ here: + + .. code-block:: python + + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import BatchNorm, Input, Linear, Sequential + + X = Input("X", shape=(None, 64)) + network = Sequential( + Linear("fc", n_in=64, n_out=32, bias=False), + BatchNorm("bn", n_in=32), + ReLU(), + ) + + activations = network(X) """ def __init__( @@ -334,6 +354,23 @@ class LayerNorm(Layer): factor to rescale a normalized activation by would defeat the layer. loc_initializer : Initializer, optional How :math:`\beta` is drawn. Zeros when omitted. + + Examples + -------- + Normalize each row over its own features, so no row depends on the others. That independence is why a + transformer uses it rather than :class:`BatchNorm`, and it needs no running statistics: + + .. code-block:: python + + from pytensor_ml.layers import Input, LayerNorm, Linear, Sequential + + X = Input("X", shape=(None, 128, 256)) + network = Sequential( + LayerNorm("ln", n_in=256), + Linear("fc", n_in=256, n_out=256), + ) + + activations = network(X) """ def __init__( diff --git a/pytensor_ml/layers/padding.py b/pytensor_ml/layers/padding.py index 1286cde..8deb6d1 100644 --- a/pytensor_ml/layers/padding.py +++ b/pytensor_ml/layers/padding.py @@ -153,6 +153,22 @@ class ZeroPad1D(_PadNd): padding : int or pair of int, optional Elements added before and after the time axis, either shared by both ends or given as ``(before, after)``. Default is 0. + + Examples + -------- + Pad a sequence with zeros on both ends, most often to keep a convolution's output length: + + .. code-block:: python + + from pytensor_ml.layers import Conv1D, Input, Sequential, ZeroPad1D + + X = Input("X", shape=(None, 128, 16)) + network = Sequential( + ZeroPad1D(padding=2), + Conv1D("conv", in_channels=16, out_channels=32, kernel_size=5), + ) + + features = network(X) """ n_spatial = 1 @@ -175,6 +191,23 @@ class ZeroPad2D(_PadNd): The same amount on every side, one amount per axis, or an explicit ``(before, after)`` for each. Axes read in input order, so ``((top, bottom), (left, right))`` -- not torch's flat last-axis-first sequence. Default is 0. + + Examples + -------- + Surround an image with zeros so a following convolution keeps its extents. One number pads every + side equally; a pair per axis pads each side separately: + + .. code-block:: python + + from pytensor_ml.layers import Conv2D, Input, Sequential, ZeroPad2D + + X = Input("X", shape=(None, 32, 32, 3)) + network = Sequential( + ZeroPad2D(padding=1), + Conv2D("conv", in_channels=3, out_channels=16, kernel_size=3), + ) + + features = network(X) """ n_spatial = 2 @@ -191,6 +224,22 @@ class ConstantPad1D(_ConstantPadNd): ---------- value : float, optional What the added elements hold. Default is 0.0, which makes this :class:`ZeroPad1D`. + + Examples + -------- + Pad a sequence with a chosen constant, for data where zero is a real value rather than absence: + + .. code-block:: python + + from pytensor_ml.layers import Conv1D, Input, Sequential, ConstantPad1D + + X = Input("X", shape=(None, 128, 16)) + network = Sequential( + ConstantPad1D(padding=2, value=0.5), + Conv1D("conv", in_channels=16, out_channels=32, kernel_size=5), + ) + + features = network(X) """ n_spatial = 1 @@ -206,6 +255,23 @@ class ConstantPad2D(_ConstantPadNd): ---------- value : float, optional What the added elements hold. Default is 0.0, which makes this :class:`ZeroPad2D`. + + Examples + -------- + Pad with a value of your choosing rather than zero, which matters when zero is a meaningful level + in the data rather than a neutral one: + + .. code-block:: python + + from pytensor_ml.layers import Conv2D, Input, Sequential, ConstantPad2D + + X = Input("X", shape=(None, 32, 32, 3)) + network = Sequential( + ConstantPad2D(padding=1, value=0.5), + Conv2D("conv", in_channels=3, out_channels=16, kernel_size=3), + ) + + features = network(X) """ n_spatial = 2 @@ -228,6 +294,23 @@ class ReflectionPad1D(_PadNd): Elements added before and after the time axis. Padding wider than the axis keeps reflecting back and forth rather than failing, which is what numpy does and where torch raises. Default is 0. + + Examples + -------- + Mirror a sequence across its ends, so the padded region continues the signal rather than cutting it + to a constant: + + .. code-block:: python + + from pytensor_ml.layers import Conv1D, Input, Sequential, ReflectionPad1D + + X = Input("X", shape=(None, 128, 16)) + network = Sequential( + ReflectionPad1D(padding=2), + Conv1D("conv", in_channels=16, out_channels=32, kernel_size=5), + ) + + features = network(X) """ n_spatial = 1 @@ -248,6 +331,23 @@ class ReflectionPad2D(_PadNd): padding : int, pair of int, or pair of pair of int, optional Padding wider than the axis it reflects keeps reflecting back and forth rather than failing. Default is 0. + + Examples + -------- + Mirror the image across its edges instead of inventing a constant, which avoids the hard border a + zero pad introduces. The padding must be smaller than the extent it mirrors: + + .. code-block:: python + + from pytensor_ml.layers import Conv2D, Input, Sequential, ReflectionPad2D + + X = Input("X", shape=(None, 32, 32, 3)) + network = Sequential( + ReflectionPad2D(padding=1), + Conv2D("conv", in_channels=3, out_channels=16, kernel_size=3), + ) + + features = network(X) """ n_spatial = 2 @@ -268,6 +368,22 @@ class ReplicationPad1D(_PadNd): Name prefix for the layer's output. Defaults to the class name when None. padding : int or pair of int, optional Elements added before and after the time axis. Default is 0. + + Examples + -------- + Hold the first and last values of a sequence flat across the padded region: + + .. code-block:: python + + from pytensor_ml.layers import Conv1D, Input, Sequential, ReplicationPad1D + + X = Input("X", shape=(None, 128, 16)) + network = Sequential( + ReplicationPad1D(padding=2), + Conv1D("conv", in_channels=16, out_channels=32, kernel_size=5), + ) + + features = network(X) """ n_spatial = 1 @@ -287,6 +403,23 @@ class ReplicationPad2D(_PadNd): Name prefix for the layer's output. Defaults to the class name when None. padding : int, pair of int, or pair of pair of int, optional Default is 0. + + Examples + -------- + Repeat the edge pixel outwards. Like reflection it avoids a hard border, but it holds the boundary + value flat rather than folding the interior back: + + .. code-block:: python + + from pytensor_ml.layers import Conv2D, Input, Sequential, ReplicationPad2D + + X = Input("X", shape=(None, 32, 32, 3)) + network = Sequential( + ReplicationPad2D(padding=1), + Conv2D("conv", in_channels=3, out_channels=16, kernel_size=3), + ) + + features = network(X) """ n_spatial = 2 diff --git a/pytensor_ml/layers/recurrent.py b/pytensor_ml/layers/recurrent.py index 9fcc42c..7166c5a 100644 --- a/pytensor_ml/layers/recurrent.py +++ b/pytensor_ml/layers/recurrent.py @@ -25,6 +25,42 @@ class RecurrentCell(ABC): A cell owns the parameters its step uses and knows the shape of the state it carries, which is all the loop needs from it. Subclasses implement :meth:`step` and :meth:`initial_state`. + + Examples + -------- + Subclass it to define one timestep and let :class:`Recurrent` scan it over the sequence. A cell owns its + parameters, says how to build its initial state, and maps ``(input, *state)`` to the next state: + + .. code-block:: python + + import numpy as np + import pytensor.tensor as pt + + from pytensor_ml.layers import Input, Recurrent + from pytensor_ml.layers.recurrent import RecurrentCell + from pytensor_ml.params import trainable + from pytensor_ml.state import XavierUniformInitializer + + + class LeakyCell(RecurrentCell): + def __init__(self, name, n_in, n_hidden): + self.n_hidden = n_hidden + self.W = trainable( + np.zeros((n_in + n_hidden, n_hidden)), + f"{name}_W", + initializer=XavierUniformInitializer(), + ) + + def step(self, x_t, h): + candidate = pt.tanh(pt.concatenate([x_t, h], axis=-1) @ self.W) + return (0.5 * h + 0.5 * candidate,) + + def initial_state(self, X): + return (pt.zeros((X.shape[0], self.n_hidden)),) + + + X = Input("X", shape=(None, 50, 16)) + hidden_states = Recurrent(LeakyCell("leaky", n_in=16, n_hidden=32))(X) """ @abstractmethod @@ -80,6 +116,19 @@ class Recurrent(Layer): Run the sequence from its last step to its first. The output stays aligned with the input, so ``out[..., t, :]`` is the step that read ``X[..., t, :]`` either way, and a backward layer's output concatenates elementwise with a forward one's. Default is False. + + Examples + -------- + Wrap any cell to scan it over the time axis. :class:`RNN`, :class:`LSTM` and :class:`GRU` are this + layer around their matching cell, and ``reverse=True`` is what the backward half of a + :class:`Bidirectional` uses: + + .. code-block:: python + + from pytensor_ml.layers import GRUCell, Input, Recurrent + + X = Input("X", shape=(None, 200, 16)) + hidden_states = Recurrent(GRUCell("cell", n_in=16, n_hidden=32), reverse=True)(X) """ def __init__(self, cell: RecurrentCell, name: str | None = None, reverse: bool = False): @@ -251,6 +300,21 @@ class ElmanCell(RecurrentCell): as in keras and flax. bias_initializer : Initializer, optional How :math:`b` is drawn. Zeros when omitted. + + Examples + -------- + One timestep of a plain recurrent layer, for handing to :class:`Recurrent` when you want the scan + configured yourself rather than through :class:`RNN`: + + .. code-block:: python + + from pytensor_ml.activations import Tanh + from pytensor_ml.layers import ElmanCell, Input, Recurrent + + X = Input("X", shape=(None, 50, 16)) + cell = ElmanCell("cell", n_in=16, n_hidden=32, activation=Tanh()) + + hidden_states = Recurrent(cell)(X) """ def __init__( @@ -327,6 +391,18 @@ class RNN(Recurrent): How :math:`b` is drawn. Zeros when omitted. reverse : bool, optional Run the sequence backward, with the output still aligned to the input. Default is False. + + Examples + -------- + The plain recurrent layer: one hidden state carried along the time axis, returning its value at every + step. Input is ``(batch, time, features)``: + + .. code-block:: python + + from pytensor_ml.layers import RNN, Input + + X = Input("X", shape=(None, 50, 16)) + hidden_states = RNN("rnn", n_in=16, n_hidden=32)(X) """ def __init__( @@ -405,6 +481,18 @@ class GRUCell(RecurrentCell): omitted; see :class:`ElmanCell` for why the state's own weight is the sensitive draw. bias_initializer : Initializer, optional How :math:`b` and :math:`c` are drawn. Zeros when omitted. + + Examples + -------- + One timestep of a GRU, carrying a single hidden state. Use it where the scan is built by hand rather + than through :class:`GRU`: + + .. code-block:: python + + from pytensor_ml.layers import GRUCell, Input, Recurrent + + X = Input("X", shape=(None, 200, 16)) + hidden_states = Recurrent(GRUCell("cell", n_in=16, n_hidden=32))(X) """ _n_gates = 3 @@ -504,6 +592,18 @@ class GRU(Recurrent): How the biases are drawn. Zeros when omitted. reverse : bool, optional Run the sequence backward, with the output still aligned to the input. Default is False. + + Examples + -------- + Gated like an :class:`LSTM` but with one state instead of two, so it trains faster and holds fewer + parameters at similar quality on many sequences: + + .. code-block:: python + + from pytensor_ml.layers import GRU, Input + + X = Input("X", shape=(None, 200, 16)) + hidden_states = GRU("gru", n_in=16, n_hidden=32)(X) """ def __init__( @@ -587,6 +687,18 @@ class LSTMCell(RecurrentCell): bias_initializer : Initializer, optional How :math:`b` is drawn. Zeros when omitted, as in torch and flax. Drawing the forget slice at one instead starts the memory holding rather than decaying, which is keras' default. + + Examples + -------- + One timestep of an LSTM, carrying ``(hidden, cell)`` as its state. Reach for it when the scan needs + configuring directly rather than through :class:`LSTM`: + + .. code-block:: python + + from pytensor_ml.layers import Input, LSTMCell, Recurrent + + X = Input("X", shape=(None, 200, 16)) + hidden_states = Recurrent(LSTMCell("cell", n_in=16, n_hidden=32), reverse=True)(X) """ _n_gates = 4 @@ -685,6 +797,18 @@ class LSTM(Recurrent): How :math:`b` is drawn. Zeros when omitted. reverse : bool, optional Run the sequence backward, with the output still aligned to the input. Default is False. + + Examples + -------- + Carries a cell state alongside the hidden state, with gates deciding what to keep. That extra path is + what lets it hold information over far longer sequences than :class:`RNN`: + + .. code-block:: python + + from pytensor_ml.layers import LSTM, Input + + X = Input("X", shape=(None, 200, 16)) + hidden_states = LSTM("lstm", n_in=16, n_hidden=32)(X) """ def __init__( @@ -740,6 +864,21 @@ class Bidirectional(Layer): Run over the sequence from its last step to its first. name : str or None Name for the layer's output. Defaults to "Bidirectional" when None. + + Examples + -------- + Run one layer forwards and another backwards, concatenating their outputs, so every step sees the whole + sequence. Give the two directions separate layers -- sharing one would tie their weights: + + .. code-block:: python + + from pytensor_ml.layers import GRU, Bidirectional, Input + + X = Input("X", shape=(None, 200, 16)) + forward = GRU("forward", n_in=16, n_hidden=32) + backward = GRU("backward", n_in=16, n_hidden=32) + + hidden_states = Bidirectional(forward, backward)(X) """ def __init__(self, forward: Recurrent, backward: Recurrent, name: str | None = None): diff --git a/pytensor_ml/layers/transformer.py b/pytensor_ml/layers/transformer.py index 805def8..a7d423b 100644 --- a/pytensor_ml/layers/transformer.py +++ b/pytensor_ml/layers/transformer.py @@ -45,6 +45,19 @@ class FeedForward(Layer): fc_out_initializer : Initializer, optional How the second layer's weight is drawn. The hidden layer is unaffected. Xavier normal when omitted, as for any other weight. + + Examples + -------- + The position-wise MLP of a transformer block: widen by ``mlp_ratio``, apply a nonlinearity, project + back. Pass ``hidden_dim`` to set the width directly instead of as a multiple: + + .. code-block:: python + + from pytensor_ml.activations import GELU + from pytensor_ml.layers import FeedForward, Input + + X = Input("X", shape=(None, 128, 256)) + activations = FeedForward("ff", d_model=256, mlp_ratio=4, activation=GELU())(X) """ def __init__( @@ -128,6 +141,24 @@ class TransformerBlock(Layer): GPT-style initialization asks for: a residual stream accumulates one contribution per block, so those projections are scaled by :math:`1/\sqrt{2 n_\text{layer}}` while the rest are not. The depth is not known here, so the scaling belongs in the initializer the caller passes. + + Examples + -------- + Attention and feed-forward with their residual connections and norms, which is the unit a transformer + repeats. ``norm_first`` puts the norm inside the residual branch, the pre-norm arrangement that trains + stably without a warmup: + + .. code-block:: python + + from pytensor_ml.layers import Input, Sequential, TransformerBlock + + X = Input("X", shape=(None, 128, 256)) + network = Sequential( + TransformerBlock("block1", d_model=256, n_head=8, norm_first=True), + TransformerBlock("block2", d_model=256, n_head=8, norm_first=True), + ) + + activations = network(X) """ def __init__( From 8153a7e11c9e43278055455e6658ebf8ff75f2c2 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 22:50:32 -0500 Subject: [PATCH 12/26] Add examples to the parameter and initializer docstrings --- pytensor_ml/params.py | 95 ++++++++++++++++++- pytensor_ml/state.py | 207 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 299 insertions(+), 3 deletions(-) diff --git a/pytensor_ml/params.py b/pytensor_ml/params.py index 0662206..b366d47 100644 --- a/pytensor_ml/params.py +++ b/pytensor_ml/params.py @@ -20,17 +20,68 @@ class TrainableParameter(TensorSharedVariable): The law this parameter's value is drawn from, which :func:`~pytensor_ml.state.initialize_params` redraws it from. Every layer declares one for each parameter it builds. None means a redraw has nothing to go on and raises. + + Examples + -------- + The shared-variable class :func:`trainable` produces. Graph traversal tells parameters apart by type, + which is how an optimizer finds exactly the weights it may write: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.params import TrainableParameter, trainable + from pytensor_ml.pytensorf import collect_trainable_params + from pytensor_ml.state import ZeroInitializer + + W = trainable(np.zeros((4, 4)), "W", initializer=ZeroInitializer()) + + is_trainable = isinstance(W, TrainableParameter) + found = collect_trainable_params(W.sum()) """ initializer: "Initializer | None" = None class NonTrainableParameter(TensorSharedVariable): - """Marker class for non-trainable state (running mean/var in BatchNorm).""" + """ + Marker class for non-trainable state (running mean/var in BatchNorm). + + Examples + -------- + The shared-variable class :func:`non_trainable` produces. The separate type is what keeps a running + statistic out of the set an optimizer differentiates and writes: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.params import NonTrainableParameter, non_trainable + + running_mean = non_trainable(np.zeros(32), "bn_running_mean") + + is_non_trainable = isinstance(running_mean, NonTrainableParameter) + """ class StepCounter(TensorSharedVariable): - """Training time: an integer scalar counting training steps, whose transition is :meth:`advance`.""" + """ + Training time: an integer scalar counting training steps, whose transition is :meth:`advance`. + + Examples + -------- + The shared-variable class :func:`step_counter` produces. Its type is how + :func:`~pytensor_ml.pytensorf.collect_clock_updates` finds every clock in a graph and writes each one's + advance into the compiled step: + + .. code-block:: python + + from pytensor_ml.params import StepCounter, step_counter + + clock = step_counter("step_count") + + is_clock = isinstance(clock, StepCounter) + """ def advance(self) -> TensorVariable: """Return the expression for this counter's value on the next training step.""" @@ -78,6 +129,20 @@ def trainable( draw. Default None, which leaves the parameter with no law and raises on a redraw. **kwargs Additional arguments passed to the SharedVariable constructor. + + Examples + -------- + Declare a weight an optimizer may write. Name it, since optimizer state and checkpoints are matched by + name, and give it the initializer it should be redrawn from: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.params import trainable + from pytensor_ml.state import XavierUniformInitializer + + W = trainable(np.zeros((64, 32)), "fc_W", initializer=XavierUniformInitializer()) """ parameter = _make_parameter(TrainableParameter, value, name, shape, strict, **kwargs) parameter.initializer = initializer @@ -114,6 +179,19 @@ def non_trainable(value, name=None, shape=None, strict=False, **kwargs) -> NonTr Takes the same arguments as :func:`trainable`; only the marker class differs, which is what keeps these out of the set an optimizer updates. + + Examples + -------- + Declare state the model owns but no optimizer may write, which is what a batch-norm running mean is. + It still travels in a checkpoint and still gets restored: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.params import non_trainable + + running_mean = non_trainable(np.zeros(32), "bn_running_mean") """ return _make_parameter(NonTrainableParameter, value, name, shape, strict, **kwargs) @@ -132,6 +210,19 @@ def step_counter(name: str = "step_count") -> StepCounter: name : str, optional Name for the counter. Training state is matched by name at serialization boundaries. Default 'step_count'. + + Examples + -------- + Build the clock a schedule reads. Every clock in a graph counts the same steps, and the compiled + function advances them, so a schedule moves through time rather than reading step zero forever: + + .. code-block:: python + + from pytensor_ml.optim import cosine_schedule + from pytensor_ml.params import step_counter + + clock = step_counter("my_schedule/step_count") + rate = cosine_schedule(3e-4, total_steps=10_000)(clock) """ return _make_parameter( StepCounter, np.asarray(0, dtype="int64"), name, shape=None, strict=False diff --git a/pytensor_ml/state.py b/pytensor_ml/state.py index f6cb93a..4569d72 100644 --- a/pytensor_ml/state.py +++ b/pytensor_ml/state.py @@ -30,6 +30,33 @@ class Initializer(ABC): ``__props__`` names the constructor arguments that define the draw, borrowing pytensor's Op convention so the same encoder serves both. A subclass taking arguments must list them, or a saved network rebuilds it with the defaults rather than the values it was built with. + + Examples + -------- + Subclass it to describe a draw of your own, or reach for the :func:`initializer` decorator, which builds + the class from a sampling function. ``__props__`` is what a saved config records, so a parameter that is + reloaded is drawn the same way: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.state import Initializer + + + class ConstantInitializer(Initializer): + __props__ = ("value",) + + def __init__(self, value=0.0): + self.value = value + + def sample(self, shape, dtype, rng): + return np.full(shape, self.value, dtype=dtype) + + + layer = Linear("fc", n_in=64, n_out=32, weight_initializer=ConstantInitializer(0.5)) + activations = layer(Input("X", shape=(None, 64))) """ __props__: tuple[str, ...] = () @@ -64,16 +91,75 @@ def _sample_like(self, param: SharedVariable, rng: RandomState | None = None) -> class ZeroInitializer(Initializer): + """ + Fill every element with zero, the right draw for a bias or any additive offset. + + Examples + -------- + Layers already draw their biases this way; pass it explicitly to zero a weight matrix that would + otherwise be drawn at random: + + .. code-block:: python + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.model import Model + from pytensor_ml.state import ZeroInitializer + + X = Input("X", shape=(None, 64)) + layer = Linear("fc", n_in=64, n_out=32, weight_initializer=ZeroInitializer()) + + model = Model(X, layer(X)).initialize(seed=0) + """ + def sample(self, shape: tuple[int, ...], dtype: str, rng: np.random.Generator) -> np.ndarray: return np.zeros(shape, dtype=dtype) class OneInitializer(Initializer): + """ + Fill every element with one, the right draw for a multiplicative scale. + + Examples + -------- + The draw a normalization scale wants, so the layer starts as the identity transform: + + .. code-block:: python + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.model import Model + from pytensor_ml.state import OneInitializer + + X = Input("X", shape=(None, 64)) + layer = Linear("fc", n_in=64, n_out=32, weight_initializer=OneInitializer()) + + model = Model(X, layer(X)).initialize(seed=0) + """ + def sample(self, shape: tuple[int, ...], dtype: str, rng: np.random.Generator) -> np.ndarray: return np.ones(shape, dtype=dtype) class UnitUniformInitializer(Initializer): + r""" + Draw from :math:`\mathcal{U}(0, 1)`. + + Examples + -------- + Draws on the unit interval, which suits a parameter that is a probability or a fraction rather than + a weight: + + .. code-block:: python + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.model import Model + from pytensor_ml.state import UnitUniformInitializer + + X = Input("X", shape=(None, 64)) + layer = Linear("fc", n_in=64, n_out=32, weight_initializer=UnitUniformInitializer()) + + model = Model(X, layer(X)).initialize(seed=0) + """ + def sample(self, shape: tuple[int, ...], dtype: str, rng: np.random.Generator) -> np.ndarray: return rng.uniform(0.0, 1.0, size=shape).astype(dtype) @@ -92,6 +178,22 @@ class NormalInitializer(Initializer): Center of the distribution :math:`\mu`. Default 0.0. std : float Standard deviation :math:`\sigma`. Default 0.01. + + Examples + -------- + A fixed-width Gaussian, ignoring the parameter's shape. GPT-2 initializes this way at ``std=0.02``, + where a fan-scaled draw would widen as layers narrow: + + .. code-block:: python + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.model import Model + from pytensor_ml.state import NormalInitializer + + X = Input("X", shape=(None, 64)) + layer = Linear("fc", n_in=64, n_out=32, weight_initializer=NormalInitializer(std=0.02)) + + model = Model(X, layer(X)).initialize(seed=0) """ __props__ = ("mean", "std") @@ -130,6 +232,19 @@ def fans(shape: tuple[int, ...]) -> tuple[int, int]: Units feeding one output position. fan_out : int Output positions one input feeds. + + Examples + -------- + Report how many units feed into and out of one position of a parameter, which is what a fan-scaled draw + needs. A convolution kernel folds its receptive field into both counts, so the answer is not simply the + last two dimensions: + + .. code-block:: python + + from pytensor_ml.state import fans + + dense_fans = fans((64, 32)) + kernel_fans = fans((3, 5, 8, 16)) """ if len(shape) < 2: raise ValueError( @@ -152,6 +267,22 @@ class XavierUniformInitializer(Initializer): ---------- .. [1] Glorot, X. and Bengio, Y. (2010). Understanding the difficulty of training deep feedforward neural networks. Proceedings of AISTATS, 249-256. + + Examples + -------- + The default for weight matrices: bounds chosen so activations neither grow nor shrink as they pass + through the layer. Only the sum of the fans enters, so the layout of the parameter cannot skew it: + + .. code-block:: python + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.model import Model + from pytensor_ml.state import XavierUniformInitializer + + X = Input("X", shape=(None, 64)) + layer = Linear("fc", n_in=64, n_out=32, weight_initializer=XavierUniformInitializer()) + + model = Model(X, layer(X)).initialize(seed=0) """ def sample(self, shape: tuple[int, ...], dtype: str, rng: np.random.Generator) -> np.ndarray: @@ -170,6 +301,22 @@ class XavierNormalInitializer(Initializer): ---------- .. [1] Glorot, X. and Bengio, Y. (2010). Understanding the difficulty of training deep feedforward neural networks. Proceedings of AISTATS, 249-256. + + Examples + -------- + Xavier's variance drawn from a Gaussian rather than a bounded interval, so a few weights start + larger than any uniform draw would allow: + + .. code-block:: python + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.model import Model + from pytensor_ml.state import XavierNormalInitializer + + X = Input("X", shape=(None, 64)) + layer = Linear("fc", n_in=64, n_out=32, weight_initializer=XavierNormalInitializer()) + + model = Model(X, layer(X)).initialize(seed=0) """ def sample(self, shape: tuple[int, ...], dtype: str, rng: np.random.Generator) -> np.ndarray: @@ -196,6 +343,22 @@ class OrthogonalInitializer(Initializer): ---------- .. [1] Saxe, A. M., McClelland, J. L., and Ganguli, S. (2014). Exact solutions to the nonlinear dynamics of learning in deep linear neural networks. Proceedings of ICLR. + + Examples + -------- + Draws a matrix whose rows stay orthogonal, preserving the norm of whatever passes through it. That + is what keeps a recurrent layer's repeated multiplications from exploding or vanishing: + + .. code-block:: python + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.model import Model + from pytensor_ml.state import OrthogonalInitializer + + X = Input("X", shape=(None, 64)) + layer = Linear("fc", n_in=64, n_out=32, weight_initializer=OrthogonalInitializer(gain=1.0)) + + model = Model(X, layer(X)).initialize(seed=0) """ __props__ = ("gain",) @@ -292,14 +455,23 @@ def initializer(sample_fn: Callable[..., np.ndarray]) -> type[Initializer]: Examples -------- + Decorate a sampling function to turn it into an initializer class a layer can take: + .. code-block:: python + import numpy as np + + from pytensor_ml.layers import Linear + from pytensor_ml.state import fans, initializer + + @initializer def he_normal(rng, shape): fan_in, _ = fans(shape) return rng.normal(0.0, np.sqrt(2.0 / fan_in), size=shape) - Linear("fc", 8, 8, weight_initializer=he_normal()) + + layer = Linear("fc", n_in=8, n_out=8, weight_initializer=he_normal()) """ parameters, defaults = _split_signature(sample_fn) @@ -356,6 +528,22 @@ class UnrecordedInitializer(Initializer): ---------- original : str Name of the initializer class the parameter was built with. + + Examples + -------- + What a parameter carries when its config named an initializer this build cannot resolve. It refuses to + draw, so a reloaded network reports the missing initializer rather than silently substituting one: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.params import trainable + from pytensor_ml.state import UnrecordedInitializer + + parameter = trainable( + np.zeros((4, 4)), "W", initializer=UnrecordedInitializer("my_package.custom_draw") + ) """ __props__ = ("original",) @@ -429,6 +617,23 @@ def initialize_params( ------- values : list of ndarray Drawn values, matching the shapes and dtypes of ``params``. + + Examples + -------- + Draw fresh values for a list of parameters without touching them, which is what + :meth:`~pytensor_ml.model.Model.initialize` does before assigning. One seed reproduces the whole set: + + .. code-block:: python + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.pytensorf import collect_trainable_params + from pytensor_ml.state import initialize_params + + X = Input("X", shape=(None, 64)) + activations = Linear("fc", n_in=64, n_out=32)(X) + + parameters = collect_trainable_params(activations) + values = initialize_params(parameters, rng=0) """ # Resolve once and share: a seed handed to each _sample_like call would repeat draws across parameters. rng = np.random.default_rng(rng) From 8dae5b15cdd1e458523f02c08c0872e73a4c0aa2 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 22:50:33 -0500 Subject: [PATCH 13/26] Add examples to the graph-tool docstrings --- pytensor_ml/pytensorf/collect.py | 216 ++++++++++++++++++++++++++++++- pytensor_ml/pytensorf/compile.py | 50 +++++++ pytensor_ml/pytensorf/rewrite.py | 34 +++++ pytensor_ml/pytensorf/rng.py | 19 ++- 4 files changed, 312 insertions(+), 7 deletions(-) diff --git a/pytensor_ml/pytensorf/collect.py b/pytensor_ml/pytensorf/collect.py index 8524df2..04c032e 100644 --- a/pytensor_ml/pytensorf/collect.py +++ b/pytensor_ml/pytensorf/collect.py @@ -13,7 +13,24 @@ def as_output_list(outputs: Variable | Sequence[Variable]) -> list[Variable]: - """Normalize one output, or a sequence of them, to a list.""" + """ + Normalize one output, or a sequence of them, to a list. + + Examples + -------- + Normalize an output argument that may be one variable or a sequence of them, so the code after it has + one shape to handle: + + .. code-block:: python + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.pytensorf import as_output_list + + X = Input("X", shape=(None, 64)) + activations = Linear("fc", n_in=64, n_out=32)(X) + + outputs = as_output_list(activations) + """ return [outputs] if isinstance(outputs, Variable) else list(outputs) @@ -30,7 +47,33 @@ def _collect_inputs_of_type[T: Variable]( def collect_graph_inputs(outputs: Variable | Sequence[Variable]) -> list[Variable]: - """Collect the graph inputs that carry data -- everything that is neither a Constant nor shared.""" + """ + Collect the graph inputs that carry data -- everything that is neither a Constant nor shared. + + Examples + -------- + The placeholders a caller has to supply, in the order the graph puts them. Weights, running + statistics and RNGs are shared, so they are left out -- the compiled function carries those itself. + :func:`collect_data_inputs` is the same function under the name the training and serialization + boundaries use: + + .. code-block:: python + + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import BatchNorm, Dropout, Input, Linear, Sequential + from pytensor_ml.pytensorf import collect_graph_inputs + + X = Input("X", shape=(None, 64)) + network = Sequential( + Linear("fc", n_in=64, n_out=32), + BatchNorm("bn", n_in=32), + ReLU(), + Dropout(p=0.5, random_state=0), + ) + activations = network(X) + + inputs = collect_graph_inputs(activations) + """ return [ variable for variable in graph_inputs(as_output_list(outputs)) @@ -43,12 +86,60 @@ def collect_graph_inputs(outputs: Variable | Sequence[Variable]) -> list[Variabl def collect_shared_variables(outputs: Variable | Sequence[Variable]) -> list[SharedVariable]: - """Collect every SharedVariable the graph reads, parameters and RNGs alike.""" + """ + Collect every SharedVariable the graph reads, parameters and RNGs alike. + + Examples + -------- + Every shared variable in the graph, whichever kind: parameters, running statistics, RNGs and + training clocks together. This is the set a checkpoint saves: + + .. code-block:: python + + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import BatchNorm, Dropout, Input, Linear, Sequential + from pytensor_ml.pytensorf import collect_shared_variables + + X = Input("X", shape=(None, 64)) + network = Sequential( + Linear("fc", n_in=64, n_out=32), + BatchNorm("bn", n_in=32), + ReLU(), + Dropout(p=0.5, random_state=0), + ) + activations = network(X) + + shared = collect_shared_variables(activations) + """ return _collect_inputs_of_type(outputs, SharedVariable) def collect_trainable_params(outputs: Variable | Sequence[Variable]) -> list[TrainableParameter]: - """Collect the parameters an optimizer should update.""" + """ + Collect the parameters an optimizer should update. + + Examples + -------- + The weights an optimizer is allowed to write. This is what a rule differentiates with respect to, + and what :meth:`~pytensor_ml.model.Model.initialize` redraws: + + .. code-block:: python + + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import BatchNorm, Dropout, Input, Linear, Sequential + from pytensor_ml.pytensorf import collect_trainable_params + + X = Input("X", shape=(None, 64)) + network = Sequential( + Linear("fc", n_in=64, n_out=32), + BatchNorm("bn", n_in=32), + ReLU(), + Dropout(p=0.5, random_state=0), + ) + activations = network(X) + + parameters = collect_trainable_params(activations) + """ return _collect_inputs_of_type(outputs, TrainableParameter) @@ -77,6 +168,28 @@ def collect_differentiable_params( ------- parameters : list of TrainableParameter The differentiable parameters, in graph-input order. + + Examples + -------- + The trainable parameters the loss actually depends on. A parameter reached only through a + non-differentiable path is left out, since asking for its gradient would raise: + + .. code-block:: python + + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import BatchNorm, Dropout, Input, Linear, Sequential + from pytensor_ml.pytensorf import collect_differentiable_params + + X = Input("X", shape=(None, 64)) + network = Sequential( + Linear("fc", n_in=64, n_out=32), + BatchNorm("bn", n_in=32), + ReLU(), + Dropout(p=0.5, random_state=0), + ) + activations = network(X) + + parameters = collect_differentiable_params(activations) """ output_list = as_output_list(outputs) stop_gradient_outputs = [ @@ -101,12 +214,60 @@ def collect_differentiable_params( def collect_non_trainable_params( outputs: Variable | Sequence[Variable], ) -> list[NonTrainableParameter]: - """Collect the state that training updates without gradients, such as batch-norm running statistics.""" + """ + Collect the state that training updates without gradients, such as batch-norm running statistics. + + Examples + -------- + State the model owns but no optimizer may write, such as a batch-norm running mean. The model + updates these from its own forward pass: + + .. code-block:: python + + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import BatchNorm, Dropout, Input, Linear, Sequential + from pytensor_ml.pytensorf import collect_non_trainable_params + + X = Input("X", shape=(None, 64)) + network = Sequential( + Linear("fc", n_in=64, n_out=32), + BatchNorm("bn", n_in=32), + ReLU(), + Dropout(p=0.5, random_state=0), + ) + activations = network(X) + + statistics = collect_non_trainable_params(activations) + """ return _collect_inputs_of_type(outputs, NonTrainableParameter) def collect_step_counters(outputs: Variable | Sequence[Variable]) -> list[StepCounter]: - """Collect the training clocks the graph reads.""" + """ + Collect the training clocks the graph reads. + + Examples + -------- + The training clocks the graph reads. Each counts steps for a schedule, and every one in a graph + counts the same steps: + + .. code-block:: python + + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import BatchNorm, Dropout, Input, Linear, Sequential + from pytensor_ml.pytensorf import collect_step_counters + + X = Input("X", shape=(None, 64)) + network = Sequential( + Linear("fc", n_in=64, n_out=32), + BatchNorm("bn", n_in=32), + ReLU(), + Dropout(p=0.5, random_state=0), + ) + activations = network(X) + + clocks = collect_step_counters(activations) + """ return _collect_inputs_of_type(outputs, StepCounter) @@ -134,6 +295,27 @@ def collect_clock_updates( ------- clock_updates : dict Mapping from each clock the graph reads to the expression for its next value. + + Examples + -------- + The one-step advance for each training clock in the graph, which is what makes a schedule move rather + than read step zero forever. :func:`function` threads these in automatically: + + .. code-block:: python + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.optim import cosine_schedule, get_gradients, scale_by_schedule + from pytensor_ml.pytensorf import collect_clock_updates, collect_trainable_params + + X = Input("X", shape=(None, 64)) + activations = Linear("fc", n_in=64, n_out=32)(X) + + parameters = collect_trainable_params(activations) + updates = scale_by_schedule(cosine_schedule(1e-3, 1_000))( + dict(zip(parameters, get_gradients(activations.sum(), parameters))), parameters + ) + + clock_updates = collect_clock_updates(list(updates.values())) """ counters = [ counter for counter in collect_step_counters(outputs) if counter not in already_written @@ -165,6 +347,28 @@ def collect_non_trainable_updates( non_trainable_updates : dict Mapping from each NonTrainableParameter to its new value, for every update an op declares through :meth:`~pytensor_ml.base.StatefulOp.update_map`. + + Examples + -------- + The writes the model makes on its own, such as a batch-norm statistic, mapped to their next values. + :func:`~pytensor_ml.optim.train.compile_train` folds these in alongside the rule's own updates: + + .. code-block:: python + + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import BatchNorm, Dropout, Input, Linear, Sequential + from pytensor_ml.pytensorf import collect_non_trainable_updates + + X = Input("X", shape=(None, 64)) + network = Sequential( + Linear("fc", n_in=64, n_out=32), + BatchNorm("bn", n_in=32), + ReLU(), + Dropout(p=0.5, random_state=0), + ) + activations = network(X) + + updates = collect_non_trainable_updates(activations) """ updates: dict[NonTrainableParameter, TensorVariable] = {} for ancestor in ancestors(as_output_list(outputs)): diff --git a/pytensor_ml/pytensorf/compile.py b/pytensor_ml/pytensorf/compile.py index d93e428..cb6841f 100644 --- a/pytensor_ml/pytensorf/compile.py +++ b/pytensor_ml/pytensorf/compile.py @@ -54,6 +54,29 @@ def function( ------- compiled_function : Function The compiled function. + + Examples + -------- + Compile like ``pytensor.function``, with the RNG and training-clock updates threaded in, so repeated + calls advance their state instead of repeating the first draw. Pass ``random_seed`` to make the draws + reproducible: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Dropout, Input, Linear, Sequential + from pytensor_ml.pytensorf import function + + X = Input("X", shape=(None, 64)) + network = Sequential( + Linear("fc", n_in=64, n_out=32), + Dropout(p=0.5), + ) + activations = network(X) + + forward = function([X], activations, random_seed=0) + first, second = forward(np.zeros((4, 64))), forward(np.zeros((4, 64))) """ updates = dict(kwargs.pop("updates", {})) input_variables = [inp.variable if isinstance(inp, pytensor.In) else inp for inp in inputs] @@ -146,6 +169,33 @@ def compile_predict( ------- predict_function : Function The compiled prediction function. + + Examples + -------- + Compile the inference pass: dropout is removed and batch norm reads its running statistics, so the + result is deterministic and independent of the rest of the batch: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import BatchNorm, Dropout, Input, Linear, Sequential + from pytensor_ml.model import Model + from pytensor_ml.pytensorf import compile_predict + + X = Input("X", shape=(None, 64)) + network = Sequential( + Linear("fc", n_in=64, n_out=32), + BatchNorm("bn", n_in=32), + ReLU(), + Dropout(p=0.5), + ) + activations = network(X) + Model(X, activations).initialize(seed=0) + + predict = compile_predict(activations, inputs=[X]) + predictions = predict(np.zeros((4, 64))) """ specialized = rewrite_for_prediction(prediction) if inputs is None: diff --git a/pytensor_ml/pytensorf/rewrite.py b/pytensor_ml/pytensorf/rewrite.py index fb877c3..a557b19 100644 --- a/pytensor_ml/pytensorf/rewrite.py +++ b/pytensor_ml/pytensorf/rewrite.py @@ -35,6 +35,21 @@ def rewrite_pregrad(graph): ``graph`` still reaches ``grad``. Lifts a draw written inside a scan out of the loop, which :func:`grad` needs rather than merely tolerates: a draw inside the differentiated region leaves no fixed sample to take a gradient against, and scan reports it as an undefined gradient. + + Examples + -------- + Apply the rewrites that have to run before differentiation, which is what every rule does to a loss + before taking its gradient: + + .. code-block:: python + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.pytensorf import rewrite_pregrad + + X = Input("X", shape=(None, 64)) + loss = Linear("fc", n_in=64, n_out=32)(X).sum() + + prepared = rewrite_pregrad(loss) """ simplified = rewrite_graph( graph, include=("canonicalize", "stabilize"), exclude=("local_view_op",) @@ -57,6 +72,25 @@ def rewrite_for_prediction(graph): specialized_graph : FunctionGraph, Variable, or list of Variable The specialized graph, matching the form of ``graph``. A FunctionGraph is rewritten in place and returned; a Variable or sequence is rewritten on a clone, leaving the original untouched. + + Examples + -------- + Specialize a training graph for inference without compiling it, which is what :func:`compile_predict` + does first. A Variable or a sequence is rewritten on a clone, leaving the original untouched: + + .. code-block:: python + + from pytensor_ml.layers import Dropout, Input, Linear, Sequential + from pytensor_ml.pytensorf import rewrite_for_prediction + + X = Input("X", shape=(None, 64)) + network = Sequential( + Linear("fc", n_in=64, n_out=32), + Dropout(p=0.5), + ) + activations = network(X) + + inference_graph = rewrite_for_prediction(activations) """ # Local by design, matching pytensor's own op/rewrite pattern: the rewrites import the layer ops they # match on, so a module-scope import would tie this module to the whole layer surface. diff --git a/pytensor_ml/pytensorf/rng.py b/pytensor_ml/pytensorf/rng.py index fef47dc..10b5bc4 100644 --- a/pytensor_ml/pytensorf/rng.py +++ b/pytensor_ml/pytensorf/rng.py @@ -30,7 +30,24 @@ def atleast_list(x): # Scan, and OpFromGraph between inputs and outputs, so a compiled function advances its generators instead # of repeating draws. def find_rng_nodes(variables: Iterable[Variable]) -> list[RandomGeneratorSharedVariable]: - """Return the shared RNG variables in a graph.""" + """ + Return the shared RNG variables in a graph. + + Examples + -------- + Locate the random generators a graph draws from, which is what a dropout layer or any other sampling op + leaves behind: + + .. code-block:: python + + from pytensor_ml.layers import Dropout, Input + from pytensor_ml.pytensorf import find_rng_nodes + + X = Input("X", shape=(None, 64)) + activations = Dropout(p=0.5, random_state=0)(X) + + generators = find_rng_nodes([activations]) + """ return [ node for node in graph_inputs(variables) if isinstance(node, RandomGeneratorSharedVariable) ] From 0512a6c71a26f9252f0545959c517dbf947ab14c Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 22:50:33 -0500 Subject: [PATCH 14/26] Add examples to the saving and loading docstrings --- pytensor_ml/checkpoint.py | 39 ++++++++++++++++++++ pytensor_ml/pretrained.py | 76 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/pytensor_ml/checkpoint.py b/pytensor_ml/checkpoint.py index a89d23b..9af73e6 100644 --- a/pytensor_ml/checkpoint.py +++ b/pytensor_ml/checkpoint.py @@ -40,6 +40,25 @@ def save_state(shared_variables: Sequence[SharedVariable], path: str | Path) -> Variables whose values to save. Every variable must have a unique, non-``None`` name. path : str or pathlib.Path Destination archive, written verbatim. + + Examples + -------- + Write parameter values to safetensors, keyed by name. This is the checkpoint half of a run: it + saves what the parameters hold, not what built them. The destination directory has to exist + already: + + .. code-block:: python + + from pytensor_ml import save_state + from pytensor_ml.layers import Input, Linear + from pytensor_ml.model import Model + from pytensor_ml.pytensorf import collect_shared_variables + + X = Input("X", shape=(None, 64)) + logits = Linear("logits", n_in=64, n_out=10)(X) + Model(X, logits).initialize(seed=0) + + save_state(collect_shared_variables(logits), "weights.safetensors") """ indexed = _index_by_name(shared_variables) tensors = { @@ -87,6 +106,26 @@ def load_state( Maps a variable's name to the archive key to read it from, for loading a checkpoint saved under different names (such as HuggingFace's). The mapping must be injective. Names absent from the map are matched directly. + + Examples + -------- + Fill an existing graph's parameters from a checkpoint, matching them by name. Build the same network + first -- this loads values into it rather than reconstructing it: + + .. code-block:: python + + from pytensor_ml import load_state, save_state + from pytensor_ml.layers import Input, Linear + from pytensor_ml.model import Model + from pytensor_ml.pytensorf import collect_shared_variables + + X = Input("X", shape=(None, 64)) + logits = Linear("logits", n_in=64, n_out=10)(X) + Model(X, logits).initialize(seed=0) + + shared = collect_shared_variables(logits) + save_state(shared, "weights.safetensors") + load_state(shared, "weights.safetensors") """ indexed = _index_by_name(shared_variables) name_map = name_map or {} diff --git a/pytensor_ml/pretrained.py b/pytensor_ml/pretrained.py index c091867..ccbd5d3 100644 --- a/pytensor_ml/pretrained.py +++ b/pytensor_ml/pretrained.py @@ -195,6 +195,22 @@ def save_network( inputs : sequence of Variable, optional The network's data inputs, in call order. Collected from ``outputs`` when omitted; pass explicitly when call order matters. + + Examples + -------- + Write the architecture alone, as JSON. The weights are not in it, so pair it with + :func:`~pytensor_ml.checkpoint.save_state` or use :func:`save_pretrained`, which writes both. + The destination directory has to exist already: + + .. code-block:: python + + from pytensor_ml import save_network + from pytensor_ml.layers import Input, Linear + + X = Input("X", shape=(None, 64)) + logits = Linear("logits", n_in=64, n_out=10)(X) + + save_network(logits, "config.json") """ output_list = as_output_list(outputs) data_inputs = list(inputs) if inputs is not None else collect_data_inputs(output_list) @@ -236,6 +252,21 @@ def load_network( The network's data inputs, in call order. outputs : Variable or list of Variable The rebuilt output(s) -- a single variable when the network has one output, otherwise a list. + + Examples + -------- + Rebuild a saved architecture with freshly drawn weights, which is what you want when the values are + about to be trained or loaded separately: + + .. code-block:: python + + from pytensor_ml import load_network, save_network + from pytensor_ml.layers import Input, Linear + + X = Input("X", shape=(None, 64)) + save_network(Linear("logits", n_in=64, n_out=10)(X), "config.json") + + inputs, outputs = load_network("config.json") """ config = json.loads(Path(path).read_text()) if config.get("format") != GRAPH_FORMAT: @@ -283,6 +314,29 @@ def save_pretrained( Destination directory, created if needed. inputs : sequence of Variable, optional The network's data inputs, in call order. Collected from ``outputs`` when omitted. + + Examples + -------- + Write the architecture and the weights together, so the directory reloads into a runnable network + without the Python that defined it: + + .. code-block:: python + + from pytensor_ml import save_pretrained + from pytensor_ml.activations import ReLU + from pytensor_ml.layers import Input, Linear, Sequential + from pytensor_ml.model import Model + + X = Input("X", shape=(None, 64)) + network = Sequential( + Linear("fc", n_in=64, n_out=32), + ReLU(), + Linear("logits", n_in=32, n_out=10), + ) + logits = network(X) + Model(X, logits).initialize(seed=0) + + save_pretrained(logits, "artifacts/model") """ directory = Path(directory) directory.mkdir(parents=True, exist_ok=True) @@ -315,6 +369,28 @@ def from_pretrained( The network's data inputs, in call order. outputs : Variable or list of Variable The rebuilt, weight-filled output(s). + + Examples + -------- + Rebuild a saved network and fill its weights, returning the data inputs and the outputs. Nothing that + defined the network has to be importable: + + .. code-block:: python + + import numpy as np + + from pytensor_ml import from_pretrained, save_pretrained + from pytensor_ml.layers import Input, Linear + from pytensor_ml.model import Model + from pytensor_ml.pytensorf import function + + X = Input("X", shape=(None, 64)) + logits = Linear("logits", n_in=64, n_out=10)(X) + Model(X, logits).initialize(seed=0) + save_pretrained(logits, "artifacts/model") + + inputs, outputs = from_pretrained("artifacts/model") + predictions = function(inputs, outputs)(np.zeros((4, 64))) """ directory = Path(directory) if source_format == "auto": From a8253e52c72429577c3a140e4992986c804df4c2 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 23:33:01 -0500 Subject: [PATCH 15/26] Write the installation guide --- docs/source/get_started/install.rst | 70 +++++++++++++++++++++-------- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/docs/source/get_started/install.rst b/docs/source/get_started/install.rst index 5c584d2..8fce41e 100644 --- a/docs/source/get_started/install.rst +++ b/docs/source/get_started/install.rst @@ -1,14 +1,8 @@ Installation ============ -.. note:: - - **WRITEME.** This page is a stub. Flesh out with per-backend install - notes and any platform-specific caveats (MLX is macOS-only, JAX GPU - wheels, Numba threading layers). - -pytensor_ml targets Python ``>= 3.12``. Its hard dependencies are PyTensor -(``>= 3.2.3``), NumPy, and safetensors. +pytensor_ml runs on Python 3.12 and newer, and depends on PyTensor, NumPy and +safetensors. Nothing else is needed to train a network and save it. From PyPI --------- @@ -17,6 +11,9 @@ From PyPI pip install pytensor-ml +The distribution is named ``pytensor-ml``; the package you import is +``pytensor_ml``. + From source ----------- @@ -26,19 +23,52 @@ From source cd pytensor-ml pip install -e . -Backends --------- - -The default C backend needs nothing extra. Every other backend is an optional -dependency, installed separately and imported only when a graph is actually -compiled against it: +Optional extras +--------------- .. code-block:: bash - pip install numba # mode="NUMBA" - pip install jax # mode="JAX" - pip install torch # mode="PYTORCH" - pip install mlx # mode="MLX", macOS only + pip install "pytensor-ml[examples]" # matplotlib and tqdm, for the example notebooks + pip install "pytensor-ml[dev]" # test, lint and type-checking tools + +Backends +-------- + +A network is a PyTensor graph, so it compiles to whichever backend PyTensor is +pointed at. Numba is the default and arrives with PyTensor itself, so a plain +install already compiles the whole graph rather than stepping through it op by +op. The rest are ordinary packages you install yourself: + +.. list-table:: + :header-rows: 1 + :widths: 18 26 56 + + * - Mode + - Install + - Notes + * - ``"NUMBA"`` + - included + - The default, and a dependency of PyTensor, so it is already there. + * - ``"C"`` + - included + - Compiles each op to C and calls it from Python. Needs a working C + compiler, which most systems already have. + * - ``"JAX"`` + - ``pip install jax`` + - CPU by default; GPU and TPU need the matching hardware-specific + wheel from the JAX project. + * - ``"PYTORCH"`` + - ``pip install torch`` + - Runs on whichever devices the installed torch build supports. + * - ``"MLX"`` + - ``pip install mlx`` + - Apple silicon only, and unavailable on every other platform. + +pytensor_ml registers its own kernels for convolution, pooling and attention on +Numba, JAX, PyTorch and MLX. Registration is lazy: nothing imports a backend +until a graph is actually compiled against it, so an installed-but-unused +backend costs nothing at import time, and a missing one is only a problem if you +ask for it. Development install ------------------- @@ -50,4 +80,8 @@ Development install pip install -e ".[dev]" pre-commit install +The conda environments under ``conda_envs/`` are the reference setup CI runs +against, pinned more tightly than the package metadata. Install one of those +when a failure does not reproduce anywhere else. + See :doc:`/dev/contributing` for the rest of the contributor setup. From 12e3f1e34acec95da801721d68cec013b3abfb7a Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 23:33:56 -0500 Subject: [PATCH 16/26] Drop the quickstart and about stubs and flatten getting started --- README.md | 6 ++- docs/source/dev/docs.rst | 4 +- docs/source/get_started/about.rst | 24 ---------- docs/source/get_started/index.rst | 9 ---- docs/source/get_started/quickstart.rst | 58 ----------------------- docs/source/index.rst | 17 +++++-- docs/source/{get_started => }/install.rst | 0 7 files changed, 21 insertions(+), 97 deletions(-) delete mode 100644 docs/source/get_started/about.rst delete mode 100644 docs/source/get_started/index.rst delete mode 100644 docs/source/get_started/quickstart.rst rename docs/source/{get_started => }/install.rst (100%) diff --git a/README.md b/README.md index b97dae2..4770933 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,14 @@ A(nother) deep learning library, built on top of [PyTensor](https://github.com/pymc-devs/pytensor). Networks are ordinary PyTensor graphs. You build one out of layers, and everything PyTensor already does — -symbolic differentiation, graph rewrites, and compilation to C, Numba, JAX, PyTorch, or MLX — applies to it +symbolic differentiation, graph rewrites, and compilation to Numba, C, JAX, PyTorch, or MLX — applies to it unchanged. Training is a compiled function that takes a batch and returns a loss; there is no separate runtime or tape. +That goes all the way down: layers are graph constructors, parameters are shared variables, and a training +step is a compiled function whose updates are the optimizer. Because a model is only a graph, it composes with +any other PyTensor graph — a PyMC model included — as there is nothing else to interoperate with. + > **Status: pre-alpha.** The API is still moving, and there is no release-to-release compatibility guarantee yet. ## Installation diff --git a/docs/source/dev/docs.rst b/docs/source/dev/docs.rst index 96cf7e6..a490275 100644 --- a/docs/source/dev/docs.rst +++ b/docs/source/dev/docs.rst @@ -36,8 +36,8 @@ Source content lives under ``docs/source/``: - Landing page + top-level toctree. * - ``api.rst`` + ``api/*.rst`` - Autosummary entry points; one file per public submodule. - * - ``get_started/`` - - Install + quickstart + about. Hand-written narrative. + * - ``install.rst`` + - Installation guide. Hand-written narrative. * - ``user_guide/`` - Conceptual pages (layers, training, optimizers, backends, design). * - ``examples/gallery.rst`` diff --git a/docs/source/get_started/about.rst b/docs/source/get_started/about.rst deleted file mode 100644 index dadcb5b..0000000 --- a/docs/source/get_started/about.rst +++ /dev/null @@ -1,24 +0,0 @@ -About pytensor_ml -================= - -.. note:: - - **WRITEME.** This page is a stub. Fill in project motivation, scope, and - how pytensor_ml relates to the rest of the ecosystem (PyTorch, JAX/Flax, - Keras) and to PyMC. - -pytensor_ml is a deep learning library built on PyTensor's symbolic graph and -rewrite system. A network is a graph, not an object hierarchy with a runtime -attached: layers are graph constructors, parameters are shared variables, and -a training step is a compiled PyTensor function whose updates are the -optimizer. - -That design is what the library trades on. Gradients come from PyTensor's -symbolic differentiation, performance comes from its rewrites and its -backends, and a model composes with any other PyTensor graph — including a -PyMC model — because there is nothing else to interoperate with. - -.. note:: - - pytensor_ml is pre-alpha. The API is still moving and there is no - release-to-release compatibility guarantee yet. diff --git a/docs/source/get_started/index.rst b/docs/source/get_started/index.rst deleted file mode 100644 index 31840e0..0000000 --- a/docs/source/get_started/index.rst +++ /dev/null @@ -1,9 +0,0 @@ -Getting Started -=============== - -.. toctree:: - :maxdepth: 1 - - install - quickstart - about diff --git a/docs/source/get_started/quickstart.rst b/docs/source/get_started/quickstart.rst deleted file mode 100644 index 2bac0cb..0000000 --- a/docs/source/get_started/quickstart.rst +++ /dev/null @@ -1,58 +0,0 @@ -Quickstart -========== - -.. note:: - - **WRITEME.** This page is a stub. Walk a new user end-to-end through - building, training, evaluating, and saving a model. Cross-link to the - :doc:`/examples/gallery` for the full notebooks. - -The snippet below trains a small classifier on scikit-learn's digits dataset. - -.. code-block:: python - - import numpy as np - import pytensor - - pytensor.config.floatX = "float32" - - from sklearn.datasets import load_digits - - from pytensor_ml.activations import ReLU - from pytensor_ml.layers import Input, Linear, Sequential - from pytensor_ml.loss import CrossEntropy - from pytensor_ml.model import Model - from pytensor_ml.optim import adam, chain, clip_by_global_norm, cosine_schedule - from pytensor_ml.util import DataLoader - - X, y = load_digits(return_X_y=True) - X = (X / 16.0).astype("float32") - y_onehot = np.eye(10, dtype="float32")[y] - - X_in = Input("X_in", shape=(None, 64)) - network = Sequential( - Linear("fc1", n_in=64, n_out=128), - ReLU(), - Linear("logits", n_in=128, n_out=10), - ) - model = Model(X_in, network(X_in)).initialize(seed=0) - - rule = chain(adam(learning_rate=cosine_schedule(1e-3, total_steps=500)), clip_by_global_norm(1.0)) - loss_fn = CrossEntropy(expect_onehot_labels=True, expect_logits=True, reduction="mean") - step = model.compile_train(rule, loss_fn, ndim_out=2) - - loader = DataLoader(X, y_onehot, batch_size=64, random_state=0) - for _ in range(500): - loss_value = step(*loader()) - - accuracy = (model.predict(X).argmax(axis=-1) == y).mean() - -:meth:`~pytensor_ml.model.Model.compile_train` builds the loss against a -target placeholder, differentiates it, folds in any stateful layer updates -(batch norm running statistics, RNG advances, the training clock a schedule -reads), and compiles a one-step function. -:meth:`~pytensor_ml.model.Model.predict` compiles a separate inference pass, -with dropout removed and batch norm reading its running statistics. - -A :class:`~pytensor_ml.model.Model` is a convenience, not a requirement: -:func:`pytensor_ml.optim.compile_train` trains any loss graph you hand it. diff --git a/docs/source/index.rst b/docs/source/index.rst index c9d3d0c..da70df0 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -5,15 +5,26 @@ A(nother) deep learning library, built on top of PyTensor. Networks are ordinary PyTensor graphs. You build one out of layers, and everything PyTensor already does — symbolic differentiation, graph rewrites, -and compilation to C, Numba, JAX, PyTorch, or MLX — applies to it unchanged. +and compilation to Numba, C, JAX, PyTorch, or MLX — applies to it unchanged. Training is a compiled function that takes a batch and returns a loss; there is no separate runtime or tape. +That goes all the way down: layers are graph constructors, parameters are +shared variables, and a training step is a compiled function whose updates are +the optimizer. Because a model is only a graph, it composes with any other +PyTensor graph — a PyMC model included — as there is nothing else to +interoperate with. + pytensor_ml ships the usual layer library (dense, convolutional, recurrent, attention, normalization), composable optimizers with learning-rate schedules and step guards, and safetensors-backed serialization that round-trips both weights and architecture. +.. note:: + + pytensor_ml is pre-alpha. The API is still moving, and there is no + release-to-release compatibility guarantee yet. + Quick install ------------- @@ -21,7 +32,7 @@ Quick install pip install pytensor-ml -See the :doc:`installation guide ` for backend extras. +See the :doc:`installation guide ` for backend extras. Quick example ------------- @@ -63,7 +74,7 @@ walkthroughs. :hidden: :titlesonly: - get_started/index + install user_guide/index examples/gallery api diff --git a/docs/source/get_started/install.rst b/docs/source/install.rst similarity index 100% rename from docs/source/get_started/install.rst rename to docs/source/install.rst From 4d647927eae6dde30bd4f1f12b0ae4c1ad4d2048 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 23:34:06 -0500 Subject: [PATCH 17/26] Write the contributing guide --- docs/source/dev/contributing.rst | 84 ++++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 14 deletions(-) diff --git a/docs/source/dev/contributing.rst b/docs/source/dev/contributing.rst index 258875d..ccdcd3f 100644 --- a/docs/source/dev/contributing.rst +++ b/docs/source/dev/contributing.rst @@ -1,14 +1,15 @@ Contributing ============ -.. note:: +Contributions are welcome, from a typo fix to a new layer family. - **WRITEME.** This page is a stub. Cover the branching and PR workflow, - test conventions (PyTorch as the reference implementation, per-backend - parametrization), and where new layers and optimizers should live. +**Every pull request needs an issue first.** Open one describing what you mean +to change, and wait for a reply before writing the code. The design may already +be settled, the behaviour may be deliberate, or someone may be halfway through +the same work. Link the issue from the pull request. -Development install -------------------- +Setting up +---------- .. code-block:: bash @@ -17,19 +18,74 @@ Development install pip install -e ".[dev]" pre-commit install -Running tests -------------- +The conda environments under ``conda_envs/`` are what CI installs, pinned +harder than the package metadata. Reach for one when a failure reproduces in CI +but not locally: .. code-block:: bash - pytest + conda env create -f conda_envs/pytensor_ml.yml + +Docs are built with pixi, which owns its own environment: + +.. code-block:: bash + + pixi run docs-build # build once into docs/build/html + pixi run docs-serve # rebuild on save, served at http://localhost:8000 + +``pixi.lock`` is checked in, and it has to stay that way. It pins the exact +environment a docs build resolves to, so a build that works for you works for +everyone else and for Read the Docs. If a change of yours updates the lock file, +commit it in the same PR as the change that caused it. A lock file that has +drifted out of sync with ``pyproject.toml`` is worse than no lock file, because +it fails somewhere far from the change that broke it. + +Running the tests +----------------- + +.. code-block:: bash + + pytest # everything a core job runs + pytest tests/test_layers.py # one file, while iterating + python scripts/run_mypy.py # the type check CI runs + +The backend dispatch tests under ``tests/dispatch/`` need that backend +installed, and the core environment deliberately installs none of them. One CI +job per backend covers those, and running the core suite with no backend present +proves the library does not quietly depend on one. Install the backend you are +working on to run its tests locally. + +**A new test file has to be added to a CI group.** ``tests/test_workflow_groups.py`` +reads ``.github/workflows/run_tests.yml`` and fails when a test file is in no +group, in two groups, or named by a group but missing from disk. Add the file to +one of the ``test-subset`` entries in the same PR. Style and typing ---------------- -Formatting and linting run through ``ruff`` under pre-commit, and ``mypy`` -checks ``pytensor_ml/``. Both also run in CI, so a clean -``pre-commit run --all-files`` locally is the fastest way to keep a PR green. +``ruff`` formats and lints, ``mypy`` type-checks ``pytensor_ml/``, and both run +in CI, so a clean ``pre-commit run --all-files`` is the cheapest way to keep a +PR green. There is no allowlist of expected type failures: the codebase is +mypy-clean, and it stays that way. + +Docstrings are numpydoc. Every public entrypoint carries an ``Examples`` +section whose code is complete and runnable -- imports included, no ``>>>`` +prompts, so a reader can paste it straight into a script. Those examples are +executed as part of the test suite, so an example that omits an import fails +like any other test. + +Working with LLMs +----------------- + +LLM-assisted contributions are welcome, on two conditions. + +**Disclose it.** Say in the pull request that a model was involved, and roughly +how much. A tab-completion here and there is not the same as a generated module, +and a reviewer reads the two differently. -Bug reports and feature requests belong in the -`issue tracker `_. +**You are responsible for every line you submit, not the model.** You understand +what the code does and why, you can defend each decision in review, and you have +run it. Review comments come to you, and "the model wrote it that way" answers +nothing. Code that nobody understands costs more to maintain than no +contribution at all, and a pull request whose author cannot explain it will be +closed. From e1d82864dfd9140995880b230b9119c37f486db0 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 23:34:38 -0500 Subject: [PATCH 18/26] Add a style guide --- docs/source/dev/contributing.rst | 3 +- docs/source/dev/docs.rst | 2 +- docs/source/dev/index.rst | 1 + docs/source/dev/style_guide.rst | 247 +++++++++++++++++++++++++++++++ 4 files changed, 251 insertions(+), 2 deletions(-) create mode 100644 docs/source/dev/style_guide.rst diff --git a/docs/source/dev/contributing.rst b/docs/source/dev/contributing.rst index ccdcd3f..3c2b2f6 100644 --- a/docs/source/dev/contributing.rst +++ b/docs/source/dev/contributing.rst @@ -66,7 +66,8 @@ Style and typing ``ruff`` formats and lints, ``mypy`` type-checks ``pytensor_ml/``, and both run in CI, so a clean ``pre-commit run --all-files`` is the cheapest way to keep a PR green. There is no allowlist of expected type failures: the codebase is -mypy-clean, and it stays that way. +mypy-clean, and it stays that way. For the conventions no tool enforces, see +the :doc:`style guide `. Docstrings are numpydoc. Every public entrypoint carries an ``Examples`` section whose code is complete and runnable -- imports included, no ``>>>`` diff --git a/docs/source/dev/docs.rst b/docs/source/dev/docs.rst index a490275..4eff841 100644 --- a/docs/source/dev/docs.rst +++ b/docs/source/dev/docs.rst @@ -45,7 +45,7 @@ Source content lives under ``docs/source/``: * - ``examples//*.ipynb`` - Notebook copies staged from ``examples/``. **Generated**. * - ``dev/`` - - This page and other contributor docs. + - This page, the contributing guide and the style guide. * - ``references.bib`` - BibTeX entries; cited via ``{cite:t}`` or ``{cite:p}``. * - ``_templates/autosummary/`` diff --git a/docs/source/dev/index.rst b/docs/source/dev/index.rst index 1937f13..5cd8c58 100644 --- a/docs/source/dev/index.rst +++ b/docs/source/dev/index.rst @@ -5,4 +5,5 @@ Developer Guide :maxdepth: 1 contributing + style_guide docs diff --git a/docs/source/dev/style_guide.rst b/docs/source/dev/style_guide.rst new file mode 100644 index 0000000..19a28c9 --- /dev/null +++ b/docs/source/dev/style_guide.rst @@ -0,0 +1,247 @@ +Style Guide +=========== + +``ruff`` settles anything mechanical -- formatting, import order, line length -- +and CI runs it, so ``pre-commit run --all-files`` decides those questions before +review does. This page covers the rest: the judgment the tools cannot make. + +Most of it you will get right by writing code that looks like the file +around it. + +Code +---- + +Keep hot paths lean: no redundant checks, no invariant recomputed inside a loop +that could be hoisted out of it, no silent ``O(n^2)`` where ``O(n)`` works. A +network trains for hundreds of thousands of steps, and anything on that path is +paid for that many times. + +Errors should be specific and loud. A bare ``except:``, or an +``except Exception: pass`` that swallows a failure, turns a bug into a mystery +three layers away. Validate input where a wrong value would otherwise surface as +a confusing error deep in a graph, and nowhere else; a check that cannot fire is +noise. + +Where you do raise, name the fix in the message. It reaches the reader at the +moment they need it, which no amount of documentation does: + +.. code-block:: python + + raise ValueError( + f"A fan-scaled initializer needs a parameter of at least two dimensions to size its draws, " + f"but got shape {shape}. A bias or a norm scale has no fans; give it an initializer of its " + "own -- `trainable(value, name, initializer=ZeroInitializer())`." + ) + +Design +------ + +Write the simplest thing that works. Generality for a future that has not +arrived is a cost paid now against a benefit that may never come. Two pieces of +code that look alike today but change for different reasons are not duplication, +and forcing them together couples them wrongly, so the rule of three is a good +prior before extracting a helper. + +Keep one function at one altitude: raw string-mangling next to high-level +orchestration means a helper is missing. Be consistent about failure, too. One +module should not raise for some errors, return ``None`` for others, and return +a sentinel for a third kind of the same failure. + +The usual anti-patterns to watch for: + +* A boolean flag that makes one function do two things, and the ``do_thing(True, + False)`` call site it leads to. +* Parameter lists too long to hold in your head. +* A dict standing in for an object, where a dataclass would say what the fields + are. +* Mutable default arguments, and hidden global state. +* A function that both computes and mutates. + +Naming and shape +---------------- + +A name should say what something is for, so the code reads as its own +documentation. Avoid names that describe a type rather than a role: ``data``, +``tmp``, ``obj``, ``result2``. Single letters are fine where they are the +mathematical convention (``i``, ``x``, a kernel's ``k``) and nowhere else. Name +the same concept the same way the surrounding code does, and name the constants: +a bare ``0.9`` in an update rule tells the reader nothing, where ``decay`` tells +them what it is for. + +Shape carries meaning too. Prefer guard clauses to nesting, so the happy path +stays prominent and error handling sits at the edges. Break up expression soup +with named intermediates, since the name *is* the documentation and costs one +line. When two branches do the same kind of thing, shape them the same way, so +that an asymmetry signals a real difference rather than drift. Within a module, +read top down: public API first, helpers below it, reading order roughly +matching call order. + +Comments +-------- + +Comments explain the **why**; the code already says what it does. Fewer is +better -- every comment can drift out of sync, so it has to change a reader's +understanding to earn its place. A better name usually beats a comment. + +Do not commit: + +* Comments that narrate the code (``# increment the counter``). +* Commented-out code. That is what version control is for. +* Process notes: ``# previously we used a loop here``, ``# fix for #123``, or a + ``TODO`` with no owner and no context. Those belong in the commit message. + +Reflow comment prose to the full line width. Short broken-up lines waste +vertical space for no gain. + +Docstrings +---------- + +Docstrings are numpydoc, and they document the **current contract**. Write each +one as if the function appeared in the codebase fresh today. A reader who cloned +the repo an hour ago should never meet a sentence that only makes sense if they +know what the code used to do. + +That rules out, however technical the prose sounds: explaining current behaviour +by contrasting it with a previous version; references to audits, benchmarks, +pull requests or incidents; and "Notes" sections that exist to justify a recent +change rather than to document an invariant. The *why we changed it* goes in the +commit message, where it stays findable without being in front of everyone who +hits ``?`` in a REPL. + +The rest of the rules: + +* **Active voice.** "Compute the gradient", not "The gradient is computed". +* **Every parameter gets a human-readable type**: ``list of int``, not + ``list[int]``. Describe a genuinely nested type in prose rather than pasting a + type hint into the docstring. +* **Optional arguments say so** -- ``, optional`` on the type line -- and the + **default goes in the last sentence** of the description, not on the type + line. +* **Return values are named**, even when nothing ever binds them. +* **No Raises sections.** The error message is the documentation. +* **No module-level docstrings.** If a module's purpose is not evident from its + name and contents, the fix is a better name or a split. +* **Math goes in** ``.. math::`` **directives**, never as backticked ASCII. Use a + raw string so the backslashes survive. Inline, a mathematical symbol takes a + ``:math:`` role and a code identifier takes double backticks: "the input + :math:`\alpha` (parameter ``alpha``)". +* Cross-reference with Sphinx roles: ``:func:``, ``:class:``, ``:mod:``. + +A short private helper whose name and signature already say everything can go +without a docstring. What it cannot have is a chatty paragraph standing in for a +short structured one: + +.. code-block:: python + + def scale(factor: Rate) -> Transform: + """ + Multiply every step by ``factor``. + + Parameters + ---------- + factor : float or TensorVariable + What each step is multiplied by. A symbolic value, such as one a schedule produced, is + applied on-graph. + + Returns + ------- + transform : Transform + A transform that rescales the updates dict. + """ + +Examples +-------- + +Every public entrypoint carries an ``Examples`` section, and the test suite runs +the code in it. Three rules: + +#. **A lead-in sentence, always**, even when the code looks self-evident. Never + open the section with the directive. Where two entrypoints have nearly + identical code, the lead-in is the only thing telling them apart, so it says + what to reach for this one for. +#. ``.. code-block:: python``, **never the** ``>>>`` **prompt.** A prompt cannot + be pasted into a script. +#. **Complete and runnable on its own**, imports included, small and tight. Add a + second block, with its own lead-in, when there is a real fork in usage or a + sharp edge worth showing. + +.. code-block:: rst + + Examples + -------- + Bound the whole update rather than each coordinate, so the direction of the step survives and + only its magnitude is capped: + + .. code-block:: python + + import numpy as np + + from pytensor_ml.layers import Input, Linear + from pytensor_ml.loss import SquaredError, supervised_loss + from pytensor_ml.optim import adam, chain, clip_by_global_norm, compile_train + + X = Input("X", shape=(None, 4)) + loss, target = supervised_loss(Linear("fc", n_in=4, n_out=1)(X), SquaredError(), ndim_out=2) + + step = compile_train(loss, chain(adam(1e-3), clip_by_global_norm(1.0))) + loss_value = step(np.zeros((8, 4)), np.zeros((8, 1))) + +Modern Python +------------- + +The supported floor is Python 3.12, so write for it: + +* PEP 604 unions (``int | None``) and PEP 585 generics (``list[int]``), not + ``Optional`` or ``typing.List``. +* **No** ``from __future__ import annotations``. It is unnecessary here. +* f-strings, context managers, ``pathlib`` over ``os.path`` string-mangling, + ``enumerate`` and ``zip`` over index bookkeeping. +* Comprehensions where they read more clearly than an accumulator loop, and not + where they get dense enough to obscure what is happening. +* **Imports at the top of the module.** A function-local import is warranted + only to break a genuine circular dependency or to guard an optional + dependency, and both cases are obvious from context. +* Public functions and methods carry type hints. They are read as + documentation. + +Tests +----- + +Test code is code, and everything above applies to it -- with one deliberate +exception. **Duplicated setup in tests is usually worth keeping.** A test earns +its value by being auditable as one self-contained block: what was seeded, +sampled, patched and asserted, all visible without jumping to a fixture defined +four hundred lines away. Repeated arrange-phase boilerplate is a smaller cost +than a fragmented test, even at six or eight occurrences. + +Extract a fixture when the block is long enough to bury the assertion it exists +to set up, when it encodes an invariant that must stay identical across tests, +or when a signature change would otherwise mean editing it everywhere. + +Commit messages +--------------- + +Subject line in the imperative, short enough to read at a glance, naming the +thing that changed: "Add windowed gradient mass matrix adapter" beats "Update +mass matrix". + +**Never hard-wrap the body.** One paragraph is one line, however long. Manual +line breaks at 72 characters become unreadable the moment anything reflows them, +and they fossilise a width nothing actually uses. Blank lines separate +paragraphs, and deliberately structured content -- lists, tables, aligned +columns -- keeps its own line breaks. + +A body is worth writing when the *why* is invisible in the diff. It is not worth +writing to restate the diff in prose. + +Cruft +----- + +The diff should look like it was written by someone who cleaned up after +themselves: no leftover ``print`` statements (a pre-commit hook rejects them), +no dead code, no unused imports or locals, no stray scratch files. A file that +genuinely should not be tracked belongs in ``.gitignore``, not deleted and +recreated. + +Every dependency is a liability. Question a new one that the standard library, +NumPy, or PyTensor already covers. From 2be16620b39ce8cf3ec08d74a2e66754e022adc9 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 23:47:01 -0500 Subject: [PATCH 19/26] Delete the unwritten user guide --- docs/source/dev/docs.rst | 2 -- docs/source/index.rst | 1 - docs/source/user_guide/backends.rst | 9 --------- docs/source/user_guide/design.rst | 9 --------- docs/source/user_guide/index.rst | 12 ------------ docs/source/user_guide/layers.rst | 9 --------- docs/source/user_guide/optimizers.rst | 9 --------- docs/source/user_guide/serialization.rst | 9 --------- docs/source/user_guide/training.rst | 9 --------- 9 files changed, 69 deletions(-) delete mode 100644 docs/source/user_guide/backends.rst delete mode 100644 docs/source/user_guide/design.rst delete mode 100644 docs/source/user_guide/index.rst delete mode 100644 docs/source/user_guide/layers.rst delete mode 100644 docs/source/user_guide/optimizers.rst delete mode 100644 docs/source/user_guide/serialization.rst delete mode 100644 docs/source/user_guide/training.rst diff --git a/docs/source/dev/docs.rst b/docs/source/dev/docs.rst index 4eff841..7be991c 100644 --- a/docs/source/dev/docs.rst +++ b/docs/source/dev/docs.rst @@ -38,8 +38,6 @@ Source content lives under ``docs/source/``: - Autosummary entry points; one file per public submodule. * - ``install.rst`` - Installation guide. Hand-written narrative. - * - ``user_guide/`` - - Conceptual pages (layers, training, optimizers, backends, design). * - ``examples/gallery.rst`` - Notebook gallery landing page. **Generated** at build time. * - ``examples//*.ipynb`` diff --git a/docs/source/index.rst b/docs/source/index.rst index da70df0..95fb1a4 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -75,7 +75,6 @@ walkthroughs. :titlesonly: install - user_guide/index examples/gallery api dev/index diff --git a/docs/source/user_guide/backends.rst b/docs/source/user_guide/backends.rst deleted file mode 100644 index 825b47c..0000000 --- a/docs/source/user_guide/backends.rst +++ /dev/null @@ -1,9 +0,0 @@ -Backends -======== - -.. note:: - - **WRITEME.** This page is a stub. Cover choosing a compile mode, which - ops have backend-specific implementations, how ``pytensor_ml.dispatch`` - registers them lazily, and what to expect performance-wise from each - backend. diff --git a/docs/source/user_guide/design.rst b/docs/source/user_guide/design.rst deleted file mode 100644 index 3daa691..0000000 --- a/docs/source/user_guide/design.rst +++ /dev/null @@ -1,9 +0,0 @@ -Design -====== - -.. note:: - - **WRITEME.** This page is a stub. Cover why a network is a graph rather - than a module tree, how parameters and RNGs live as shared variables, the - rewrite passes the library adds, and the consequences for interoperating - with plain PyTensor and PyMC. diff --git a/docs/source/user_guide/index.rst b/docs/source/user_guide/index.rst deleted file mode 100644 index 9fa188e..0000000 --- a/docs/source/user_guide/index.rst +++ /dev/null @@ -1,12 +0,0 @@ -User Guide -========== - -.. toctree:: - :maxdepth: 1 - - layers - training - optimizers - serialization - backends - design diff --git a/docs/source/user_guide/layers.rst b/docs/source/user_guide/layers.rst deleted file mode 100644 index 66a3311..0000000 --- a/docs/source/user_guide/layers.rst +++ /dev/null @@ -1,9 +0,0 @@ -Building networks -================= - -.. note:: - - **WRITEME.** This page is a stub. Cover what a layer is (a graph - constructor, not a stateful module), naming and parameter ownership, - composing with ``Sequential`` and the other combinators, and writing a - custom layer. diff --git a/docs/source/user_guide/optimizers.rst b/docs/source/user_guide/optimizers.rst deleted file mode 100644 index cb2c66e..0000000 --- a/docs/source/user_guide/optimizers.rst +++ /dev/null @@ -1,9 +0,0 @@ -Optimizers and schedules -======================== - -.. note:: - - **WRITEME.** This page is a stub. Cover update rules as compositions of - transforms, ``chain``, gradient clipping and weight decay, learning-rate - schedules and the training clock they read, and step guards - (``skip_if``, ``apply_if_finite``). diff --git a/docs/source/user_guide/serialization.rst b/docs/source/user_guide/serialization.rst deleted file mode 100644 index 97ea95c..0000000 --- a/docs/source/user_guide/serialization.rst +++ /dev/null @@ -1,9 +0,0 @@ -Saving and loading -================== - -.. note:: - - **WRITEME.** This page is a stub. Cover weights-only checkpoints - (``save_state`` / ``load_state``) versus full-network round-trips - (``save_pretrained`` / ``from_pretrained``), the on-disk layout, and what - graph serialization does and does not support. diff --git a/docs/source/user_guide/training.rst b/docs/source/user_guide/training.rst deleted file mode 100644 index d277864..0000000 --- a/docs/source/user_guide/training.rst +++ /dev/null @@ -1,9 +0,0 @@ -Training -======== - -.. note:: - - **WRITEME.** This page is a stub. Cover the training step as a compiled - function, losses and target placeholders, batching with ``DataLoader``, - stateful layers (batch norm statistics, dropout RNGs) and how their - updates are threaded, and the train/predict graph split. From b86902a9739e9951b213cee09fccfa4d07b1b634 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 23:47:35 -0500 Subject: [PATCH 20/26] Emit one gallery page per category, with sections inside each A notebook's path sets where it lands: examples//
/nb.ipynb, where a page with no notebooks writes no file rather than a dangling toctree entry. --- docs/.gitignore | 6 +- docs/source/dev/docs.rst | 10 +- docs/sphinxext/generate_gallery.py | 220 ++++++++++++------ .../{ => gallery}/mnist_feed_forward.ipynb | 0 4 files changed, 153 insertions(+), 83 deletions(-) rename examples/{ => gallery}/mnist_feed_forward.ipynb (100%) diff --git a/docs/.gitignore b/docs/.gitignore index 7872e43..f72e953 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -12,9 +12,5 @@ source/api/**/generated/ source/api/**/classmethods/ # Notebook gallery artifacts written by docs/sphinxext/generate_gallery.py -source/examples/gallery.rst -source/examples/examples/ -source/examples/introductory/ -source/examples/advanced/ -source/examples/case_study/ +source/examples/ source/_thumbnails/ diff --git a/docs/source/dev/docs.rst b/docs/source/dev/docs.rst index 7be991c..ac59dbf 100644 --- a/docs/source/dev/docs.rst +++ b/docs/source/dev/docs.rst @@ -38,10 +38,12 @@ Source content lives under ``docs/source/``: - Autosummary entry points; one file per public submodule. * - ``install.rst`` - Installation guide. Hand-written narrative. - * - ``examples/gallery.rst`` - - Notebook gallery landing page. **Generated** at build time. - * - ``examples//*.ipynb`` - - Notebook copies staged from ``examples/``. **Generated**. + * - ``examples/*.rst`` + - One page per notebook category, in the order set by ``GALLERY_PAGES`` in + ``generate_gallery.py``. A category with no notebooks writes no page, so add its entry to the + toctree in ``index.rst`` along with the first notebook. **Generated** at build time. + * - ``examples//**/*.ipynb`` + - Notebook copies staged from ``examples/``, under the same folders. **Generated**. * - ``dev/`` - This page, the contributing guide and the style guide. * - ``references.bib`` diff --git a/docs/sphinxext/generate_gallery.py b/docs/sphinxext/generate_gallery.py index d732413..184b242 100644 --- a/docs/sphinxext/generate_gallery.py +++ b/docs/sphinxext/generate_gallery.py @@ -24,17 +24,25 @@ REPO_ROOT = Path(__file__).resolve().parent.parent.parent NOTEBOOKS_ROOT = REPO_ROOT / "examples" -# Pretty titles for known subfolders. Anything not listed is title-cased. -CATEGORY_TITLES = { - "examples": "Examples", - "introductory": "Introductory", - "advanced": "Advanced", - "case_study": "Case Studies", -} - -TITLE = """ -Example Gallery -=============== +# A notebook's path under examples/ decides where it lands: `examples//
/nb.ipynb` +# puts it in a named section of that page, `examples//nb.ipynb` puts it on the page with no +# section heading, and a notebook at the top level falls to CATCH_ALL_PAGE. Pages appear in the order +# listed here; a page with no notebooks writes no file at all. +GALLERY_PAGES = ( + ("getting_started", "Getting Started", "getting_started"), + ("gallery", "Example Gallery", "gallery"), +) +CATCH_ALL_PAGE = "gallery" + +# Sections are the folders themselves, titled by their folder name. List a folder here to pin where +# it sits on the page (anything unlisted follows, alphabetically), and give it a title only when +# title-casing the folder name would get it wrong. +SECTION_ORDER: tuple[str, ...] = () +SECTION_TITLES: dict[str, str] = {} + +PAGE_TITLE = """ +{title} +{underlines} """ TOCTREE_HEAD = """ @@ -43,6 +51,12 @@ """ +GRID_HEAD = """ +.. grid:: 1 2 3 3 + :gutter: 4 + +""" + SECTION_TEMPLATE = """ .. _gallery-{section_id}: @@ -153,29 +167,82 @@ def gen_previews(self): def discover_notebooks(): """ - Group notebooks by the category they belong to. + Group every notebook under ``examples/`` by the page and section its path puts it in. Returns ------- - grouped : dict mapping str to list of pathlib.Path - Notebook paths keyed by category: the immediate subfolder of ``examples/`` a notebook sits in, - or ``"examples"`` for one at the top level. + grouped : dict mapping tuple to list of pathlib.Path + Notebook paths keyed by ``(page, section)``, where ``section`` is None for a notebook that + sits directly in a page's folder. """ if not NOTEBOOKS_ROOT.exists(): return {} - grouped: dict[str, list[Path]] = {} + grouped: dict[tuple[str, str | None], list[Path]] = {} for path in sorted(NOTEBOOKS_ROOT.rglob("*.ipynb")): if ".ipynb_checkpoints" in path.parts: continue - rel = path.relative_to(NOTEBOOKS_ROOT) - category = rel.parts[0] if len(rel.parts) > 1 else "examples" - grouped.setdefault(category, []).append(path) + parts = path.relative_to(NOTEBOOKS_ROOT).parts + page = parts[0] if len(parts) > 1 else CATCH_ALL_PAGE + section = parts[1] if len(parts) > 2 else None + grouped.setdefault((page, section), []).append(path) return grouped +def _section_title(section: str) -> str: + return SECTION_TITLES.get(section, section.replace("_", " ").title()) + + +def _page_layout(grouped): + """ + Lay the discovered notebooks out into pages and the sections within them. + + Returns + ------- + layout : list of tuple of str, str and list of tuple + One entry per page that has notebooks, in the order the pages appear: the document name, its + title, and its sections as ``(section title or None, notebook paths)``. + """ + known_pages = {page for page, _, _ in GALLERY_PAGES} + ordering = {section: index for index, section in enumerate(SECTION_ORDER)} + + layout = [] + for page, page_title, document in GALLERY_PAGES: + sections = [(key[1], paths) for key, paths in grouped.items() if key[0] == page] + if document == CATCH_ALL_PAGE: + sections += [ + (key[1], paths) for key, paths in grouped.items() if key[0] not in known_pages + ] + if not sections: + continue + + # Unsectioned notebooks lead the page; the rest follow SECTION_ORDER, then alphabetically. + sections.sort( + key=lambda item: ( + item[0] is not None, + ordering.get(item[0], len(ordering)), + item[0] or "", + ) + ) + merged: dict[str | None, list[Path]] = {} + for section, paths in sections: + merged.setdefault(section, []).extend(paths) + sections = list(merged.items()) + layout.append( + ( + document, + page_title, + [ + (None if section is None else _section_title(section), paths) + for section, paths in sections + ], + ) + ) + return layout + + def main(app): - logger.info("Starting pytensor_ml example gallery generation.") + logger.info("Starting pytensor_ml gallery generation.") src_dir = Path(app.builder.srcdir) examples_dir = src_dir / "examples" @@ -184,69 +251,74 @@ def main(app): thumbnails_dir.mkdir(parents=True, exist_ok=True) grouped = discover_notebooks() - if not grouped: logger.warning( - "No notebooks found under examples/; writing empty gallery.", + "No notebooks found under examples/; no gallery pages will be written.", type="thumbnail_extractor", ) - toctree_entries: list[str] = [] - section_lines: list[str] = [] - - for category in sorted(grouped): - nb_paths = grouped[category] - title = CATEGORY_TITLES.get(category, category.replace("_", " ").title()) - section_lines.append( - SECTION_TEMPLATE.format( - section_title=title, - section_id=category, - underlines="-" * len(title), - ) - ) - - for nb_path in nb_paths: - if not is_tracked_by_git(nb_path): - logger.info( - f"Skipping {nb_path.name}, not tracked by git", - type="thumbnail_extractor", + for document, page_title, sections in _page_layout(grouped): + toctree_entries: list[str] = [] + body: list[str] = [] + + for section_title, nb_paths in sections: + if section_title is None: + body.append(GRID_HEAD) + else: + body.append( + SECTION_TEMPLATE.format( + section_title=section_title, + section_id=section_title.lower().replace(" ", "-"), + underlines="-" * len(section_title), + ) ) - continue - nbg = NotebookGenerator( - src_nb=nb_path, - category=category, - examples_dir=examples_dir, - thumbnails_dir=thumbnails_dir, - ) - nbg.stage_notebook() - nbg.gen_previews() - - doc_name = f"{category}/{nbg.stripped_name}" - toctree_entries.append(doc_name) - # Path is relative to docs/source/ — the leading slash makes - # Sphinx resolve it from the source root, matching gEconpy's - # convention so users can drop in custom thumbnails too. - img_path = f"/_thumbnails/{category}/{nbg.stripped_name}.png" - section_lines.append( - ITEM_TEMPLATE.format( - doc_name=doc_name, - image=img_path, - doc_reference=doc_name, - link_type="doc", + for nb_path in nb_paths: + if not is_tracked_by_git(nb_path): + logger.info( + f"Skipping {nb_path.name}, not tracked by git", + type="thumbnail_extractor", + ) + continue + + # Staged under the same folders it came from, so two notebooks sharing a name in + # different sections cannot collide. + relative_dir = nb_path.parent.relative_to(NOTEBOOKS_ROOT) + nbg = NotebookGenerator( + src_nb=nb_path, + category=str(relative_dir) if relative_dir.parts else CATCH_ALL_PAGE, + examples_dir=examples_dir, + thumbnails_dir=thumbnails_dir, + ) + nbg.stage_notebook() + nbg.gen_previews() + + doc_name = f"{nbg.category}/{nbg.stripped_name}" + toctree_entries.append(doc_name) + # Relative to docs/source/ — the leading slash makes Sphinx resolve it from the source + # root, so a hand-supplied thumbnail can be dropped in at the same path. + img_path = f"/_thumbnails/{nbg.category}/{nbg.stripped_name}.png" + body.append( + ITEM_TEMPLATE.format( + doc_name=doc_name, + image=img_path, + doc_reference=doc_name, + link_type="doc", + ) ) - ) - - # Assemble: title, hidden toctree (so notebooks register with Sphinx), - # then the visible grid-card sections. - file_lines = [TITLE, TOCTREE_HEAD] - file_lines.extend(f" {entry}\n" for entry in toctree_entries) - file_lines.append("\n") - file_lines.extend(section_lines) - gallery_rst = examples_dir / "gallery.rst" - gallery_rst.write_text("\n".join(file_lines), encoding="utf-8") - logger.info(f"Wrote gallery to {gallery_rst.relative_to(src_dir)}") + # Title, then a hidden toctree so the notebooks register with Sphinx, then the grid cards. + file_lines = [ + PAGE_TITLE.format(title=page_title, underlines="=" * len(page_title)), + TOCTREE_HEAD, + ] + file_lines.extend(f" {entry}\n" for entry in toctree_entries) + file_lines.append("\n") + file_lines.extend(body) + + page_rst = examples_dir / f"{document}.rst" + page_rst.write_text("\n".join(file_lines), encoding="utf-8") + logger.info(f"Wrote {page_title} to {page_rst.relative_to(src_dir)}") def setup(app): diff --git a/examples/mnist_feed_forward.ipynb b/examples/gallery/mnist_feed_forward.ipynb similarity index 100% rename from examples/mnist_feed_forward.ipynb rename to examples/gallery/mnist_feed_forward.ipynb From 9e7f4c701a1b8c25094ad5cfbd96e4d0e34913b5 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 23:55:53 -0500 Subject: [PATCH 21/26] Simplify how the gallery lays sections out on a page --- docs/sphinxext/generate_gallery.py | 50 +++++++++++++----------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/docs/sphinxext/generate_gallery.py b/docs/sphinxext/generate_gallery.py index 184b242..06c23ee 100644 --- a/docs/sphinxext/generate_gallery.py +++ b/docs/sphinxext/generate_gallery.py @@ -165,7 +165,7 @@ def gen_previews(self): ) -def discover_notebooks(): +def discover_notebooks() -> dict[tuple[str, str | None], list[Path]]: """ Group every notebook under ``examples/`` by the page and section its path puts it in. @@ -206,38 +206,30 @@ def _page_layout(grouped): known_pages = {page for page, _, _ in GALLERY_PAGES} ordering = {section: index for index, section in enumerate(SECTION_ORDER)} + def position(section: str | None) -> tuple[bool, int, str]: + # Unsectioned notebooks lead the page; the rest follow SECTION_ORDER, then alphabetically. + return section is not None, ordering.get(section, len(ordering)), section or "" + layout = [] for page, page_title, document in GALLERY_PAGES: - sections = [(key[1], paths) for key, paths in grouped.items() if key[0] == page] - if document == CATCH_ALL_PAGE: - sections += [ - (key[1], paths) for key, paths in grouped.items() if key[0] not in known_pages - ] - if not sections: - continue - - # Unsectioned notebooks lead the page; the rest follow SECTION_ORDER, then alphabetically. - sections.sort( - key=lambda item: ( - item[0] is not None, - ordering.get(item[0], len(ordering)), - item[0] or "", - ) - ) + # One entry per section, so two folders of the same name land under one heading rather than + # two headings competing for the same label. merged: dict[str | None, list[Path]] = {} - for section, paths in sections: - merged.setdefault(section, []).extend(paths) - sections = list(merged.items()) - layout.append( - ( - document, - page_title, - [ - (None if section is None else _section_title(section), paths) - for section, paths in sections - ], + for (notebook_page, section), paths in grouped.items(): + on_this_page = notebook_page == page or ( + document == CATCH_ALL_PAGE and notebook_page not in known_pages ) - ) + if on_this_page: + merged.setdefault(section, []).extend(paths) + + if not merged: + continue + + sections = [ + (None if section is None else _section_title(section), merged[section]) + for section in sorted(merged, key=position) + ] + layout.append((document, page_title, sections)) return layout From 198150633b76976115f6c8b6b309c34d5bbd14f2 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 23:56:10 -0500 Subject: [PATCH 22/26] Fail one docstring example instead of the whole collection --- tests/test_docstring_examples.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_docstring_examples.py b/tests/test_docstring_examples.py index 3f909b4..f35c9f8 100644 --- a/tests/test_docstring_examples.py +++ b/tests/test_docstring_examples.py @@ -25,11 +25,7 @@ def _example_blocks(docstring: str) -> list[str]: if candidate.strip() and indent <= directive_indent: break body.append(candidate) - code = textwrap.dedent("\n".join(body)).strip() - # An empty block means the directive is there but its body is not indented under it, which used to - # pass as a trivially successful exec. - assert code, f"code-block at line {index} has no indented body" - blocks.append(code) + blocks.append(textwrap.dedent("\n".join(body)).strip()) return blocks @@ -53,5 +49,9 @@ def test_docstring_example_runs(qualified_name, source, tmp_path, monkeypatch): # A fresh namespace per block: an example that leans on a name another example imported is not the # self-contained snippet a reader is invited to paste into a script. Each runs in its own directory # so an example that writes a checkpoint can use the plain relative path a reader would. + # A block whose body is not indented under the directive reads as empty here, and an empty exec + # passes for the wrong reason. + assert source, f"the code-block in {qualified_name} has no indented body" + monkeypatch.chdir(tmp_path) exec(compile(source, f"<{qualified_name}>", "exec"), {"__name__": "__main__"}) From 3ce5a49bbb7a5dc50a45d0898475322b37edf1b4 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 23:56:26 -0500 Subject: [PATCH 23/26] Scan attribute docstrings for control characters too A type alias carries its docstring under the assignment rather than in __doc__, so the six in optim were never scanned. --- tests/public_api.py | 20 ++++++++++++++++++++ tests/test_docstring_examples.py | 13 ++++--------- tests/test_docstrings.py | 14 +++++--------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/tests/public_api.py b/tests/public_api.py index 7527d3f..a4a303d 100644 --- a/tests/public_api.py +++ b/tests/public_api.py @@ -77,3 +77,23 @@ def attribute_docstrings(module: types.ModuleType) -> list[tuple[str, str]]: if name and not name.startswith("_") and is_docstring: documented.append((f"{module.__name__}.{name}", following.value.value)) return documented + + +def all_docstrings() -> list[tuple[str, str]]: + """ + Collect every public docstring in the package, wherever it is written. + + Returns + ------- + documented : list of tuple of str and str + Each qualified name paired with its docstring, covering objects that carry ``__doc__`` and + module attributes documented by a string literal beneath them. + """ + documented = [] + for qualified_name, obj in public_objects(): + docstring = getattr(obj, "__doc__", None) + if docstring: + documented.append((qualified_name, docstring)) + if isinstance(obj, types.ModuleType): + documented.extend(attribute_docstrings(obj)) + return documented diff --git a/tests/test_docstring_examples.py b/tests/test_docstring_examples.py index f35c9f8..8b8706a 100644 --- a/tests/test_docstring_examples.py +++ b/tests/test_docstring_examples.py @@ -1,9 +1,8 @@ import textwrap -import types import pytest -from tests.public_api import attribute_docstrings, public_objects +from tests.public_api import all_docstrings CODE_BLOCK_DIRECTIVE = ".. code-block:: python" @@ -31,13 +30,9 @@ def _example_blocks(docstring: str) -> list[str]: def _collect_examples() -> list[tuple[str, str]]: examples = [] - for qualified_name, obj in public_objects(): - docstrings = [(qualified_name, obj.__doc__ or "")] - if isinstance(obj, types.ModuleType): - docstrings.extend(attribute_docstrings(obj)) - for name, docstring in docstrings: - for position, block in enumerate(_example_blocks(docstring)): - examples.append((f"{name}[{position}]", block)) + for qualified_name, docstring in all_docstrings(): + for position, block in enumerate(_example_blocks(docstring)): + examples.append((f"{qualified_name}[{position}]", block)) return examples diff --git a/tests/test_docstrings.py b/tests/test_docstrings.py index cfadfb9..71625bb 100644 --- a/tests/test_docstrings.py +++ b/tests/test_docstrings.py @@ -1,23 +1,19 @@ import pytest -from tests.public_api import public_objects +from tests.public_api import all_docstrings # A LaTeX macro in a docstring that forgot its `r` prefix silently becomes a control character: -# `\text` is a tab, `\rceil` a carriage return, `\b` a backspace. The docstring still renders, just +# `\text` is a tab, `\rceil` a carriage return, `\frac` a formfeed. The docstring still renders, just # wrong, so nothing but a scan like this catches it. CONTROL_CHARACTERS = {"\t": r"\t", "\r": r"\r", "\x08": r"\b", "\x0c": r"\f", "\x0b": r"\v"} -PUBLIC_OBJECTS = public_objects() +DOCSTRINGS = all_docstrings() @pytest.mark.parametrize( - "qualified_name, obj", PUBLIC_OBJECTS, ids=[name for name, _ in PUBLIC_OBJECTS] + "qualified_name, docstring", DOCSTRINGS, ids=[name for name, _ in DOCSTRINGS] ) -def test_docstring_has_no_control_characters(qualified_name, obj): - docstring = getattr(obj, "__doc__", None) - if not docstring: - return - +def test_docstring_has_no_control_characters(qualified_name, docstring): found = {escape for character, escape in CONTROL_CHARACTERS.items() if character in docstring} assert not found, ( f"{qualified_name} has {sorted(found)} in its docstring, which means a LaTeX macro was " From 00f558062202feb67abe791db6997a04159c1e8a Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Mon, 24 Aug 2026 23:56:34 -0500 Subject: [PATCH 24/26] Add tests for the public-API collectors Both docstring sweeps parametrize over these, so a collector that quietly returns less takes its own tests with it and the suite stays green. --- .github/workflows/run_tests.yml | 1 + tests/test_public_api.py | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 tests/test_public_api.py diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index a1f190e..80a5816 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -60,6 +60,7 @@ jobs: tests/test_workflow_groups.py tests/test_docstrings.py tests/test_docstring_examples.py + tests/test_public_api.py # Windows runners are roughly twice as slow, and almost everything here is platform independent. # It runs one job over the parts that are not: file IO, paths, and compiling a graph to train. # One job per backend, each installing only that backend. They live here rather than in diff --git a/tests/test_public_api.py b/tests/test_public_api.py new file mode 100644 index 0000000..f883cc6 --- /dev/null +++ b/tests/test_public_api.py @@ -0,0 +1,37 @@ +import pytensor_ml + +from tests.public_api import all_docstrings, attribute_docstrings, public_objects + +# Both docstring sweeps parametrize over these collectors, so a collector that quietly returns less +# than it should takes its tests with it: the suite reports a smaller number and stays green. These +# assert the collectors reach the corners of the package that are easy to lose. + + +def test_public_objects_reaches_every_layer_of_the_package(): + found = {name for name, _ in public_objects()} + + assert "pytensor_ml.model.Model" in found, "a class re-exported from the package root" + assert "pytensor_ml.optim.alias.adam" in found, "a function nested two packages deep" + assert "pytensor_ml.layers.linear" in found, "a module, not just the objects inside it" + + +def test_public_objects_skips_the_backend_dispatch_modules(): + dispatch_modules = [name for name, _ in public_objects() if ".dispatch." in name] + + # Importing one pulls in the backend itself, and the core test jobs install none of them. + assert not dispatch_modules + + +def test_attribute_docstrings_finds_a_type_alias(): + documented = dict(attribute_docstrings(pytensor_ml.optim.base)) + + # A type alias cannot carry __doc__, so the literal beneath the assignment is its only docstring + # and the only thing autodoc renders. + assert "Examples" in documented["pytensor_ml.optim.base.Transform"] + + +def test_all_docstrings_covers_both_kinds_of_docstring(): + documented = dict(all_docstrings()) + + assert "pytensor_ml.model.Model" in documented, "an object's own __doc__" + assert "pytensor_ml.optim.base.Schedule" in documented, "a module attribute's docstring" From 4253dc5ee4e2b7d162570dab72fd7e1097095323 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Tue, 25 Aug 2026 00:29:26 -0500 Subject: [PATCH 25/26] Remove the hand-rolled docstring test harness A doctest plugin covers this ground, so the docs no longer claim the suite runs the examples in them. --- .github/workflows/run_tests.yml | 3 - docs/source/dev/contributing.rst | 5 +- docs/source/dev/style_guide.rst | 3 +- tests/public_api.py | 99 -------------------------------- tests/test_docstring_examples.py | 52 ----------------- tests/test_docstrings.py | 21 ------- tests/test_public_api.py | 37 ------------ 7 files changed, 3 insertions(+), 217 deletions(-) delete mode 100644 tests/public_api.py delete mode 100644 tests/test_docstring_examples.py delete mode 100644 tests/test_docstrings.py delete mode 100644 tests/test_public_api.py diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 80a5816..4dd5960 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -58,9 +58,6 @@ jobs: tests/test_collect.py tests/test_util.py tests/test_workflow_groups.py - tests/test_docstrings.py - tests/test_docstring_examples.py - tests/test_public_api.py # Windows runners are roughly twice as slow, and almost everything here is platform independent. # It runs one job over the parts that are not: file IO, paths, and compiling a graph to train. # One job per backend, each installing only that backend. They live here rather than in diff --git a/docs/source/dev/contributing.rst b/docs/source/dev/contributing.rst index 3c2b2f6..5b20901 100644 --- a/docs/source/dev/contributing.rst +++ b/docs/source/dev/contributing.rst @@ -71,9 +71,8 @@ the :doc:`style guide `. Docstrings are numpydoc. Every public entrypoint carries an ``Examples`` section whose code is complete and runnable -- imports included, no ``>>>`` -prompts, so a reader can paste it straight into a script. Those examples are -executed as part of the test suite, so an example that omits an import fails -like any other test. +prompts, so a reader can paste it straight into a script. Run a new example +before you submit it; nothing checks that for you yet. Working with LLMs ----------------- diff --git a/docs/source/dev/style_guide.rst b/docs/source/dev/style_guide.rst index 19a28c9..033c19b 100644 --- a/docs/source/dev/style_guide.rst +++ b/docs/source/dev/style_guide.rst @@ -152,8 +152,7 @@ short structured one: Examples -------- -Every public entrypoint carries an ``Examples`` section, and the test suite runs -the code in it. Three rules: +Every public entrypoint carries an ``Examples`` section. Three rules: #. **A lead-in sentence, always**, even when the code looks self-evident. Never open the section with the directive. Where two entrypoints have nearly diff --git a/tests/public_api.py b/tests/public_api.py deleted file mode 100644 index a4a303d..0000000 --- a/tests/public_api.py +++ /dev/null @@ -1,99 +0,0 @@ -import ast -import importlib -import inspect -import itertools -import pkgutil -import types - -import pytensor_ml - -# Importing a backend dispatch module pulls in the backend itself, and the core test jobs deliberately -# install none of them. -BACKEND_DISPATCH = "pytensor_ml.dispatch." - - -def public_objects() -> list[tuple[str, object]]: - """ - Collect every public object the package defines, for tests that sweep the whole API. - - Returns - ------- - objects : list of tuple of str and object - Each object paired with its qualified name, listed under the module that defines it rather than - every module that re-exports it. - """ - objects: list[tuple[str, object]] = [] - for module_info in pkgutil.walk_packages(pytensor_ml.__path__, f"{pytensor_ml.__name__}."): - if module_info.name.startswith(BACKEND_DISPATCH): - continue - module = importlib.import_module(module_info.name) - objects.append((module_info.name, module)) - for name, obj in vars(module).items(): - if not name.startswith("_") and getattr(obj, "__module__", None) == module_info.name: - objects.append((f"{module_info.name}.{name}", obj)) - return objects - - -def _assigned_name(node: ast.stmt) -> str | None: - if ( - isinstance(node, ast.Assign) - and len(node.targets) == 1 - and isinstance(node.targets[0], ast.Name) - ): - return node.targets[0].id - if isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name): - return node.target.id - if isinstance(node, ast.TypeAlias) and isinstance(node.name, ast.Name): - return node.name.id - return None - - -def attribute_docstrings(module: types.ModuleType) -> list[tuple[str, str]]: - """ - Collect the docstrings written under a module's public assignments. - - A type alias cannot carry ``__doc__``, so the string literal below its assignment is the only - documentation it has, and the only place Sphinx looks. - - Parameters - ---------- - module : module - The module to read. - - Returns - ------- - documented : list of tuple of str and str - Each qualified attribute name paired with the docstring written beneath it. - """ - body = ast.parse(inspect.getsource(module)).body - documented = [] - for assignment, following in itertools.pairwise(body): - name = _assigned_name(assignment) - is_docstring = ( - isinstance(following, ast.Expr) - and isinstance(following.value, ast.Constant) - and isinstance(following.value.value, str) - ) - if name and not name.startswith("_") and is_docstring: - documented.append((f"{module.__name__}.{name}", following.value.value)) - return documented - - -def all_docstrings() -> list[tuple[str, str]]: - """ - Collect every public docstring in the package, wherever it is written. - - Returns - ------- - documented : list of tuple of str and str - Each qualified name paired with its docstring, covering objects that carry ``__doc__`` and - module attributes documented by a string literal beneath them. - """ - documented = [] - for qualified_name, obj in public_objects(): - docstring = getattr(obj, "__doc__", None) - if docstring: - documented.append((qualified_name, docstring)) - if isinstance(obj, types.ModuleType): - documented.extend(attribute_docstrings(obj)) - return documented diff --git a/tests/test_docstring_examples.py b/tests/test_docstring_examples.py deleted file mode 100644 index 8b8706a..0000000 --- a/tests/test_docstring_examples.py +++ /dev/null @@ -1,52 +0,0 @@ -import textwrap - -import pytest - -from tests.public_api import all_docstrings - -CODE_BLOCK_DIRECTIVE = ".. code-block:: python" - - -def _example_blocks(docstring: str) -> list[str]: - lines = docstring.splitlines() - start = next((i for i, line in enumerate(lines) if line.strip() == "Examples"), None) - if start is None: - return [] - - blocks = [] - for index, line in enumerate(lines[start:], start=start): - if line.strip() != CODE_BLOCK_DIRECTIVE: - continue - directive_indent = len(line) - len(line.lstrip()) - body = [] - for candidate in lines[index + 1 :]: - indent = len(candidate) - len(candidate.lstrip()) - if candidate.strip() and indent <= directive_indent: - break - body.append(candidate) - blocks.append(textwrap.dedent("\n".join(body)).strip()) - return blocks - - -def _collect_examples() -> list[tuple[str, str]]: - examples = [] - for qualified_name, docstring in all_docstrings(): - for position, block in enumerate(_example_blocks(docstring)): - examples.append((f"{qualified_name}[{position}]", block)) - return examples - - -EXAMPLES = _collect_examples() - - -@pytest.mark.parametrize("qualified_name, source", EXAMPLES, ids=[name for name, _ in EXAMPLES]) -def test_docstring_example_runs(qualified_name, source, tmp_path, monkeypatch): - # A fresh namespace per block: an example that leans on a name another example imported is not the - # self-contained snippet a reader is invited to paste into a script. Each runs in its own directory - # so an example that writes a checkpoint can use the plain relative path a reader would. - # A block whose body is not indented under the directive reads as empty here, and an empty exec - # passes for the wrong reason. - assert source, f"the code-block in {qualified_name} has no indented body" - - monkeypatch.chdir(tmp_path) - exec(compile(source, f"<{qualified_name}>", "exec"), {"__name__": "__main__"}) diff --git a/tests/test_docstrings.py b/tests/test_docstrings.py deleted file mode 100644 index 71625bb..0000000 --- a/tests/test_docstrings.py +++ /dev/null @@ -1,21 +0,0 @@ -import pytest - -from tests.public_api import all_docstrings - -# A LaTeX macro in a docstring that forgot its `r` prefix silently becomes a control character: -# `\text` is a tab, `\rceil` a carriage return, `\frac` a formfeed. The docstring still renders, just -# wrong, so nothing but a scan like this catches it. -CONTROL_CHARACTERS = {"\t": r"\t", "\r": r"\r", "\x08": r"\b", "\x0c": r"\f", "\x0b": r"\v"} - -DOCSTRINGS = all_docstrings() - - -@pytest.mark.parametrize( - "qualified_name, docstring", DOCSTRINGS, ids=[name for name, _ in DOCSTRINGS] -) -def test_docstring_has_no_control_characters(qualified_name, docstring): - found = {escape for character, escape in CONTROL_CHARACTERS.items() if character in docstring} - assert not found, ( - f"{qualified_name} has {sorted(found)} in its docstring, which means a LaTeX macro was " - f'interpreted as an escape sequence. Mark the docstring raw: r"""..."""' - ) diff --git a/tests/test_public_api.py b/tests/test_public_api.py deleted file mode 100644 index f883cc6..0000000 --- a/tests/test_public_api.py +++ /dev/null @@ -1,37 +0,0 @@ -import pytensor_ml - -from tests.public_api import all_docstrings, attribute_docstrings, public_objects - -# Both docstring sweeps parametrize over these collectors, so a collector that quietly returns less -# than it should takes its tests with it: the suite reports a smaller number and stays green. These -# assert the collectors reach the corners of the package that are easy to lose. - - -def test_public_objects_reaches_every_layer_of_the_package(): - found = {name for name, _ in public_objects()} - - assert "pytensor_ml.model.Model" in found, "a class re-exported from the package root" - assert "pytensor_ml.optim.alias.adam" in found, "a function nested two packages deep" - assert "pytensor_ml.layers.linear" in found, "a module, not just the objects inside it" - - -def test_public_objects_skips_the_backend_dispatch_modules(): - dispatch_modules = [name for name, _ in public_objects() if ".dispatch." in name] - - # Importing one pulls in the backend itself, and the core test jobs install none of them. - assert not dispatch_modules - - -def test_attribute_docstrings_finds_a_type_alias(): - documented = dict(attribute_docstrings(pytensor_ml.optim.base)) - - # A type alias cannot carry __doc__, so the literal beneath the assignment is its only docstring - # and the only thing autodoc renders. - assert "Examples" in documented["pytensor_ml.optim.base.Transform"] - - -def test_all_docstrings_covers_both_kinds_of_docstring(): - documented = dict(all_docstrings()) - - assert "pytensor_ml.model.Model" in documented, "an object's own __doc__" - assert "pytensor_ml.optim.base.Schedule" in documented, "a module attribute's docstring" From 4873aa2a501b0c39a45d00bbda41dd7e76d9f401 Mon Sep 17 00:00:00 2001 From: jessegrabowski Date: Tue, 25 Aug 2026 17:24:54 -0500 Subject: [PATCH 26/26] Stop docs-serve rebuilding in a loop A build writes the gallery pages, thumbnails and autosummary stubs back into source/, which the watcher saw as changes. --- pyproject.toml | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d00f94e..6b6eb92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -181,8 +181,16 @@ pytensor_ml = { path = ".", editable = true } [tool.pixi.feature.docs.tasks] docs-build = { cmd = "sphinx-build -b html source build/html", cwd = "docs" } -# Rebuilds on save and serves the result at http://localhost:8000. -docs-serve = { cmd = "sphinx-autobuild source build/html --port 8000 --open-browser", cwd = "docs" } +# Rebuilds on save and serves the result at http://localhost:8000. The ignored paths are the ones a +# build writes back into source/ -- the gallery pages and staged notebooks, their thumbnails, and the +# autosummary stubs -- which would otherwise retrigger the watcher and rebuild forever. They are the +# same set docs/.gitignore lists, and have to move together. +docs-serve = { cmd = """\ + sphinx-autobuild source build/html --port 8000 --open-browser \ + --ignore '*/source/examples/*' \ + --ignore '*/source/_thumbnails/*' \ + --ignore '*/source/api/generated/*'\ +""", cwd = "docs" } [tool.pixi.environments] docs = { features = ["docs"] }