diff --git a/.gitignore b/.gitignore index fef67c6..f660432 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ memory_runtime* +NEW_E2_not_working +OLD_E2_working +NEW_run_old_code_line_tensions tests/other_tests vertax_final_alessandro edges.txt @@ -11,6 +14,8 @@ Alessandro_plot_code .ipynb_checkpoints test_n_bilevel/ test_n_bilevel_bounded/ +tests/correlation/results* +VertAX-7.pdf # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/README.md b/README.md index ec9e952..3e5f746 100644 --- a/README.md +++ b/README.md @@ -1,102 +1,298 @@ -# VertAX +
- + -[![License](https://img.shields.io/pypi/l/vertAX.svg)](https://github.com/vertAX/vertAX/raw/main/LICENSE) +[![License: CC BY-SA](https://img.shields.io/badge/License-CC%20BY--SA-lightgrey.svg)](https://creativecommons.org/licenses/by-sa/4.0/) [![Python package index](https://img.shields.io/pypi/v/vertAX.svg)](https://pypi.org/project/vertAX) -[![DOI](https://zenodo.org/badge/144513571.svg)](https://zenodo.org/badge/latestdoi/144513571) +[![Documentation](https://img.shields.io/badge/documentation-green)](https://html-preview.github.io/?url=https://github.com/VirtualEmbryo/VertAX/blob/main/docs/vertax.html) - + -[![Development Status](https://img.shields.io/pypi/status/vertAX.svg)](https://en.wikipedia.org/wiki/Software_release_life_cycle#Beta) +
-JAX-based differentiable vertex model suitable for solving inverse problems for -confluent tissues through bilevel optimization. + + + + + +
-TODO @Alessandro : more explanations ? Images ? +VertAX -## installation + + +A differentiable JAX-based framework
+for vertex modeling and inverse design of epithelial tissues. +
+

-For a full installation, we recommend installing vertAX into a virtual environment, like this: +[![Typing SVG](https://readme-typing-svg.herokuapp.com?font=Fira+Code&size=18&duration=2000&pause=1200&color=38C2FF¢er=false&width=295&height=30&lines=%5C+Forward+Simulations;%5C+Parameter+Inference;%5C+Inverse+Mechanical+Design)](https://gitlab.college-de-france.fr/virtualembryo/vertax)
— all in one unified Python package. + +
+ +--- + +## What is VertAX? + +Epithelial tissues dynamically reshape through local mechanical interactions among cells. Understanding, inferring, and designing these mechanics is a central challenge in developmental biology and biophysics. **VertAX** is a computational framework built to address this challenge. + +**VertAX** is a **framework for vertex-based modeling**: it represents epithelial tissues as two-dimensional polygonal meshes in which cells are faces, junctions are edges, tricellular contacts are vertices, and mechanical equilibrium is defined by the minimum of a user-specified energy. Built on **JAX**, VertAX is designed not only for forward simulation, but also for inverse problems such as parameter inference and tissue design. + +--- + +## Conceptual Overview + +VertAX treats inverse modeling as a **bilevel optimization** problem: + +$$ +\begin{aligned} +\textbf{Outer problem (learning):} \quad +\theta^{\ast} &= \arg\min_{\theta} \mathcal{C}\left(X^{\ast}_{\theta},\theta\right) +&& \leftarrow \text{fit data or reach a target} \\ +\textbf{Inner problem (physics):} \quad \text{s.t.}& +X^{\ast}_{\theta} \in \arg\min_{X} \mathcal{E}(X,\theta) +&& \leftarrow \text{compute mechanical equilibrium} +\end{aligned} +$$ + +Here, $X$ denotes the tissue configuration, i.e. the vertex positions of the mesh, and $\theta$ denotes the model parameters, such as line tensions, target areas, or shape factors. + +In other words, VertAX repeatedly solves a mechanical equilibrium problem for a given parameter set $\theta$, then updates those parameters to better match data or a design objective. + +

+ VertAX Concept
+ Figure: Bilevel optimization loop in VertAX. +

+ +--- + +## Core Features + +- 🧩 **Bilevel optimization framework** + VertAX formulates inverse problems as nested optimization: an inner mechanical equilibrium problem and an outer parameter-learning problem. + +- 🔬 **Multiple gradient strategies** + Supports **Automatic Differentiation (AD)**, **Implicit Differentiation (ID)**, and **Equilibrium Propagation (EP)**. + +- 🔁 **Differentiable and non-differentiable workflows** + VertAX supports fully differentiable pipelines, while EP also enables inverse modeling with simulators that are only accessible through repeated executions. + +- ⚡ **GPU acceleration with JAX** + JIT compilation and vectorization enable efficient simulations on CPU and GPU. + +- 🎨 **Custom energies and costs in plain Python** + Define your own mechanical models and inverse-design objectives without changing the library internals. + +- 🏗️ **Two simulation modes** + Supports both periodic tissues (bulk mechanics) and bounded tissues (finite clusters with curved interfaces). + +- 🔀 **Automatic topology changes** + Handles T1 neighbor exchanges during optimization. + +- 🔗 **Seamless ML integration** + Designed to work naturally with the JAX/Optax ecosystem. + +--- + +## Installation + +We recommend installing VertAX in a virtual environment: ```sh python -m venv .venv source .venv/bin/activate -pip install "vertax" ``` -## simple example +### From source + +```sh +git clone https://github.com/VirtualEmbryo/VertAX.git +cd vertax +pip install -e . +``` + +### From PyPI + +```sh +pip install vertax +``` + +**Dependencies**: JAX, Optax, SciPy (for Voronoi initialization), Matplotlib (for plotting). + +For GPU support, install JAX with CUDA as described in the [JAX docs](https://github.com/google/jax#installation) before installing VertAX. + +--- + +## Simulation modes + +VertAX supports two complementary simulation modes, designed for different classes of epithelial mechanics problems. The **periodic** mode is best suited for bulk tissue dynamics without explicit external boundaries, while the **bounded** mode is designed for finite tissue clusters with curved free interfaces. Both modes share the same vertex-based formulation and optimization framework, but differ in how boundaries are represented and initialized. -TODO @Alessandro : explain what it does + + + + + + + + + + + + + + + + + + +
ModeUse caseInitializationIllustration
PeriodicBulk tissue dynamics, no explicit boundariesRandom Voronoi seeds or segmented images (Cellpose) + Periodic and bounded simulation modes in VertAX +
BoundedFinite tissue clusters with curved interfacesRandom Voronoi seeds; boundary arcs as additional degrees of freedom (DOFs)
+ +--- + +## Gradient Strategies + +VertAX implements and benchmarks three complementary methods for computing outer gradients through the implicit inner problem: + +| Method | How it works | Pros | Cons | +| -------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | ----------------------------------------------------- | +| **AD** (Automatic Diff.) | Unrolls the inner optimization steps; forward-mode JVP via `jax.jacfwd` | Exact for differentiable pipelines; easy in JAX | Cost scales with # iterations × # parameters | +| **ID** (Implicit Diff.) | Differentiates the optimality condition ∇ₓE=0 via Implicit Function Theorem; JVP or adjoint (VJP) variant | No unrolling; constant memory; exact near equilibrium | Requires Hessian solve; sensitive to ill-conditioning | +| **EP** (Equilibrium Prop.) | Estimates gradient from perturbed free and nudged equilibria; no backprop required | Memory-efficient; works with non-differentiable/incomplete solvers | Approximate; depends on perturbation size β | + +**In practice**: AD and EP often recover similar parameter trends on synthetic inverse problems, while EP is especially attractive for simulators that cannot be made fully differentiable. + +--- + +## Tutorials + +See the [`examples/`](examples) folder for in-depth examples: + +| Notebook | Description | +| ----------------------------------------- | ---------------------------------------------------------- | +| `inverse_modelling_example.ipynb` | Inverse modeling with periodic boundary conditions | +| `inverse_modelling_example_bounded.ipynb` | Inverse design with bounded cluster (convergent extension) | + +--- + +## API Reference + +See the [documentation](https://html-preview.github.io/?url=https://github.com/VirtualEmbryo/VertAX/blob/main/docs/vertax.html). + +--- + +## Quick Start — Inverse Modeling + +VertAX can also optimize model parameters to match a target geometry. ```python +import math +import jax import jax.numpy as jnp -from jax import Array - -from vertax import PbcBilevelOptimizer, PbcMesh, plot_mesh -from vertax.energy import energy_shape_factor_homo - -# Settings -n_cells = 100 -# Initial condition -L_box = jnp.sqrt(n_cells) -width = float(L_box) -height = float(L_box) -# Create a mesh with periodic boundary conditions -mesh = PbcMesh.periodic_voronoi_from_random_seeds(nb_seeds=n_cells, width=width, height=height, random_key=1) -# Parameters such as tensions, target areas, ... can be attached to vertices, edges, faces. -mesh.vertices_params = jnp.asarray([0.0]) -mesh.edges_params = jnp.asarray([0.0]) -mesh.faces_params = jnp.asarray([3.7]) - -def energy( - vertTable: Array, heTable: Array, faceTable: Array, _vert_params: Array, _he_params: Array, face_params: Array -) -> Array: - """We use an energy given in vertAX for this example. - - But only indirectly as the loss function for an inner optimization needs a specific function signature. - """ - return energy_shape_factor_homo(vertTable, heTable, faceTable, width, height, face_params) - -# Energy minimization -bilevel_optimizer = PbcBilevelOptimizer() -bilevel_optimizer.loss_function_inner = energy -bilevel_optimizer.inner_optimization(mesh) - -mesh.save_mesh("mesh.npz") -plot_mesh(mesh) +import optax + +from vertax import PbcBilevelOptimizer, PbcMesh, BilevelOptimizationMethod, plot_mesh +from vertax.cost import cost_v2v +from vertax.energy import energy_shape_factor_hetero + +# --- Mesh setup --- +n_cells = 20 +width = height = math.sqrt(n_cells) + +# New mesh with Periodic Boundary Conditions and 20 cells. +mesh = PbcMesh.from_random_seeds( + nb_seeds=n_cells, width=width, height=height, random_key=0 +) + +# --- Attach parameters --- +mesh.vertices_params = jnp.zeros(mesh.nb_vertices) +mesh.edges_params = jnp.zeros(mesh.nb_half_edges) # not used here +mesh.faces_params = jnp.full(mesh.nb_faces, 3.7) # initial target shape factors + +selected_faces = jnp.arange(mesh.nb_faces) + +# --- Built-in energy function --- +def energy(vertTable, heTable, faceTable, _vert_params, _he_params, face_params): + return energy_shape_factor_hetero( + vertTable, heTable, faceTable, + width, height, + selected_faces, + face_params, + ) + +# --- Optimizer setup --- +optimizer = PbcBilevelOptimizer() +optimizer.loss_function_inner = energy +optimizer.inner_solver = optax.sgd(learning_rate=0.01) +optimizer.update_T1 = True +optimizer.min_dist_T1 = 0.005 + +# --- Relax the initial mesh --- +optimizer.inner_optimization(mesh) + +# --- Create a target mesh with different face parameters --- +target = PbcMesh.copy_mesh(mesh) + +key = jax.random.PRNGKey(1) +target.faces_params = 3.7 + 0.2 * jax.random.normal(key, shape=(target.nb_faces,)) +target.vertices_params = jnp.zeros(target.nb_vertices) +target.edges_params = jnp.zeros(target.nb_half_edges) + +optimizer.inner_optimization(target) + +# --- Register the target --- +optimizer.vertices_target = target.vertices.copy() +optimizer.edges_target = target.edges.copy() +optimizer.faces_target = target.faces.copy() + +# --- Outer loss and bilevel method --- +optimizer.loss_function_outer = cost_v2v +optimizer.outer_solver = optax.adam(learning_rate=1e-4, nesterov=True) +optimizer.bilevel_optimization_method = BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION + +# --- Run bilevel optimization --- +for epoch in range(20): + optimizer.bilevel_optimization(mesh) + +plot_mesh(mesh, title="Recovered mesh after inverse modeling") ``` -## features +For full inverse-modeling examples, see [Tutorials](#tutorials) section. + +--- + +## Citing VertAX + +If you use _VertAX_ in your research, please cite: + +``` + +@misc{pasqui2026vertaxdifferentiablevertexmodel, +            title={VertAX: a differentiable vertex model for learning epithelial tissue mechanics}, +            author={Alessandro Pasqui and Jim Martin Catacora Ocana and Anshuman Sinha and Matthieu Perez and Fabrice Delbary and Giorgio Gosti and Mattia Miotto and Domenico Caudo and Maxence Ernoult and Hervé Turlier}, +            year={2026}, +            eprint={2604.06896}, +            archivePrefix={arXiv}, +            primaryClass={cs.LG}, +            url={, +} +``` -- Forward ? -- Inverse ? -- AD, ID, AS, EP... -- periodic boundary conditions from random Voronoi cells -- periodic boundary conditions from an image -- bounded boundaries from random seeds -- custom energy and cost -- plot function -- save / load meshes + -## tutorials + -See the [docs](docs) folder for more in-depht examples. +--- -## citing VertAX +## Funding -If you find `vertAX` useful please cite [this repository](https://github.com/vertAX/vertAX) using its DOI as follows: +This project received funding from the European Union’s Horizon 2020 research and innovation programme under the **European Research Council** (ERC) grant agreement no. **949267**, and under the **Marie Skłodowska-Curie** grant agreement no. **945304** — Cofund **AI4theSciences**, hosted by **PSL University**. AP, JMCO, AS, FB, MP, FD and HT acknowledge support from CNRS and Collège de France -> vertAX contributors (2019). vertAX: a multi-dimensional image viewer for python. [doi:10.5281/zenodo.3555620](https://zenodo.org/record/3555620) +--- -Note this DOI will resolve to all versions of vertAX. To cite a specific version please find the -DOI of that version on our [zenodo page](https://zenodo.org/record/3555620). The DOI of the latest version is in the badge at the top of this page. +## License -## institutional and funding partners +VertAX is distributed under the **Creative Commons Attribution–ShareAlike 4.0 International (CC BY-SA 4.0)** [`license`](LICENSE). - - - - CZI logo - - +You are free to share and adapt the material, provided that appropriate credit is given and that any derivative work is distributed under the same license. diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..8da3881 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/docs/search.js b/docs/search.js new file mode 100644 index 0000000..c296211 --- /dev/null +++ b/docs/search.js @@ -0,0 +1,46 @@ +window.pdocSearch = (function(){ +/** elasticlunr - http://weixsong.github.io * Copyright (C) 2017 Oliver Nightingale * Copyright (C) 2017 Wei Song * MIT Licensed */!function(){function e(e){if(null===e||"object"!=typeof e)return e;var t=e.constructor();for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.9.5",lunr=t,t.utils={},t.utils.warn=function(e){return function(t){e.console&&console.warn&&console.warn(t)}}(this),t.utils.toString=function(e){return void 0===e||null===e?"":e.toString()},t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var e=Array.prototype.slice.call(arguments),t=e.pop(),n=e;if("function"!=typeof t)throw new TypeError("last argument must be a function");n.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},t.EventEmitter.prototype.removeListener=function(e,t){if(this.hasHandler(e)){var n=this.events[e].indexOf(t);-1!==n&&(this.events[e].splice(n,1),0==this.events[e].length&&delete this.events[e])}},t.EventEmitter.prototype.emit=function(e){if(this.hasHandler(e)){var t=Array.prototype.slice.call(arguments,1);this.events[e].forEach(function(e){e.apply(void 0,t)},this)}},t.EventEmitter.prototype.hasHandler=function(e){return e in this.events},t.tokenizer=function(e){if(!arguments.length||null===e||void 0===e)return[];if(Array.isArray(e)){var n=e.filter(function(e){return null===e||void 0===e?!1:!0});n=n.map(function(e){return t.utils.toString(e).toLowerCase()});var i=[];return n.forEach(function(e){var n=e.split(t.tokenizer.seperator);i=i.concat(n)},this),i}return e.toString().trim().toLowerCase().split(t.tokenizer.seperator)},t.tokenizer.defaultSeperator=/[\s\-]+/,t.tokenizer.seperator=t.tokenizer.defaultSeperator,t.tokenizer.setSeperator=function(e){null!==e&&void 0!==e&&"object"==typeof e&&(t.tokenizer.seperator=e)},t.tokenizer.resetSeperator=function(){t.tokenizer.seperator=t.tokenizer.defaultSeperator},t.tokenizer.getSeperator=function(){return t.tokenizer.seperator},t.Pipeline=function(){this._queue=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in t.Pipeline.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[n]=e},t.Pipeline.getRegisteredFunction=function(e){return e in t.Pipeline.registeredFunctions!=!0?null:t.Pipeline.registeredFunctions[e]},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.getRegisteredFunction(e);if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i+1,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._queue.indexOf(e);if(-1===i)throw new Error("Cannot find existingFn");this._queue.splice(i,0,n)},t.Pipeline.prototype.remove=function(e){var t=this._queue.indexOf(e);-1!==t&&this._queue.splice(t,1)},t.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,i=this._queue.length,o=0;n>o;o++){for(var r=e[o],s=0;i>s&&(r=this._queue[s](r,o,e),void 0!==r&&null!==r);s++);void 0!==r&&null!==r&&t.push(r)}return t},t.Pipeline.prototype.reset=function(){this._queue=[]},t.Pipeline.prototype.get=function(){return this._queue},t.Pipeline.prototype.toJSON=function(){return this._queue.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.DocumentStore,this.index={},this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var e=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,e)},t.Index.prototype.off=function(e,t){return this.eventEmitter.removeListener(e,t)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;n._fields=e.fields,n._ref=e.ref,n.documentStore=t.DocumentStore.load(e.documentStore),n.pipeline=t.Pipeline.load(e.pipeline),n.index={};for(var i in e.index)n.index[i]=t.InvertedIndex.load(e.index[i]);return n},t.Index.prototype.addField=function(e){return this._fields.push(e),this.index[e]=new t.InvertedIndex,this},t.Index.prototype.setRef=function(e){return this._ref=e,this},t.Index.prototype.saveDocument=function(e){return this.documentStore=new t.DocumentStore(e),this},t.Index.prototype.addDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.addDoc(i,e),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));this.documentStore.addFieldLength(i,n,o.length);var r={};o.forEach(function(e){e in r?r[e]+=1:r[e]=1},this);for(var s in r){var u=r[s];u=Math.sqrt(u),this.index[n].addToken(s,{ref:i,tf:u})}},this),n&&this.eventEmitter.emit("add",e,this)}},t.Index.prototype.removeDocByRef=function(e){if(e&&this.documentStore.isDocStored()!==!1&&this.documentStore.hasDoc(e)){var t=this.documentStore.getDoc(e);this.removeDoc(t,!1)}},t.Index.prototype.removeDoc=function(e,n){if(e){var n=void 0===n?!0:n,i=e[this._ref];this.documentStore.hasDoc(i)&&(this.documentStore.removeDoc(i),this._fields.forEach(function(n){var o=this.pipeline.run(t.tokenizer(e[n]));o.forEach(function(e){this.index[n].removeToken(e,i)},this)},this),n&&this.eventEmitter.emit("remove",e,this))}},t.Index.prototype.updateDoc=function(e,t){var t=void 0===t?!0:t;this.removeDocByRef(e[this._ref],!1),this.addDoc(e,!1),t&&this.eventEmitter.emit("update",e,this)},t.Index.prototype.idf=function(e,t){var n="@"+t+"/"+e;if(Object.prototype.hasOwnProperty.call(this._idfCache,n))return this._idfCache[n];var i=this.index[t].getDocFreq(e),o=1+Math.log(this.documentStore.length/(i+1));return this._idfCache[n]=o,o},t.Index.prototype.getFields=function(){return this._fields.slice()},t.Index.prototype.search=function(e,n){if(!e)return[];e="string"==typeof e?{any:e}:JSON.parse(JSON.stringify(e));var i=null;null!=n&&(i=JSON.stringify(n));for(var o=new t.Configuration(i,this.getFields()).get(),r={},s=Object.keys(e),u=0;u0&&t.push(e);for(var i in n)"docs"!==i&&"df"!==i&&this.expandToken(e+i,t,n[i]);return t},t.InvertedIndex.prototype.toJSON=function(){return{root:this.root}},t.Configuration=function(e,n){var e=e||"";if(void 0==n||null==n)throw new Error("fields should not be null");this.config={};var i;try{i=JSON.parse(e),this.buildUserConfig(i,n)}catch(o){t.utils.warn("user configuration parse failed, will use default configuration"),this.buildDefaultConfig(n)}},t.Configuration.prototype.buildDefaultConfig=function(e){this.reset(),e.forEach(function(e){this.config[e]={boost:1,bool:"OR",expand:!1}},this)},t.Configuration.prototype.buildUserConfig=function(e,n){var i="OR",o=!1;if(this.reset(),"bool"in e&&(i=e.bool||i),"expand"in e&&(o=e.expand||o),"fields"in e)for(var r in e.fields)if(n.indexOf(r)>-1){var s=e.fields[r],u=o;void 0!=s.expand&&(u=s.expand),this.config[r]={boost:s.boost||0===s.boost?s.boost:1,bool:s.bool||i,expand:u}}else t.utils.warn("field name in user configuration not found in index instance fields");else this.addAllFields2UserConfig(i,o,n)},t.Configuration.prototype.addAllFields2UserConfig=function(e,t,n){n.forEach(function(n){this.config[n]={boost:1,bool:e,expand:t}},this)},t.Configuration.prototype.get=function(){return this.config},t.Configuration.prototype.reset=function(){this.config={}},lunr.SortedSet=function(){this.length=0,this.elements=[]},lunr.SortedSet.load=function(e){var t=new this;return t.elements=e,t.length=e.length,t},lunr.SortedSet.prototype.add=function(){var e,t;for(e=0;e1;){if(r===e)return o;e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o]}return r===e?o:-1},lunr.SortedSet.prototype.locationFor=function(e){for(var t=0,n=this.elements.length,i=n-t,o=t+Math.floor(i/2),r=this.elements[o];i>1;)e>r&&(t=o),r>e&&(n=o),i=n-t,o=t+Math.floor(i/2),r=this.elements[o];return r>e?o:e>r?o+1:void 0},lunr.SortedSet.prototype.intersect=function(e){for(var t=new lunr.SortedSet,n=0,i=0,o=this.length,r=e.length,s=this.elements,u=e.elements;;){if(n>o-1||i>r-1)break;s[n]!==u[i]?s[n]u[i]&&i++:(t.add(s[n]),n++,i++)}return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){var t,n,i;this.length>=e.length?(t=this,n=e):(t=e,n=this),i=t.clone();for(var o=0,r=n.toArray();oA differentiable JAX-based framework for vertex modeling and inverse design of epithelial tissues.

\n\n

Epithelial tissues dynamically reshape through local mechanical interactions among cells.\nUnderstanding, inferring, and designing these mechanics is a central challenge in developmental biology and biophysics.\nVertAX is a computational framework built to address this challenge.

\n\n

VertAX is a framework for vertex-based modeling:\n it represents epithelial tissues as two-dimensional polygonal meshes in which cells are faces, junctions are edges,\n tricellular contacts are vertices, and mechanical equilibrium is defined by the minimum of a user-specified energy.

\n\n

Built on JAX, VertAX is designed not only for forward simulation,\n but also for inverse problems such as parameter inference and tissue design.

\n\n

In VertAX, a Mesh represents a vertex model of an epithelial tissue. It is made of faces (cells), edges (interface between cells) and vertices (where 3 cells or more meet).

\n\n

More specifically, two types of meshes are currently supported:

\n\n
    \n
  • PbcMesh have Periodic Boundary Conditions, and are used for bulk tissue dynamics without explicit external boundaries.
  • \n
  • BoundedMesh are designed for finite tissue clusters, with curved free interfaces.
  • \n
\n\n

The other first class objects of VertAX are called Bilevel Optimizers. They allow to formulate inverse problems as nested optimizations:

\n\n

$$\n\\begin{aligned}\n\\textbf{Outer problem (learning):} \\quad\n\\theta^{\\ast} &= \\arg\\min_{\\theta} \\mathcal{C}\\left(X^{\\ast}_{\\theta},\\theta\\right)\n&& \\leftarrow \\text{fit data or reach a target} \\\n\\end{aligned}\n$$\n$$\n\\begin{aligned}\n\\textbf{Inner problem (physics):} \\quad \\text{s.t.}&\nX^{\\ast}_{\\theta} \\in \\arg\\min_{X} \\mathcal{E}(X,\\theta)\n&& \\leftarrow \\text{compute mechanical equilibrium}\n\\end{aligned}\n$$

\n\n

Here, $X$ denotes the tissue configuration, i.e. the vertex positions of the mesh, and $\\theta$ denotes the model parameters, such as line tensions, target areas, or shape factors.

\n\n

In other words, VertAX repeatedly solves a mechanical equilibrium problem for a given parameter set $\\theta$, then updates those parameters to better match data or a design objective.

\n\n

In symmetry with meshes, a base abstract class _BilevelOptimizer defines common hyper-parameters and methods for the bilevel optimization, but you need to use the specialized classes:

\n\n
    \n
  • PbcBilevelOptimizer for PbcMesh,
  • \n
  • BoundedBilevelOptimizer for BoundedMesh.
  • \n
\n\n

VertAX comes with pre-defined energy and cost functions but you can easily define your own functions. See the examples folder in the repository to have a typical example on how to use VertAX.

\n\n

Finally, there are plot functions to easily see the results of your experiments. See plot_mesh for example.

\n\n

Users can define their own energy functions for the inner optimization and cost function for the outer optimization, however we provide some basic ones.\nIf you use your own make sure to use the exact same signature as we do for these functions, otherwise it won't work. See the cost and energy functions we provide in this documentation.

\n"}, "vertax.Mesh": {"fullname": "vertax.Mesh", "modulename": "vertax", "qualname": "Mesh", "kind": "class", "doc": "

Generic mesh structure. It is an abstract base class, not to be used directly.

\n\n

It defines common attributes and functions between PbcMesh and BoundedMesh.

\n"}, "vertax.Mesh.__init__": {"fullname": "vertax.Mesh.__init__", "modulename": "vertax", "qualname": "Mesh.__init__", "kind": "function", "doc": "

Do nothing but create attributes. Do not call this, but call specialized class methods to create meshes.

\n\n

See PbcMesh and BoundedMesh.

\n\n

Technically, it uses a DCEL structure.

\n", "signature": "()"}, "vertax.Mesh.faces": {"fullname": "vertax.Mesh.faces", "modulename": "vertax", "qualname": "Mesh.faces", "kind": "variable", "doc": "

The cells of the tissue.

\n", "annotation": ": jax.Array"}, "vertax.Mesh.edges": {"fullname": "vertax.Mesh.edges", "modulename": "vertax", "qualname": "Mesh.edges", "kind": "variable", "doc": "

The interface between cells. Technically half-edges.

\n", "annotation": ": jax.Array"}, "vertax.Mesh.vertices": {"fullname": "vertax.Mesh.vertices", "modulename": "vertax", "qualname": "Mesh.vertices", "kind": "variable", "doc": "

The mesh vertices, where cells meet.

\n", "annotation": ": jax.Array"}, "vertax.Mesh.width": {"fullname": "vertax.Mesh.width", "modulename": "vertax", "qualname": "Mesh.width", "kind": "variable", "doc": "

The mesh live in a rectangle of size [0, width] in the X direction.

\n", "annotation": ": float"}, "vertax.Mesh.height": {"fullname": "vertax.Mesh.height", "modulename": "vertax", "qualname": "Mesh.height", "kind": "variable", "doc": "

The mesh live in a rectangle of size [0, height] in the Y direction.

\n", "annotation": ": float"}, "vertax.Mesh.faces_params": {"fullname": "vertax.Mesh.faces_params", "modulename": "vertax", "qualname": "Mesh.faces_params", "kind": "variable", "doc": "

Parameters attached to faces. Can be optimized.

\n", "annotation": ": jax.Array"}, "vertax.Mesh.edges_params": {"fullname": "vertax.Mesh.edges_params", "modulename": "vertax", "qualname": "Mesh.edges_params", "kind": "variable", "doc": "

Parameters attached to edges. Can be optimized.

\n", "annotation": ": jax.Array"}, "vertax.Mesh.vertices_params": {"fullname": "vertax.Mesh.vertices_params", "modulename": "vertax", "qualname": "Mesh.vertices_params", "kind": "variable", "doc": "

Parameters attached to vertices. Can be optimized.

\n", "annotation": ": jax.Array"}, "vertax.Mesh.nb_faces": {"fullname": "vertax.Mesh.nb_faces", "modulename": "vertax", "qualname": "Mesh.nb_faces", "kind": "variable", "doc": "

Get the number of faces of the mesh.

\n", "annotation": ": int"}, "vertax.Mesh.nb_edges": {"fullname": "vertax.Mesh.nb_edges", "modulename": "vertax", "qualname": "Mesh.nb_edges", "kind": "variable", "doc": "

Get the number of edges of the mesh.

\n", "annotation": ": int"}, "vertax.Mesh.nb_half_edges": {"fullname": "vertax.Mesh.nb_half_edges", "modulename": "vertax", "qualname": "Mesh.nb_half_edges", "kind": "variable", "doc": "

Get the number of half-edges of the mesh, ie. twice the number of edges.

\n", "annotation": ": int"}, "vertax.Mesh.nb_vertices": {"fullname": "vertax.Mesh.nb_vertices", "modulename": "vertax", "qualname": "Mesh.nb_vertices", "kind": "variable", "doc": "

Get the number of vertices of the mesh.

\n", "annotation": ": int"}, "vertax.Mesh.save_mesh": {"fullname": "vertax.Mesh.save_mesh", "modulename": "vertax", "qualname": "Mesh.save_mesh", "kind": "function", "doc": "

Save mesh to a file.

\n\n

All mesh data is saved.

\n\n
Arguments:
\n\n
    \n
  • path (str): Path to the saved file. The extension is .npz.
  • \n
\n", "signature": "(self, path: str) -> None:", "funcdef": "def"}, "vertax.Mesh.load_mesh": {"fullname": "vertax.Mesh.load_mesh", "modulename": "vertax", "qualname": "Mesh.load_mesh", "kind": "function", "doc": "

Load a mesh from a file.

\n\n
Arguments:
\n\n
    \n
  • path (str): Path to the mesh file (.npz).
  • \n
\n\n
Returns:
\n\n
\n

The mesh loaded from the .npz file.

\n
\n", "signature": "(cls, path: str) -> Self:", "funcdef": "def"}, "vertax.PbcMesh": {"fullname": "vertax.PbcMesh", "modulename": "vertax", "qualname": "PbcMesh", "kind": "class", "doc": "

Periodic Boundary Condition on a mesh.

\n\n

For a PbcMesh, vertices is a 2D array of floats of size (nb_vertices, 2) ;\nwith the coordinates of the vertices (in ]0, width[ x ]0, height[ ).

\n\n

edges is a 2D array of integers of size (nb_half_edges, 8), with:

\n\n
    \n
  • id of previous half-edge,
  • \n
  • id of next half-edge,
  • \n
  • id of twin half-edge,
  • \n
  • id of source vertex,
  • \n
  • id of target vertex,
  • \n
  • id of the face containing the half-edge.
  • \n
  • x-offset (either -1, 0 or 1) depending on the target vertex crossing a boundary.
  • \n
  • y-offset (either -1, 0 or 1) depending on the target vertex crossing a boundary.
  • \n
\n\n

faces is a 1D array of integers of size (nb_faces) containing the id of a half-edge belonging to this face.

\n", "bases": "vertax.meshes.mesh.Mesh"}, "vertax.PbcMesh.__init__": {"fullname": "vertax.PbcMesh.__init__", "modulename": "vertax", "qualname": "PbcMesh.__init__", "kind": "function", "doc": "

Do not call the constructor directly, use the dedicated class methods such as:

\n\n
    \n
  • from_random_seeds,
  • \n
  • from_seeds,
  • \n
  • from_image,
  • \n
  • from_mask,
  • \n
  • create_empty.
  • \n
\n", "signature": "()"}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"fullname": "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE", "modulename": "vertax", "qualname": "PbcMesh.MAX_EDGES_IN_ANY_FACE", "kind": "variable", "doc": "

Optimization parameter : must be more than the estimated maximum number of edges in a face. Base value is 20.

\n", "annotation": ": int"}, "vertax.PbcMesh.from_random_seeds": {"fullname": "vertax.PbcMesh.from_random_seeds", "modulename": "vertax", "qualname": "PbcMesh.from_random_seeds", "kind": "function", "doc": "

Create a Periodic Boundary Conditions Mesh from a Voronoi diagram based on random seeds.

\n\n
Arguments:
\n\n
    \n
  • nb_seeds (int): Number of random seeds to use.
  • \n
  • width (float): Width of the rectangular domains the seeds will be in.
  • \n
  • height (float): Height of the rectangular domains the seeds will be in.
  • \n
  • random_key (int): Set the random key for reproducibility.
  • \n
\n\n
Returns:
\n\n
\n

Self: The corresponding mesh.

\n
\n", "signature": "(cls, nb_seeds: int, width: float, height: float, random_key: int) -> Self:", "funcdef": "def"}, "vertax.PbcMesh.from_seeds": {"fullname": "vertax.PbcMesh.from_seeds", "modulename": "vertax", "qualname": "PbcMesh.from_seeds", "kind": "function", "doc": "

Create a Periodic Boundary Conditions Mesh from a Voronoi diagram based on a list of seeds.

\n\n

The seeds are assumed to have positive x and y positions.

\n\n
Arguments:
\n\n
    \n
  • seeds (Array[float32]): jax float array of seed positions of shape (nbSeeds, 2).
  • \n
  • width (float): width of the box containing the seeds.
  • \n
  • height (float): height of the box containing the seeds.
  • \n
\n", "signature": "(cls, seeds: jax.Array, width: float, height: float) -> Self:", "funcdef": "def"}, "vertax.PbcMesh.from_image": {"fullname": "vertax.PbcMesh.from_image", "modulename": "vertax", "qualname": "PbcMesh.from_image", "kind": "function", "doc": "

Create a rudimentary mesh with periodic boundary conditions from an image.

\n\n

To do that, we perform a segmentation using Cellpose and we try to fill the holes.\nThe result will probably be imperfect and it will always be better if you\nprovide directly a mask (with no holes) with the function \"periodic_from_mask\".

\n\n

The mask is made periodic by mirroring its edges.

\n\n
Arguments:
\n\n
    \n
  • image (NDArray): The image which will act as a template for the mesh.
  • \n
\n\n
Returns:
\n\n
\n

Self: The corresponding mesh.

\n
\n", "signature": "(\tcls,\timage: numpy.ndarray[typing.Any, numpy.dtype[+_ScalarType_co]]) -> Self:", "funcdef": "def"}, "vertax.PbcMesh.from_mask": {"fullname": "vertax.PbcMesh.from_mask", "modulename": "vertax", "qualname": "PbcMesh.from_mask", "kind": "function", "doc": "

Create a rudimentary mesh with periodic boundary conditions from a mask with no holes.

\n\n

The mask is made periodic by mirroring its edges.

\n\n
Arguments:
\n\n
    \n
  • mask (NDArray): The mask with no holes which will act as a template for the mesh.
  • \n
\n\n
Returns:
\n\n
\n

Self: The corresponding mesh.

\n
\n", "signature": "(\tcls,\tmask: numpy.ndarray[typing.Any, numpy.dtype[+_ScalarType_co]]) -> Self:", "funcdef": "def"}, "vertax.PbcMesh.create_empty": {"fullname": "vertax.PbcMesh.create_empty", "modulename": "vertax", "qualname": "PbcMesh.create_empty", "kind": "function", "doc": "

Create an empty mesh. Use if you know what you're doing !

\n", "signature": "(cls) -> Self:", "funcdef": "def"}, "vertax.PbcMesh.copy_mesh": {"fullname": "vertax.PbcMesh.copy_mesh", "modulename": "vertax", "qualname": "PbcMesh.copy_mesh", "kind": "function", "doc": "

Copy all parameters from another mesh in a new mesh.

\n", "signature": "(cls, other_mesh: Self) -> Self:", "funcdef": "def"}, "vertax.PbcMesh.save_mesh": {"fullname": "vertax.PbcMesh.save_mesh", "modulename": "vertax", "qualname": "PbcMesh.save_mesh", "kind": "function", "doc": "

Save mesh to a file.

\n\n

All PBCMesh data is saved.

\n\n
Arguments:
\n\n
    \n
  • path (str): Path to the saved file. The extension is .npz, a numpy format.
  • \n
\n", "signature": "(self, path: str) -> None:", "funcdef": "def"}, "vertax.PbcMesh.load_mesh": {"fullname": "vertax.PbcMesh.load_mesh", "modulename": "vertax", "qualname": "PbcMesh.load_mesh", "kind": "function", "doc": "

Load a mesh from a file.

\n\n

All PBCMesh data is reloaded.

\n\n
Arguments:
\n\n
    \n
  • path (str): Path to the mesh file (.npz), numpy format.
  • \n
\n\n
Returns:
\n\n
\n

Mesh: the mesh loaded from the numpy .npz file.

\n
\n", "signature": "(cls, path: str) -> Self:", "funcdef": "def"}, "vertax.PbcMesh.save_mesh_txt": {"fullname": "vertax.PbcMesh.save_mesh_txt", "modulename": "vertax", "qualname": "PbcMesh.save_mesh_txt", "kind": "function", "doc": "

Save a mesh in separate text files that can be read by numpy.

\n\n

Only save the vertices, edges and faces, not other parameters.

\n\n
Arguments:
\n\n
    \n
  • directory (str): Path to the directory where to save the files.
  • \n
  • vertices_filename (str, optional): Filename for the vertices table. Defaults to \"vertTable.txt\".
  • \n
  • edges_filename (str, optional): Filename for the half-edges table. Defaults to \"heTable.txt\".
  • \n
  • faces_filename (str, optional): Filename for the faces table. Defaults to \"faceTable.txt\".
  • \n
  • vertices_params_filename (str, optional): Filename for the vertices parameters table.\nDefaults to \"vertParamsTable.txt\".
  • \n
  • edges_params_filename (str, optional): Filename for the half-edges parameters table.\nDefaults to \"heParamsTable.txt\".
  • \n
  • faces_params_filename (str, optional): Filename for the faces parameters table.\nDefaults to \"faceParamsTable.txt\".
  • \n
  • constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE.\nDefaults to \"constants.txt\".
  • \n
\n", "signature": "(\tself,\tdirectory: str,\tvertices_filename: str = 'vertTable.txt',\tedges_filename: str = 'heTable.txt',\tfaces_filename: str = 'faceTable.txt',\tvertices_params_filename: str = 'vertParamsTable.txt',\tedges_params_filename: str = 'heParamsTable.txt',\tfaces_params_filename: str = 'faceParamsTable.txt',\tconstants_filename: str = 'constants.txt') -> None:", "funcdef": "def"}, "vertax.PbcMesh.load_mesh_txt": {"fullname": "vertax.PbcMesh.load_mesh_txt", "modulename": "vertax", "qualname": "PbcMesh.load_mesh_txt", "kind": "function", "doc": "

Load a mesh from text files.

\n\n

Only load the vertices, edges and faces, not other parameters.

\n\n
Arguments:
\n\n
    \n
  • directory (str): Directory where the text files are stored.
  • \n
  • vertices_filename (str, optional): Filename for the vertices table. Defaults to \"vertTable.txt\".
  • \n
  • edges_filename (str, optional): Filename for the half-edges table. Defaults to \"heTable.txt\".
  • \n
  • faces_filename (str, optional): Filename for the faces table. Defaults to \"faceTable.txt\".
  • \n
  • vertices_params_filename (str, optional): Filename for the vertices parameters table.\nDefaults to \"vertParamsTable.txt\".
  • \n
  • edges_params_filename (str, optional): Filename for the half-edges parameters table.\nDefaults to \"heParamsTable.txt\".
  • \n
  • faces_params_filename (str, optional): Filename for the faces parameters table.\nDefaults to \"faceParamsTable.txt\".
  • \n
  • constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE.\nDefaults to \"constants.txt\".
  • \n
\n\n
Returns:
\n\n
\n

Self: The loaded mesh.

\n
\n", "signature": "(\tcls,\tdirectory: str,\tvertices_filename: str = 'vertTable.txt',\tedges_filename: str = 'heTable.txt',\tfaces_filename: str = 'faceTable.txt',\tvertices_params_filename: str = 'vertParamsTable.txt',\tedges_params_filename: str = 'heParamsTable.txt',\tfaces_params_filename: str = 'faceParamsTable.txt',\tconstants_filename: str = 'constants.txt') -> Self:", "funcdef": "def"}, "vertax.PbcMesh.get_length": {"fullname": "vertax.PbcMesh.get_length", "modulename": "vertax", "qualname": "PbcMesh.get_length", "kind": "function", "doc": "

Get the length of given edges.

\n", "signature": "(self, half_edge_id: jax.Array) -> jax.Array:", "funcdef": "def"}, "vertax.PbcMesh.get_length_with_offset": {"fullname": "vertax.PbcMesh.get_length_with_offset", "modulename": "vertax", "qualname": "PbcMesh.get_length_with_offset", "kind": "function", "doc": "

Get the length of given edges along with its offsets in an array (length, offset x, offset y).

\n", "signature": "(self, half_edge_id: jax.Array) -> jax.Array:", "funcdef": "def"}, "vertax.PbcMesh.get_perimeter": {"fullname": "vertax.PbcMesh.get_perimeter", "modulename": "vertax", "qualname": "PbcMesh.get_perimeter", "kind": "function", "doc": "

Get the perimeter of given faces.

\n", "signature": "(self, face_id: jax.Array) -> jax.Array:", "funcdef": "def"}, "vertax.PbcMesh.get_area": {"fullname": "vertax.PbcMesh.get_area", "modulename": "vertax", "qualname": "PbcMesh.get_area", "kind": "function", "doc": "

Get the area of given faces.

\n", "signature": "(self, face_id: jax.Array) -> jax.Array:", "funcdef": "def"}, "vertax.PbcMesh.update_boundary_conditions": {"fullname": "vertax.PbcMesh.update_boundary_conditions", "modulename": "vertax", "qualname": "PbcMesh.update_boundary_conditions", "kind": "function", "doc": "

Force periodic boundary conditions again after an update.

\n\n

Most likely you'll never have to use it yourself, as it can be made automatically with a PbcBilevelOptimizer.

\n", "signature": "(self) -> None:", "funcdef": "def"}, "vertax.BoundedMesh": {"fullname": "vertax.BoundedMesh", "modulename": "vertax", "qualname": "BoundedMesh", "kind": "class", "doc": "

Bounded mesh with arc circles for boundary cells.

\n\n

For a BoundedMesh, vertices is a 2D array of floats of size (nb_vertices, 2) ;\nwith the coordinates of the vertices (in ]0, width[ x ]0, height[ ).

\n\n

edges is a 2D array of integers of size (nb_half_edges, 8), with:

\n\n
    \n
  • id of previous half-edge,
  • \n
  • id of next half-edge,
  • \n
  • id of twin half-edge,
  • \n
  • id of source vertex + 2 if current half-edge is an inside edge, else 0,
  • \n
  • id of target vertex + 2 if current half-edge is an inside edge, else 1,
  • \n
  • id of source vertex + 2 if current half-edge is an outside edge, else 0,
  • \n
  • id of target vertex + 2 if current half-edge is an outside edge, else 1,
  • \n
  • id of the face containing the half-edge.
  • \n
\n\n

faces is a 1D array of integers of size (nb_faces) containing the id of a half-edge belonging to this face.

\n\n

angles is a 1D array of floats of size (nb_angles), with the angles sustaining the arcs of the free interfaces ;\nbetween 0 and PI / 2.

\n", "bases": "vertax.meshes.mesh.Mesh"}, "vertax.BoundedMesh.__init__": {"fullname": "vertax.BoundedMesh.__init__", "modulename": "vertax", "qualname": "BoundedMesh.__init__", "kind": "function", "doc": "

Do not call the constructor.

\n", "signature": "()"}, "vertax.BoundedMesh.angles": {"fullname": "vertax.BoundedMesh.angles", "modulename": "vertax", "qualname": "BoundedMesh.angles", "kind": "variable", "doc": "

Angle sustaining the arced free interfaces. Between 0 and PI / 2.

\n", "annotation": ": jax.Array"}, "vertax.BoundedMesh.nb_angles": {"fullname": "vertax.BoundedMesh.nb_angles", "modulename": "vertax", "qualname": "BoundedMesh.nb_angles", "kind": "variable", "doc": "

Get the number of angles (free interfaces) of the mesh.

\n", "annotation": ": int"}, "vertax.BoundedMesh.from_random_seeds": {"fullname": "vertax.BoundedMesh.from_random_seeds", "modulename": "vertax", "qualname": "BoundedMesh.from_random_seeds", "kind": "function", "doc": "

Create a bounded Mesh from random seeds, based on a Voronoi diagram with arced free interfaces.

\n\n
Arguments:
\n\n
    \n
  • nb_seeds (int): Number of random seeds to use.
  • \n
  • width (float): width of the box containing the seeds.
  • \n
  • height (float): height of the box containing the seeds.
  • \n
  • random_key (int): seed for a random number generator to add new seeds if needed, decide on cell fates...
  • \n
  • nb_fates (int, default=2): number of possible different fate marker for a cell.
  • \n
\n\n
Returns:
\n\n
\n

Self: The corresponding mesh.

\n
\n", "signature": "(\tcls,\tnb_seeds: int,\twidth: float,\theight: float,\trandom_key: int,\tnb_fates: int = 2) -> Self:", "funcdef": "def"}, "vertax.BoundedMesh.from_seeds": {"fullname": "vertax.BoundedMesh.from_seeds", "modulename": "vertax", "qualname": "BoundedMesh.from_seeds", "kind": "function", "doc": "

Create a bounded Mesh from a list of given seeds.

\n\n

The seeds are assumed to have x-coordinate in ]0, width[ and y-coordinate in ]0, height[.\nNote that the final mesh might not use your seeds if they don't work to create a correct\nbounded mesh via our method.

\n\n
Arguments:
\n\n
    \n
  • seeds (Array[float32]): jax float array of seed positions of shape (nbSeeds, 2).
  • \n
  • width (float): width of the box containing the seeds.
  • \n
  • height (float): height of the box containing the seeds.
  • \n
  • random_key (int): seed for a random number generator to add new seeds if needed, decide on cell fates...
  • \n
  • nb_fates (int, default=2): number of possible different fate marker for a cell.
  • \n
\n", "signature": "(\tcls,\tseeds: numpy.ndarray[typing.Any, numpy.dtype[+_ScalarType_co]],\twidth: float,\theight: float,\trandom_key: int,\tnb_fates: int = 2) -> Self:", "funcdef": "def"}, "vertax.BoundedMesh.create_empty": {"fullname": "vertax.BoundedMesh.create_empty", "modulename": "vertax", "qualname": "BoundedMesh.create_empty", "kind": "function", "doc": "

Create an empty mesh. Use if you know what you're doing !

\n", "signature": "(cls) -> Self:", "funcdef": "def"}, "vertax.BoundedMesh.copy_mesh": {"fullname": "vertax.BoundedMesh.copy_mesh", "modulename": "vertax", "qualname": "BoundedMesh.copy_mesh", "kind": "function", "doc": "

Copy all parameters from another mesh in a new mesh.

\n", "signature": "(cls, other_mesh: Self) -> Self:", "funcdef": "def"}, "vertax.BoundedMesh.save_mesh": {"fullname": "vertax.BoundedMesh.save_mesh", "modulename": "vertax", "qualname": "BoundedMesh.save_mesh", "kind": "function", "doc": "

Save mesh to a file.

\n\n

All BoundedMesh data is saved.

\n\n
Arguments:
\n\n
    \n
  • path (str): Path to the saved file. The extension is .npz.
  • \n
\n", "signature": "(self, path: str) -> None:", "funcdef": "def"}, "vertax.BoundedMesh.load_mesh": {"fullname": "vertax.BoundedMesh.load_mesh", "modulename": "vertax", "qualname": "BoundedMesh.load_mesh", "kind": "function", "doc": "

Load a mesh from a file.

\n\n

All BoundedMesh data is reloaded.

\n\n
Arguments:
\n\n
    \n
  • path (str): Path to the mesh file (.npz).
  • \n
\n\n
Returns:
\n\n
\n

Mesh: the mesh loaded from the .npz file.

\n
\n", "signature": "(cls, path: str) -> Self:", "funcdef": "def"}, "vertax.BoundedMesh.save_mesh_txt": {"fullname": "vertax.BoundedMesh.save_mesh_txt", "modulename": "vertax", "qualname": "BoundedMesh.save_mesh_txt", "kind": "function", "doc": "

Save a mesh in separate text files that can be read by numpy.

\n\n

Only save the vertices, angles, edges and faces, not other parameters.

\n\n
Arguments:
\n\n
    \n
  • directory (str): Path to the directory where to save the files.
  • \n
  • vertices_filename (str, optional): Filename for the vertices table. Defaults to \"vertTable.txt\".
  • \n
  • angles_filename (str, optional): Filename for the angles table. Defaults to \"angTable.txt\".
  • \n
  • edges_filename (str, optional): Filename for the half-edges table. Defaults to \"heTable.txt\".
  • \n
  • faces_filename (str, optional): Filename for the faces table. Defaults to \"faceTable.txt\".
  • \n
  • vertices_params_filename (str, optional): Filename for the vertices parameters table.\nDefaults to \"vertParamsTable.txt\".
  • \n
  • edges_params_filename (str, optional): Filename for the half-edges parameters table.\nDefaults to \"heParamsTable.txt\".
  • \n
  • faces_params_filename (str, optional): Filename for the faces parameters table.\nDefaults to \"faceParamsTable.txt\".
  • \n
  • constants_filename (str, optional): Filename for width/height.\nDefaults to \"constants.txt\".
  • \n
\n", "signature": "(\tself,\tdirectory: str,\tvertices_filename: str = 'vertTable.txt',\tangles_filename: str = 'angTable.txt',\tedges_filename: str = 'heTable.txt',\tfaces_filename: str = 'faceTable.txt',\tvertices_params_filename: str = 'vertParamsTable.txt',\tedges_params_filename: str = 'heParamsTable.txt',\tfaces_params_filename: str = 'faceParamsTable.txt',\tconstants_filename: str = 'constants.txt') -> None:", "funcdef": "def"}, "vertax.BoundedMesh.load_mesh_txt": {"fullname": "vertax.BoundedMesh.load_mesh_txt", "modulename": "vertax", "qualname": "BoundedMesh.load_mesh_txt", "kind": "function", "doc": "

Load a mesh from text files.

\n\n

Only load the vertices, angles, edges and faces, not other parameters.

\n\n
Arguments:
\n\n
    \n
  • directory (str): Directory where the text files are stored.
  • \n
  • vertices_filename (str, optional): Filename for the vertices table. Defaults to \"vertTable.txt\".
  • \n
  • angles_filename (str, optional): Filename for the angles table. Defaults to \"angTable.txt\".
  • \n
  • edges_filename (str, optional): Filename for the half-edges table. Defaults to \"heTable.txt\".
  • \n
  • faces_filename (str, optional): Filename for the faces table. Defaults to \"faceTable.txt\".
  • \n
  • vertices_params_filename (str, optional): Filename for the vertices parameters table.\nDefaults to \"vertParamsTable.txt\".
  • \n
  • edges_params_filename (str, optional): Filename for the half-edges parameters table.\nDefaults to \"heParamsTable.txt\".
  • \n
  • faces_params_filename (str, optional): Filename for the faces parameters table.\nDefaults to \"faceParamsTable.txt\".
  • \n
  • constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE.\nDefaults to \"constants.txt\".
  • \n
\n\n
Returns:
\n\n
\n

Self: The loaded mesh.

\n
\n", "signature": "(\tcls,\tdirectory: str,\tvertices_filename: str = 'vertTable.txt',\tangles_filename: str = 'angTable.txt',\tedges_filename: str = 'heTable.txt',\tfaces_filename: str = 'faceTable.txt',\tvertices_params_filename: str = 'vertParamsTable.txt',\tedges_params_filename: str = 'heParamsTable.txt',\tfaces_params_filename: str = 'faceParamsTable.txt',\tconstants_filename: str = 'constants.txt') -> Self:", "funcdef": "def"}, "vertax.BoundedMesh.get_length": {"fullname": "vertax.BoundedMesh.get_length", "modulename": "vertax", "qualname": "BoundedMesh.get_length", "kind": "function", "doc": "

Get the length of an edge.

\n", "signature": "(self, half_edge_id: jax.Array) -> jax.Array:", "funcdef": "def"}, "vertax.BoundedMesh.get_perimeter": {"fullname": "vertax.BoundedMesh.get_perimeter", "modulename": "vertax", "qualname": "BoundedMesh.get_perimeter", "kind": "function", "doc": "

Get the area of a face.

\n", "signature": "(self, face_id: jax.Array) -> jax.Array:", "funcdef": "def"}, "vertax.BoundedMesh.get_area": {"fullname": "vertax.BoundedMesh.get_area", "modulename": "vertax", "qualname": "BoundedMesh.get_area", "kind": "function", "doc": "

Get the area of a face.

\n", "signature": "(self, face_id: jax.Array) -> jax.Array:", "funcdef": "def"}, "vertax.BilevelOptimizationMethod": {"fullname": "vertax.BilevelOptimizationMethod", "modulename": "vertax", "qualname": "BilevelOptimizationMethod", "kind": "class", "doc": "

Which optimization method to use in the bi-level optimization.

\n", "bases": "enum.Enum"}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"fullname": "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION", "modulename": "vertax", "qualname": "BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION", "kind": "variable", "doc": "

Unrolls the inner optimization steps ; forward-mode JVP via jax.jacfwd,\ncost scales with the number of parameters and iterations.

\n", "default_value": "<BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION: 'ad'>"}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"fullname": "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION", "modulename": "vertax", "qualname": "BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION", "kind": "variable", "doc": "

Estimates the gradient from perturbed free vs nudged equilibria ; no backdrop required.\nMost efficient but depends on the perturbation size \u03b2.

\n", "default_value": "<BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION: 'ep'>"}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"fullname": "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION", "modulename": "vertax", "qualname": "BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION", "kind": "variable", "doc": "

Differentiates the optimality condition \u2207\u2093E=0 via Implicit Function Theorem ; JVP variant ;\nrequires Hessian solve and sensitive to ill-conditioning.

\n", "default_value": "<BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION: 'id'>"}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"fullname": "vertax.BilevelOptimizationMethod.ADJOINT_STATE", "modulename": "vertax", "qualname": "BilevelOptimizationMethod.ADJOINT_STATE", "kind": "variable", "doc": "

Differentiates the optimality condition \u2207\u2093E=0 via Implicit Function Theorem ; VJP variant ;\nrequires Hessian solve or sensitive to ill-conditioning.

\n", "default_value": "<BilevelOptimizationMethod.ADJOINT_STATE: 'as'>"}, "vertax.PbcBilevelOptimizer": {"fullname": "vertax.PbcBilevelOptimizer", "modulename": "vertax", "qualname": "PbcBilevelOptimizer", "kind": "class", "doc": "

Bi-level optimizer for periodic boundary condition meshes (PbcMesh).

\n", "bases": "vertax.bilevelopt.bilevelopt._BilevelOptimizer"}, "vertax.PbcBilevelOptimizer.__init__": {"fullname": "vertax.PbcBilevelOptimizer.__init__", "modulename": "vertax", "qualname": "PbcBilevelOptimizer.__init__", "kind": "function", "doc": "

Create a Bi-level optimizer for periodic boundary condition meshes with default parameters.

\n\n

Does not specify the inner and outer loss yet.

\n", "signature": "()"}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"fullname": "vertax.PbcBilevelOptimizer.compute_outer_loss", "modulename": "vertax", "qualname": "PbcBilevelOptimizer.compute_outer_loss", "kind": "function", "doc": "

Get the result of self.loss_function_outer called with the correct arguments.

\n", "signature": "(\tself,\tmesh: vertax.meshes.mesh.Mesh,\tonly_on_vertices: None | list[int] = None,\tonly_on_edges: None | list[int] = None,\tonly_on_faces: None | list[int] = None) -> float:", "funcdef": "def"}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"fullname": "vertax.PbcBilevelOptimizer.compute_inner_loss", "modulename": "vertax", "qualname": "PbcBilevelOptimizer.compute_inner_loss", "kind": "function", "doc": "

Get the result of self.loss_function_inner called with the correct arguments.

\n", "signature": "(\tself,\tmesh: vertax.meshes.mesh.Mesh,\tonly_on_vertices: None | list[int] = None,\tonly_on_edges: None | list[int] = None,\tonly_on_faces: None | list[int] = None) -> float:", "funcdef": "def"}, "vertax.BoundedBilevelOptimizer": {"fullname": "vertax.BoundedBilevelOptimizer", "modulename": "vertax", "qualname": "BoundedBilevelOptimizer", "kind": "class", "doc": "

Bi-level optimizer for bounded meshes (BoundedMesh).

\n", "bases": "vertax.bilevelopt.bilevelopt._BilevelOptimizer"}, "vertax.BoundedBilevelOptimizer.__init__": {"fullname": "vertax.BoundedBilevelOptimizer.__init__", "modulename": "vertax", "qualname": "BoundedBilevelOptimizer.__init__", "kind": "function", "doc": "

Create a Bi-level optimizer for bounded meshes with default parameters.

\n\n

Does not set the inner and outer loss functions yet.

\n", "signature": "()"}, "vertax.BoundedBilevelOptimizer.angles_target": {"fullname": "vertax.BoundedBilevelOptimizer.angles_target", "modulename": "vertax", "qualname": "BoundedBilevelOptimizer.angles_target", "kind": "variable", "doc": "

\n"}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"fullname": "vertax.BoundedBilevelOptimizer.compute_outer_loss", "modulename": "vertax", "qualname": "BoundedBilevelOptimizer.compute_outer_loss", "kind": "function", "doc": "

Get the result of self.loss_function_outer called with the correct arguments.

\n\n

Must be implemented by child classes.

\n", "signature": "(\tself,\tmesh: vertax.meshes.mesh.Mesh,\tonly_on_vertices: None | list[int] = None,\tonly_on_edges: None | list[int] = None,\tonly_on_faces: None | list[int] = None) -> float:", "funcdef": "def"}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"fullname": "vertax.BoundedBilevelOptimizer.compute_inner_loss", "modulename": "vertax", "qualname": "BoundedBilevelOptimizer.compute_inner_loss", "kind": "function", "doc": "

Get the result of self.loss_function_inner called with the correct arguments.

\n\n

Must be implemented by child classes.

\n", "signature": "(\tself,\tmesh: vertax.meshes.mesh.Mesh,\tonly_on_vertices: None | list[int] = None,\tonly_on_edges: None | list[int] = None,\tonly_on_faces: None | list[int] = None) -> float:", "funcdef": "def"}, "vertax.plot_mesh": {"fullname": "vertax.plot_mesh", "modulename": "vertax", "qualname": "plot_mesh", "kind": "function", "doc": "

Plot the mesh and decide to save and/or show the mesh or not.

\n", "signature": "(\tmesh: vertax.meshes.mesh.Mesh,\tvertex_plot: vertax.meshes.plot.VertexPlot = <VertexPlot.INVISIBLE: 3>,\tedge_plot: vertax.meshes.plot.EdgePlot = <EdgePlot.BLACK: 1>,\tface_plot: vertax.meshes.plot.FacePlot = <FacePlot.MULTICOLOR: 1>,\tvertex_parameters_name: str = '',\tedge_parameters_name: str = '',\tface_parameters_name: str = '',\tshow: bool = True,\tsave: bool = False,\tsave_path: str = 'pbc_mesh.png',\tfaces_cmap_name: str = 'cividis',\tedges_cmap_name: str = 'coolwarm',\tedges_width: float = 2,\tvertices_cmap_name: str = 'spring',\tvertices_size: float = 20,\ttitle: str = '',\tforced_vertex_scale: tuple[float, float] | None = None,\tforced_edge_scale: tuple[float, float] | None = None,\tforced_face_scale: tuple[float, float] | None = None) -> None:", "funcdef": "def"}, "vertax.get_plot_mesh": {"fullname": "vertax.get_plot_mesh", "modulename": "vertax", "qualname": "get_plot_mesh", "kind": "function", "doc": "

Get the matplotlib figure and and ax for one plot.

\n", "signature": "(\tmesh: vertax.meshes.mesh.Mesh,\tvertex_plot: vertax.meshes.plot.VertexPlot = <VertexPlot.INVISIBLE: 3>,\tedge_plot: vertax.meshes.plot.EdgePlot = <EdgePlot.BLACK: 1>,\tface_plot: vertax.meshes.plot.FacePlot = <FacePlot.MULTICOLOR: 1>,\tvertex_parameters_name: str = '',\tedge_parameters_name: str = '',\tface_parameters_name: str = '',\tfaces_cmap_name: str = 'cividis',\tedges_cmap_name: str = 'coolwarm',\tedges_width: float = 2,\tvertices_cmap_name: str = 'spring',\tvertices_size: float = 20,\ttitle: str = '',\tforced_vertex_scale: tuple[float, float] | None = None,\tforced_edge_scale: tuple[float, float] | None = None,\tforced_face_scale: tuple[float, float] | None = None) -> tuple[matplotlib.figure.Figure, matplotlib.axes._axes.Axes]:", "funcdef": "def"}, "vertax.FacePlot": {"fullname": "vertax.FacePlot", "modulename": "vertax", "qualname": "FacePlot", "kind": "class", "doc": "

What it is possible to show on a face.

\n", "bases": "enum.Enum"}, "vertax.FacePlot.MULTICOLOR": {"fullname": "vertax.FacePlot.MULTICOLOR", "modulename": "vertax", "qualname": "FacePlot.MULTICOLOR", "kind": "variable", "doc": "

Each face get a random color.

\n", "default_value": "<FacePlot.MULTICOLOR: 1>"}, "vertax.FacePlot.FACE_PARAMETER": {"fullname": "vertax.FacePlot.FACE_PARAMETER", "modulename": "vertax", "qualname": "FacePlot.FACE_PARAMETER", "kind": "variable", "doc": "

The face color depends on its parameter.

\n", "default_value": "<FacePlot.FACE_PARAMETER: 2>"}, "vertax.FacePlot.AREA": {"fullname": "vertax.FacePlot.AREA", "modulename": "vertax", "qualname": "FacePlot.AREA", "kind": "variable", "doc": "

The face color depends on its area.

\n", "default_value": "<FacePlot.AREA: 3>"}, "vertax.FacePlot.PERIMETER": {"fullname": "vertax.FacePlot.PERIMETER", "modulename": "vertax", "qualname": "FacePlot.PERIMETER", "kind": "variable", "doc": "

The face color depends on its perimeter.

\n", "default_value": "<FacePlot.PERIMETER: 4>"}, "vertax.FacePlot.WHITE": {"fullname": "vertax.FacePlot.WHITE", "modulename": "vertax", "qualname": "FacePlot.WHITE", "kind": "variable", "doc": "

All faces are just white.

\n", "default_value": "<FacePlot.WHITE: 5>"}, "vertax.FacePlot.FATES": {"fullname": "vertax.FacePlot.FATES", "modulename": "vertax", "qualname": "FacePlot.FATES", "kind": "variable", "doc": "

Faces are colored depending on their fate marker (if any).

\n", "default_value": "<FacePlot.FATES: 6>"}, "vertax.EdgePlot": {"fullname": "vertax.EdgePlot", "modulename": "vertax", "qualname": "EdgePlot", "kind": "class", "doc": "

What it is possible to show on an edge.

\n", "bases": "enum.Enum"}, "vertax.EdgePlot.BLACK": {"fullname": "vertax.EdgePlot.BLACK", "modulename": "vertax", "qualname": "EdgePlot.BLACK", "kind": "variable", "doc": "

All edges are black.

\n", "default_value": "<EdgePlot.BLACK: 1>"}, "vertax.EdgePlot.EDGE_PARAMETER": {"fullname": "vertax.EdgePlot.EDGE_PARAMETER", "modulename": "vertax", "qualname": "EdgePlot.EDGE_PARAMETER", "kind": "variable", "doc": "

The edge color depends on its parameter.

\n", "default_value": "<EdgePlot.EDGE_PARAMETER: 2>"}, "vertax.EdgePlot.LENGTH": {"fullname": "vertax.EdgePlot.LENGTH", "modulename": "vertax", "qualname": "EdgePlot.LENGTH", "kind": "variable", "doc": "

The edge color depends on its length.

\n", "default_value": "<EdgePlot.LENGTH: 3>"}, "vertax.EdgePlot.INVISIBLE": {"fullname": "vertax.EdgePlot.INVISIBLE", "modulename": "vertax", "qualname": "EdgePlot.INVISIBLE", "kind": "variable", "doc": "

Do not show edges.

\n", "default_value": "<EdgePlot.INVISIBLE: 4>"}, "vertax.VertexPlot": {"fullname": "vertax.VertexPlot", "modulename": "vertax", "qualname": "VertexPlot", "kind": "class", "doc": "

What it is possible to show on a vertex.

\n", "bases": "enum.Enum"}, "vertax.VertexPlot.BLACK": {"fullname": "vertax.VertexPlot.BLACK", "modulename": "vertax", "qualname": "VertexPlot.BLACK", "kind": "variable", "doc": "

Vertices are black.

\n", "default_value": "<VertexPlot.BLACK: 1>"}, "vertax.VertexPlot.VERTEX_PARAMETER": {"fullname": "vertax.VertexPlot.VERTEX_PARAMETER", "modulename": "vertax", "qualname": "VertexPlot.VERTEX_PARAMETER", "kind": "variable", "doc": "

The vertex color depends on its parameter.

\n", "default_value": "<VertexPlot.VERTEX_PARAMETER: 2>"}, "vertax.VertexPlot.INVISIBLE": {"fullname": "vertax.VertexPlot.INVISIBLE", "modulename": "vertax", "qualname": "VertexPlot.INVISIBLE", "kind": "variable", "doc": "

Do not show vertices.

\n", "default_value": "<VertexPlot.INVISIBLE: 3>"}, "vertax.cost_v2v": {"fullname": "vertax.cost_v2v", "modulename": "vertax", "qualname": "cost_v2v", "kind": "function", "doc": "

Cost vertex to vertex. Compare the positions of given vertices to target vertices (PbcMesh).

\n", "signature": "(\tvertTable: jax.Array,\theTable: jax.Array,\tfaceTable: jax.Array,\twidth: float,\theight: float,\tvertTable_target: jax.Array,\t_heTable_target: jax.Array,\t_faceTable_target: jax.Array,\tselected_verts: jax.Array | None = None,\tselected_hes: jax.Array | None = None,\tselected_faces: jax.Array | None = None,\t_image_target: jax.Array | None = None) -> jax.Array:", "funcdef": "def"}, "vertax.cost_mesh2image": {"fullname": "vertax.cost_mesh2image", "modulename": "vertax", "qualname": "cost_mesh2image", "kind": "function", "doc": "

Cost mesh to image. Compare the given vertices positions to a target image (PbcMesh).

\n", "signature": "(\tvertTable: jax.Array,\theTable: jax.Array,\t_faceTable: jax.Array,\twidth: float,\theight: float,\t_vertTable_target: jax.Array,\t_heTable_target: jax.Array,\t_faceTable_target: jax.Array,\t_selected_verts: jax.Array,\tselected_hes: jax.Array,\t_selected_faces: jax.Array,\timage_target: jax.Array) -> jax.Array:", "funcdef": "def"}, "vertax.cost_areas": {"fullname": "vertax.cost_areas", "modulename": "vertax", "qualname": "cost_areas", "kind": "function", "doc": "

Cost areas : compare the areas of the given mesh versus the areas of the target mesh (PbcMesh).

\n", "signature": "(\tvertTable: jax.Array,\theTable: jax.Array,\tfaceTable: jax.Array,\twidth: float,\theight: float,\t_selected_verts: jax.Array,\t_selected_hes: jax.Array,\tselected_faces: jax.Array,\tvertTable_target: jax.Array,\theTable_target: jax.Array,\tfaceTable_target: jax.Array,\t_image_target: jax.Array) -> jax.Array:", "funcdef": "def"}, "vertax.cost_IAS": {"fullname": "vertax.cost_IAS", "modulename": "vertax", "qualname": "cost_IAS", "kind": "function", "doc": "

Differentiable Index Aware Structural Loss. Force to respect the topology.

\n\n

C_{IAS}(i,j) = \\sqrt{\\sum_{k=1}^{N} (S_1(i,k) - S_2(j,k))^2}

\n", "signature": "(\tvertTable: jax.Array,\theTable: jax.Array,\tfaceTable: jax.Array,\t_width: float,\t_height: float,\tvertTable_target: jax.Array,\theTable_target: jax.Array,\tfaceTable_target: jax.Array,\t_selected_verts: jax.Array | None = None,\t_selected_hes: jax.Array | None = None,\t_selected_faces: jax.Array | None = None,\t_image_target: jax.Array | None = None) -> jax.Array:", "funcdef": "def"}, "vertax.cost_d_IAS": {"fullname": "vertax.cost_d_IAS", "modulename": "vertax", "qualname": "cost_d_IAS", "kind": "function", "doc": "

Discrete Index Aware Structural loss. Counts mismatched edges.

\n\n

C_{d-IAS}(i,j) = \\sum_{k=1}^{N} |A_1(i,k) - A_2(j,k)|

\n", "signature": "(\t_vertTable: jax.Array,\theTable: jax.Array,\tfaceTable: jax.Array,\t_width: float,\t_height: float,\t_vertTable_target: jax.Array,\theTable_target: jax.Array,\tfaceTable_target: jax.Array,\t_selected_verts: jax.Array | None = None,\t_selected_hes: jax.Array | None = None,\t_selected_faces: jax.Array | None = None,\t_image_target: jax.Array | None = None) -> int:", "funcdef": "def"}, "vertax.cost_tem_halfedge": {"fullname": "vertax.cost_tem_halfedge", "modulename": "vertax", "qualname": "cost_tem_halfedge", "kind": "function", "doc": "

TEM-inspired loss using half-edge dual graph (cells).

\n", "signature": "(\tvertTable: jax.Array,\theTable: jax.Array,\tfaceTable: jax.Array,\twidth: float,\theight: float,\tvertTable_target: jax.Array,\theTable_target: jax.Array,\tfaceTable_target: jax.Array,\t_selected_verts: jax.Array | None = None,\t_selected_hes: jax.Array | None = None,\t_selected_faces: jax.Array | None = None,\t_image_target: jax.Array | None = None) -> jax.Array:", "funcdef": "def"}, "vertax.cost_v2v_ias": {"fullname": "vertax.cost_v2v_ias", "modulename": "vertax", "qualname": "cost_v2v_ias", "kind": "function", "doc": "

Mix of cost_v2v and cost_IAS (with weight 0.6 and 0.4).

\n", "signature": "(\tvertTable: jax.Array,\theTable: jax.Array,\tfaceTable: jax.Array,\twidth: float,\theight: float,\tvertTable_target: jax.Array,\theTable_target: jax.Array,\tfaceTable_target: jax.Array,\t_selected_verts: jax.Array | None = None,\t_selected_hes: jax.Array | None = None,\t_selected_faces: jax.Array | None = None,\t_image_target: jax.Array | None = None) -> jax.Array:", "funcdef": "def"}, "vertax.cost_v2v_tem": {"fullname": "vertax.cost_v2v_tem", "modulename": "vertax", "qualname": "cost_v2v_tem", "kind": "function", "doc": "

Mix of cost_v2v and cost_tem_halfedge (with respective weights 0.99 and 0.01).

\n", "signature": "(\tvertTable: jax.Array,\theTable: jax.Array,\tfaceTable: jax.Array,\twidth: float,\theight: float,\tvertTable_target: jax.Array,\theTable_target: jax.Array,\tfaceTable_target: jax.Array,\t_selected_verts: jax.Array | None = None,\t_selected_hes: jax.Array | None = None,\t_selected_faces: jax.Array | None = None,\t_image_target: jax.Array | None = None) -> jax.Array:", "funcdef": "def"}, "vertax.cost_ratio": {"fullname": "vertax.cost_ratio", "modulename": "vertax", "qualname": "cost_ratio", "kind": "function", "doc": "

Cost that nudges a BoundedMesh to elongate along one axis while narrowing along the orthogonal axis.

\n", "signature": "(\tvertTable: jax.Array,\t_angTable: jax.Array | None = None,\t_heTable: jax.Array | None = None,\t_faceTable: jax.Array | None = None,\t_vertTable_target: jax.Array | None = None,\t_angTable_target: jax.Array | None = None,\t_heTable_target: jax.Array | None = None,\t_faceTable_target: jax.Array | None = None,\t_selected_verts: jax.Array | None = None,\t_selected_hes: jax.Array | None = None,\t_selected_faces: jax.Array | None = None,\t_image_target: jax.Array | None = None) -> jax.Array:", "funcdef": "def"}, "vertax.cost_checkerboard": {"fullname": "vertax.cost_checkerboard", "modulename": "vertax", "qualname": "cost_checkerboard", "kind": "function", "doc": "

Cost that nudges a BoundedMesh with different fated cells to avoid having neighboring cells with same fate.

\n\n

This leads to a checkerboard pattern when there is 2 fates.

\n", "signature": "(\tvertTable: jax.Array,\t_angTable: jax.Array,\theTable: jax.Array,\tfaceTable: jax.Array,\t_vertTable_target: jax.Array | None = None,\t_angTable_target: jax.Array | None = None,\t_heTable_target: jax.Array | None = None,\t_faceTable_target: jax.Array | None = None,\t_selected_verts: jax.Array | None = None,\t_selected_hes: jax.Array | None = None,\t_selected_faces: jax.Array | None = None,\t_image_target: jax.Array | None = None) -> jax.Array:", "funcdef": "def"}, "vertax.energy_shape_factor_hetero": {"fullname": "vertax.energy_shape_factor_hetero", "modulename": "vertax", "qualname": "energy_shape_factor_hetero", "kind": "function", "doc": "

E1 energy where the shape factor depends on the cell.

\n", "signature": "(\tvertTable: jax.Array,\theTable: jax.Array,\tfaceTable: jax.Array,\twidth: float,\theight: float,\tselected_faces: jax.Array,\tface_params: jax.Array) -> jax.Array:", "funcdef": "def"}, "vertax.energy_shape_factor_homo": {"fullname": "vertax.energy_shape_factor_homo", "modulename": "vertax", "qualname": "energy_shape_factor_homo", "kind": "function", "doc": "

E1 energy where the shape factor is uniform (give only one face_params, it will be broadcasted).

\n", "signature": "(\tvertTable: jax.Array,\theTable: jax.Array,\tfaceTable: jax.Array,\twidth: float,\theight: float,\tface_params: jax.Array) -> jax.Array:", "funcdef": "def"}, "vertax.energy_line_tensions": {"fullname": "vertax.energy_line_tensions", "modulename": "vertax", "qualname": "energy_line_tensions", "kind": "function", "doc": "

E2 energy for PBC meshes, elastic penalty on cell areas and line tension term weighted by edge lengths.

\n", "signature": "(\tvertTable: jax.Array,\theTable: jax.Array,\tfaceTable: jax.Array,\twidth: float,\theight: float,\the_params: jax.Array,\tface_params: jax.Array) -> jax.Array:", "funcdef": "def"}, "vertax.energy_line_tensions_bounded": {"fullname": "vertax.energy_line_tensions_bounded", "modulename": "vertax", "qualname": "energy_line_tensions_bounded", "kind": "function", "doc": "

E2 energy for bounded meshes, elastic penalty on cell areas and line tension term weighted by edge lengths.

\n", "signature": "(\tvertTable: jax.Array,\tangTable: jax.Array,\theTable: jax.Array,\tfaceTable: jax.Array,\t_selected_verts: jax.Array | None,\t_selected_hes: jax.Array | None,\t_selected_faces: jax.Array | None,\t_vert_params: jax.Array,\the_params: jax.Array,\t_face_params: jax.Array) -> jax.Array:", "funcdef": "def"}}, "docInfo": {"vertax": {"qualname": 0, "fullname": 1, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 550}, "vertax.Mesh": {"qualname": 1, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 34}, "vertax.Mesh.__init__": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 4, "bases": 0, "doc": 40}, "vertax.Mesh.faces": {"qualname": 2, "fullname": 3, "annotation": 3, "default_value": 0, "signature": 0, "bases": 0, "doc": 8}, "vertax.Mesh.edges": {"qualname": 2, "fullname": 3, "annotation": 3, "default_value": 0, "signature": 0, "bases": 0, "doc": 10}, "vertax.Mesh.vertices": {"qualname": 2, "fullname": 3, "annotation": 3, "default_value": 0, "signature": 0, "bases": 0, "doc": 9}, "vertax.Mesh.width": {"qualname": 2, "fullname": 3, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 17}, "vertax.Mesh.height": {"qualname": 2, "fullname": 3, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 17}, "vertax.Mesh.faces_params": {"qualname": 3, "fullname": 4, "annotation": 3, "default_value": 0, "signature": 0, "bases": 0, "doc": 10}, "vertax.Mesh.edges_params": {"qualname": 3, "fullname": 4, "annotation": 3, "default_value": 0, "signature": 0, "bases": 0, "doc": 10}, "vertax.Mesh.vertices_params": {"qualname": 3, "fullname": 4, "annotation": 3, "default_value": 0, "signature": 0, "bases": 0, "doc": 10}, "vertax.Mesh.nb_faces": {"qualname": 3, "fullname": 4, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 11}, "vertax.Mesh.nb_edges": {"qualname": 3, "fullname": 4, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 11}, "vertax.Mesh.nb_half_edges": {"qualname": 4, "fullname": 5, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 18}, "vertax.Mesh.nb_vertices": {"qualname": 3, "fullname": 4, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 11}, "vertax.Mesh.save_mesh": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 24, "bases": 0, "doc": 40}, "vertax.Mesh.load_mesh": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 24, "bases": 0, "doc": 47}, "vertax.PbcMesh": {"qualname": 1, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 4, "doc": 175}, "vertax.PbcMesh.__init__": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 4, "bases": 0, "doc": 55}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"qualname": 6, "fullname": 7, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 23}, "vertax.PbcMesh.from_random_seeds": {"qualname": 4, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 56, "bases": 0, "doc": 104}, "vertax.PbcMesh.from_seeds": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 49, "bases": 0, "doc": 88}, "vertax.PbcMesh.from_image": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 61, "bases": 0, "doc": 112}, "vertax.PbcMesh.from_mask": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 61, "bases": 0, "doc": 72}, "vertax.PbcMesh.create_empty": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 15}, "vertax.PbcMesh.copy_mesh": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 25, "bases": 0, "doc": 13}, "vertax.PbcMesh.save_mesh": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 24, "bases": 0, "doc": 43}, "vertax.PbcMesh.load_mesh": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 24, "bases": 0, "doc": 59}, "vertax.PbcMesh.save_mesh_txt": {"qualname": 4, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 197, "bases": 0, "doc": 196}, "vertax.PbcMesh.load_mesh_txt": {"qualname": 4, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 197, "bases": 0, "doc": 201}, "vertax.PbcMesh.get_length": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 36, "bases": 0, "doc": 9}, "vertax.PbcMesh.get_length_with_offset": {"qualname": 5, "fullname": 6, "annotation": 0, "default_value": 0, "signature": 36, "bases": 0, "doc": 21}, "vertax.PbcMesh.get_perimeter": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 35, "bases": 0, "doc": 9}, "vertax.PbcMesh.get_area": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 35, "bases": 0, "doc": 9}, "vertax.PbcMesh.update_boundary_conditions": {"qualname": 4, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 35}, "vertax.BoundedMesh": {"qualname": 1, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 4, "doc": 235}, "vertax.BoundedMesh.__init__": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 4, "bases": 0, "doc": 8}, "vertax.BoundedMesh.angles": {"qualname": 2, "fullname": 3, "annotation": 3, "default_value": 0, "signature": 0, "bases": 0, "doc": 15}, "vertax.BoundedMesh.nb_angles": {"qualname": 3, "fullname": 4, "annotation": 2, "default_value": 0, "signature": 0, "bases": 0, "doc": 13}, "vertax.BoundedMesh.from_random_seeds": {"qualname": 4, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 80, "bases": 0, "doc": 130}, "vertax.BoundedMesh.from_seeds": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 114, "bases": 0, "doc": 157}, "vertax.BoundedMesh.create_empty": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 14, "bases": 0, "doc": 15}, "vertax.BoundedMesh.copy_mesh": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 25, "bases": 0, "doc": 13}, "vertax.BoundedMesh.save_mesh": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 24, "bases": 0, "doc": 40}, "vertax.BoundedMesh.load_mesh": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 24, "bases": 0, "doc": 56}, "vertax.BoundedMesh.save_mesh_txt": {"qualname": 4, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 221, "bases": 0, "doc": 212}, "vertax.BoundedMesh.load_mesh_txt": {"qualname": 4, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 221, "bases": 0, "doc": 221}, "vertax.BoundedMesh.get_length": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 36, "bases": 0, "doc": 9}, "vertax.BoundedMesh.get_perimeter": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 35, "bases": 0, "doc": 9}, "vertax.BoundedMesh.get_area": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 35, "bases": 0, "doc": 9}, "vertax.BilevelOptimizationMethod": {"qualname": 1, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 2, "doc": 13}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 10, "signature": 0, "bases": 0, "doc": 25}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 10, "signature": 0, "bases": 0, "doc": 24}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 10, "signature": 0, "bases": 0, "doc": 23}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 10, "signature": 0, "bases": 0, "doc": 23}, "vertax.PbcBilevelOptimizer": {"qualname": 1, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 4, "doc": 14}, "vertax.PbcBilevelOptimizer.__init__": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 4, "bases": 0, "doc": 28}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"qualname": 4, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 137, "bases": 0, "doc": 16}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"qualname": 4, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 137, "bases": 0, "doc": 16}, "vertax.BoundedBilevelOptimizer": {"qualname": 1, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 4, "doc": 12}, "vertax.BoundedBilevelOptimizer.__init__": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 4, "bases": 0, "doc": 27}, "vertax.BoundedBilevelOptimizer.angles_target": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 0, "bases": 0, "doc": 3}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"qualname": 4, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 137, "bases": 0, "doc": 25}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"qualname": 4, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 137, "bases": 0, "doc": 25}, "vertax.plot_mesh": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 566, "bases": 0, "doc": 16}, "vertax.get_plot_mesh": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 543, "bases": 0, "doc": 13}, "vertax.FacePlot": {"qualname": 1, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 2, "doc": 12}, "vertax.FacePlot.MULTICOLOR": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 7, "signature": 0, "bases": 0, "doc": 9}, "vertax.FacePlot.FACE_PARAMETER": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 8, "signature": 0, "bases": 0, "doc": 10}, "vertax.FacePlot.AREA": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 7, "signature": 0, "bases": 0, "doc": 10}, "vertax.FacePlot.PERIMETER": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 7, "signature": 0, "bases": 0, "doc": 10}, "vertax.FacePlot.WHITE": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 7, "signature": 0, "bases": 0, "doc": 8}, "vertax.FacePlot.FATES": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 7, "signature": 0, "bases": 0, "doc": 13}, "vertax.EdgePlot": {"qualname": 1, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 2, "doc": 12}, "vertax.EdgePlot.BLACK": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 7, "signature": 0, "bases": 0, "doc": 7}, "vertax.EdgePlot.EDGE_PARAMETER": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 8, "signature": 0, "bases": 0, "doc": 10}, "vertax.EdgePlot.LENGTH": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 7, "signature": 0, "bases": 0, "doc": 10}, "vertax.EdgePlot.INVISIBLE": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 7, "signature": 0, "bases": 0, "doc": 7}, "vertax.VertexPlot": {"qualname": 1, "fullname": 2, "annotation": 0, "default_value": 0, "signature": 0, "bases": 2, "doc": 12}, "vertax.VertexPlot.BLACK": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 7, "signature": 0, "bases": 0, "doc": 6}, "vertax.VertexPlot.VERTEX_PARAMETER": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 8, "signature": 0, "bases": 0, "doc": 10}, "vertax.VertexPlot.INVISIBLE": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 7, "signature": 0, "bases": 0, "doc": 7}, "vertax.cost_v2v": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 258, "bases": 0, "doc": 19}, "vertax.cost_mesh2image": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 209, "bases": 0, "doc": 19}, "vertax.cost_areas": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 206, "bases": 0, "doc": 23}, "vertax.cost_IAS": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 261, "bases": 0, "doc": 31}, "vertax.cost_d_IAS": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 258, "bases": 0, "doc": 30}, "vertax.cost_tem_halfedge": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 259, "bases": 0, "doc": 12}, "vertax.cost_v2v_ias": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 259, "bases": 0, "doc": 17}, "vertax.cost_v2v_tem": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 259, "bases": 0, "doc": 19}, "vertax.cost_ratio": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 368, "bases": 0, "doc": 21}, "vertax.cost_checkerboard": {"qualname": 2, "fullname": 3, "annotation": 0, "default_value": 0, "signature": 327, "bases": 0, "doc": 36}, "vertax.energy_shape_factor_hetero": {"qualname": 4, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 118, "bases": 0, "doc": 13}, "vertax.energy_shape_factor_homo": {"qualname": 4, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 101, "bases": 0, "doc": 20}, "vertax.energy_line_tensions": {"qualname": 3, "fullname": 4, "annotation": 0, "default_value": 0, "signature": 118, "bases": 0, "doc": 21}, "vertax.energy_line_tensions_bounded": {"qualname": 4, "fullname": 5, "annotation": 0, "default_value": 0, "signature": 203, "bases": 0, "doc": 21}}, "length": 96, "save": true}, "index": {"qualname": {"root": {"docs": {"vertax.Mesh.__init__": {"tf": 1}, "vertax.PbcMesh.__init__": {"tf": 1}, "vertax.BoundedMesh.__init__": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}}, "df": 5, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "h": {"2": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"vertax.cost_mesh2image": {"tf": 1}}, "df": 1}}}}}}, "docs": {"vertax.Mesh": {"tf": 1}, "vertax.Mesh.__init__": {"tf": 1}, "vertax.Mesh.faces": {"tf": 1}, "vertax.Mesh.edges": {"tf": 1}, "vertax.Mesh.vertices": {"tf": 1}, "vertax.Mesh.width": {"tf": 1}, "vertax.Mesh.height": {"tf": 1}, "vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}, "vertax.Mesh.nb_faces": {"tf": 1}, "vertax.Mesh.nb_edges": {"tf": 1}, "vertax.Mesh.nb_half_edges": {"tf": 1}, "vertax.Mesh.nb_vertices": {"tf": 1}, "vertax.Mesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.Mesh.load_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 28}}}, "a": {"docs": {}, "df": 0, "x": {"docs": {"vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}}, "df": 1}, "s": {"docs": {}, "df": 0, "k": {"docs": {"vertax.PbcMesh.from_mask": {"tf": 1}}, "df": 1}}}, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"vertax.FacePlot.MULTICOLOR": {"tf": 1}}, "df": 1}}}}}}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {"vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}}, "df": 1, "i": {"docs": {}, "df": 0, "t": {"docs": {"vertax.Mesh.__init__": {"tf": 1}, "vertax.PbcMesh.__init__": {"tf": 1}, "vertax.BoundedMesh.__init__": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}}, "df": 5}}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 2}}}, "v": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.EdgePlot.INVISIBLE": {"tf": 1}, "vertax.VertexPlot.INVISIBLE": {"tf": 1}}, "df": 2}}}}}}}}, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}}, "df": 1}}}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}}, "df": 1}}}}}}}, "a": {"docs": {}, "df": 0, "s": {"docs": {"vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1}}, "df": 3}}}, "f": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1}}, "df": 2, "s": {"docs": {"vertax.Mesh.faces": {"tf": 1}, "vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.nb_faces": {"tf": 1}}, "df": 3}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"vertax.FacePlot": {"tf": 1}, "vertax.FacePlot.MULTICOLOR": {"tf": 1}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.FacePlot.AREA": {"tf": 1}, "vertax.FacePlot.PERIMETER": {"tf": 1}, "vertax.FacePlot.WHITE": {"tf": 1}, "vertax.FacePlot.FATES": {"tf": 1}}, "df": 7}}}}}, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 2}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.FacePlot.FATES": {"tf": 1}}, "df": 1}}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 6}}}}, "e": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}}, "df": 1, "s": {"docs": {"vertax.Mesh.edges": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.nb_edges": {"tf": 1}, "vertax.Mesh.nb_half_edges": {"tf": 1}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}}, "df": 5}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"vertax.EdgePlot": {"tf": 1}, "vertax.EdgePlot.BLACK": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.LENGTH": {"tf": 1}, "vertax.EdgePlot.INVISIBLE": {"tf": 1}}, "df": 5}}}}}}}, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.create_empty": {"tf": 1}}, "df": 2}}}}, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "m": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}}}}}}}}}}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "y": {"docs": {"vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 4}}}}}}, "v": {"2": {"docs": {}, "df": 0, "v": {"docs": {"vertax.cost_v2v": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}}, "df": 3}}, "docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.Mesh.vertices": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}, "vertax.Mesh.nb_vertices": {"tf": 1}}, "df": 3}}}}, "e": {"docs": {}, "df": 0, "x": {"docs": {"vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}}, "df": 1, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"vertax.VertexPlot": {"tf": 1}, "vertax.VertexPlot.BLACK": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}, "vertax.VertexPlot.INVISIBLE": {"tf": 1}}, "df": 4}}}}}}}}}}, "w": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"vertax.Mesh.width": {"tf": 1}}, "df": 1}}}, "t": {"docs": {}, "df": 0, "h": {"docs": {"vertax.PbcMesh.get_length_with_offset": {"tf": 1}}, "df": 1}}}, "h": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.FacePlot.WHITE": {"tf": 1}}, "df": 1}}}}}, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "t": {"docs": {"vertax.Mesh.height": {"tf": 1}}, "df": 1}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {"vertax.energy_shape_factor_hetero": {"tf": 1}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "f": {"docs": {"vertax.Mesh.nb_half_edges": {"tf": 1}}, "df": 1, "e": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"vertax.cost_tem_halfedge": {"tf": 1}}, "df": 1}}}}}}}, "o": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "o": {"docs": {"vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 1}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "s": {"docs": {"vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}}, "df": 3}, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}}, "df": 3}}}}}}}}, "b": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "h": {"docs": {"vertax.PbcMesh": {"tf": 1}, "vertax.PbcMesh.__init__": {"tf": 1}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.PbcMesh.get_perimeter": {"tf": 1}, "vertax.PbcMesh.get_area": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 18}}}}, "b": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcBilevelOptimizer": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 4}}}}}}}}}}}}}}}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcMesh.get_perimeter": {"tf": 1}, "vertax.BoundedMesh.get_perimeter": {"tf": 1}, "vertax.FacePlot.PERIMETER": {"tf": 1}}, "df": 3}}}}}}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}}}}}}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 2}}}}, "n": {"docs": {}, "df": 0, "b": {"docs": {"vertax.Mesh.nb_faces": {"tf": 1}, "vertax.Mesh.nb_edges": {"tf": 1}, "vertax.Mesh.nb_half_edges": {"tf": 1}, "vertax.Mesh.nb_vertices": {"tf": 1}, "vertax.BoundedMesh.nb_angles": {"tf": 1}}, "df": 5}}, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"vertax.Mesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}}, "df": 5}}}, "e": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 4}}}}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 1}}}}, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {"vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 2}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {"vertax.Mesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 5}}, "s": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 4}}}, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.BoundedMesh.get_length": {"tf": 1}, "vertax.EdgePlot.LENGTH": {"tf": 1}}, "df": 4}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 2}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}}, "df": 1}, "g": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.BoundedMesh.angles": {"tf": 1}, "vertax.BoundedMesh.nb_angles": {"tf": 1}, "vertax.BoundedBilevelOptimizer.angles_target": {"tf": 1}}, "df": 3}}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {"vertax.PbcMesh.get_area": {"tf": 1}, "vertax.BoundedMesh.get_area": {"tf": 1}, "vertax.FacePlot.AREA": {"tf": 1}}, "df": 3, "s": {"docs": {"vertax.cost_areas": {"tf": 1}}, "df": 1}}}}, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}}, "df": 1}}}}}}}}, "d": {"docs": {}, "df": 0, "j": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 1}}}}}}}, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}}, "df": 2}}}}, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {"vertax.cost_ratio": {"tf": 1}}, "df": 1}}}}}, "c": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.create_empty": {"tf": 1}}, "df": 2}}}}}, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}}, "df": 2}}, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 1}}}}}}}}, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 4}}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {"vertax.cost_v2v": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_areas": {"tf": 1}, "vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}, "vertax.cost_tem_halfedge": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}, "vertax.cost_ratio": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}}, "df": 10}}}, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "d": {"docs": {"vertax.cost_checkerboard": {"tf": 1}}, "df": 1}}}}}}}}}}}}, "t": {"docs": {}, "df": 0, "x": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 4}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BoundedBilevelOptimizer.angles_target": {"tf": 1}}, "df": 1}}}}}, "e": {"docs": {}, "df": 0, "m": {"docs": {"vertax.cost_tem_halfedge": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}}, "df": 2}, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 2}}}}}}}}, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.PbcMesh.get_perimeter": {"tf": 1}, "vertax.PbcMesh.get_area": {"tf": 1}, "vertax.BoundedMesh.get_length": {"tf": 1}, "vertax.BoundedMesh.get_perimeter": {"tf": 1}, "vertax.BoundedMesh.get_area": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 8}}}, "o": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.get_length_with_offset": {"tf": 1}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}}, "df": 2}}}}}, "u": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 1}}}}}}, "b": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 1}}}, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 1, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "h": {"docs": {"vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.__init__": {"tf": 1}, "vertax.BoundedMesh.angles": {"tf": 1}, "vertax.BoundedMesh.nb_angles": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.get_length": {"tf": 1}, "vertax.BoundedMesh.get_perimeter": {"tf": 1}, "vertax.BoundedMesh.get_area": {"tf": 1}}, "df": 15}}}}, "b": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.BoundedBilevelOptimizer": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.angles_target": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 5}}}}}}}}}}}}}}}}}}}}}}, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "d": {"docs": {"vertax.BilevelOptimizationMethod": {"tf": 1}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 5}}}}}}}}}}}}}}}}}}}}}}}}, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"vertax.EdgePlot.BLACK": {"tf": 1}, "vertax.VertexPlot.BLACK": {"tf": 1}}, "df": 2}}}}}, "d": {"docs": {"vertax.cost_d_IAS": {"tf": 1}}, "df": 1, "i": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}}, "df": 2}}}}}}}}}}}}}}}}}, "fullname": {"root": {"docs": {"vertax.Mesh.__init__": {"tf": 1}, "vertax.PbcMesh.__init__": {"tf": 1}, "vertax.BoundedMesh.__init__": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}}, "df": 5, "v": {"2": {"docs": {}, "df": 0, "v": {"docs": {"vertax.cost_v2v": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}}, "df": 3}}, "docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "x": {"docs": {"vertax": {"tf": 1}, "vertax.Mesh": {"tf": 1}, "vertax.Mesh.__init__": {"tf": 1}, "vertax.Mesh.faces": {"tf": 1}, "vertax.Mesh.edges": {"tf": 1}, "vertax.Mesh.vertices": {"tf": 1}, "vertax.Mesh.width": {"tf": 1}, "vertax.Mesh.height": {"tf": 1}, "vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}, "vertax.Mesh.nb_faces": {"tf": 1}, "vertax.Mesh.nb_edges": {"tf": 1}, "vertax.Mesh.nb_half_edges": {"tf": 1}, "vertax.Mesh.nb_vertices": {"tf": 1}, "vertax.Mesh.save_mesh": {"tf": 1}, "vertax.Mesh.load_mesh": {"tf": 1}, "vertax.PbcMesh": {"tf": 1}, "vertax.PbcMesh.__init__": {"tf": 1}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.PbcMesh.get_perimeter": {"tf": 1}, "vertax.PbcMesh.get_area": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.__init__": {"tf": 1}, "vertax.BoundedMesh.angles": {"tf": 1}, "vertax.BoundedMesh.nb_angles": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.get_length": {"tf": 1}, "vertax.BoundedMesh.get_perimeter": {"tf": 1}, "vertax.BoundedMesh.get_area": {"tf": 1}, "vertax.BilevelOptimizationMethod": {"tf": 1}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}, "vertax.PbcBilevelOptimizer": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.angles_target": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}, "vertax.FacePlot": {"tf": 1}, "vertax.FacePlot.MULTICOLOR": {"tf": 1}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.FacePlot.AREA": {"tf": 1}, "vertax.FacePlot.PERIMETER": {"tf": 1}, "vertax.FacePlot.WHITE": {"tf": 1}, "vertax.FacePlot.FATES": {"tf": 1}, "vertax.EdgePlot": {"tf": 1}, "vertax.EdgePlot.BLACK": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.LENGTH": {"tf": 1}, "vertax.EdgePlot.INVISIBLE": {"tf": 1}, "vertax.VertexPlot": {"tf": 1}, "vertax.VertexPlot.BLACK": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}, "vertax.VertexPlot.INVISIBLE": {"tf": 1}, "vertax.cost_v2v": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_areas": {"tf": 1}, "vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}, "vertax.cost_tem_halfedge": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}, "vertax.cost_ratio": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}, "vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 96}}, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.Mesh.vertices": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}, "vertax.Mesh.nb_vertices": {"tf": 1}}, "df": 3}}}}, "e": {"docs": {}, "df": 0, "x": {"docs": {"vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}}, "df": 1, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"vertax.VertexPlot": {"tf": 1}, "vertax.VertexPlot.BLACK": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}, "vertax.VertexPlot.INVISIBLE": {"tf": 1}}, "df": 4}}}}}}}}}}, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "h": {"2": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"vertax.cost_mesh2image": {"tf": 1}}, "df": 1}}}}}}, "docs": {"vertax.Mesh": {"tf": 1}, "vertax.Mesh.__init__": {"tf": 1}, "vertax.Mesh.faces": {"tf": 1}, "vertax.Mesh.edges": {"tf": 1}, "vertax.Mesh.vertices": {"tf": 1}, "vertax.Mesh.width": {"tf": 1}, "vertax.Mesh.height": {"tf": 1}, "vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}, "vertax.Mesh.nb_faces": {"tf": 1}, "vertax.Mesh.nb_edges": {"tf": 1}, "vertax.Mesh.nb_half_edges": {"tf": 1}, "vertax.Mesh.nb_vertices": {"tf": 1}, "vertax.Mesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.Mesh.load_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 28}}}, "a": {"docs": {}, "df": 0, "x": {"docs": {"vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}}, "df": 1}, "s": {"docs": {}, "df": 0, "k": {"docs": {"vertax.PbcMesh.from_mask": {"tf": 1}}, "df": 1}}}, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"vertax.FacePlot.MULTICOLOR": {"tf": 1}}, "df": 1}}}}}}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {"vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}}, "df": 1, "i": {"docs": {}, "df": 0, "t": {"docs": {"vertax.Mesh.__init__": {"tf": 1}, "vertax.PbcMesh.__init__": {"tf": 1}, "vertax.BoundedMesh.__init__": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}}, "df": 5}}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 2}}}, "v": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.EdgePlot.INVISIBLE": {"tf": 1}, "vertax.VertexPlot.INVISIBLE": {"tf": 1}}, "df": 2}}}}}}}}, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}}, "df": 1}}}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}}, "df": 1}}}}}}}, "a": {"docs": {}, "df": 0, "s": {"docs": {"vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1}}, "df": 3}}}, "f": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1}}, "df": 2, "s": {"docs": {"vertax.Mesh.faces": {"tf": 1}, "vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.nb_faces": {"tf": 1}}, "df": 3}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"vertax.FacePlot": {"tf": 1}, "vertax.FacePlot.MULTICOLOR": {"tf": 1}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.FacePlot.AREA": {"tf": 1}, "vertax.FacePlot.PERIMETER": {"tf": 1}, "vertax.FacePlot.WHITE": {"tf": 1}, "vertax.FacePlot.FATES": {"tf": 1}}, "df": 7}}}}}, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 2}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.FacePlot.FATES": {"tf": 1}}, "df": 1}}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 6}}}}, "e": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}}, "df": 1, "s": {"docs": {"vertax.Mesh.edges": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.nb_edges": {"tf": 1}, "vertax.Mesh.nb_half_edges": {"tf": 1}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}}, "df": 5}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"vertax.EdgePlot": {"tf": 1}, "vertax.EdgePlot.BLACK": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.LENGTH": {"tf": 1}, "vertax.EdgePlot.INVISIBLE": {"tf": 1}}, "df": 5}}}}}}}, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.create_empty": {"tf": 1}}, "df": 2}}}}, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "m": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}}}}}}}}}}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "y": {"docs": {"vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 4}}}}}}, "w": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"vertax.Mesh.width": {"tf": 1}}, "df": 1}}}, "t": {"docs": {}, "df": 0, "h": {"docs": {"vertax.PbcMesh.get_length_with_offset": {"tf": 1}}, "df": 1}}}, "h": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.FacePlot.WHITE": {"tf": 1}}, "df": 1}}}}}, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "t": {"docs": {"vertax.Mesh.height": {"tf": 1}}, "df": 1}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {"vertax.energy_shape_factor_hetero": {"tf": 1}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "f": {"docs": {"vertax.Mesh.nb_half_edges": {"tf": 1}}, "df": 1, "e": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"vertax.cost_tem_halfedge": {"tf": 1}}, "df": 1}}}}}}}, "o": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "o": {"docs": {"vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 1}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "s": {"docs": {"vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}}, "df": 3}, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}}, "df": 3}}}}}}}}, "b": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "h": {"docs": {"vertax.PbcMesh": {"tf": 1}, "vertax.PbcMesh.__init__": {"tf": 1}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.PbcMesh.get_perimeter": {"tf": 1}, "vertax.PbcMesh.get_area": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 18}}}}, "b": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcBilevelOptimizer": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 4}}}}}}}}}}}}}}}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcMesh.get_perimeter": {"tf": 1}, "vertax.BoundedMesh.get_perimeter": {"tf": 1}, "vertax.FacePlot.PERIMETER": {"tf": 1}}, "df": 3}}}}}}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}}}}}}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 2}}}}, "n": {"docs": {}, "df": 0, "b": {"docs": {"vertax.Mesh.nb_faces": {"tf": 1}, "vertax.Mesh.nb_edges": {"tf": 1}, "vertax.Mesh.nb_half_edges": {"tf": 1}, "vertax.Mesh.nb_vertices": {"tf": 1}, "vertax.BoundedMesh.nb_angles": {"tf": 1}}, "df": 5}}, "s": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"vertax.Mesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}}, "df": 5}}}, "e": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 4}}}}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 1}}}}, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {"vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 2}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {"vertax.Mesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 5}}, "s": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 4}}}, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.BoundedMesh.get_length": {"tf": 1}, "vertax.EdgePlot.LENGTH": {"tf": 1}}, "df": 4}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 2}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}}, "df": 1}, "g": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.BoundedMesh.angles": {"tf": 1}, "vertax.BoundedMesh.nb_angles": {"tf": 1}, "vertax.BoundedBilevelOptimizer.angles_target": {"tf": 1}}, "df": 3}}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {"vertax.PbcMesh.get_area": {"tf": 1}, "vertax.BoundedMesh.get_area": {"tf": 1}, "vertax.FacePlot.AREA": {"tf": 1}}, "df": 3, "s": {"docs": {"vertax.cost_areas": {"tf": 1}}, "df": 1}}}}, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}}, "df": 1}}}}}}}}, "d": {"docs": {}, "df": 0, "j": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 1}}}}}}}, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}}, "df": 2}}}}, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {"vertax.cost_ratio": {"tf": 1}}, "df": 1}}}}}, "c": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.create_empty": {"tf": 1}}, "df": 2}}}}}, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}}, "df": 2}}, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 1}}}}}}}}, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 4}}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {"vertax.cost_v2v": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_areas": {"tf": 1}, "vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}, "vertax.cost_tem_halfedge": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}, "vertax.cost_ratio": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}}, "df": 10}}}, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "d": {"docs": {"vertax.cost_checkerboard": {"tf": 1}}, "df": 1}}}}}}}}}}}}, "t": {"docs": {}, "df": 0, "x": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 4}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BoundedBilevelOptimizer.angles_target": {"tf": 1}}, "df": 1}}}}}, "e": {"docs": {}, "df": 0, "m": {"docs": {"vertax.cost_tem_halfedge": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}}, "df": 2}, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 2}}}}}}}}, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.PbcMesh.get_perimeter": {"tf": 1}, "vertax.PbcMesh.get_area": {"tf": 1}, "vertax.BoundedMesh.get_length": {"tf": 1}, "vertax.BoundedMesh.get_perimeter": {"tf": 1}, "vertax.BoundedMesh.get_area": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 8}}}, "o": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.get_length_with_offset": {"tf": 1}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}}, "df": 2}}}}}, "u": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 1}}}}}}, "b": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 1}}}, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 1, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "h": {"docs": {"vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.__init__": {"tf": 1}, "vertax.BoundedMesh.angles": {"tf": 1}, "vertax.BoundedMesh.nb_angles": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.get_length": {"tf": 1}, "vertax.BoundedMesh.get_perimeter": {"tf": 1}, "vertax.BoundedMesh.get_area": {"tf": 1}}, "df": 15}}}}, "b": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.BoundedBilevelOptimizer": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.angles_target": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 5}}}}}}}}}}}}}}}}}}}}}}, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "d": {"docs": {"vertax.BilevelOptimizationMethod": {"tf": 1}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 5}}}}}}}}}}}}}}}}}}}}}}}}, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"vertax.EdgePlot.BLACK": {"tf": 1}, "vertax.VertexPlot.BLACK": {"tf": 1}}, "df": 2}}}}}, "d": {"docs": {"vertax.cost_d_IAS": {"tf": 1}}, "df": 1, "i": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}}, "df": 2}}}}}}}}}}}}}}}}}, "annotation": {"root": {"docs": {"vertax.Mesh.faces": {"tf": 1}, "vertax.Mesh.edges": {"tf": 1}, "vertax.Mesh.vertices": {"tf": 1}, "vertax.Mesh.width": {"tf": 1}, "vertax.Mesh.height": {"tf": 1}, "vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}, "vertax.Mesh.nb_faces": {"tf": 1}, "vertax.Mesh.nb_edges": {"tf": 1}, "vertax.Mesh.nb_half_edges": {"tf": 1}, "vertax.Mesh.nb_vertices": {"tf": 1}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.BoundedMesh.angles": {"tf": 1}, "vertax.BoundedMesh.nb_angles": {"tf": 1}}, "df": 15, "j": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "x": {"docs": {"vertax.Mesh.faces": {"tf": 1}, "vertax.Mesh.edges": {"tf": 1}, "vertax.Mesh.vertices": {"tf": 1}, "vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}, "vertax.BoundedMesh.angles": {"tf": 1}}, "df": 7}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "y": {"docs": {"vertax.Mesh.faces": {"tf": 1}, "vertax.Mesh.edges": {"tf": 1}, "vertax.Mesh.vertices": {"tf": 1}, "vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}, "vertax.BoundedMesh.angles": {"tf": 1}}, "df": 7}}}}}, "f": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {"vertax.Mesh.width": {"tf": 1}, "vertax.Mesh.height": {"tf": 1}}, "df": 2}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"vertax.Mesh.nb_faces": {"tf": 1}, "vertax.Mesh.nb_edges": {"tf": 1}, "vertax.Mesh.nb_half_edges": {"tf": 1}, "vertax.Mesh.nb_vertices": {"tf": 1}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.BoundedMesh.nb_angles": {"tf": 1}}, "df": 6}}}}}, "default_value": {"root": {"1": {"docs": {"vertax.FacePlot.MULTICOLOR": {"tf": 1}, "vertax.EdgePlot.BLACK": {"tf": 1}, "vertax.VertexPlot.BLACK": {"tf": 1}}, "df": 3}, "2": {"docs": {"vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}}, "df": 3}, "3": {"docs": {"vertax.FacePlot.AREA": {"tf": 1}, "vertax.EdgePlot.LENGTH": {"tf": 1}, "vertax.VertexPlot.INVISIBLE": {"tf": 1}}, "df": 3}, "4": {"docs": {"vertax.FacePlot.PERIMETER": {"tf": 1}, "vertax.EdgePlot.INVISIBLE": {"tf": 1}}, "df": 2}, "5": {"docs": {"vertax.FacePlot.WHITE": {"tf": 1}}, "df": 1}, "6": {"docs": {"vertax.FacePlot.FATES": {"tf": 1}}, "df": 1}, "docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1.4142135623730951}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1.4142135623730951}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1.4142135623730951}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1.4142135623730951}, "vertax.FacePlot.MULTICOLOR": {"tf": 1.4142135623730951}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1.4142135623730951}, "vertax.FacePlot.AREA": {"tf": 1.4142135623730951}, "vertax.FacePlot.PERIMETER": {"tf": 1.4142135623730951}, "vertax.FacePlot.WHITE": {"tf": 1.4142135623730951}, "vertax.FacePlot.FATES": {"tf": 1.4142135623730951}, "vertax.EdgePlot.BLACK": {"tf": 1.4142135623730951}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1.4142135623730951}, "vertax.EdgePlot.LENGTH": {"tf": 1.4142135623730951}, "vertax.EdgePlot.INVISIBLE": {"tf": 1.4142135623730951}, "vertax.VertexPlot.BLACK": {"tf": 1.4142135623730951}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1.4142135623730951}, "vertax.VertexPlot.INVISIBLE": {"tf": 1.4142135623730951}}, "df": 17, "l": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}, "vertax.FacePlot.MULTICOLOR": {"tf": 1}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.FacePlot.AREA": {"tf": 1}, "vertax.FacePlot.PERIMETER": {"tf": 1}, "vertax.FacePlot.WHITE": {"tf": 1}, "vertax.FacePlot.FATES": {"tf": 1}, "vertax.EdgePlot.BLACK": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.LENGTH": {"tf": 1}, "vertax.EdgePlot.INVISIBLE": {"tf": 1}, "vertax.VertexPlot.BLACK": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}, "vertax.VertexPlot.INVISIBLE": {"tf": 1}}, "df": 17}, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"vertax.EdgePlot.LENGTH": {"tf": 1}}, "df": 1}}}}}}, "b": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "d": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 4}}}}}}}}}}}}}}}}}}}}}}}}, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"vertax.EdgePlot.BLACK": {"tf": 1}, "vertax.VertexPlot.BLACK": {"tf": 1}}, "df": 2}}}}}, "a": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}}, "df": 1}}}}}}}}, "d": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}}, "df": 1, "j": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 1}}}}}}, "s": {"docs": {"vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 1}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {"vertax.FacePlot.AREA": {"tf": 1}}, "df": 1}}}}, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}}, "df": 2}}}}}}}}}}}}}}}, "x": {"2": {"7": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1.4142135623730951}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1.4142135623730951}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1.4142135623730951}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1.4142135623730951}}, "df": 4}, "docs": {}, "df": 0}, "docs": {}, "df": 0}, "g": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}, "vertax.FacePlot.MULTICOLOR": {"tf": 1}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.FacePlot.AREA": {"tf": 1}, "vertax.FacePlot.PERIMETER": {"tf": 1}, "vertax.FacePlot.WHITE": {"tf": 1}, "vertax.FacePlot.FATES": {"tf": 1}, "vertax.EdgePlot.BLACK": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.LENGTH": {"tf": 1}, "vertax.EdgePlot.INVISIBLE": {"tf": 1}, "vertax.VertexPlot.BLACK": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}, "vertax.VertexPlot.INVISIBLE": {"tf": 1}}, "df": 17}}, "e": {"docs": {}, "df": 0, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "m": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}}}}}}}}}}, "p": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}, "d": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}}, "df": 1, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"vertax.EdgePlot.BLACK": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.LENGTH": {"tf": 1}, "vertax.EdgePlot.INVISIBLE": {"tf": 1}}, "df": 4}}}}}}}}, "p": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}}}}}}}}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}}, "df": 3}}}}}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.FacePlot.PERIMETER": {"tf": 1}}, "df": 1}}}}}}}}}, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}}, "df": 1}}}}}}}, "d": {"docs": {"vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}}, "df": 1}, "n": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.EdgePlot.INVISIBLE": {"tf": 1}, "vertax.VertexPlot.INVISIBLE": {"tf": 1}}, "df": 2}}}}}}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 1}}}}}, "f": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"vertax.FacePlot.FACE_PARAMETER": {"tf": 1}}, "df": 1, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"vertax.FacePlot.MULTICOLOR": {"tf": 1}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.FacePlot.AREA": {"tf": 1}, "vertax.FacePlot.PERIMETER": {"tf": 1}, "vertax.FacePlot.WHITE": {"tf": 1}, "vertax.FacePlot.FATES": {"tf": 1}}, "df": 6}}}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.FacePlot.FATES": {"tf": 1}}, "df": 1}}}}}, "m": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"vertax.FacePlot.MULTICOLOR": {"tf": 1}}, "df": 1}}}}}}}}}}, "w": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.FacePlot.WHITE": {"tf": 1}}, "df": 1}}}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "x": {"docs": {"vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}}, "df": 1, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"vertax.VertexPlot.BLACK": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}, "vertax.VertexPlot.INVISIBLE": {"tf": 1}}, "df": 3}}}}}}}}}}}}, "signature": {"root": {"1": {"docs": {"vertax.plot_mesh": {"tf": 1.4142135623730951}, "vertax.get_plot_mesh": {"tf": 1.4142135623730951}}, "df": 2}, "2": {"0": {"docs": {"vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 2}, "docs": {"vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 4}, "3": {"9": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 3.7416573867739413}, "vertax.PbcMesh.load_mesh_txt": {"tf": 3.7416573867739413}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 4}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 4}, "vertax.plot_mesh": {"tf": 4}, "vertax.get_plot_mesh": {"tf": 3.7416573867739413}}, "df": 6}, "docs": {"vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 2}, "docs": {"vertax.Mesh.__init__": {"tf": 2}, "vertax.Mesh.save_mesh": {"tf": 4.47213595499958}, "vertax.Mesh.load_mesh": {"tf": 4.47213595499958}, "vertax.PbcMesh.__init__": {"tf": 2}, "vertax.PbcMesh.from_random_seeds": {"tf": 6.6332495807108}, "vertax.PbcMesh.from_seeds": {"tf": 6.324555320336759}, "vertax.PbcMesh.from_image": {"tf": 7.0710678118654755}, "vertax.PbcMesh.from_mask": {"tf": 7.0710678118654755}, "vertax.PbcMesh.create_empty": {"tf": 3.4641016151377544}, "vertax.PbcMesh.copy_mesh": {"tf": 4.47213595499958}, "vertax.PbcMesh.save_mesh": {"tf": 4.47213595499958}, "vertax.PbcMesh.load_mesh": {"tf": 4.47213595499958}, "vertax.PbcMesh.save_mesh_txt": {"tf": 11.874342087037917}, "vertax.PbcMesh.load_mesh_txt": {"tf": 11.874342087037917}, "vertax.PbcMesh.get_length": {"tf": 5.291502622129181}, "vertax.PbcMesh.get_length_with_offset": {"tf": 5.291502622129181}, "vertax.PbcMesh.get_perimeter": {"tf": 5.291502622129181}, "vertax.PbcMesh.get_area": {"tf": 5.291502622129181}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 3.4641016151377544}, "vertax.BoundedMesh.__init__": {"tf": 2}, "vertax.BoundedMesh.from_random_seeds": {"tf": 8}, "vertax.BoundedMesh.from_seeds": {"tf": 9.591663046625438}, "vertax.BoundedMesh.create_empty": {"tf": 3.4641016151377544}, "vertax.BoundedMesh.copy_mesh": {"tf": 4.47213595499958}, "vertax.BoundedMesh.save_mesh": {"tf": 4.47213595499958}, "vertax.BoundedMesh.load_mesh": {"tf": 4.47213595499958}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 12.569805089976535}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 12.569805089976535}, "vertax.BoundedMesh.get_length": {"tf": 5.291502622129181}, "vertax.BoundedMesh.get_perimeter": {"tf": 5.291502622129181}, "vertax.BoundedMesh.get_area": {"tf": 5.291502622129181}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 2}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 10.44030650891055}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 10.44030650891055}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 2}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 10.44030650891055}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 10.44030650891055}, "vertax.plot_mesh": {"tf": 20.92844953645635}, "vertax.get_plot_mesh": {"tf": 20.54263858417414}, "vertax.cost_v2v": {"tf": 14.38749456993816}, "vertax.cost_mesh2image": {"tf": 12.884098726725126}, "vertax.cost_areas": {"tf": 12.767145334803704}, "vertax.cost_IAS": {"tf": 14.491376746189438}, "vertax.cost_d_IAS": {"tf": 14.422205101855956}, "vertax.cost_tem_halfedge": {"tf": 14.422205101855956}, "vertax.cost_v2v_ias": {"tf": 14.422205101855956}, "vertax.cost_v2v_tem": {"tf": 14.422205101855956}, "vertax.cost_ratio": {"tf": 17.320508075688775}, "vertax.cost_checkerboard": {"tf": 16.278820596099706}, "vertax.energy_shape_factor_hetero": {"tf": 9.746794344808963}, "vertax.energy_shape_factor_homo": {"tf": 9.055385138137417}, "vertax.energy_line_tensions": {"tf": 9.746794344808963}, "vertax.energy_line_tensions_bounded": {"tf": 12.727922061357855}}, "df": 53, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "f": {"docs": {"vertax.Mesh.save_mesh": {"tf": 1}, "vertax.Mesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.PbcMesh.copy_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.PbcMesh.get_perimeter": {"tf": 1}, "vertax.PbcMesh.get_area": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.get_length": {"tf": 1}, "vertax.BoundedMesh.get_perimeter": {"tf": 1}, "vertax.BoundedMesh.get_area": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 32}, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.cost_v2v": {"tf": 1.7320508075688772}, "vertax.cost_mesh2image": {"tf": 1.7320508075688772}, "vertax.cost_areas": {"tf": 1.7320508075688772}, "vertax.cost_IAS": {"tf": 1.7320508075688772}, "vertax.cost_d_IAS": {"tf": 1.7320508075688772}, "vertax.cost_tem_halfedge": {"tf": 1.7320508075688772}, "vertax.cost_v2v_ias": {"tf": 1.7320508075688772}, "vertax.cost_v2v_tem": {"tf": 1.7320508075688772}, "vertax.cost_ratio": {"tf": 1.7320508075688772}, "vertax.cost_checkerboard": {"tf": 1.7320508075688772}, "vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1.7320508075688772}}, "df": 12}}}}}}, "e": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 4}}}}, "t": {"docs": {}, "df": 0, "r": {"docs": {"vertax.Mesh.save_mesh": {"tf": 1}, "vertax.Mesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 2.8284271247461903}, "vertax.PbcMesh.load_mesh_txt": {"tf": 2.8284271247461903}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 3}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 3}, "vertax.plot_mesh": {"tf": 2.8284271247461903}, "vertax.get_plot_mesh": {"tf": 2.6457513110645907}}, "df": 12}}, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 3}}}}}}, "e": {"docs": {"vertax.plot_mesh": {"tf": 1.7320508075688772}, "vertax.get_plot_mesh": {"tf": 1.7320508075688772}}, "df": 2}}}}, "h": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "w": {"docs": {"vertax.plot_mesh": {"tf": 1}}, "df": 1}}}, "a": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"vertax.plot_mesh": {"tf": 1.4142135623730951}}, "df": 1}}}, "p": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 2}}}}}, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "e": {"docs": {"vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 2}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"vertax.Mesh.save_mesh": {"tf": 1}, "vertax.Mesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.plot_mesh": {"tf": 1}}, "df": 7}}, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1.7320508075688772}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1.7320508075688772}, "vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1.4142135623730951}, "vertax.energy_line_tensions_bounded": {"tf": 1.7320508075688772}}, "df": 8}, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"vertax.plot_mesh": {"tf": 1.7320508075688772}, "vertax.get_plot_mesh": {"tf": 1.7320508075688772}}, "df": 2}}}}}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"vertax.plot_mesh": {"tf": 2.449489742783178}, "vertax.get_plot_mesh": {"tf": 2.449489742783178}}, "df": 2}}}, "b": {"docs": {}, "df": 0, "c": {"docs": {"vertax.plot_mesh": {"tf": 1}}, "df": 1}}, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.plot_mesh": {"tf": 1}}, "df": 1}}}, "n": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"vertax.Mesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 2.449489742783178}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 2.449489742783178}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 2.449489742783178}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 2.449489742783178}, "vertax.plot_mesh": {"tf": 2.6457513110645907}, "vertax.get_plot_mesh": {"tf": 2.449489742783178}, "vertax.cost_v2v": {"tf": 2.8284271247461903}, "vertax.cost_IAS": {"tf": 2.8284271247461903}, "vertax.cost_d_IAS": {"tf": 2.8284271247461903}, "vertax.cost_tem_halfedge": {"tf": 2.8284271247461903}, "vertax.cost_v2v_ias": {"tf": 2.8284271247461903}, "vertax.cost_v2v_tem": {"tf": 2.8284271247461903}, "vertax.cost_ratio": {"tf": 4.69041575982343}, "vertax.cost_checkerboard": {"tf": 4}, "vertax.energy_line_tensions_bounded": {"tf": 1.7320508075688772}}, "df": 21}}}, "b": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 3}, "u": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_mask": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}}, "df": 3}}}}, "d": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 3}}}}}}, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"vertax.plot_mesh": {"tf": 2.449489742783178}, "vertax.get_plot_mesh": {"tf": 2.449489742783178}}, "df": 2}}}}, "c": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "s": {"docs": {"vertax.Mesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 15}}, "o": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 3, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1.4142135623730951}}, "df": 4}}}}}}}, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "m": {"docs": {"vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 2}}}}}}}, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "p": {"docs": {"vertax.plot_mesh": {"tf": 1.7320508075688772}, "vertax.get_plot_mesh": {"tf": 1.7320508075688772}}, "df": 2}}}, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {"vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 2}}}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1.7320508075688772}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1.7320508075688772}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1.7320508075688772}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1.7320508075688772}, "vertax.cost_d_IAS": {"tf": 1}}, "df": 8}, "v": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 2}}}}}}}}, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.cost_v2v": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_areas": {"tf": 1}, "vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}, "vertax.cost_tem_halfedge": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}, "vertax.cost_ratio": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}}, "df": 11}}}}, "d": {"docs": {"vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.PbcMesh.get_perimeter": {"tf": 1}, "vertax.PbcMesh.get_area": {"tf": 1}, "vertax.BoundedMesh.get_length": {"tf": 1}, "vertax.BoundedMesh.get_perimeter": {"tf": 1}, "vertax.BoundedMesh.get_area": {"tf": 1}}, "df": 7}}, "w": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}, "vertax.cost_v2v": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_areas": {"tf": 1}, "vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}, "vertax.cost_tem_halfedge": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}, "vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}}, "df": 17}}}}}, "f": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.plot_mesh": {"tf": 2.8284271247461903}, "vertax.get_plot_mesh": {"tf": 2.8284271247461903}, "vertax.cost_v2v": {"tf": 1.4142135623730951}, "vertax.cost_mesh2image": {"tf": 1.4142135623730951}, "vertax.cost_areas": {"tf": 1.4142135623730951}, "vertax.cost_IAS": {"tf": 1.4142135623730951}, "vertax.cost_d_IAS": {"tf": 1.4142135623730951}, "vertax.cost_tem_halfedge": {"tf": 1.4142135623730951}, "vertax.cost_v2v_ias": {"tf": 1.4142135623730951}, "vertax.cost_v2v_tem": {"tf": 1.4142135623730951}, "vertax.energy_shape_factor_hetero": {"tf": 1.4142135623730951}, "vertax.energy_shape_factor_homo": {"tf": 1.4142135623730951}, "vertax.energy_line_tensions": {"tf": 1.4142135623730951}}, "df": 21}}}}, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 2.6457513110645907}, "vertax.PbcMesh.load_mesh_txt": {"tf": 2.6457513110645907}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 2.8284271247461903}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 2.8284271247461903}}, "df": 4}}}}}}, "g": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"vertax.get_plot_mesh": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.get_perimeter": {"tf": 1}, "vertax.PbcMesh.get_area": {"tf": 1}, "vertax.BoundedMesh.get_perimeter": {"tf": 1}, "vertax.BoundedMesh.get_area": {"tf": 1}, "vertax.plot_mesh": {"tf": 1.7320508075688772}, "vertax.get_plot_mesh": {"tf": 1.7320508075688772}, "vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 10, "s": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1.4142135623730951}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}, "vertax.cost_v2v": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_areas": {"tf": 1}, "vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}, "vertax.cost_tem_halfedge": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}, "vertax.cost_ratio": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}, "vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 22}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.cost_v2v": {"tf": 1.4142135623730951}, "vertax.cost_mesh2image": {"tf": 1.4142135623730951}, "vertax.cost_areas": {"tf": 1.4142135623730951}, "vertax.cost_IAS": {"tf": 1.4142135623730951}, "vertax.cost_d_IAS": {"tf": 1.4142135623730951}, "vertax.cost_tem_halfedge": {"tf": 1.4142135623730951}, "vertax.cost_v2v_ias": {"tf": 1.4142135623730951}, "vertax.cost_v2v_tem": {"tf": 1.4142135623730951}, "vertax.cost_ratio": {"tf": 1.4142135623730951}, "vertax.cost_checkerboard": {"tf": 1.4142135623730951}, "vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 18}}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 4}}}}}}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"vertax.plot_mesh": {"tf": 1.4142135623730951}, "vertax.get_plot_mesh": {"tf": 1.4142135623730951}}, "df": 2}}}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 2}}}, "l": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"vertax.plot_mesh": {"tf": 1}}, "df": 1}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.plot_mesh": {"tf": 1.7320508075688772}, "vertax.get_plot_mesh": {"tf": 1.7320508075688772}}, "df": 2}}}}}}, "h": {"docs": {}, "df": 0, "e": {"docs": {"vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 2, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.cost_v2v": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_areas": {"tf": 1}, "vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}, "vertax.cost_tem_halfedge": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}, "vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}}, "df": 15}}}}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.cost_v2v": {"tf": 1.4142135623730951}, "vertax.cost_mesh2image": {"tf": 1.4142135623730951}, "vertax.cost_areas": {"tf": 1.4142135623730951}, "vertax.cost_IAS": {"tf": 1.4142135623730951}, "vertax.cost_d_IAS": {"tf": 1.4142135623730951}, "vertax.cost_tem_halfedge": {"tf": 1.4142135623730951}, "vertax.cost_v2v_ias": {"tf": 1.4142135623730951}, "vertax.cost_v2v_tem": {"tf": 1.4142135623730951}, "vertax.cost_ratio": {"tf": 1.4142135623730951}, "vertax.cost_checkerboard": {"tf": 1.4142135623730951}, "vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 18}}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 4}}}}}}}}}}}, "s": {"docs": {"vertax.cost_v2v": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_areas": {"tf": 1}, "vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}, "vertax.cost_tem_halfedge": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}, "vertax.cost_ratio": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 11}}, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "f": {"docs": {"vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.BoundedMesh.get_length": {"tf": 1}}, "df": 3}}}}, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 3}}}}}}, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 3}}}, "j": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "x": {"docs": {"vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.get_length": {"tf": 1.4142135623730951}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1.4142135623730951}, "vertax.PbcMesh.get_perimeter": {"tf": 1.4142135623730951}, "vertax.PbcMesh.get_area": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.get_length": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.get_perimeter": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.get_area": {"tf": 1.4142135623730951}, "vertax.cost_v2v": {"tf": 3.3166247903554}, "vertax.cost_mesh2image": {"tf": 3.3166247903554}, "vertax.cost_areas": {"tf": 3.3166247903554}, "vertax.cost_IAS": {"tf": 3.3166247903554}, "vertax.cost_d_IAS": {"tf": 3.1622776601683795}, "vertax.cost_tem_halfedge": {"tf": 3.3166247903554}, "vertax.cost_v2v_ias": {"tf": 3.3166247903554}, "vertax.cost_v2v_tem": {"tf": 3.3166247903554}, "vertax.cost_ratio": {"tf": 3.605551275463989}, "vertax.cost_checkerboard": {"tf": 3.605551275463989}, "vertax.energy_shape_factor_hetero": {"tf": 2.449489742783178}, "vertax.energy_shape_factor_homo": {"tf": 2.23606797749979}, "vertax.energy_line_tensions": {"tf": 2.449489742783178}, "vertax.energy_line_tensions_bounded": {"tf": 3.3166247903554}}, "df": 22}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.get_length": {"tf": 1.4142135623730951}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1.4142135623730951}, "vertax.PbcMesh.get_perimeter": {"tf": 1.4142135623730951}, "vertax.PbcMesh.get_area": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.get_length": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.get_perimeter": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.get_area": {"tf": 1.4142135623730951}, "vertax.cost_v2v": {"tf": 3.3166247903554}, "vertax.cost_mesh2image": {"tf": 3.3166247903554}, "vertax.cost_areas": {"tf": 3.3166247903554}, "vertax.cost_IAS": {"tf": 3.3166247903554}, "vertax.cost_d_IAS": {"tf": 3.1622776601683795}, "vertax.cost_tem_halfedge": {"tf": 3.3166247903554}, "vertax.cost_v2v_ias": {"tf": 3.3166247903554}, "vertax.cost_v2v_tem": {"tf": 3.3166247903554}, "vertax.cost_ratio": {"tf": 3.605551275463989}, "vertax.cost_checkerboard": {"tf": 3.605551275463989}, "vertax.energy_shape_factor_hetero": {"tf": 2.449489742783178}, "vertax.energy_shape_factor_homo": {"tf": 2.23606797749979}, "vertax.energy_line_tensions": {"tf": 2.449489742783178}, "vertax.energy_line_tensions_bounded": {"tf": 3.3166247903554}}, "df": 22}}}}, "n": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 3}, "g": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 2}}}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.cost_ratio": {"tf": 1.4142135623730951}, "vertax.cost_checkerboard": {"tf": 1.4142135623730951}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 5}}}}}}}, "x": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.get_plot_mesh": {"tf": 1.7320508075688772}}, "df": 1}}}}, "t": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 3}}}}}, "x": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 2.6457513110645907}, "vertax.PbcMesh.load_mesh_txt": {"tf": 2.6457513110645907}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 2.8284271247461903}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 2.8284271247461903}}, "df": 4}}, "r": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"vertax.plot_mesh": {"tf": 1}}, "df": 1}}}, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 2}}}}, "u": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.plot_mesh": {"tf": 1.7320508075688772}, "vertax.get_plot_mesh": {"tf": 2}}, "df": 2}}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"vertax.cost_v2v": {"tf": 2}, "vertax.cost_mesh2image": {"tf": 2}, "vertax.cost_areas": {"tf": 2}, "vertax.cost_IAS": {"tf": 2}, "vertax.cost_d_IAS": {"tf": 2}, "vertax.cost_tem_halfedge": {"tf": 2}, "vertax.cost_v2v_ias": {"tf": 2}, "vertax.cost_v2v_tem": {"tf": 2}, "vertax.cost_ratio": {"tf": 2.23606797749979}, "vertax.cost_checkerboard": {"tf": 2.23606797749979}}, "df": 10}}}}}}, "d": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 3}}}}, "i": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 4}}}}}}}}}, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "k": {"docs": {"vertax.PbcMesh.from_mask": {"tf": 1}}, "df": 1}}, "t": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {"vertax.get_plot_mesh": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}}, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "h": {"docs": {"vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1.7320508075688772}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1.7320508075688772}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1.7320508075688772}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1.7320508075688772}, "vertax.plot_mesh": {"tf": 2}, "vertax.get_plot_mesh": {"tf": 1.7320508075688772}}, "df": 8, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.plot_mesh": {"tf": 2}, "vertax.get_plot_mesh": {"tf": 2}}, "df": 6}}}}}, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 2}}}}}}}}}}, "o": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}}, "df": 2}}}}, "n": {"docs": {"vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1.7320508075688772}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1.7320508075688772}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1.7320508075688772}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1.7320508075688772}}, "df": 4, "l": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1.7320508075688772}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1.7320508075688772}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1.7320508075688772}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1.7320508075688772}}, "df": 4}}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {"vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 1, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1.4142135623730951}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.plot_mesh": {"tf": 1.4142135623730951}, "vertax.get_plot_mesh": {"tf": 1.4142135623730951}}, "df": 10}}}}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.cost_v2v": {"tf": 1.4142135623730951}, "vertax.cost_mesh2image": {"tf": 1.4142135623730951}, "vertax.cost_areas": {"tf": 1.4142135623730951}, "vertax.cost_IAS": {"tf": 1.4142135623730951}, "vertax.cost_d_IAS": {"tf": 1.4142135623730951}, "vertax.cost_tem_halfedge": {"tf": 1.4142135623730951}, "vertax.cost_v2v_ias": {"tf": 1.4142135623730951}, "vertax.cost_v2v_tem": {"tf": 1.4142135623730951}, "vertax.cost_ratio": {"tf": 1.4142135623730951}, "vertax.cost_checkerboard": {"tf": 1.4142135623730951}, "vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 18}}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 4}}}}}}}}}}}, "a": {"docs": {}, "df": 0, "x": {"docs": {"vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.plot_mesh": {"tf": 2}, "vertax.get_plot_mesh": {"tf": 2}}, "df": 6}}, "e": {"docs": {}, "df": 0, "x": {"docs": {"vertax.plot_mesh": {"tf": 1.7320508075688772}, "vertax.get_plot_mesh": {"tf": 1.7320508075688772}}, "df": 2, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"vertax.plot_mesh": {"tf": 1.4142135623730951}, "vertax.get_plot_mesh": {"tf": 1.4142135623730951}}, "df": 2}}}}}}, "s": {"docs": {"vertax.cost_v2v": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_areas": {"tf": 1}, "vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}, "vertax.cost_tem_halfedge": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}, "vertax.cost_ratio": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 11}}}}}, "e": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.BoundedMesh.get_length": {"tf": 1}, "vertax.plot_mesh": {"tf": 1.7320508075688772}, "vertax.get_plot_mesh": {"tf": 1.7320508075688772}}, "df": 5, "s": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1.4142135623730951}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.plot_mesh": {"tf": 1.4142135623730951}, "vertax.get_plot_mesh": {"tf": 1.4142135623730951}}, "df": 10}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"vertax.plot_mesh": {"tf": 1.4142135623730951}, "vertax.get_plot_mesh": {"tf": 1.4142135623730951}}, "df": 2}}}}}}}}, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1.7320508075688772}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1.7320508075688772}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1.7320508075688772}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1.7320508075688772}}, "df": 4}}}, "t": {"docs": {"vertax.plot_mesh": {"tf": 1.7320508075688772}, "vertax.get_plot_mesh": {"tf": 1.7320508075688772}}, "df": 2}}, "g": {"docs": {}, "df": 0, "t": {"docs": {"vertax.plot_mesh": {"tf": 1.7320508075688772}, "vertax.get_plot_mesh": {"tf": 1.7320508075688772}}, "df": 2}}, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 2}}}}, "o": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {"vertax.plot_mesh": {"tf": 1.4142135623730951}}, "df": 1}}}}}}, "bases": {"root": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "x": {"docs": {"vertax.PbcMesh": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}, "vertax.PbcBilevelOptimizer": {"tf": 1}, "vertax.BoundedBilevelOptimizer": {"tf": 1}}, "df": 4}}}}}}, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "h": {"docs": {"vertax.PbcMesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh": {"tf": 1.4142135623730951}}, "df": 2, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcMesh": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}}, "df": 2}}}}}}, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "m": {"docs": {"vertax.BilevelOptimizationMethod": {"tf": 1.4142135623730951}, "vertax.FacePlot": {"tf": 1.4142135623730951}, "vertax.EdgePlot": {"tf": 1.4142135623730951}, "vertax.VertexPlot": {"tf": 1.4142135623730951}}, "df": 4}}}}, "b": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcBilevelOptimizer": {"tf": 1.4142135623730951}, "vertax.BoundedBilevelOptimizer": {"tf": 1.4142135623730951}}, "df": 2, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcBilevelOptimizer": {"tf": 1}, "vertax.BoundedBilevelOptimizer": {"tf": 1}}, "df": 2}}}}}}}}}}}}}}}}}}, "doc": {"root": {"0": {"1": {"docs": {"vertax.cost_v2v_tem": {"tf": 1}}, "df": 1}, "docs": {"vertax.Mesh.width": {"tf": 1}, "vertax.Mesh.height": {"tf": 1}, "vertax.PbcMesh": {"tf": 2}, "vertax.BoundedMesh": {"tf": 2.23606797749979}, "vertax.BoundedMesh.angles": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1.4142135623730951}, "vertax.cost_v2v_tem": {"tf": 1.4142135623730951}}, "df": 10}, "1": {"docs": {"vertax.PbcMesh": {"tf": 2}, "vertax.BoundedMesh": {"tf": 1.4142135623730951}, "vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}}, "df": 4, "d": {"docs": {"vertax.PbcMesh": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1.4142135623730951}}, "df": 2}, "}": {"docs": {}, "df": 0, "^": {"docs": {}, "df": 0, "{": {"docs": {}, "df": 0, "n": {"docs": {"vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}}, "df": 2}}}}}, "2": {"0": {"docs": {"vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}}, "df": 1}, "docs": {"vertax.PbcMesh": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh": {"tf": 2.449489742783178}, "vertax.BoundedMesh.angles": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.cost_IAS": {"tf": 1.4142135623730951}, "vertax.cost_d_IAS": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}}, "df": 9, "d": {"docs": {"vertax.PbcMesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh": {"tf": 1.4142135623730951}}, "df": 2}}, "3": {"docs": {"vertax": {"tf": 1}}, "df": 1}, "4": {"docs": {"vertax.cost_v2v_ias": {"tf": 1}}, "df": 1}, "6": {"docs": {"vertax.cost_v2v_ias": {"tf": 1}}, "df": 1}, "8": {"docs": {"vertax.PbcMesh": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}}, "df": 2}, "9": {"9": {"docs": {"vertax.cost_v2v_tem": {"tf": 1}}, "df": 1}, "docs": {}, "df": 0}, "docs": {"vertax": {"tf": 10.14889156509222}, "vertax.Mesh": {"tf": 3.1622776601683795}, "vertax.Mesh.__init__": {"tf": 3.605551275463989}, "vertax.Mesh.faces": {"tf": 1.7320508075688772}, "vertax.Mesh.edges": {"tf": 1.7320508075688772}, "vertax.Mesh.vertices": {"tf": 1.7320508075688772}, "vertax.Mesh.width": {"tf": 1.7320508075688772}, "vertax.Mesh.height": {"tf": 1.7320508075688772}, "vertax.Mesh.faces_params": {"tf": 1.7320508075688772}, "vertax.Mesh.edges_params": {"tf": 1.7320508075688772}, "vertax.Mesh.vertices_params": {"tf": 1.7320508075688772}, "vertax.Mesh.nb_faces": {"tf": 1.7320508075688772}, "vertax.Mesh.nb_edges": {"tf": 1.7320508075688772}, "vertax.Mesh.nb_half_edges": {"tf": 1.7320508075688772}, "vertax.Mesh.nb_vertices": {"tf": 1.7320508075688772}, "vertax.Mesh.save_mesh": {"tf": 4.242640687119285}, "vertax.Mesh.load_mesh": {"tf": 4.898979485566356}, "vertax.PbcMesh": {"tf": 6.708203932499369}, "vertax.PbcMesh.__init__": {"tf": 5.5677643628300215}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 2}, "vertax.PbcMesh.from_random_seeds": {"tf": 6.48074069840786}, "vertax.PbcMesh.from_seeds": {"tf": 5.477225575051661}, "vertax.PbcMesh.from_image": {"tf": 5.477225575051661}, "vertax.PbcMesh.from_mask": {"tf": 5.196152422706632}, "vertax.PbcMesh.create_empty": {"tf": 1.7320508075688772}, "vertax.PbcMesh.copy_mesh": {"tf": 1.7320508075688772}, "vertax.PbcMesh.save_mesh": {"tf": 4.242640687119285}, "vertax.PbcMesh.load_mesh": {"tf": 5.196152422706632}, "vertax.PbcMesh.save_mesh_txt": {"tf": 7.745966692414834}, "vertax.PbcMesh.load_mesh_txt": {"tf": 8.306623862918075}, "vertax.PbcMesh.get_length": {"tf": 1.7320508075688772}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1.7320508075688772}, "vertax.PbcMesh.get_perimeter": {"tf": 1.7320508075688772}, "vertax.PbcMesh.get_area": {"tf": 1.7320508075688772}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 2.8284271247461903}, "vertax.BoundedMesh": {"tf": 7.416198487095663}, "vertax.BoundedMesh.__init__": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.angles": {"tf": 2}, "vertax.BoundedMesh.nb_angles": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.from_random_seeds": {"tf": 6.928203230275509}, "vertax.BoundedMesh.from_seeds": {"tf": 6.48074069840786}, "vertax.BoundedMesh.create_empty": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.copy_mesh": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.save_mesh": {"tf": 4.242640687119285}, "vertax.BoundedMesh.load_mesh": {"tf": 5.196152422706632}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 8.12403840463596}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 8.660254037844387}, "vertax.BoundedMesh.get_length": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.get_perimeter": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.get_area": {"tf": 1.7320508075688772}, "vertax.BilevelOptimizationMethod": {"tf": 1.7320508075688772}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 2.23606797749979}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 2}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1.7320508075688772}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1.7320508075688772}, "vertax.PbcBilevelOptimizer": {"tf": 2.23606797749979}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 2.449489742783178}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1.7320508075688772}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1.7320508075688772}, "vertax.BoundedBilevelOptimizer": {"tf": 2.23606797749979}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 2.449489742783178}, "vertax.BoundedBilevelOptimizer.angles_target": {"tf": 1.7320508075688772}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 2.449489742783178}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 2.449489742783178}, "vertax.plot_mesh": {"tf": 1.7320508075688772}, "vertax.get_plot_mesh": {"tf": 1.7320508075688772}, "vertax.FacePlot": {"tf": 1.7320508075688772}, "vertax.FacePlot.MULTICOLOR": {"tf": 1.7320508075688772}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1.7320508075688772}, "vertax.FacePlot.AREA": {"tf": 1.7320508075688772}, "vertax.FacePlot.PERIMETER": {"tf": 1.7320508075688772}, "vertax.FacePlot.WHITE": {"tf": 1.7320508075688772}, "vertax.FacePlot.FATES": {"tf": 1.7320508075688772}, "vertax.EdgePlot": {"tf": 1.7320508075688772}, "vertax.EdgePlot.BLACK": {"tf": 1.7320508075688772}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1.7320508075688772}, "vertax.EdgePlot.LENGTH": {"tf": 1.7320508075688772}, "vertax.EdgePlot.INVISIBLE": {"tf": 1.7320508075688772}, "vertax.VertexPlot": {"tf": 1.7320508075688772}, "vertax.VertexPlot.BLACK": {"tf": 1.7320508075688772}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1.7320508075688772}, "vertax.VertexPlot.INVISIBLE": {"tf": 1.7320508075688772}, "vertax.cost_v2v": {"tf": 2.23606797749979}, "vertax.cost_mesh2image": {"tf": 2.23606797749979}, "vertax.cost_areas": {"tf": 2.449489742783178}, "vertax.cost_IAS": {"tf": 2.23606797749979}, "vertax.cost_d_IAS": {"tf": 2.449489742783178}, "vertax.cost_tem_halfedge": {"tf": 1.7320508075688772}, "vertax.cost_v2v_ias": {"tf": 1.7320508075688772}, "vertax.cost_v2v_tem": {"tf": 1.7320508075688772}, "vertax.cost_ratio": {"tf": 2.23606797749979}, "vertax.cost_checkerboard": {"tf": 2.8284271247461903}, "vertax.energy_shape_factor_hetero": {"tf": 1.7320508075688772}, "vertax.energy_shape_factor_homo": {"tf": 1.7320508075688772}, "vertax.energy_line_tensions": {"tf": 1.7320508075688772}, "vertax.energy_line_tensions_bounded": {"tf": 1.7320508075688772}}, "df": 96, "a": {"docs": {"vertax": {"tf": 3.605551275463989}, "vertax.Mesh.__init__": {"tf": 1}, "vertax.Mesh.width": {"tf": 1}, "vertax.Mesh.height": {"tf": 1}, "vertax.Mesh.save_mesh": {"tf": 1}, "vertax.Mesh.load_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh": {"tf": 2.8284271247461903}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_seeds": {"tf": 1.7320508075688772}, "vertax.PbcMesh.from_image": {"tf": 2}, "vertax.PbcMesh.from_mask": {"tf": 1.7320508075688772}, "vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.load_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}, "vertax.BoundedMesh": {"tf": 2.449489742783178}, "vertax.BoundedMesh.from_random_seeds": {"tf": 2}, "vertax.BoundedMesh.from_seeds": {"tf": 2.23606797749979}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.get_perimeter": {"tf": 1}, "vertax.BoundedMesh.get_area": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}, "vertax.FacePlot": {"tf": 1}, "vertax.FacePlot.MULTICOLOR": {"tf": 1}, "vertax.VertexPlot": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1.4142135623730951}, "vertax.cost_ratio": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1.4142135623730951}}, "df": 37, "n": {"docs": {"vertax": {"tf": 1}, "vertax.Mesh": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}, "vertax.BoundedMesh": {"tf": 2}, "vertax.BoundedMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.get_length": {"tf": 1}, "vertax.EdgePlot": {"tf": 1}}, "df": 10, "d": {"docs": {"vertax": {"tf": 3.4641016151377544}, "vertax.Mesh": {"tf": 1.4142135623730951}, "vertax.Mesh.__init__": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1.4142135623730951}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.angles": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}, "vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1.4142135623730951}, "vertax.cost_v2v_ias": {"tf": 1.4142135623730951}, "vertax.cost_v2v_tem": {"tf": 1.4142135623730951}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 22, "/": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"vertax.plot_mesh": {"tf": 1}}, "df": 1}}}}, "o": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}}, "df": 2}}}}}, "y": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.FacePlot.FATES": {"tf": 1}}, "df": 4}, "g": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.BoundedMesh.angles": {"tf": 1}}, "df": 1, "s": {"docs": {"vertax.BoundedMesh": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.nb_angles": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1.7320508075688772}}, "df": 4}}}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 2}}}}}}}, "m": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}, "p": {"docs": {"vertax": {"tf": 2.449489742783178}}, "df": 1}}, "d": {"docs": {}, "df": 0, "d": {"docs": {"vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 2, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}, "s": {"docs": {"vertax": {"tf": 2.23606797749979}, "vertax.PbcMesh.__init__": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 5, "s": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 2}}}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 2.8284271247461903}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.FacePlot.WHITE": {"tf": 1}, "vertax.FacePlot.FATES": {"tf": 1}, "vertax.EdgePlot.BLACK": {"tf": 1}, "vertax.VertexPlot.BLACK": {"tf": 1}}, "df": 9, "a": {"docs": {"vertax.PbcMesh.get_area": {"tf": 1}, "vertax.BoundedMesh.get_perimeter": {"tf": 1}, "vertax.BoundedMesh.get_area": {"tf": 1}, "vertax.FacePlot.AREA": {"tf": 1}}, "df": 4, "s": {"docs": {"vertax": {"tf": 1}, "vertax.cost_areas": {"tf": 1.7320508075688772}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 4}}}, "g": {"docs": {}, "df": 0, "\\": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}}}}, "u": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"vertax.Mesh.save_mesh": {"tf": 1}, "vertax.Mesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 20}}}}}}}, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh": {"tf": 1.7320508075688772}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.BoundedMesh": {"tf": 2}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 5, "[": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"3": {"2": {"docs": {"vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 2}, "docs": {}, "df": 0}, "docs": {}, "df": 0}}}}}}}}}, "c": {"docs": {"vertax.BoundedMesh": {"tf": 1}}, "df": 1, "s": {"docs": {"vertax.BoundedMesh": {"tf": 1}}, "df": 1}, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.BoundedMesh.angles": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}}, "df": 2}}}}, "l": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "o": {"docs": {"vertax": {"tf": 1}}, "df": 1}}, "l": {"docs": {"vertax.Mesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.FacePlot.WHITE": {"tf": 1}, "vertax.EdgePlot.BLACK": {"tf": 1}}, "df": 9, "o": {"docs": {}, "df": 0, "w": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}}, "df": 1}}}}, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.cost_ratio": {"tf": 1.4142135623730951}}, "df": 2}}}}, "b": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"vertax": {"tf": 1}, "vertax.Mesh": {"tf": 1}}, "df": 2}}}}}}}, "t": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.Mesh": {"tf": 1}, "vertax.Mesh.__init__": {"tf": 1}}, "df": 2}}}}}}}, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}}, "df": 3}}}}}}}, "c": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}}, "df": 2}}, "g": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {"vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 1}}}}, "f": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 1}}}}, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 1}}}}}}}}}}}}, "x": {"docs": {"vertax.get_plot_mesh": {"tf": 1}}, "df": 1, "i": {"docs": {}, "df": 0, "s": {"docs": {"vertax.cost_ratio": {"tf": 1.4142135623730951}}, "df": 1}}}, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}}, "df": 2}}}}, "v": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {"vertax.cost_checkerboard": {"tf": 1}}, "df": 1}}}}}, "d": {"docs": {"vertax.cost_d_IAS": {"tf": 1}}, "df": 1, "i": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}}, "df": 3, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}, "vertax.cost_IAS": {"tf": 1}}, "df": 2}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 2}}}}}}}}}}}}, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"vertax.Mesh": {"tf": 1}, "vertax.PbcMesh.__init__": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}}, "df": 3}}, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax.Mesh.width": {"tf": 1}, "vertax.Mesh.height": {"tf": 1}}, "df": 2}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1.4142135623730951}}, "df": 4}}}}}}}, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}}, "df": 3}}}}}, "s": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.cost_d_IAS": {"tf": 1}}, "df": 1}}}}}}}, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "n": {"docs": {"vertax": {"tf": 1.7320508075688772}}, "df": 1, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}}}}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}}}}, "f": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1, "d": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}, "s": {"docs": {"vertax": {"tf": 1}, "vertax.Mesh": {"tf": 1}}, "df": 2}}}}, "a": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}}, "df": 4, "s": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 2.6457513110645907}, "vertax.PbcMesh.load_mesh_txt": {"tf": 2.6457513110645907}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 2.8284271247461903}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 2.8284271247461903}}, "df": 4}}}}}}, "n": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.PbcMesh": {"tf": 1.4142135623730951}, "vertax.FacePlot.FATES": {"tf": 1}}, "df": 2}}}, "s": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.FacePlot.AREA": {"tf": 1}, "vertax.FacePlot.PERIMETER": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.LENGTH": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}, "vertax.energy_shape_factor_hetero": {"tf": 1}}, "df": 8}}}}}, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.PbcMesh.__init__": {"tf": 1}}, "df": 1}}}}}}}, "c": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {"vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.plot_mesh": {"tf": 1}}, "df": 3}}}}}, "y": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.Mesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}}, "df": 6}}}, "o": {"docs": {"vertax": {"tf": 1}, "vertax.Mesh.__init__": {"tf": 1.4142135623730951}, "vertax.PbcMesh.__init__": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.BoundedMesh.__init__": {"tf": 1}, "vertax.EdgePlot.INVISIBLE": {"tf": 1}, "vertax.VertexPlot.INVISIBLE": {"tf": 1}}, "df": 7, "c": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}}}}, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.create_empty": {"tf": 1}}, "df": 2}}}, "n": {"docs": {"vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 1}, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}}, "df": 2}}}, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {"vertax.Mesh.__init__": {"tf": 1}}, "df": 1}}}, "u": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"vertax.cost_tem_halfedge": {"tf": 1}}, "df": 1}}}}, "j": {"docs": {"vertax.cost_IAS": {"tf": 1.4142135623730951}, "vertax.cost_d_IAS": {"tf": 1.4142135623730951}}, "df": 2, "a": {"docs": {}, "df": 0, "x": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}}, "df": 4}, "c": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "d": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {"vertax.FacePlot.WHITE": {"tf": 1}}, "df": 1}}}, "v": {"docs": {}, "df": 0, "p": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}}, "df": 2}}}, "b": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}, "vertax.Mesh": {"tf": 1}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}}, "df": 3, "d": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}}, "df": 4}}, "i": {"docs": {}, "df": 0, "c": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}}}}}}}, "i": {"docs": {"vertax.BilevelOptimizationMethod": {"tf": 1}, "vertax.PbcBilevelOptimizer": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}}, "df": 5, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "y": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}, "p": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}}}}}}}}, "u": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}}}, "t": {"docs": {"vertax": {"tf": 1.7320508075688772}, "vertax.Mesh.__init__": {"tf": 1.4142135623730951}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 3}, "l": {"docs": {}, "df": 0, "k": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}, "y": {"docs": {"vertax": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 9}, "e": {"docs": {"vertax.Mesh": {"tf": 1}, "vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_image": {"tf": 1.4142135623730951}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 13, "t": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {"vertax": {"tf": 1}, "vertax.Mesh": {"tf": 1}, "vertax.Mesh.edges": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.angles": {"tf": 1}}, "df": 5}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}}, "df": 2}}}}, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "{": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.PbcMesh": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}}, "df": 2}}}}}}}}, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"vertax": {"tf": 1}, "vertax.PbcMesh": {"tf": 1.7320508075688772}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}, "vertax.PbcBilevelOptimizer": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}}, "df": 10}, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedBilevelOptimizer": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 6, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "h": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.Mesh": {"tf": 1}, "vertax.Mesh.__init__": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedBilevelOptimizer": {"tf": 1}, "vertax.cost_ratio": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}}, "df": 9}}}}, "b": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}}}}}}}}}}}}}}, "x": {"docs": {"vertax.PbcMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}}, "df": 3}}, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {"vertax.EdgePlot.BLACK": {"tf": 1}, "vertax.VertexPlot.BLACK": {"tf": 1}}, "df": 2}}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 1}}}}}}}}}}}, "f": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "k": {"docs": {"vertax": {"tf": 1.7320508075688772}}, "df": 1}}}}}}}, "e": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.angles": {"tf": 1}, "vertax.BoundedMesh.nb_angles": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 6}}, "o": {"docs": {}, "df": 0, "m": {"docs": {"vertax.Mesh.load_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.__init__": {"tf": 2}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 15}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {"vertax": {"tf": 3.7416573867739413}, "vertax.PbcMesh": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 2.6457513110645907}, "vertax.PbcMesh.load_mesh_txt": {"tf": 2.6457513110645907}, "vertax.BoundedMesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 2.8284271247461903}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 2.8284271247461903}, "vertax.PbcBilevelOptimizer": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 19, "w": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "d": {"docs": {"vertax": {"tf": 1}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}}, "df": 2}}}}, "m": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}}, "df": 2}}}, "c": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.update_boundary_conditions": {"tf": 1}, "vertax.cost_IAS": {"tf": 1}}, "df": 2}}}, "l": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.get_perimeter": {"tf": 1}, "vertax.BoundedMesh.get_area": {"tf": 1}, "vertax.FacePlot": {"tf": 1}, "vertax.FacePlot.MULTICOLOR": {"tf": 1}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.FacePlot.AREA": {"tf": 1}, "vertax.FacePlot.PERIMETER": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 14, "s": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.nb_faces": {"tf": 1}, "vertax.PbcMesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.save_mesh_txt": {"tf": 2.23606797749979}, "vertax.PbcMesh.load_mesh_txt": {"tf": 2.23606797749979}, "vertax.PbcMesh.get_perimeter": {"tf": 1}, "vertax.PbcMesh.get_area": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 2.23606797749979}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 2.23606797749979}, "vertax.FacePlot.WHITE": {"tf": 1}, "vertax.FacePlot.FATES": {"tf": 1}}, "df": 13}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 4}}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 4}}}}}}}}}}}}, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 2, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.FacePlot.FATES": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}}, "df": 4, "s": {"docs": {"vertax.BoundedMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.cost_checkerboard": {"tf": 1}}, "df": 3}, "d": {"docs": {"vertax.cost_checkerboard": {"tf": 1}}, "df": 1}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}, "a": {"docs": {}, "df": 0, "l": {"docs": {"vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 1, "l": {"docs": {}, "df": 0, "y": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}, "r": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.Mesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.Mesh.load_mesh": {"tf": 1.7320508075688772}, "vertax.PbcMesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.load_mesh": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.load_mesh": {"tf": 1.7320508075688772}}, "df": 6, "s": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1.4142135623730951}}, "df": 4}, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 3.7416573867739413}, "vertax.PbcMesh.load_mesh_txt": {"tf": 3.7416573867739413}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 4}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 4}}, "df": 4}}}}}, "l": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}}, "df": 1}}, "g": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"vertax.get_plot_mesh": {"tf": 1}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 8, "s": {"docs": {"vertax": {"tf": 2.449489742783178}, "vertax.Mesh": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}}, "df": 3}}}}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_seeds": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1.7320508075688772}}, "df": 4, "s": {"docs": {"vertax.PbcMesh": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1.4142135623730951}}, "df": 2}}}}}}, "v": {"2": {"docs": {}, "df": 0, "v": {"docs": {"vertax.cost_v2v_ias": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}}, "df": 2}}, "docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "x": {"docs": {"vertax": {"tf": 2}, "vertax.PbcMesh": {"tf": 2}, "vertax.BoundedMesh": {"tf": 2}, "vertax.VertexPlot": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}, "vertax.cost_v2v": {"tf": 1.4142135623730951}}, "df": 6}}, "a": {"docs": {}, "df": 0, "x": {"docs": {"vertax": {"tf": 2.8284271247461903}}, "df": 1}}, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.Mesh.vertices": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}, "vertax.Mesh.nb_vertices": {"tf": 1}, "vertax.PbcMesh": {"tf": 1.7320508075688772}, "vertax.PbcMesh.save_mesh_txt": {"tf": 2.23606797749979}, "vertax.PbcMesh.load_mesh_txt": {"tf": 2.23606797749979}, "vertax.BoundedMesh": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 2.23606797749979}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 2.23606797749979}, "vertax.VertexPlot.BLACK": {"tf": 1}, "vertax.VertexPlot.INVISIBLE": {"tf": 1}, "vertax.cost_v2v": {"tf": 1.4142135623730951}, "vertax.cost_mesh2image": {"tf": 1}}, "df": 14}}}}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 4}}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 4}}}}}}}}}}}}, "s": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {"vertax.cost_areas": {"tf": 1}}, "df": 1}}}}}, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}}, "df": 1}}}, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 2}}}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "i": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}}, "df": 3}}}}}}, "i": {"docs": {}, "df": 0, "a": {"docs": {"vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 4}}, "s": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}, "j": {"docs": {}, "df": 0, "p": {"docs": {"vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 1}}}, "m": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}}, "df": 1, "l": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}}}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}}, "df": 2}}, "s": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.update_boundary_conditions": {"tf": 1}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 2}}}, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"vertax": {"tf": 2}}, "df": 1}}, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}, "s": {"docs": {}, "df": 0, "h": {"docs": {"vertax": {"tf": 1.7320508075688772}, "vertax.Mesh": {"tf": 1}, "vertax.Mesh.vertices": {"tf": 1}, "vertax.Mesh.width": {"tf": 1}, "vertax.Mesh.height": {"tf": 1}, "vertax.Mesh.nb_faces": {"tf": 1}, "vertax.Mesh.nb_edges": {"tf": 1}, "vertax.Mesh.nb_half_edges": {"tf": 1}, "vertax.Mesh.nb_vertices": {"tf": 1}, "vertax.Mesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.Mesh.load_mesh": {"tf": 1.7320508075688772}, "vertax.PbcMesh": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1.7320508075688772}, "vertax.PbcMesh.from_mask": {"tf": 1.7320508075688772}, "vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.PbcMesh.copy_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 2}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.nb_angles": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 2}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1.4142135623730951}, "vertax.plot_mesh": {"tf": 1.4142135623730951}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_areas": {"tf": 1.4142135623730951}}, "df": 35, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1.7320508075688772}, "vertax.Mesh.__init__": {"tf": 1}, "vertax.PbcBilevelOptimizer": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 8}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"vertax": {"tf": 1}, "vertax.Mesh.vertices": {"tf": 1}}, "df": 2}}, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "d": {"docs": {"vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BilevelOptimizationMethod": {"tf": 1}}, "df": 2, "s": {"docs": {"vertax": {"tf": 1}, "vertax.Mesh.__init__": {"tf": 1}, "vertax.PbcMesh.__init__": {"tf": 1}}, "df": 3}}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "m": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}}, "df": 2}}}}}}}, "g": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 1}}}, "s": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.cost_d_IAS": {"tf": 1}}, "df": 1}}}}}}}}, "x": {"docs": {"vertax.cost_v2v_ias": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}}, "df": 2}}, "a": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 4}}, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "{": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "}": {"docs": {}, "df": 0, "\\": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "t": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}, "e": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}, "c": {"docs": {}, "df": 0, "h": {"docs": {"vertax": {"tf": 1}}, "df": 1}}, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {"vertax.get_plot_mesh": {"tf": 1}}, "df": 1}}}}}}}}, "k": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}}, "df": 1}}, "s": {"docs": {}, "df": 0, "k": {"docs": {"vertax.PbcMesh.__init__": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1.7320508075688772}, "vertax.PbcMesh.from_mask": {"tf": 2}}, "df": 3}}, "x": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "m": {"docs": {"vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}}, "df": 1}}}}}, "r": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.FacePlot.FATES": {"tf": 1}}, "df": 3}}}}}, "u": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 3}}}}, "i": {"docs": {"vertax": {"tf": 1}, "vertax.cost_IAS": {"tf": 1.4142135623730951}, "vertax.cost_d_IAS": {"tf": 1.4142135623730951}}, "df": 3, "n": {"docs": {"vertax": {"tf": 2.8284271247461903}, "vertax.Mesh.width": {"tf": 1.4142135623730951}, "vertax.Mesh.height": {"tf": 1.4142135623730951}, "vertax.PbcMesh": {"tf": 1}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.BilevelOptimizationMethod": {"tf": 1}}, "df": 16, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1.7320508075688772}}, "df": 1}}}}}, "t": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}}, "df": 3, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}, "f": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}, "vertax.Mesh.edges": {"tf": 1}}, "df": 2, "s": {"docs": {"vertax": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.angles": {"tf": 1}, "vertax.BoundedMesh.nb_angles": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}}, "df": 5}}}}}}, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcMesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh": {"tf": 1.4142135623730951}}, "df": 2}}}}}}, "f": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax": {"tf": 1}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 6}}}, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {"vertax.BoundedMesh": {"tf": 1.4142135623730951}}, "df": 1}}}, "p": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.cost_tem_halfedge": {"tf": 1}}, "df": 1}}}}}}, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "x": {"docs": {"vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}}, "df": 2}}}}, "s": {"docs": {"vertax": {"tf": 2.449489742783178}, "vertax.Mesh": {"tf": 1}, "vertax.Mesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh": {"tf": 1.7320508075688772}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh": {"tf": 2.8284271247461903}, "vertax.BoundedMesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.FacePlot": {"tf": 1}, "vertax.EdgePlot": {"tf": 1}, "vertax.VertexPlot": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 17}, "t": {"docs": {"vertax": {"tf": 1.7320508075688772}, "vertax.Mesh": {"tf": 1.4142135623730951}, "vertax.Mesh.__init__": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1.4142135623730951}, "vertax.FacePlot": {"tf": 1}, "vertax.EdgePlot": {"tf": 1}, "vertax.VertexPlot": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 9, "s": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.FacePlot.AREA": {"tf": 1}, "vertax.FacePlot.PERIMETER": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.LENGTH": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}}, "df": 9}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}}, "df": 1}}}}}}}}}, "f": {"docs": {"vertax": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh": {"tf": 2}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.create_empty": {"tf": 1}, "vertax.FacePlot.FATES": {"tf": 1}}, "df": 8}, "e": {"docs": {"vertax.Mesh.nb_half_edges": {"tf": 1}}, "df": 1}, "d": {"docs": {"vertax.PbcMesh": {"tf": 2.6457513110645907}, "vertax.BoundedMesh": {"tf": 3}}, "df": 2}, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.__init__": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1.7320508075688772}, "vertax.cost_mesh2image": {"tf": 1.4142135623730951}}, "df": 3}}}, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}}, "df": 1}}}}}}, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 2}}}}, "e": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 2}}}}}}}}}}, "l": {"docs": {}, "df": 0, "l": {"docs": {"vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 2}}, "a": {"docs": {}, "df": 0, "s": {"docs": {"vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1}}, "df": 3}}}, "o": {"docs": {}, "df": 0, "f": {"docs": {"vertax": {"tf": 2.8284271247461903}, "vertax.Mesh.faces": {"tf": 1}, "vertax.Mesh.width": {"tf": 1}, "vertax.Mesh.height": {"tf": 1}, "vertax.Mesh.nb_faces": {"tf": 1.4142135623730951}, "vertax.Mesh.nb_edges": {"tf": 1.4142135623730951}, "vertax.Mesh.nb_half_edges": {"tf": 1.7320508075688772}, "vertax.Mesh.nb_vertices": {"tf": 1.4142135623730951}, "vertax.PbcMesh": {"tf": 3.7416573867739413}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1.7320508075688772}, "vertax.PbcMesh.from_seeds": {"tf": 2.23606797749979}, "vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.PbcMesh.get_perimeter": {"tf": 1}, "vertax.PbcMesh.get_area": {"tf": 1}, "vertax.BoundedMesh": {"tf": 4.358898943540674}, "vertax.BoundedMesh.nb_angles": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_random_seeds": {"tf": 2}, "vertax.BoundedMesh.from_seeds": {"tf": 2.449489742783178}, "vertax.BoundedMesh.get_length": {"tf": 1}, "vertax.BoundedMesh.get_perimeter": {"tf": 1}, "vertax.BoundedMesh.get_area": {"tf": 1}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.cost_v2v": {"tf": 1}, "vertax.cost_areas": {"tf": 1.4142135623730951}, "vertax.cost_v2v_ias": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}}, "df": 32, "f": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1.4142135623730951}}, "df": 2, "s": {"docs": {"vertax.PbcMesh.get_length_with_offset": {"tf": 1}}, "df": 1}}}}}}, "n": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.PbcMesh": {"tf": 1.7320508075688772}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}, "vertax.FacePlot": {"tf": 1}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.FacePlot.AREA": {"tf": 1}, "vertax.FacePlot.PERIMETER": {"tf": 1}, "vertax.FacePlot.FATES": {"tf": 1}, "vertax.EdgePlot": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.LENGTH": {"tf": 1}, "vertax.VertexPlot": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}, "vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 20, "l": {"docs": {}, "df": 0, "y": {"docs": {"vertax": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 6}}, "e": {"docs": {"vertax.get_plot_mesh": {"tf": 1}, "vertax.cost_ratio": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 3, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}, "r": {"docs": {"vertax": {"tf": 2}, "vertax.PbcMesh": {"tf": 1.4142135623730951}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}, "vertax.plot_mesh": {"tf": 1}}, "df": 4, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"vertax.cost_ratio": {"tf": 1}}, "df": 1}}}}}}}}}, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 5, "w": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}, "b": {"docs": {}, "df": 0, "j": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcBilevelOptimizer": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}}, "df": 4, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}, "d": {"docs": {"vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}}, "df": 3}}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax": {"tf": 1.7320508075688772}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.BilevelOptimizationMethod": {"tf": 1.4142135623730951}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}}, "df": 4, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {"vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 2}}}}}}, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 2.6457513110645907}, "vertax.PbcMesh.load_mesh_txt": {"tf": 2.6457513110645907}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 2.8284271247461903}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 2.8284271247461903}}, "df": 4}}}}}}}, "w": {"docs": {}, "df": 0, "n": {"docs": {"vertax": {"tf": 1.7320508075688772}}, "df": 1}}, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}}, "df": 5}}, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {"vertax.BoundedMesh": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "r": {"docs": {"vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 1}}}, "e": {"1": {"docs": {"vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 2}, "2": {"docs": {"vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 2}, "docs": {"vertax": {"tf": 1}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 3, "p": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"vertax": {"tf": 2}}, "df": 1}}}}}}}}}, "d": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh": {"tf": 2.23606797749979}, "vertax.BoundedMesh": {"tf": 3.605551275463989}, "vertax.BoundedMesh.get_length": {"tf": 1}, "vertax.EdgePlot": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.LENGTH": {"tf": 1}, "vertax.cost_tem_halfedge": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 9, "s": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.Mesh.edges": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.nb_edges": {"tf": 1}, "vertax.Mesh.nb_half_edges": {"tf": 1.4142135623730951}, "vertax.PbcMesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 2.449489742783178}, "vertax.PbcMesh.load_mesh_txt": {"tf": 2.449489742783178}, "vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 2.23606797749979}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 2.449489742783178}, "vertax.EdgePlot.BLACK": {"tf": 1}, "vertax.EdgePlot.INVISIBLE": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}}, "df": 19}}}}, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "m": {"docs": {"vertax": {"tf": 1.7320508075688772}}, "df": 1}}, "a": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}}}}}}}}}, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "y": {"docs": {"vertax": {"tf": 2}, "vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 5}}}}, "d": {"docs": {}, "df": 0, "{": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}}}, "x": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax.Mesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh": {"tf": 1}}, "df": 3}}}}}}}, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}, "c": {"docs": {}, "df": 0, "t": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}}}}, "c": {"docs": {}, "df": 0, "h": {"docs": {"vertax.FacePlot.MULTICOLOR": {"tf": 1}}, "df": 1}}}, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcMesh": {"tf": 1.4142135623730951}}, "df": 1}}}}}, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.__init__": {"tf": 1}, "vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.create_empty": {"tf": 1}}, "df": 3}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}}, "df": 1}, "s": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}}}}}}}}, "l": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"vertax.BoundedMesh": {"tf": 2}}, "df": 1}}, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.cost_ratio": {"tf": 1}}, "df": 1}}}}}}, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {"vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 2}}}}}}, "f": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}}}}}}}}}, "t": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 2, "i": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 2.23606797749979}, "vertax.Mesh.faces": {"tf": 1}}, "df": 2, "s": {"docs": {"vertax": {"tf": 1.7320508075688772}}, "df": 1}}}}}}, "h": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "h": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}, "e": {"docs": {"vertax": {"tf": 3.872983346207417}, "vertax.Mesh.faces": {"tf": 1.4142135623730951}, "vertax.Mesh.edges": {"tf": 1}, "vertax.Mesh.vertices": {"tf": 1}, "vertax.Mesh.width": {"tf": 1.4142135623730951}, "vertax.Mesh.height": {"tf": 1.4142135623730951}, "vertax.Mesh.nb_faces": {"tf": 1.4142135623730951}, "vertax.Mesh.nb_edges": {"tf": 1.4142135623730951}, "vertax.Mesh.nb_half_edges": {"tf": 1.7320508075688772}, "vertax.Mesh.nb_vertices": {"tf": 1.4142135623730951}, "vertax.Mesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.Mesh.load_mesh": {"tf": 1.7320508075688772}, "vertax.PbcMesh": {"tf": 2.6457513110645907}, "vertax.PbcMesh.__init__": {"tf": 1.4142135623730951}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 2.449489742783178}, "vertax.PbcMesh.from_seeds": {"tf": 2.23606797749979}, "vertax.PbcMesh.from_image": {"tf": 2.6457513110645907}, "vertax.PbcMesh.from_mask": {"tf": 2}, "vertax.PbcMesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.load_mesh": {"tf": 1.7320508075688772}, "vertax.PbcMesh.save_mesh_txt": {"tf": 3}, "vertax.PbcMesh.load_mesh_txt": {"tf": 3}, "vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.PbcMesh.get_perimeter": {"tf": 1}, "vertax.PbcMesh.get_area": {"tf": 1}, "vertax.BoundedMesh": {"tf": 2.8284271247461903}, "vertax.BoundedMesh.__init__": {"tf": 1}, "vertax.BoundedMesh.angles": {"tf": 1}, "vertax.BoundedMesh.nb_angles": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_random_seeds": {"tf": 2.23606797749979}, "vertax.BoundedMesh.from_seeds": {"tf": 2.449489742783178}, "vertax.BoundedMesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.load_mesh": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 3.1622776601683795}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 3.1622776601683795}, "vertax.BoundedMesh.get_length": {"tf": 1}, "vertax.BoundedMesh.get_perimeter": {"tf": 1}, "vertax.BoundedMesh.get_area": {"tf": 1}, "vertax.BilevelOptimizationMethod": {"tf": 1}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1.4142135623730951}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1.4142135623730951}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1.4142135623730951}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1.4142135623730951}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1.4142135623730951}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1.4142135623730951}, "vertax.plot_mesh": {"tf": 1.4142135623730951}, "vertax.get_plot_mesh": {"tf": 1}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.FacePlot.AREA": {"tf": 1}, "vertax.FacePlot.PERIMETER": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.LENGTH": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}, "vertax.cost_v2v": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_areas": {"tf": 2}, "vertax.cost_IAS": {"tf": 1}, "vertax.cost_ratio": {"tf": 1}, "vertax.energy_shape_factor_hetero": {"tf": 1.4142135623730951}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 66, "s": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}}, "y": {"docs": {"vertax": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 2}, "t": {"docs": {}, "df": 0, "a": {"docs": {"vertax": {"tf": 2.449489742783178}}, "df": 1, "^": {"docs": {}, "df": 0, "{": {"docs": {}, "df": 0, "\\": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}, "\\": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "t": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}, "n": {"docs": {"vertax": {"tf": 1}}, "df": 1}, "r": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}}, "df": 2}}, "i": {"docs": {}, "df": 0, "r": {"docs": {"vertax": {"tf": 1}, "vertax.FacePlot.FATES": {"tf": 1}}, "df": 2}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {"vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 2}}}}}, "i": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.Mesh.__init__": {"tf": 1}, "vertax.PbcMesh": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}}, "df": 5}}, "o": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {"vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}}, "df": 1}, "t": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.cost_ratio": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}}, "df": 6}}}, "o": {"docs": {"vertax": {"tf": 2.8284271247461903}, "vertax.Mesh": {"tf": 1}, "vertax.Mesh.__init__": {"tf": 1}, "vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}, "vertax.Mesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.Mesh.load_mesh": {"tf": 1}, "vertax.PbcMesh": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1.4142135623730951}, "vertax.PbcMesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 3}, "vertax.PbcMesh.load_mesh_txt": {"tf": 2.6457513110645907}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 3.1622776601683795}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 2.8284271247461903}, "vertax.BilevelOptimizationMethod": {"tf": 1}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}, "vertax.plot_mesh": {"tf": 1}, "vertax.FacePlot": {"tf": 1}, "vertax.EdgePlot": {"tf": 1}, "vertax.VertexPlot": {"tf": 1}, "vertax.cost_v2v": {"tf": 1.4142135623730951}, "vertax.cost_mesh2image": {"tf": 1.4142135623730951}, "vertax.cost_IAS": {"tf": 1}, "vertax.cost_ratio": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1.4142135623730951}}, "df": 36, "p": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "y": {"docs": {"vertax.cost_IAS": {"tf": 1}}, "df": 1}}}}}}}, "w": {"docs": {}, "df": 0, "o": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"vertax.Mesh.nb_half_edges": {"tf": 1}}, "df": 1}}, "n": {"docs": {"vertax.PbcMesh": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}}, "df": 2}}}, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}}, "y": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}}, "df": 1}}, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}, "e": {"docs": {}, "df": 0, "x": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1.4142135623730951}}, "df": 4, "b": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "{": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}, "{": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}, "c": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}}}, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 2, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}, "c": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"vertax.Mesh.__init__": {"tf": 1}, "vertax.Mesh.edges": {"tf": 1}}, "df": 2}}}}}}}}}, "m": {"docs": {"vertax.cost_tem_halfedge": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}}, "df": 2, "p": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}}, "df": 2}}}}}}, "r": {"docs": {}, "df": 0, "m": {"docs": {"vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 2}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.PbcMesh": {"tf": 1.7320508075688772}, "vertax.BoundedMesh": {"tf": 1.4142135623730951}, "vertax.cost_v2v": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_areas": {"tf": 1}}, "df": 6}}}}, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 2.449489742783178}, "vertax.PbcMesh.load_mesh_txt": {"tf": 2.449489742783178}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 2.6457513110645907}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 2.6457513110645907}}, "df": 4}}}}, "x": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 2.6457513110645907}, "vertax.PbcMesh.load_mesh_txt": {"tf": 2.6457513110645907}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 2.8284271247461903}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 2.8284271247461903}}, "df": 4}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.create_empty": {"tf": 1}}, "df": 2, "s": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 5, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {"vertax.cost_IAS": {"tf": 1}}, "df": 1, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"vertax.cost_v2v_tem": {"tf": 1}}, "df": 1}}}}}}}}, "p": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}}}}}}, "o": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}}, "df": 1}}}}}}}}}}}}, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}, "o": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {"vertax": {"tf": 1}}, "df": 1}}, "d": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}}, "df": 2}}, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.Mesh.width": {"tf": 1}, "vertax.Mesh.height": {"tf": 1}}, "df": 2}}, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}}}, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"vertax.Mesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 9}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}}, "df": 2}}}}}}, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}, "s": {"docs": {"vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 2}}}}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "m": {"docs": {"vertax.PbcMesh.__init__": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 2}, "vertax.BoundedMesh.from_random_seeds": {"tf": 2}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.FacePlot.MULTICOLOR": {"tf": 1}}, "df": 5}}}}}, "u": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}}, "df": 2}}}}}}}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}, "a": {"docs": {}, "df": 0, "d": {"docs": {"vertax.Mesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1.4142135623730951}}, "df": 5, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.Mesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 5}}}}, "s": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}, "vertax.cost_tem_halfedge": {"tf": 1}}, "df": 9}}}, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}, "d": {"docs": {}, "df": 0, "s": {"docs": {"vertax.cost_checkerboard": {"tf": 1}}, "df": 1}}}, "f": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "w": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.get_length": {"tf": 1}, "vertax.EdgePlot.LENGTH": {"tf": 1}}, "df": 4, "s": {"docs": {"vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 2}}}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {"vertax.BilevelOptimizationMethod": {"tf": 1}, "vertax.PbcBilevelOptimizer": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}}, "df": 5}}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 3}}, "v": {"docs": {}, "df": 0, "e": {"docs": {"vertax.Mesh.width": {"tf": 1}, "vertax.Mesh.height": {"tf": 1}}, "df": 2}}, "s": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 2}}, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 1}}}}}, "l": {"docs": {"vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 1}}, "c": {"docs": {"vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}}, "df": 2, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {"vertax.BoundedMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 5, "s": {"docs": {"vertax": {"tf": 2.23606797749979}, "vertax.Mesh.faces": {"tf": 1}, "vertax.Mesh.edges": {"tf": 1}, "vertax.Mesh.vertices": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}, "vertax.cost_tem_halfedge": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1.4142135623730951}}, "df": 7}, "p": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}}, "df": 1}}}}}}, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "d": {"docs": {"vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 2}}}, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "k": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "d": {"docs": {"vertax.cost_checkerboard": {"tf": 1}}, "df": 1}}}}}}}}}}}, "o": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"vertax.cost_v2v": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_areas": {"tf": 1}}, "df": 3}}}}, "m": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax": {"tf": 1}, "vertax.Mesh": {"tf": 1}}, "df": 2}}}, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.PbcMesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}}, "df": 5}}}}}}}, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax.PbcMesh": {"tf": 1}, "vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}, "vertax.PbcBilevelOptimizer": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}}, "df": 5, "s": {"docs": {"vertax": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 6}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 2}}}}}}}}}, "f": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcMesh.__init__": {"tf": null}, "vertax.BoundedMesh.__init__": {"tf": null}}, "df": 2}}}}}}, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1.4142135623730951}}, "df": 4}}}}}}}, "s": {"docs": {}, "df": 0, "t": {"docs": {"vertax": {"tf": 1.7320508075688772}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.cost_v2v": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_areas": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1.4142135623730951}, "vertax.cost_v2v_tem": {"tf": 1.4142135623730951}, "vertax.cost_ratio": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}}, "df": 9}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}}, "df": 1, "s": {"docs": {"vertax.PbcMesh": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}}, "df": 2}}}}}}}}}, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}}, "df": 4}}}}}}}}, "c": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 5}}}}}, "p": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}}, "df": 2}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"vertax.FacePlot.MULTICOLOR": {"tf": 1}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.FacePlot.AREA": {"tf": 1}, "vertax.FacePlot.PERIMETER": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.LENGTH": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}}, "df": 7, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.FacePlot.FATES": {"tf": 1}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "s": {"docs": {"vertax.cost_d_IAS": {"tf": 1}}, "df": 1}}}}}, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BoundedMesh": {"tf": 2}}, "df": 1, "l": {"docs": {}, "df": 0, "y": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}, "l": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.Mesh": {"tf": 1}, "vertax.Mesh.__init__": {"tf": 1}, "vertax.PbcMesh.__init__": {"tf": 1}}, "df": 4, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 3}}}}}}, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {"vertax.Mesh.__init__": {"tf": 1.4142135623730951}, "vertax.PbcMesh.__init__": {"tf": 1}, "vertax.BoundedMesh.__init__": {"tf": 1}}, "df": 3, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 5}}}}, "n": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}}, "df": 7}}, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.Mesh.__init__": {"tf": 1.4142135623730951}, "vertax.PbcMesh.__init__": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.create_empty": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}}, "df": 12}}}}, "o": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.PbcMesh": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}, "i": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.BoundedMesh": {"tf": 1}}, "df": 1}}}}}}}, "u": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "s": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}}, "df": 1}}}}}, "i": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "m": {"docs": {"vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 1}}}}}}, "s": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 2}, "vertax.PbcMesh.__init__": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.create_empty": {"tf": 1}, "vertax.BilevelOptimizationMethod": {"tf": 1}}, "df": 9, "r": {"docs": {"vertax": {"tf": 1}}, "df": 1, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}, "d": {"docs": {"vertax": {"tf": 1}, "vertax.Mesh": {"tf": 1}}, "df": 2}, "s": {"docs": {"vertax.Mesh.__init__": {"tf": 1}}, "df": 1}}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.cost_tem_halfedge": {"tf": 1}}, "df": 2}}}}, "p": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 1, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}, "p": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.cost_v2v": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}}, "df": 5}}}, "v": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.from_seeds": {"tf": 1}}, "df": 1}}}}}, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.FacePlot": {"tf": 1}, "vertax.EdgePlot": {"tf": 1}, "vertax.VertexPlot": {"tf": 1}}, "df": 5}}}}}}}, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "m": {"docs": {"vertax": {"tf": 1.7320508075688772}}, "df": 1, "s": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}}}}, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}}, "df": 1}}}}}, "v": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_image": {"tf": 1}}, "df": 2}}}}}, "e": {"docs": {"vertax": {"tf": 1}}, "df": 1, "v": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcMesh": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}}, "df": 2}}}}}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.FacePlot.FACE_PARAMETER": {"tf": 1}, "vertax.EdgePlot.EDGE_PARAMETER": {"tf": 1}, "vertax.VertexPlot.VERTEX_PARAMETER": {"tf": 1}}, "df": 5, "s": {"docs": {"vertax": {"tf": 1.7320508075688772}, "vertax.Mesh.faces_params": {"tf": 1}, "vertax.Mesh.edges_params": {"tf": 1}, "vertax.Mesh.vertices_params": {"tf": 1}, "vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 2}, "vertax.PbcMesh.load_mesh_txt": {"tf": 2}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 2}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 2}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}}, "df": 13}}}}}, "s": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1.7320508075688772}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1.7320508075688772}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 5}}}}, "t": {"docs": {}, "df": 0, "h": {"docs": {"vertax.Mesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.Mesh.load_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.load_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.load_mesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}}, "df": 8}, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "n": {"docs": {"vertax.cost_checkerboard": {"tf": 1}}, "df": 1}}}}}}, "b": {"docs": {}, "df": 0, "c": {"docs": {"vertax.energy_line_tensions": {"tf": 1}}, "df": 1, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "h": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.Mesh": {"tf": 1}, "vertax.Mesh.__init__": {"tf": 1}, "vertax.PbcMesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcBilevelOptimizer": {"tf": 1}, "vertax.cost_v2v": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_areas": {"tf": 1}}, "df": 10}}}}, "b": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 2}}}}}}}}}}}}}}}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {"vertax": {"tf": 1}, "vertax.PbcMesh": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1.7320508075688772}, "vertax.PbcMesh.from_mask": {"tf": 1.4142135623730951}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}, "vertax.PbcBilevelOptimizer": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}}, "df": 9}}}}, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcMesh.get_perimeter": {"tf": 1}, "vertax.FacePlot.PERIMETER": {"tf": 1}}, "df": 2}}}}}}, "f": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "m": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}}, "df": 1}}}}, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}}}}}}}}}}, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "y": {"docs": {"vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 2}}}}}}, "h": {"docs": {}, "df": 0, "y": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}, "l": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "t": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.plot_mesh": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}}, "df": 3}}}, "i": {"docs": {"vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.angles": {"tf": 1}}, "df": 2}}, "w": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {"vertax": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.BilevelOptimizationMethod": {"tf": 1}}, "df": 4}}, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.FacePlot.WHITE": {"tf": 1}}, "df": 1}}, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.cost_ratio": {"tf": 1}}, "df": 1}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}, "vertax.Mesh.vertices": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 8}}, "n": {"docs": {"vertax.cost_checkerboard": {"tf": 1}}, "df": 1}}, "a": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.create_empty": {"tf": 1}, "vertax.FacePlot": {"tf": 1}, "vertax.EdgePlot": {"tf": 1}, "vertax.VertexPlot": {"tf": 1}}, "df": 5}}}, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"vertax": {"tf": 1.7320508075688772}, "vertax.PbcMesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_image": {"tf": 1.7320508075688772}, "vertax.PbcMesh.from_mask": {"tf": 1.7320508075688772}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}, "vertax.BoundedMesh": {"tf": 2}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.cost_v2v_ias": {"tf": 1}, "vertax.cost_v2v_tem": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1.4142135623730951}}, "df": 18, "o": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "t": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}, "d": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "h": {"docs": {"vertax.Mesh.width": {"tf": 1}, "vertax.PbcMesh": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1.7320508075688772}}, "df": 7, "/": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BoundedMesh.save_mesh_txt": {"tf": 1}}, "df": 1, "/": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "x": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 3}}}}}}}}}}}}}}, "l": {"docs": {}, "df": 0, "l": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_image": {"tf": 1.7320508075688772}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 4}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}, "k": {"docs": {"vertax": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 2}}, "n": {"docs": {"vertax": {"tf": 1}}, "df": 1}}, "e": {"docs": {"vertax": {"tf": 1.7320508075688772}, "vertax.PbcMesh.from_image": {"tf": 1.4142135623730951}}, "df": 2, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "t": {"docs": {"vertax.cost_v2v_ias": {"tf": 1}}, "df": 1, "s": {"docs": {"vertax.cost_v2v_tem": {"tf": 1}}, "df": 1}, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.energy_line_tensions": {"tf": 1}, "vertax.energy_line_tensions_bounded": {"tf": 1}}, "df": 2}}}}}}}}, "s": {"docs": {"vertax.cost_IAS": {"tf": 1.4142135623730951}}, "df": 1, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "f": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax": {"tf": 1}}, "df": 1}}, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "y": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}, "y": {"docs": {"vertax.PbcBilevelOptimizer.__init__": {"tf": 1}}, "df": 1}}, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "z": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax": {"tf": 1}, "vertax.Mesh.__init__": {"tf": 1}}, "df": 2}}}}}}}}}}, "i": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}}, "g": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}, "z": {"docs": {}, "df": 0, "e": {"docs": {"vertax.Mesh.width": {"tf": 1}, "vertax.Mesh.height": {"tf": 1}, "vertax.PbcMesh": {"tf": 1.7320508075688772}, "vertax.BoundedMesh": {"tf": 2}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 5}}}, "u": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "h": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.PbcMesh.__init__": {"tf": 1}}, "df": 2}}, "p": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}, "r": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}}, "df": 1}}, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.angles": {"tf": 1}}, "df": 2}}}}}}}}, "m": {"docs": {"vertax.cost_d_IAS": {"tf": 1}}, "df": 1}}, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.energy_shape_factor_hetero": {"tf": 1}, "vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 5}}}, "o": {"docs": {}, "df": 0, "w": {"docs": {"vertax.plot_mesh": {"tf": 1}, "vertax.FacePlot": {"tf": 1}, "vertax.EdgePlot": {"tf": 1}, "vertax.EdgePlot.INVISIBLE": {"tf": 1}, "vertax.VertexPlot": {"tf": 1}, "vertax.VertexPlot.INVISIBLE": {"tf": 1}}, "df": 6}}}, "o": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 2, "s": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}, "m": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}}, "df": 1}}, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1.4142135623730951}}, "df": 2}}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"vertax": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}}, "df": 3}, "e": {"docs": {"vertax": {"tf": 2}, "vertax.Mesh.__init__": {"tf": 1}}, "df": 2, "d": {"docs": {"vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}}, "df": 3, "s": {"docs": {"vertax.PbcMesh.__init__": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_random_seeds": {"tf": 2.23606797749979}, "vertax.PbcMesh.from_seeds": {"tf": 2.23606797749979}, "vertax.BoundedMesh.from_random_seeds": {"tf": 2.449489742783178}, "vertax.BoundedMesh.from_seeds": {"tf": 2.6457513110645907}}, "df": 5}}}, "l": {"docs": {}, "df": 0, "f": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}}, "df": 10}}, "g": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "n": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}}, "df": 1}}}}}}}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}}, "df": 2}}}}}}, "n": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 2}}}}}}}}, "y": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "y": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}}}, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}}, "df": 2}}, "v": {"docs": {}, "df": 0, "e": {"docs": {"vertax.Mesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1.7320508075688772}, "vertax.plot_mesh": {"tf": 1}}, "df": 6, "d": {"docs": {"vertax.Mesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.save_mesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh": {"tf": 1.4142135623730951}}, "df": 3}}}}, "t": {"docs": {}, "df": 0, "r": {"docs": {"vertax.Mesh.save_mesh": {"tf": 1}, "vertax.Mesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 2.8284271247461903}, "vertax.PbcMesh.load_mesh_txt": {"tf": 2.8284271247461903}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 3}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 3}}, "df": 10, "u": {"docs": {}, "df": 0, "c": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"vertax.Mesh": {"tf": 1}, "vertax.Mesh.__init__": {"tf": 1}}, "df": 2}, "a": {"docs": {}, "df": 0, "l": {"docs": {"vertax.cost_IAS": {"tf": 1}, "vertax.cost_d_IAS": {"tf": 1}}, "df": 2}}}}}}}}, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 2}}}}, "e": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "s": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}}, "df": 1}}}}, "c": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}}, "df": 1}}}}}, "q": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "{": {"docs": {}, "df": 0, "\\": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "m": {"docs": {"vertax.cost_IAS": {"tf": 1}}, "df": 1}}}}}}}}}, "n": {"docs": {}, "df": 0, "o": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1.4142135623730951}, "vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 3, "t": {"docs": {"vertax": {"tf": 1}, "vertax.Mesh": {"tf": 1}, "vertax.Mesh.__init__": {"tf": 1}, "vertax.PbcMesh.__init__": {"tf": 1}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.__init__": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}, "vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}, "vertax.plot_mesh": {"tf": 1}, "vertax.EdgePlot.INVISIBLE": {"tf": 1}, "vertax.VertexPlot.INVISIBLE": {"tf": 1}}, "df": 15, "h": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.Mesh.__init__": {"tf": 1}}, "df": 1}}}}, "e": {"docs": {"vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 1}}}, "e": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax": {"tf": 1}}, "df": 1, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 2}}}}, "x": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcMesh": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}}, "df": 2}}, "w": {"docs": {"vertax.PbcMesh.copy_mesh": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.copy_mesh": {"tf": 1}}, "df": 4}, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 1}}}, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.cost_checkerboard": {"tf": 1}}, "df": 1}}}}}}}}}}, "u": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax.Mesh.nb_faces": {"tf": 1}, "vertax.Mesh.nb_edges": {"tf": 1}, "vertax.Mesh.nb_half_edges": {"tf": 1.4142135623730951}, "vertax.Mesh.nb_vertices": {"tf": 1}, "vertax.PbcMesh.MAX_EDGES_IN_ANY_FACE": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.nb_angles": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION": {"tf": 1}}, "df": 10}}}, "p": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}}, "df": 4}}}, "d": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}, "s": {"docs": {"vertax.cost_ratio": {"tf": 1}, "vertax.cost_checkerboard": {"tf": 1}}, "df": 2}}}}}, "p": {"docs": {}, "df": 0, "z": {"docs": {"vertax.Mesh.save_mesh": {"tf": 1}, "vertax.Mesh.load_mesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.save_mesh": {"tf": 1}, "vertax.PbcMesh.load_mesh": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.save_mesh": {"tf": 1}, "vertax.BoundedMesh.load_mesh": {"tf": 1.4142135623730951}}, "df": 6}}, "b": {"docs": {"vertax.PbcMesh": {"tf": 1.7320508075688772}, "vertax.PbcMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh": {"tf": 2}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 5, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 2}}}}}}, "d": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.from_mask": {"tf": 1}}, "df": 2}}}}}}, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "w": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.cost_ratio": {"tf": 1}}, "df": 1}}}}}}}}}, "h": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 4}, "i": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "g": {"docs": {"vertax.cost_checkerboard": {"tf": 1}}, "df": 1}}}}, "l": {"docs": {}, "df": 0, "f": {"docs": {"vertax.Mesh.edges": {"tf": 1}, "vertax.Mesh.nb_half_edges": {"tf": 1}, "vertax.PbcMesh": {"tf": 2.449489742783178}, "vertax.PbcMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh": {"tf": 3.1622776601683795}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1.4142135623730951}, "vertax.cost_tem_halfedge": {"tf": 1}}, "df": 9, "e": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "e": {"docs": {"vertax.cost_v2v_tem": {"tf": 1}}, "df": 1}}}}}}}, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "e": {"docs": {"vertax": {"tf": 1}}, "df": 1}}, "i": {"docs": {}, "df": 0, "g": {"docs": {}, "df": 0, "h": {"docs": {}, "df": 0, "t": {"docs": {"vertax.Mesh.height": {"tf": 1}, "vertax.PbcMesh": {"tf": 1}, "vertax.PbcMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_seeds": {"tf": 1.7320508075688772}}, "df": 7}}}}, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 4}}}}}, "p": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "m": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "b": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "e": {"docs": {"vertax.PbcMesh.save_mesh_txt": {"tf": 1}, "vertax.PbcMesh.load_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.save_mesh_txt": {"tf": 1}, "vertax.BoundedMesh.load_mesh_txt": {"tf": 1}}, "df": 4}}}}}}}}}}}, "s": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "n": {"docs": {"vertax.BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION": {"tf": 1}, "vertax.BilevelOptimizationMethod.ADJOINT_STATE": {"tf": 1}}, "df": 2}}}}}}, "y": {"docs": {}, "df": 0, "p": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}, "o": {"docs": {}, "df": 0, "w": {"docs": {"vertax": {"tf": 1}}, "df": 1, "e": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {"vertax": {"tf": 1}}, "df": 1}}}}}, "l": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "s": {"docs": {"vertax.PbcMesh.from_image": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_mask": {"tf": 1.4142135623730951}}, "df": 2}}}}}, "q": {"docs": {}, "df": 0, "u": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}}}}, "x": {"docs": {"vertax": {"tf": 1.7320508075688772}, "vertax.Mesh.width": {"tf": 1}, "vertax.PbcMesh": {"tf": 1.4142135623730951}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.BoundedMesh": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 7, "^": {"docs": {}, "df": 0, "{": {"docs": {}, "df": 0, "\\": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "s": {"docs": {}, "df": 0, "t": {"docs": {"vertax": {"tf": 1.4142135623730951}}, "df": 1}}}}}}}, "g": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "v": {"docs": {}, "df": 0, "e": {"docs": {"vertax.energy_shape_factor_homo": {"tf": 1}}, "df": 1, "n": {"docs": {"vertax": {"tf": 1}, "vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.PbcMesh.get_perimeter": {"tf": 1}, "vertax.PbcMesh.get_area": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}, "vertax.cost_v2v": {"tf": 1}, "vertax.cost_mesh2image": {"tf": 1}, "vertax.cost_areas": {"tf": 1}}, "df": 9}}}}, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "r": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "c": {"docs": {"vertax.Mesh": {"tf": 1}}, "df": 1}}, "a": {"docs": {}, "df": 0, "t": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "r": {"docs": {"vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 2}}}}}}}, "t": {"docs": {"vertax.Mesh.nb_faces": {"tf": 1}, "vertax.Mesh.nb_edges": {"tf": 1}, "vertax.Mesh.nb_half_edges": {"tf": 1}, "vertax.Mesh.nb_vertices": {"tf": 1}, "vertax.PbcMesh.get_length": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.PbcMesh.get_perimeter": {"tf": 1}, "vertax.PbcMesh.get_area": {"tf": 1}, "vertax.BoundedMesh.nb_angles": {"tf": 1}, "vertax.BoundedMesh.get_length": {"tf": 1}, "vertax.BoundedMesh.get_perimeter": {"tf": 1}, "vertax.BoundedMesh.get_area": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.PbcBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_outer_loss": {"tf": 1}, "vertax.BoundedBilevelOptimizer.compute_inner_loss": {"tf": 1}, "vertax.get_plot_mesh": {"tf": 1}, "vertax.FacePlot.MULTICOLOR": {"tf": 1}}, "df": 18}}, "r": {"docs": {}, "df": 0, "a": {"docs": {}, "df": 0, "d": {"docs": {}, "df": 0, "i": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "n": {"docs": {}, "df": 0, "t": {"docs": {"vertax.BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION": {"tf": 1}}, "df": 1}}}}}, "p": {"docs": {}, "df": 0, "h": {"docs": {"vertax.cost_tem_halfedge": {"tf": 1}}, "df": 1}}}}}, "y": {"docs": {"vertax.Mesh.height": {"tf": 1}, "vertax.PbcMesh": {"tf": 1}, "vertax.PbcMesh.from_seeds": {"tf": 1}, "vertax.PbcMesh.get_length_with_offset": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 5, "o": {"docs": {}, "df": 0, "u": {"docs": {"vertax": {"tf": 1.7320508075688772}, "vertax.PbcMesh.from_image": {"tf": 1}, "vertax.PbcMesh.create_empty": {"tf": 1.4142135623730951}, "vertax.PbcMesh.update_boundary_conditions": {"tf": 1}, "vertax.BoundedMesh.create_empty": {"tf": 1.4142135623730951}}, "df": 5, "r": {"docs": {"vertax": {"tf": 1.7320508075688772}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 2, "s": {"docs": {}, "df": 0, "e": {"docs": {}, "df": 0, "l": {"docs": {}, "df": 0, "f": {"docs": {"vertax.PbcMesh.update_boundary_conditions": {"tf": 1}}, "df": 1}}}}}}}, "e": {"docs": {}, "df": 0, "t": {"docs": {"vertax.PbcBilevelOptimizer.__init__": {"tf": 1}, "vertax.BoundedBilevelOptimizer.__init__": {"tf": 1}}, "df": 2}}}, "k": {"docs": {"vertax.cost_IAS": {"tf": 1.7320508075688772}, "vertax.cost_d_IAS": {"tf": 1.7320508075688772}}, "df": 2, "e": {"docs": {}, "df": 0, "y": {"docs": {"vertax.PbcMesh.from_random_seeds": {"tf": 1.4142135623730951}, "vertax.BoundedMesh.from_random_seeds": {"tf": 1}, "vertax.BoundedMesh.from_seeds": {"tf": 1}}, "df": 3}}, "n": {"docs": {}, "df": 0, "o": {"docs": {}, "df": 0, "w": {"docs": {"vertax.PbcMesh.create_empty": {"tf": 1}, "vertax.BoundedMesh.create_empty": {"tf": 1}}, "df": 2}}}}}}}, "pipeline": ["trimmer"], "_isPrebuiltIndex": true}; + + // mirrored in build-search-index.js (part 1) + // Also split on html tags. this is a cheap heuristic, but good enough. + elasticlunr.tokenizer.setSeperator(/[\s\-.;&_'"=,()]+|<[^>]*>/); + + let searchIndex; + if (docs._isPrebuiltIndex) { + console.info("using precompiled search index"); + searchIndex = elasticlunr.Index.load(docs); + } else { + console.time("building search index"); + // mirrored in build-search-index.js (part 2) + searchIndex = elasticlunr(function () { + this.pipeline.remove(elasticlunr.stemmer); + this.pipeline.remove(elasticlunr.stopWordFilter); + this.addField("qualname"); + this.addField("fullname"); + this.addField("annotation"); + this.addField("default_value"); + this.addField("signature"); + this.addField("bases"); + this.addField("doc"); + this.setRef("fullname"); + }); + for (let doc of docs) { + searchIndex.addDoc(doc); + } + console.timeEnd("building search index"); + } + + return (term) => searchIndex.search(term, { + fields: { + qualname: {boost: 4}, + fullname: {boost: 2}, + annotation: {boost: 2}, + default_value: {boost: 2}, + signature: {boost: 2}, + bases: {boost: 2}, + doc: {boost: 1}, + }, + expand: true + }); +})(); \ No newline at end of file diff --git a/docs/vertax.html b/docs/vertax.html new file mode 100644 index 0000000..1a3b3bf --- /dev/null +++ b/docs/vertax.html @@ -0,0 +1,7436 @@ + + + + + + + VertAX API documentation + + + + + + + + + + + + +
+
+

+VertAX

+ +

A differentiable JAX-based framework for vertex modeling and inverse design of epithelial tissues.

+ +

Epithelial tissues dynamically reshape through local mechanical interactions among cells. +Understanding, inferring, and designing these mechanics is a central challenge in developmental biology and biophysics. +VertAX is a computational framework built to address this challenge.

+ +

VertAX is a framework for vertex-based modeling: + it represents epithelial tissues as two-dimensional polygonal meshes in which cells are faces, junctions are edges, + tricellular contacts are vertices, and mechanical equilibrium is defined by the minimum of a user-specified energy.

+ +

Built on JAX, VertAX is designed not only for forward simulation, + but also for inverse problems such as parameter inference and tissue design.

+ +

In VertAX, a Mesh represents a vertex model of an epithelial tissue. It is made of faces (cells), edges (interface between cells) and vertices (where 3 cells or more meet).

+ +

More specifically, two types of meshes are currently supported:

+ +
    +
  • PbcMesh have Periodic Boundary Conditions, and are used for bulk tissue dynamics without explicit external boundaries.
  • +
  • BoundedMesh are designed for finite tissue clusters, with curved free interfaces.
  • +
+ +

The other first class objects of VertAX are called Bilevel Optimizers. They allow to formulate inverse problems as nested optimizations:

+ +

$$ +\begin{aligned} +\textbf{Outer problem (learning):} \quad +\theta^{\ast} &= \arg\min_{\theta} \mathcal{C}\left(X^{\ast}_{\theta},\theta\right) +&& \leftarrow \text{fit data or reach a target} \ +\end{aligned} +$$ +$$ +\begin{aligned} +\textbf{Inner problem (physics):} \ \ \text{s.t.}\ \ & +X^{\ast}_{\theta} \in \arg\min_{X} \mathcal{E}(X,\theta) +&& \leftarrow \text{compute mechanical equilibrium} +\end{aligned} +$$

+ +

Here, $X$ denotes the tissue configuration, i.e. the vertex positions of the mesh, and $\theta$ denotes the model parameters, such as line tensions, target areas, or shape factors.

+ +

In other words, VertAX repeatedly solves a mechanical equilibrium problem for a given parameter set $\theta$, then updates those parameters to better match data or a design objective.

+ +

In symmetry with meshes, a base abstract class _BilevelOptimizer defines common hyper-parameters and methods for the bilevel optimization, but you need to use the specialized classes:

+ + + +

VertAX comes with pre-defined energy and cost functions but you can easily define your own functions. See the examples folder in the repository to have a typical example on how to use VertAX.

+ +

Finally, there are plot functions to easily see the results of your experiments. See plot_mesh for example.

+ +

Users can define their own energy functions for the inner optimization and cost function for the outer optimization, however we provide some basic ones. +If you use your own make sure to use the exact same signature as we do for these functions, otherwise it won't work. See the cost and energy functions we provide in this documentation.

+
+ + + + + +
  1"""A differentiable JAX-based framework for vertex modeling and inverse design of epithelial tissues.
+  2
+  3Epithelial tissues dynamically reshape through local mechanical interactions among cells.
+  4Understanding, inferring, and designing these mechanics is a central challenge in developmental biology and biophysics.
+  5**VertAX** is a computational framework built to address this challenge.
+  6
+  7**VertAX** is a **framework for vertex-based modeling**:
+  8 it represents epithelial tissues as two-dimensional polygonal meshes in which cells are faces, junctions are edges,
+  9  tricellular contacts are vertices, and mechanical equilibrium is defined by the minimum of a user-specified energy.
+ 10
+ 11  Built on **JAX**, VertAX is designed not only for forward simulation,
+ 12  but also for inverse problems such as parameter inference and tissue design.
+ 13
+ 14In **VertAX**, a `Mesh` represents a vertex model of an epithelial tissue. It is made of faces (cells), edges (interface between cells) and vertices (where 3 cells or more meet).
+ 15
+ 16More specifically, two types of meshes are currently supported:
+ 17- `PbcMesh` have Periodic Boundary Conditions, and are used for bulk tissue dynamics without explicit external boundaries.
+ 18- `BoundedMesh` are designed for finite tissue clusters, with curved free interfaces.
+ 19
+ 20The other first class objects of **VertAX** are called Bilevel Optimizers. They allow to formulate inverse problems as nested optimizations:
+ 21
+ 22$$
+ 23\\begin{aligned}
+ 24\\textbf{Outer problem (learning):} \\quad
+ 25\\theta^{\\ast} &= \\arg\\min_{\\theta} \\mathcal{C}\\left(X^{\\ast}_{\\theta},\\theta\\right)
+ 26&& \\leftarrow \\text{fit data or reach a target} \\\\
+ 27\\end{aligned}
+ 28$$
+ 29$$
+ 30\\begin{aligned}
+ 31\\textbf{Inner problem (physics):} \\quad \\text{s.t.}&
+ 32X^{\\ast}_{\\theta} \\in \\arg\\min_{X} \\mathcal{E}(X,\\theta)
+ 33&& \\leftarrow \\text{compute mechanical equilibrium}
+ 34\\end{aligned}
+ 35$$
+ 36
+ 37
+ 38Here, $X$ denotes the tissue configuration, i.e. the vertex positions of the mesh, and $\\theta$ denotes the model parameters, such as line tensions, target areas, or shape factors.
+ 39
+ 40In other words, VertAX repeatedly solves a mechanical equilibrium problem for a given parameter set $\\theta$, then updates those parameters to better match data or a design objective.
+ 41
+ 42In symmetry with meshes, a base abstract class `_BilevelOptimizer` defines common hyper-parameters and methods for the bilevel optimization, but you need to use the specialized classes:
+ 43- `PbcBilevelOptimizer` for `PbcMesh`,
+ 44- `BoundedBilevelOptimizer` for `BoundedMesh`.
+ 45
+ 46**VertAX** comes with pre-defined energy and cost functions but you can easily define your own functions. See the `examples` folder in the repository to have a typical example on how to use **VertAX**.
+ 47
+ 48Finally, there are plot functions to easily see the results of your experiments. See `plot_mesh` for example.
+ 49
+ 50Users can define their own energy functions for the inner optimization and cost function for the outer optimization, however we provide some basic ones.
+ 51If you use your own make sure to use the exact same signature as we do for these functions, otherwise it won't work. See the cost and energy functions we provide in this documentation.
+ 52"""  # noqa: D301, E501
+ 53
+ 54from vertax.bilevelopt.bilevelopt import _BilevelOptimizer
+ 55from vertax.bilevelopt.boundedbop import BoundedBilevelOptimizer
+ 56from vertax.bilevelopt.pbcbop import PbcBilevelOptimizer
+ 57from vertax.cost import (
+ 58    cost_areas,
+ 59    cost_checkerboard,
+ 60    cost_d_IAS,
+ 61    cost_IAS,
+ 62    cost_mesh2image,
+ 63    cost_ratio,
+ 64    cost_tem_halfedge,
+ 65    cost_v2v,
+ 66    cost_v2v_ias,
+ 67    cost_v2v_tem,
+ 68)
+ 69from vertax.energy import (
+ 70    energy_line_tensions,
+ 71    energy_line_tensions_bounded,
+ 72    energy_shape_factor_hetero,
+ 73    energy_shape_factor_homo,
+ 74)
+ 75from vertax.meshes.bounded_mesh import BoundedMesh
+ 76from vertax.meshes.mesh import Mesh
+ 77from vertax.meshes.pbc_mesh import PbcMesh
+ 78from vertax.meshes.plot import EdgePlot, FacePlot, VertexPlot, get_plot_mesh, plot_mesh
+ 79from vertax.method_enum import BilevelOptimizationMethod
+ 80
+ 81__all__ = [  # noqa: RUF022
+ 82    "Mesh",
+ 83    "PbcMesh",
+ 84    "BoundedMesh",
+ 85    "BilevelOptimizationMethod",
+ 86    "_BilevelOptimizer",
+ 87    "PbcBilevelOptimizer",
+ 88    "BoundedBilevelOptimizer",
+ 89    "plot_mesh",
+ 90    "get_plot_mesh",
+ 91    "FacePlot",
+ 92    "EdgePlot",
+ 93    "VertexPlot",
+ 94    "cost_v2v",
+ 95    "cost_mesh2image",
+ 96    "cost_areas",
+ 97    "cost_IAS",
+ 98    "cost_d_IAS",
+ 99    "cost_tem_halfedge",
+100    "cost_v2v_ias",
+101    "cost_v2v_tem",
+102    "cost_ratio",
+103    "cost_checkerboard",
+104    "energy_shape_factor_hetero",
+105    "energy_shape_factor_homo",
+106    "energy_line_tensions",
+107    "energy_line_tensions_bounded",
+108]
+
+ + +
+
+ +
+ + class + Mesh: + + + +
+ +
 37class Mesh(metaclass=NoPublicConstructor):
+ 38    """Generic mesh structure. It is an abstract base class, not to be used directly.
+ 39
+ 40    It defines common attributes and functions between `PbcMesh` and `BoundedMesh`.
+ 41    """
+ 42
+ 43    def __init__(self) -> None:
+ 44        """Do nothing but create attributes. Do not call this, but call specialized class methods to create meshes.
+ 45
+ 46        See `PbcMesh` and `BoundedMesh`.
+ 47
+ 48        Technically, it uses a DCEL structure.
+ 49        """
+ 50        self.faces: Array = jnp.array([])
+ 51        """The cells of the tissue."""
+ 52        self.edges: Array = jnp.array([])
+ 53        """The interface between cells. Technically half-edges."""
+ 54        self.vertices: Array = jnp.array([])
+ 55        """The mesh vertices, where cells meet."""
+ 56        self.width: float = 0
+ 57        """The mesh live in a rectangle of size [0, width] in the X direction."""
+ 58        self.height: float = 0
+ 59        """The mesh live in a rectangle of size [0, height] in the Y direction."""
+ 60
+ 61        self.faces_params: Array = jnp.array([])
+ 62        """Parameters attached to faces. Can be optimized."""
+ 63        self.edges_params: Array = jnp.array([])
+ 64        """Parameters attached to edges. Can be optimized."""
+ 65        self.vertices_params: Array = jnp.array([])
+ 66        """Parameters attached to vertices. Can be optimized."""
+ 67
+ 68    @property
+ 69    def nb_faces(self) -> int:
+ 70        """Get the number of faces of the mesh."""
+ 71        return len(self.faces)
+ 72
+ 73    @property
+ 74    def nb_edges(self) -> int:
+ 75        """Get the number of edges of the mesh."""
+ 76        return self.nb_half_edges // 2
+ 77
+ 78    @property
+ 79    def nb_half_edges(self) -> int:
+ 80        """Get the number of half-edges of the mesh, ie. twice the number of edges."""
+ 81        return len(self.edges)
+ 82
+ 83    @property
+ 84    def nb_vertices(self) -> int:
+ 85        """Get the number of vertices of the mesh."""
+ 86        return len(self.vertices)
+ 87
+ 88    def save_mesh(self, path: str) -> None:
+ 89        """Save mesh to a file.
+ 90
+ 91        All mesh data is saved.
+ 92
+ 93        Args:
+ 94            path (str): Path to the saved file. The extension is .npz.
+ 95        """
+ 96        raise NotImplementedError
+ 97
+ 98    @classmethod
+ 99    def load_mesh(cls, path: str) -> Self:
+100        """Load a mesh from a file.
+101
+102        Args:
+103            path (str): Path to the mesh file (.npz).
+104
+105        Returns:
+106            The mesh loaded from the .npz file.
+107        """
+108        raise NotImplementedError
+
+ + +

Generic mesh structure. It is an abstract base class, not to be used directly.

+ +

It defines common attributes and functions between PbcMesh and BoundedMesh.

+
+ + +
+ +
+ + Mesh() + + + +
+ +
43    def __init__(self) -> None:
+44        """Do nothing but create attributes. Do not call this, but call specialized class methods to create meshes.
+45
+46        See `PbcMesh` and `BoundedMesh`.
+47
+48        Technically, it uses a DCEL structure.
+49        """
+50        self.faces: Array = jnp.array([])
+51        """The cells of the tissue."""
+52        self.edges: Array = jnp.array([])
+53        """The interface between cells. Technically half-edges."""
+54        self.vertices: Array = jnp.array([])
+55        """The mesh vertices, where cells meet."""
+56        self.width: float = 0
+57        """The mesh live in a rectangle of size [0, width] in the X direction."""
+58        self.height: float = 0
+59        """The mesh live in a rectangle of size [0, height] in the Y direction."""
+60
+61        self.faces_params: Array = jnp.array([])
+62        """Parameters attached to faces. Can be optimized."""
+63        self.edges_params: Array = jnp.array([])
+64        """Parameters attached to edges. Can be optimized."""
+65        self.vertices_params: Array = jnp.array([])
+66        """Parameters attached to vertices. Can be optimized."""
+
+ + +

Do nothing but create attributes. Do not call this, but call specialized class methods to create meshes.

+ +

See PbcMesh and BoundedMesh.

+ +

Technically, it uses a DCEL structure.

+
+ + +
+
+
+ faces: jax.Array + + +
+ + +

The cells of the tissue.

+
+ + +
+
+
+ edges: jax.Array + + +
+ + +

The interface between cells. Technically half-edges.

+
+ + +
+
+
+ vertices: jax.Array + + +
+ + +

The mesh vertices, where cells meet.

+
+ + +
+
+
+ width: float + + +
+ + +

The mesh live in a rectangle of size [0, width] in the X direction.

+
+ + +
+
+
+ height: float + + +
+ + +

The mesh live in a rectangle of size [0, height] in the Y direction.

+
+ + +
+
+
+ faces_params: jax.Array + + +
+ + +

Parameters attached to faces. Can be optimized.

+
+ + +
+
+
+ edges_params: jax.Array + + +
+ + +

Parameters attached to edges. Can be optimized.

+
+ + +
+
+
+ vertices_params: jax.Array + + +
+ + +

Parameters attached to vertices. Can be optimized.

+
+ + +
+
+ +
+ nb_faces: int + + + +
+ +
68    @property
+69    def nb_faces(self) -> int:
+70        """Get the number of faces of the mesh."""
+71        return len(self.faces)
+
+ + +

Get the number of faces of the mesh.

+
+ + +
+
+ +
+ nb_edges: int + + + +
+ +
73    @property
+74    def nb_edges(self) -> int:
+75        """Get the number of edges of the mesh."""
+76        return self.nb_half_edges // 2
+
+ + +

Get the number of edges of the mesh.

+
+ + +
+
+ +
+ nb_half_edges: int + + + +
+ +
78    @property
+79    def nb_half_edges(self) -> int:
+80        """Get the number of half-edges of the mesh, ie. twice the number of edges."""
+81        return len(self.edges)
+
+ + +

Get the number of half-edges of the mesh, ie. twice the number of edges.

+
+ + +
+
+ +
+ nb_vertices: int + + + +
+ +
83    @property
+84    def nb_vertices(self) -> int:
+85        """Get the number of vertices of the mesh."""
+86        return len(self.vertices)
+
+ + +

Get the number of vertices of the mesh.

+
+ + +
+
+ +
+ + def + save_mesh(self, path: str) -> None: + + + +
+ +
88    def save_mesh(self, path: str) -> None:
+89        """Save mesh to a file.
+90
+91        All mesh data is saved.
+92
+93        Args:
+94            path (str): Path to the saved file. The extension is .npz.
+95        """
+96        raise NotImplementedError
+
+ + +

Save mesh to a file.

+ +

All mesh data is saved.

+ +
Arguments:
+ +
    +
  • path (str): Path to the saved file. The extension is .npz.
  • +
+
+ + +
+
+ +
+
@classmethod
+ + def + load_mesh(cls, path: str) -> Self: + + + +
+ +
 98    @classmethod
+ 99    def load_mesh(cls, path: str) -> Self:
+100        """Load a mesh from a file.
+101
+102        Args:
+103            path (str): Path to the mesh file (.npz).
+104
+105        Returns:
+106            The mesh loaded from the .npz file.
+107        """
+108        raise NotImplementedError
+
+ + +

Load a mesh from a file.

+ +
Arguments:
+ +
    +
  • path (str): Path to the mesh file (.npz).
  • +
+ +
Returns:
+ +
+

The mesh loaded from the .npz file.

+
+
+ + +
+
+
+ +
+ + class + PbcMesh(vertax.Mesh): + + + +
+ +
 21class PbcMesh(Mesh):
+ 22    """Periodic Boundary Condition on a mesh.
+ 23
+ 24    For a PbcMesh, `vertices` is a 2D array of floats of size (nb_vertices, 2) ;
+ 25    with the coordinates of the vertices (in ]0, width[ x ]0, height[ ).
+ 26
+ 27    `edges` is a  2D array of integers of size (nb_half_edges, 8), with:
+ 28
+ 29    - id of previous half-edge,
+ 30    - id of next half-edge,
+ 31    - id of twin half-edge,
+ 32    - id of source vertex,
+ 33    - id of target vertex,
+ 34    - id of the face containing the half-edge.
+ 35    - x-offset (either -1, 0 or 1) depending on the target vertex crossing a boundary.
+ 36    - y-offset (either -1, 0 or 1) depending on the target vertex crossing a boundary.
+ 37
+ 38    `faces` is a 1D array of integers of size (nb_faces) containing the id of a half-edge belonging to this face.
+ 39    """
+ 40
+ 41    def __init__(self) -> None:
+ 42        """Do not call the constructor directly, use the dedicated class methods such as:
+ 43
+ 44        - `from_random_seeds`,
+ 45        - `from_seeds`,
+ 46        - `from_image`,
+ 47        - `from_mask`,
+ 48        - `create_empty`.
+ 49        """  # noqa: D415
+ 50        super().__init__()
+ 51
+ 52        self.MAX_EDGES_IN_ANY_FACE: int = 20
+ 53        """Optimization parameter : must be more than the estimated maximum number of edges in a face. Base value is 20."""  # noqa: E501
+ 54
+ 55    @classmethod
+ 56    def from_random_seeds(cls, nb_seeds: int, width: float, height: float, random_key: int) -> Self:
+ 57        """Create a Periodic Boundary Conditions Mesh from a Voronoi diagram based on random seeds.
+ 58
+ 59        Args:
+ 60            nb_seeds (int): Number of random seeds to use.
+ 61            width (float): Width of the rectangular domains the seeds will be in.
+ 62            height (float): Height of the rectangular domains the seeds will be in.
+ 63            random_key (int): Set the random key for reproducibility.
+ 64
+ 65        Returns:
+ 66            Self: The corresponding mesh.
+ 67        """
+ 68        key = jax.random.PRNGKey(random_key)
+ 69        seeds = jnp.array((width, height)) * jax.random.uniform(key, (nb_seeds, 2))
+ 70        return cls.from_seeds(seeds, width, height)
+ 71
+ 72    @classmethod
+ 73    def from_seeds(cls, seeds: Array, width: float, height: float) -> Self:
+ 74        """Create a Periodic Boundary Conditions Mesh from a Voronoi diagram based on a list of seeds.
+ 75
+ 76        The seeds are assumed to have positive x and y positions.
+ 77
+ 78        Args:
+ 79            seeds (Array[float32]): jax float array of seed positions of shape (nbSeeds, 2).
+ 80            width (float): width of the box containing the seeds.
+ 81            height (float): height of the box containing the seeds.
+ 82        """
+ 83        (
+ 84            periodic_voronoi_vertices_idx,
+ 85            periodic_voronoi_vertices_pos,
+ 86            periodic_voronoi_edges,
+ 87            offsets,
+ 88            periodic_voronoi_faces,
+ 89        ) = _make_periodic(seeds, width, height)
+ 90
+ 91        vertices, edges, faces = _make_he_structure(
+ 92            width,
+ 93            height,
+ 94            periodic_voronoi_vertices_idx,
+ 95            periodic_voronoi_vertices_pos,
+ 96            periodic_voronoi_edges,
+ 97            offsets,
+ 98            periodic_voronoi_faces,
+ 99        )
+100
+101        pbc_mesh = cls._create()
+102        pbc_mesh.vertices = jnp.array(vertices, dtype=np.float32)
+103        pbc_mesh.edges = jnp.array(edges, dtype=np.int32)
+104        pbc_mesh.faces = jnp.array(faces, dtype=np.int32)
+105        pbc_mesh.width = width
+106        pbc_mesh.height = height
+107
+108        return pbc_mesh
+109
+110    @classmethod
+111    def from_image(
+112        cls,
+113        image: NDArray,
+114    ) -> Self:
+115        """Create a rudimentary mesh with periodic boundary conditions from an image.
+116
+117        To do that, we perform a segmentation using Cellpose and we try to fill the holes.
+118        The result will probably be imperfect and it will always be better if you
+119        provide directly a mask (with no holes) with the function "periodic_from_mask".
+120
+121        The mask is made periodic by mirroring its edges.
+122
+123        Args:
+124            image (NDArray): The image which will act as a template for the mesh.
+125
+126        Returns:
+127            Self: The corresponding mesh.
+128        """
+129        return cls.from_mask(mask_from_image(image))
+130
+131    @classmethod
+132    def from_mask(  # noqa: C901
+133        cls,
+134        mask: NDArray,
+135    ) -> Self:
+136        """Create a rudimentary mesh with periodic boundary conditions from a mask with no holes.
+137
+138        The mask is made periodic by mirroring its edges.
+139
+140        Args:
+141            mask (NDArray): The mask with no holes which will act as a template for the mesh.
+142
+143        Returns:
+144            Self: The corresponding mesh.
+145        """
+146        padded_mask = pad(mask, save=False, output_path="refined_and_padded_image.tiff")
+147        # Find vertices, edges, faces
+148        vertices, edges, faces = find_vertices_edges_faces(padded_mask)
+149
+150        # imread tiff = Y is the first axis, X the second.
+151        height: int = mask.shape[0]  # original image length. Padded is 3 times bigger.
+152        y_min = height / 2
+153        y_max = 2 * height + (height / 2)
+154        width: int = mask.shape[1]
+155        x_min = width / 2
+156        x_max = 2 * width + (width / 2)
+157
+158        col0_mask = (vertices[:, 0] >= x_min) & (vertices[:, 0] < x_max)
+159        col1_mask = (vertices[:, 1] >= y_min) & (vertices[:, 1] < y_max)
+160
+161        periodic_vertices_idx = np.arange(len(vertices))[col0_mask & col1_mask]
+162        periodic_vertices_pos = vertices[col0_mask & col1_mask]
+163
+164        # store map between vertex id -> inside vertex id
+165        inside_vertex: dict[int, int] = {idx: idx for idx in periodic_vertices_idx}
+166        for i, vertex in enumerate(vertices):
+167            if i not in periodic_vertices_idx:
+168                x, y = vertex
+169                if x < x_min:
+170                    x += 2 * width
+171                elif x >= x_max:
+172                    x -= 2 * width
+173
+174                if y < y_min:
+175                    y += 2 * height
+176                elif y >= y_max:
+177                    y -= 2 * height
+178
+179                # Find corresponding inside vertex to the outside dest vertex
+180                for idx, pos in zip(periodic_vertices_idx, periodic_vertices_pos, strict=True):
+181                    if np.max(np.abs(pos - [x, y])) < 1:
+182                        inside_vertex[i] = idx
+183                        break
+184
+185        edges_inside = []
+186        edges_outside = []
+187        offsets_inside = {}
+188        offsets_outside = {}
+189        visited = []
+190
+191        for e in edges:
+192            if e[0] in periodic_vertices_idx and e[1] in periodic_vertices_idx:
+193                edges_inside.append(tuple(sorted((e[0], e[1]))))
+194                offsets_inside[(e[0], e[1])] = (0, 0)
+195                offsets_inside[(e[1], e[0])] = (0, 0)
+196            elif bool(e[0] in periodic_vertices_idx) != bool(e[1] in periodic_vertices_idx):
+197                if e[0] in periodic_vertices_idx:
+198                    # origin in, dest out
+199                    # check x coord
+200                    if vertices[e[1]][0] < x_min:
+201                        offset_x1 = -1
+202                    elif vertices[e[1]][0] >= x_max:
+203                        offset_x1 = 1
+204                    else:
+205                        offset_x1 = 0
+206
+207                    # Now check y coord
+208                    if vertices[e[1]][1] < y_min:
+209                        offset_y1 = -1
+210                    elif vertices[e[1]][1] >= y_max:
+211                        offset_y1 = 1
+212                    else:
+213                        offset_y1 = 0
+214
+215                    # Find corresponding inside vertex to the outside dest vertex
+216                    if e[1] not in inside_vertex:
+217                        print(f"Error, no inside vertex found for vertex {e[1]}.")
+218                    else:
+219                        idx = inside_vertex[e[1]]
+220                        edges_outside.append(tuple(sorted((e[0], idx))))
+221                        if (e[0], e[1]) not in visited and (e[1], e[0]) not in visited:
+222                            offsets_outside[(e[0], idx)] = (offset_x1, offset_y1)
+223                            offsets_outside[(idx, e[0])] = (-offset_x1, -offset_y1)
+224                            visited.append((e[0], e[1]))
+225                            visited.append((e[1], e[0]))
+226                else:
+227                    # dest in, origin out
+228                    if vertices[e[0]][0] < x_min:
+229                        offset_x0 = -1
+230                    elif vertices[e[0]][0] >= x_max:
+231                        offset_x0 = 1
+232                    else:
+233                        offset_x0 = 0
+234
+235                    if vertices[e[0]][1] < y_min:
+236                        offset_y0 = -1
+237                    elif vertices[e[0]][1] >= y_max:
+238                        offset_y0 = 1
+239                    else:
+240                        offset_y0 = 0
+241
+242                    # Find corresponding inside vertex to the outside dest vertex
+243                    if e[0] not in inside_vertex:
+244                        print(f"Error, no inside vertex found for vertex {e[0]}.")
+245                    else:
+246                        idx = inside_vertex[e[0]]
+247                        edges_outside.append(tuple(sorted((idx, e[1]))))
+248                        if (e[0], e[1]) not in visited and (e[1], e[0]) not in visited:
+249                            offsets_outside[(idx, e[1])] = (-offset_x0, -offset_y0)
+250                            offsets_outside[(e[1], idx)] = (offset_x0, offset_y0)
+251                            visited.append((e[0], e[1]))
+252                            visited.append((e[1], e[0]))
+253
+254        periodic_edges = list(set(edges_inside)) + list(set(edges_outside))
+255        offsets = offsets_inside | offsets_outside
+256
+257        periodic_faces: list[set[int]] = [
+258            {inside_vertex[i] for i in face} for face in faces if any(v_id in periodic_vertices_idx for v_id in face)
+259        ]
+260
+261        vertices, edges, faces = _make_he_structure(
+262            2 * width,
+263            2 * height,
+264            periodic_vertices_idx,  # ty:ignore[invalid-argument-type]
+265            periodic_vertices_pos,
+266            periodic_edges,
+267            offsets,
+268            periodic_faces,
+269            vertices_offset=(x_min, y_min),
+270        )
+271
+272        pbc_mesh = cls._create()
+273        pbc_mesh.vertices = jnp.array(vertices, dtype=np.float32)
+274        pbc_mesh.edges = jnp.array(edges, dtype=np.int32)
+275        pbc_mesh.faces = jnp.array(faces, dtype=np.int32)
+276        pbc_mesh.width = 2 * width
+277        pbc_mesh.height = 2 * height
+278
+279        return pbc_mesh
+280
+281    @classmethod
+282    def create_empty(cls) -> Self:
+283        """Create an empty mesh. Use if you know what you're doing !"""
+284        return cls._create()
+285
+286    @classmethod
+287    def copy_mesh(cls, other_mesh: Self) -> Self:
+288        """Copy all parameters from another mesh in a new mesh."""
+289        mesh = cls._create()
+290        mesh.vertices = other_mesh.vertices.copy()
+291        mesh.edges = other_mesh.edges.copy()
+292        mesh.faces = other_mesh.faces.copy()
+293        mesh.width = other_mesh.width
+294        mesh.height = other_mesh.height
+295        mesh.vertices_params = other_mesh.vertices_params.copy()
+296        mesh.edges_params = other_mesh.edges_params.copy()
+297        mesh.faces_params = other_mesh.faces_params.copy()
+298        mesh.MAX_EDGES_IN_ANY_FACE = other_mesh.MAX_EDGES_IN_ANY_FACE
+299
+300        return mesh
+301
+302    def save_mesh(self, path: str) -> None:
+303        """Save mesh to a file.
+304
+305        All PBCMesh data is saved.
+306
+307        Args:
+308            path (str): Path to the saved file. The extension is .npz, a numpy format.
+309        """
+310        Path(path).parent.mkdir(parents=True, exist_ok=True)
+311        np.savez_compressed(
+312            path,
+313            allow_pickle=False,
+314            vertices=self.vertices,
+315            edges=self.edges,
+316            faces=self.faces,
+317            width=self.width,
+318            height=self.height,
+319            vertices_params=self.vertices_params,
+320            edges_params=self.edges_params,
+321            faces_params=self.faces_params,
+322            MAX_EDGES_IN_ANY_FACE=self.MAX_EDGES_IN_ANY_FACE,
+323        )
+324
+325    @classmethod
+326    def load_mesh(cls, path: str) -> Self:
+327        """Load a mesh from a file.
+328
+329        All PBCMesh data is reloaded.
+330
+331        Args:
+332            path (str): Path to the mesh file (.npz), numpy format.
+333
+334        Returns:
+335            Mesh: the mesh loaded from the numpy .npz file.
+336        """
+337        mesh_file = np.load(path)
+338        mesh = cls._create()
+339        mesh.vertices, mesh.edges, mesh.faces = (
+340            jnp.array(mesh_file["vertices"]),
+341            jnp.array(mesh_file["edges"].reshape(-1, 8)),
+342            jnp.array(mesh_file["faces"]),
+343        )
+344        mesh.width, mesh.height = float(mesh_file["width"]), float(mesh_file["height"])
+345        mesh.vertices_params = jnp.array(mesh_file["vertices_params"])
+346        mesh.edges_params = jnp.array(mesh_file["edges_params"])
+347        mesh.faces_params = jnp.array(mesh_file["faces_params"])
+348        mesh.MAX_EDGES_IN_ANY_FACE = mesh_file["MAX_EDGES_IN_ANY_FACE"]
+349        return mesh
+350
+351    def save_mesh_txt(
+352        self,
+353        directory: str,
+354        vertices_filename: str = "vertTable.txt",
+355        edges_filename: str = "heTable.txt",
+356        faces_filename: str = "faceTable.txt",
+357        vertices_params_filename: str = "vertParamsTable.txt",
+358        edges_params_filename: str = "heParamsTable.txt",
+359        faces_params_filename: str = "faceParamsTable.txt",
+360        constants_filename: str = "constants.txt",
+361    ) -> None:
+362        """Save a mesh in separate text files that can be read by numpy.
+363
+364        Only save the vertices, edges and faces, not other parameters.
+365
+366        Args:
+367            directory (str): Path to the directory where to save the files.
+368            vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt".
+369            edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt".
+370            faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt".
+371            vertices_params_filename (str, optional): Filename for the vertices parameters table.
+372                    Defaults to "vertParamsTable.txt".
+373            edges_params_filename (str, optional): Filename for the half-edges parameters table.
+374                    Defaults to "heParamsTable.txt".
+375            faces_params_filename (str, optional): Filename for the faces parameters table.
+376                    Defaults to "faceParamsTable.txt".
+377            constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE.
+378                    Defaults to "constants.txt".
+379        """
+380        dirpath = Path(directory)
+381        dirpath.mkdir(parents=True, exist_ok=True)
+382        np.savetxt(dirpath / vertices_filename, self.vertices)
+383        np.savetxt(dirpath / edges_filename, self.edges)
+384        np.savetxt(dirpath / faces_filename, self.faces)
+385        np.savetxt(dirpath / vertices_params_filename, self.vertices_params)
+386        np.savetxt(dirpath / edges_params_filename, self.edges_params)
+387        np.savetxt(dirpath / faces_params_filename, self.faces_params)
+388        with (dirpath / constants_filename).open("w") as f:
+389            f.write(f"{self.width} {self.height} {self.MAX_EDGES_IN_ANY_FACE}")
+390
+391    @classmethod
+392    def load_mesh_txt(
+393        cls,
+394        directory: str,
+395        vertices_filename: str = "vertTable.txt",
+396        edges_filename: str = "heTable.txt",
+397        faces_filename: str = "faceTable.txt",
+398        vertices_params_filename: str = "vertParamsTable.txt",
+399        edges_params_filename: str = "heParamsTable.txt",
+400        faces_params_filename: str = "faceParamsTable.txt",
+401        constants_filename: str = "constants.txt",
+402    ) -> Self:
+403        """Load a mesh from text files.
+404
+405        Only load the vertices, edges and faces, not other parameters.
+406
+407        Args:
+408            directory (str): Directory where the text files are stored.
+409            vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt".
+410            edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt".
+411            faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt".
+412            vertices_params_filename (str, optional): Filename for the vertices parameters table.
+413                    Defaults to "vertParamsTable.txt".
+414            edges_params_filename (str, optional): Filename for the half-edges parameters table.
+415                    Defaults to "heParamsTable.txt".
+416            faces_params_filename (str, optional): Filename for the faces parameters table.
+417                    Defaults to "faceParamsTable.txt".
+418            constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE.
+419                    Defaults to "constants.txt".
+420
+421        Returns:
+422            Self: The loaded mesh.
+423        """
+424        dirpath = Path(directory)
+425        dirpath.mkdir(parents=True, exist_ok=True)
+426
+427        mesh = cls._create()
+428        mesh.vertices = jnp.array(np.loadtxt(dirpath / vertices_filename, dtype=np.float64))
+429        mesh.edges = jnp.array(np.loadtxt(dirpath / edges_filename, dtype=np.int64))
+430        mesh.faces = jnp.array(np.loadtxt(dirpath / faces_filename, dtype=np.int64))
+431        mesh.vertices_params = jnp.array(np.loadtxt(dirpath / vertices_params_filename, dtype=np.float64))
+432        mesh.edges_params = jnp.array(np.loadtxt(dirpath / edges_params_filename, dtype=np.int64))
+433        mesh.faces_params = jnp.array(np.loadtxt(dirpath / faces_params_filename, dtype=np.int64))
+434        with (dirpath / constants_filename).open("r") as f:
+435            numbers = f.readline().split()
+436            mesh.width = float(numbers[0])
+437            mesh.height = float(numbers[1])
+438            mesh.MAX_EDGES_IN_ANY_FACE = int(numbers[2])
+439        return mesh
+440
+441    def get_length(self, half_edge_id: Array) -> Array:
+442        """Get the length of given edges."""
+443
+444        def _get_length(half_edge_id: Array) -> Array:
+445            return get_length(half_edge_id, self.vertices, self.edges, self.faces, self.width, self.height)
+446
+447        return jax.vmap(_get_length)(half_edge_id)
+448
+449    def get_length_with_offset(self, half_edge_id: Array) -> Array:
+450        """Get the length of given edges along with its offsets in an array (length, offset x, offset y)."""
+451
+452        def _get_length_with_offset(half_edge_id: Array) -> Array:
+453            return get_length_with_offset(half_edge_id, self.vertices, self.edges, self.faces, self.width, self.height)
+454
+455        return jax.vmap(_get_length_with_offset)(half_edge_id)
+456
+457    def get_perimeter(self, face_id: Array) -> Array:
+458        """Get the perimeter of given faces."""
+459
+460        def _get_perimeter(face_id: Array) -> Array:
+461            return get_perimeter(
+462                face_id, self.vertices, self.edges, self.faces, self.width, self.height, self.MAX_EDGES_IN_ANY_FACE
+463            )
+464
+465        return jax.vmap(_get_perimeter)(face_id)
+466
+467    def get_area(self, face_id: Array) -> Array:
+468        """Get the area of given faces."""
+469
+470        def _get_area(face_id: Array) -> Array:
+471            return get_area(
+472                face_id, self.vertices, self.edges, self.faces, self.width, self.height, self.MAX_EDGES_IN_ANY_FACE
+473            )
+474
+475        return jax.vmap(_get_area)(face_id)
+476
+477    def update_boundary_conditions(self) -> None:
+478        """Force periodic boundary conditions again after an update.
+479
+480        Most likely you'll never have to use it yourself, as it can be made automatically with a `PbcBilevelOptimizer`.
+481        """
+482        self.vertices, self.edges, self.faces = update_pbc(
+483            self.vertices, self.edges, self.faces, self.width, self.height
+484        )
+
+ + +

Periodic Boundary Condition on a mesh.

+ +

For a PbcMesh, vertices is a 2D array of floats of size (nb_vertices, 2) ; +with the coordinates of the vertices (in ]0, width[ x ]0, height[ ).

+ +

edges is a 2D array of integers of size (nb_half_edges, 8), with:

+ +
    +
  • id of previous half-edge,
  • +
  • id of next half-edge,
  • +
  • id of twin half-edge,
  • +
  • id of source vertex,
  • +
  • id of target vertex,
  • +
  • id of the face containing the half-edge.
  • +
  • x-offset (either -1, 0 or 1) depending on the target vertex crossing a boundary.
  • +
  • y-offset (either -1, 0 or 1) depending on the target vertex crossing a boundary.
  • +
+ +

faces is a 1D array of integers of size (nb_faces) containing the id of a half-edge belonging to this face.

+
+ + +
+ +
+ + PbcMesh() + + + +
+ +
41    def __init__(self) -> None:
+42        """Do not call the constructor directly, use the dedicated class methods such as:
+43
+44        - `from_random_seeds`,
+45        - `from_seeds`,
+46        - `from_image`,
+47        - `from_mask`,
+48        - `create_empty`.
+49        """  # noqa: D415
+50        super().__init__()
+51
+52        self.MAX_EDGES_IN_ANY_FACE: int = 20
+53        """Optimization parameter : must be more than the estimated maximum number of edges in a face. Base value is 20."""  # noqa: E501
+
+ + +

Do not call the constructor directly, use the dedicated class methods such as:

+ + +
+ + +
+
+
+ MAX_EDGES_IN_ANY_FACE: int + + +
+ + +

Optimization parameter : must be more than the estimated maximum number of edges in a face. Base value is 20.

+
+ + +
+
+ +
+
@classmethod
+ + def + from_random_seeds(cls, nb_seeds: int, width: float, height: float, random_key: int) -> Self: + + + +
+ +
55    @classmethod
+56    def from_random_seeds(cls, nb_seeds: int, width: float, height: float, random_key: int) -> Self:
+57        """Create a Periodic Boundary Conditions Mesh from a Voronoi diagram based on random seeds.
+58
+59        Args:
+60            nb_seeds (int): Number of random seeds to use.
+61            width (float): Width of the rectangular domains the seeds will be in.
+62            height (float): Height of the rectangular domains the seeds will be in.
+63            random_key (int): Set the random key for reproducibility.
+64
+65        Returns:
+66            Self: The corresponding mesh.
+67        """
+68        key = jax.random.PRNGKey(random_key)
+69        seeds = jnp.array((width, height)) * jax.random.uniform(key, (nb_seeds, 2))
+70        return cls.from_seeds(seeds, width, height)
+
+ + +

Create a Periodic Boundary Conditions Mesh from a Voronoi diagram based on random seeds.

+ +
Arguments:
+ +
    +
  • nb_seeds (int): Number of random seeds to use.
  • +
  • width (float): Width of the rectangular domains the seeds will be in.
  • +
  • height (float): Height of the rectangular domains the seeds will be in.
  • +
  • random_key (int): Set the random key for reproducibility.
  • +
+ +
Returns:
+ +
+

Self: The corresponding mesh.

+
+
+ + +
+
+ +
+
@classmethod
+ + def + from_seeds(cls, seeds: jax.Array, width: float, height: float) -> Self: + + + +
+ +
 72    @classmethod
+ 73    def from_seeds(cls, seeds: Array, width: float, height: float) -> Self:
+ 74        """Create a Periodic Boundary Conditions Mesh from a Voronoi diagram based on a list of seeds.
+ 75
+ 76        The seeds are assumed to have positive x and y positions.
+ 77
+ 78        Args:
+ 79            seeds (Array[float32]): jax float array of seed positions of shape (nbSeeds, 2).
+ 80            width (float): width of the box containing the seeds.
+ 81            height (float): height of the box containing the seeds.
+ 82        """
+ 83        (
+ 84            periodic_voronoi_vertices_idx,
+ 85            periodic_voronoi_vertices_pos,
+ 86            periodic_voronoi_edges,
+ 87            offsets,
+ 88            periodic_voronoi_faces,
+ 89        ) = _make_periodic(seeds, width, height)
+ 90
+ 91        vertices, edges, faces = _make_he_structure(
+ 92            width,
+ 93            height,
+ 94            periodic_voronoi_vertices_idx,
+ 95            periodic_voronoi_vertices_pos,
+ 96            periodic_voronoi_edges,
+ 97            offsets,
+ 98            periodic_voronoi_faces,
+ 99        )
+100
+101        pbc_mesh = cls._create()
+102        pbc_mesh.vertices = jnp.array(vertices, dtype=np.float32)
+103        pbc_mesh.edges = jnp.array(edges, dtype=np.int32)
+104        pbc_mesh.faces = jnp.array(faces, dtype=np.int32)
+105        pbc_mesh.width = width
+106        pbc_mesh.height = height
+107
+108        return pbc_mesh
+
+ + +

Create a Periodic Boundary Conditions Mesh from a Voronoi diagram based on a list of seeds.

+ +

The seeds are assumed to have positive x and y positions.

+ +
Arguments:
+ +
    +
  • seeds (Array[float32]): jax float array of seed positions of shape (nbSeeds, 2).
  • +
  • width (float): width of the box containing the seeds.
  • +
  • height (float): height of the box containing the seeds.
  • +
+
+ + +
+
+ +
+
@classmethod
+ + def + from_image( cls, image: numpy.ndarray[typing.Any, numpy.dtype[+_ScalarType_co]]) -> Self: + + + +
+ +
110    @classmethod
+111    def from_image(
+112        cls,
+113        image: NDArray,
+114    ) -> Self:
+115        """Create a rudimentary mesh with periodic boundary conditions from an image.
+116
+117        To do that, we perform a segmentation using Cellpose and we try to fill the holes.
+118        The result will probably be imperfect and it will always be better if you
+119        provide directly a mask (with no holes) with the function "periodic_from_mask".
+120
+121        The mask is made periodic by mirroring its edges.
+122
+123        Args:
+124            image (NDArray): The image which will act as a template for the mesh.
+125
+126        Returns:
+127            Self: The corresponding mesh.
+128        """
+129        return cls.from_mask(mask_from_image(image))
+
+ + +

Create a rudimentary mesh with periodic boundary conditions from an image.

+ +

To do that, we perform a segmentation using Cellpose and we try to fill the holes. +The result will probably be imperfect and it will always be better if you +provide directly a mask (with no holes) with the function "periodic_from_mask".

+ +

The mask is made periodic by mirroring its edges.

+ +
Arguments:
+ +
    +
  • image (NDArray): The image which will act as a template for the mesh.
  • +
+ +
Returns:
+ +
+

Self: The corresponding mesh.

+
+
+ + +
+
+ +
+
@classmethod
+ + def + from_mask( cls, mask: numpy.ndarray[typing.Any, numpy.dtype[+_ScalarType_co]]) -> Self: + + + +
+ +
131    @classmethod
+132    def from_mask(  # noqa: C901
+133        cls,
+134        mask: NDArray,
+135    ) -> Self:
+136        """Create a rudimentary mesh with periodic boundary conditions from a mask with no holes.
+137
+138        The mask is made periodic by mirroring its edges.
+139
+140        Args:
+141            mask (NDArray): The mask with no holes which will act as a template for the mesh.
+142
+143        Returns:
+144            Self: The corresponding mesh.
+145        """
+146        padded_mask = pad(mask, save=False, output_path="refined_and_padded_image.tiff")
+147        # Find vertices, edges, faces
+148        vertices, edges, faces = find_vertices_edges_faces(padded_mask)
+149
+150        # imread tiff = Y is the first axis, X the second.
+151        height: int = mask.shape[0]  # original image length. Padded is 3 times bigger.
+152        y_min = height / 2
+153        y_max = 2 * height + (height / 2)
+154        width: int = mask.shape[1]
+155        x_min = width / 2
+156        x_max = 2 * width + (width / 2)
+157
+158        col0_mask = (vertices[:, 0] >= x_min) & (vertices[:, 0] < x_max)
+159        col1_mask = (vertices[:, 1] >= y_min) & (vertices[:, 1] < y_max)
+160
+161        periodic_vertices_idx = np.arange(len(vertices))[col0_mask & col1_mask]
+162        periodic_vertices_pos = vertices[col0_mask & col1_mask]
+163
+164        # store map between vertex id -> inside vertex id
+165        inside_vertex: dict[int, int] = {idx: idx for idx in periodic_vertices_idx}
+166        for i, vertex in enumerate(vertices):
+167            if i not in periodic_vertices_idx:
+168                x, y = vertex
+169                if x < x_min:
+170                    x += 2 * width
+171                elif x >= x_max:
+172                    x -= 2 * width
+173
+174                if y < y_min:
+175                    y += 2 * height
+176                elif y >= y_max:
+177                    y -= 2 * height
+178
+179                # Find corresponding inside vertex to the outside dest vertex
+180                for idx, pos in zip(periodic_vertices_idx, periodic_vertices_pos, strict=True):
+181                    if np.max(np.abs(pos - [x, y])) < 1:
+182                        inside_vertex[i] = idx
+183                        break
+184
+185        edges_inside = []
+186        edges_outside = []
+187        offsets_inside = {}
+188        offsets_outside = {}
+189        visited = []
+190
+191        for e in edges:
+192            if e[0] in periodic_vertices_idx and e[1] in periodic_vertices_idx:
+193                edges_inside.append(tuple(sorted((e[0], e[1]))))
+194                offsets_inside[(e[0], e[1])] = (0, 0)
+195                offsets_inside[(e[1], e[0])] = (0, 0)
+196            elif bool(e[0] in periodic_vertices_idx) != bool(e[1] in periodic_vertices_idx):
+197                if e[0] in periodic_vertices_idx:
+198                    # origin in, dest out
+199                    # check x coord
+200                    if vertices[e[1]][0] < x_min:
+201                        offset_x1 = -1
+202                    elif vertices[e[1]][0] >= x_max:
+203                        offset_x1 = 1
+204                    else:
+205                        offset_x1 = 0
+206
+207                    # Now check y coord
+208                    if vertices[e[1]][1] < y_min:
+209                        offset_y1 = -1
+210                    elif vertices[e[1]][1] >= y_max:
+211                        offset_y1 = 1
+212                    else:
+213                        offset_y1 = 0
+214
+215                    # Find corresponding inside vertex to the outside dest vertex
+216                    if e[1] not in inside_vertex:
+217                        print(f"Error, no inside vertex found for vertex {e[1]}.")
+218                    else:
+219                        idx = inside_vertex[e[1]]
+220                        edges_outside.append(tuple(sorted((e[0], idx))))
+221                        if (e[0], e[1]) not in visited and (e[1], e[0]) not in visited:
+222                            offsets_outside[(e[0], idx)] = (offset_x1, offset_y1)
+223                            offsets_outside[(idx, e[0])] = (-offset_x1, -offset_y1)
+224                            visited.append((e[0], e[1]))
+225                            visited.append((e[1], e[0]))
+226                else:
+227                    # dest in, origin out
+228                    if vertices[e[0]][0] < x_min:
+229                        offset_x0 = -1
+230                    elif vertices[e[0]][0] >= x_max:
+231                        offset_x0 = 1
+232                    else:
+233                        offset_x0 = 0
+234
+235                    if vertices[e[0]][1] < y_min:
+236                        offset_y0 = -1
+237                    elif vertices[e[0]][1] >= y_max:
+238                        offset_y0 = 1
+239                    else:
+240                        offset_y0 = 0
+241
+242                    # Find corresponding inside vertex to the outside dest vertex
+243                    if e[0] not in inside_vertex:
+244                        print(f"Error, no inside vertex found for vertex {e[0]}.")
+245                    else:
+246                        idx = inside_vertex[e[0]]
+247                        edges_outside.append(tuple(sorted((idx, e[1]))))
+248                        if (e[0], e[1]) not in visited and (e[1], e[0]) not in visited:
+249                            offsets_outside[(idx, e[1])] = (-offset_x0, -offset_y0)
+250                            offsets_outside[(e[1], idx)] = (offset_x0, offset_y0)
+251                            visited.append((e[0], e[1]))
+252                            visited.append((e[1], e[0]))
+253
+254        periodic_edges = list(set(edges_inside)) + list(set(edges_outside))
+255        offsets = offsets_inside | offsets_outside
+256
+257        periodic_faces: list[set[int]] = [
+258            {inside_vertex[i] for i in face} for face in faces if any(v_id in periodic_vertices_idx for v_id in face)
+259        ]
+260
+261        vertices, edges, faces = _make_he_structure(
+262            2 * width,
+263            2 * height,
+264            periodic_vertices_idx,  # ty:ignore[invalid-argument-type]
+265            periodic_vertices_pos,
+266            periodic_edges,
+267            offsets,
+268            periodic_faces,
+269            vertices_offset=(x_min, y_min),
+270        )
+271
+272        pbc_mesh = cls._create()
+273        pbc_mesh.vertices = jnp.array(vertices, dtype=np.float32)
+274        pbc_mesh.edges = jnp.array(edges, dtype=np.int32)
+275        pbc_mesh.faces = jnp.array(faces, dtype=np.int32)
+276        pbc_mesh.width = 2 * width
+277        pbc_mesh.height = 2 * height
+278
+279        return pbc_mesh
+
+ + +

Create a rudimentary mesh with periodic boundary conditions from a mask with no holes.

+ +

The mask is made periodic by mirroring its edges.

+ +
Arguments:
+ +
    +
  • mask (NDArray): The mask with no holes which will act as a template for the mesh.
  • +
+ +
Returns:
+ +
+

Self: The corresponding mesh.

+
+
+ + +
+
+ +
+
@classmethod
+ + def + create_empty(cls) -> Self: + + + +
+ +
281    @classmethod
+282    def create_empty(cls) -> Self:
+283        """Create an empty mesh. Use if you know what you're doing !"""
+284        return cls._create()
+
+ + +

Create an empty mesh. Use if you know what you're doing !

+
+ + +
+
+ +
+
@classmethod
+ + def + copy_mesh(cls, other_mesh: Self) -> Self: + + + +
+ +
286    @classmethod
+287    def copy_mesh(cls, other_mesh: Self) -> Self:
+288        """Copy all parameters from another mesh in a new mesh."""
+289        mesh = cls._create()
+290        mesh.vertices = other_mesh.vertices.copy()
+291        mesh.edges = other_mesh.edges.copy()
+292        mesh.faces = other_mesh.faces.copy()
+293        mesh.width = other_mesh.width
+294        mesh.height = other_mesh.height
+295        mesh.vertices_params = other_mesh.vertices_params.copy()
+296        mesh.edges_params = other_mesh.edges_params.copy()
+297        mesh.faces_params = other_mesh.faces_params.copy()
+298        mesh.MAX_EDGES_IN_ANY_FACE = other_mesh.MAX_EDGES_IN_ANY_FACE
+299
+300        return mesh
+
+ + +

Copy all parameters from another mesh in a new mesh.

+
+ + +
+
+ +
+ + def + save_mesh(self, path: str) -> None: + + + +
+ +
302    def save_mesh(self, path: str) -> None:
+303        """Save mesh to a file.
+304
+305        All PBCMesh data is saved.
+306
+307        Args:
+308            path (str): Path to the saved file. The extension is .npz, a numpy format.
+309        """
+310        Path(path).parent.mkdir(parents=True, exist_ok=True)
+311        np.savez_compressed(
+312            path,
+313            allow_pickle=False,
+314            vertices=self.vertices,
+315            edges=self.edges,
+316            faces=self.faces,
+317            width=self.width,
+318            height=self.height,
+319            vertices_params=self.vertices_params,
+320            edges_params=self.edges_params,
+321            faces_params=self.faces_params,
+322            MAX_EDGES_IN_ANY_FACE=self.MAX_EDGES_IN_ANY_FACE,
+323        )
+
+ + +

Save mesh to a file.

+ +

All PBCMesh data is saved.

+ +
Arguments:
+ +
    +
  • path (str): Path to the saved file. The extension is .npz, a numpy format.
  • +
+
+ + +
+
+ +
+
@classmethod
+ + def + load_mesh(cls, path: str) -> Self: + + + +
+ +
325    @classmethod
+326    def load_mesh(cls, path: str) -> Self:
+327        """Load a mesh from a file.
+328
+329        All PBCMesh data is reloaded.
+330
+331        Args:
+332            path (str): Path to the mesh file (.npz), numpy format.
+333
+334        Returns:
+335            Mesh: the mesh loaded from the numpy .npz file.
+336        """
+337        mesh_file = np.load(path)
+338        mesh = cls._create()
+339        mesh.vertices, mesh.edges, mesh.faces = (
+340            jnp.array(mesh_file["vertices"]),
+341            jnp.array(mesh_file["edges"].reshape(-1, 8)),
+342            jnp.array(mesh_file["faces"]),
+343        )
+344        mesh.width, mesh.height = float(mesh_file["width"]), float(mesh_file["height"])
+345        mesh.vertices_params = jnp.array(mesh_file["vertices_params"])
+346        mesh.edges_params = jnp.array(mesh_file["edges_params"])
+347        mesh.faces_params = jnp.array(mesh_file["faces_params"])
+348        mesh.MAX_EDGES_IN_ANY_FACE = mesh_file["MAX_EDGES_IN_ANY_FACE"]
+349        return mesh
+
+ + +

Load a mesh from a file.

+ +

All PBCMesh data is reloaded.

+ +
Arguments:
+ +
    +
  • path (str): Path to the mesh file (.npz), numpy format.
  • +
+ +
Returns:
+ +
+

Mesh: the mesh loaded from the numpy .npz file.

+
+
+ + +
+
+ +
+ + def + save_mesh_txt( self, directory: str, vertices_filename: str = 'vertTable.txt', edges_filename: str = 'heTable.txt', faces_filename: str = 'faceTable.txt', vertices_params_filename: str = 'vertParamsTable.txt', edges_params_filename: str = 'heParamsTable.txt', faces_params_filename: str = 'faceParamsTable.txt', constants_filename: str = 'constants.txt') -> None: + + + +
+ +
351    def save_mesh_txt(
+352        self,
+353        directory: str,
+354        vertices_filename: str = "vertTable.txt",
+355        edges_filename: str = "heTable.txt",
+356        faces_filename: str = "faceTable.txt",
+357        vertices_params_filename: str = "vertParamsTable.txt",
+358        edges_params_filename: str = "heParamsTable.txt",
+359        faces_params_filename: str = "faceParamsTable.txt",
+360        constants_filename: str = "constants.txt",
+361    ) -> None:
+362        """Save a mesh in separate text files that can be read by numpy.
+363
+364        Only save the vertices, edges and faces, not other parameters.
+365
+366        Args:
+367            directory (str): Path to the directory where to save the files.
+368            vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt".
+369            edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt".
+370            faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt".
+371            vertices_params_filename (str, optional): Filename for the vertices parameters table.
+372                    Defaults to "vertParamsTable.txt".
+373            edges_params_filename (str, optional): Filename for the half-edges parameters table.
+374                    Defaults to "heParamsTable.txt".
+375            faces_params_filename (str, optional): Filename for the faces parameters table.
+376                    Defaults to "faceParamsTable.txt".
+377            constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE.
+378                    Defaults to "constants.txt".
+379        """
+380        dirpath = Path(directory)
+381        dirpath.mkdir(parents=True, exist_ok=True)
+382        np.savetxt(dirpath / vertices_filename, self.vertices)
+383        np.savetxt(dirpath / edges_filename, self.edges)
+384        np.savetxt(dirpath / faces_filename, self.faces)
+385        np.savetxt(dirpath / vertices_params_filename, self.vertices_params)
+386        np.savetxt(dirpath / edges_params_filename, self.edges_params)
+387        np.savetxt(dirpath / faces_params_filename, self.faces_params)
+388        with (dirpath / constants_filename).open("w") as f:
+389            f.write(f"{self.width} {self.height} {self.MAX_EDGES_IN_ANY_FACE}")
+
+ + +

Save a mesh in separate text files that can be read by numpy.

+ +

Only save the vertices, edges and faces, not other parameters.

+ +
Arguments:
+ +
    +
  • directory (str): Path to the directory where to save the files.
  • +
  • vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt".
  • +
  • edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt".
  • +
  • faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt".
  • +
  • vertices_params_filename (str, optional): Filename for the vertices parameters table. +Defaults to "vertParamsTable.txt".
  • +
  • edges_params_filename (str, optional): Filename for the half-edges parameters table. +Defaults to "heParamsTable.txt".
  • +
  • faces_params_filename (str, optional): Filename for the faces parameters table. +Defaults to "faceParamsTable.txt".
  • +
  • constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE. +Defaults to "constants.txt".
  • +
+
+ + +
+
+ +
+
@classmethod
+ + def + load_mesh_txt( cls, directory: str, vertices_filename: str = 'vertTable.txt', edges_filename: str = 'heTable.txt', faces_filename: str = 'faceTable.txt', vertices_params_filename: str = 'vertParamsTable.txt', edges_params_filename: str = 'heParamsTable.txt', faces_params_filename: str = 'faceParamsTable.txt', constants_filename: str = 'constants.txt') -> Self: + + + +
+ +
391    @classmethod
+392    def load_mesh_txt(
+393        cls,
+394        directory: str,
+395        vertices_filename: str = "vertTable.txt",
+396        edges_filename: str = "heTable.txt",
+397        faces_filename: str = "faceTable.txt",
+398        vertices_params_filename: str = "vertParamsTable.txt",
+399        edges_params_filename: str = "heParamsTable.txt",
+400        faces_params_filename: str = "faceParamsTable.txt",
+401        constants_filename: str = "constants.txt",
+402    ) -> Self:
+403        """Load a mesh from text files.
+404
+405        Only load the vertices, edges and faces, not other parameters.
+406
+407        Args:
+408            directory (str): Directory where the text files are stored.
+409            vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt".
+410            edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt".
+411            faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt".
+412            vertices_params_filename (str, optional): Filename for the vertices parameters table.
+413                    Defaults to "vertParamsTable.txt".
+414            edges_params_filename (str, optional): Filename for the half-edges parameters table.
+415                    Defaults to "heParamsTable.txt".
+416            faces_params_filename (str, optional): Filename for the faces parameters table.
+417                    Defaults to "faceParamsTable.txt".
+418            constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE.
+419                    Defaults to "constants.txt".
+420
+421        Returns:
+422            Self: The loaded mesh.
+423        """
+424        dirpath = Path(directory)
+425        dirpath.mkdir(parents=True, exist_ok=True)
+426
+427        mesh = cls._create()
+428        mesh.vertices = jnp.array(np.loadtxt(dirpath / vertices_filename, dtype=np.float64))
+429        mesh.edges = jnp.array(np.loadtxt(dirpath / edges_filename, dtype=np.int64))
+430        mesh.faces = jnp.array(np.loadtxt(dirpath / faces_filename, dtype=np.int64))
+431        mesh.vertices_params = jnp.array(np.loadtxt(dirpath / vertices_params_filename, dtype=np.float64))
+432        mesh.edges_params = jnp.array(np.loadtxt(dirpath / edges_params_filename, dtype=np.int64))
+433        mesh.faces_params = jnp.array(np.loadtxt(dirpath / faces_params_filename, dtype=np.int64))
+434        with (dirpath / constants_filename).open("r") as f:
+435            numbers = f.readline().split()
+436            mesh.width = float(numbers[0])
+437            mesh.height = float(numbers[1])
+438            mesh.MAX_EDGES_IN_ANY_FACE = int(numbers[2])
+439        return mesh
+
+ + +

Load a mesh from text files.

+ +

Only load the vertices, edges and faces, not other parameters.

+ +
Arguments:
+ +
    +
  • directory (str): Directory where the text files are stored.
  • +
  • vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt".
  • +
  • edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt".
  • +
  • faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt".
  • +
  • vertices_params_filename (str, optional): Filename for the vertices parameters table. +Defaults to "vertParamsTable.txt".
  • +
  • edges_params_filename (str, optional): Filename for the half-edges parameters table. +Defaults to "heParamsTable.txt".
  • +
  • faces_params_filename (str, optional): Filename for the faces parameters table. +Defaults to "faceParamsTable.txt".
  • +
  • constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE. +Defaults to "constants.txt".
  • +
+ +
Returns:
+ +
+

Self: The loaded mesh.

+
+
+ + +
+
+ +
+ + def + get_length(self, half_edge_id: jax.Array) -> jax.Array: + + + +
+ +
441    def get_length(self, half_edge_id: Array) -> Array:
+442        """Get the length of given edges."""
+443
+444        def _get_length(half_edge_id: Array) -> Array:
+445            return get_length(half_edge_id, self.vertices, self.edges, self.faces, self.width, self.height)
+446
+447        return jax.vmap(_get_length)(half_edge_id)
+
+ + +

Get the length of given edges.

+
+ + +
+
+ +
+ + def + get_length_with_offset(self, half_edge_id: jax.Array) -> jax.Array: + + + +
+ +
449    def get_length_with_offset(self, half_edge_id: Array) -> Array:
+450        """Get the length of given edges along with its offsets in an array (length, offset x, offset y)."""
+451
+452        def _get_length_with_offset(half_edge_id: Array) -> Array:
+453            return get_length_with_offset(half_edge_id, self.vertices, self.edges, self.faces, self.width, self.height)
+454
+455        return jax.vmap(_get_length_with_offset)(half_edge_id)
+
+ + +

Get the length of given edges along with its offsets in an array (length, offset x, offset y).

+
+ + +
+
+ +
+ + def + get_perimeter(self, face_id: jax.Array) -> jax.Array: + + + +
+ +
457    def get_perimeter(self, face_id: Array) -> Array:
+458        """Get the perimeter of given faces."""
+459
+460        def _get_perimeter(face_id: Array) -> Array:
+461            return get_perimeter(
+462                face_id, self.vertices, self.edges, self.faces, self.width, self.height, self.MAX_EDGES_IN_ANY_FACE
+463            )
+464
+465        return jax.vmap(_get_perimeter)(face_id)
+
+ + +

Get the perimeter of given faces.

+
+ + +
+
+ +
+ + def + get_area(self, face_id: jax.Array) -> jax.Array: + + + +
+ +
467    def get_area(self, face_id: Array) -> Array:
+468        """Get the area of given faces."""
+469
+470        def _get_area(face_id: Array) -> Array:
+471            return get_area(
+472                face_id, self.vertices, self.edges, self.faces, self.width, self.height, self.MAX_EDGES_IN_ANY_FACE
+473            )
+474
+475        return jax.vmap(_get_area)(face_id)
+
+ + +

Get the area of given faces.

+
+ + +
+
+ +
+ + def + update_boundary_conditions(self) -> None: + + + +
+ +
477    def update_boundary_conditions(self) -> None:
+478        """Force periodic boundary conditions again after an update.
+479
+480        Most likely you'll never have to use it yourself, as it can be made automatically with a `PbcBilevelOptimizer`.
+481        """
+482        self.vertices, self.edges, self.faces = update_pbc(
+483            self.vertices, self.edges, self.faces, self.width, self.height
+484        )
+
+ + +

Force periodic boundary conditions again after an update.

+ +

Most likely you'll never have to use it yourself, as it can be made automatically with a PbcBilevelOptimizer.

+
+ + +
+
+
+ +
+ + class + BoundedMesh(vertax.Mesh): + + + +
+ +
 25class BoundedMesh(Mesh):
+ 26    """Bounded mesh with arc circles for boundary cells.
+ 27
+ 28    For a BoundedMesh, `vertices` is a 2D array of floats of size (nb_vertices, 2) ;
+ 29    with the coordinates of the vertices (in ]0, width[ x ]0, height[ ).
+ 30
+ 31    `edges` is a  2D array of integers of size (nb_half_edges, 8), with:
+ 32
+ 33    - id of previous half-edge,
+ 34    - id of next half-edge,
+ 35    - id of twin half-edge,
+ 36    - id of source vertex + 2 if current half-edge is an inside edge, else 0,
+ 37    - id of target vertex + 2 if current half-edge is an inside edge, else 1,
+ 38    - id of source vertex + 2 if current half-edge is an outside edge, else 0,
+ 39    - id of target vertex + 2 if current half-edge is an outside edge, else 1,
+ 40    - id of the face containing the half-edge.
+ 41
+ 42    `faces` is a 1D array of integers of size (nb_faces) containing the id of a half-edge belonging to this face.
+ 43
+ 44    `angles` is a 1D array of floats of size (nb_angles), with the angles sustaining the arcs of the free interfaces ;
+ 45    between 0 and PI / 2.
+ 46    """
+ 47
+ 48    def __init__(self) -> None:
+ 49        """Do not call the constructor."""
+ 50        super().__init__()
+ 51        self.angles: Array = jnp.array([])
+ 52        """Angle sustaining the arced free interfaces. Between 0 and PI / 2."""
+ 53
+ 54    @property
+ 55    def nb_angles(self) -> int:
+ 56        """Get the number of angles (free interfaces) of the mesh."""
+ 57        return len(self.angles)
+ 58
+ 59    @classmethod
+ 60    def from_random_seeds(cls, nb_seeds: int, width: float, height: float, random_key: int, nb_fates: int = 2) -> Self:
+ 61        """Create a bounded Mesh from random seeds, based on a Voronoi diagram with arced free interfaces.
+ 62
+ 63        Args:
+ 64            nb_seeds (int): Number of random seeds to use.
+ 65            width (float): width of the box containing the seeds.
+ 66            height (float): height of the box containing the seeds.
+ 67            random_key (int): seed for a random number generator to add new seeds if needed, decide on cell fates...
+ 68            nb_fates (int, default=2): number of possible different fate marker for a cell.
+ 69
+ 70        Returns:
+ 71            Self: The corresponding mesh.
+ 72        """
+ 73        rng = np.random.default_rng(seed=random_key)
+ 74        seeds = rng.random((nb_seeds, 2)) * (width, height)
+ 75        return cls.from_seeds(seeds, width, height, random_key, nb_fates)
+ 76
+ 77    @classmethod
+ 78    def from_seeds(cls, seeds: NDArray, width: float, height: float, random_key: int, nb_fates: int = 2) -> Self:  # noqa: C901
+ 79        """Create a bounded Mesh from a list of given seeds.
+ 80
+ 81        The seeds are assumed to have x-coordinate in ]0, width[ and y-coordinate in ]0, height[.
+ 82        Note that the final mesh might not use your seeds if they don't work to create a correct
+ 83        bounded mesh via our method.
+ 84
+ 85        Args:
+ 86            seeds (Array[float32]): jax float array of seed positions of shape (nbSeeds, 2).
+ 87            width (float): width of the box containing the seeds.
+ 88            height (float): height of the box containing the seeds.
+ 89            random_key (int): seed for a random number generator to add new seeds if needed, decide on cell fates...
+ 90            nb_fates (int, default=2): number of possible different fate marker for a cell.
+ 91        """
+ 92        rng = np.random.default_rng(seed=random_key)
+ 93        n_cells = len(seeds)  # starting number of seeds must be equal to the desired number of cells (faces)
+ 94
+ 95        # We'll try to construct a Voronoi diagram with n_cells closed (bounded) cells.
+ 96        # This will not work with exactly n_cells seeds because there will be unbounded cells.
+ 97        # If the number of bounded cells is insufficient, we add a new seed to the list.
+ 98        # If there is too many bounded cells, we retry with entirely new seeds.
+ 99        # We also check that the bounded cells are connected.
+100        while True:
+101            success = 0  # if 1 : not enough bounded cells. If 2 : too many bounded cells.
+102
+103            # Create the Voronoi diagrams from current seed list.
+104            voronoi = Voronoi(seeds)
+105            vertices = voronoi.vertices
+106            edges = voronoi.ridge_vertices
+107            faces = voronoi.regions  # regions = faces = cells
+108
+109            # We count the number of bounded cells and the connectivity of vertices.
+110            inbound_faces = []
+111            inbound_vertices = np.zeros(vertices.shape[0], dtype=np.int32)
+112            for face in faces:
+113                if face and all(item > -1 for item in face):  # the face must not be an empty list
+114                    face_vertices_positions = vertices[face]
+115                    # We check that all of the face's vertices are in a box [0, width]x[0, height]
+116                    if (
+117                        np.all(face_vertices_positions[:, 0] < width)
+118                        and np.all(face_vertices_positions[:, 1] < height)
+119                        and np.all(face_vertices_positions > 0)
+120                    ):
+121                        inbound_faces.append(face)  # the face is bounded
+122                        inbound_vertices[face] += 1  # +1 to the connectivity of the face's vertices.
+123
+124            # getting rid of faces connected to a single other inbound face
+125            # (these can be problematic and lead to many special cases later on)
+126            while True:
+127                num_infaces = len(inbound_faces)
+128                del_count = 0
+129                for i, face in enumerate(reversed(inbound_faces)):
+130                    # only 2 (or less (not sure it's possible)) vertices of the face are shared with other faces :
+131                    # that means it is connected to one other bounded face only -> We remove it.
+132                    if np.sum(inbound_vertices[face] > 1) <= 2:
+133                        inbound_vertices[face] -= 1
+134                        del inbound_faces[num_infaces - i - 1]
+135                        del_count += 1
+136                # Removing one face can possibly alter other faces so we might do another loop.
+137                # We stop when there is no more face to remove.
+138                if del_count == 0:
+139                    break
+140
+141            # Check that we have the correct number of cells.
+142            if num_infaces < n_cells:
+143                success = 1
+144            elif num_infaces > n_cells:
+145                success = 2
+146            else:
+147                # There is exactly n_cells connected bounded faces.
+148                # Now, it is possible that a bounded face has vertices or edges
+149                # that are not shared with other faces. We get rid of those,
+150                # in order to have only one exterior edge that will be an arc circle.
+151                for i, face in enumerate(inbound_faces):
+152                    useful_vertices = []  # List of the face vertices that are shared with other.
+153                    # (We'll keep them and call them "useful").
+154                    extra_edges = []  # List of edges to replace what we have removed
+155                    last_useful = -1  # ID of the last "useful" vertex. -1 at the beginning (we'll treat that case)
+156                    new_edge = []  # New edge that will replace current vertices we're trying to remove
+157                    incomplete_new_edge = False  # State boolean : are we replacing vertices right now ?
+158                    for vertex in face:
+159                        if inbound_vertices[vertex] == 1:  # We found a vertex that is not shared with other faces.
+160                            # We plan to remove it by not adding it to the useful vertices list,
+161                            # and by creating a new edge from last useful vertex to the next one.
+162                            if not incomplete_new_edge:  # Detect if we're not already in the incomplete edge state
+163                                new_edge = []  # re-init
+164                                new_edge.append(last_useful)
+165                                incomplete_new_edge = True  # Move to incomplete edge state.
+166                        else:  # We found a useful vertex
+167                            useful_vertices.append(vertex)
+168                            last_useful = vertex
+169                            if incomplete_new_edge:  # If in incomplete edge state we can finally close the new edge.
+170                                new_edge.append(vertex)
+171                                extra_edges.append(new_edge)
+172                                incomplete_new_edge = False
+173                    # After looping through the vertices of the face, we need to take care of
+174                    # two special cases : the first or the last vertex is not shared.
+175                    if extra_edges and extra_edges[0][0] == -1:
+176                        extra_edges[0][0] = useful_vertices[-1]
+177                    elif incomplete_new_edge:
+178                        new_edge.append(useful_vertices[0])
+179                        extra_edges.append(new_edge)
+180                    # The extra edges are added to the list of all edges
+181                    edges.extend(extra_edges)
+182                    inbound_faces[i] = tuple(
+183                        sorted(useful_vertices)
+184                    )  # And the face itself is replaced by only the useful vertices.
+185                    # Note that the vertices here are not ordered in clockwise or counterclockwise order anymore.
+186                useful_vertices_set = set(np.where(inbound_vertices > 1)[0])  # We filter the useful vertices.
+187
+188                # HALF EDGE DATA STRUCTURE
+189                # Filter edges with useful vertices only.
+190                useful_edges = [tuple(sorted(e)) for e in edges if set(e).issubset(useful_vertices_set)]
+191
+192                # failing to abide by the following relation results in disconnected topologies
+193                if len(useful_edges) != (n_cells - 1) * 3:
+194                    success = 2  # Case : we want to restart with new seeds because current solution is not OK.
+195                else:
+196                    # We construct the half-edges.
+197                    half_edges = []
+198                    for e in useful_edges:
+199                        half_edges.append(e)
+200                        # reciprocating edges
+201                        half_edges.append((e[1], e[0]))
+202
+203                    # finding clockwise (or counterclockwise) half edge set for each face,
+204                    # as we broke it earlier.
+205                    ordered_edges_inbound_faces = []
+206                    for face in inbound_faces:
+207                        # Find all edges fon this face and we'll loow through them to order them.
+208                        edges_face = [(f1, f2) for f1 in face for f2 in face if (f1, f2) in useful_edges]
+209
+210                        i = 0
+211                        start_edge = edges_face[i]
+212                        ordered_face = [start_edge]
+213                        e = start_edge
+214                        visited = [e]
+215                        while sorted(edges_face) != sorted(visited):
+216                            if e[0] == start_edge[1] and e not in visited:
+217                                ordered_face.append(e)
+218                                start_edge = e
+219                                visited.append(e)
+220                            # We must be careful because some edges might be in the wrong order.
+221                            if e[1] == start_edge[1] and e not in visited:
+222                                ordered_face.append((e[1], e[0]))
+223                                start_edge = (e[1], e[0])
+224                                visited.append(e)
+225                            i += 1
+226                            e = edges_face[i % len(edges_face)]
+227
+228                        # Sanity check : do we have a correct ordering ?
+229                        order = 0
+230                        for e in ordered_face:
+231                            idx0 = e[0]
+232                            idx1 = e[1]
+233
+234                            order += (vertices[idx1][0] - vertices[idx0][0]) * (vertices[idx1][1] + vertices[idx0][1])
+235
+236                        if order < 0:
+237                            ordered_edges_inbound_faces.append(ordered_face)
+238                        if order > 0:
+239                            ordered_edges_inbound_faces.append([(e[1], e[0]) for e in reversed(ordered_face)])
+240                        if order == 0:
+241                            print("\nError: no order detected for face " + str(face) + "\n")
+242                            exit()
+243
+244                    # Now we fill the tables with the info we have.
+245                    useful_vertices_list = list(useful_vertices_set)
+246                    vertTable = np.zeros((len(useful_vertices_list), 2))
+247                    for i, idx in enumerate(useful_vertices_list):
+248                        pos = vertices[idx]
+249                        vertTable[i][0] = pos[0]  # x pos vert
+250                        vertTable[i][1] = pos[1]  # y pos vert
+251
+252                    faceTable = np.zeros((len(inbound_faces), 1), dtype=np.int32)
+253                    for i, hedges_face in enumerate(ordered_edges_inbound_faces):
+254                        for j, he in enumerate(half_edges):
+255                            if he == hedges_face[0]:
+256                                faceTable[i] = j  # he_inside
+257                    faceTable = _fate_selection(faceTable, nb_fates, rng)
+258
+259                    nb_half_edges = len(half_edges)
+260                    heTable = np.zeros((nb_half_edges, 8), dtype=np.int32)
+261                    heTable[:, 4] = 1
+262                    heTable[:, 6] = 1
+263                    relevant_twins = []
+264                    # HE TABLE :
+265                    # 0 : previous half-edge.
+266                    # 1 : next half-edge.
+267                    # 2 : twin half-edge.
+268                    # 3 : source vertex id + 2 if edge is inside, 0 if the edge is outside
+269                    # 4 : target vertex id + 2 if edge is inside, 0 if the edge is outside
+270                    # 5 : source vertex id + 2 if edge is outside, 0 if the edge is inside
+271                    # 6 : target vertex id + 2 if edge is outside, 0 if the edge is inside
+272                    # 7 : id of the face containing this half-edge.
+273                    for i, he in enumerate(half_edges):
+274                        belongs_to_any_face = False
+275                        for hedges_face in ordered_edges_inbound_faces:
+276                            if he in hedges_face:
+277                                idx = hedges_face.index(he)
+278                                heTable[i][0] = half_edges.index(hedges_face[(idx - 1) % len(hedges_face)])  # he_prev
+279                                heTable[i][1] = half_edges.index(hedges_face[(idx + 1) % len(hedges_face)])  # he_next
+280                                # indices 0 and 1 are reserved for source or target vertices of "outside" edges.
+281                                # So we have to add +2 to other indices.
+282                                heTable[i][3] = useful_vertices_list.index(he[0]) + 2  # vert source inner edges
+283                                heTable[i][4] = useful_vertices_list.index(he[1]) + 2  # vert target inner edges
+284                                heTable[i][7] = ordered_edges_inbound_faces.index(hedges_face)  # face
+285                                belongs_to_any_face = True
+286                                break
+287                        twin_idx = half_edges.index((he[1], he[0]))
+288                        heTable[i][2] = twin_idx  # he twin
+289                        if not belongs_to_any_face:
+290                            relevant_twins.append(twin_idx)
+291
+292                    # Angles are randomly chosen between 0 and pi/2 (with some margin to avoid extreme cases).
+293                    angTable = np.ones(nb_half_edges // 2)
+294                    for tidx in relevant_twins:
+295                        angTable[tidx // 2] = rng.random() * (np.pi / 2 - 0.018) + 0.017
+296                        heTable[tidx][5] = heTable[tidx][3]  # vert source surface edges
+297                        heTable[tidx][6] = heTable[tidx][4]  # vert target surface edges
+298                        heTable[tidx][3] = 0
+299                        heTable[tidx][4] = 1
+300
+301                    bounded_mesh = cls._create()
+302                    bounded_mesh.vertices = jnp.array(vertTable, dtype=np.float32)
+303                    bounded_mesh.angles = jnp.array(angTable, dtype=np.float32)
+304                    bounded_mesh.faces = jnp.array(faceTable, dtype=np.int32)
+305                    bounded_mesh.edges = jnp.array(heTable, dtype=np.int32)
+306                    bounded_mesh.width = width
+307                    bounded_mesh.height = height
+308
+309                    return bounded_mesh
+310
+311            # If success was 1, we had not enough bounded faces, we add a new seed to see if it helps.
+312            # Otherwise we retry with new seeds entirely.
+313            seeds = (
+314                np.vstack([seeds, (width, height) * rng.random((1, 2))])
+315                if success == 1
+316                else (width, height) * rng.random((n_cells, 2))
+317            )  # type: ignore
+318
+319    @classmethod
+320    def create_empty(cls) -> Self:
+321        """Create an empty mesh. Use if you know what you're doing !"""
+322        return cls._create()
+323
+324    @classmethod
+325    def copy_mesh(cls, other_mesh: Self) -> Self:
+326        """Copy all parameters from another mesh in a new mesh."""
+327        mesh = cls._create()
+328        mesh.vertices = other_mesh.vertices.copy()
+329        mesh.edges = other_mesh.edges.copy()
+330        mesh.faces = other_mesh.faces.copy()
+331        mesh.angles = other_mesh.angles.copy()
+332        mesh.width = other_mesh.width
+333        mesh.height = other_mesh.height
+334        mesh.vertices_params = other_mesh.vertices_params.copy()
+335        mesh.edges_params = other_mesh.edges_params.copy()
+336        mesh.faces_params = other_mesh.faces_params.copy()
+337
+338        return mesh
+339
+340    def save_mesh(self, path: str) -> None:
+341        """Save mesh to a file.
+342
+343        All BoundedMesh data is saved.
+344
+345        Args:
+346            path (str): Path to the saved file. The extension is .npz.
+347        """
+348        Path(path).parent.mkdir(parents=True, exist_ok=True)
+349        np.savez_compressed(
+350            path,
+351            allow_pickle=False,
+352            vertices=self.vertices,
+353            edges=self.edges,
+354            faces=self.faces,
+355            angles=self.angles,
+356            width=self.width,
+357            height=self.height,
+358            vertices_params=self.vertices_params,
+359            edges_params=self.edges_params,
+360            faces_params=self.faces_params,
+361        )
+362
+363    @classmethod
+364    def load_mesh(cls, path: str) -> Self:
+365        """Load a mesh from a file.
+366
+367        All BoundedMesh data is reloaded.
+368
+369        Args:
+370            path (str): Path to the mesh file (.npz).
+371
+372        Returns:
+373            Mesh: the mesh loaded from the .npz file.
+374        """
+375        mesh_file = np.load(path)
+376        mesh = cls._create()
+377        mesh.vertices, mesh.edges, mesh.faces, mesh.angles = (
+378            mesh_file["vertices"],
+379            mesh_file["edges"],
+380            mesh_file["faces"],
+381            mesh_file["angles"],
+382        )
+383        mesh.width = mesh_file["width"]
+384        mesh.height = mesh_file["height"]
+385        mesh.vertices_params = mesh_file["vertices_params"]
+386        mesh.edges_params = mesh_file["edges_params"]
+387        mesh.faces_params = mesh_file["faces_params"]
+388        return mesh
+389
+390    def save_mesh_txt(
+391        self,
+392        directory: str,
+393        vertices_filename: str = "vertTable.txt",
+394        angles_filename: str = "angTable.txt",
+395        edges_filename: str = "heTable.txt",
+396        faces_filename: str = "faceTable.txt",
+397        vertices_params_filename: str = "vertParamsTable.txt",
+398        edges_params_filename: str = "heParamsTable.txt",
+399        faces_params_filename: str = "faceParamsTable.txt",
+400        constants_filename: str = "constants.txt",
+401    ) -> None:
+402        """Save a mesh in separate text files that can be read by numpy.
+403
+404        Only save the vertices, angles, edges and faces, not other parameters.
+405
+406        Args:
+407            directory (str): Path to the directory where to save the files.
+408            vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt".
+409            angles_filename (str, optional): Filename for the angles table. Defaults to "angTable.txt".
+410            edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt".
+411            faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt".
+412            vertices_params_filename (str, optional): Filename for the vertices parameters table.
+413                    Defaults to "vertParamsTable.txt".
+414            edges_params_filename (str, optional): Filename for the half-edges parameters table.
+415                    Defaults to "heParamsTable.txt".
+416            faces_params_filename (str, optional): Filename for the faces parameters table.
+417                    Defaults to "faceParamsTable.txt".
+418            constants_filename (str, optional): Filename for width/height.
+419                    Defaults to "constants.txt".
+420        """
+421        dirpath = Path(directory)
+422        dirpath.mkdir(parents=True, exist_ok=True)
+423        np.savetxt(dirpath / vertices_filename, self.vertices)
+424        np.savetxt(dirpath / angles_filename, self.angles)
+425        np.savetxt(dirpath / edges_filename, self.edges)
+426        np.savetxt(dirpath / faces_filename, self.faces)
+427        np.savetxt(dirpath / vertices_params_filename, self.vertices_params)
+428        np.savetxt(dirpath / edges_params_filename, self.edges_params)
+429        np.savetxt(dirpath / faces_params_filename, self.faces_params)
+430        with (dirpath / constants_filename).open("w") as f:
+431            f.write(f"{self.width} {self.height}")
+432
+433    @classmethod
+434    def load_mesh_txt(
+435        cls,
+436        directory: str,
+437        vertices_filename: str = "vertTable.txt",
+438        angles_filename: str = "angTable.txt",
+439        edges_filename: str = "heTable.txt",
+440        faces_filename: str = "faceTable.txt",
+441        vertices_params_filename: str = "vertParamsTable.txt",
+442        edges_params_filename: str = "heParamsTable.txt",
+443        faces_params_filename: str = "faceParamsTable.txt",
+444        constants_filename: str = "constants.txt",
+445    ) -> Self:
+446        """Load a mesh from text files.
+447
+448        Only load the vertices, angles, edges and faces, not other parameters.
+449
+450        Args:
+451            directory (str): Directory where the text files are stored.
+452            vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt".
+453            angles_filename (str, optional): Filename for the angles table. Defaults to "angTable.txt".
+454            edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt".
+455            faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt".
+456            vertices_params_filename (str, optional): Filename for the vertices parameters table.
+457                    Defaults to "vertParamsTable.txt".
+458            edges_params_filename (str, optional): Filename for the half-edges parameters table.
+459                    Defaults to "heParamsTable.txt".
+460            faces_params_filename (str, optional): Filename for the faces parameters table.
+461                    Defaults to "faceParamsTable.txt".
+462            constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE.
+463                    Defaults to "constants.txt".
+464
+465        Returns:
+466            Self: The loaded mesh.
+467        """
+468        dirpath = Path(directory)
+469        dirpath.mkdir(parents=True, exist_ok=True)
+470
+471        mesh = cls._create()
+472        mesh.vertices = jnp.array(np.loadtxt(dirpath / vertices_filename, dtype=np.float64))
+473        mesh.angles = jnp.array(np.loadtxt(dirpath / angles_filename, dtype=np.float64))
+474        mesh.edges = jnp.array(np.loadtxt(dirpath / edges_filename, dtype=np.int64))
+475        mesh.faces = jnp.array(np.loadtxt(dirpath / faces_filename, dtype=np.int64))
+476        mesh.vertices_params = jnp.array(np.loadtxt(dirpath / vertices_params_filename, dtype=np.float64))
+477        mesh.edges_params = jnp.array(np.loadtxt(dirpath / edges_params_filename, dtype=np.int64))
+478        mesh.faces_params = jnp.array(np.loadtxt(dirpath / faces_params_filename, dtype=np.int64))
+479        with (dirpath / constants_filename).open("r") as f:
+480            numbers = f.readline().split()
+481            mesh.width = float(numbers[0])
+482            mesh.height = float(numbers[1])
+483        return mesh
+484
+485    def get_length(self, half_edge_id: Array) -> Array:
+486        """Get the length of an edge."""
+487        vertTable = jnp.vstack([jnp.array([[0.0, 0.0], [1.0, 1.0]]), self.vertices])
+488        angTable = jnp.repeat(self.angles, 2)
+489
+490        def _get_length(half_edge_id: Array) -> Array:
+491            return get_any_length(half_edge_id, vertTable, angTable, self.edges)
+492
+493        return jax.vmap(_get_length)(half_edge_id)
+494
+495    def get_perimeter(self, face_id: Array) -> Array:
+496        """Get the area of a face."""
+497        vertTable = jnp.vstack([jnp.array([[0.0, 0.0], [1.0, 1.0]]), self.vertices])
+498        angTable = jnp.repeat(self.angles, 2)
+499
+500        def _get_perimeter(face_id: Array) -> Array:
+501            return get_perimeter_bounded(face_id, vertTable, angTable, self.edges, self.faces)
+502
+503        return jax.vmap(_get_perimeter)(face_id)
+504
+505    def get_area(self, face_id: Array) -> Array:
+506        """Get the area of a face."""
+507        vertTable = jnp.vstack([jnp.array([[0.0, 0.0], [1.0, 1.0]]), self.vertices])
+508        angTable = jnp.repeat(self.angles, 2)
+509
+510        def _get_area(face_id: Array) -> Array:
+511            return get_area_bounded(face_id, vertTable, angTable, self.edges, self.faces)
+512
+513        return jax.vmap(_get_area)(face_id)
+
+ + +

Bounded mesh with arc circles for boundary cells.

+ +

For a BoundedMesh, vertices is a 2D array of floats of size (nb_vertices, 2) ; +with the coordinates of the vertices (in ]0, width[ x ]0, height[ ).

+ +

edges is a 2D array of integers of size (nb_half_edges, 8), with:

+ +
    +
  • id of previous half-edge,
  • +
  • id of next half-edge,
  • +
  • id of twin half-edge,
  • +
  • id of source vertex + 2 if current half-edge is an inside edge, else 0,
  • +
  • id of target vertex + 2 if current half-edge is an inside edge, else 1,
  • +
  • id of source vertex + 2 if current half-edge is an outside edge, else 0,
  • +
  • id of target vertex + 2 if current half-edge is an outside edge, else 1,
  • +
  • id of the face containing the half-edge.
  • +
+ +

faces is a 1D array of integers of size (nb_faces) containing the id of a half-edge belonging to this face.

+ +

angles is a 1D array of floats of size (nb_angles), with the angles sustaining the arcs of the free interfaces ; +between 0 and PI / 2.

+
+ + +
+ +
+ + BoundedMesh() + + + +
+ +
48    def __init__(self) -> None:
+49        """Do not call the constructor."""
+50        super().__init__()
+51        self.angles: Array = jnp.array([])
+52        """Angle sustaining the arced free interfaces. Between 0 and PI / 2."""
+
+ + +

Do not call the constructor.

+
+ + +
+
+
+ angles: jax.Array + + +
+ + +

Angle sustaining the arced free interfaces. Between 0 and PI / 2.

+
+ + +
+
+ +
+ nb_angles: int + + + +
+ +
54    @property
+55    def nb_angles(self) -> int:
+56        """Get the number of angles (free interfaces) of the mesh."""
+57        return len(self.angles)
+
+ + +

Get the number of angles (free interfaces) of the mesh.

+
+ + +
+
+ +
+
@classmethod
+ + def + from_random_seeds( cls, nb_seeds: int, width: float, height: float, random_key: int, nb_fates: int = 2) -> Self: + + + +
+ +
59    @classmethod
+60    def from_random_seeds(cls, nb_seeds: int, width: float, height: float, random_key: int, nb_fates: int = 2) -> Self:
+61        """Create a bounded Mesh from random seeds, based on a Voronoi diagram with arced free interfaces.
+62
+63        Args:
+64            nb_seeds (int): Number of random seeds to use.
+65            width (float): width of the box containing the seeds.
+66            height (float): height of the box containing the seeds.
+67            random_key (int): seed for a random number generator to add new seeds if needed, decide on cell fates...
+68            nb_fates (int, default=2): number of possible different fate marker for a cell.
+69
+70        Returns:
+71            Self: The corresponding mesh.
+72        """
+73        rng = np.random.default_rng(seed=random_key)
+74        seeds = rng.random((nb_seeds, 2)) * (width, height)
+75        return cls.from_seeds(seeds, width, height, random_key, nb_fates)
+
+ + +

Create a bounded Mesh from random seeds, based on a Voronoi diagram with arced free interfaces.

+ +
Arguments:
+ +
    +
  • nb_seeds (int): Number of random seeds to use.
  • +
  • width (float): width of the box containing the seeds.
  • +
  • height (float): height of the box containing the seeds.
  • +
  • random_key (int): seed for a random number generator to add new seeds if needed, decide on cell fates...
  • +
  • nb_fates (int, default=2): number of possible different fate marker for a cell.
  • +
+ +
Returns:
+ +
+

Self: The corresponding mesh.

+
+
+ + +
+
+ +
+
@classmethod
+ + def + from_seeds( cls, seeds: numpy.ndarray[typing.Any, numpy.dtype[+_ScalarType_co]], width: float, height: float, random_key: int, nb_fates: int = 2) -> Self: + + + +
+ +
 77    @classmethod
+ 78    def from_seeds(cls, seeds: NDArray, width: float, height: float, random_key: int, nb_fates: int = 2) -> Self:  # noqa: C901
+ 79        """Create a bounded Mesh from a list of given seeds.
+ 80
+ 81        The seeds are assumed to have x-coordinate in ]0, width[ and y-coordinate in ]0, height[.
+ 82        Note that the final mesh might not use your seeds if they don't work to create a correct
+ 83        bounded mesh via our method.
+ 84
+ 85        Args:
+ 86            seeds (Array[float32]): jax float array of seed positions of shape (nbSeeds, 2).
+ 87            width (float): width of the box containing the seeds.
+ 88            height (float): height of the box containing the seeds.
+ 89            random_key (int): seed for a random number generator to add new seeds if needed, decide on cell fates...
+ 90            nb_fates (int, default=2): number of possible different fate marker for a cell.
+ 91        """
+ 92        rng = np.random.default_rng(seed=random_key)
+ 93        n_cells = len(seeds)  # starting number of seeds must be equal to the desired number of cells (faces)
+ 94
+ 95        # We'll try to construct a Voronoi diagram with n_cells closed (bounded) cells.
+ 96        # This will not work with exactly n_cells seeds because there will be unbounded cells.
+ 97        # If the number of bounded cells is insufficient, we add a new seed to the list.
+ 98        # If there is too many bounded cells, we retry with entirely new seeds.
+ 99        # We also check that the bounded cells are connected.
+100        while True:
+101            success = 0  # if 1 : not enough bounded cells. If 2 : too many bounded cells.
+102
+103            # Create the Voronoi diagrams from current seed list.
+104            voronoi = Voronoi(seeds)
+105            vertices = voronoi.vertices
+106            edges = voronoi.ridge_vertices
+107            faces = voronoi.regions  # regions = faces = cells
+108
+109            # We count the number of bounded cells and the connectivity of vertices.
+110            inbound_faces = []
+111            inbound_vertices = np.zeros(vertices.shape[0], dtype=np.int32)
+112            for face in faces:
+113                if face and all(item > -1 for item in face):  # the face must not be an empty list
+114                    face_vertices_positions = vertices[face]
+115                    # We check that all of the face's vertices are in a box [0, width]x[0, height]
+116                    if (
+117                        np.all(face_vertices_positions[:, 0] < width)
+118                        and np.all(face_vertices_positions[:, 1] < height)
+119                        and np.all(face_vertices_positions > 0)
+120                    ):
+121                        inbound_faces.append(face)  # the face is bounded
+122                        inbound_vertices[face] += 1  # +1 to the connectivity of the face's vertices.
+123
+124            # getting rid of faces connected to a single other inbound face
+125            # (these can be problematic and lead to many special cases later on)
+126            while True:
+127                num_infaces = len(inbound_faces)
+128                del_count = 0
+129                for i, face in enumerate(reversed(inbound_faces)):
+130                    # only 2 (or less (not sure it's possible)) vertices of the face are shared with other faces :
+131                    # that means it is connected to one other bounded face only -> We remove it.
+132                    if np.sum(inbound_vertices[face] > 1) <= 2:
+133                        inbound_vertices[face] -= 1
+134                        del inbound_faces[num_infaces - i - 1]
+135                        del_count += 1
+136                # Removing one face can possibly alter other faces so we might do another loop.
+137                # We stop when there is no more face to remove.
+138                if del_count == 0:
+139                    break
+140
+141            # Check that we have the correct number of cells.
+142            if num_infaces < n_cells:
+143                success = 1
+144            elif num_infaces > n_cells:
+145                success = 2
+146            else:
+147                # There is exactly n_cells connected bounded faces.
+148                # Now, it is possible that a bounded face has vertices or edges
+149                # that are not shared with other faces. We get rid of those,
+150                # in order to have only one exterior edge that will be an arc circle.
+151                for i, face in enumerate(inbound_faces):
+152                    useful_vertices = []  # List of the face vertices that are shared with other.
+153                    # (We'll keep them and call them "useful").
+154                    extra_edges = []  # List of edges to replace what we have removed
+155                    last_useful = -1  # ID of the last "useful" vertex. -1 at the beginning (we'll treat that case)
+156                    new_edge = []  # New edge that will replace current vertices we're trying to remove
+157                    incomplete_new_edge = False  # State boolean : are we replacing vertices right now ?
+158                    for vertex in face:
+159                        if inbound_vertices[vertex] == 1:  # We found a vertex that is not shared with other faces.
+160                            # We plan to remove it by not adding it to the useful vertices list,
+161                            # and by creating a new edge from last useful vertex to the next one.
+162                            if not incomplete_new_edge:  # Detect if we're not already in the incomplete edge state
+163                                new_edge = []  # re-init
+164                                new_edge.append(last_useful)
+165                                incomplete_new_edge = True  # Move to incomplete edge state.
+166                        else:  # We found a useful vertex
+167                            useful_vertices.append(vertex)
+168                            last_useful = vertex
+169                            if incomplete_new_edge:  # If in incomplete edge state we can finally close the new edge.
+170                                new_edge.append(vertex)
+171                                extra_edges.append(new_edge)
+172                                incomplete_new_edge = False
+173                    # After looping through the vertices of the face, we need to take care of
+174                    # two special cases : the first or the last vertex is not shared.
+175                    if extra_edges and extra_edges[0][0] == -1:
+176                        extra_edges[0][0] = useful_vertices[-1]
+177                    elif incomplete_new_edge:
+178                        new_edge.append(useful_vertices[0])
+179                        extra_edges.append(new_edge)
+180                    # The extra edges are added to the list of all edges
+181                    edges.extend(extra_edges)
+182                    inbound_faces[i] = tuple(
+183                        sorted(useful_vertices)
+184                    )  # And the face itself is replaced by only the useful vertices.
+185                    # Note that the vertices here are not ordered in clockwise or counterclockwise order anymore.
+186                useful_vertices_set = set(np.where(inbound_vertices > 1)[0])  # We filter the useful vertices.
+187
+188                # HALF EDGE DATA STRUCTURE
+189                # Filter edges with useful vertices only.
+190                useful_edges = [tuple(sorted(e)) for e in edges if set(e).issubset(useful_vertices_set)]
+191
+192                # failing to abide by the following relation results in disconnected topologies
+193                if len(useful_edges) != (n_cells - 1) * 3:
+194                    success = 2  # Case : we want to restart with new seeds because current solution is not OK.
+195                else:
+196                    # We construct the half-edges.
+197                    half_edges = []
+198                    for e in useful_edges:
+199                        half_edges.append(e)
+200                        # reciprocating edges
+201                        half_edges.append((e[1], e[0]))
+202
+203                    # finding clockwise (or counterclockwise) half edge set for each face,
+204                    # as we broke it earlier.
+205                    ordered_edges_inbound_faces = []
+206                    for face in inbound_faces:
+207                        # Find all edges fon this face and we'll loow through them to order them.
+208                        edges_face = [(f1, f2) for f1 in face for f2 in face if (f1, f2) in useful_edges]
+209
+210                        i = 0
+211                        start_edge = edges_face[i]
+212                        ordered_face = [start_edge]
+213                        e = start_edge
+214                        visited = [e]
+215                        while sorted(edges_face) != sorted(visited):
+216                            if e[0] == start_edge[1] and e not in visited:
+217                                ordered_face.append(e)
+218                                start_edge = e
+219                                visited.append(e)
+220                            # We must be careful because some edges might be in the wrong order.
+221                            if e[1] == start_edge[1] and e not in visited:
+222                                ordered_face.append((e[1], e[0]))
+223                                start_edge = (e[1], e[0])
+224                                visited.append(e)
+225                            i += 1
+226                            e = edges_face[i % len(edges_face)]
+227
+228                        # Sanity check : do we have a correct ordering ?
+229                        order = 0
+230                        for e in ordered_face:
+231                            idx0 = e[0]
+232                            idx1 = e[1]
+233
+234                            order += (vertices[idx1][0] - vertices[idx0][0]) * (vertices[idx1][1] + vertices[idx0][1])
+235
+236                        if order < 0:
+237                            ordered_edges_inbound_faces.append(ordered_face)
+238                        if order > 0:
+239                            ordered_edges_inbound_faces.append([(e[1], e[0]) for e in reversed(ordered_face)])
+240                        if order == 0:
+241                            print("\nError: no order detected for face " + str(face) + "\n")
+242                            exit()
+243
+244                    # Now we fill the tables with the info we have.
+245                    useful_vertices_list = list(useful_vertices_set)
+246                    vertTable = np.zeros((len(useful_vertices_list), 2))
+247                    for i, idx in enumerate(useful_vertices_list):
+248                        pos = vertices[idx]
+249                        vertTable[i][0] = pos[0]  # x pos vert
+250                        vertTable[i][1] = pos[1]  # y pos vert
+251
+252                    faceTable = np.zeros((len(inbound_faces), 1), dtype=np.int32)
+253                    for i, hedges_face in enumerate(ordered_edges_inbound_faces):
+254                        for j, he in enumerate(half_edges):
+255                            if he == hedges_face[0]:
+256                                faceTable[i] = j  # he_inside
+257                    faceTable = _fate_selection(faceTable, nb_fates, rng)
+258
+259                    nb_half_edges = len(half_edges)
+260                    heTable = np.zeros((nb_half_edges, 8), dtype=np.int32)
+261                    heTable[:, 4] = 1
+262                    heTable[:, 6] = 1
+263                    relevant_twins = []
+264                    # HE TABLE :
+265                    # 0 : previous half-edge.
+266                    # 1 : next half-edge.
+267                    # 2 : twin half-edge.
+268                    # 3 : source vertex id + 2 if edge is inside, 0 if the edge is outside
+269                    # 4 : target vertex id + 2 if edge is inside, 0 if the edge is outside
+270                    # 5 : source vertex id + 2 if edge is outside, 0 if the edge is inside
+271                    # 6 : target vertex id + 2 if edge is outside, 0 if the edge is inside
+272                    # 7 : id of the face containing this half-edge.
+273                    for i, he in enumerate(half_edges):
+274                        belongs_to_any_face = False
+275                        for hedges_face in ordered_edges_inbound_faces:
+276                            if he in hedges_face:
+277                                idx = hedges_face.index(he)
+278                                heTable[i][0] = half_edges.index(hedges_face[(idx - 1) % len(hedges_face)])  # he_prev
+279                                heTable[i][1] = half_edges.index(hedges_face[(idx + 1) % len(hedges_face)])  # he_next
+280                                # indices 0 and 1 are reserved for source or target vertices of "outside" edges.
+281                                # So we have to add +2 to other indices.
+282                                heTable[i][3] = useful_vertices_list.index(he[0]) + 2  # vert source inner edges
+283                                heTable[i][4] = useful_vertices_list.index(he[1]) + 2  # vert target inner edges
+284                                heTable[i][7] = ordered_edges_inbound_faces.index(hedges_face)  # face
+285                                belongs_to_any_face = True
+286                                break
+287                        twin_idx = half_edges.index((he[1], he[0]))
+288                        heTable[i][2] = twin_idx  # he twin
+289                        if not belongs_to_any_face:
+290                            relevant_twins.append(twin_idx)
+291
+292                    # Angles are randomly chosen between 0 and pi/2 (with some margin to avoid extreme cases).
+293                    angTable = np.ones(nb_half_edges // 2)
+294                    for tidx in relevant_twins:
+295                        angTable[tidx // 2] = rng.random() * (np.pi / 2 - 0.018) + 0.017
+296                        heTable[tidx][5] = heTable[tidx][3]  # vert source surface edges
+297                        heTable[tidx][6] = heTable[tidx][4]  # vert target surface edges
+298                        heTable[tidx][3] = 0
+299                        heTable[tidx][4] = 1
+300
+301                    bounded_mesh = cls._create()
+302                    bounded_mesh.vertices = jnp.array(vertTable, dtype=np.float32)
+303                    bounded_mesh.angles = jnp.array(angTable, dtype=np.float32)
+304                    bounded_mesh.faces = jnp.array(faceTable, dtype=np.int32)
+305                    bounded_mesh.edges = jnp.array(heTable, dtype=np.int32)
+306                    bounded_mesh.width = width
+307                    bounded_mesh.height = height
+308
+309                    return bounded_mesh
+310
+311            # If success was 1, we had not enough bounded faces, we add a new seed to see if it helps.
+312            # Otherwise we retry with new seeds entirely.
+313            seeds = (
+314                np.vstack([seeds, (width, height) * rng.random((1, 2))])
+315                if success == 1
+316                else (width, height) * rng.random((n_cells, 2))
+317            )  # type: ignore
+
+ + +

Create a bounded Mesh from a list of given seeds.

+ +

The seeds are assumed to have x-coordinate in ]0, width[ and y-coordinate in ]0, height[. +Note that the final mesh might not use your seeds if they don't work to create a correct +bounded mesh via our method.

+ +
Arguments:
+ +
    +
  • seeds (Array[float32]): jax float array of seed positions of shape (nbSeeds, 2).
  • +
  • width (float): width of the box containing the seeds.
  • +
  • height (float): height of the box containing the seeds.
  • +
  • random_key (int): seed for a random number generator to add new seeds if needed, decide on cell fates...
  • +
  • nb_fates (int, default=2): number of possible different fate marker for a cell.
  • +
+
+ + +
+
+ +
+
@classmethod
+ + def + create_empty(cls) -> Self: + + + +
+ +
319    @classmethod
+320    def create_empty(cls) -> Self:
+321        """Create an empty mesh. Use if you know what you're doing !"""
+322        return cls._create()
+
+ + +

Create an empty mesh. Use if you know what you're doing !

+
+ + +
+
+ +
+
@classmethod
+ + def + copy_mesh(cls, other_mesh: Self) -> Self: + + + +
+ +
324    @classmethod
+325    def copy_mesh(cls, other_mesh: Self) -> Self:
+326        """Copy all parameters from another mesh in a new mesh."""
+327        mesh = cls._create()
+328        mesh.vertices = other_mesh.vertices.copy()
+329        mesh.edges = other_mesh.edges.copy()
+330        mesh.faces = other_mesh.faces.copy()
+331        mesh.angles = other_mesh.angles.copy()
+332        mesh.width = other_mesh.width
+333        mesh.height = other_mesh.height
+334        mesh.vertices_params = other_mesh.vertices_params.copy()
+335        mesh.edges_params = other_mesh.edges_params.copy()
+336        mesh.faces_params = other_mesh.faces_params.copy()
+337
+338        return mesh
+
+ + +

Copy all parameters from another mesh in a new mesh.

+
+ + +
+
+ +
+ + def + save_mesh(self, path: str) -> None: + + + +
+ +
340    def save_mesh(self, path: str) -> None:
+341        """Save mesh to a file.
+342
+343        All BoundedMesh data is saved.
+344
+345        Args:
+346            path (str): Path to the saved file. The extension is .npz.
+347        """
+348        Path(path).parent.mkdir(parents=True, exist_ok=True)
+349        np.savez_compressed(
+350            path,
+351            allow_pickle=False,
+352            vertices=self.vertices,
+353            edges=self.edges,
+354            faces=self.faces,
+355            angles=self.angles,
+356            width=self.width,
+357            height=self.height,
+358            vertices_params=self.vertices_params,
+359            edges_params=self.edges_params,
+360            faces_params=self.faces_params,
+361        )
+
+ + +

Save mesh to a file.

+ +

All BoundedMesh data is saved.

+ +
Arguments:
+ +
    +
  • path (str): Path to the saved file. The extension is .npz.
  • +
+
+ + +
+
+ +
+
@classmethod
+ + def + load_mesh(cls, path: str) -> Self: + + + +
+ +
363    @classmethod
+364    def load_mesh(cls, path: str) -> Self:
+365        """Load a mesh from a file.
+366
+367        All BoundedMesh data is reloaded.
+368
+369        Args:
+370            path (str): Path to the mesh file (.npz).
+371
+372        Returns:
+373            Mesh: the mesh loaded from the .npz file.
+374        """
+375        mesh_file = np.load(path)
+376        mesh = cls._create()
+377        mesh.vertices, mesh.edges, mesh.faces, mesh.angles = (
+378            mesh_file["vertices"],
+379            mesh_file["edges"],
+380            mesh_file["faces"],
+381            mesh_file["angles"],
+382        )
+383        mesh.width = mesh_file["width"]
+384        mesh.height = mesh_file["height"]
+385        mesh.vertices_params = mesh_file["vertices_params"]
+386        mesh.edges_params = mesh_file["edges_params"]
+387        mesh.faces_params = mesh_file["faces_params"]
+388        return mesh
+
+ + +

Load a mesh from a file.

+ +

All BoundedMesh data is reloaded.

+ +
Arguments:
+ +
    +
  • path (str): Path to the mesh file (.npz).
  • +
+ +
Returns:
+ +
+

Mesh: the mesh loaded from the .npz file.

+
+
+ + +
+
+ +
+ + def + save_mesh_txt( self, directory: str, vertices_filename: str = 'vertTable.txt', angles_filename: str = 'angTable.txt', edges_filename: str = 'heTable.txt', faces_filename: str = 'faceTable.txt', vertices_params_filename: str = 'vertParamsTable.txt', edges_params_filename: str = 'heParamsTable.txt', faces_params_filename: str = 'faceParamsTable.txt', constants_filename: str = 'constants.txt') -> None: + + + +
+ +
390    def save_mesh_txt(
+391        self,
+392        directory: str,
+393        vertices_filename: str = "vertTable.txt",
+394        angles_filename: str = "angTable.txt",
+395        edges_filename: str = "heTable.txt",
+396        faces_filename: str = "faceTable.txt",
+397        vertices_params_filename: str = "vertParamsTable.txt",
+398        edges_params_filename: str = "heParamsTable.txt",
+399        faces_params_filename: str = "faceParamsTable.txt",
+400        constants_filename: str = "constants.txt",
+401    ) -> None:
+402        """Save a mesh in separate text files that can be read by numpy.
+403
+404        Only save the vertices, angles, edges and faces, not other parameters.
+405
+406        Args:
+407            directory (str): Path to the directory where to save the files.
+408            vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt".
+409            angles_filename (str, optional): Filename for the angles table. Defaults to "angTable.txt".
+410            edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt".
+411            faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt".
+412            vertices_params_filename (str, optional): Filename for the vertices parameters table.
+413                    Defaults to "vertParamsTable.txt".
+414            edges_params_filename (str, optional): Filename for the half-edges parameters table.
+415                    Defaults to "heParamsTable.txt".
+416            faces_params_filename (str, optional): Filename for the faces parameters table.
+417                    Defaults to "faceParamsTable.txt".
+418            constants_filename (str, optional): Filename for width/height.
+419                    Defaults to "constants.txt".
+420        """
+421        dirpath = Path(directory)
+422        dirpath.mkdir(parents=True, exist_ok=True)
+423        np.savetxt(dirpath / vertices_filename, self.vertices)
+424        np.savetxt(dirpath / angles_filename, self.angles)
+425        np.savetxt(dirpath / edges_filename, self.edges)
+426        np.savetxt(dirpath / faces_filename, self.faces)
+427        np.savetxt(dirpath / vertices_params_filename, self.vertices_params)
+428        np.savetxt(dirpath / edges_params_filename, self.edges_params)
+429        np.savetxt(dirpath / faces_params_filename, self.faces_params)
+430        with (dirpath / constants_filename).open("w") as f:
+431            f.write(f"{self.width} {self.height}")
+
+ + +

Save a mesh in separate text files that can be read by numpy.

+ +

Only save the vertices, angles, edges and faces, not other parameters.

+ +
Arguments:
+ +
    +
  • directory (str): Path to the directory where to save the files.
  • +
  • vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt".
  • +
  • angles_filename (str, optional): Filename for the angles table. Defaults to "angTable.txt".
  • +
  • edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt".
  • +
  • faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt".
  • +
  • vertices_params_filename (str, optional): Filename for the vertices parameters table. +Defaults to "vertParamsTable.txt".
  • +
  • edges_params_filename (str, optional): Filename for the half-edges parameters table. +Defaults to "heParamsTable.txt".
  • +
  • faces_params_filename (str, optional): Filename for the faces parameters table. +Defaults to "faceParamsTable.txt".
  • +
  • constants_filename (str, optional): Filename for width/height. +Defaults to "constants.txt".
  • +
+
+ + +
+
+ +
+
@classmethod
+ + def + load_mesh_txt( cls, directory: str, vertices_filename: str = 'vertTable.txt', angles_filename: str = 'angTable.txt', edges_filename: str = 'heTable.txt', faces_filename: str = 'faceTable.txt', vertices_params_filename: str = 'vertParamsTable.txt', edges_params_filename: str = 'heParamsTable.txt', faces_params_filename: str = 'faceParamsTable.txt', constants_filename: str = 'constants.txt') -> Self: + + + +
+ +
433    @classmethod
+434    def load_mesh_txt(
+435        cls,
+436        directory: str,
+437        vertices_filename: str = "vertTable.txt",
+438        angles_filename: str = "angTable.txt",
+439        edges_filename: str = "heTable.txt",
+440        faces_filename: str = "faceTable.txt",
+441        vertices_params_filename: str = "vertParamsTable.txt",
+442        edges_params_filename: str = "heParamsTable.txt",
+443        faces_params_filename: str = "faceParamsTable.txt",
+444        constants_filename: str = "constants.txt",
+445    ) -> Self:
+446        """Load a mesh from text files.
+447
+448        Only load the vertices, angles, edges and faces, not other parameters.
+449
+450        Args:
+451            directory (str): Directory where the text files are stored.
+452            vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt".
+453            angles_filename (str, optional): Filename for the angles table. Defaults to "angTable.txt".
+454            edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt".
+455            faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt".
+456            vertices_params_filename (str, optional): Filename for the vertices parameters table.
+457                    Defaults to "vertParamsTable.txt".
+458            edges_params_filename (str, optional): Filename for the half-edges parameters table.
+459                    Defaults to "heParamsTable.txt".
+460            faces_params_filename (str, optional): Filename for the faces parameters table.
+461                    Defaults to "faceParamsTable.txt".
+462            constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE.
+463                    Defaults to "constants.txt".
+464
+465        Returns:
+466            Self: The loaded mesh.
+467        """
+468        dirpath = Path(directory)
+469        dirpath.mkdir(parents=True, exist_ok=True)
+470
+471        mesh = cls._create()
+472        mesh.vertices = jnp.array(np.loadtxt(dirpath / vertices_filename, dtype=np.float64))
+473        mesh.angles = jnp.array(np.loadtxt(dirpath / angles_filename, dtype=np.float64))
+474        mesh.edges = jnp.array(np.loadtxt(dirpath / edges_filename, dtype=np.int64))
+475        mesh.faces = jnp.array(np.loadtxt(dirpath / faces_filename, dtype=np.int64))
+476        mesh.vertices_params = jnp.array(np.loadtxt(dirpath / vertices_params_filename, dtype=np.float64))
+477        mesh.edges_params = jnp.array(np.loadtxt(dirpath / edges_params_filename, dtype=np.int64))
+478        mesh.faces_params = jnp.array(np.loadtxt(dirpath / faces_params_filename, dtype=np.int64))
+479        with (dirpath / constants_filename).open("r") as f:
+480            numbers = f.readline().split()
+481            mesh.width = float(numbers[0])
+482            mesh.height = float(numbers[1])
+483        return mesh
+
+ + +

Load a mesh from text files.

+ +

Only load the vertices, angles, edges and faces, not other parameters.

+ +
Arguments:
+ +
    +
  • directory (str): Directory where the text files are stored.
  • +
  • vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt".
  • +
  • angles_filename (str, optional): Filename for the angles table. Defaults to "angTable.txt".
  • +
  • edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt".
  • +
  • faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt".
  • +
  • vertices_params_filename (str, optional): Filename for the vertices parameters table. +Defaults to "vertParamsTable.txt".
  • +
  • edges_params_filename (str, optional): Filename for the half-edges parameters table. +Defaults to "heParamsTable.txt".
  • +
  • faces_params_filename (str, optional): Filename for the faces parameters table. +Defaults to "faceParamsTable.txt".
  • +
  • constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE. +Defaults to "constants.txt".
  • +
+ +
Returns:
+ +
+

Self: The loaded mesh.

+
+
+ + +
+
+ +
+ + def + get_length(self, half_edge_id: jax.Array) -> jax.Array: + + + +
+ +
485    def get_length(self, half_edge_id: Array) -> Array:
+486        """Get the length of an edge."""
+487        vertTable = jnp.vstack([jnp.array([[0.0, 0.0], [1.0, 1.0]]), self.vertices])
+488        angTable = jnp.repeat(self.angles, 2)
+489
+490        def _get_length(half_edge_id: Array) -> Array:
+491            return get_any_length(half_edge_id, vertTable, angTable, self.edges)
+492
+493        return jax.vmap(_get_length)(half_edge_id)
+
+ + +

Get the length of an edge.

+
+ + +
+
+ +
+ + def + get_perimeter(self, face_id: jax.Array) -> jax.Array: + + + +
+ +
495    def get_perimeter(self, face_id: Array) -> Array:
+496        """Get the area of a face."""
+497        vertTable = jnp.vstack([jnp.array([[0.0, 0.0], [1.0, 1.0]]), self.vertices])
+498        angTable = jnp.repeat(self.angles, 2)
+499
+500        def _get_perimeter(face_id: Array) -> Array:
+501            return get_perimeter_bounded(face_id, vertTable, angTable, self.edges, self.faces)
+502
+503        return jax.vmap(_get_perimeter)(face_id)
+
+ + +

Get the area of a face.

+
+ + +
+
+ +
+ + def + get_area(self, face_id: jax.Array) -> jax.Array: + + + +
+ +
505    def get_area(self, face_id: Array) -> Array:
+506        """Get the area of a face."""
+507        vertTable = jnp.vstack([jnp.array([[0.0, 0.0], [1.0, 1.0]]), self.vertices])
+508        angTable = jnp.repeat(self.angles, 2)
+509
+510        def _get_area(face_id: Array) -> Array:
+511            return get_area_bounded(face_id, vertTable, angTable, self.edges, self.faces)
+512
+513        return jax.vmap(_get_area)(face_id)
+
+ + +

Get the area of a face.

+
+ + +
+
+
+ +
+ + class + BilevelOptimizationMethod(enum.Enum): + + + +
+ +
 7class BilevelOptimizationMethod(Enum):
+ 8    """Which optimization method to use in the bi-level optimization."""
+ 9
+10    AUTOMATIC_DIFFERENTIATION = "ad"
+11    """Unrolls the inner optimization steps ; forward-mode JVP via `jax.jacfwd`,
+12    cost scales with the number of parameters and iterations."""
+13    EQUILIBRIUM_PROPAGATION = "ep"
+14    """Estimates the gradient from perturbed free vs nudged equilibria ; no backdrop required.
+15    Most efficient but depends on the perturbation size β."""
+16    IMPLICIT_DIFFERENTIATION = "id"
+17    """Differentiates the optimality condition ∇ₓE=0 via Implicit Function Theorem ; JVP variant ;
+18    requires Hessian solve and sensitive to ill-conditioning."""
+19    ADJOINT_STATE = "as"
+20    """Differentiates the optimality condition ∇ₓE=0 via Implicit Function Theorem ; VJP variant ;
+21    requires Hessian solve or sensitive to ill-conditioning."""
+
+ + +

Which optimization method to use in the bi-level optimization.

+
+ + +
+
+ AUTOMATIC_DIFFERENTIATION = +<BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION: 'ad'> + + +
+ + +

Unrolls the inner optimization steps ; forward-mode JVP via jax.jacfwd, +cost scales with the number of parameters and iterations.

+
+ + +
+
+
+ EQUILIBRIUM_PROPAGATION = +<BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION: 'ep'> + + +
+ + +

Estimates the gradient from perturbed free vs nudged equilibria ; no backdrop required. +Most efficient but depends on the perturbation size β.

+
+ + +
+
+
+ IMPLICIT_DIFFERENTIATION = +<BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION: 'id'> + + +
+ + +

Differentiates the optimality condition ∇ₓE=0 via Implicit Function Theorem ; JVP variant ; +requires Hessian solve and sensitive to ill-conditioning.

+
+ + +
+
+
+ ADJOINT_STATE = +<BilevelOptimizationMethod.ADJOINT_STATE: 'as'> + + +
+ + +

Differentiates the optimality condition ∇ₓE=0 via Implicit Function Theorem ; VJP variant ; +requires Hessian solve or sensitive to ill-conditioning.

+
+ + +
+
+
+ +
+ + class + _BilevelOptimizer: + + + +
+ +
 23class _BilevelOptimizer:
+ 24    """Abstract base class for Bi-level optimizers.
+ 25
+ 26    Use the specialezed corresponding ones:
+ 27    - to optimize a `PbcMesh` use a `PbcBilevelOptimizer`,
+ 28    - to optimize  a `BoundedMesh` use a `BoundedBilevelOptimizer`.
+ 29    """
+ 30
+ 31    def __init__(self) -> None:
+ 32        """Initialize shared parameters and hyper-parameters between Bi-level optimizers."""
+ 33        self.bilevel_optimization_method: BilevelOptimizationMethod = BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION
+ 34        """Which bilevel optimization method to use. Defaults to Equilibrium Propagation."""
+ 35        self.inner_solver: optax.GradientTransformation = optax.sgd(learning_rate=0.01)
+ 36        """Which inner solver to use. Defaults to `optax.sgd(learning_rate=0.01)`."""
+ 37        self.outer_solver: optax.GradientTransformation = optax.adam(learning_rate=0.0001, nesterov=True)
+ 38        """Which outer solver to use. Defaults to `optax.adam(learning_rate=0.0001, nesterov=True)`"""
+ 39        self.loss_function_inner: Callable | None = None
+ 40        """Define your inner loss function."""
+ 41        self.loss_function_outer: Callable | None = None
+ 42        """Define your outer loss function."""
+ 43        self.custom_metrics: dict[str, tuple[Callable[[Any, Any], float], list[float], list[float]]] = {}
+ 44        """Define custom metrics that can be saved during optimization.
+ 45        The function must take two arguments : a mesh and a bilevel optimizer ; and return a float."""
+ 46
+ 47        self.max_nb_iterations: int = 1000
+ 48        """Maximum number of iterations during an optimization step."""
+ 49        self.tolerance: float = 1e-4
+ 50        """Below this level, we consider that the loss is stagnating."""
+ 51        self.patience: int = 5
+ 52        """Maximum number of consecutive stagnating loss before we stop."""
+ 53
+ 54        self.min_dist_T1: float = 0.005
+ 55        """Threshold to perform T1 transitions."""
+ 56        self._update_T1: bool = False
+ 57        self._update_T1_func: Callable | None = None  # value set by _set_update_T1_func
+ 58
+ 59        # These values will be set in the init function of child classes
+ 60        self._inner_opt_func: Callable[[Mesh, Array | None, Array | None, Array | None], list[float]] | None = None
+ 61        self._outer_opt_func: Callable[[Mesh, Array | None, Array | None, Array | None], None] | None = None
+ 62
+ 63        self.update_T1 = True  # Force the setting of update T1 func
+ 64        """Perform T1 transitions if necessary."""
+ 65
+ 66        # Targets
+ 67        self.vertices_target = jnp.array([])
+ 68        """Vertices table of a target mesh if any."""
+ 69        self.edges_target = jnp.array([])
+ 70        """Edges table of a target mesh if any."""
+ 71        self.faces_target = jnp.array([])
+ 72        """Faces table of a target mesh if any."""
+ 73        # Those attributes are not always used (depends on the bilevel_optimization_method)
+ 74        self.image_target: Array = jnp.array([])
+ 75        """Image target if any."""
+ 76        self.beta = 0.01
+ 77        """β parameter used for Equilibrium Propagation."""
+ 78
+ 79    def _set_update_T1_func(self, b: bool) -> None:  # noqa: N802
+ 80        """Set the _update_T1_func callable with respect to whether it is needed or not."""
+ 81        raise NotImplementedError
+ 82
+ 83    @property
+ 84    def update_T1(self) -> bool:  # noqa: N802
+ 85        """Whether to process T1 topological operations or not."""
+ 86        return self._update_T1
+ 87
+ 88    @update_T1.setter
+ 89    def update_T1(self, value: bool) -> None:  # noqa: N802
+ 90        self._update_T1 = value
+ 91        self._set_update_T1_func(value)
+ 92
+ 93    def compute_outer_loss(
+ 94        self,
+ 95        mesh: Mesh,
+ 96        only_on_vertices: None | list[int] = None,
+ 97        only_on_edges: None | list[int] = None,
+ 98        only_on_faces: None | list[int] = None,
+ 99    ) -> float:
+100        """Get the result of self.loss_function_outer called with the correct arguments.
+101
+102        Must be implemented by child classes.
+103        """
+104        raise NotImplementedError
+105
+106    def compute_inner_loss(
+107        self,
+108        mesh: Mesh,
+109        only_on_vertices: None | list[int] = None,
+110        only_on_edges: None | list[int] = None,
+111        only_on_faces: None | list[int] = None,
+112    ) -> float:
+113        """Get the result of self.loss_function_inner called with the correct arguments.
+114
+115        Must be implemented by child classes.
+116        """
+117        raise NotImplementedError
+118
+119    def inner_optimization(
+120        self,
+121        mesh: Mesh,
+122        only_on_vertices: None | list[int] = None,
+123        only_on_edges: None | list[int] = None,
+124        only_on_faces: None | list[int] = None,
+125    ) -> list[float]:
+126        """Optimize the mesh for the inner loss function.
+127
+128        Args:
+129            mesh (Mesh): The mesh to act on.
+130            only_on_vertices (None | list[int] = None): Consider only a subset of vertices for the loss function.
+131                                                        All vertices if None.
+132            only_on_edges (None | list[int] = None): Consider only a subset of edges for the loss function.
+133                                                        All edges if None.
+134            only_on_faces (None | list[int] = None): Consider only a subset of faces for the loss function.
+135                                                        All faces if None.
+136
+137        Returns:
+138            list[float]: Histor of loss values during optimization.
+139        """
+140        # To be defined by child classes
+141        if self._inner_opt_func is None:
+142            msg = "The inner function was not initialized."
+143            raise AttributeError(msg)
+144        else:
+145            return self._inner_opt_func(
+146                mesh, *self._selection_to_jax_arrays(only_on_vertices, only_on_edges, only_on_faces)
+147            )
+148
+149    def outer_optimization(
+150        self,
+151        mesh: Mesh,
+152        only_on_vertices: None | list[int] = None,
+153        only_on_edges: None | list[int] = None,
+154        only_on_faces: None | list[int] = None,
+155    ) -> None:
+156        """Optimize the mesh for the outer loss function.
+157
+158        Args:
+159            mesh (Mesh): The mesh to act on.
+160            only_on_vertices (None | list[int] = None): Consider only a subset of vertices for the loss function.
+161                                                        All vertices if None.
+162            only_on_edges (None | list[int] = None): Consider only a subset of edges for the loss function.
+163                                                        All edges if None.
+164            only_on_faces (None | list[int] = None): Consider only a subset of faces for the loss function.
+165                                                        All faces if None.
+166        """
+167        # To be defined by child classes
+168        if self._outer_opt_func is None:
+169            msg = "The outer function was not initialized."
+170            raise AttributeError(msg)
+171        else:
+172            self._outer_opt_func(mesh, *self._selection_to_jax_arrays(only_on_vertices, only_on_edges, only_on_faces))
+173
+174    def bilevel_optimization(
+175        self,
+176        mesh: Mesh,
+177        only_on_vertices: None | list[int] = None,
+178        only_on_edges: None | list[int] = None,
+179        only_on_faces: None | list[int] = None,
+180    ) -> list[float]:
+181        """Optimize the mesh for the loss functions given.
+182
+183        Args:
+184            mesh (Mesh): The mesh to act on.
+185            only_on_vertices (None | list[int] = None): Consider only a subset of vertices for the loss function.
+186                                                        All vertices if None.
+187            only_on_edges (None | list[int] = None): Consider only a subset of edges for the loss function.
+188                                                        All edges if None.
+189            only_on_faces (None | list[int] = None): Consider only a subset of faces for the loss function.
+190                                                        All faces if None.
+191
+192        Returns:
+193            list[float]: History of loss values during optimization.
+194        """
+195        self.outer_optimization(mesh, only_on_vertices, only_on_edges, only_on_faces)
+196        return self.inner_optimization(mesh, only_on_vertices, only_on_edges, only_on_faces)
+197
+198    def do_n_bilevel_optimization(  # noqa: C901
+199        self,
+200        nb_epochs: int,
+201        mesh: Mesh,
+202        only_on_vertices: None | list[int] = None,
+203        only_on_edges: None | list[int] = None,
+204        only_on_faces: None | list[int] = None,
+205        pre_inner_optimization: bool = False,
+206        report_every: int = 0,
+207        also_report_to_stdout: bool = False,
+208        save_plotmesh_every: int = 0,
+209        save_mesh_data_every: int = 0,
+210        save_folder: str = ".",
+211    ) -> None:
+212        """Optimize the mesh for the loss function given.
+213
+214        Args:
+215            nb_epochs (int): The number of bilevel optimization steps to perform.
+216            mesh (Mesh): The mesh to act on.
+217            only_on_vertices (None | list[int] = None): Consider only a subset of vertices for the loss function.
+218                                                        All vertices if None.
+219            only_on_edges (None | list[int] = None): Consider only a subset of edges for the loss function.
+220                                                        All edges if None.
+221            only_on_faces (None | list[int] = None): Consider only a subset of faces for the loss function.
+222                                                        All faces if None.
+223            pre_inner_optimization (bool = False): Whether to perform an optional initial inner optimization first.
+224            report_every (int = 0): If strictly positive, report current state (costs, metrics, time) at this frequency.
+225            also_report_to_stdout (bool = False): Whether to also report to stdout (print).
+226            save_plotmesh_every (int = 0): If positive, save the plot of the mesh at this frequency.
+227            save_mesh_data_every (int = 0): If positive, save the mesh data at this frequency.
+228            save_folder: (str = "."): Base folder where to save data if needed.
+229        """
+230        save_folder_path = Path(save_folder)
+231        save_folder_path.mkdir(parents=True, exist_ok=True)
+232        mesh_save_plot_folder = save_folder_path / "meshes_plot"
+233        mesh_save_data_folder = save_folder_path / "meshes_data"
+234        outer_cost_graph_filename = save_folder_path / "outer_cost_over_time.png"
+235        inner_cost_graph_filename = save_folder_path / "inner_cost_over_time.png"
+236        summary_filename = save_folder_path / "summary.csv"
+237        parameters_filename = save_folder_path / "hyper-parameters.txt"
+238
+239        with parameters_filename.open("w") as f:
+240            f.write(self.self_summary())
+241
+242        all_epoch_data = []
+243        epoch_data = []
+244        if save_plotmesh_every > 0:
+245            mesh_save_plot_folder.mkdir(parents=True, exist_ok=True)
+246            plot_mesh(mesh, show=False, save=True, save_path=str(mesh_save_plot_folder / "mesh_epoch_0.png"))
+247        if save_mesh_data_every > 0:
+248            mesh_save_data_folder.mkdir(parents=True, exist_ok=True)
+249            mesh.save_mesh(path=str(mesh_save_data_folder / "mesh_epoch_0.npz"))
+250
+251        x_epochs: list[float] = []
+252        y_outer_costs: list[float] = []
+253        y_inner_costs: list[float] = []
+254        if report_every > 0:
+255            epoch_data = [0]
+256            outer_cost = self.compute_outer_loss(mesh, only_on_vertices, only_on_edges, only_on_faces)
+257            inner_cost = self.compute_inner_loss(mesh)
+258            x_epochs.append(0)
+259            y_outer_costs.append(outer_cost)
+260            y_inner_costs.append(inner_cost)
+261            epoch_data.extend([outer_cost, inner_cost, 0, 0])  # the zeros are time and delta time
+262            # Reset and put initial metrics
+263            for metric_name in self.custom_metrics:
+264                self.custom_metrics[metric_name][1].clear()
+265                self.custom_metrics[metric_name][2].clear()
+266                self.custom_metrics[metric_name][1].append(0.0)
+267                metric = self.custom_metrics[metric_name][0](mesh, self)
+268                self.custom_metrics[metric_name][2].append(metric)
+269                epoch_data.append(metric)
+270            all_epoch_data.append(epoch_data)
+271            pandas.DataFrame(
+272                all_epoch_data,
+273                columns=[
+274                    "Epoch",
+275                    "Outer cost",
+276                    "Inner cost",
+277                    "Time (s)",
+278                    "Delta Time (s)",
+279                    *(metric_name for metric_name in self.custom_metrics),
+280                ],  # ty:ignore[invalid-argument-type]
+281            ).to_csv(summary_filename)
+282
+283            if also_report_to_stdout:
+284                msg = "First epoch may be a bit long."
+285                msg += f" Initial state: Outer cost = {outer_cost}, Inner cost = {inner_cost}"
+286                for metric_name in self.custom_metrics:
+287                    msg += f", {metric_name} = {self.custom_metrics[metric_name][2][-1]}"
+288                print(msg)
+289
+290        if pre_inner_optimization:
+291            self.inner_optimization(mesh, only_on_vertices, only_on_edges, only_on_faces)
+292
+293        t_initial = perf_counter()
+294        for epoch in range(1, nb_epochs + 1):
+295            epoch_data = [epoch]
+296            t_begin_of_epoch = perf_counter()
+297
+298            self.bilevel_optimization(mesh, only_on_vertices, only_on_edges, only_on_faces)
+299            t_end_of_epoch = perf_counter()
+300            total_time = t_end_of_epoch - t_initial
+301            delta_time = t_end_of_epoch - t_begin_of_epoch
+302
+303            if save_plotmesh_every > 0 and epoch % save_plotmesh_every == 0:
+304                plot_mesh(mesh, show=False, save=True, save_path=str(mesh_save_plot_folder / f"mesh_epoch_{epoch}.png"))
+305            if save_mesh_data_every > 0 and epoch % save_mesh_data_every == 0:
+306                mesh.save_mesh(path=str(mesh_save_data_folder / f"mesh_epoch_{epoch}.npz"))
+307
+308            if report_every > 0 and epoch % report_every == 0:
+309                # Compute results data for this step
+310                outer_cost = self.compute_outer_loss(mesh, only_on_vertices, only_on_edges, only_on_faces)
+311                inner_cost = self.compute_inner_loss(mesh)
+312                x_epochs.append(epoch)
+313                y_outer_costs.append(outer_cost)
+314                y_inner_costs.append(inner_cost)
+315                epoch_data.extend([outer_cost, inner_cost, total_time, delta_time])  # the zeros are time and delta time
+316                for metric_name in self.custom_metrics:
+317                    self.custom_metrics[metric_name][1].append(epoch)
+318                    metric = self.custom_metrics[metric_name][0](mesh, self)
+319                    self.custom_metrics[metric_name][2].append(metric)
+320                    epoch_data.append(metric)
+321
+322                all_epoch_data.append(epoch_data)
+323                pandas.DataFrame(
+324                    all_epoch_data,
+325                    columns=[
+326                        "Epoch",
+327                        "Outer cost",
+328                        "Inner cost",
+329                        "Time (s)",
+330                        "Delta Time (s)",
+331                        *(metric_name for metric_name in self.custom_metrics),
+332                    ],  # ty:ignore[invalid-argument-type]
+333                ).to_csv(summary_filename)
+334
+335                # Report in log.
+336                if also_report_to_stdout:
+337                    msg = f"\nEpoch {epoch}/{nb_epochs} : Outer cost = {outer_cost}, Inner cost = {inner_cost}"
+338                    for metric_name in self.custom_metrics:
+339                        msg += f", {metric_name} = {self.custom_metrics[metric_name][2][-1]}"
+340                    msg += f", Time = {total_time}s (+{delta_time}s)"
+341                    print(msg)
+342
+343                # Update graphs
+344                save_simple_xy_graph(
+345                    str(outer_cost_graph_filename),
+346                    x_epochs,
+347                    y_outer_costs,
+348                    "Outer cost over time",
+349                    "Epoch",
+350                    "Outer cost",
+351                )
+352                save_simple_xy_graph(
+353                    str(inner_cost_graph_filename),
+354                    x_epochs,
+355                    y_inner_costs,
+356                    "Inner cost over time",
+357                    "Epoch",
+358                    "Inner cost",
+359                )
+360                for metric_name in self.custom_metrics:
+361                    graph_name = save_folder_path / (metric_name + ".png")
+362                    save_simple_xy_graph(
+363                        str(graph_name),
+364                        self.custom_metrics[metric_name][1],
+365                        self.custom_metrics[metric_name][2],
+366                        f"{metric_name} over time",
+367                        "Epoch",
+368                        metric_name,
+369                    )
+370
+371    def self_summary(self) -> str:
+372        """Give a summary of hyper-parameters of the optimizer."""
+373        summary = f"Bi-level optimization method: {self.bilevel_optimization_method.value}\n"
+374        if self.loss_function_inner is not None:
+375            summary += f"Inner loss function: {self.loss_function_inner.__name__}\n"  # ty:ignore[unresolved-attribute]
+376        else:
+377            summary += "Inner loss function: None\n"
+378        if self.loss_function_outer is not None:
+379            summary += f"Outer loss function: {self.loss_function_outer.__name__}\n"  # ty:ignore[unresolved-attribute]
+380        else:
+381            summary += "Outer loss function: None\n"
+382        summary += f"Max number of iterations: {self.max_nb_iterations}\n"
+383        summary += f"Tolerance: {self.tolerance}\n"
+384        summary += f"Patience: {self.patience}\n"
+385        summary += f"Minimum distance for T1: {self.min_dist_T1}\n"
+386        summary += f"Update T1: {self.update_T1}\n"
+387        if self._update_T1_func is not None:
+388            summary += f"Update T1 function: {self._update_T1_func.__name__}\n"  # ty:ignore[unresolved-attribute]
+389        else:
+390            summary += "Update T1 function: None\n"
+391        summary += f"Beta: {self.beta}"
+392        return summary
+393
+394    def add_custom_metric(self, name: str, function: Callable[[Any, Any], float]) -> None:
+395        """Add a custom metric to the metrics to save when performing n bilevel optimizations.
+396
+397        The function must take two arguments : a mesh and a bilevel optimizer.
+398        """
+399        self.custom_metrics[name] = (function, [], [])
+400
+401    def remove_custom_metric(self, name: str) -> None:
+402        """Remove a custom metric from the metrics to save when performing n bilevel optimizations."""
+403        if name in self.custom_metrics:
+404            del self.custom_metrics[name]
+405
+406    def _selection_to_jax_arrays(
+407        self,
+408        only_on_vertices: None | list[int] = None,
+409        only_on_edges: None | list[int] = None,
+410        only_on_faces: None | list[int] = None,
+411    ) -> tuple[Array | None, Array | None, Array | None]:
+412        selected_vertices, selected_edges, selected_faces = None, None, None
+413        if only_on_vertices is not None:
+414            selected_vertices = jnp.array(only_on_vertices)
+415        if only_on_edges is not None:
+416            selected_edges = jnp.array(only_on_edges)
+417        if only_on_faces is not None:
+418            selected_faces = jnp.array(only_on_faces)
+419        return selected_vertices, selected_edges, selected_faces
+
+ + +

Abstract base class for Bi-level optimizers.

+ +

Use the specialezed corresponding ones:

+ + +
+ + +
+ +
+ + _BilevelOptimizer() + + + +
+ +
31    def __init__(self) -> None:
+32        """Initialize shared parameters and hyper-parameters between Bi-level optimizers."""
+33        self.bilevel_optimization_method: BilevelOptimizationMethod = BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION
+34        """Which bilevel optimization method to use. Defaults to Equilibrium Propagation."""
+35        self.inner_solver: optax.GradientTransformation = optax.sgd(learning_rate=0.01)
+36        """Which inner solver to use. Defaults to `optax.sgd(learning_rate=0.01)`."""
+37        self.outer_solver: optax.GradientTransformation = optax.adam(learning_rate=0.0001, nesterov=True)
+38        """Which outer solver to use. Defaults to `optax.adam(learning_rate=0.0001, nesterov=True)`"""
+39        self.loss_function_inner: Callable | None = None
+40        """Define your inner loss function."""
+41        self.loss_function_outer: Callable | None = None
+42        """Define your outer loss function."""
+43        self.custom_metrics: dict[str, tuple[Callable[[Any, Any], float], list[float], list[float]]] = {}
+44        """Define custom metrics that can be saved during optimization.
+45        The function must take two arguments : a mesh and a bilevel optimizer ; and return a float."""
+46
+47        self.max_nb_iterations: int = 1000
+48        """Maximum number of iterations during an optimization step."""
+49        self.tolerance: float = 1e-4
+50        """Below this level, we consider that the loss is stagnating."""
+51        self.patience: int = 5
+52        """Maximum number of consecutive stagnating loss before we stop."""
+53
+54        self.min_dist_T1: float = 0.005
+55        """Threshold to perform T1 transitions."""
+56        self._update_T1: bool = False
+57        self._update_T1_func: Callable | None = None  # value set by _set_update_T1_func
+58
+59        # These values will be set in the init function of child classes
+60        self._inner_opt_func: Callable[[Mesh, Array | None, Array | None, Array | None], list[float]] | None = None
+61        self._outer_opt_func: Callable[[Mesh, Array | None, Array | None, Array | None], None] | None = None
+62
+63        self.update_T1 = True  # Force the setting of update T1 func
+64        """Perform T1 transitions if necessary."""
+65
+66        # Targets
+67        self.vertices_target = jnp.array([])
+68        """Vertices table of a target mesh if any."""
+69        self.edges_target = jnp.array([])
+70        """Edges table of a target mesh if any."""
+71        self.faces_target = jnp.array([])
+72        """Faces table of a target mesh if any."""
+73        # Those attributes are not always used (depends on the bilevel_optimization_method)
+74        self.image_target: Array = jnp.array([])
+75        """Image target if any."""
+76        self.beta = 0.01
+77        """β parameter used for Equilibrium Propagation."""
+
+ + +

Initialize shared parameters and hyper-parameters between Bi-level optimizers.

+
+ + +
+
+
+ bilevel_optimization_method: BilevelOptimizationMethod + + +
+ + +

Which bilevel optimization method to use. Defaults to Equilibrium Propagation.

+
+ + +
+
+
+ inner_solver: optax._src.base.GradientTransformation + + +
+ + +

Which inner solver to use. Defaults to optax.sgd(learning_rate=0.01).

+
+ + +
+
+
+ outer_solver: optax._src.base.GradientTransformation + + +
+ + +

Which outer solver to use. Defaults to optax.adam(learning_rate=0.0001, nesterov=True)

+
+ + +
+
+
+ loss_function_inner: Callable | None + + +
+ + +

Define your inner loss function.

+
+ + +
+
+
+ loss_function_outer: Callable | None + + +
+ + +

Define your outer loss function.

+
+ + +
+
+
+ custom_metrics: dict[str, tuple[Callable[[typing.Any, typing.Any], float], list[float], list[float]]] + + +
+ + +

Define custom metrics that can be saved during optimization. +The function must take two arguments : a mesh and a bilevel optimizer ; and return a float.

+
+ + +
+
+
+ max_nb_iterations: int + + +
+ + +

Maximum number of iterations during an optimization step.

+
+ + +
+
+
+ tolerance: float + + +
+ + +

Below this level, we consider that the loss is stagnating.

+
+ + +
+
+
+ patience: int + + +
+ + +

Maximum number of consecutive stagnating loss before we stop.

+
+ + +
+
+
+ min_dist_T1: float + + +
+ + +

Threshold to perform T1 transitions.

+
+ + +
+
+ +
+ update_T1: bool + + + +
+ +
83    @property
+84    def update_T1(self) -> bool:  # noqa: N802
+85        """Whether to process T1 topological operations or not."""
+86        return self._update_T1
+
+ + +

Perform T1 transitions if necessary.

+
+ + +
+
+
+ vertices_target + + +
+ + +

Vertices table of a target mesh if any.

+
+ + +
+
+
+ edges_target + + +
+ + +

Edges table of a target mesh if any.

+
+ + +
+
+
+ faces_target + + +
+ + +

Faces table of a target mesh if any.

+
+ + +
+
+
+ image_target: jax.Array + + +
+ + +

Image target if any.

+
+ + +
+
+
+ beta + + +
+ + +

β parameter used for Equilibrium Propagation.

+
+ + +
+
+ +
+ + def + compute_outer_loss( self, mesh: Mesh, only_on_vertices: None | list[int] = None, only_on_edges: None | list[int] = None, only_on_faces: None | list[int] = None) -> float: + + + +
+ +
 93    def compute_outer_loss(
+ 94        self,
+ 95        mesh: Mesh,
+ 96        only_on_vertices: None | list[int] = None,
+ 97        only_on_edges: None | list[int] = None,
+ 98        only_on_faces: None | list[int] = None,
+ 99    ) -> float:
+100        """Get the result of self.loss_function_outer called with the correct arguments.
+101
+102        Must be implemented by child classes.
+103        """
+104        raise NotImplementedError
+
+ + +

Get the result of self.loss_function_outer called with the correct arguments.

+ +

Must be implemented by child classes.

+
+ + +
+
+ +
+ + def + compute_inner_loss( self, mesh: Mesh, only_on_vertices: None | list[int] = None, only_on_edges: None | list[int] = None, only_on_faces: None | list[int] = None) -> float: + + + +
+ +
106    def compute_inner_loss(
+107        self,
+108        mesh: Mesh,
+109        only_on_vertices: None | list[int] = None,
+110        only_on_edges: None | list[int] = None,
+111        only_on_faces: None | list[int] = None,
+112    ) -> float:
+113        """Get the result of self.loss_function_inner called with the correct arguments.
+114
+115        Must be implemented by child classes.
+116        """
+117        raise NotImplementedError
+
+ + +

Get the result of self.loss_function_inner called with the correct arguments.

+ +

Must be implemented by child classes.

+
+ + +
+
+ +
+ + def + inner_optimization( self, mesh: Mesh, only_on_vertices: None | list[int] = None, only_on_edges: None | list[int] = None, only_on_faces: None | list[int] = None) -> list[float]: + + + +
+ +
119    def inner_optimization(
+120        self,
+121        mesh: Mesh,
+122        only_on_vertices: None | list[int] = None,
+123        only_on_edges: None | list[int] = None,
+124        only_on_faces: None | list[int] = None,
+125    ) -> list[float]:
+126        """Optimize the mesh for the inner loss function.
+127
+128        Args:
+129            mesh (Mesh): The mesh to act on.
+130            only_on_vertices (None | list[int] = None): Consider only a subset of vertices for the loss function.
+131                                                        All vertices if None.
+132            only_on_edges (None | list[int] = None): Consider only a subset of edges for the loss function.
+133                                                        All edges if None.
+134            only_on_faces (None | list[int] = None): Consider only a subset of faces for the loss function.
+135                                                        All faces if None.
+136
+137        Returns:
+138            list[float]: Histor of loss values during optimization.
+139        """
+140        # To be defined by child classes
+141        if self._inner_opt_func is None:
+142            msg = "The inner function was not initialized."
+143            raise AttributeError(msg)
+144        else:
+145            return self._inner_opt_func(
+146                mesh, *self._selection_to_jax_arrays(only_on_vertices, only_on_edges, only_on_faces)
+147            )
+
+ + +

Optimize the mesh for the inner loss function.

+ +
Arguments:
+ +
    +
  • mesh (Mesh): The mesh to act on.
  • +
  • only_on_vertices (None | list[int] = None): Consider only a subset of vertices for the loss function. +All vertices if None.
  • +
  • only_on_edges (None | list[int] = None): Consider only a subset of edges for the loss function. +All edges if None.
  • +
  • only_on_faces (None | list[int] = None): Consider only a subset of faces for the loss function. +All faces if None.
  • +
+ +
Returns:
+ +
+

list[float]: Histor of loss values during optimization.

+
+
+ + +
+
+ +
+ + def + outer_optimization( self, mesh: Mesh, only_on_vertices: None | list[int] = None, only_on_edges: None | list[int] = None, only_on_faces: None | list[int] = None) -> None: + + + +
+ +
149    def outer_optimization(
+150        self,
+151        mesh: Mesh,
+152        only_on_vertices: None | list[int] = None,
+153        only_on_edges: None | list[int] = None,
+154        only_on_faces: None | list[int] = None,
+155    ) -> None:
+156        """Optimize the mesh for the outer loss function.
+157
+158        Args:
+159            mesh (Mesh): The mesh to act on.
+160            only_on_vertices (None | list[int] = None): Consider only a subset of vertices for the loss function.
+161                                                        All vertices if None.
+162            only_on_edges (None | list[int] = None): Consider only a subset of edges for the loss function.
+163                                                        All edges if None.
+164            only_on_faces (None | list[int] = None): Consider only a subset of faces for the loss function.
+165                                                        All faces if None.
+166        """
+167        # To be defined by child classes
+168        if self._outer_opt_func is None:
+169            msg = "The outer function was not initialized."
+170            raise AttributeError(msg)
+171        else:
+172            self._outer_opt_func(mesh, *self._selection_to_jax_arrays(only_on_vertices, only_on_edges, only_on_faces))
+
+ + +

Optimize the mesh for the outer loss function.

+ +
Arguments:
+ +
    +
  • mesh (Mesh): The mesh to act on.
  • +
  • only_on_vertices (None | list[int] = None): Consider only a subset of vertices for the loss function. +All vertices if None.
  • +
  • only_on_edges (None | list[int] = None): Consider only a subset of edges for the loss function. +All edges if None.
  • +
  • only_on_faces (None | list[int] = None): Consider only a subset of faces for the loss function. +All faces if None.
  • +
+
+ + +
+
+ +
+ + def + bilevel_optimization( self, mesh: Mesh, only_on_vertices: None | list[int] = None, only_on_edges: None | list[int] = None, only_on_faces: None | list[int] = None) -> list[float]: + + + +
+ +
174    def bilevel_optimization(
+175        self,
+176        mesh: Mesh,
+177        only_on_vertices: None | list[int] = None,
+178        only_on_edges: None | list[int] = None,
+179        only_on_faces: None | list[int] = None,
+180    ) -> list[float]:
+181        """Optimize the mesh for the loss functions given.
+182
+183        Args:
+184            mesh (Mesh): The mesh to act on.
+185            only_on_vertices (None | list[int] = None): Consider only a subset of vertices for the loss function.
+186                                                        All vertices if None.
+187            only_on_edges (None | list[int] = None): Consider only a subset of edges for the loss function.
+188                                                        All edges if None.
+189            only_on_faces (None | list[int] = None): Consider only a subset of faces for the loss function.
+190                                                        All faces if None.
+191
+192        Returns:
+193            list[float]: History of loss values during optimization.
+194        """
+195        self.outer_optimization(mesh, only_on_vertices, only_on_edges, only_on_faces)
+196        return self.inner_optimization(mesh, only_on_vertices, only_on_edges, only_on_faces)
+
+ + +

Optimize the mesh for the loss functions given.

+ +
Arguments:
+ +
    +
  • mesh (Mesh): The mesh to act on.
  • +
  • only_on_vertices (None | list[int] = None): Consider only a subset of vertices for the loss function. +All vertices if None.
  • +
  • only_on_edges (None | list[int] = None): Consider only a subset of edges for the loss function. +All edges if None.
  • +
  • only_on_faces (None | list[int] = None): Consider only a subset of faces for the loss function. +All faces if None.
  • +
+ +
Returns:
+ +
+

list[float]: History of loss values during optimization.

+
+
+ + +
+
+ +
+ + def + do_n_bilevel_optimization( self, nb_epochs: int, mesh: Mesh, only_on_vertices: None | list[int] = None, only_on_edges: None | list[int] = None, only_on_faces: None | list[int] = None, pre_inner_optimization: bool = False, report_every: int = 0, also_report_to_stdout: bool = False, save_plotmesh_every: int = 0, save_mesh_data_every: int = 0, save_folder: str = '.') -> None: + + + +
+ +
198    def do_n_bilevel_optimization(  # noqa: C901
+199        self,
+200        nb_epochs: int,
+201        mesh: Mesh,
+202        only_on_vertices: None | list[int] = None,
+203        only_on_edges: None | list[int] = None,
+204        only_on_faces: None | list[int] = None,
+205        pre_inner_optimization: bool = False,
+206        report_every: int = 0,
+207        also_report_to_stdout: bool = False,
+208        save_plotmesh_every: int = 0,
+209        save_mesh_data_every: int = 0,
+210        save_folder: str = ".",
+211    ) -> None:
+212        """Optimize the mesh for the loss function given.
+213
+214        Args:
+215            nb_epochs (int): The number of bilevel optimization steps to perform.
+216            mesh (Mesh): The mesh to act on.
+217            only_on_vertices (None | list[int] = None): Consider only a subset of vertices for the loss function.
+218                                                        All vertices if None.
+219            only_on_edges (None | list[int] = None): Consider only a subset of edges for the loss function.
+220                                                        All edges if None.
+221            only_on_faces (None | list[int] = None): Consider only a subset of faces for the loss function.
+222                                                        All faces if None.
+223            pre_inner_optimization (bool = False): Whether to perform an optional initial inner optimization first.
+224            report_every (int = 0): If strictly positive, report current state (costs, metrics, time) at this frequency.
+225            also_report_to_stdout (bool = False): Whether to also report to stdout (print).
+226            save_plotmesh_every (int = 0): If positive, save the plot of the mesh at this frequency.
+227            save_mesh_data_every (int = 0): If positive, save the mesh data at this frequency.
+228            save_folder: (str = "."): Base folder where to save data if needed.
+229        """
+230        save_folder_path = Path(save_folder)
+231        save_folder_path.mkdir(parents=True, exist_ok=True)
+232        mesh_save_plot_folder = save_folder_path / "meshes_plot"
+233        mesh_save_data_folder = save_folder_path / "meshes_data"
+234        outer_cost_graph_filename = save_folder_path / "outer_cost_over_time.png"
+235        inner_cost_graph_filename = save_folder_path / "inner_cost_over_time.png"
+236        summary_filename = save_folder_path / "summary.csv"
+237        parameters_filename = save_folder_path / "hyper-parameters.txt"
+238
+239        with parameters_filename.open("w") as f:
+240            f.write(self.self_summary())
+241
+242        all_epoch_data = []
+243        epoch_data = []
+244        if save_plotmesh_every > 0:
+245            mesh_save_plot_folder.mkdir(parents=True, exist_ok=True)
+246            plot_mesh(mesh, show=False, save=True, save_path=str(mesh_save_plot_folder / "mesh_epoch_0.png"))
+247        if save_mesh_data_every > 0:
+248            mesh_save_data_folder.mkdir(parents=True, exist_ok=True)
+249            mesh.save_mesh(path=str(mesh_save_data_folder / "mesh_epoch_0.npz"))
+250
+251        x_epochs: list[float] = []
+252        y_outer_costs: list[float] = []
+253        y_inner_costs: list[float] = []
+254        if report_every > 0:
+255            epoch_data = [0]
+256            outer_cost = self.compute_outer_loss(mesh, only_on_vertices, only_on_edges, only_on_faces)
+257            inner_cost = self.compute_inner_loss(mesh)
+258            x_epochs.append(0)
+259            y_outer_costs.append(outer_cost)
+260            y_inner_costs.append(inner_cost)
+261            epoch_data.extend([outer_cost, inner_cost, 0, 0])  # the zeros are time and delta time
+262            # Reset and put initial metrics
+263            for metric_name in self.custom_metrics:
+264                self.custom_metrics[metric_name][1].clear()
+265                self.custom_metrics[metric_name][2].clear()
+266                self.custom_metrics[metric_name][1].append(0.0)
+267                metric = self.custom_metrics[metric_name][0](mesh, self)
+268                self.custom_metrics[metric_name][2].append(metric)
+269                epoch_data.append(metric)
+270            all_epoch_data.append(epoch_data)
+271            pandas.DataFrame(
+272                all_epoch_data,
+273                columns=[
+274                    "Epoch",
+275                    "Outer cost",
+276                    "Inner cost",
+277                    "Time (s)",
+278                    "Delta Time (s)",
+279                    *(metric_name for metric_name in self.custom_metrics),
+280                ],  # ty:ignore[invalid-argument-type]
+281            ).to_csv(summary_filename)
+282
+283            if also_report_to_stdout:
+284                msg = "First epoch may be a bit long."
+285                msg += f" Initial state: Outer cost = {outer_cost}, Inner cost = {inner_cost}"
+286                for metric_name in self.custom_metrics:
+287                    msg += f", {metric_name} = {self.custom_metrics[metric_name][2][-1]}"
+288                print(msg)
+289
+290        if pre_inner_optimization:
+291            self.inner_optimization(mesh, only_on_vertices, only_on_edges, only_on_faces)
+292
+293        t_initial = perf_counter()
+294        for epoch in range(1, nb_epochs + 1):
+295            epoch_data = [epoch]
+296            t_begin_of_epoch = perf_counter()
+297
+298            self.bilevel_optimization(mesh, only_on_vertices, only_on_edges, only_on_faces)
+299            t_end_of_epoch = perf_counter()
+300            total_time = t_end_of_epoch - t_initial
+301            delta_time = t_end_of_epoch - t_begin_of_epoch
+302
+303            if save_plotmesh_every > 0 and epoch % save_plotmesh_every == 0:
+304                plot_mesh(mesh, show=False, save=True, save_path=str(mesh_save_plot_folder / f"mesh_epoch_{epoch}.png"))
+305            if save_mesh_data_every > 0 and epoch % save_mesh_data_every == 0:
+306                mesh.save_mesh(path=str(mesh_save_data_folder / f"mesh_epoch_{epoch}.npz"))
+307
+308            if report_every > 0 and epoch % report_every == 0:
+309                # Compute results data for this step
+310                outer_cost = self.compute_outer_loss(mesh, only_on_vertices, only_on_edges, only_on_faces)
+311                inner_cost = self.compute_inner_loss(mesh)
+312                x_epochs.append(epoch)
+313                y_outer_costs.append(outer_cost)
+314                y_inner_costs.append(inner_cost)
+315                epoch_data.extend([outer_cost, inner_cost, total_time, delta_time])  # the zeros are time and delta time
+316                for metric_name in self.custom_metrics:
+317                    self.custom_metrics[metric_name][1].append(epoch)
+318                    metric = self.custom_metrics[metric_name][0](mesh, self)
+319                    self.custom_metrics[metric_name][2].append(metric)
+320                    epoch_data.append(metric)
+321
+322                all_epoch_data.append(epoch_data)
+323                pandas.DataFrame(
+324                    all_epoch_data,
+325                    columns=[
+326                        "Epoch",
+327                        "Outer cost",
+328                        "Inner cost",
+329                        "Time (s)",
+330                        "Delta Time (s)",
+331                        *(metric_name for metric_name in self.custom_metrics),
+332                    ],  # ty:ignore[invalid-argument-type]
+333                ).to_csv(summary_filename)
+334
+335                # Report in log.
+336                if also_report_to_stdout:
+337                    msg = f"\nEpoch {epoch}/{nb_epochs} : Outer cost = {outer_cost}, Inner cost = {inner_cost}"
+338                    for metric_name in self.custom_metrics:
+339                        msg += f", {metric_name} = {self.custom_metrics[metric_name][2][-1]}"
+340                    msg += f", Time = {total_time}s (+{delta_time}s)"
+341                    print(msg)
+342
+343                # Update graphs
+344                save_simple_xy_graph(
+345                    str(outer_cost_graph_filename),
+346                    x_epochs,
+347                    y_outer_costs,
+348                    "Outer cost over time",
+349                    "Epoch",
+350                    "Outer cost",
+351                )
+352                save_simple_xy_graph(
+353                    str(inner_cost_graph_filename),
+354                    x_epochs,
+355                    y_inner_costs,
+356                    "Inner cost over time",
+357                    "Epoch",
+358                    "Inner cost",
+359                )
+360                for metric_name in self.custom_metrics:
+361                    graph_name = save_folder_path / (metric_name + ".png")
+362                    save_simple_xy_graph(
+363                        str(graph_name),
+364                        self.custom_metrics[metric_name][1],
+365                        self.custom_metrics[metric_name][2],
+366                        f"{metric_name} over time",
+367                        "Epoch",
+368                        metric_name,
+369                    )
+
+ + +

Optimize the mesh for the loss function given.

+ +
Arguments:
+ +
    +
  • nb_epochs (int): The number of bilevel optimization steps to perform.
  • +
  • mesh (Mesh): The mesh to act on.
  • +
  • only_on_vertices (None | list[int] = None): Consider only a subset of vertices for the loss function. +All vertices if None.
  • +
  • only_on_edges (None | list[int] = None): Consider only a subset of edges for the loss function. +All edges if None.
  • +
  • only_on_faces (None | list[int] = None): Consider only a subset of faces for the loss function. +All faces if None.
  • +
  • pre_inner_optimization (bool = False): Whether to perform an optional initial inner optimization first.
  • +
  • report_every (int = 0): If strictly positive, report current state (costs, metrics, time) at this frequency.
  • +
  • also_report_to_stdout (bool = False): Whether to also report to stdout (print).
  • +
  • save_plotmesh_every (int = 0): If positive, save the plot of the mesh at this frequency.
  • +
  • save_mesh_data_every (int = 0): If positive, save the mesh data at this frequency.
  • +
  • save_folder: (str = "."): Base folder where to save data if needed.
  • +
+
+ + +
+
+ +
+ + def + self_summary(self) -> str: + + + +
+ +
371    def self_summary(self) -> str:
+372        """Give a summary of hyper-parameters of the optimizer."""
+373        summary = f"Bi-level optimization method: {self.bilevel_optimization_method.value}\n"
+374        if self.loss_function_inner is not None:
+375            summary += f"Inner loss function: {self.loss_function_inner.__name__}\n"  # ty:ignore[unresolved-attribute]
+376        else:
+377            summary += "Inner loss function: None\n"
+378        if self.loss_function_outer is not None:
+379            summary += f"Outer loss function: {self.loss_function_outer.__name__}\n"  # ty:ignore[unresolved-attribute]
+380        else:
+381            summary += "Outer loss function: None\n"
+382        summary += f"Max number of iterations: {self.max_nb_iterations}\n"
+383        summary += f"Tolerance: {self.tolerance}\n"
+384        summary += f"Patience: {self.patience}\n"
+385        summary += f"Minimum distance for T1: {self.min_dist_T1}\n"
+386        summary += f"Update T1: {self.update_T1}\n"
+387        if self._update_T1_func is not None:
+388            summary += f"Update T1 function: {self._update_T1_func.__name__}\n"  # ty:ignore[unresolved-attribute]
+389        else:
+390            summary += "Update T1 function: None\n"
+391        summary += f"Beta: {self.beta}"
+392        return summary
+
+ + +

Give a summary of hyper-parameters of the optimizer.

+
+ + +
+
+ +
+ + def + add_custom_metric( self, name: str, function: Callable[[typing.Any, typing.Any], float]) -> None: + + + +
+ +
394    def add_custom_metric(self, name: str, function: Callable[[Any, Any], float]) -> None:
+395        """Add a custom metric to the metrics to save when performing n bilevel optimizations.
+396
+397        The function must take two arguments : a mesh and a bilevel optimizer.
+398        """
+399        self.custom_metrics[name] = (function, [], [])
+
+ + +

Add a custom metric to the metrics to save when performing n bilevel optimizations.

+ +

The function must take two arguments : a mesh and a bilevel optimizer.

+
+ + +
+
+ +
+ + def + remove_custom_metric(self, name: str) -> None: + + + +
+ +
401    def remove_custom_metric(self, name: str) -> None:
+402        """Remove a custom metric from the metrics to save when performing n bilevel optimizations."""
+403        if name in self.custom_metrics:
+404            del self.custom_metrics[name]
+
+ + +

Remove a custom metric from the metrics to save when performing n bilevel optimizations.

+
+ + +
+
+
+ +
+ + class + PbcBilevelOptimizer(vertax._BilevelOptimizer): + + + +
+ +
 22class PbcBilevelOptimizer(_BilevelOptimizer):
+ 23    """Bi-level optimizer for periodic boundary condition meshes (`PbcMesh`)."""
+ 24
+ 25    def __init__(self) -> None:
+ 26        """Create a Bi-level optimizer for periodic boundary condition meshes with default parameters.
+ 27
+ 28        Does not specify the inner and outer loss yet.
+ 29        """
+ 30        super().__init__()
+ 31        self._inner_opt_func = self._inner_opt
+ 32        self._outer_opt_func = self._outer_opt
+ 33
+ 34    def _set_update_T1_func(self, b: bool) -> None:  # noqa: N802
+ 35        """Set the _update_T1_func callable with respect to whether it is needed or not.
+ 36
+ 37        Must be implemented by child classes.
+ 38        """
+ 39        if b:
+ 40            self._update_T1_func = update_T1
+ 41        else:
+ 42            self._update_T1_func = do_not_update_T1
+ 43
+ 44    def compute_outer_loss(
+ 45        self,
+ 46        mesh: Mesh,
+ 47        only_on_vertices: None | list[int] = None,
+ 48        only_on_edges: None | list[int] = None,
+ 49        only_on_faces: None | list[int] = None,
+ 50    ) -> float:
+ 51        """Get the result of self.loss_function_outer called with the correct arguments."""
+ 52        selected_vertices, selected_edges, selected_faces = self._selection_to_jax_arrays(
+ 53            only_on_vertices, only_on_edges, only_on_faces
+ 54        )
+ 55        if not isinstance(mesh, PbcMesh):
+ 56            msg = "The mesh given to a PbcBilevelOptimizer must be a PbcMesh."
+ 57            raise ValueError(msg)
+ 58        elif self.loss_function_outer is None:
+ 59            msg = "The outer loss function was not defined."
+ 60            raise AttributeError(msg)
+ 61        return float(
+ 62            self.loss_function_outer(
+ 63                mesh.vertices,
+ 64                mesh.edges,
+ 65                mesh.faces,
+ 66                mesh.width,
+ 67                mesh.height,
+ 68                self.vertices_target,
+ 69                self.edges_target,
+ 70                self.faces_target,
+ 71                selected_vertices,
+ 72                selected_edges,
+ 73                selected_faces,
+ 74                self.image_target,
+ 75            )
+ 76        )
+ 77
+ 78    def compute_inner_loss(
+ 79        self,
+ 80        mesh: Mesh,
+ 81        only_on_vertices: None | list[int] = None,  # noqa: ARG002
+ 82        only_on_edges: None | list[int] = None,  # noqa: ARG002
+ 83        only_on_faces: None | list[int] = None,  # noqa: ARG002
+ 84    ) -> float:
+ 85        """Get the result of self.loss_function_inner called with the correct arguments."""
+ 86        if not isinstance(mesh, PbcMesh):
+ 87            msg = "The mesh given to a PbcBilevelOptimizer must be a PbcMesh."
+ 88            raise ValueError(msg)
+ 89        elif self.loss_function_inner is None:
+ 90            msg = "The inner loss function was not defined."
+ 91            raise AttributeError(msg)
+ 92        return float(
+ 93            self.loss_function_inner(
+ 94                mesh.vertices,
+ 95                mesh.edges,
+ 96                mesh.faces,
+ 97                mesh.vertices_params,
+ 98                mesh.edges_params,
+ 99                mesh.faces_params,
+100            )
+101        )
+102
+103    def _inner_opt(
+104        self,
+105        mesh: Mesh,
+106        only_on_vertices: None | Array = None,
+107        only_on_edges: None | Array = None,
+108        only_on_faces: None | Array = None,
+109    ) -> list:
+110        """Call the correct inner optimization function for a PbcMesh."""
+111        if not isinstance(mesh, PbcMesh):
+112            msg = "The mesh given to a PbcBilevelOptimizer must be a PbcMesh."
+113            raise ValueError(msg)
+114        elif self.loss_function_inner is None:
+115            msg = "The inner loss function was not defined."
+116            raise AttributeError(msg)
+117        elif self._update_T1_func is None:
+118            msg = "The update T1 method was not set by a boolean."
+119            raise AttributeError(msg)
+120        else:
+121            (mesh.vertices, mesh.edges, mesh.faces), loss_history = inner_opt(
+122                vertTable=mesh.vertices,
+123                heTable=mesh.edges,
+124                faceTable=mesh.faces,
+125                width=mesh.width,
+126                height=mesh.height,
+127                vert_params=mesh.vertices_params,
+128                he_params=mesh.edges_params,
+129                face_params=mesh.faces_params,
+130                L_in=self.loss_function_inner,
+131                solver=self.inner_solver,
+132                min_dist_T1=self.min_dist_T1,
+133                iterations_max=self.max_nb_iterations,
+134                tolerance=self.tolerance,
+135                patience=self.patience,
+136                selected_verts=only_on_vertices,
+137                selected_hes=only_on_edges,
+138                selected_faces=only_on_faces,
+139                update_t1_func=self._update_T1_func,
+140            )
+141        return list(loss_history)
+142
+143    def _outer_opt(
+144        self,
+145        mesh: Mesh,
+146        selected_vertices: None | Array = None,
+147        selected_edges: None | Array = None,
+148        selected_faces: None | Array = None,
+149    ) -> None:
+150        """Call the correct outer optimization function for a PbcMesh."""
+151        if not isinstance(mesh, PbcMesh):
+152            msg = "The mesh given to a PbcBilevelOptimizer must be a PbcMesh."
+153            raise ValueError(msg)
+154        elif self.loss_function_inner is None:
+155            msg = "The inner loss function was not defined."
+156            raise AttributeError(msg)
+157        elif self.loss_function_outer is None:
+158            msg = "The outer loss function was not defined."
+159            raise AttributeError(msg)
+160        elif self._update_T1_func is None:
+161            msg = "The update T1 method was not set by a boolean."
+162            raise AttributeError(msg)
+163        else:
+164            match self.bilevel_optimization_method:
+165                case BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION:
+166                    mesh.vertices_params, mesh.edges_params, mesh.faces_params = outer_opt(
+167                        mesh.vertices,
+168                        mesh.edges,
+169                        mesh.faces,
+170                        mesh.width,
+171                        mesh.height,
+172                        mesh.vertices_params,
+173                        mesh.edges_params,
+174                        mesh.faces_params,
+175                        self.vertices_target,
+176                        self.edges_target,
+177                        self.faces_target,
+178                        self.loss_function_inner,
+179                        self.loss_function_outer,
+180                        self.inner_solver,
+181                        self.outer_solver,
+182                        self.min_dist_T1,
+183                        self.max_nb_iterations,
+184                        self.tolerance,
+185                        self.patience,
+186                        selected_vertices,
+187                        selected_edges,
+188                        selected_faces,
+189                        self.image_target,
+190                        self._update_T1_func,
+191                    )
+192
+193                case BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION:
+194                    mesh.vertices_params, mesh.edges_params, mesh.faces_params = outer_eq_prop(
+195                        mesh.vertices,
+196                        mesh.edges,
+197                        mesh.faces,
+198                        mesh.width,
+199                        mesh.height,
+200                        mesh.vertices_params,
+201                        mesh.edges_params,
+202                        mesh.faces_params,
+203                        self.vertices_target,
+204                        self.edges_target,
+205                        self.faces_target,
+206                        self.loss_function_inner,
+207                        self.loss_function_outer,
+208                        self.inner_solver,
+209                        self.outer_solver,
+210                        self.min_dist_T1,
+211                        self.max_nb_iterations,
+212                        self.tolerance,
+213                        self.patience,
+214                        selected_vertices,
+215                        selected_edges,
+216                        selected_faces,
+217                        self.image_target,
+218                        self.beta,
+219                        self._update_T1_func,
+220                    )
+221
+222                case BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION:
+223                    mesh.vertices_params, mesh.edges_params, mesh.faces_params = outer_implicit(
+224                        mesh.vertices,
+225                        mesh.edges,
+226                        mesh.faces,
+227                        mesh.width,
+228                        mesh.height,
+229                        mesh.vertices_params,
+230                        mesh.edges_params,
+231                        mesh.faces_params,
+232                        self.vertices_target,
+233                        self.edges_target,
+234                        self.faces_target,
+235                        self.loss_function_inner,
+236                        self.loss_function_outer,
+237                        self.inner_solver,
+238                        self.outer_solver,
+239                        self.min_dist_T1,
+240                        self.max_nb_iterations,
+241                        self.tolerance,
+242                        self.patience,
+243                        selected_vertices,
+244                        selected_edges,
+245                        selected_faces,
+246                        self.image_target,
+247                        self._update_T1_func,
+248                    )
+249
+250                case BilevelOptimizationMethod.ADJOINT_STATE:
+251                    mesh.vertices_params, mesh.edges_params, mesh.faces_params = outer_adjoint_state(
+252                        mesh.vertices,
+253                        mesh.edges,
+254                        mesh.faces,
+255                        mesh.width,
+256                        mesh.height,
+257                        mesh.vertices_params,
+258                        mesh.edges_params,
+259                        mesh.faces_params,
+260                        self.vertices_target,
+261                        self.edges_target,
+262                        self.faces_target,
+263                        self.loss_function_inner,
+264                        self.loss_function_outer,
+265                        self.inner_solver,
+266                        self.outer_solver,
+267                        self.min_dist_T1,
+268                        self.max_nb_iterations,
+269                        self.tolerance,
+270                        self.patience,
+271                        selected_vertices,
+272                        selected_edges,
+273                        selected_faces,
+274                        self.image_target,
+275                        self._update_T1_func,
+276                    )
+277
+278                case _:
+279                    msg = f"Method not recognized. Must be a BilevelOptimizationMethod. \
+280                        Got {self.bilevel_optimization_method}."
+281                    raise ValueError(msg)
+
+ + +

Bi-level optimizer for periodic boundary condition meshes (PbcMesh).

+
+ + +
+ +
+ + PbcBilevelOptimizer() + + + +
+ +
25    def __init__(self) -> None:
+26        """Create a Bi-level optimizer for periodic boundary condition meshes with default parameters.
+27
+28        Does not specify the inner and outer loss yet.
+29        """
+30        super().__init__()
+31        self._inner_opt_func = self._inner_opt
+32        self._outer_opt_func = self._outer_opt
+
+ + +

Create a Bi-level optimizer for periodic boundary condition meshes with default parameters.

+ +

Does not specify the inner and outer loss yet.

+
+ + +
+
+ +
+ + def + compute_outer_loss( self, mesh: Mesh, only_on_vertices: None | list[int] = None, only_on_edges: None | list[int] = None, only_on_faces: None | list[int] = None) -> float: + + + +
+ +
44    def compute_outer_loss(
+45        self,
+46        mesh: Mesh,
+47        only_on_vertices: None | list[int] = None,
+48        only_on_edges: None | list[int] = None,
+49        only_on_faces: None | list[int] = None,
+50    ) -> float:
+51        """Get the result of self.loss_function_outer called with the correct arguments."""
+52        selected_vertices, selected_edges, selected_faces = self._selection_to_jax_arrays(
+53            only_on_vertices, only_on_edges, only_on_faces
+54        )
+55        if not isinstance(mesh, PbcMesh):
+56            msg = "The mesh given to a PbcBilevelOptimizer must be a PbcMesh."
+57            raise ValueError(msg)
+58        elif self.loss_function_outer is None:
+59            msg = "The outer loss function was not defined."
+60            raise AttributeError(msg)
+61        return float(
+62            self.loss_function_outer(
+63                mesh.vertices,
+64                mesh.edges,
+65                mesh.faces,
+66                mesh.width,
+67                mesh.height,
+68                self.vertices_target,
+69                self.edges_target,
+70                self.faces_target,
+71                selected_vertices,
+72                selected_edges,
+73                selected_faces,
+74                self.image_target,
+75            )
+76        )
+
+ + +

Get the result of self.loss_function_outer called with the correct arguments.

+
+ + +
+
+ +
+ + def + compute_inner_loss( self, mesh: Mesh, only_on_vertices: None | list[int] = None, only_on_edges: None | list[int] = None, only_on_faces: None | list[int] = None) -> float: + + + +
+ +
 78    def compute_inner_loss(
+ 79        self,
+ 80        mesh: Mesh,
+ 81        only_on_vertices: None | list[int] = None,  # noqa: ARG002
+ 82        only_on_edges: None | list[int] = None,  # noqa: ARG002
+ 83        only_on_faces: None | list[int] = None,  # noqa: ARG002
+ 84    ) -> float:
+ 85        """Get the result of self.loss_function_inner called with the correct arguments."""
+ 86        if not isinstance(mesh, PbcMesh):
+ 87            msg = "The mesh given to a PbcBilevelOptimizer must be a PbcMesh."
+ 88            raise ValueError(msg)
+ 89        elif self.loss_function_inner is None:
+ 90            msg = "The inner loss function was not defined."
+ 91            raise AttributeError(msg)
+ 92        return float(
+ 93            self.loss_function_inner(
+ 94                mesh.vertices,
+ 95                mesh.edges,
+ 96                mesh.faces,
+ 97                mesh.vertices_params,
+ 98                mesh.edges_params,
+ 99                mesh.faces_params,
+100            )
+101        )
+
+ + +

Get the result of self.loss_function_inner called with the correct arguments.

+
+ + +
+
+
+ +
+ + class + BoundedBilevelOptimizer(vertax._BilevelOptimizer): + + + +
+ +
 23class BoundedBilevelOptimizer(_BilevelOptimizer):
+ 24    """Bi-level optimizer for bounded meshes (`BoundedMesh`)."""
+ 25
+ 26    def __init__(self) -> None:
+ 27        """Create a Bi-level optimizer for bounded meshes with default parameters.
+ 28
+ 29        Does not set the inner and outer loss functions yet.
+ 30        """
+ 31        super().__init__()
+ 32        self._inner_opt_func = self._inner_opt
+ 33        self._outer_opt_func = self._outer_opt
+ 34        self.angles_target = jnp.array([])
+ 35
+ 36    def _set_update_T1_func(self, b: bool) -> None:  # noqa: N802
+ 37        """Set the _update_T1_func callable with respect to whether it is needed or not.
+ 38
+ 39        Must be implemented by child classes.
+ 40        """
+ 41        if b:
+ 42            self._update_T1_func = update_T1_bounded
+ 43        else:
+ 44            self._update_T1_func = do_not_update_T1_bounded
+ 45
+ 46    def compute_outer_loss(
+ 47        self,
+ 48        mesh: Mesh,
+ 49        only_on_vertices: None | list[int] = None,
+ 50        only_on_edges: None | list[int] = None,
+ 51        only_on_faces: None | list[int] = None,
+ 52    ) -> float:
+ 53        """Get the result of self.loss_function_outer called with the correct arguments.
+ 54
+ 55        Must be implemented by child classes.
+ 56        """
+ 57        selected_vertices, selected_edges, selected_faces = self._selection_to_jax_arrays(
+ 58            only_on_vertices, only_on_edges, only_on_faces
+ 59        )
+ 60        if not isinstance(mesh, BoundedMesh):
+ 61            msg = "The mesh given to a BoundedBilevelOptimizer must be a BoundedMesh."
+ 62            raise ValueError(msg)
+ 63        elif self.loss_function_outer is None:
+ 64            msg = "The outer loss function was not defined."
+ 65            raise AttributeError(msg)
+ 66        return float(
+ 67            self.loss_function_outer(
+ 68                mesh.vertices,
+ 69                mesh.angles,
+ 70                mesh.edges,
+ 71                mesh.faces,
+ 72                self.vertices_target,
+ 73                self.angles_target,
+ 74                self.edges_target,
+ 75                self.faces_target,
+ 76                selected_vertices,
+ 77                selected_edges,
+ 78                selected_faces,
+ 79                self.image_target,
+ 80            )
+ 81        )
+ 82
+ 83    def compute_inner_loss(
+ 84        self,
+ 85        mesh: Mesh,
+ 86        only_on_vertices: None | list[int] = None,
+ 87        only_on_edges: None | list[int] = None,
+ 88        only_on_faces: None | list[int] = None,
+ 89    ) -> float:
+ 90        """Get the result of self.loss_function_inner called with the correct arguments.
+ 91
+ 92        Must be implemented by child classes.
+ 93        """
+ 94        selected_vertices, selected_edges, selected_faces = self._selection_to_jax_arrays(
+ 95            only_on_vertices, only_on_edges, only_on_faces
+ 96        )
+ 97        if not isinstance(mesh, BoundedMesh):
+ 98            msg = "The mesh given to a BoundedBilevelOptimizer must be a BoundedMesh."
+ 99            raise ValueError(msg)
+100        elif self.loss_function_inner is None:
+101            msg = "The inner loss function was not defined."
+102            raise AttributeError(msg)
+103        return float(
+104            self.loss_function_inner(
+105                mesh.vertices,
+106                mesh.angles,
+107                mesh.edges,
+108                mesh.faces,
+109                selected_vertices,
+110                selected_edges,
+111                selected_faces,
+112                mesh.vertices_params,
+113                mesh.edges_params,
+114                mesh.faces_params,
+115            )
+116        )
+117
+118    def _inner_opt(
+119        self,
+120        mesh: Mesh,
+121        only_on_vertices: None | Array = None,
+122        only_on_edges: None | Array = None,
+123        only_on_faces: None | Array = None,
+124    ) -> list:
+125        """Call the correct inner optimization function for a BoundedMesh."""
+126        if not isinstance(mesh, BoundedMesh):
+127            msg = "The mesh given to a BoundedBilevelOptimizer must be a BoundedMesh."
+128            raise ValueError(msg)
+129        elif self.loss_function_inner is None:
+130            msg = "The inner loss function was not defined."
+131            raise AttributeError(msg)
+132        elif self._update_T1_func is None:
+133            msg = "The update T1 method was not set by a boolean."
+134            raise AttributeError(msg)
+135        else:
+136            (mesh.vertices, mesh.angles, mesh.edges, mesh.faces), loss_history = inner_opt_bounded(
+137                vertTable=mesh.vertices,
+138                angTable=mesh.angles,
+139                heTable=mesh.edges,
+140                faceTable=mesh.faces,
+141                vert_params=mesh.vertices_params,
+142                he_params=mesh.edges_params,
+143                face_params=mesh.faces_params,
+144                L_in=self.loss_function_inner,
+145                solver=self.inner_solver,
+146                min_dist_T1=self.min_dist_T1,
+147                iterations_max=self.max_nb_iterations,
+148                tolerance=self.tolerance,
+149                patience=self.patience,
+150                selected_verts=only_on_vertices,
+151                selected_hes=only_on_edges,
+152                selected_faces=only_on_faces,
+153                update_T1_func=self._update_T1_func,
+154            )
+155        return list(loss_history)
+156
+157    def _outer_opt(
+158        self,
+159        mesh: Mesh,
+160        selected_vertices: None | Array = None,
+161        selected_edges: None | Array = None,
+162        selected_faces: None | Array = None,
+163    ) -> None:
+164        """Call the correct outer optimization function for a BoundedMesh."""
+165        if not isinstance(mesh, BoundedMesh):
+166            msg = "The mesh given to a BoundedBilevelOptimizer must be a BoundedMesh."
+167            raise ValueError(msg)
+168        elif self.loss_function_inner is None:
+169            msg = "The inner loss function was not defined."
+170            raise AttributeError(msg)
+171        elif self.loss_function_outer is None:
+172            msg = "The outer loss function was not defined."
+173            raise AttributeError(msg)
+174        elif self._update_T1_func is None:
+175            msg = "The update T1 method was not set by a boolean."
+176            raise AttributeError(msg)
+177        else:
+178            match self.bilevel_optimization_method:
+179                case BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION:
+180                    mesh.vertices_params, mesh.edges_params, mesh.faces_params = outer_opt_bounded(
+181                        mesh.vertices,
+182                        mesh.angles,
+183                        mesh.edges,
+184                        mesh.faces,
+185                        mesh.vertices_params,
+186                        mesh.edges_params,
+187                        mesh.faces_params,
+188                        self.vertices_target,
+189                        self.angles_target,
+190                        self.edges_target,
+191                        self.faces_target,
+192                        self.loss_function_inner,
+193                        self.loss_function_outer,
+194                        self.inner_solver,
+195                        self.outer_solver,
+196                        self.min_dist_T1,
+197                        self.max_nb_iterations,
+198                        self.tolerance,
+199                        self.patience,
+200                        selected_vertices,
+201                        selected_edges,
+202                        selected_faces,
+203                        self.image_target,
+204                        self._update_T1_func,
+205                    )
+206
+207                case BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION:
+208                    mesh.vertices_params, mesh.edges_params, mesh.faces_params = outer_eq_prop_bounded(
+209                        mesh.vertices,
+210                        mesh.angles,
+211                        mesh.edges,
+212                        mesh.faces,
+213                        mesh.vertices_params,
+214                        mesh.edges_params,
+215                        mesh.faces_params,
+216                        self.vertices_target,
+217                        self.angles_target,
+218                        self.edges_target,
+219                        self.faces_target,
+220                        self.loss_function_inner,
+221                        self.loss_function_outer,
+222                        self.inner_solver,
+223                        self.outer_solver,
+224                        self.min_dist_T1,
+225                        self.max_nb_iterations,
+226                        self.tolerance,
+227                        self.patience,
+228                        selected_vertices,
+229                        selected_edges,
+230                        selected_faces,
+231                        self.image_target,
+232                        self.beta,
+233                        self._update_T1_func,
+234                    )
+235
+236                case BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION:
+237                    mesh.vertices_params, mesh.edges_params, mesh.faces_params = outer_implicit_bounded(
+238                        mesh.vertices,
+239                        mesh.angles,
+240                        mesh.edges,
+241                        mesh.faces,
+242                        mesh.vertices_params,
+243                        mesh.edges_params,
+244                        mesh.faces_params,
+245                        self.vertices_target,
+246                        self.angles_target,
+247                        self.edges_target,
+248                        self.faces_target,
+249                        self.loss_function_inner,
+250                        self.loss_function_outer,
+251                        self.inner_solver,
+252                        self.outer_solver,
+253                        self.min_dist_T1,
+254                        self.max_nb_iterations,
+255                        self.tolerance,
+256                        self.patience,
+257                        selected_vertices,
+258                        selected_edges,
+259                        selected_faces,
+260                        self.image_target,
+261                        self._update_T1_func,
+262                    )
+263                case BilevelOptimizationMethod.ADJOINT_STATE:
+264                    mesh.vertices_params, mesh.edges_params, mesh.faces_params = outer_adjoint_state_bounded(
+265                        mesh.vertices,
+266                        mesh.angles,
+267                        mesh.edges,
+268                        mesh.faces,
+269                        mesh.vertices_params,
+270                        mesh.edges_params,
+271                        mesh.faces_params,
+272                        self.vertices_target,
+273                        self.angles_target,
+274                        self.edges_target,
+275                        self.faces_target,
+276                        self.loss_function_inner,
+277                        self.loss_function_outer,
+278                        self.inner_solver,
+279                        self.outer_solver,
+280                        self.min_dist_T1,
+281                        self.max_nb_iterations,
+282                        self.tolerance,
+283                        self.patience,
+284                        selected_vertices,
+285                        selected_edges,
+286                        selected_faces,
+287                        self.image_target,
+288                        self._update_T1_func,
+289                    )
+290                case _:
+291                    msg = f"Method not recognized. Must be a BilevelOptimizationMethod. \
+292                        Got {self.bilevel_optimization_method}."
+293                    raise AttributeError(msg)
+
+ + +

Bi-level optimizer for bounded meshes (BoundedMesh).

+
+ + +
+ +
+ + BoundedBilevelOptimizer() + + + +
+ +
26    def __init__(self) -> None:
+27        """Create a Bi-level optimizer for bounded meshes with default parameters.
+28
+29        Does not set the inner and outer loss functions yet.
+30        """
+31        super().__init__()
+32        self._inner_opt_func = self._inner_opt
+33        self._outer_opt_func = self._outer_opt
+34        self.angles_target = jnp.array([])
+
+ + +

Create a Bi-level optimizer for bounded meshes with default parameters.

+ +

Does not set the inner and outer loss functions yet.

+
+ + +
+
+
+ angles_target + + +
+ + + + +
+
+ +
+ + def + compute_outer_loss( self, mesh: Mesh, only_on_vertices: None | list[int] = None, only_on_edges: None | list[int] = None, only_on_faces: None | list[int] = None) -> float: + + + +
+ +
46    def compute_outer_loss(
+47        self,
+48        mesh: Mesh,
+49        only_on_vertices: None | list[int] = None,
+50        only_on_edges: None | list[int] = None,
+51        only_on_faces: None | list[int] = None,
+52    ) -> float:
+53        """Get the result of self.loss_function_outer called with the correct arguments.
+54
+55        Must be implemented by child classes.
+56        """
+57        selected_vertices, selected_edges, selected_faces = self._selection_to_jax_arrays(
+58            only_on_vertices, only_on_edges, only_on_faces
+59        )
+60        if not isinstance(mesh, BoundedMesh):
+61            msg = "The mesh given to a BoundedBilevelOptimizer must be a BoundedMesh."
+62            raise ValueError(msg)
+63        elif self.loss_function_outer is None:
+64            msg = "The outer loss function was not defined."
+65            raise AttributeError(msg)
+66        return float(
+67            self.loss_function_outer(
+68                mesh.vertices,
+69                mesh.angles,
+70                mesh.edges,
+71                mesh.faces,
+72                self.vertices_target,
+73                self.angles_target,
+74                self.edges_target,
+75                self.faces_target,
+76                selected_vertices,
+77                selected_edges,
+78                selected_faces,
+79                self.image_target,
+80            )
+81        )
+
+ + +

Get the result of self.loss_function_outer called with the correct arguments.

+ +

Must be implemented by child classes.

+
+ + +
+
+ +
+ + def + compute_inner_loss( self, mesh: Mesh, only_on_vertices: None | list[int] = None, only_on_edges: None | list[int] = None, only_on_faces: None | list[int] = None) -> float: + + + +
+ +
 83    def compute_inner_loss(
+ 84        self,
+ 85        mesh: Mesh,
+ 86        only_on_vertices: None | list[int] = None,
+ 87        only_on_edges: None | list[int] = None,
+ 88        only_on_faces: None | list[int] = None,
+ 89    ) -> float:
+ 90        """Get the result of self.loss_function_inner called with the correct arguments.
+ 91
+ 92        Must be implemented by child classes.
+ 93        """
+ 94        selected_vertices, selected_edges, selected_faces = self._selection_to_jax_arrays(
+ 95            only_on_vertices, only_on_edges, only_on_faces
+ 96        )
+ 97        if not isinstance(mesh, BoundedMesh):
+ 98            msg = "The mesh given to a BoundedBilevelOptimizer must be a BoundedMesh."
+ 99            raise ValueError(msg)
+100        elif self.loss_function_inner is None:
+101            msg = "The inner loss function was not defined."
+102            raise AttributeError(msg)
+103        return float(
+104            self.loss_function_inner(
+105                mesh.vertices,
+106                mesh.angles,
+107                mesh.edges,
+108                mesh.faces,
+109                selected_vertices,
+110                selected_edges,
+111                selected_faces,
+112                mesh.vertices_params,
+113                mesh.edges_params,
+114                mesh.faces_params,
+115            )
+116        )
+
+ + +

Get the result of self.loss_function_inner called with the correct arguments.

+ +

Must be implemented by child classes.

+
+ + +
+
+
+ +
+ + def + plot_mesh( mesh: Mesh, vertex_plot: VertexPlot = <VertexPlot.INVISIBLE: 3>, edge_plot: EdgePlot = <EdgePlot.BLACK: 1>, face_plot: FacePlot = <FacePlot.MULTICOLOR: 1>, vertex_parameters_name: str = '', edge_parameters_name: str = '', face_parameters_name: str = '', show: bool = True, save: bool = False, save_path: str = 'pbc_mesh.png', faces_cmap_name: str = 'cividis', edges_cmap_name: str = 'coolwarm', edges_width: float = 2, vertices_cmap_name: str = 'spring', vertices_size: float = 20, title: str = '', forced_vertex_scale: tuple[float, float] | None = None, forced_edge_scale: tuple[float, float] | None = None, forced_face_scale: tuple[float, float] | None = None) -> None: + + + +
+ +
108def plot_mesh(
+109    mesh: Mesh,
+110    vertex_plot: VertexPlot = VertexPlot.INVISIBLE,
+111    edge_plot: EdgePlot = EdgePlot.BLACK,
+112    face_plot: FacePlot = FacePlot.MULTICOLOR,
+113    vertex_parameters_name: str = "",
+114    edge_parameters_name: str = "",
+115    face_parameters_name: str = "",
+116    show: bool = True,
+117    save: bool = False,
+118    save_path: str = "pbc_mesh.png",
+119    faces_cmap_name: str = "cividis",
+120    edges_cmap_name: str = "coolwarm",
+121    edges_width: float = 2,
+122    vertices_cmap_name: str = "spring",
+123    vertices_size: float = 20,
+124    title: str = "",
+125    forced_vertex_scale: tuple[float, float] | None = None,
+126    forced_edge_scale: tuple[float, float] | None = None,
+127    forced_face_scale: tuple[float, float] | None = None,
+128) -> None:
+129    """Plot the mesh and decide to save and/or show the mesh or not."""
+130    if isinstance(mesh, PbcMesh):
+131        _plot_pbc_mesh(
+132            mesh,
+133            vertex_plot,
+134            edge_plot,
+135            face_plot,
+136            vertex_parameters_name,
+137            edge_parameters_name,
+138            face_parameters_name,
+139            show,
+140            save,
+141            save_path,
+142            faces_cmap_name,
+143            edges_cmap_name,
+144            edges_width,
+145            vertices_cmap_name,
+146            vertices_size,
+147            title,
+148            forced_vertex_scale,
+149            forced_edge_scale,
+150            forced_face_scale,
+151        )
+152    elif isinstance(mesh, BoundedMesh):
+153        _plot_bounded_mesh(
+154            mesh,
+155            vertex_plot,
+156            edge_plot,
+157            face_plot,
+158            vertex_parameters_name,
+159            edge_parameters_name,
+160            face_parameters_name,
+161            show,
+162            save,
+163            save_path,
+164            faces_cmap_name,
+165            edges_cmap_name,
+166            edges_width,
+167            vertices_cmap_name,
+168            vertices_size,
+169            title,
+170            forced_vertex_scale,
+171            forced_edge_scale,
+172            forced_face_scale,
+173        )
+
+ + +

Plot the mesh and decide to save and/or show the mesh or not.

+
+ + +
+
+ +
+ + def + get_plot_mesh( mesh: Mesh, vertex_plot: VertexPlot = <VertexPlot.INVISIBLE: 3>, edge_plot: EdgePlot = <EdgePlot.BLACK: 1>, face_plot: FacePlot = <FacePlot.MULTICOLOR: 1>, vertex_parameters_name: str = '', edge_parameters_name: str = '', face_parameters_name: str = '', faces_cmap_name: str = 'cividis', edges_cmap_name: str = 'coolwarm', edges_width: float = 2, vertices_cmap_name: str = 'spring', vertices_size: float = 20, title: str = '', forced_vertex_scale: tuple[float, float] | None = None, forced_edge_scale: tuple[float, float] | None = None, forced_face_scale: tuple[float, float] | None = None) -> tuple[matplotlib.figure.Figure, matplotlib.axes._axes.Axes]: + + + +
+ +
176def get_plot_mesh(
+177    mesh: Mesh,
+178    vertex_plot: VertexPlot = VertexPlot.INVISIBLE,
+179    edge_plot: EdgePlot = EdgePlot.BLACK,
+180    face_plot: FacePlot = FacePlot.MULTICOLOR,
+181    vertex_parameters_name: str = "",
+182    edge_parameters_name: str = "",
+183    face_parameters_name: str = "",
+184    faces_cmap_name: str = "cividis",
+185    edges_cmap_name: str = "coolwarm",
+186    edges_width: float = 2,
+187    vertices_cmap_name: str = "spring",
+188    vertices_size: float = 20,
+189    title: str = "",
+190    forced_vertex_scale: tuple[float, float] | None = None,
+191    forced_edge_scale: tuple[float, float] | None = None,
+192    forced_face_scale: tuple[float, float] | None = None,
+193) -> tuple[Figure, Axes]:
+194    """Get the matplotlib figure and and ax for one plot."""
+195    if isinstance(mesh, PbcMesh):
+196        return _get_plot_pbc_mesh(
+197            mesh,
+198            vertex_plot,
+199            edge_plot,
+200            face_plot,
+201            vertex_parameters_name,
+202            edge_parameters_name,
+203            face_parameters_name,
+204            faces_cmap_name,
+205            edges_cmap_name,
+206            edges_width,
+207            vertices_cmap_name,
+208            vertices_size,
+209            title,
+210            forced_vertex_scale,
+211            forced_edge_scale,
+212            forced_face_scale,
+213        )
+214    elif isinstance(mesh, BoundedMesh):
+215        return _get_plot_bounded_mesh(
+216            mesh,
+217            vertex_plot,
+218            edge_plot,
+219            face_plot,
+220            vertex_parameters_name,
+221            edge_parameters_name,
+222            face_parameters_name,
+223            faces_cmap_name,
+224            edges_cmap_name,
+225            edges_width,
+226            vertices_cmap_name,
+227            vertices_size,
+228            title,
+229            forced_vertex_scale,
+230            forced_edge_scale,
+231            forced_face_scale,
+232        )
+233    else:
+234        msg = f"Expected either a PbcMesh or a BoundedMesh. Got {mesh} instead."
+235        raise ValueError(msg)
+
+ + +

Get the matplotlib figure and and ax for one plot.

+
+ + +
+
+ +
+ + class + FacePlot(enum.Enum): + + + +
+ +
25class FacePlot(Enum):
+26    """What it is possible to show on a face."""
+27
+28    MULTICOLOR = 1
+29    """Each face get a random color."""
+30    FACE_PARAMETER = 2
+31    """The face color depends on its parameter."""
+32    AREA = 3
+33    """The face color depends on its area."""
+34    PERIMETER = 4
+35    """The face color depends on its perimeter."""
+36    WHITE = 5
+37    """All faces are just white."""
+38    FATES = 6
+39    """Faces are colored depending on their fate marker (if any)."""
+
+ + +

What it is possible to show on a face.

+
+ + +
+
+ MULTICOLOR = +<FacePlot.MULTICOLOR: 1> + + +
+ + +

Each face get a random color.

+
+ + +
+
+
+ FACE_PARAMETER = +<FacePlot.FACE_PARAMETER: 2> + + +
+ + +

The face color depends on its parameter.

+
+ + +
+
+
+ AREA = +<FacePlot.AREA: 3> + + +
+ + +

The face color depends on its area.

+
+ + +
+
+
+ PERIMETER = +<FacePlot.PERIMETER: 4> + + +
+ + +

The face color depends on its perimeter.

+
+ + +
+
+
+ WHITE = +<FacePlot.WHITE: 5> + + +
+ + +

All faces are just white.

+
+ + +
+
+
+ FATES = +<FacePlot.FATES: 6> + + +
+ + +

Faces are colored depending on their fate marker (if any).

+
+ + +
+
+
+ +
+ + class + EdgePlot(enum.Enum): + + + +
+ +
42class EdgePlot(Enum):
+43    """What it is possible to show on an edge."""
+44
+45    BLACK = 1
+46    """All edges are black."""
+47    EDGE_PARAMETER = 2
+48    """The edge color depends on its parameter."""
+49    LENGTH = 3
+50    """The edge color depends on its length."""
+51    INVISIBLE = 4
+52    """Do not show edges."""
+
+ + +

What it is possible to show on an edge.

+
+ + +
+
+ BLACK = +<EdgePlot.BLACK: 1> + + +
+ + +

All edges are black.

+
+ + +
+
+
+ EDGE_PARAMETER = +<EdgePlot.EDGE_PARAMETER: 2> + + +
+ + +

The edge color depends on its parameter.

+
+ + +
+
+
+ LENGTH = +<EdgePlot.LENGTH: 3> + + +
+ + +

The edge color depends on its length.

+
+ + +
+
+
+ INVISIBLE = +<EdgePlot.INVISIBLE: 4> + + +
+ + +

Do not show edges.

+
+ + +
+
+
+ +
+ + class + VertexPlot(enum.Enum): + + + +
+ +
55class VertexPlot(Enum):
+56    """What it is possible to show on a vertex."""
+57
+58    BLACK = 1
+59    """Vertices are black."""
+60    VERTEX_PARAMETER = 2
+61    """The vertex color depends on its parameter."""
+62    INVISIBLE = 3
+63    """Do not show vertices."""
+
+ + +

What it is possible to show on a vertex.

+
+ + +
+
+ BLACK = +<VertexPlot.BLACK: 1> + + +
+ + +

Vertices are black.

+
+ + +
+
+
+ VERTEX_PARAMETER = +<VertexPlot.VERTEX_PARAMETER: 2> + + +
+ + +

The vertex color depends on its parameter.

+
+ + +
+
+
+ INVISIBLE = +<VertexPlot.INVISIBLE: 3> + + +
+ + +

Do not show vertices.

+
+ + +
+
+
+ +
+
@partial(jit, static_argnums=(3, 4))
+ + def + cost_v2v( vertTable: jax.Array, heTable: jax.Array, faceTable: jax.Array, width: float, height: float, vertTable_target: jax.Array, _heTable_target: jax.Array, _faceTable_target: jax.Array, selected_verts: jax.Array | None = None, selected_hes: jax.Array | None = None, selected_faces: jax.Array | None = None, _image_target: jax.Array | None = None) -> jax.Array: + + + +
+ +
217@partial(jit, static_argnums=(3, 4))
+218def cost_v2v(
+219    vertTable: Array,
+220    heTable: Array,
+221    faceTable: Array,
+222    width: float,
+223    height: float,
+224    vertTable_target: Array,
+225    _heTable_target: Array,
+226    _faceTable_target: Array,
+227    selected_verts: Array | None = None,
+228    selected_hes: Array | None = None,
+229    selected_faces: Array | None = None,
+230    _image_target: Array | None = None,
+231) -> Array:
+232    """Cost vertex to vertex. Compare the positions of given vertices to target vertices (`PbcMesh`)."""
+233    if selected_verts is None:
+234        selected_verts = jnp.arange(vertTable.shape[0])
+235    if selected_hes is None:
+236        selected_hes = jnp.arange(heTable.shape[0])
+237    if selected_faces is None:
+238        selected_faces = jnp.arange(faceTable.shape[0])
+239
+240    def squared_distance(v: Array, vertTable: Array, vertTable_target: Array, width: float, height: float) -> Array:
+241        return (
+242            jnp.min(
+243                jnp.array(
+244                    [
+245                        (
+246                            (vertTable[v][0] - vertTable_target[v][0]) ** 2
+247                            + (vertTable[v][1] - vertTable_target[v][1]) ** 2
+248                        )
+249                        ** 0.5,
+250                        (
+251                            (vertTable[v][0] - (vertTable_target[v][0] + width)) ** 2
+252                            + (vertTable[v][1] - vertTable_target[v][1]) ** 2
+253                        )
+254                        ** 0.5,
+255                        (
+256                            (vertTable[v][0] - (vertTable_target[v][0] - width)) ** 2
+257                            + (vertTable[v][1] - vertTable_target[v][1]) ** 2
+258                        )
+259                        ** 0.5,
+260                        (
+261                            (vertTable[v][0] - vertTable_target[v][0]) ** 2
+262                            + (vertTable[v][1] - (vertTable_target[v][1] + height)) ** 2
+263                        )
+264                        ** 0.5,
+265                        (
+266                            (vertTable[v][0] - vertTable_target[v][0]) ** 2
+267                            + (vertTable[v][1] - (vertTable_target[v][1] - height)) ** 2
+268                        )
+269                        ** 0.5,
+270                        (
+271                            (vertTable[v][0] - (vertTable_target[v][0] + width)) ** 2
+272                            + (vertTable[v][1] - (vertTable_target[v][1] + height)) ** 2
+273                        )
+274                        ** 0.5,
+275                        (
+276                            (vertTable[v][0] - (vertTable_target[v][0] + width)) ** 2
+277                            + (vertTable[v][1] - (vertTable_target[v][1] - height)) ** 2
+278                        )
+279                        ** 0.5,
+280                        (
+281                            (vertTable[v][0] - (vertTable_target[v][0] - width)) ** 2
+282                            + (vertTable[v][1] - (vertTable_target[v][1] + height)) ** 2
+283                        )
+284                        ** 0.5,
+285                        (
+286                            (vertTable[v][0] - (vertTable_target[v][0] - width)) ** 2
+287                            + (vertTable[v][1] - (vertTable_target[v][1] - height)) ** 2
+288                        )
+289                        ** 0.5,
+290                    ]
+291                )
+292            )
+293        ) ** 2
+294
+295    def mapped_fn(v: Array) -> Array:
+296        return squared_distance(v, vertTable, vertTable_target, width, height)
+297
+298    distances = vmap(mapped_fn)(selected_verts)
+299
+300    return (1.0 / (2 * len(distances))) * jnp.sum(distances)
+
+ + +

Cost vertex to vertex. Compare the positions of given vertices to target vertices (PbcMesh).

+
+ + +
+
+ +
+
@jit
+ + def + cost_mesh2image( vertTable: jax.Array, heTable: jax.Array, _faceTable: jax.Array, width: float, height: float, _vertTable_target: jax.Array, _heTable_target: jax.Array, _faceTable_target: jax.Array, _selected_verts: jax.Array, selected_hes: jax.Array, _selected_faces: jax.Array, image_target: jax.Array) -> jax.Array: + + + +
+ +
303@jit
+304def cost_mesh2image(
+305    vertTable: Array,
+306    heTable: Array,
+307    _faceTable: Array,
+308    width: float,
+309    height: float,
+310    _vertTable_target: Array,
+311    _heTable_target: Array,
+312    _faceTable_target: Array,
+313    _selected_verts: Array,
+314    selected_hes: Array,
+315    _selected_faces: Array,
+316    image_target: Array,
+317) -> Array:
+318    """Cost mesh to image. Compare the given vertices positions to a target image (`PbcMesh`)."""
+319    wh = jnp.asarray([width, height])
+320    starting = (vertTable[heTable[selected_hes, 3], :2]) * 2 / wh  # (M, 2)
+321    # ending = (vertTable[heTable[selected_hes, 4], :2]) * 2 / L_box  # (M, 2)
+322    ending = (
+323        (
+324            vertTable[heTable[selected_hes, 4], :2]
+325            + jnp.stack(
+326                [
+327                    heTable[selected_hes, 6],
+328                    heTable[selected_hes, 7],
+329                ],
+330                axis=-1,
+331            )
+332            * wh
+333        )
+334        * 2
+335        / wh
+336    )
+337
+338    he_edges = stack((starting, ending), axis=1)  # (N, 2, 2)
+339    x = he_edges.transpose(1, 2, 0) - 1  # (2, 2, N)
+340
+341    # Blur
+342    image = _gaussian_blur_line_segments(x).real
+343
+344    # Normalization
+345    image = image / image.sum()
+346    image_target = image_target / image_target.sum()
+347
+348    l2_norm = jnp.linalg.norm(jnp.sqrt(jnp.sum(((image - image_target) ** 2) * (1 - image_target), axis=-1)).flatten())
+349
+350    return l2_norm
+
+ + +

Cost mesh to image. Compare the given vertices positions to a target image (PbcMesh).

+
+ + +
+
+ +
+
@jit
+ + def + cost_areas( vertTable: jax.Array, heTable: jax.Array, faceTable: jax.Array, width: float, height: float, _selected_verts: jax.Array, _selected_hes: jax.Array, selected_faces: jax.Array, vertTable_target: jax.Array, heTable_target: jax.Array, faceTable_target: jax.Array, _image_target: jax.Array) -> jax.Array: + + + +
+ +
353@jit
+354def cost_areas(
+355    vertTable: Array,
+356    heTable: Array,
+357    faceTable: Array,
+358    width: float,
+359    height: float,
+360    _selected_verts: Array,
+361    _selected_hes: Array,
+362    selected_faces: Array,
+363    vertTable_target: Array,
+364    heTable_target: Array,
+365    faceTable_target: Array,
+366    _image_target: Array,
+367) -> Array:
+368    """Cost areas : compare the areas of the given mesh versus the areas of the target mesh (`PbcMesh`)."""
+369
+370    def mapped_fn(f: Array) -> Array:
+371        return (
+372            get_area(f, vertTable, heTable, faceTable, width, height)
+373            - get_area(f, vertTable_target, heTable_target, faceTable_target, width, height)
+374        ) ** 2  # + \
+375
+376    difference = vmap(mapped_fn)(selected_faces)
+377    return (1.0 / len(difference)) * jnp.sum(difference)
+
+ + +

Cost areas : compare the areas of the given mesh versus the areas of the target mesh (PbcMesh).

+
+ + +
+
+ +
+
@partial(jit, static_argnums=(3, 4))
+ + def + cost_IAS( vertTable: jax.Array, heTable: jax.Array, faceTable: jax.Array, _width: float, _height: float, vertTable_target: jax.Array, heTable_target: jax.Array, faceTable_target: jax.Array, _selected_verts: jax.Array | None = None, _selected_hes: jax.Array | None = None, _selected_faces: jax.Array | None = None, _image_target: jax.Array | None = None) -> jax.Array: + + + +
+ +
466@partial(jit, static_argnums=(3, 4))
+467def cost_IAS(  # noqa: C901, N802
+468    vertTable: Array,
+469    heTable: Array,
+470    faceTable: Array,
+471    _width: float,
+472    _height: float,
+473    vertTable_target: Array,
+474    heTable_target: Array,
+475    faceTable_target: Array,
+476    _selected_verts: Array | None = None,
+477    _selected_hes: Array | None = None,
+478    _selected_faces: Array | None = None,
+479    _image_target: Array | None = None,
+480) -> Array:
+481    r"""Differentiable Index Aware Structural Loss. Force to respect the topology.
+482
+483    C_{IAS}(i,j)   = \sqrt{\sum_{k=1}^{N} (S_1(i,k) - S_2(j,k))^2}
+484    """
+485    L_box = jnp.sqrt(len(faceTable))
+486
+487    def l2(x: Array, y: Array) -> Array:
+488        diff = x[:, None, :] - y[None, :, :]
+489        return jnp.sqrt(jnp.sum(diff**2, axis=-1) + 1e-12)
+490
+491    def mse(x: Array, y: Array) -> Array:
+492        diff = x[:, None, :] - y[None, :, :]
+493        return jnp.sum(diff**2, axis=-1) + 1e-12
+494
+495    def sinkhorn(a: Array, b: Array, C: Array, eps: float = 5e-2, n_iters: int = 50) -> Array:
+496        K = jnp.exp(-C / eps)
+497        u = jnp.ones_like(a)
+498        v = jnp.ones_like(b)
+499
+500        def body(_: int, state: tuple[Array, Array]) -> tuple[Array, Array]:
+501            u, v = state
+502            u = a / (K @ v + 1e-12)
+503            v = b / (K.T @ u + 1e-12)
+504            return (u, v)
+505
+506        u, v = fori_loop(0, n_iters, body, (u, v))
+507        return jnp.outer(u, v) * K
+508
+509    # dual graph from half-edges
+510    def build_dual_adj(heTable: Array, n_faces: int) -> Array:
+511        face = heTable[:, 5]
+512        twin = heTable[:, 2]
+513
+514        face_twin = face[twin]
+515
+516        # mask: 1.0 for valid edges, 0.0 for boundary/self
+517        mask = (face != face_twin).astype(jnp.float32)
+518
+519        A = jnp.zeros((n_faces, n_faces))
+520
+521        # scatter with mask weighting (no boolean indexing)
+522        A = A.at[face, face_twin].add(mask)
+523        A = A.at[face_twin, face].add(mask)
+524
+525        # binarize (avoid double counts)
+526        A = jnp.clip(A, 0.0, 1.0)
+527
+528        return A
+529
+530    # structure metric (1-hop + 2-hop)
+531    def structure_matrix(A: Array) -> Array:
+532        # A2 = A @ A
+533        # normalize to avoid scaling issues
+534        # return 2.0 - A - 0.5 * A2
+535        return 2.0 - A
+536
+537    def get_face_vertices(
+538        he_start: Array, heTable: Array, vertTable: Array, max_edges: int = 20
+539    ) -> tuple[Array, Array]:
+540        """Collect vertices of one face using half-edge traversal.
+541
+542        Returns fixed-size array (max_edges, 2) + mask
+543        """
+544        verts = jnp.zeros((max_edges, 2))
+545        mask = jnp.zeros((max_edges,))
+546        offset = jnp.array([0, 0])
+547
+548        def body_fun(i: int, state: tuple[Array, Array, Array, Array]) -> tuple[Array, Array, Array, Array]:
+549            he, verts, mask, offset = state
+550
+551            source = heTable[he, 3].astype(jnp.int32)
+552            off = heTable[he, 6:8]
+553
+554            pos = vertTable[source] + offset * L_box  # jnp.array([width, height])
+555
+556            verts = verts.at[i].set(pos)
+557            mask = mask.at[i].set(1.0)
+558
+559            offset += off
+560            he_next = heTable[he, 1].astype(jnp.int32)
+561
+562            return (he_next, verts, mask, offset)
+563
+564        _he_final, verts, mask, offset = fori_loop(0, max_edges, body_fun, (he_start, verts, mask, offset))
+565
+566        return verts, mask
+567
+568    def polygon_centroid(verts: Array, mask: Array) -> Array:
+569        """Compute centroid from masked polygon vertices."""
+570        # shift for edges
+571        v = verts
+572        v_next = jnp.roll(v, -1, axis=0)
+573
+574        cross = v[:, 0] * v_next[:, 1] - v_next[:, 0] * v[:, 1]
+575        cross = cross * mask
+576
+577        area = jnp.sum(cross) / 2.0 + 1e-12
+578
+579        cx = jnp.sum((v[:, 0] + v_next[:, 0]) * cross) / (6 * area)
+580        cy = jnp.sum((v[:, 1] + v_next[:, 1]) * cross) / (6 * area)
+581
+582        return jnp.array([cx, cy])
+583
+584    def compute_face_centroids(faceTable: Array, heTable: Array, vertTable: Array) -> Array:
+585        """Compute centroids for all faces."""
+586        he_start = faceTable[:].astype(jnp.int32)
+587
+588        def single_face(he: Array) -> Array:
+589            verts, mask = get_face_vertices(he, heTable, vertTable)
+590            return polygon_centroid(verts, mask)
+591
+592        return vmap(single_face)(he_start)
+593
+594    alpha = 0.0
+595    """ATTENTION !!!!!!!!!!!!"""
+596
+597    # face centroids
+598    X = compute_face_centroids(faceTable, heTable, vertTable)
+599    Y = compute_face_centroids(faceTable_target, heTable_target, vertTable_target)
+600
+601    N = X.shape[0]
+602    M = Y.shape[0]
+603
+604    # uniform weights
+605    a = jnp.ones(N) / N
+606    b = jnp.ones(M) / M
+607
+608    # geometry cost
+609    C_geom = l2(X, Y)
+610
+611    # structure cost
+612    A1 = build_dual_adj(heTable, N)
+613    A2 = build_dual_adj(heTable_target, M)
+614
+615    S1 = structure_matrix(A1)
+616    S2 = structure_matrix(A2)
+617
+618    # project structure into pairwise node cost
+619    # (cheap approximation of tem)
+620    # C_l2 = l2(S1, S2)
+621    C_mse = mse(S1, S2)
+622
+623    # combined cost
+624    C_total = alpha * C_geom + (1.0 - alpha) * C_mse
+625
+626    # transport
+627    gamma = sinkhorn(a, b, C_total)
+628
+629    # final loss
+630    loss = jnp.sum(gamma * C_total)
+631
+632    return loss
+
+ + +

Differentiable Index Aware Structural Loss. Force to respect the topology.

+ +

C_{IAS}(i,j) = \sqrt{\sum_{k=1}^{N} (S_1(i,k) - S_2(j,k))^2}

+
+ + +
+
+ +
+ + def + cost_d_IAS( _vertTable: jax.Array, heTable: jax.Array, faceTable: jax.Array, _width: float, _height: float, _vertTable_target: jax.Array, heTable_target: jax.Array, faceTable_target: jax.Array, _selected_verts: jax.Array | None = None, _selected_hes: jax.Array | None = None, _selected_faces: jax.Array | None = None, _image_target: jax.Array | None = None) -> int: + + + +
+ +
635def cost_d_IAS(  # noqa: N802
+636    _vertTable: Array,
+637    heTable: Array,
+638    faceTable: Array,
+639    _width: float,
+640    _height: float,
+641    _vertTable_target: Array,
+642    heTable_target: Array,
+643    faceTable_target: Array,
+644    _selected_verts: Array | None = None,
+645    _selected_hes: Array | None = None,
+646    _selected_faces: Array | None = None,
+647    _image_target: Array | None = None,
+648) -> int:
+649    r"""Discrete Index Aware Structural loss. Counts mismatched edges.
+650
+651    C_{d-IAS}(i,j) = \sum_{k=1}^{N} |A_1(i,k) - A_2(j,k)|
+652    """
+653
+654    def build_dual_adj_np(heTable: Array, n_faces: int) -> NDArray:
+655        face = heTable[:, 5]
+656        twin = heTable[:, 2]
+657
+658        face_twin = face[twin]
+659        mask = (face != face_twin).astype(float)
+660
+661        A = np.zeros((n_faces, n_faces))
+662
+663        A[face, face_twin] += mask
+664        A[face_twin, face] += mask
+665
+666        A = np.clip(A, 0.0, 1.0)
+667        return A
+668
+669    n1 = len(faceTable)
+670    n2 = len(faceTable_target)
+671
+672    if n1 != n2:
+673        msg = "This version requires same number of faces"
+674        raise ValueError(msg)
+675
+676    n = n1
+677
+678    A1 = build_dual_adj_np(heTable, n)
+679    A2 = build_dual_adj_np(heTable_target, n)
+680
+681    # --- Step 1: node matching via Hungarian ---
+682    # cost between nodes = L1 difference of adjacency rows
+683    C = np.sum(np.abs(A1[:, None, :] - A2[None, :, :]), axis=-1)
+684
+685    row_ind, col_ind = linear_sum_assignment(C)
+686
+687    # permutation: face i in A1 ↔ face col_ind[i] in A2
+688    P = np.zeros((n, n))
+689    P[row_ind, col_ind] = 1.0
+690
+691    # --- Step 2: permute A2 ---
+692    A2_perm = P @ A2 @ P.T
+693
+694    # --- Step 3: count edge mismatches ---
+695    diff = np.abs(A1 - A2_perm)
+696
+697    # count each edge once
+698    diff_upper = np.triu(diff, k=1)
+699
+700    loss = int(np.sum(diff_upper))
+701
+702    return loss
+
+ + +

Discrete Index Aware Structural loss. Counts mismatched edges.

+ +

C_{d-IAS}(i,j) = \sum_{k=1}^{N} |A_1(i,k) - A_2(j,k)|

+
+ + +
+
+ +
+
@partial(jit, static_argnums=(3, 4))
+ + def + cost_tem_halfedge( vertTable: jax.Array, heTable: jax.Array, faceTable: jax.Array, width: float, height: float, vertTable_target: jax.Array, heTable_target: jax.Array, faceTable_target: jax.Array, _selected_verts: jax.Array | None = None, _selected_hes: jax.Array | None = None, _selected_faces: jax.Array | None = None, _image_target: jax.Array | None = None) -> jax.Array: + + + +
+ +
705@partial(jit, static_argnums=(3, 4))
+706def cost_tem_halfedge(
+707    vertTable: Array,
+708    heTable: Array,
+709    faceTable: Array,
+710    width: float,
+711    height: float,
+712    vertTable_target: Array,
+713    heTable_target: Array,
+714    faceTable_target: Array,
+715    _selected_verts: Array | None = None,
+716    _selected_hes: Array | None = None,
+717    _selected_faces: Array | None = None,
+718    _image_target: Array | None = None,
+719) -> Array:
+720    """TEM-inspired loss using half-edge dual graph (cells)."""
+721
+722    # ---------- helpers ----------
+723    def pairwise_periodic_distances(x: Array, y: Array) -> Array:
+724        dx = x[:, None, 0] - y[None, :, 0]
+725        dy = x[:, None, 1] - y[None, :, 1]
+726
+727        dx = jnp.minimum(jnp.abs(dx), width - jnp.abs(dx))
+728        dy = jnp.minimum(jnp.abs(dy), height - jnp.abs(dy))
+729
+730        return jnp.sqrt(dx**2 + dy**2 + 1e-12)
+731
+732    def sinkhorn(a: Array, b: Array, C: Array, eps: float = 5e-2, n_iters: int = 50) -> Array:
+733        K = jnp.exp(-C / eps)
+734        u = jnp.ones_like(a)
+735        v = jnp.ones_like(b)
+736
+737        def body(_: int, state: tuple[Array, Array]) -> tuple[Array, Array]:
+738            u, v = state
+739            u = a / (K @ v + 1e-12)
+740            v = b / (K.T @ u + 1e-12)
+741            return (u, v)
+742
+743        u, v = fori_loop(0, n_iters, body, (u, v))
+744        return jnp.outer(u, v) * K
+745
+746    # ---------- dual graph ----------
+747    def build_dual_adj(heTable: Array, n_faces: int) -> Array:
+748        face = heTable[:, 5].astype(jnp.int32)
+749        twin = heTable[:, 2].astype(jnp.int32)
+750
+751        face_twin = face[twin]
+752
+753        valid = face != face_twin
+754        keep = valid & (face < face_twin)
+755
+756        weight = keep.astype(jnp.float32)
+757
+758        A = jnp.zeros((n_faces, n_faces))
+759
+760        A = A.at[face, face_twin].add(weight)
+761        A = A.at[face_twin, face].add(weight)
+762
+763        return A
+764
+765    def structure_matrix(A: Array) -> Array:
+766        # A2 = A @ A
+767        # return 2.0 - A - 0.5 * A2
+768        return 2.0 - A
+769
+770    # ---------- centroids ----------
+771    # faceTable stores a half-edge index → get its source vertex
+772    he_idx = faceTable[:, 0].astype(jnp.int32)
+773
+774    X = vertTable[heTable[he_idx, 3].astype(jnp.int32), :2]
+775    Y = vertTable_target[heTable_target[faceTable_target[:, 0].astype(jnp.int32), 3].astype(jnp.int32), :2]
+776
+777    N = X.shape[0]
+778    M = Y.shape[0]
+779
+780    a = jnp.ones(N) / N
+781    b = jnp.ones(M) / M
+782
+783    # ---------- costs ----------
+784    C_geom = pairwise_periodic_distances(X, Y)
+785
+786    A1 = build_dual_adj(heTable, N)
+787    A2 = build_dual_adj(heTable_target, M)
+788
+789    S1 = structure_matrix(A1)
+790    S2 = structure_matrix(A2)
+791
+792    # structure comparison (non-periodic, abstract space)
+793    diff = S1[:, None, :] - S2[None, :, :]
+794    C_struct = jnp.sqrt(jnp.sum(diff**2, axis=-1) + 1e-12)
+795
+796    alpha = 0.5
+797    C_total = alpha * C_geom + (1.0 - alpha) * C_struct
+798
+799    gamma = sinkhorn(a, b, C_total)
+800
+801    return jnp.sum(gamma * C_total)
+
+ + +

TEM-inspired loss using half-edge dual graph (cells).

+
+ + +
+
+ +
+
@partial(jit, static_argnums=(3, 4))
+ + def + cost_v2v_ias( vertTable: jax.Array, heTable: jax.Array, faceTable: jax.Array, width: float, height: float, vertTable_target: jax.Array, heTable_target: jax.Array, faceTable_target: jax.Array, _selected_verts: jax.Array | None = None, _selected_hes: jax.Array | None = None, _selected_faces: jax.Array | None = None, _image_target: jax.Array | None = None) -> jax.Array: + + + +
+ +
849@partial(jit, static_argnums=(3, 4))
+850def cost_v2v_ias(
+851    vertTable: Array,
+852    heTable: Array,
+853    faceTable: Array,
+854    width: float,
+855    height: float,
+856    vertTable_target: Array,
+857    heTable_target: Array,
+858    faceTable_target: Array,
+859    _selected_verts: Array | None = None,
+860    _selected_hes: Array | None = None,
+861    _selected_faces: Array | None = None,
+862    _image_target: Array | None = None,
+863) -> Array:
+864    """Mix of cost_v2v and cost_IAS (with weight 0.6 and 0.4)."""
+865    return 0.6 * cost_v2v(
+866        vertTable,
+867        heTable,
+868        faceTable,
+869        width,
+870        height,
+871        vertTable_target,
+872        heTable_target,
+873        faceTable_target,
+874        selected_verts=None,
+875        selected_hes=None,
+876        selected_faces=None,
+877        _image_target=None,
+878    ) + 0.4 * cost_IAS(
+879        vertTable,
+880        heTable,
+881        faceTable,
+882        width,
+883        height,
+884        vertTable_target,
+885        heTable_target,
+886        faceTable_target,
+887        selected_verts=None,
+888        selected_hes=None,
+889        selected_faces=None,
+890        image_target=None,
+891    )
+
+ + +

Mix of cost_v2v and cost_IAS (with weight 0.6 and 0.4).

+
+ + +
+
+ +
+
@partial(jit, static_argnums=(3, 4))
+ + def + cost_v2v_tem( vertTable: jax.Array, heTable: jax.Array, faceTable: jax.Array, width: float, height: float, vertTable_target: jax.Array, heTable_target: jax.Array, faceTable_target: jax.Array, _selected_verts: jax.Array | None = None, _selected_hes: jax.Array | None = None, _selected_faces: jax.Array | None = None, _image_target: jax.Array | None = None) -> jax.Array: + + + +
+ +
804@partial(jit, static_argnums=(3, 4))
+805def cost_v2v_tem(
+806    vertTable: Array,
+807    heTable: Array,
+808    faceTable: Array,
+809    width: float,
+810    height: float,
+811    vertTable_target: Array,
+812    heTable_target: Array,
+813    faceTable_target: Array,
+814    _selected_verts: Array | None = None,
+815    _selected_hes: Array | None = None,
+816    _selected_faces: Array | None = None,
+817    _image_target: Array | None = None,
+818) -> Array:
+819    """Mix of cost_v2v and cost_tem_halfedge (with respective weights 0.99 and 0.01)."""
+820    return 0.99 * cost_v2v(
+821        vertTable,
+822        heTable,
+823        faceTable,
+824        width,
+825        height,
+826        vertTable_target,
+827        heTable_target,
+828        faceTable_target,
+829        selected_verts=None,
+830        selected_hes=None,
+831        selected_faces=None,
+832        image_target=None,
+833    ) + 0.01 * cost_tem_halfedge(
+834        vertTable,
+835        heTable,
+836        faceTable,
+837        width,
+838        height,
+839        vertTable_target,
+840        heTable_target,
+841        faceTable_target,
+842        selected_verts=None,
+843        selected_hes=None,
+844        selected_faces=None,
+845        image_target=None,
+846    )
+
+ + +

Mix of cost_v2v and cost_tem_halfedge (with respective weights 0.99 and 0.01).

+
+ + +
+
+ +
+
@jit
+ + def + cost_ratio( vertTable: jax.Array, _angTable: jax.Array | None = None, _heTable: jax.Array | None = None, _faceTable: jax.Array | None = None, _vertTable_target: jax.Array | None = None, _angTable_target: jax.Array | None = None, _heTable_target: jax.Array | None = None, _faceTable_target: jax.Array | None = None, _selected_verts: jax.Array | None = None, _selected_hes: jax.Array | None = None, _selected_faces: jax.Array | None = None, _image_target: jax.Array | None = None) -> jax.Array: + + + +
+ +
380@jit
+381def cost_ratio(
+382    vertTable: Array,
+383    _angTable: Array | None = None,
+384    _heTable: Array | None = None,
+385    _faceTable: Array | None = None,
+386    _vertTable_target: Array | None = None,
+387    _angTable_target: Array | None = None,
+388    _heTable_target: Array | None = None,
+389    _faceTable_target: Array | None = None,
+390    _selected_verts: Array | None = None,
+391    _selected_hes: Array | None = None,
+392    _selected_faces: Array | None = None,
+393    _image_target: Array | None = None,
+394) -> Array:
+395    """Cost that nudges a `BoundedMesh` to elongate along one axis while narrowing along the orthogonal axis."""
+396    # Compute pairwise squared distances by broadcasting
+397    # diff shape: (n, n, d)
+398    diff = vertTable[:, None, :] - vertTable[None, :, :]
+399    sq_dists = jnp.sum(diff**2, axis=-1)  # (n, n)
+400    distances = jnp.sqrt(sq_dists + 1e-12)  # small eps for numerical stability
+401
+402    # For each row, get index of farthest point
+403    max_distances = jnp.max(distances, axis=1)
+404    max_idx_1 = jnp.argmax(max_distances)  # index of row with largest max distance
+405    long_axis = max_distances[max_idx_1]  # longest distance (already excludes diagonal)
+406    max_idx_2 = jnp.argmax(distances[max_idx_1])  # the farthest point from max_idx_1
+407
+408    max_point_1 = vertTable[max_idx_1]
+409    max_point_2 = vertTable[max_idx_2]
+410    long_axis_vector = max_point_2 - max_point_1
+411    long_axis_vector = long_axis_vector / (jnp.linalg.norm(long_axis_vector) + 1e-12)
+412
+413    # Short axis (perpendicular)
+414    short_axis_vector = jnp.array([long_axis_vector[1], -long_axis_vector[0]])
+415
+416    # Project points onto short axis: signed distances
+417    # If vertTable is (n,2) this works. If higher dim, modify accordingly.
+418    signed_distances_to_long_axis = jnp.dot(vertTable - max_point_1, short_axis_vector)
+419
+420    short_axis = jnp.max(signed_distances_to_long_axis) - jnp.min(signed_distances_to_long_axis)
+421
+422    # Numerically stabilize division
+423    ratio = long_axis / (short_axis + 1e-12)
+424    return (ratio - TARGET_RATIO) ** 2
+
+ + +

Cost that nudges a BoundedMesh to elongate along one axis while narrowing along the orthogonal axis.

+
+ + +
+
+ +
+
@jit
+ + def + cost_checkerboard( vertTable: jax.Array, _angTable: jax.Array, heTable: jax.Array, faceTable: jax.Array, _vertTable_target: jax.Array | None = None, _angTable_target: jax.Array | None = None, _heTable_target: jax.Array | None = None, _faceTable_target: jax.Array | None = None, _selected_verts: jax.Array | None = None, _selected_hes: jax.Array | None = None, _selected_faces: jax.Array | None = None, _image_target: jax.Array | None = None) -> jax.Array: + + + +
+ +
427@jit
+428def cost_checkerboard(
+429    vertTable: Array,
+430    _angTable: Array,
+431    heTable: Array,
+432    faceTable: Array,
+433    _vertTable_target: Array | None = None,
+434    _angTable_target: Array | None = None,
+435    _heTable_target: Array | None = None,
+436    _faceTable_target: Array | None = None,
+437    _selected_verts: Array | None = None,
+438    _selected_hes: Array | None = None,
+439    _selected_faces: Array | None = None,
+440    _image_target: Array | None = None,
+441) -> Array:
+442    """Cost that nudges a `BoundedMesh` with different fated cells to avoid having neighboring cells with same fate.
+443
+444    This leads to a checkerboard pattern when there is 2 fates.
+445    """
+446
+447    def body_fun(i: int, current_len: Array) -> Array:
+448        idx = 2 * i
+449        he_twin = heTable[idx, 2]
+450        he_face = heTable[idx, 7]
+451        he_fate = faceTable[he_face, 1]
+452        he_face_twin = heTable[he_twin, 7]
+453        he_fate_twin = faceTable[he_face_twin, 1]
+454        v_source = heTable[idx, 3] - 2
+455        v_target = heTable[idx, 4] - 2
+456        pos_source = vertTable[v_source]
+457        pos_target = vertTable[v_target]
+458        return current_len + jnp.where(
+459            jnp.logical_and(he_fate == he_fate_twin, heTable[idx, 3] > 0), jnp.sum((pos_target - pos_source) ** 2), 0.0
+460        )
+461
+462    n_edges = heTable.shape[0] // 2
+463    return fori_loop(0, n_edges, body_fun, 0.0)
+
+ + +

Cost that nudges a BoundedMesh with different fated cells to avoid having neighboring cells with same fate.

+ +

This leads to a checkerboard pattern when there is 2 fates.

+
+ + +
+
+ +
+ + def + energy_shape_factor_hetero( vertTable: jax.Array, heTable: jax.Array, faceTable: jax.Array, width: float, height: float, selected_faces: jax.Array, face_params: jax.Array) -> jax.Array: + + + +
+ +
75def energy_shape_factor_hetero(
+76    vertTable: Array,
+77    heTable: Array,
+78    faceTable: Array,
+79    width: float,
+80    height: float,
+81    selected_faces: Array,
+82    face_params: Array,
+83) -> Array:
+84    """E1 energy where the shape factor depends on the cell."""
+85
+86    def mapped_fn(face: Array, param: Array) -> Array:
+87        return _cell_energy(face, param, vertTable, heTable, faceTable, width, height)
+88
+89    cell_energies = vmap(mapped_fn)(selected_faces, face_params[selected_faces])
+90    # cell_energies = vmap(mapped_fn)(jnp.arange(len(faceTable)), face_params)
+91    return jnp.sum(cell_energies)
+
+ + +

E1 energy where the shape factor depends on the cell.

+
+ + +
+
+ +
+ + def + energy_shape_factor_homo( vertTable: jax.Array, heTable: jax.Array, faceTable: jax.Array, width: float, height: float, face_params: jax.Array) -> jax.Array: + + + +
+ +
57def energy_shape_factor_homo(
+58    vertTable: Array,
+59    heTable: Array,
+60    faceTable: Array,
+61    width: float,
+62    height: float,
+63    face_params: Array,
+64) -> Array:
+65    """E1 energy where the shape factor is uniform (give only one face_params, it will be broadcasted)."""
+66
+67    def mapped_fn(face: Array, param: Array) -> Array:
+68        return _cell_energy(face, param, vertTable, heTable, faceTable, width, height)
+69
+70    face_params_broadcasted = jnp.broadcast_to(face_params, (len(faceTable), *face_params.shape[1:]))
+71    cell_energies = vmap(mapped_fn)(jnp.arange(len(faceTable)), face_params_broadcasted)
+72    return jnp.sum(cell_energies)
+
+ + +

E1 energy where the shape factor is uniform (give only one face_params, it will be broadcasted).

+
+ + +
+
+ +
+ + def + energy_line_tensions( vertTable: jax.Array, heTable: jax.Array, faceTable: jax.Array, width: float, height: float, he_params: jax.Array, face_params: jax.Array) -> jax.Array: + + + +
+ +
110def energy_line_tensions(
+111    vertTable: Array,
+112    heTable: Array,
+113    faceTable: Array,
+114    width: float,
+115    height: float,
+116    he_params: Array,
+117    face_params: Array,
+118) -> Array:
+119    """E2 energy for PBC meshes, elastic penalty on cell areas and line tension term weighted by edge lengths."""
+120    K_areas = 20
+121
+122    def mapped_areas_part(face: Array, face_param: Array) -> Array:
+123        return _area_part(face, face_param, vertTable, heTable, faceTable, width, height)
+124
+125    def mapped_hedges_part(he: Array, he_param: Array) -> Array:
+126        return _hedge_part(he, he_param, vertTable, heTable, faceTable, width, height)
+127
+128    areas_part = vmap(mapped_areas_part)(jnp.arange(len(faceTable)), face_params)
+129    hedges_part = vmap(mapped_hedges_part)(jnp.arange(len(heTable)), he_params)
+130    # areas_part = vmap(mapped_areas_part)(selected_faces, face_params[selected_faces])
+131    # hedges_part = vmap(mapped_hedges_part)(selected_hes, he_params[selected_hes])
+132    return jnp.sum(hedges_part) + (0.5 * K_areas) * jnp.sum(areas_part)
+
+ + +

E2 energy for PBC meshes, elastic penalty on cell areas and line tension term weighted by edge lengths.

+
+ + +
+
+ +
+
@jit
+ + def + energy_line_tensions_bounded( vertTable: jax.Array, angTable: jax.Array, heTable: jax.Array, faceTable: jax.Array, _selected_verts: jax.Array | None, _selected_hes: jax.Array | None, _selected_faces: jax.Array | None, _vert_params: jax.Array, he_params: jax.Array, _face_params: jax.Array) -> jax.Array: + + + +
+ +
159@jit
+160def energy_line_tensions_bounded(
+161    vertTable: Array,
+162    angTable: Array,
+163    heTable: Array,
+164    faceTable: Array,
+165    _selected_verts: Array | None,
+166    _selected_hes: Array | None,
+167    _selected_faces: Array | None,
+168    _vert_params: Array,
+169    he_params: Array,
+170    _face_params: Array,
+171) -> Array:
+172    """E2 energy for bounded meshes, elastic penalty on cell areas and line tension term weighted by edge lengths."""
+173    num_faces = faceTable.shape[0]
+174    faces = jnp.arange(num_faces)
+175    num_edges = angTable.size
+176    num_half_edges = num_edges * 2
+177    unique_edges = jnp.arange(num_edges) * 2
+178    edges = jnp.arange(num_half_edges)
+179    vertTable = jnp.vstack([jnp.array([[0.0, 0.0], [1.0, 1.0]]), vertTable])
+180    angTable = jnp.repeat(angTable, 2)
+181    he_params = jax.nn.sigmoid(he_params) + 1
+182
+183    def mapped_fn_area(face: Array) -> Array:
+184        return _cell_area_energy(face, vertTable, angTable, heTable, faceTable)
+185
+186    cell_area_energies = jnp.sum(vmap(mapped_fn_area)(faces))
+187
+188    def mapped_fn_inner(edge: Array, tension: Array) -> Array:
+189        return _inner_edge_energy(edge, tension, vertTable, heTable)
+190
+191    inner_edge_energies = jnp.sum(vmap(mapped_fn_inner)(unique_edges, he_params))
+192
+193    def mapped_fn_surface(edge: Array, tension: Array) -> Array:
+194        return _surface_edge_energy(edge, tension, vertTable, angTable, heTable)
+195
+196    surface_edge_energies = jnp.sum(vmap(mapped_fn_surface)(edges, jnp.repeat(he_params, 2)))
+197    return 20 * cell_area_energies + inner_edge_energies + surface_edge_energies
+
+ + +

E2 energy for bounded meshes, elastic penalty on cell areas and line tension term weighted by edge lengths.

+
+ + +
+
+ + \ No newline at end of file diff --git a/docs/image.tif b/examples/image.tif similarity index 100% rename from docs/image.tif rename to examples/image.tif diff --git a/docs/inverse_modelling_example.ipynb b/examples/inverse_modelling_example.ipynb similarity index 99% rename from docs/inverse_modelling_example.ipynb rename to examples/inverse_modelling_example.ipynb index ac4fe4b..bf7ae10 100644 --- a/docs/inverse_modelling_example.ipynb +++ b/examples/inverse_modelling_example.ipynb @@ -2,21 +2,28 @@ "cells": [ { "cell_type": "markdown", - "id": "84054881", + "id": "13f2b77c", "metadata": {}, "source": [ "# Inverse Modeling example\n", "\n", "We'll show a simple example for inverse modeling using automatic differentiation on a vertex model with periodic boundary conditions.\n", "\n", - "First, let's check that VertAX is correctly installed by importing all we'll need for the next parts" + "First, let's check that VertAX is correctly installed by importing all we'll need for the next parts\n", + "coucou" ] }, { "cell_type": "code", "execution_count": 1, - "id": "345bbc89", - "metadata": {}, + "id": "1b7ac4eb", + "metadata": { + "collapsed": false, + "jupyter": { + "outputs_hidden": false + }, + "lines_to_next_cell": 2 + }, "outputs": [ { "name": "stderr", @@ -28,12 +35,12 @@ ], "source": [ "from vertax import PbcBilevelOptimizer, PbcMesh\n", - "# We'll see how to those important classes.\n" + "# We'll see how to those important classes." ] }, { "cell_type": "markdown", - "id": "8bed60a6", + "id": "2d955336", "metadata": {}, "source": [ "Now, let's create our initial mesh. It will have 20 cells, in a square domain." @@ -42,7 +49,7 @@ { "cell_type": "code", "execution_count": 2, - "id": "9c73e90f", + "id": "f67d2428", "metadata": {}, "outputs": [], "source": [ @@ -62,7 +69,7 @@ { "cell_type": "code", "execution_count": 3, - "id": "8f21528d", + "id": "6ca2dd8c", "metadata": {}, "outputs": [ { @@ -93,7 +100,7 @@ }, { "cell_type": "markdown", - "id": "7ddaf864", + "id": "9666a337", "metadata": {}, "source": [ "The mesh is associated to parameters on vertices, (half-)edges and faces (cells). The meaning of these parameters is \"given\" by the loss functions.\n", @@ -104,7 +111,7 @@ { "cell_type": "code", "execution_count": 4, - "id": "9de49e7d", + "id": "2a82d406", "metadata": {}, "outputs": [], "source": [ @@ -130,7 +137,7 @@ }, { "cell_type": "markdown", - "id": "f02b8ac7", + "id": "4902f316", "metadata": {}, "source": [ "It is time to define our custom inner loss function. This energy function will penalize cell areas different than 1 and the length of edges depending on the tension on them." @@ -139,7 +146,7 @@ { "cell_type": "code", "execution_count": 5, - "id": "f66f352a", + "id": "47f3e4c9", "metadata": {}, "outputs": [], "source": [ @@ -201,7 +208,7 @@ }, { "cell_type": "markdown", - "id": "3090d63f", + "id": "aae27e7d", "metadata": {}, "source": [ "Now we are ready to perform the inner optimization ! This part is done by a PbcBilevelOptimizer, that stores hyper-parameters for the optimizations.\n", @@ -212,7 +219,7 @@ { "cell_type": "code", "execution_count": 6, - "id": "cf6e18fc", + "id": "9d7f09cc", "metadata": {}, "outputs": [], "source": [ @@ -220,7 +227,7 @@ "\n", "bilevel_optimizer = PbcBilevelOptimizer()\n", "bilevel_optimizer.loss_function_inner = energy # Don't forget to set the loss function !\n", - "# Note: those are base values so the following can be ommited\n", + "# Note: those are base values so the following can be omitted\n", "bilevel_optimizer.inner_solver = optax.sgd(learning_rate=0.01) # inner solver\n", "bilevel_optimizer.update_T1 = True # Perform T1 transitions if necessary.\n", "bilevel_optimizer.min_dist_T1 = 0.005 # Threshold to perform the T1 transitions.\n", @@ -231,7 +238,7 @@ }, { "cell_type": "markdown", - "id": "6ed9524b", + "id": "b3d999ae", "metadata": {}, "source": [ "Let's perform the inner optimization. It's as simple as that:" @@ -240,11 +247,11 @@ { "cell_type": "code", "execution_count": 7, - "id": "9972d85c", + "id": "33d0af5f", "metadata": {}, "outputs": [], "source": [ - "# Other paramerters are image_target (for cost_mesh2image), beta (for EP).\n", + "# Other parameters are image_target (for cost_mesh2image), beta (for EP).\n", "loss_history = bilevel_optimizer.inner_optimization(mesh)\n", "# If you want to select only a subset of vertices, edges, and faces, it's possible:\n", "# bilevel_optimizer.inner_optimization(mesh,\n", @@ -257,7 +264,7 @@ { "cell_type": "code", "execution_count": 8, - "id": "4308eaef", + "id": "efa086f1", "metadata": {}, "outputs": [ { @@ -277,7 +284,7 @@ }, { "cell_type": "markdown", - "id": "2a7048cc", + "id": "7b7fe595", "metadata": {}, "source": [ "The inner optimization nudged the mesh to minimize the length of edges with high tension while keeping the area of each face close to 1.\n", @@ -292,7 +299,7 @@ { "cell_type": "code", "execution_count": 9, - "id": "b5b0ff6e", + "id": "54bb4d68", "metadata": {}, "outputs": [], "source": [ @@ -314,7 +321,7 @@ { "cell_type": "code", "execution_count": 10, - "id": "dbc9b248", + "id": "5f9422b5", "metadata": {}, "outputs": [ { @@ -334,7 +341,7 @@ }, { "cell_type": "markdown", - "id": "45a64071", + "id": "470b7566", "metadata": {}, "source": [ "OK now we obtained our target vertices, edges and faces, let's store them in our mesh : they become the mesh target." @@ -343,7 +350,7 @@ { "cell_type": "code", "execution_count": 11, - "id": "aeecde26", + "id": "6ce435cb", "metadata": {}, "outputs": [], "source": [ @@ -351,7 +358,7 @@ "bilevel_optimizer.edges_target = target.edges.copy()\n", "bilevel_optimizer.faces_target = target.faces.copy()\n", "# We can discard the target mesh now because it is of no use.\n", - "del target\n", + "# del target\n", "\n", "energy_target = get_energy_function(he_params_reference_target)\n", "bilevel_optimizer.loss_function_inner = energy_target # Update the inner loss function" @@ -359,7 +366,7 @@ }, { "cell_type": "markdown", - "id": "f619f041", + "id": "15d02844", "metadata": {}, "source": [ "In addition to the previous parameters for the inner optimization, the following parameters are used during the bilevel optimization process:" @@ -368,7 +375,7 @@ { "cell_type": "code", "execution_count": 12, - "id": "f6520722", + "id": "a360cf63", "metadata": {}, "outputs": [], "source": [ @@ -379,14 +386,14 @@ "\n", "from vertax import BilevelOptimizationMethod # An enumeration of the methods you can use\n", "\n", - "# Once again this part can be ommited because those are the base value for these parameters\n", + "# Once again this part can be omitted because those are the base value for these parameters\n", "bilevel_optimizer.outer_solver = optax.adam(learning_rate=0.0001, nesterov=True) # outer solver\n", "bilevel_optimizer.bilevel_optimization_method = BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION" ] }, { "cell_type": "markdown", - "id": "78008e0c", + "id": "8d27e00d", "metadata": {}, "source": [ "Let's run our bilevel optimization process. The first epoch is always a bit long but the next ones will be quick.\n", @@ -398,36 +405,47 @@ }, { "cell_type": "code", - "execution_count": 13, - "id": "9a463668", + "execution_count": 39, + "id": "efe743c7", "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ - "epoch: 0/10, actual cost is 0.00399027019739151\n", - "epoch 0 took 6.920719959000053 seconds.\n", - "epoch: 1/10, actual cost is 0.003931049257516861\n", - "epoch 1 took 1.240284757000154 seconds.\n", - "epoch: 2/10, actual cost is 0.003896141890436411\n", - "epoch 2 took 1.2602369420001196 seconds.\n", - "epoch: 3/10, actual cost is 0.003853569971397519\n", - "epoch 3 took 1.2731893489999493 seconds.\n", - "epoch: 4/10, actual cost is 0.0038316622376441956\n", - "epoch 4 took 1.2410099319995425 seconds.\n", - "epoch: 5/10, actual cost is 0.003800261067226529\n", - "epoch 5 took 1.2721578809996572 seconds.\n", - "epoch: 6/10, actual cost is 0.0037877082359045744\n", - "epoch 6 took 1.2755202250000366 seconds.\n", - "epoch: 7/10, actual cost is 0.0037637564819306135\n", - "epoch 7 took 1.2615269740008443 seconds.\n", - "epoch: 8/10, actual cost is 0.0037574756424874067\n", - "epoch 8 took 1.259819206999964 seconds.\n", - "epoch: 9/10, actual cost is 0.003738644067198038\n", - "epoch 9 took 1.2245857420002721 seconds.\n", - "epoch: 10/10, actual cost is 0.0037368000485002995\n", - "epoch 10 took 1.2501073559997167 seconds.\n" + "epoch: 0/10, actual cost is 0.0037211880553513765\n", + "epoch 0 took 1.2170525080000516 seconds.\n", + "-0.009380522184073925\n", + "epoch: 1/10, actual cost is 0.003721958724781871\n", + "epoch 1 took 1.1607601240002623 seconds.\n", + "-0.008781428448855877\n", + "epoch: 2/10, actual cost is 0.0037086878437548876\n", + "epoch 2 took 1.1663910829997803 seconds.\n", + "-0.008116534911096096\n", + "epoch: 3/10, actual cost is 0.003711005672812462\n", + "epoch 3 took 1.1220879219999915 seconds.\n", + "-0.007402104791253805\n", + "epoch: 4/10, actual cost is 0.0036993816029280424\n", + "epoch 4 took 1.1419530690000101 seconds.\n", + "-0.006774387322366238\n", + "epoch: 5/10, actual cost is 0.0037029192317277193\n", + "epoch 5 took 1.1655332840000483 seconds.\n", + "-0.0061574820429086685\n", + "epoch: 6/10, actual cost is 0.0036917426623404026\n", + "epoch 6 took 1.1711282279998159 seconds.\n", + "-0.0055525656789541245\n", + "epoch: 7/10, actual cost is 0.003695834195241332\n", + "epoch 7 took 1.146710374000122 seconds.\n", + "-0.004930838942527771\n", + "epoch: 8/10, actual cost is 0.003685095114633441\n", + "epoch 8 took 1.1108373649999521 seconds.\n", + "-0.0042711300775408745\n", + "epoch: 9/10, actual cost is 0.0036892385687679052\n", + "epoch 9 took 1.291769063999709 seconds.\n", + "-0.0035925169941037893\n", + "epoch: 10/10, actual cost is 0.003678437788039446\n", + "epoch 10 took 1.1649115280001752 seconds.\n", + "-0.002958990866318345\n" ] } ], @@ -455,12 +473,14 @@ " print(f\"epoch: {epoch}/{nb_epochs}, actual cost is {cost}\")\n", "\n", " loss_history = bilevel_optimizer.bilevel_optimization(mesh)\n", - " print(f\"epoch {epoch} took {perf_counter() - t_start} seconds.\")" + " print(f\"epoch {epoch} took {perf_counter() - t_start} seconds.\")\n", + " pearson_corr = float(jnp.corrcoef(mesh.edges_params, target.edges_params)[0, 1])\n", + " print(pearson_corr)" ] }, { "cell_type": "markdown", - "id": "f0e4752d", + "id": "ea837f4c", "metadata": {}, "source": [ "Just to check that the process is working, let's do a basic plot of the cost in function of the epoch." @@ -469,7 +489,7 @@ { "cell_type": "code", "execution_count": 14, - "id": "d5f6ece4", + "id": "9a8e62c0", "metadata": {}, "outputs": [ { @@ -492,7 +512,7 @@ }, { "cell_type": "markdown", - "id": "546c53b1", + "id": "90477738", "metadata": {}, "source": [ "Of course, a higher number of epochs is necessary for the cost function to stabilize and for a result to be visible in the mesh. If we plot the mesh after a sufficient number of epochs, we'll see that the vertices moved to match the best it can the target while respecting tensions and area constraints." @@ -501,7 +521,7 @@ { "cell_type": "code", "execution_count": 15, - "id": "99a31f74", + "id": "0ee24c4d", "metadata": {}, "outputs": [ { @@ -521,7 +541,7 @@ }, { "cell_type": "markdown", - "id": "83bd2c95", + "id": "37cb905f", "metadata": {}, "source": [ "We can also plot the tensions and areas:" @@ -530,7 +550,7 @@ { "cell_type": "code", "execution_count": 16, - "id": "e1d89e64", + "id": "f33a4402", "metadata": {}, "outputs": [ { @@ -555,11 +575,59 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "1bf3f553-62ca-476c-a780-ddaa1a8fe372", + "execution_count": 36, + "id": "1e3e63f9", "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "nan\n", + "[1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1. 1.]\n", + "[1.0005894 0.999558 1.0007367 0.99882126 0.9992633 0.9986739\n", + " 0.99911594 0.9997053 1.0014734 1.000442 1.0016208 0.9994106\n", + " 1.000442 1. 1.0005894 0.9997053 1.000884 0.99985266\n", + " 0.99985266 0.99882126]\n", + "20\n", + "20\n", + "[[ 1. nan]\n", + " [nan nan]]\n", + "1.0\n", + "[[7.2733764e-07 0.0000000e+00]\n", + " [0.0000000e+00 0.0000000e+00]]\n" + ] + } + ], + "source": [ + "pearson_corr = float(jnp.corrcoef(mesh.faces_params, target.faces_params)[0, 1])\n", + "print(pearson_corr)\n", + "print(target.faces_params)\n", + "print(mesh.faces_params)\n", + "print(len(mesh.faces_params))\n", + "print(len(target.faces_params))\n", + "print((jnp.corrcoef(mesh.faces_params, target.faces_params)))\n", + "print((jnp.cov(mesh.faces_params, target.faces_params)))" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "id": "29dc679f-8bcd-428f-a3f7-305e1f3af68c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "-0.009977000765502453\n" + ] + } + ], + "source": [ + "pearson_corr = float(jnp.corrcoef(mesh.edges_params, target.edges_params)[0, 1])\n", + "print(pearson_corr)" + ] } ], "metadata": { diff --git a/docs/inverse_modelling_example_bounded.ipynb b/examples/inverse_modelling_example_bounded.ipynb similarity index 100% rename from docs/inverse_modelling_example_bounded.ipynb rename to examples/inverse_modelling_example_bounded.ipynb diff --git a/docs/mask.tif b/examples/mask.tif similarity index 100% rename from docs/mask.tif rename to examples/mask.tif diff --git a/docs/pbc_from_image.ipynb b/examples/pbc_from_image.ipynb similarity index 99% rename from docs/pbc_from_image.ipynb rename to examples/pbc_from_image.ipynb index 268d149..50375df 100644 --- a/docs/pbc_from_image.ipynb +++ b/examples/pbc_from_image.ipynb @@ -174,7 +174,7 @@ ], "metadata": { "kernelspec": { - "display_name": ".venv", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -188,7 +188,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.13.11" + "version": "3.14.3" } }, "nbformat": 4, diff --git a/figures/benchmarks.png b/figures/benchmarks.png new file mode 100644 index 0000000..868de14 Binary files /dev/null and b/figures/benchmarks.png differ diff --git a/figures/concept.png b/figures/concept.png new file mode 100644 index 0000000..0670163 Binary files /dev/null and b/figures/concept.png differ diff --git a/figures/periodic_bounded.png b/figures/periodic_bounded.png new file mode 100644 index 0000000..54e01d1 Binary files /dev/null and b/figures/periodic_bounded.png differ diff --git a/figures/vertax_text.png b/figures/vertax_text.png new file mode 100644 index 0000000..bcf35fc Binary files /dev/null and b/figures/vertax_text.png differ diff --git a/pyproject.toml b/pyproject.toml index 0f8c625..86ea895 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -149,6 +149,7 @@ markers = ["long: marks tests as slow (deselect with '-m \"not slow\"')"] [dependency-groups] dev = [ + "pdoc>=16.0.0", "pytest>=8.4.2", "scalene>=1.5.55", ] diff --git a/src/vertax/__init__.py b/src/vertax/__init__.py index 44f8ec9..97e830b 100644 --- a/src/vertax/__init__.py +++ b/src/vertax/__init__.py @@ -1,21 +1,108 @@ -"""Main VertAX module.""" +"""A differentiable JAX-based framework for vertex modeling and inverse design of epithelial tissues. +Epithelial tissues dynamically reshape through local mechanical interactions among cells. +Understanding, inferring, and designing these mechanics is a central challenge in developmental biology and biophysics. +**VertAX** is a computational framework built to address this challenge. + +**VertAX** is a **framework for vertex-based modeling**: + it represents epithelial tissues as two-dimensional polygonal meshes in which cells are faces, junctions are edges, + tricellular contacts are vertices, and mechanical equilibrium is defined by the minimum of a user-specified energy. + + Built on **JAX**, VertAX is designed not only for forward simulation, + but also for inverse problems such as parameter inference and tissue design. + +In **VertAX**, a `Mesh` represents a vertex model of an epithelial tissue. It is made of faces (cells), edges (interface between cells) and vertices (where 3 cells or more meet). + +More specifically, two types of meshes are currently supported: +- `PbcMesh` have Periodic Boundary Conditions, and are used for bulk tissue dynamics without explicit external boundaries. +- `BoundedMesh` are designed for finite tissue clusters, with curved free interfaces. + +The other first class objects of **VertAX** are called Bilevel Optimizers. They allow to formulate inverse problems as nested optimizations: + +$$ +\\begin{aligned} +\\textbf{Outer problem (learning):} \\quad +\\theta^{\\ast} &= \\arg\\min_{\\theta} \\mathcal{C}\\left(X^{\\ast}_{\\theta},\\theta\\right) +&& \\leftarrow \\text{fit data or reach a target} \\\\ +\\end{aligned} +$$ +$$ +\\begin{aligned} +\\textbf{Inner problem (physics):} \\quad \\text{s.t.}& +X^{\\ast}_{\\theta} \\in \\arg\\min_{X} \\mathcal{E}(X,\\theta) +&& \\leftarrow \\text{compute mechanical equilibrium} +\\end{aligned} +$$ + + +Here, $X$ denotes the tissue configuration, i.e. the vertex positions of the mesh, and $\\theta$ denotes the model parameters, such as line tensions, target areas, or shape factors. + +In other words, VertAX repeatedly solves a mechanical equilibrium problem for a given parameter set $\\theta$, then updates those parameters to better match data or a design objective. + +In symmetry with meshes, a base abstract class `_BilevelOptimizer` defines common hyper-parameters and methods for the bilevel optimization, but you need to use the specialized classes: +- `PbcBilevelOptimizer` for `PbcMesh`, +- `BoundedBilevelOptimizer` for `BoundedMesh`. + +**VertAX** comes with pre-defined energy and cost functions but you can easily define your own functions. See the `examples` folder in the repository to have a typical example on how to use **VertAX**. + +Finally, there are plot functions to easily see the results of your experiments. See `plot_mesh` for example. + +Users can define their own energy functions for the inner optimization and cost function for the outer optimization, however we provide some basic ones. +If you use your own make sure to use the exact same signature as we do for these functions, otherwise it won't work. See the cost and energy functions we provide in this documentation. +""" # noqa: D301, E501 + +from vertax.bilevelopt.bilevelopt import _BilevelOptimizer from vertax.bilevelopt.boundedbop import BoundedBilevelOptimizer from vertax.bilevelopt.pbcbop import PbcBilevelOptimizer +from vertax.cost import ( + cost_areas, + cost_checkerboard, + cost_d_IAS, + cost_IAS, + cost_mesh2image, + cost_ratio, + cost_tem_halfedge, + cost_v2v, + cost_v2v_ias, + cost_v2v_tem, +) +from vertax.energy import ( + energy_line_tensions, + energy_line_tensions_bounded, + energy_shape_factor_hetero, + energy_shape_factor_homo, +) from vertax.meshes.bounded_mesh import BoundedMesh +from vertax.meshes.mesh import Mesh from vertax.meshes.pbc_mesh import PbcMesh from vertax.meshes.plot import EdgePlot, FacePlot, VertexPlot, get_plot_mesh, plot_mesh from vertax.method_enum import BilevelOptimizationMethod -__all__ = [ +__all__ = [ # noqa: RUF022 + "Mesh", + "PbcMesh", + "BoundedMesh", "BilevelOptimizationMethod", + "_BilevelOptimizer", + "PbcBilevelOptimizer", "BoundedBilevelOptimizer", - "BoundedMesh", - "EdgePlot", + "plot_mesh", + "get_plot_mesh", "FacePlot", - "PbcBilevelOptimizer", - "PbcMesh", + "EdgePlot", "VertexPlot", - "get_plot_mesh", - "plot_mesh", + "cost_v2v", + "cost_mesh2image", + "cost_areas", + "cost_IAS", + "cost_d_IAS", + "cost_tem_halfedge", + "cost_v2v_ias", + "cost_v2v_tem", + "cost_ratio", + "cost_checkerboard", + "energy_shape_factor_hetero", + "energy_shape_factor_homo", + "energy_line_tensions", + "energy_line_tensions_bounded", ] diff --git a/src/vertax/bilevelopt/__init__.py b/src/vertax/bilevelopt/__init__.py index 3e063ab..8b2eb09 100644 --- a/src/vertax/bilevelopt/__init__.py +++ b/src/vertax/bilevelopt/__init__.py @@ -1 +1 @@ -"""Bilevel optimization problem classes.""" +"""Bilevel optimization problem classes. Use the class corresponding to your meshes.""" diff --git a/src/vertax/bilevelopt/bilevelopt.py b/src/vertax/bilevelopt/bilevelopt.py index 20ce2f9..bbad8a1 100644 --- a/src/vertax/bilevelopt/bilevelopt.py +++ b/src/vertax/bilevelopt/bilevelopt.py @@ -20,22 +20,38 @@ class _BilevelOptimizer: - """Abstract class for Bi-level optimizers.""" + """Abstract base class for Bi-level optimizers. + + Use the specialezed corresponding ones: + - to optimize a `PbcMesh` use a `PbcBilevelOptimizer`, + - to optimize a `BoundedMesh` use a `BoundedBilevelOptimizer`. + """ def __init__(self) -> None: """Initialize shared parameters and hyper-parameters between Bi-level optimizers.""" - self.custom_metrics: dict[str, tuple[Callable[[Any, Any], float], list[float], list[float]]] = {} self.bilevel_optimization_method: BilevelOptimizationMethod = BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION + """Which bilevel optimization method to use. Defaults to Equilibrium Propagation.""" self.inner_solver: optax.GradientTransformation = optax.sgd(learning_rate=0.01) + """Which inner solver to use. Defaults to `optax.sgd(learning_rate=0.01)`.""" self.outer_solver: optax.GradientTransformation = optax.adam(learning_rate=0.0001, nesterov=True) + """Which outer solver to use. Defaults to `optax.adam(learning_rate=0.0001, nesterov=True)`""" self.loss_function_inner: Callable | None = None + """Define your inner loss function.""" self.loss_function_outer: Callable | None = None + """Define your outer loss function.""" + self.custom_metrics: dict[str, tuple[Callable[[Any, Any], float], list[float], list[float]]] = {} + """Define custom metrics that can be saved during optimization. + The function must take two arguments : a mesh and a bilevel optimizer ; and return a float.""" self.max_nb_iterations: int = 1000 + """Maximum number of iterations during an optimization step.""" self.tolerance: float = 1e-4 + """Below this level, we consider that the loss is stagnating.""" self.patience: int = 5 + """Maximum number of consecutive stagnating loss before we stop.""" self.min_dist_T1: float = 0.005 + """Threshold to perform T1 transitions.""" self._update_T1: bool = False self._update_T1_func: Callable | None = None # value set by _set_update_T1_func @@ -44,20 +60,23 @@ def __init__(self) -> None: self._outer_opt_func: Callable[[Mesh, Array | None, Array | None, Array | None], None] | None = None self.update_T1 = True # Force the setting of update T1 func + """Perform T1 transitions if necessary.""" # Targets self.vertices_target = jnp.array([]) + """Vertices table of a target mesh if any.""" self.edges_target = jnp.array([]) + """Edges table of a target mesh if any.""" self.faces_target = jnp.array([]) + """Faces table of a target mesh if any.""" # Those attributes are not always used (depends on the bilevel_optimization_method) self.image_target: Array = jnp.array([]) + """Image target if any.""" self.beta = 0.01 + """β parameter used for Equilibrium Propagation.""" def _set_update_T1_func(self, b: bool) -> None: # noqa: N802 - """Set the _update_T1_func callable with respect to whether it is needed or not. - - Must be implemented by child classes. - """ + """Set the _update_T1_func callable with respect to whether it is needed or not.""" raise NotImplementedError @property @@ -158,7 +177,7 @@ def bilevel_optimization( only_on_edges: None | list[int] = None, only_on_faces: None | list[int] = None, ) -> list[float]: - """Optimize the mesh for the loss function given. + """Optimize the mesh for the loss functions given. Args: mesh (Mesh): The mesh to act on. diff --git a/src/vertax/bilevelopt/boundedbop.py b/src/vertax/bilevelopt/boundedbop.py index 6c7d25a..5b4c581 100644 --- a/src/vertax/bilevelopt/boundedbop.py +++ b/src/vertax/bilevelopt/boundedbop.py @@ -20,10 +20,13 @@ class BoundedBilevelOptimizer(_BilevelOptimizer): - """Bi-level optimizer for periodic boundary condition meshes.""" + """Bi-level optimizer for bounded meshes (`BoundedMesh`).""" def __init__(self) -> None: - """Create a Bi-level optimizer for periodic boundary condition meshes with default parameters.""" + """Create a Bi-level optimizer for bounded meshes with default parameters. + + Does not set the inner and outer loss functions yet. + """ super().__init__() self._inner_opt_func = self._inner_opt self._outer_opt_func = self._outer_opt diff --git a/src/vertax/bilevelopt/pbcbop.py b/src/vertax/bilevelopt/pbcbop.py index 5c3f75f..a1bbcdf 100644 --- a/src/vertax/bilevelopt/pbcbop.py +++ b/src/vertax/bilevelopt/pbcbop.py @@ -19,10 +19,13 @@ class PbcBilevelOptimizer(_BilevelOptimizer): - """Bi-level optimizer for periodic boundary condition meshes.""" + """Bi-level optimizer for periodic boundary condition meshes (`PbcMesh`).""" def __init__(self) -> None: - """Create a Bi-level optimizer for periodic boundary condition meshes with default parameters.""" + """Create a Bi-level optimizer for periodic boundary condition meshes with default parameters. + + Does not specify the inner and outer loss yet. + """ super().__init__() self._inner_opt_func = self._inner_opt self._outer_opt_func = self._outer_opt @@ -159,7 +162,14 @@ def _outer_opt( else: match self.bilevel_optimization_method: case BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION: - mesh.vertices_params, mesh.edges_params, mesh.faces_params = outer_opt( + ( + mesh.vertices, + mesh.edges, + mesh.faces, + mesh.vertices_params, + mesh.edges_params, + mesh.faces_params, + ) = outer_opt( mesh.vertices, mesh.edges, mesh.faces, @@ -187,7 +197,14 @@ def _outer_opt( ) case BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION: - mesh.vertices_params, mesh.edges_params, mesh.faces_params = outer_eq_prop( + ( + mesh.vertices, + mesh.edges, + mesh.faces, + mesh.vertices_params, + mesh.edges_params, + mesh.faces_params, + ) = outer_eq_prop( mesh.vertices, mesh.edges, mesh.faces, @@ -216,7 +233,14 @@ def _outer_opt( ) case BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION: - mesh.vertices_params, mesh.edges_params, mesh.faces_params = outer_implicit( + ( + mesh.vertices, + mesh.edges, + mesh.faces, + mesh.vertices_params, + mesh.edges_params, + mesh.faces_params, + ) = outer_implicit( mesh.vertices, mesh.edges, mesh.faces, @@ -244,7 +268,14 @@ def _outer_opt( ) case BilevelOptimizationMethod.ADJOINT_STATE: - mesh.vertices_params, mesh.edges_params, mesh.faces_params = outer_adjoint_state( + ( + mesh.vertices, + mesh.edges, + mesh.faces, + mesh.vertices_params, + mesh.edges_params, + mesh.faces_params, + ) = outer_adjoint_state( mesh.vertices, mesh.edges, mesh.faces, diff --git a/src/vertax/bounded.py b/src/vertax/bounded.py deleted file mode 100644 index 1699f49..0000000 --- a/src/vertax/bounded.py +++ /dev/null @@ -1,1051 +0,0 @@ -"""Bounded mesh with arc circles for boundary cells.""" - -from __future__ import annotations - -from collections.abc import Callable -from pathlib import Path -from typing import TYPE_CHECKING, Literal, Self - -import jax -import jax.numpy as jnp -import matplotlib -import matplotlib.pyplot as plt -import numpy as np -from matplotlib.axes import Axes -from matplotlib.figure import Figure -from scipy.spatial import Voronoi - -from vertax.geo import get_any_length, get_area_bounded, get_perimeter_bounded -from vertax.mesh import Mesh -from vertax.method_enum import BilevelOptimizationMethod -from vertax.opt_bounded import ( - InnerLossFunctionBounded, - OuterLossFunction, - UpdateT1FuncBounded, - inner_opt_bounded, - outer_eq_prop_bounded, - outer_implicit_bounded, - outer_opt_bounded, -) -from vertax.plot import EdgePlot, FacePlot, VertexPlot, add_colorbar, adjust_lightness, get_cmap -from vertax.topo import do_not_update_T1_bounded, update_T1_bounded - -if TYPE_CHECKING: - from jax import Array - from matplotlib.pylab import Generator - from numpy.typing import NDArray - - -class BoundedMesh(Mesh): - """Bounded mesh with arc circles for boundary cells.""" - - def __init__(self) -> None: - """Do not call the constructor.""" - super().__init__() - self.angles: Array = jnp.array([]) - self.angles_target: Array = jnp.array([]) - self._update_T1_func: UpdateT1FuncBounded = update_T1_bounded - - def save_mesh_txt( - self, - directory: str, - vertices_filename: str = "vertTable.txt", - angles_filename: str = "angTable.txt", - edges_filename: str = "heTable.txt", - faces_filename: str = "faceTable.txt", - ) -> None: - """Save a mesh in separate text files that can be read by numpy. - - Only save the vertices, angles, edges and faces, not other parameters. - - Args: - directory (str): Path to the directory where to save the files. - vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt". - angles_filename (str, optional): Filename for the angles table. Defaults to "angTable.txt". - edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt". - faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt". - """ - dirpath = Path(directory) - dirpath.mkdir(parents=True, exist_ok=True) - vertpath = dirpath / vertices_filename - angpath = dirpath / angles_filename - hepath = dirpath / edges_filename - facepath = dirpath / faces_filename - np.savetxt(vertpath, self.vertices) - np.savetxt(angpath, self.angles) - np.savetxt(hepath, self.edges) - np.savetxt(facepath, self.faces) - - def save_mesh(self, path: str) -> None: - """Save mesh to a file. - - All BoundedMesh data is saved, except for the solvers that are not saved. - - Args: - path (str): Path to the saved file. The extension is .npz. - """ - Path(path).parent.mkdir(parents=True, exist_ok=True) - np.savez_compressed( - path, - allow_pickle=False, - vertices=self.vertices, - edges=self.edges, - faces=self.faces, - angles=self.angles, - width=self.width, - height=self.height, - vertices_params=self.vertices_params, - edges_params=self.edges_params, - faces_params=self.faces_params, - vertices_target=self.vertices_target, - edges_target=self.edges_target, - faces_target=self.faces_target, - angles_target=self.angles_target, - image_target=self.image_target, - bilevel_optimization_method=self.bilevel_optimization_method.value, - beta=self.beta, - min_dist_T1=self.min_dist_T1, - max_nb_iterations=self.max_nb_iterations, - tolerance=self.tolerance, - patience=self.patience, - update_t1=self.update_t1, - ) - - @classmethod - def load_mesh(cls, path: str) -> Self: - """Load a mesh from a file. - - All BoundedMesh data is reloaded, except for the solvers that are not saved. - - Args: - path (str): Path to the mesh file (.npz). - - Returns: - Mesh: the mesh loaded from the .npz file. - """ - mesh_file = np.load(path) - mesh = cls._create() - mesh.vertices, mesh.edges, mesh.faces, mesh.angles = ( - mesh_file["vertices"], - mesh_file["edges"], - mesh_file["faces"], - mesh_file["angles"], - ) - mesh.width = mesh_file["width"] - mesh.height = mesh_file["height"] - mesh.vertices_params = mesh_file["vertices_params"] - mesh.edges_params = mesh_file["edges_params"] - mesh.faces_params = mesh_file["faces_params"] - mesh.vertices_target = mesh_file["vertices_target"] - mesh.edges_target = mesh_file["edges_target"] - mesh.faces_target = mesh_file["faces_target"] - mesh.angles_target = mesh_file["angles_target"] - mesh.image_target = mesh_file["image_target"] - mesh.bilevel_optimization_method = BilevelOptimizationMethod(mesh_file["bilevel_optimization_method"]) - mesh.beta = mesh_file["beta"] - mesh.min_dist_T1 = mesh_file["min_dist_T1"] - mesh.max_nb_iterations = mesh_file["max_nb_iterations"] - mesh.tolerance = mesh_file["tolerance"] - mesh.patience = mesh_file["patience"] - mesh.update_t1 = mesh_file["update_t1"] - return mesh - - @classmethod - def load_mesh_txt( - cls, - directory: str, - vertices_filename: str = "vertTable.txt", - angles_filename: str = "angTable.txt", - edges_filename: str = "heTable.txt", - faces_filename: str = "faceTable.txt", - ) -> Self: - """Load a mesh from text files. - - Only load the vertices, angles, edges and faces, not other parameters. - - Args: - directory (str): Directory where the text files are stored. - vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt". - angles_filename (str, optional): Filename for the angles table. Defaults to "angTable.txt". - edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt". - faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt". - - Returns: - Self: The loaded mesh. - """ - dirpath = Path(directory) - dirpath.mkdir(parents=True, exist_ok=True) - vertpath = dirpath / vertices_filename - angpath = dirpath / angles_filename - hepath = dirpath / edges_filename - facepath = dirpath / faces_filename - - mesh = cls._create() - mesh.vertices = jnp.array(np.loadtxt(vertpath, dtype=np.float64)) - mesh.angles = jnp.array(np.loadtxt(angpath, dtype=np.float64)) - mesh.edges = jnp.array(np.loadtxt(hepath, dtype=np.int64)) - mesh.faces = jnp.array(np.loadtxt(facepath, dtype=np.int64)) - return mesh - - @classmethod - def copy_mesh(cls, other_mesh: Self) -> Self: - """Copy all parameters from another mesh in a new mesh.""" - mesh = cls._create() - mesh.vertices = other_mesh.vertices.copy() - mesh.edges = other_mesh.edges.copy() - mesh.faces = other_mesh.faces.copy() - mesh.angles = other_mesh.angles.copy() - mesh.angles_target = other_mesh.angles_target.copy() - mesh.width = other_mesh.width - mesh.height = other_mesh.height - mesh.vertices_params = other_mesh.vertices_params.copy() - mesh.edges_params = other_mesh.edges_params.copy() - mesh.faces_params = other_mesh.faces_params.copy() - mesh.vertices_target = other_mesh.vertices_target.copy() - mesh.edges_target = other_mesh.edges_target.copy() - mesh.faces_target = other_mesh.faces_target.copy() - mesh.image_target = other_mesh.image_target.copy() - mesh.bilevel_optimization_method = other_mesh.bilevel_optimization_method - mesh.beta = other_mesh.beta - mesh.min_dist_T1 = other_mesh.min_dist_T1 - mesh.max_nb_iterations = other_mesh.max_nb_iterations - mesh.tolerance = other_mesh.tolerance - mesh.patience = other_mesh.patience - mesh.inner_solver = other_mesh.inner_solver - mesh.outer_solver = other_mesh.outer_solver - mesh._update_T1_func = other_mesh._update_T1_func - - return mesh - - @property - def update_t1(self) -> bool: - """Whether or not update the mesh by applying T1 transitions.""" - return self._update_T1_func != do_not_update_T1_bounded - - @update_t1.setter - def update_t1(self, b: bool) -> None: - """Whether or not update the mesh by applying T1 transitions.""" - if b: - self._update_T1_func = update_T1_bounded - else: - self._update_T1_func = do_not_update_T1_bounded - - @property - def nb_angles(self) -> int: - """Get the number of angles of the mesh.""" - return len(self.angles) - - def get_length(self, half_edge_id: Array) -> Array: - """Get the length of an edge.""" - vertTable = jnp.vstack([jnp.array([[0.0, 0.0], [1.0, 1.0]]), self.vertices]) - angTable = jnp.repeat(self.angles, 2) - - def _get_length(half_edge_id: Array) -> Array: - return get_any_length(half_edge_id, vertTable, angTable, self.edges) - - return jax.vmap(_get_length)(half_edge_id) - - def get_perimeter(self, face_id: Array) -> Array: - """Get the area of a face.""" - vertTable = jnp.vstack([jnp.array([[0.0, 0.0], [1.0, 1.0]]), self.vertices]) - angTable = jnp.repeat(self.angles, 2) - - def _get_perimeter(face_id: Array) -> Array: - return get_perimeter_bounded(face_id, vertTable, angTable, self.edges, self.faces) - - return jax.vmap(_get_perimeter)(face_id) - - def get_area(self, face_id: Array) -> Array: - """Get the area of a face.""" - vertTable = jnp.vstack([jnp.array([[0.0, 0.0], [1.0, 1.0]]), self.vertices]) - angTable = jnp.repeat(self.angles, 2) - - def _get_area(face_id: Array) -> Array: - return get_area_bounded(face_id, vertTable, angTable, self.edges, self.faces) - - return jax.vmap(_get_area)(face_id) - - @classmethod - def from_random_seeds(cls, nb_seeds: int, width: float, height: float, random_key: int, nb_fates: int = 2) -> Self: - """Create a bounded Mesh from random seeds. - - Args: - nb_seeds (int): Number of random seeds to use. - width (float): width of the box containing the seeds. - height (float): height of the box containing the seeds. - random_key (int): seed for a random number generator to add new seeds if needed, decide on cell fates... - nb_fates (int, default=2): number of possible different fate marker for a cell. - - Returns: - Self: The corresponding mesh. - """ - rng = np.random.default_rng(seed=random_key) - seeds = rng.random((nb_seeds, 2)) * (width, height) - return cls.from_seeds(seeds, width, height, random_key, nb_fates) - - @classmethod - def from_seeds(cls, seeds: NDArray, width: float, height: float, random_key: int, nb_fates: int = 2) -> Self: # noqa: C901 - """Create a bounded Mesh from a list of seeds. - - The seeds are assumed to have x-coordinate in ]0, width[ and y-coordinate in ]0, height[. - Note that the final mesh might not use your seeds if they don't work to create a correct - bounded mesh via our method. - - Args: - seeds (Array[float32]): jax float array of seed positions of shape (nbSeeds, 2). - width (float): width of the box containing the seeds. - height (float): height of the box containing the seeds. - random_key (int): seed for a random number generator to add new seeds if needed, decide on cell fates... - nb_fates (int, default=2): number of possible different fate marker for a cell. - """ - rng = np.random.default_rng(seed=random_key) - n_cells = len(seeds) # starting number of seeds must be equal to the desired number of cells (faces) - - # We'll try to construct a Voronoi diagram with n_cells closed (bounded) cells. - # This will not work with exactly n_cells seeds because there will be unbounded cells. - # If the number of bounded cells is insufficient, we add a new seed to the list. - # If there is too many bounded cells, we retry with entirely new seeds. - # We also check that the bounded cells are connected. - while True: - success = 0 # if 1 : not enough bounded cells. If 2 : too many bounded cells. - - # Create the Voronoi diagrams from current seed list. - voronoi = Voronoi(seeds) - vertices = voronoi.vertices - edges = voronoi.ridge_vertices - faces = voronoi.regions # regions = faces = cells - - # We count the number of bounded cells and the connectivity of vertices. - inbound_faces = [] - inbound_vertices = np.zeros(vertices.shape[0], dtype=np.int32) - for face in faces: - if face and all(item > -1 for item in face): # the face must not be an empty list - face_vertices_positions = vertices[face] - # We check that all of the face's vertices are in a box [0, width]x[0, height] - if ( - np.all(face_vertices_positions[:, 0] < width) - and np.all(face_vertices_positions[:, 1] < height) - and np.all(face_vertices_positions > 0) - ): - inbound_faces.append(face) # the face is bounded - inbound_vertices[face] += 1 # +1 to the connectivity of the face's vertices. - - # getting rid of faces connected to a single other inbound face - # (these can be problematic and lead to many special cases later on) - while True: - num_infaces = len(inbound_faces) - del_count = 0 - for i, face in enumerate(reversed(inbound_faces)): - # only 2 (or less (not sure it's possible)) vertices of the face are shared with other faces : - # that means it is connected to one other bounded face only -> We remove it. - if np.sum(inbound_vertices[face] > 1) <= 2: - inbound_vertices[face] -= 1 - del inbound_faces[num_infaces - i - 1] - del_count += 1 - # Removing one face can possibly alter other faces so we might do another loop. - # We stop when there is no more face to remove. - if del_count == 0: - break - - # Check that we have the correct number of cells. - if num_infaces < n_cells: - success = 1 - elif num_infaces > n_cells: - success = 2 - else: - # There is exactly n_cells connected bounded faces. - # Now, it is possible that a bounded face has vertices or edges - # that are not shared with other faces. We get rid of those, - # in order to have only one exterior edge that will be an arc circle. - for i, face in enumerate(inbound_faces): - useful_vertices = [] # List of the face vertices that are shared with other. - # (We'll keep them and call them "useful"). - extra_edges = [] # List of edges to replace what we have removed - last_useful = -1 # ID of the last "useful" vertex. -1 at the beginning (we'll treat that case) - new_edge = [] # New edge that will replace current vertices we're trying to remove - incomplete_new_edge = False # State boolean : are we replacing vertices right now ? - for vertex in face: - if inbound_vertices[vertex] == 1: # We found a vertex that is not shared with other faces. - # We plan to remove it by not adding it to the useful vertices list, - # and by creating a new edge from last useful vertex to the next one. - if not incomplete_new_edge: # Detect if we're not already in the incomplete edge state - new_edge = [] # re-init - new_edge.append(last_useful) - incomplete_new_edge = True # Move to incomplete edge state. - else: # We found a useful vertex - useful_vertices.append(vertex) - last_useful = vertex - if incomplete_new_edge: # If in incomplete edge state we can finally close the new edge. - new_edge.append(vertex) - extra_edges.append(new_edge) - incomplete_new_edge = False - # After looping through the vertices of the face, we need to take care of - # two special cases : the first or the last vertex is not shared. - if extra_edges and extra_edges[0][0] == -1: - extra_edges[0][0] = useful_vertices[-1] - elif incomplete_new_edge: - new_edge.append(useful_vertices[0]) - extra_edges.append(new_edge) - # The extra edges are added to the list of all edges - edges.extend(extra_edges) - inbound_faces[i] = tuple( - sorted(useful_vertices) - ) # And the face itself is replaced by only the useful vertices. - # Note that the vertices here are not ordered in clockwise or counterclockwise order anymore. - useful_vertices_set = set(np.where(inbound_vertices > 1)[0]) # We filter the useful vertices. - - # HALF EDGE DATA STRUCTURE - # Filter edges with useful vertices only. - useful_edges = [tuple(sorted(e)) for e in edges if set(e).issubset(useful_vertices_set)] - - # failing to abide by the following relation results in disconnected topologies - if len(useful_edges) != (n_cells - 1) * 3: - success = 2 # Case : we want to restart with new seeds because current solution is not OK. - else: - # We construct the half-edges. - half_edges = [] - for e in useful_edges: - half_edges.append(e) - # reciprocating edges - half_edges.append((e[1], e[0])) - - # finding clockwise (or counterclockwise) half edge set for each face, - # as we broke it earlier. - ordered_edges_inbound_faces = [] - for face in inbound_faces: - # Find all edges fon this face and we'll loow through them to order them. - edges_face = [(f1, f2) for f1 in face for f2 in face if (f1, f2) in useful_edges] - - i = 0 - start_edge = edges_face[i] - ordered_face = [start_edge] - e = start_edge - visited = [e] - while sorted(edges_face) != sorted(visited): - if e[0] == start_edge[1] and e not in visited: - ordered_face.append(e) - start_edge = e - visited.append(e) - # We must be careful because some edges might be in the wrong order. - if e[1] == start_edge[1] and e not in visited: - ordered_face.append((e[1], e[0])) - start_edge = (e[1], e[0]) - visited.append(e) - i += 1 - e = edges_face[i % len(edges_face)] - - # Sanity check : do we have a correct ordering ? - order = 0 - for e in ordered_face: - idx0 = e[0] - idx1 = e[1] - - order += (vertices[idx1][0] - vertices[idx0][0]) * (vertices[idx1][1] + vertices[idx0][1]) - - if order < 0: - ordered_edges_inbound_faces.append(ordered_face) - if order > 0: - ordered_edges_inbound_faces.append([(e[1], e[0]) for e in reversed(ordered_face)]) - if order == 0: - print("\nError: no order detected for face " + str(face) + "\n") - exit() - - # Now we fill the tables with the info we have. - useful_vertices_list = list(useful_vertices_set) - vertTable = np.zeros((len(useful_vertices_list), 2)) - for i, idx in enumerate(useful_vertices_list): - pos = vertices[idx] - vertTable[i][0] = pos[0] # x pos vert - vertTable[i][1] = pos[1] # y pos vert - - faceTable = np.zeros((len(inbound_faces), 1), dtype=np.int32) - for i, hedges_face in enumerate(ordered_edges_inbound_faces): - for j, he in enumerate(half_edges): - if he == hedges_face[0]: - faceTable[i] = j # he_inside - faceTable = _fate_selection(faceTable, nb_fates, rng) - - nb_half_edges = len(half_edges) - heTable = np.zeros((nb_half_edges, 8), dtype=np.int32) - heTable[:, 4] = 1 - heTable[:, 6] = 1 - relevant_twins = [] - # HE TABLE : - # 0 : previous half-edge. - # 1 : next half-edge. - # 2 : twin half-edge. - # 3 : source vertex id + 2 if edge is inside, 0 if the edge is outside - # 4 : target vertex id + 2 if edge is inside, 0 if the edge is outside - # 5 : source vertex id + 2 if edge is outside, 0 if the edge is inside - # 6 : target vertex id + 2 if edge is outside, 0 if the edge is inside - # 7 : id of the face containing this half-edge. - for i, he in enumerate(half_edges): - belongs_to_any_face = False - for hedges_face in ordered_edges_inbound_faces: - if he in hedges_face: - idx = hedges_face.index(he) - heTable[i][0] = half_edges.index(hedges_face[(idx - 1) % len(hedges_face)]) # he_prev - heTable[i][1] = half_edges.index(hedges_face[(idx + 1) % len(hedges_face)]) # he_next - # indices 0 and 1 are reserved for source or target vertices of "outside" edges. - # So we have to add +2 to other indices. - heTable[i][3] = useful_vertices_list.index(he[0]) + 2 # vert source inner edges - heTable[i][4] = useful_vertices_list.index(he[1]) + 2 # vert target inner edges - heTable[i][7] = ordered_edges_inbound_faces.index(hedges_face) # face - belongs_to_any_face = True - break - twin_idx = half_edges.index((he[1], he[0])) - heTable[i][2] = twin_idx # he twin - if not belongs_to_any_face: - relevant_twins.append(twin_idx) - - # Angles are randomly chosen between 0 and pi/2 (with some margin to avoid extreme cases). - angTable = np.ones(nb_half_edges // 2) - for tidx in relevant_twins: - angTable[tidx // 2] = rng.random() * (np.pi / 2 - 0.018) + 0.017 - heTable[tidx][5] = heTable[tidx][3] # vert source surface edges - heTable[tidx][6] = heTable[tidx][4] # vert target surface edges - heTable[tidx][3] = 0 - heTable[tidx][4] = 1 - - bounded_mesh = cls._create() - bounded_mesh.vertices = jnp.array(vertTable, dtype=np.float32) - bounded_mesh.angles = jnp.array(angTable, dtype=np.float32) - bounded_mesh.faces = jnp.array(faceTable, dtype=np.int32) - bounded_mesh.edges = jnp.array(heTable, dtype=np.int32) - bounded_mesh.width = width - bounded_mesh.height = height - - return bounded_mesh - - # If success was 1, we had not enough bounded faces, we add a new seed to see if it helps. - # Otherwise we retry with new seeds entirely. - seeds = ( - np.vstack([seeds, (width, height) * rng.random((1, 2))]) - if success == 1 - else (width, height) * rng.random((n_cells, 2)) - ) # type: ignore - - def inner_opt( - self, - loss_function_inner: InnerLossFunctionBounded, - only_on_vertices: None | list[int] = None, - only_on_edges: None | list[int] = None, - only_on_faces: None | list[int] = None, - ) -> list[float]: - """Optimize the mesh for the loss function given. - - Args: - loss_function_inner (InnerLossFunction): Loss function to optimize. - only_on_vertices (None | list[int] = None): Consider only a subset of vertices for the loss function. - All vertices if None. - only_on_edges (None | list[int] = None): Consider only a subset of edges for the loss function. - All edges if None. - only_on_faces (None | list[int] = None): Consider only a subset of faces for the loss function. - All faces if None. - - Returns: - list[float]: History of loss values during optimization. - """ - selected_vertices, selected_edges, selected_faces = None, None, None - if only_on_vertices is not None: - selected_vertices = jnp.array(only_on_vertices) - if only_on_edges is not None: - selected_edges = jnp.array(only_on_edges) - if only_on_faces is not None: - selected_faces = jnp.array(only_on_faces) - - (self.vertices, self.angles, self.edges, self.faces), energies = inner_opt_bounded( - self.vertices, - self.angles, - self.edges, - self.faces, - self.vertices_params, - self.edges_params, - self.faces_params, - loss_function_inner, - self.inner_solver, - self.min_dist_T1, - self.max_nb_iterations, - self.tolerance, - self.patience, - selected_verts=selected_vertices, - selected_hes=selected_edges, - selected_faces=selected_faces, - update_T1_func=self._update_T1_func, - ) - return list(energies) - - def outer_opt( - self, - loss_function_inner: InnerLossFunctionBounded, - loss_function_outer: OuterLossFunction, - only_on_vertices: None | list[int] = None, - only_on_edges: None | list[int] = None, - only_on_faces: None | list[int] = None, - ) -> None: - """Outer optimization depending on the optimization method.""" - selected_vertices, selected_edges, selected_faces = None, None, None - if only_on_vertices is not None: - selected_vertices = jnp.array(only_on_vertices) - if only_on_edges is not None: - selected_edges = jnp.array(only_on_edges) - if only_on_faces is not None: - selected_faces = jnp.array(only_on_faces) - match self.bilevel_optimization_method: - case BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION: - self.vertices_params, self.edges_params, self.faces_params = outer_opt_bounded( - self.vertices, - self.angles, - self.edges, - self.faces, - self.vertices_params, - self.edges_params, - self.faces_params, - self.vertices_target, - self.angles_target, - self.edges_target, - self.faces_target, - loss_function_inner, - loss_function_outer, - self.inner_solver, - self.outer_solver, - self.min_dist_T1, - self.max_nb_iterations, - self.tolerance, - self.patience, - selected_vertices, - selected_edges, - selected_faces, - self.image_target, - self._update_T1_func, - ) - case BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION: - self.vertices_params, self.edges_params, self.faces_params = outer_eq_prop_bounded( - self.vertices, - self.angles, - self.edges, - self.faces, - self.vertices_params, - self.edges_params, - self.faces_params, - self.vertices_target, - self.angles_target, - self.edges_target, - self.faces_target, - loss_function_inner, - loss_function_outer, - self.inner_solver, - self.outer_solver, - self.min_dist_T1, - self.max_nb_iterations, - self.tolerance, - self.patience, - selected_vertices, - selected_edges, - selected_faces, - self.image_target, - self.beta, - self._update_T1_func, - ) - case BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION: - self.vertices_params, self.edges_params, self.faces_params = outer_implicit_bounded( - self.vertices, - self.angles, - self.edges, - self.faces, - self.vertices_params, - self.edges_params, - self.faces_params, - self.vertices_target, - self.angles_target, - self.edges_target, - self.faces_target, - loss_function_inner, - loss_function_outer, - self.inner_solver, - self.outer_solver, - self.min_dist_T1, - self.max_nb_iterations, - self.tolerance, - self.patience, - selected_vertices, - selected_edges, - selected_faces, - self.image_target, - self._update_T1_func, - ) - case _: - msg = f"{self.bilevel_optimization_method} is not implemented for bounded meshes." - raise ValueError(msg) - - def bilevel_opt( - self, - loss_function_inner: InnerLossFunctionBounded, - loss_function_outer: OuterLossFunction, - only_on_vertices: None | list[int] = None, - only_on_edges: None | list[int] = None, - only_on_faces: None | list[int] = None, - ) -> list[float]: - """Optimize the mesh for the loss function given. - - Args: - loss_function_inner (InnerLossFunction): Loss function to optimize. - loss_function_outer (OuterLossFunction): Loss function to optimize. - only_on_vertices (None | list[int] = None): Consider only a subset of vertices for the loss function. - All vertices if None. - only_on_edges (None | list[int] = None): Consider only a subset of edges for the loss function. - All edges if None. - only_on_faces (None | list[int] = None): Consider only a subset of faces for the loss function. - All faces if None. - - Returns: - list[float]: History of loss values during optimization. - """ - self.outer_opt(loss_function_inner, loss_function_outer, only_on_vertices, only_on_edges, only_on_faces) - return self.inner_opt(loss_function_inner, only_on_vertices, only_on_edges, only_on_faces) - - def plot( - self, - vertex_plot: VertexPlot = VertexPlot.BLACK, - edge_plot: EdgePlot = EdgePlot.BLACK, - face_plot: FacePlot = FacePlot.MULTICOLOR, - vertex_parameters_name: str = "", - edge_parameters_name: str = "", - face_parameters_name: str = "", - show: bool = True, - save: bool = False, - save_path: str = "bounded_mesh.png", - faces_cmap_name: str = "cividis", - edges_cmap_name: str = "coolwarm", - edges_width: float = 2, - vertices_cmap_name: str = "spring", - vertices_size: float = 20, - title: str = "", - ) -> None: - """Plot the mesh and decide to save and/or show the mesh or not.""" - fig, _ax = self.get_plot( - vertex_plot, - edge_plot, - face_plot, - vertex_parameters_name, - edge_parameters_name, - face_parameters_name, - faces_cmap_name, - edges_cmap_name, - edges_width, - vertices_cmap_name, - vertices_size, - title, - ) - - if save: - plt.savefig(save_path) # format maybe should be left as a choice - - if show: - plt.show() - - plt.close(fig) - - def get_plot( - self, - vertex_plot: VertexPlot = VertexPlot.BLACK, - edge_plot: EdgePlot = EdgePlot.BLACK, - face_plot: FacePlot = FacePlot.MULTICOLOR, - vertex_parameters_name: str = "", - edge_parameters_name: str = "", - face_parameters_name: str = "", - faces_cmap_name: str = "cividis", - edges_cmap_name: str = "coolwarm", - edges_width: float = 2, - vertices_cmap_name: str = "spring", - vertices_size: float = 20, - title: str = "", - ) -> tuple[Figure, Axes]: - """Get the matplotlib figure and and ax for one plot.""" - fig, _ = plt.subplots(layout="constrained") - ax = plt.gca() - self._plot_faces(fig, ax, face_plot, faces_cmap_name, face_parameters_name) - self._plot_edges(fig, ax, edge_plot, edges_cmap_name, edges_width, edge_parameters_name) - self._plot_vertices(fig, ax, vertex_plot, vertices_cmap_name, vertices_size, vertex_parameters_name) - - ax.set_title(title) - # unlike the pbc case, here is not easy to know a priori - # what the limits of the progressively optimized cell cluster will be (across a stack of images) - ax.set_xlim(-0.5, self.width + 0.5) - ax.set_ylim(-0.5, self.height + 0.5) - ax.set_aspect((self.height + 1) / (self.width + 1)) - - return fig, ax - - def _plot_faces( - self, fig: Figure, ax: Axes, face_plot: FacePlot, faces_cmap_name: str, face_parameters_name: str - ) -> None: - multicolor_cmap = self._get_multicolor_face_cmap() - faces_cmap = matplotlib.colormaps.get_cmap(faces_cmap_name) - - v_max = 1 - v_min = 0 - values = jnp.array([1]) - # set the correct colorbar if needed - # Get values, min and max - cbar_label = "Face parameter" if face_parameters_name == "" else face_parameters_name - match face_plot: - case FacePlot.FACE_PARAMETER: - values = self.faces_params - case FacePlot.AREA: - values = self.get_area(jnp.arange(self.nb_faces)) - cbar_label = "Area of cell" - case FacePlot.PERIMETER: - values = self.get_perimeter(jnp.arange(self.nb_faces)) - cbar_label = "Perimeter of cell" - case FacePlot.FATES: - values = self.faces[:, 1] - v_max = float(values.max()) - v_min = float(values.min()) - match face_plot: - case FacePlot.MULTICOLOR | FacePlot.WHITE | FacePlot.FATES: - pass - case _: - cbar = add_colorbar(fig, ax, v_min, v_max, faces_cmap) - cbar.ax.set_ylabel(cbar_label, rotation=270, labelpad=13) - cbar.ax.yaxis.set_ticks_position("left") - - # Draw each face - for face in range(len(self.faces)): - match face_plot: - # Find correct color depending on chosen colormap - case FacePlot.MULTICOLOR: - color = multicolor_cmap(face) - case FacePlot.WHITE: - color = (1, 1, 1, 1) - case _: - norm_val = 1 if v_max == v_min else (values[face] - v_min) / (v_max - v_min) - color = faces_cmap(norm_val) - # Find face's vertices and draw the corresponding polygon. - face_vertices = self._get_face_vertices(face) - ax.fill(face_vertices[:, 0], face_vertices[:, 1], color=color) - - def _get_face_vertices(self, face_id: int) -> NDArray: - """Get all vertices positions corresponding to a face.""" - face_vertices = [] - - draw_curve_threshold = 0.01 # radians. Must be above 0 to avoid overcomplicating a simple plot - # one will be 0, other real id offsetted by 2 as the first two values 0 and 1 are reserved - # initialize the loop on every face's edges. - start_he = int(self.faces[face_id][0]) - he = start_he - - # Do the while loop at least one time and add a max iter to "catch" bugs of infinite loop (should not happen !) - do_while = False - max_iter = 1000 - nb_iter = 0 - while not do_while or ((do_while and he != start_he) and nb_iter < max_iter): - do_while = True # Just for the first time - nb_iter += 1 - # Always add origin point - v_source_id = int(self.edges[he][3] + self.edges[he][5] - 2) - pos_source = self.vertices[v_source_id] - - face_vertices.append(pos_source) - ang = float(self.angles[he // 2]) - if self.edges[he][3] != 0 or ang <= draw_curve_threshold: - # means it's an inside edge, we do not need to do anything, - # as the target will be the source of next half-edge. Or the angle is too flat. - pass - else: # means it's an outside edge so it might be drawn as an arc - # one will be 1, other real id offsetted by 2 as the first two values 0 and 1 are reserved - # so real_id + 2 - 1 - v_target_id = int(self.edges[he][4] + self.edges[he][6] - 3) - pos_target = self.vertices[v_target_id] - arc_points = _get_arc_with_n_points(ang, pos_source, pos_target) - face_vertices.extend(arc_points) - - # Next edge for next loop - he = int(self.edges[he][1]) - - face_vertices.append(face_vertices[0]) - return np.array(face_vertices) - - def _get_multicolor_face_cmap(self) -> Callable[[int], tuple[float, float, float, float]]: - def cmap_light_hsv(n: int) -> Callable[[int], tuple[float, float, float, Literal[1]]]: - def light_hsv(i: int) -> tuple[float, float, float, Literal[1]]: - fun: Callable[[int], tuple[float, float, float, float]] = get_cmap(n, name="hsv") - return (*adjust_lightness(fun(i)[:3], 1.4), 1) - - return light_hsv - - return cmap_light_hsv(len(self.faces)) - - def _plot_edges( - self, - fig: Figure, - ax: Axes, - edge_plot: EdgePlot, - edges_cmap_name: str, - edges_width: float, - edge_parameters_name: str, - ) -> None: - if edge_plot != EdgePlot.INVISIBLE: - edge_params_cmap = matplotlib.colormaps.get_cmap(edges_cmap_name) - # set the correct colorbar - - v_max = 1 - v_min = 0 - values = jnp.array([1]) - # Get values, min and max - cbar_label = "Edge parameter" if edge_parameters_name == "" else edge_parameters_name - match edge_plot: - case EdgePlot.EDGE_PARAMETER: - values = self.edges_params - case EdgePlot.LENGTH: - values = self.get_length(jnp.arange(2 * self.nb_edges)) - cbar_label = "Length of edge" - v_max = float(values.max()) - v_min = float(values.min()) - - # set the correct colorbar if needed - match edge_plot: - case EdgePlot.BLACK: - pass - case _: - cbar = add_colorbar(fig, ax, v_min, v_max, edge_params_cmap) - cbar.ax.set_ylabel(cbar_label, rotation=270, labelpad=13) - cbar.ax.yaxis.set_ticks_position("left") - - # Draw each edge - for i in range(len(self.edges)): - he = i - # Find correct color depending on chosen colormap - match edge_plot: - case EdgePlot.BLACK: - color = (0, 0, 0, 1) - case _: - norm_val = 1 if v_max == v_min else (values[he] - v_min) / (v_max - v_min) - color = edge_params_cmap(norm_val) - # Draw the edge with color - self._draw_edge(ax, he, color, edges_width) - - def _draw_edge( - self, - ax: Axes, - he: int, - color: tuple[float, float, float, float] | NDArray, - edges_width: float, - ) -> None: - draw_curve_threshold = 0.01 # radians. Must be above 0 to avoid overcomplicating a simple plot - - v_source_id = int(self.edges[he][3] + self.edges[he][5] - 2) - v_target_id = int(self.edges[he][4] + self.edges[he][6] - 3) - if v_source_id >= 0 and v_target_id >= 0: # else = twin of surface edges - pos_source = self.vertices[v_source_id] - pos_target = self.vertices[v_target_id] - ang = float(self.angles[he // 2]) - - if self.edges[he][3] != 0 or ang <= draw_curve_threshold: - points = np.array([pos_source, pos_target]) - else: - points = np.vstack( - (pos_source, np.array(_get_arc_with_n_points(ang, pos_source, pos_target)), pos_target) - ) - x = points[:, 0] - y = points[:, 1] - ax.plot(x, y, color=color, linewidth=edges_width) - - def _plot_vertices( - self, - fig: Figure, - ax: Axes, - vertex_plot: VertexPlot, - vertices_cmap_name: str, - vertices_size: float, - vertex_parameters_name: str, - ) -> None: - if vertex_plot != VertexPlot.INVISIBLE: - # set the correct colorbar - vertices_params_cmap = matplotlib.colormaps.get_cmap(vertices_cmap_name) - v_max = 1 - v_min = 0 - if vertex_plot == VertexPlot.VERTEX_PARAMETER: - v_max = float(self.vertices_params.max()) - v_min = float(self.vertices_params.min()) - cbar = add_colorbar(fig, ax, v_min, v_max, vertices_params_cmap) - cbar_label = "Vertex parameter" if vertex_parameters_name == "" else vertex_parameters_name - cbar.ax.set_ylabel(cbar_label, rotation=270, labelpad=13) - cbar.ax.yaxis.set_ticks_position("left") - # Draw each vertex - for i, vertex in enumerate(self.vertices): - # Find correct color depending on chosen colormap - match vertex_plot: - case VertexPlot.VERTEX_PARAMETER: - norm_val = 1 if v_max == v_min else (self.vertices_params[i] - v_min) / (v_max - v_min) - color = vertices_params_cmap(norm_val) - case VertexPlot.BLACK: - color = (0, 0, 0, 1) - case _: - color = (0, 0, 0, 0) - # Draw the vertex with color - ax.scatter(vertex[0], vertex[1], color=color, s=vertices_size) - - -def _fate_selection(faceTable: NDArray, n_fates: int, rng: Generator) -> NDArray: - n_cells = faceTable.size - n_cells_per_fate = n_cells // n_fates - n_cells_left = n_cells % n_fates - cell_fates = np.repeat(np.arange(n_fates), n_cells_per_fate) - cell_fates = np.concatenate([cell_fates, np.arange(n_cells_left)]) - rng.shuffle(cell_fates) - return np.hstack([faceTable, cell_fates[:, None]]) - - -def _get_arc_with_n_points(ang: float, pos_source: Array, pos_target: Array, nb_draw_points: int = 40) -> list[NDArray]: - edge_vector = pos_target - pos_source - edge_half_length = np.linalg.norm(edge_vector) / 2 - # shouldn't it be ang/2 ? No, ang is between 0 and pi/2, it's already divided by 2 (if you ask me) - radius = edge_half_length / np.sin(ang) - # diameter = 2 * radius - d_midpoint_to_center = np.sqrt(radius**2 - edge_half_length**2) - midpoint = (pos_source + pos_target) / 2 - unit_vector = edge_vector / np.linalg.norm(edge_vector) - unit_vector_midpoint_to_center = np.array([-unit_vector[1], unit_vector[0]]) - center = midpoint + unit_vector_midpoint_to_center * d_midpoint_to_center - # angles in radians between -pi and pi - ang_source = np.angle(np.dot((pos_source - center), np.array([1, 1j]))) # angle of a + bi - ang_target = np.angle(np.dot((pos_target - center), np.array([1, 1j]))) - # rad2deg = 180 / np.pi - # # angle in degrees between -180 and 180 - # normalized_ang_source_degrees = ang_source * rad2deg - # normalized_ang_target_degrees = ang_target * rad2deg - # # angle in degrees between 0 and 360 - # if normalized_ang_source_degrees < 0.0: - # normalized_ang_source_degrees += 360 - # if normalized_ang_target_degrees < 0.0: - # normalized_ang_target_degrees += 360 - - # # draw the curved edge - # if lines: - # surface_arc = Arc( - # center, - # diameter, - # diameter, - # theta1=normalized_ang_source_degrees, - # theta2=normalized_ang_target_degrees, - # color="black", - # linewidth=2.0, - # ) - # plt.gca().add_patch(surface_arc) - - # Now we'll take nb_draw_points the points along the curved edge and close the figure. - tau = 2 * np.pi - if abs(ang_target - ang_source) > np.pi: # can it happen ? - if ang_source < ang_target: - ang_source += tau - else: - ang_target += tau - intermediate_angles = np.linspace(ang_source, ang_target, nb_draw_points, False)[1:] # without both endpoints - # points = [pos_source] - # points.extend([radius * np.array([np.cos(a), np.sin(a)]) + center for a in intermediate_angles]) - # points.append(pos_target) - # points.append(pos_source) # make it a closed form - # points = np.array(points) - # x, y = points[:, 0], points[:, 1] - return [np.array(radius * np.array([np.cos(a), np.sin(a)]) + center) for a in intermediate_angles] diff --git a/src/vertax/cost.py b/src/vertax/cost.py index 693a3c4..a341427 100644 --- a/src/vertax/cost.py +++ b/src/vertax/cost.py @@ -1,13 +1,57 @@ -"""Cost functions collection.""" +"""Cost functions collection, used for outer optimization. + +A cost function can be any user-defined function but it has to respect a strict signature. + +For a `PbcMesh` and `PbcBilevelOptimizer`, the cost function must have the following signature: + vertTable: Array, + heTable: Array, + faceTable: Array, + width: float, + height: float, + vertTable_target: Array, + heTable_target: Array, + faceTable_target: Array, + selected_verts: Array | None, + selected_hes: Array | None, + selected_faces: Array | None, + image_target: Array | None, +and return an Array (or float). + +The names can vary and you can give default parameters. But the number and type of parameters is important. +You don't have to use every parameters but they all have to be here. +An unused parameters can of course also have the type None. + +Same for `BoundedMesh` and `BoundedBilevelOptimizer`, but with a slightly different signature: + vertTable: Array, + angTable: Array, + heTable: Array, + faceTable: Array, + vertTable_target: Array, + angTable_target: Array, + heTable_target: Array, + faceTable_target: Array, + selected_verts: Array | None, + selected_hes: Array | None, + selected_faces: Array | None, + image_target: Array | None, +and return an Array (or float). + +Hopefully the variable names are self-explanatory. + +You can create a function with this signature exactly that uses also locally-accessible external variable if you want. +""" from functools import partial import jax.numpy as jnp +import numpy as np from jax import Array, jit, vmap from jax.lax import fori_loop from jax.numpy import arange, array, diff, einsum, exp, expand_dims, int32, meshgrid, pi, sqrt, stack from jax.numpy import sinc as npsinc from jax.numpy.fft import ifft2 +from numpy.typing import NDArray +from scipy.optimize import linear_sum_assignment from vertax.geo import get_area @@ -184,7 +228,7 @@ def cost_v2v( selected_faces: Array | None = None, _image_target: Array | None = None, ) -> Array: - """Example of a cost function.""" + """Cost vertex to vertex. Compare the positions of given vertices to target vertices (`PbcMesh`).""" if selected_verts is None: selected_verts = jnp.arange(vertTable.shape[0]) if selected_hes is None: @@ -270,8 +314,9 @@ def cost_mesh2image( _selected_faces: Array, image_target: Array, ) -> Array: - """Example of a cost function.""" - starting = (vertTable[heTable[selected_hes, 3], :2]) * 2 / [width, height] # (M, 2) + """Cost mesh to image. Compare the given vertices positions to a target image (`PbcMesh`).""" + wh = jnp.asarray([width, height]) + starting = (vertTable[heTable[selected_hes, 3], :2]) * 2 / wh # (M, 2) # ending = (vertTable[heTable[selected_hes, 4], :2]) * 2 / L_box # (M, 2) ending = ( ( @@ -283,10 +328,10 @@ def cost_mesh2image( ], axis=-1, ) - * [width, height] + * wh ) * 2 - / [width, height] + / wh ) he_edges = stack((starting, ending), axis=1) # (N, 2, 2) @@ -319,7 +364,7 @@ def cost_areas( faceTable_target: Array, _image_target: Array, ) -> Array: - """Example of a cost function.""" + """Cost areas : compare the areas of the given mesh versus the areas of the target mesh (`PbcMesh`).""" def mapped_fn(f: Array) -> Array: return ( @@ -346,7 +391,7 @@ def cost_ratio( _selected_faces: Array | None = None, _image_target: Array | None = None, ) -> Array: - """Example of a cost function.""" + """Cost that nudges a `BoundedMesh` to elongate along one axis while narrowing along the orthogonal axis.""" # Compute pairwise squared distances by broadcasting # diff shape: (n, n, d) diff = vertTable[:, None, :] - vertTable[None, :, :] @@ -393,7 +438,10 @@ def cost_checkerboard( _selected_faces: Array | None = None, _image_target: Array | None = None, ) -> Array: - """Example of a cost function.""" + """Cost that nudges a `BoundedMesh` with different fated cells to avoid having neighboring cells with same fate. + + This leads to a checkerboard pattern when there is 2 fates. + """ def body_fun(i: int, current_len: Array) -> Array: idx = 2 * i @@ -412,3 +460,431 @@ def body_fun(i: int, current_len: Array) -> Array: n_edges = heTable.shape[0] // 2 return fori_loop(0, n_edges, body_fun, 0.0) + + +@partial(jit, static_argnums=(3, 4)) +def cost_IAS( # noqa: C901, N802 + vertTable: Array, + heTable: Array, + faceTable: Array, + _width: float, + _height: float, + vertTable_target: Array, + heTable_target: Array, + faceTable_target: Array, + _selected_verts: Array | None = None, + _selected_hes: Array | None = None, + _selected_faces: Array | None = None, + _image_target: Array | None = None, +) -> Array: + r"""Differentiable Index Aware Structural Loss. Force to respect the topology. + + C_{IAS}(i,j) = \sqrt{\sum_{k=1}^{N} (S_1(i,k) - S_2(j,k))^2} + """ + L_box = jnp.sqrt(len(faceTable)) + + def l2(x: Array, y: Array) -> Array: + diff = x[:, None, :] - y[None, :, :] + return jnp.sqrt(jnp.sum(diff**2, axis=-1) + 1e-12) + + def mse(x: Array, y: Array) -> Array: + diff = x[:, None, :] - y[None, :, :] + return jnp.sum(diff**2, axis=-1) + 1e-12 + + def sinkhorn(a: Array, b: Array, C: Array, eps: float = 5e-2, n_iters: int = 50) -> Array: + K = jnp.exp(-C / eps) + u = jnp.ones_like(a) + v = jnp.ones_like(b) + + def body(_: int, state: tuple[Array, Array]) -> tuple[Array, Array]: + u, v = state + u = a / (K @ v + 1e-12) + v = b / (K.T @ u + 1e-12) + return (u, v) + + u, v = fori_loop(0, n_iters, body, (u, v)) + return jnp.outer(u, v) * K + + # dual graph from half-edges + def build_dual_adj(heTable: Array, n_faces: int) -> Array: + face = heTable[:, 5] + twin = heTable[:, 2] + + face_twin = face[twin] + + # mask: 1.0 for valid edges, 0.0 for boundary/self + mask = (face != face_twin).astype(jnp.float32) + + A = jnp.zeros((n_faces, n_faces)) + + # scatter with mask weighting (no boolean indexing) + A = A.at[face, face_twin].add(mask) + A = A.at[face_twin, face].add(mask) + + # binarize (avoid double counts) + A = jnp.clip(A, 0.0, 1.0) + + return A + + # structure metric (1-hop + 2-hop) + def structure_matrix(A: Array) -> Array: + # A2 = A @ A + # normalize to avoid scaling issues + # return 2.0 - A - 0.5 * A2 + return 2.0 - A + + def get_face_vertices( + he_start: Array, heTable: Array, vertTable: Array, max_edges: int = 20 + ) -> tuple[Array, Array]: + """Collect vertices of one face using half-edge traversal. + + Returns fixed-size array (max_edges, 2) + mask + """ + verts = jnp.zeros((max_edges, 2)) + mask = jnp.zeros((max_edges,)) + offset = jnp.array([0, 0]) + + def body_fun(i: int, state: tuple[Array, Array, Array, Array]) -> tuple[Array, Array, Array, Array]: + he, verts, mask, offset = state + + source = heTable[he, 3].astype(jnp.int32) + off = heTable[he, 6:8] + + pos = vertTable[source] + offset * L_box # jnp.array([width, height]) + + verts = verts.at[i].set(pos) + mask = mask.at[i].set(1.0) + + offset += off + he_next = heTable[he, 1].astype(jnp.int32) + + return (he_next, verts, mask, offset) + + _he_final, verts, mask, offset = fori_loop(0, max_edges, body_fun, (he_start, verts, mask, offset)) + + return verts, mask + + def polygon_centroid(verts: Array, mask: Array) -> Array: + """Compute centroid from masked polygon vertices.""" + # shift for edges + v = verts + v_next = jnp.roll(v, -1, axis=0) + + cross = v[:, 0] * v_next[:, 1] - v_next[:, 0] * v[:, 1] + cross = cross * mask + + area = jnp.sum(cross) / 2.0 + 1e-12 + + cx = jnp.sum((v[:, 0] + v_next[:, 0]) * cross) / (6 * area) + cy = jnp.sum((v[:, 1] + v_next[:, 1]) * cross) / (6 * area) + + return jnp.array([cx, cy]) + + def compute_face_centroids(faceTable: Array, heTable: Array, vertTable: Array) -> Array: + """Compute centroids for all faces.""" + he_start = faceTable[:].astype(jnp.int32) + + def single_face(he: Array) -> Array: + verts, mask = get_face_vertices(he, heTable, vertTable) + return polygon_centroid(verts, mask) + + return vmap(single_face)(he_start) + + alpha = 0.0 + """ATTENTION !!!!!!!!!!!!""" + + # face centroids + X = compute_face_centroids(faceTable, heTable, vertTable) + Y = compute_face_centroids(faceTable_target, heTable_target, vertTable_target) + + N = X.shape[0] + M = Y.shape[0] + + # uniform weights + a = jnp.ones(N) / N + b = jnp.ones(M) / M + + # geometry cost + C_geom = l2(X, Y) + + # structure cost + A1 = build_dual_adj(heTable, N) + A2 = build_dual_adj(heTable_target, M) + + S1 = structure_matrix(A1) + S2 = structure_matrix(A2) + + # project structure into pairwise node cost + # (cheap approximation of tem) + # C_l2 = l2(S1, S2) + C_mse = mse(S1, S2) + + # combined cost + C_total = alpha * C_geom + (1.0 - alpha) * C_mse + + # transport + gamma = sinkhorn(a, b, C_total) + + # final loss + loss = jnp.sum(gamma * C_total) + + return loss + + +def cost_d_IAS( # noqa: N802 + _vertTable: Array, + heTable: Array, + faceTable: Array, + _width: float, + _height: float, + _vertTable_target: Array, + heTable_target: Array, + faceTable_target: Array, + _selected_verts: Array | None = None, + _selected_hes: Array | None = None, + _selected_faces: Array | None = None, + _image_target: Array | None = None, +) -> int: + r"""Discrete Index Aware Structural loss. Counts mismatched edges. + + C_{d-IAS}(i,j) = \sum_{k=1}^{N} |A_1(i,k) - A_2(j,k)| + """ + + def build_dual_adj_np(heTable: Array, n_faces: int) -> NDArray: + face = heTable[:, 5] + twin = heTable[:, 2] + + face_twin = face[twin] + mask = (face != face_twin).astype(float) + + A = np.zeros((n_faces, n_faces)) + + A[face, face_twin] += mask + A[face_twin, face] += mask + + A = np.clip(A, 0.0, 1.0) + return A + + n1 = len(faceTable) + n2 = len(faceTable_target) + + if n1 != n2: + msg = "This version requires same number of faces" + raise ValueError(msg) + + n = n1 + + A1 = build_dual_adj_np(heTable, n) + A2 = build_dual_adj_np(heTable_target, n) + + # --- Step 1: node matching via Hungarian --- + # cost between nodes = L1 difference of adjacency rows + C = np.sum(np.abs(A1[:, None, :] - A2[None, :, :]), axis=-1) + + row_ind, col_ind = linear_sum_assignment(C) + + # permutation: face i in A1 ↔ face col_ind[i] in A2 + P = np.zeros((n, n)) + P[row_ind, col_ind] = 1.0 + + # --- Step 2: permute A2 --- + A2_perm = P @ A2 @ P.T + + # --- Step 3: count edge mismatches --- + diff = np.abs(A1 - A2_perm) + + # count each edge once + diff_upper = np.triu(diff, k=1) + + loss = int(np.sum(diff_upper)) + + return loss + + +@partial(jit, static_argnums=(3, 4)) +def cost_tem_halfedge( + vertTable: Array, + heTable: Array, + faceTable: Array, + width: float, + height: float, + vertTable_target: Array, + heTable_target: Array, + faceTable_target: Array, + _selected_verts: Array | None = None, + _selected_hes: Array | None = None, + _selected_faces: Array | None = None, + _image_target: Array | None = None, +) -> Array: + """TEM-inspired loss using half-edge dual graph (cells).""" + + # ---------- helpers ---------- + def pairwise_periodic_distances(x: Array, y: Array) -> Array: + dx = x[:, None, 0] - y[None, :, 0] + dy = x[:, None, 1] - y[None, :, 1] + + dx = jnp.minimum(jnp.abs(dx), width - jnp.abs(dx)) + dy = jnp.minimum(jnp.abs(dy), height - jnp.abs(dy)) + + return jnp.sqrt(dx**2 + dy**2 + 1e-12) + + def sinkhorn(a: Array, b: Array, C: Array, eps: float = 5e-2, n_iters: int = 50) -> Array: + K = jnp.exp(-C / eps) + u = jnp.ones_like(a) + v = jnp.ones_like(b) + + def body(_: int, state: tuple[Array, Array]) -> tuple[Array, Array]: + u, v = state + u = a / (K @ v + 1e-12) + v = b / (K.T @ u + 1e-12) + return (u, v) + + u, v = fori_loop(0, n_iters, body, (u, v)) + return jnp.outer(u, v) * K + + # ---------- dual graph ---------- + def build_dual_adj(heTable: Array, n_faces: int) -> Array: + face = heTable[:, 5].astype(jnp.int32) + twin = heTable[:, 2].astype(jnp.int32) + + face_twin = face[twin] + + valid = face != face_twin + keep = valid & (face < face_twin) + + weight = keep.astype(jnp.float32) + + A = jnp.zeros((n_faces, n_faces)) + + A = A.at[face, face_twin].add(weight) + A = A.at[face_twin, face].add(weight) + + return A + + def structure_matrix(A: Array) -> Array: + # A2 = A @ A + # return 2.0 - A - 0.5 * A2 + return 2.0 - A + + # ---------- centroids ---------- + # faceTable stores a half-edge index → get its source vertex + he_idx = faceTable[:, 0].astype(jnp.int32) + + X = vertTable[heTable[he_idx, 3].astype(jnp.int32), :2] + Y = vertTable_target[heTable_target[faceTable_target[:, 0].astype(jnp.int32), 3].astype(jnp.int32), :2] + + N = X.shape[0] + M = Y.shape[0] + + a = jnp.ones(N) / N + b = jnp.ones(M) / M + + # ---------- costs ---------- + C_geom = pairwise_periodic_distances(X, Y) + + A1 = build_dual_adj(heTable, N) + A2 = build_dual_adj(heTable_target, M) + + S1 = structure_matrix(A1) + S2 = structure_matrix(A2) + + # structure comparison (non-periodic, abstract space) + diff = S1[:, None, :] - S2[None, :, :] + C_struct = jnp.sqrt(jnp.sum(diff**2, axis=-1) + 1e-12) + + alpha = 0.5 + C_total = alpha * C_geom + (1.0 - alpha) * C_struct + + gamma = sinkhorn(a, b, C_total) + + return jnp.sum(gamma * C_total) + + +@partial(jit, static_argnums=(3, 4)) +def cost_v2v_tem( + vertTable: Array, + heTable: Array, + faceTable: Array, + width: float, + height: float, + vertTable_target: Array, + heTable_target: Array, + faceTable_target: Array, + _selected_verts: Array | None = None, + _selected_hes: Array | None = None, + _selected_faces: Array | None = None, + _image_target: Array | None = None, +) -> Array: + """Mix of cost_v2v and cost_tem_halfedge (with respective weights 0.99 and 0.01).""" + return 0.99 * cost_v2v( + vertTable, + heTable, + faceTable, + width, + height, + vertTable_target, + heTable_target, + faceTable_target, + selected_verts=None, + selected_hes=None, + selected_faces=None, + image_target=None, + ) + 0.01 * cost_tem_halfedge( + vertTable, + heTable, + faceTable, + width, + height, + vertTable_target, + heTable_target, + faceTable_target, + selected_verts=None, + selected_hes=None, + selected_faces=None, + image_target=None, + ) + + +@partial(jit, static_argnums=(3, 4)) +def cost_v2v_ias( + vertTable: Array, + heTable: Array, + faceTable: Array, + width: float, + height: float, + vertTable_target: Array, + heTable_target: Array, + faceTable_target: Array, + _selected_verts: Array | None = None, + _selected_hes: Array | None = None, + _selected_faces: Array | None = None, + _image_target: Array | None = None, +) -> Array: + """Mix of cost_v2v and cost_IAS (with weight 0.6 and 0.4).""" + return 0.6 * cost_v2v( + vertTable, + heTable, + faceTable, + width, + height, + vertTable_target, + heTable_target, + faceTable_target, + selected_verts=None, + selected_hes=None, + selected_faces=None, + _image_target=None, + ) + 0.4 * cost_IAS( + vertTable, + heTable, + faceTable, + width, + height, + vertTable_target, + heTable_target, + faceTable_target, + selected_verts=None, + selected_hes=None, + selected_faces=None, + image_target=None, + ) diff --git a/src/vertax/energy.py b/src/vertax/energy.py index 8c67d61..bc35ccd 100644 --- a/src/vertax/energy.py +++ b/src/vertax/energy.py @@ -1,4 +1,37 @@ -"""Energy related functions.""" +"""Energy related functions, used for the inner optimization. + +They can be user defined functions but they have to respect a strict signature. + +For a `PbcMesh` and `PbcBilevelOptimizer`, the energy function must have the following signature: + vertTable: Array, + heTable: Array, + faceTable: Array, + vert_params: Array, + he_params: Array, + face_params: Array, +and return an Array (or float). + +The names can vary and you can give default parameters. But the number and type of parameters is important. +You don't have to use every parameters but they all have to be here. +An unused parameters can of course also have the type None. + +Same for `BoundedMesh` and `BoundedBilevelOptimizer`, but with a slightly different signature: + vertTable: Array, + angTable: Array, + heTable: Array, + faceTable: Array, + selected_verts: Array | None, + selected_hes: Array | None, + selected_faces: Array | None, + vert_params: Array, + he_params: Array, + face_params: Array, +and return an Array (or float). + +Hopefully the variable names are self-explanatory. + +You can create a function with this signature exactly that uses also locally-accessible external variable if you want. +""" import jax import jax.numpy as jnp @@ -11,10 +44,10 @@ MAX_EDGES_IN_ANY_FACE = 20 -def cell_energy( +def _cell_energy( face: Array, face_param: Array, vertTable: Array, heTable: Array, faceTable: Array, width: float, height: float ) -> Array: - """Example of a cell energy function.""" + """E1 energy for `PbcMesh` for a given face. Elastic term on cell areas and shape factors.""" area = get_area(face, vertTable, heTable, faceTable, width, height, MAX_EDGES_IN_ANY_FACE) perimeter = get_perimeter(face, vertTable, heTable, faceTable, width, height, MAX_EDGES_IN_ANY_FACE) return ((area - 1) ** 2) + ((perimeter - face_param) ** 2) @@ -28,10 +61,10 @@ def energy_shape_factor_homo( height: float, face_params: Array, ) -> Array: - """Example of an energy function.""" + """E1 energy where the shape factor is uniform (give only one face_params, it will be broadcasted).""" def mapped_fn(face: Array, param: Array) -> Array: - return cell_energy(face, param, vertTable, heTable, faceTable, width, height) + return _cell_energy(face, param, vertTable, heTable, faceTable, width, height) face_params_broadcasted = jnp.broadcast_to(face_params, (len(faceTable), *face_params.shape[1:])) cell_energies = vmap(mapped_fn)(jnp.arange(len(faceTable)), face_params_broadcasted) @@ -47,17 +80,17 @@ def energy_shape_factor_hetero( selected_faces: Array, face_params: Array, ) -> Array: - """Example of an energy function.""" + """E1 energy where the shape factor depends on the cell.""" def mapped_fn(face: Array, param: Array) -> Array: - return cell_energy(face, param, vertTable, heTable, faceTable, width, height) + return _cell_energy(face, param, vertTable, heTable, faceTable, width, height) cell_energies = vmap(mapped_fn)(selected_faces, face_params[selected_faces]) # cell_energies = vmap(mapped_fn)(jnp.arange(len(faceTable)), face_params) return jnp.sum(cell_energies) -def area_part( +def _area_part( face: Array, face_param: Array, vertTable: Array, heTable: Array, faceTable: Array, width: float, height: float ) -> Array: """Part of an energy function.""" @@ -65,7 +98,7 @@ def area_part( return (a - face_param) ** 2 -def hedge_part( +def _hedge_part( he: Array, he_param: Array, vertTable: Array, heTable: Array, faceTable: Array, width: float, height: float ) -> Array: """Part of an energy function.""" @@ -82,14 +115,14 @@ def energy_line_tensions( he_params: Array, face_params: Array, ) -> Array: - """Example of an energy function.""" + """E2 energy for PBC meshes, elastic penalty on cell areas and line tension term weighted by edge lengths.""" K_areas = 20 def mapped_areas_part(face: Array, face_param: Array) -> Array: - return area_part(face, face_param, vertTable, heTable, faceTable, width, height) + return _area_part(face, face_param, vertTable, heTable, faceTable, width, height) def mapped_hedges_part(he: Array, he_param: Array) -> Array: - return hedge_part(he, he_param, vertTable, heTable, faceTable, width, height) + return _hedge_part(he, he_param, vertTable, heTable, faceTable, width, height) areas_part = vmap(mapped_areas_part)(jnp.arange(len(faceTable)), face_params) hedges_part = vmap(mapped_hedges_part)(jnp.arange(len(heTable)), he_params) @@ -102,28 +135,28 @@ def mapped_hedges_part(he: Array, he_param: Array) -> Array: # Bounded # ========== @jit -def cell_area_energy(face: Array, vertTable: Array, angTable: Array, heTable: Array, faceTable: Array) -> Array: +def _cell_area_energy(face: Array, vertTable: Array, angTable: Array, heTable: Array, faceTable: Array) -> Array: """Part of an energy function.""" area = get_area_bounded(face, vertTable, angTable, heTable, faceTable) return (area - TARGET_AREA) ** 2 @jit -def surface_edge_energy(edge: Array, tension: Array, vertTable: Array, angTable: Array, heTable: Array) -> Array: +def _surface_edge_energy(edge: Array, tension: Array, vertTable: Array, angTable: Array, heTable: Array) -> Array: """Part of an energy function.""" length = get_surface_length(edge, vertTable, angTable, heTable) return length * tension @jit -def inner_edge_energy(edge: Array, tension: Array, vertTable: Array, heTable: Array) -> Array: +def _inner_edge_energy(edge: Array, tension: Array, vertTable: Array, heTable: Array) -> Array: """Part of an energy function.""" length = get_edge_length(edge, vertTable, heTable) return length * tension @jit -def energy_bounded( +def energy_line_tensions_bounded( vertTable: Array, angTable: Array, heTable: Array, @@ -135,7 +168,7 @@ def energy_bounded( he_params: Array, _face_params: Array, ) -> Array: - """Base energy function for bounded meshes.""" + """E2 energy for bounded meshes, elastic penalty on cell areas and line tension term weighted by edge lengths.""" num_faces = faceTable.shape[0] faces = jnp.arange(num_faces) num_edges = angTable.size @@ -147,17 +180,17 @@ def energy_bounded( he_params = jax.nn.sigmoid(he_params) + 1 def mapped_fn_area(face: Array) -> Array: - return cell_area_energy(face, vertTable, angTable, heTable, faceTable) + return _cell_area_energy(face, vertTable, angTable, heTable, faceTable) cell_area_energies = jnp.sum(vmap(mapped_fn_area)(faces)) def mapped_fn_inner(edge: Array, tension: Array) -> Array: - return inner_edge_energy(edge, tension, vertTable, heTable) + return _inner_edge_energy(edge, tension, vertTable, heTable) inner_edge_energies = jnp.sum(vmap(mapped_fn_inner)(unique_edges, he_params)) def mapped_fn_surface(edge: Array, tension: Array) -> Array: - return surface_edge_energy(edge, tension, vertTable, angTable, heTable) + return _surface_edge_energy(edge, tension, vertTable, angTable, heTable) surface_edge_energies = jnp.sum(vmap(mapped_fn_surface)(edges, jnp.repeat(he_params, 2))) return 20 * cell_area_energies + inner_edge_energies + surface_edge_energies diff --git a/src/vertax/mesh.py b/src/vertax/mesh.py deleted file mode 100644 index 0981735..0000000 --- a/src/vertax/mesh.py +++ /dev/null @@ -1,89 +0,0 @@ -"""Abstract mesh module.""" - -from __future__ import annotations - -from typing import Any, NoReturn, TypeVar - -import jax.numpy as jnp -import optax -from jax import Array - -from vertax.method_enum import BilevelOptimizationMethod - -T = TypeVar("T") - - -class NoPublicConstructor(type): - """Metaclass that ensures a private constructor. - - If a class uses this metaclass like this: - - class SomeClass(metaclass=NoPublicConstructor): - pass - - If you try to instantiate your class (`SomeClass()`), - a `TypeError` will be thrown. - """ - - def __call__(cls, *args, **kwargs) -> NoReturn: # noqa - """Make it impossible to call with ClassName().""" - msg = f"{cls.__module__}.{cls.__qualname__} has no public constructor" - raise TypeError(msg) - - def _create(cls: type[T], *args: Any, **kwargs: Any) -> T: # noqa: ANN401 - return super().__call__(*args, **kwargs) # type: ignore - - -class Mesh(metaclass=NoPublicConstructor): - """Generic mesh structure.""" - - def __init__(self) -> None: - """Do nothing but create attributes. Do not call this.""" - self.vertices: Array = jnp.array([]) - self.edges: Array = jnp.array([]) - self.faces: Array = jnp.array([]) - self.width: float = 0 - self.height: float = 0 - - self.vertices_params: Array = jnp.array([]) - self.edges_params: Array = jnp.array([]) - self.faces_params: Array = jnp.array([]) - - self.vertices_target: Array = jnp.array([]) - self.edges_target: Array = jnp.array([]) - self.faces_target: Array = jnp.array([]) - - self.image_target: Array = jnp.array([]) - - self.bilevel_optimization_method: BilevelOptimizationMethod = ( - BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION - ) - self.beta: float = 0.01 - - self.min_dist_T1: float = 0.005 - self.max_nb_iterations: int = 1000 - self.tolerance: float = 1e-4 - self.patience: int = 5 - - self.inner_solver: optax.GradientTransformation = optax.sgd(learning_rate=0.01) - self.outer_solver: optax.GradientTransformation = optax.adam(learning_rate=0.0001, nesterov=True) - - @property - def nb_vertices(self) -> int: - """Get the number of vertices of the mesh.""" - return len(self.vertices) - - @property - def nb_edges(self) -> int: - """Get the number of edges of the mesh.""" - return self.nb_half_edges // 2 - - @property - def nb_half_edges(self) -> int: - """Get the number of half-edges of the mesh.""" - return len(self.edges) - - @property - def nb_faces(self) -> int: - """Get the number of faces of the mesh.""" - return len(self.faces) diff --git a/src/vertax/meshes/bounded_mesh.py b/src/vertax/meshes/bounded_mesh.py index 9a496d1..ea2cd54 100644 --- a/src/vertax/meshes/bounded_mesh.py +++ b/src/vertax/meshes/bounded_mesh.py @@ -1,4 +1,4 @@ -"""Bounded mesh with arc circles for boundary cells.""" +"""Bounded mesh are useful to represent finite tissue clusters with curved interfaces.""" from __future__ import annotations @@ -22,212 +22,42 @@ class BoundedMesh(Mesh): - """Bounded mesh with arc circles for boundary cells.""" + """Bounded mesh with arc circles for boundary cells. - def __init__(self) -> None: - """Do not call the constructor.""" - super().__init__() - self.angles: Array = jnp.array([]) - - def save_mesh_txt( - self, - directory: str, - vertices_filename: str = "vertTable.txt", - angles_filename: str = "angTable.txt", - edges_filename: str = "heTable.txt", - faces_filename: str = "faceTable.txt", - vertices_params_filename: str = "vertParamsTable.txt", - edges_params_filename: str = "heParamsTable.txt", - faces_params_filename: str = "faceParamsTable.txt", - constants_filename: str = "constants.txt", - ) -> None: - """Save a mesh in separate text files that can be read by numpy. - - Only save the vertices, angles, edges and faces, not other parameters. - - Args: - directory (str): Path to the directory where to save the files. - vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt". - angles_filename (str, optional): Filename for the angles table. Defaults to "angTable.txt". - edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt". - faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt". - vertices_params_filename (str, optional): Filename for the vertices parameters table. - Defaults to "vertParamsTable.txt". - edges_params_filename (str, optional): Filename for the half-edges parameters table. - Defaults to "heParamsTable.txt". - faces_params_filename (str, optional): Filename for the faces parameters table. - Defaults to "faceParamsTable.txt". - constants_filename (str, optional): Filename for width/height. - Defaults to "constants.txt". - """ - dirpath = Path(directory) - dirpath.mkdir(parents=True, exist_ok=True) - np.savetxt(dirpath / vertices_filename, self.vertices) - np.savetxt(dirpath / angles_filename, self.angles) - np.savetxt(dirpath / edges_filename, self.edges) - np.savetxt(dirpath / faces_filename, self.faces) - np.savetxt(dirpath / vertices_params_filename, self.vertices_params) - np.savetxt(dirpath / edges_params_filename, self.edges_params) - np.savetxt(dirpath / faces_params_filename, self.faces_params) - with (dirpath / constants_filename).open("w") as f: - f.write(f"{self.width} {self.height}") - - def save_mesh(self, path: str) -> None: - """Save mesh to a file. - - All BoundedMesh data is saved. - - Args: - path (str): Path to the saved file. The extension is .npz. - """ - Path(path).parent.mkdir(parents=True, exist_ok=True) - np.savez_compressed( - path, - allow_pickle=False, - vertices=self.vertices, - edges=self.edges, - faces=self.faces, - angles=self.angles, - width=self.width, - height=self.height, - vertices_params=self.vertices_params, - edges_params=self.edges_params, - faces_params=self.faces_params, - ) - - @classmethod - def load_mesh(cls, path: str) -> Self: - """Load a mesh from a file. - - All BoundedMesh data is reloaded. - - Args: - path (str): Path to the mesh file (.npz). - - Returns: - Mesh: the mesh loaded from the .npz file. - """ - mesh_file = np.load(path) - mesh = cls._create() - mesh.vertices, mesh.edges, mesh.faces, mesh.angles = ( - mesh_file["vertices"], - mesh_file["edges"], - mesh_file["faces"], - mesh_file["angles"], - ) - mesh.width = mesh_file["width"] - mesh.height = mesh_file["height"] - mesh.vertices_params = mesh_file["vertices_params"] - mesh.edges_params = mesh_file["edges_params"] - mesh.faces_params = mesh_file["faces_params"] - return mesh + For a BoundedMesh, `vertices` is a 2D array of floats of size (nb_vertices, 2) ; + with the coordinates of the vertices (in ]0, width[ x ]0, height[ ). - @classmethod - def load_mesh_txt( - cls, - directory: str, - vertices_filename: str = "vertTable.txt", - angles_filename: str = "angTable.txt", - edges_filename: str = "heTable.txt", - faces_filename: str = "faceTable.txt", - vertices_params_filename: str = "vertParamsTable.txt", - edges_params_filename: str = "heParamsTable.txt", - faces_params_filename: str = "faceParamsTable.txt", - constants_filename: str = "constants.txt", - ) -> Self: - """Load a mesh from text files. + `edges` is a 2D array of integers of size (nb_half_edges, 8), with: - Only load the vertices, angles, edges and faces, not other parameters. + - id of previous half-edge, + - id of next half-edge, + - id of twin half-edge, + - id of source vertex + 2 if current half-edge is an inside edge, else 0, + - id of target vertex + 2 if current half-edge is an inside edge, else 1, + - id of source vertex + 2 if current half-edge is an outside edge, else 0, + - id of target vertex + 2 if current half-edge is an outside edge, else 1, + - id of the face containing the half-edge. - Args: - directory (str): Directory where the text files are stored. - vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt". - angles_filename (str, optional): Filename for the angles table. Defaults to "angTable.txt". - edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt". - faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt". - vertices_params_filename (str, optional): Filename for the vertices parameters table. - Defaults to "vertParamsTable.txt". - edges_params_filename (str, optional): Filename for the half-edges parameters table. - Defaults to "heParamsTable.txt". - faces_params_filename (str, optional): Filename for the faces parameters table. - Defaults to "faceParamsTable.txt". - constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE. - Defaults to "constants.txt". - - Returns: - Self: The loaded mesh. - """ - dirpath = Path(directory) - dirpath.mkdir(parents=True, exist_ok=True) + `faces` is a 1D array of integers of size (nb_faces) containing the id of a half-edge belonging to this face. - mesh = cls._create() - mesh.vertices = jnp.array(np.loadtxt(dirpath / vertices_filename, dtype=np.float64)) - mesh.angles = jnp.array(np.loadtxt(dirpath / angles_filename, dtype=np.float64)) - mesh.edges = jnp.array(np.loadtxt(dirpath / edges_filename, dtype=np.int64)) - mesh.faces = jnp.array(np.loadtxt(dirpath / faces_filename, dtype=np.int64)) - mesh.vertices_params = jnp.array(np.loadtxt(dirpath / vertices_params_filename, dtype=np.float64)) - mesh.edges_params = jnp.array(np.loadtxt(dirpath / edges_params_filename, dtype=np.int64)) - mesh.faces_params = jnp.array(np.loadtxt(dirpath / faces_params_filename, dtype=np.int64)) - with (dirpath / constants_filename).open("r") as f: - numbers = f.readline().split() - mesh.width = float(numbers[0]) - mesh.height = float(numbers[1]) - return mesh + `angles` is a 1D array of floats of size (nb_angles), with the angles sustaining the arcs of the free interfaces ; + between 0 and PI / 2. + """ - @classmethod - def copy_mesh(cls, other_mesh: Self) -> Self: - """Copy all parameters from another mesh in a new mesh.""" - mesh = cls._create() - mesh.vertices = other_mesh.vertices.copy() - mesh.edges = other_mesh.edges.copy() - mesh.faces = other_mesh.faces.copy() - mesh.angles = other_mesh.angles.copy() - mesh.width = other_mesh.width - mesh.height = other_mesh.height - mesh.vertices_params = other_mesh.vertices_params.copy() - mesh.edges_params = other_mesh.edges_params.copy() - mesh.faces_params = other_mesh.faces_params.copy() - - return mesh + def __init__(self) -> None: + """Do not call the constructor.""" + super().__init__() + self.angles: Array = jnp.array([]) + """Angle sustaining the arced free interfaces. Between 0 and PI / 2.""" @property def nb_angles(self) -> int: - """Get the number of angles of the mesh.""" + """Get the number of angles (free interfaces) of the mesh.""" return len(self.angles) - def get_length(self, half_edge_id: Array) -> Array: - """Get the length of an edge.""" - vertTable = jnp.vstack([jnp.array([[0.0, 0.0], [1.0, 1.0]]), self.vertices]) - angTable = jnp.repeat(self.angles, 2) - - def _get_length(half_edge_id: Array) -> Array: - return get_any_length(half_edge_id, vertTable, angTable, self.edges) - - return jax.vmap(_get_length)(half_edge_id) - - def get_perimeter(self, face_id: Array) -> Array: - """Get the area of a face.""" - vertTable = jnp.vstack([jnp.array([[0.0, 0.0], [1.0, 1.0]]), self.vertices]) - angTable = jnp.repeat(self.angles, 2) - - def _get_perimeter(face_id: Array) -> Array: - return get_perimeter_bounded(face_id, vertTable, angTable, self.edges, self.faces) - - return jax.vmap(_get_perimeter)(face_id) - - def get_area(self, face_id: Array) -> Array: - """Get the area of a face.""" - vertTable = jnp.vstack([jnp.array([[0.0, 0.0], [1.0, 1.0]]), self.vertices]) - angTable = jnp.repeat(self.angles, 2) - - def _get_area(face_id: Array) -> Array: - return get_area_bounded(face_id, vertTable, angTable, self.edges, self.faces) - - return jax.vmap(_get_area)(face_id) - @classmethod def from_random_seeds(cls, nb_seeds: int, width: float, height: float, random_key: int, nb_fates: int = 2) -> Self: - """Create a bounded Mesh from random seeds. + """Create a bounded Mesh from random seeds, based on a Voronoi diagram with arced free interfaces. Args: nb_seeds (int): Number of random seeds to use. @@ -245,7 +75,7 @@ def from_random_seeds(cls, nb_seeds: int, width: float, height: float, random_ke @classmethod def from_seeds(cls, seeds: NDArray, width: float, height: float, random_key: int, nb_fates: int = 2) -> Self: # noqa: C901 - """Create a bounded Mesh from a list of seeds. + """Create a bounded Mesh from a list of given seeds. The seeds are assumed to have x-coordinate in ]0, width[ and y-coordinate in ]0, height[. Note that the final mesh might not use your seeds if they don't work to create a correct @@ -485,6 +315,202 @@ def from_seeds(cls, seeds: NDArray, width: float, height: float, random_key: int else (width, height) * rng.random((n_cells, 2)) ) # type: ignore + @classmethod + def create_empty(cls) -> Self: + """Create an empty mesh. Use if you know what you're doing !""" + return cls._create() + + @classmethod + def copy_mesh(cls, other_mesh: Self) -> Self: + """Copy all parameters from another mesh in a new mesh.""" + mesh = cls._create() + mesh.vertices = other_mesh.vertices.copy() + mesh.edges = other_mesh.edges.copy() + mesh.faces = other_mesh.faces.copy() + mesh.angles = other_mesh.angles.copy() + mesh.width = other_mesh.width + mesh.height = other_mesh.height + mesh.vertices_params = other_mesh.vertices_params.copy() + mesh.edges_params = other_mesh.edges_params.copy() + mesh.faces_params = other_mesh.faces_params.copy() + + return mesh + + def save_mesh(self, path: str) -> None: + """Save mesh to a file. + + All BoundedMesh data is saved. + + Args: + path (str): Path to the saved file. The extension is .npz. + """ + Path(path).parent.mkdir(parents=True, exist_ok=True) + np.savez_compressed( + path, + allow_pickle=False, + vertices=self.vertices, + edges=self.edges, + faces=self.faces, + angles=self.angles, + width=self.width, + height=self.height, + vertices_params=self.vertices_params, + edges_params=self.edges_params, + faces_params=self.faces_params, + ) + + @classmethod + def load_mesh(cls, path: str) -> Self: + """Load a mesh from a file. + + All BoundedMesh data is reloaded. + + Args: + path (str): Path to the mesh file (.npz). + + Returns: + Mesh: the mesh loaded from the .npz file. + """ + mesh_file = np.load(path) + mesh = cls._create() + mesh.vertices, mesh.edges, mesh.faces, mesh.angles = ( + mesh_file["vertices"], + mesh_file["edges"], + mesh_file["faces"], + mesh_file["angles"], + ) + mesh.width = mesh_file["width"] + mesh.height = mesh_file["height"] + mesh.vertices_params = mesh_file["vertices_params"] + mesh.edges_params = mesh_file["edges_params"] + mesh.faces_params = mesh_file["faces_params"] + return mesh + + def save_mesh_txt( + self, + directory: str, + vertices_filename: str = "vertTable.txt", + angles_filename: str = "angTable.txt", + edges_filename: str = "heTable.txt", + faces_filename: str = "faceTable.txt", + vertices_params_filename: str = "vertParamsTable.txt", + edges_params_filename: str = "heParamsTable.txt", + faces_params_filename: str = "faceParamsTable.txt", + constants_filename: str = "constants.txt", + ) -> None: + """Save a mesh in separate text files that can be read by numpy. + + Only save the vertices, angles, edges and faces, not other parameters. + + Args: + directory (str): Path to the directory where to save the files. + vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt". + angles_filename (str, optional): Filename for the angles table. Defaults to "angTable.txt". + edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt". + faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt". + vertices_params_filename (str, optional): Filename for the vertices parameters table. + Defaults to "vertParamsTable.txt". + edges_params_filename (str, optional): Filename for the half-edges parameters table. + Defaults to "heParamsTable.txt". + faces_params_filename (str, optional): Filename for the faces parameters table. + Defaults to "faceParamsTable.txt". + constants_filename (str, optional): Filename for width/height. + Defaults to "constants.txt". + """ + dirpath = Path(directory) + dirpath.mkdir(parents=True, exist_ok=True) + np.savetxt(dirpath / vertices_filename, self.vertices) + np.savetxt(dirpath / angles_filename, self.angles) + np.savetxt(dirpath / edges_filename, self.edges) + np.savetxt(dirpath / faces_filename, self.faces) + np.savetxt(dirpath / vertices_params_filename, self.vertices_params) + np.savetxt(dirpath / edges_params_filename, self.edges_params) + np.savetxt(dirpath / faces_params_filename, self.faces_params) + with (dirpath / constants_filename).open("w") as f: + f.write(f"{self.width} {self.height}") + + @classmethod + def load_mesh_txt( + cls, + directory: str, + vertices_filename: str = "vertTable.txt", + angles_filename: str = "angTable.txt", + edges_filename: str = "heTable.txt", + faces_filename: str = "faceTable.txt", + vertices_params_filename: str = "vertParamsTable.txt", + edges_params_filename: str = "heParamsTable.txt", + faces_params_filename: str = "faceParamsTable.txt", + constants_filename: str = "constants.txt", + ) -> Self: + """Load a mesh from text files. + + Only load the vertices, angles, edges and faces, not other parameters. + + Args: + directory (str): Directory where the text files are stored. + vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt". + angles_filename (str, optional): Filename for the angles table. Defaults to "angTable.txt". + edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt". + faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt". + vertices_params_filename (str, optional): Filename for the vertices parameters table. + Defaults to "vertParamsTable.txt". + edges_params_filename (str, optional): Filename for the half-edges parameters table. + Defaults to "heParamsTable.txt". + faces_params_filename (str, optional): Filename for the faces parameters table. + Defaults to "faceParamsTable.txt". + constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE. + Defaults to "constants.txt". + + Returns: + Self: The loaded mesh. + """ + dirpath = Path(directory) + dirpath.mkdir(parents=True, exist_ok=True) + + mesh = cls._create() + mesh.vertices = jnp.array(np.loadtxt(dirpath / vertices_filename, dtype=np.float64)) + mesh.angles = jnp.array(np.loadtxt(dirpath / angles_filename, dtype=np.float64)) + mesh.edges = jnp.array(np.loadtxt(dirpath / edges_filename, dtype=np.int64)) + mesh.faces = jnp.array(np.loadtxt(dirpath / faces_filename, dtype=np.int64)) + mesh.vertices_params = jnp.array(np.loadtxt(dirpath / vertices_params_filename, dtype=np.float64)) + mesh.edges_params = jnp.array(np.loadtxt(dirpath / edges_params_filename, dtype=np.int64)) + mesh.faces_params = jnp.array(np.loadtxt(dirpath / faces_params_filename, dtype=np.int64)) + with (dirpath / constants_filename).open("r") as f: + numbers = f.readline().split() + mesh.width = float(numbers[0]) + mesh.height = float(numbers[1]) + return mesh + + def get_length(self, half_edge_id: Array) -> Array: + """Get the length of an edge.""" + vertTable = jnp.vstack([jnp.array([[0.0, 0.0], [1.0, 1.0]]), self.vertices]) + angTable = jnp.repeat(self.angles, 2) + + def _get_length(half_edge_id: Array) -> Array: + return get_any_length(half_edge_id, vertTable, angTable, self.edges) + + return jax.vmap(_get_length)(half_edge_id) + + def get_perimeter(self, face_id: Array) -> Array: + """Get the area of a face.""" + vertTable = jnp.vstack([jnp.array([[0.0, 0.0], [1.0, 1.0]]), self.vertices]) + angTable = jnp.repeat(self.angles, 2) + + def _get_perimeter(face_id: Array) -> Array: + return get_perimeter_bounded(face_id, vertTable, angTable, self.edges, self.faces) + + return jax.vmap(_get_perimeter)(face_id) + + def get_area(self, face_id: Array) -> Array: + """Get the area of a face.""" + vertTable = jnp.vstack([jnp.array([[0.0, 0.0], [1.0, 1.0]]), self.vertices]) + angTable = jnp.repeat(self.angles, 2) + + def _get_area(face_id: Array) -> Array: + return get_area_bounded(face_id, vertTable, angTable, self.edges, self.faces) + + return jax.vmap(_get_area)(face_id) + def _fate_selection(faceTable: NDArray, n_fates: int, rng: Generator) -> NDArray: n_cells = faceTable.size diff --git a/src/vertax/meshes/mesh.py b/src/vertax/meshes/mesh.py index 985a605..5ff6091 100644 --- a/src/vertax/meshes/mesh.py +++ b/src/vertax/meshes/mesh.py @@ -34,24 +34,60 @@ def _create(cls: type[T], *args: Any, **kwargs: Any) -> T: # noqa: ANN401 class Mesh(metaclass=NoPublicConstructor): - """Generic mesh structure.""" + """Generic mesh structure. It is an abstract base class, not to be used directly. + + It defines common attributes and functions between `PbcMesh` and `BoundedMesh`. + """ def __init__(self) -> None: - """Do nothing but create attributes. Do not call this.""" - self.vertices: Array = jnp.array([]) - self.edges: Array = jnp.array([]) + """Do nothing but create attributes. Do not call this, but call specialized class methods to create meshes. + + See `PbcMesh` and `BoundedMesh`. + + Technically, it uses a DCEL structure. + """ self.faces: Array = jnp.array([]) + """The cells of the tissue.""" + self.edges: Array = jnp.array([]) + """The interface between cells. Technically half-edges.""" + self.vertices: Array = jnp.array([]) + """The mesh vertices, where cells meet.""" self.width: float = 0 + """The mesh live in a rectangle of size [0, width] in the X direction.""" self.height: float = 0 + """The mesh live in a rectangle of size [0, height] in the Y direction.""" - self.vertices_params: Array = jnp.array([]) - self.edges_params: Array = jnp.array([]) self.faces_params: Array = jnp.array([]) + """Parameters attached to faces. Can be optimized.""" + self.edges_params: Array = jnp.array([]) + """Parameters attached to edges. Can be optimized.""" + self.vertices_params: Array = jnp.array([]) + """Parameters attached to vertices. Can be optimized.""" + + @property + def nb_faces(self) -> int: + """Get the number of faces of the mesh.""" + return len(self.faces) + + @property + def nb_edges(self) -> int: + """Get the number of edges of the mesh.""" + return self.nb_half_edges // 2 + + @property + def nb_half_edges(self) -> int: + """Get the number of half-edges of the mesh, ie. twice the number of edges.""" + return len(self.edges) + + @property + def nb_vertices(self) -> int: + """Get the number of vertices of the mesh.""" + return len(self.vertices) def save_mesh(self, path: str) -> None: """Save mesh to a file. - All mesh data is saved. Must be implemented by child classes. + All mesh data is saved. Args: path (str): Path to the saved file. The extension is .npz. @@ -62,32 +98,10 @@ def save_mesh(self, path: str) -> None: def load_mesh(cls, path: str) -> Self: """Load a mesh from a file. - Must be implemented by child classes. - Args: path (str): Path to the mesh file (.npz). Returns: - Mesh: the mesh loaded from the .npz file. + The mesh loaded from the .npz file. """ raise NotImplementedError - - @property - def nb_vertices(self) -> int: - """Get the number of vertices of the mesh.""" - return len(self.vertices) - - @property - def nb_edges(self) -> int: - """Get the number of edges of the mesh.""" - return self.nb_half_edges // 2 - - @property - def nb_half_edges(self) -> int: - """Get the number of half-edges of the mesh.""" - return len(self.edges) - - @property - def nb_faces(self) -> int: - """Get the number of faces of the mesh.""" - return len(self.faces) diff --git a/src/vertax/meshes/pbc_mesh.py b/src/vertax/meshes/pbc_mesh.py index e47dd12..10b65cc 100644 --- a/src/vertax/meshes/pbc_mesh.py +++ b/src/vertax/meshes/pbc_mesh.py @@ -1,4 +1,4 @@ -"""Periodic Boundary Condition on a mesh.""" +"""Periodic Boundary Condition on a mesh, useful for bulk tissue dynamics with no explicit boundaries.""" from pathlib import Path from typing import Self @@ -18,210 +18,42 @@ class PbcMesh(Mesh): - """Periodic Boundary Condition on a mesh.""" + """Periodic Boundary Condition on a mesh. - def __init__(self) -> None: - """Do not call the constructor.""" - super().__init__() - - self.MAX_EDGES_IN_ANY_FACE: int = 20 - - @classmethod - def copy_mesh(cls, other_mesh: Self) -> Self: - """Copy all parameters from another mesh in a new mesh.""" - mesh = cls._create() - mesh.vertices = other_mesh.vertices.copy() - mesh.edges = other_mesh.edges.copy() - mesh.faces = other_mesh.faces.copy() - mesh.width = other_mesh.width - mesh.height = other_mesh.height - mesh.vertices_params = other_mesh.vertices_params.copy() - mesh.edges_params = other_mesh.edges_params.copy() - mesh.faces_params = other_mesh.faces_params.copy() - mesh.MAX_EDGES_IN_ANY_FACE = other_mesh.MAX_EDGES_IN_ANY_FACE + For a PbcMesh, `vertices` is a 2D array of floats of size (nb_vertices, 2) ; + with the coordinates of the vertices (in ]0, width[ x ]0, height[ ). - return mesh - - def save_mesh_txt( - self, - directory: str, - vertices_filename: str = "vertTable.txt", - edges_filename: str = "heTable.txt", - faces_filename: str = "faceTable.txt", - vertices_params_filename: str = "vertParamsTable.txt", - edges_params_filename: str = "heParamsTable.txt", - faces_params_filename: str = "faceParamsTable.txt", - constants_filename: str = "constants.txt", - ) -> None: - """Save a mesh in separate text files that can be read by numpy. + `edges` is a 2D array of integers of size (nb_half_edges, 8), with: - Only save the vertices, edges and faces, not other parameters. + - id of previous half-edge, + - id of next half-edge, + - id of twin half-edge, + - id of source vertex, + - id of target vertex, + - id of the face containing the half-edge. + - x-offset (either -1, 0 or 1) depending on the target vertex crossing a boundary. + - y-offset (either -1, 0 or 1) depending on the target vertex crossing a boundary. - Args: - directory (str): Path to the directory where to save the files. - vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt". - edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt". - faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt". - vertices_params_filename (str, optional): Filename for the vertices parameters table. - Defaults to "vertParamsTable.txt". - edges_params_filename (str, optional): Filename for the half-edges parameters table. - Defaults to "heParamsTable.txt". - faces_params_filename (str, optional): Filename for the faces parameters table. - Defaults to "faceParamsTable.txt". - constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE. - Defaults to "constants.txt". - """ - dirpath = Path(directory) - dirpath.mkdir(parents=True, exist_ok=True) - np.savetxt(dirpath / vertices_filename, self.vertices) - np.savetxt(dirpath / edges_filename, self.edges) - np.savetxt(dirpath / faces_filename, self.faces) - np.savetxt(dirpath / vertices_params_filename, self.vertices_params) - np.savetxt(dirpath / edges_params_filename, self.edges_params) - np.savetxt(dirpath / faces_params_filename, self.faces_params) - with (dirpath / constants_filename).open("w") as f: - f.write(f"{self.width} {self.height} {self.MAX_EDGES_IN_ANY_FACE}") - - def save_mesh(self, path: str) -> None: - """Save mesh to a file. - - All PBCMesh data is saved. - - Args: - path (str): Path to the saved file. The extension is .npz, a numpy format. - """ - Path(path).parent.mkdir(parents=True, exist_ok=True) - np.savez_compressed( - path, - allow_pickle=False, - vertices=self.vertices, - edges=self.edges, - faces=self.faces, - width=self.width, - height=self.height, - vertices_params=self.vertices_params, - edges_params=self.edges_params, - faces_params=self.faces_params, - MAX_EDGES_IN_ANY_FACE=self.MAX_EDGES_IN_ANY_FACE, - ) - - @classmethod - def load_mesh(cls, path: str) -> Self: - """Load a mesh from a file. - - All PBCMesh data is reloaded. - - Args: - path (str): Path to the mesh file (.npz), numpy format. - - Returns: - Mesh: the mesh loaded from the numpy .npz file. - """ - mesh_file = np.load(path) - mesh = cls._create() - mesh.vertices, mesh.edges, mesh.faces = mesh_file["vertices"], mesh_file["edges"], mesh_file["faces"] - mesh.width, mesh.height = mesh_file["width"], mesh_file["height"] - mesh.vertices_params = mesh_file["vertices_params"] - mesh.edges_params = mesh_file["edges_params"] - mesh.faces_params = mesh_file["faces_params"] - mesh.MAX_EDGES_IN_ANY_FACE = mesh_file["MAX_EDGES_IN_ANY_FACE"] - return mesh - - @classmethod - def load_mesh_txt( - cls, - directory: str, - vertices_filename: str = "vertTable.txt", - edges_filename: str = "heTable.txt", - faces_filename: str = "faceTable.txt", - vertices_params_filename: str = "vertParamsTable.txt", - edges_params_filename: str = "heParamsTable.txt", - faces_params_filename: str = "faceParamsTable.txt", - constants_filename: str = "constants.txt", - ) -> Self: - """Load a mesh from text files. - - Only load the vertices, edges and faces, not other parameters. - - Args: - directory (str): Directory where the text files are stored. - vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt". - edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt". - faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt". - vertices_params_filename (str, optional): Filename for the vertices parameters table. - Defaults to "vertParamsTable.txt". - edges_params_filename (str, optional): Filename for the half-edges parameters table. - Defaults to "heParamsTable.txt". - faces_params_filename (str, optional): Filename for the faces parameters table. - Defaults to "faceParamsTable.txt". - constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE. - Defaults to "constants.txt". - - Returns: - Self: The loaded mesh. - """ - dirpath = Path(directory) - dirpath.mkdir(parents=True, exist_ok=True) - - mesh = cls._create() - mesh.vertices = jnp.array(np.loadtxt(dirpath / vertices_filename, dtype=np.float64)) - mesh.edges = jnp.array(np.loadtxt(dirpath / edges_filename, dtype=np.int64)) - mesh.faces = jnp.array(np.loadtxt(dirpath / faces_filename, dtype=np.int64)) - mesh.vertices_params = jnp.array(np.loadtxt(dirpath / vertices_params_filename, dtype=np.float64)) - mesh.edges_params = jnp.array(np.loadtxt(dirpath / edges_params_filename, dtype=np.int64)) - mesh.faces_params = jnp.array(np.loadtxt(dirpath / faces_params_filename, dtype=np.int64)) - with (dirpath / constants_filename).open("r") as f: - numbers = f.readline().split() - mesh.width = float(numbers[0]) - mesh.height = float(numbers[1]) - mesh.MAX_EDGES_IN_ANY_FACE = int(numbers[2]) - return mesh - - def get_length(self, half_edge_id: Array) -> Array: - """Get the length of an edge.""" - - def _get_length(half_edge_id: Array) -> Array: - return get_length(half_edge_id, self.vertices, self.edges, self.faces, self.width, self.height) - - return jax.vmap(_get_length)(half_edge_id) - - def get_length_with_offset(self, half_edge_id: Array) -> Array: - """Get the length of an edge along with its offsets in an array (length, offset x, offset y).""" - - def _get_length_with_offset(half_edge_id: Array) -> Array: - return get_length_with_offset(half_edge_id, self.vertices, self.edges, self.faces, self.width, self.height) - - return jax.vmap(_get_length_with_offset)(half_edge_id) - - def get_perimeter(self, face_id: Array) -> Array: - """Get the perimeter of a face.""" - - def _get_perimeter(face_id: Array) -> Array: - return get_perimeter( - face_id, self.vertices, self.edges, self.faces, self.width, self.height, self.MAX_EDGES_IN_ANY_FACE - ) - - return jax.vmap(_get_perimeter)(face_id) - - def get_area(self, face_id: Array) -> Array: - """Get the area of a face.""" - - def _get_area(face_id: Array) -> Array: - return get_area( - face_id, self.vertices, self.edges, self.faces, self.width, self.height, self.MAX_EDGES_IN_ANY_FACE - ) + `faces` is a 1D array of integers of size (nb_faces) containing the id of a half-edge belonging to this face. + """ - return jax.vmap(_get_area)(face_id) + def __init__(self) -> None: + """Do not call the constructor directly, use the dedicated class methods such as: + + - `from_random_seeds`, + - `from_seeds`, + - `from_image`, + - `from_mask`, + - `create_empty`. + """ # noqa: D415 + super().__init__() - def update_boundary_conditions(self) -> None: - """Force periodic boundary conditions again after an update.""" - self.vertices, self.edges, self.faces = update_pbc( - self.vertices, self.edges, self.faces, self.width, self.height - ) + self.MAX_EDGES_IN_ANY_FACE: int = 20 + """Optimization parameter : must be more than the estimated maximum number of edges in a face. Base value is 20.""" # noqa: E501 @classmethod - def periodic_voronoi_from_random_seeds(cls, nb_seeds: int, width: float, height: float, random_key: int) -> Self: - """Create a Periodic Voronoi Mesh from random seeds. + def from_random_seeds(cls, nb_seeds: int, width: float, height: float, random_key: int) -> Self: + """Create a Periodic Boundary Conditions Mesh from a Voronoi diagram based on random seeds. Args: nb_seeds (int): Number of random seeds to use. @@ -234,11 +66,11 @@ def periodic_voronoi_from_random_seeds(cls, nb_seeds: int, width: float, height: """ key = jax.random.PRNGKey(random_key) seeds = jnp.array((width, height)) * jax.random.uniform(key, (nb_seeds, 2)) - return cls.periodic_voronoi_from_seeds(seeds, width, height) + return cls.from_seeds(seeds, width, height) @classmethod - def periodic_voronoi_from_seeds(cls, seeds: Array, width: float, height: float) -> Self: - """Create a Periodic Voronoi Mesh from a list of seeds. + def from_seeds(cls, seeds: Array, width: float, height: float) -> Self: + """Create a Periodic Boundary Conditions Mesh from a Voronoi diagram based on a list of seeds. The seeds are assumed to have positive x and y positions. @@ -275,7 +107,7 @@ def periodic_voronoi_from_seeds(cls, seeds: Array, width: float, height: float) return pbc_mesh @classmethod - def periodic_from_image( + def from_image( cls, image: NDArray, ) -> Self: @@ -285,26 +117,30 @@ def periodic_from_image( The result will probably be imperfect and it will always be better if you provide directly a mask (with no holes) with the function "periodic_from_mask". + The mask is made periodic by mirroring its edges. + Args: image (NDArray): The image which will act as a template for the mesh. Returns: - tuple[Array, Array, Array]: The vertices, half-edges and faces table of the mesh. + Self: The corresponding mesh. """ - return cls.periodic_from_mask(mask_from_image(image)) + return cls.from_mask(mask_from_image(image)) @classmethod - def periodic_from_mask( # noqa: C901 + def from_mask( # noqa: C901 cls, mask: NDArray, ) -> Self: """Create a rudimentary mesh with periodic boundary conditions from a mask with no holes. + The mask is made periodic by mirroring its edges. + Args: mask (NDArray): The mask with no holes which will act as a template for the mesh. Returns: - tuple[Array, Array, Array]: The vertices, half-edges and faces table of the mesh. + Self: The corresponding mesh. """ padded_mask = pad(mask, save=False, output_path="refined_and_padded_image.tiff") # Find vertices, edges, faces @@ -441,6 +277,211 @@ def periodic_from_mask( # noqa: C901 return pbc_mesh + @classmethod + def create_empty(cls) -> Self: + """Create an empty mesh. Use if you know what you're doing !""" + return cls._create() + + @classmethod + def copy_mesh(cls, other_mesh: Self) -> Self: + """Copy all parameters from another mesh in a new mesh.""" + mesh = cls._create() + mesh.vertices = other_mesh.vertices.copy() + mesh.edges = other_mesh.edges.copy() + mesh.faces = other_mesh.faces.copy() + mesh.width = other_mesh.width + mesh.height = other_mesh.height + mesh.vertices_params = other_mesh.vertices_params.copy() + mesh.edges_params = other_mesh.edges_params.copy() + mesh.faces_params = other_mesh.faces_params.copy() + mesh.MAX_EDGES_IN_ANY_FACE = other_mesh.MAX_EDGES_IN_ANY_FACE + + return mesh + + def save_mesh(self, path: str) -> None: + """Save mesh to a file. + + All PBCMesh data is saved. + + Args: + path (str): Path to the saved file. The extension is .npz, a numpy format. + """ + Path(path).parent.mkdir(parents=True, exist_ok=True) + np.savez_compressed( + path, + allow_pickle=False, + vertices=self.vertices, + edges=self.edges, + faces=self.faces, + width=self.width, + height=self.height, + vertices_params=self.vertices_params, + edges_params=self.edges_params, + faces_params=self.faces_params, + MAX_EDGES_IN_ANY_FACE=self.MAX_EDGES_IN_ANY_FACE, + ) + + @classmethod + def load_mesh(cls, path: str) -> Self: + """Load a mesh from a file. + + All PBCMesh data is reloaded. + + Args: + path (str): Path to the mesh file (.npz), numpy format. + + Returns: + Mesh: the mesh loaded from the numpy .npz file. + """ + mesh_file = np.load(path) + mesh = cls._create() + mesh.vertices, mesh.edges, mesh.faces = ( + jnp.array(mesh_file["vertices"]), + jnp.array(mesh_file["edges"].reshape(-1, 8)), + jnp.array(mesh_file["faces"]), + ) + mesh.width, mesh.height = float(mesh_file["width"]), float(mesh_file["height"]) + mesh.vertices_params = jnp.array(mesh_file["vertices_params"]) + mesh.edges_params = jnp.array(mesh_file["edges_params"]) + mesh.faces_params = jnp.array(mesh_file["faces_params"]) + mesh.MAX_EDGES_IN_ANY_FACE = mesh_file["MAX_EDGES_IN_ANY_FACE"] + return mesh + + def save_mesh_txt( + self, + directory: str, + vertices_filename: str = "vertTable.txt", + edges_filename: str = "heTable.txt", + faces_filename: str = "faceTable.txt", + vertices_params_filename: str = "vertParamsTable.txt", + edges_params_filename: str = "heParamsTable.txt", + faces_params_filename: str = "faceParamsTable.txt", + constants_filename: str = "constants.txt", + ) -> None: + """Save a mesh in separate text files that can be read by numpy. + + Only save the vertices, edges and faces, not other parameters. + + Args: + directory (str): Path to the directory where to save the files. + vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt". + edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt". + faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt". + vertices_params_filename (str, optional): Filename for the vertices parameters table. + Defaults to "vertParamsTable.txt". + edges_params_filename (str, optional): Filename for the half-edges parameters table. + Defaults to "heParamsTable.txt". + faces_params_filename (str, optional): Filename for the faces parameters table. + Defaults to "faceParamsTable.txt". + constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE. + Defaults to "constants.txt". + """ + dirpath = Path(directory) + dirpath.mkdir(parents=True, exist_ok=True) + np.savetxt(dirpath / vertices_filename, self.vertices) + np.savetxt(dirpath / edges_filename, self.edges) + np.savetxt(dirpath / faces_filename, self.faces) + np.savetxt(dirpath / vertices_params_filename, self.vertices_params) + np.savetxt(dirpath / edges_params_filename, self.edges_params) + np.savetxt(dirpath / faces_params_filename, self.faces_params) + with (dirpath / constants_filename).open("w") as f: + f.write(f"{self.width} {self.height} {self.MAX_EDGES_IN_ANY_FACE}") + + @classmethod + def load_mesh_txt( + cls, + directory: str, + vertices_filename: str = "vertTable.txt", + edges_filename: str = "heTable.txt", + faces_filename: str = "faceTable.txt", + vertices_params_filename: str = "vertParamsTable.txt", + edges_params_filename: str = "heParamsTable.txt", + faces_params_filename: str = "faceParamsTable.txt", + constants_filename: str = "constants.txt", + ) -> Self: + """Load a mesh from text files. + + Only load the vertices, edges and faces, not other parameters. + + Args: + directory (str): Directory where the text files are stored. + vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt". + edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt". + faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt". + vertices_params_filename (str, optional): Filename for the vertices parameters table. + Defaults to "vertParamsTable.txt". + edges_params_filename (str, optional): Filename for the half-edges parameters table. + Defaults to "heParamsTable.txt". + faces_params_filename (str, optional): Filename for the faces parameters table. + Defaults to "faceParamsTable.txt". + constants_filename (str, optional): Filename for width/height/MAX_EDGES_IN_ANY_FACE. + Defaults to "constants.txt". + + Returns: + Self: The loaded mesh. + """ + dirpath = Path(directory) + dirpath.mkdir(parents=True, exist_ok=True) + + mesh = cls._create() + mesh.vertices = jnp.array(np.loadtxt(dirpath / vertices_filename, dtype=np.float64)) + mesh.edges = jnp.array(np.loadtxt(dirpath / edges_filename, dtype=np.int64)) + mesh.faces = jnp.array(np.loadtxt(dirpath / faces_filename, dtype=np.int64)) + mesh.vertices_params = jnp.array(np.loadtxt(dirpath / vertices_params_filename, dtype=np.float64)) + mesh.edges_params = jnp.array(np.loadtxt(dirpath / edges_params_filename, dtype=np.int64)) + mesh.faces_params = jnp.array(np.loadtxt(dirpath / faces_params_filename, dtype=np.int64)) + with (dirpath / constants_filename).open("r") as f: + numbers = f.readline().split() + mesh.width = float(numbers[0]) + mesh.height = float(numbers[1]) + mesh.MAX_EDGES_IN_ANY_FACE = int(numbers[2]) + return mesh + + def get_length(self, half_edge_id: Array) -> Array: + """Get the length of given edges.""" + + def _get_length(half_edge_id: Array) -> Array: + return get_length(half_edge_id, self.vertices, self.edges, self.faces, self.width, self.height) + + return jax.vmap(_get_length)(half_edge_id) + + def get_length_with_offset(self, half_edge_id: Array) -> Array: + """Get the length of given edges along with its offsets in an array (length, offset x, offset y).""" + + def _get_length_with_offset(half_edge_id: Array) -> Array: + return get_length_with_offset(half_edge_id, self.vertices, self.edges, self.faces, self.width, self.height) + + return jax.vmap(_get_length_with_offset)(half_edge_id) + + def get_perimeter(self, face_id: Array) -> Array: + """Get the perimeter of given faces.""" + + def _get_perimeter(face_id: Array) -> Array: + return get_perimeter( + face_id, self.vertices, self.edges, self.faces, self.width, self.height, self.MAX_EDGES_IN_ANY_FACE + ) + + return jax.vmap(_get_perimeter)(face_id) + + def get_area(self, face_id: Array) -> Array: + """Get the area of given faces.""" + + def _get_area(face_id: Array) -> Array: + return get_area( + face_id, self.vertices, self.edges, self.faces, self.width, self.height, self.MAX_EDGES_IN_ANY_FACE + ) + + return jax.vmap(_get_area)(face_id) + + def update_boundary_conditions(self) -> None: + """Force periodic boundary conditions again after an update. + + Most likely you'll never have to use it yourself, as it can be made automatically with a `PbcBilevelOptimizer`. + """ + self.vertices, self.edges, self.faces = update_pbc( + self.vertices, self.edges, self.faces, self.width, self.height + ) + def _make_periodic( # noqa: C901 seeds: Array, diff --git a/src/vertax/meshes/plot.py b/src/vertax/meshes/plot.py index f73413e..5d69daa 100644 --- a/src/vertax/meshes/plot.py +++ b/src/vertax/meshes/plot.py @@ -25,28 +25,41 @@ class FacePlot(Enum): """What it is possible to show on a face.""" MULTICOLOR = 1 + """Each face get a random color.""" FACE_PARAMETER = 2 + """The face color depends on its parameter.""" AREA = 3 + """The face color depends on its area.""" PERIMETER = 4 + """The face color depends on its perimeter.""" WHITE = 5 + """All faces are just white.""" FATES = 6 + """Faces are colored depending on their fate marker (if any).""" class EdgePlot(Enum): """What it is possible to show on an edge.""" BLACK = 1 + """All edges are black.""" EDGE_PARAMETER = 2 + """The edge color depends on its parameter.""" LENGTH = 3 + """The edge color depends on its length.""" INVISIBLE = 4 + """Do not show edges.""" class VertexPlot(Enum): """What it is possible to show on a vertex.""" BLACK = 1 + """Vertices are black.""" VERTEX_PARAMETER = 2 + """The vertex color depends on its parameter.""" INVISIBLE = 3 + """Do not show vertices.""" def add_colorbar(fig: Figure, ax: Axes, v_min: float, v_max: float, cmap: Colormap) -> Colorbar: @@ -108,6 +121,9 @@ def plot_mesh( vertices_cmap_name: str = "spring", vertices_size: float = 20, title: str = "", + forced_vertex_scale: tuple[float, float] | None = None, + forced_edge_scale: tuple[float, float] | None = None, + forced_face_scale: tuple[float, float] | None = None, ) -> None: """Plot the mesh and decide to save and/or show the mesh or not.""" if isinstance(mesh, PbcMesh): @@ -128,6 +144,9 @@ def plot_mesh( vertices_cmap_name, vertices_size, title, + forced_vertex_scale, + forced_edge_scale, + forced_face_scale, ) elif isinstance(mesh, BoundedMesh): _plot_bounded_mesh( @@ -147,6 +166,9 @@ def plot_mesh( vertices_cmap_name, vertices_size, title, + forced_vertex_scale, + forced_edge_scale, + forced_face_scale, ) @@ -164,6 +186,9 @@ def get_plot_mesh( vertices_cmap_name: str = "spring", vertices_size: float = 20, title: str = "", + forced_vertex_scale: tuple[float, float] | None = None, + forced_edge_scale: tuple[float, float] | None = None, + forced_face_scale: tuple[float, float] | None = None, ) -> tuple[Figure, Axes]: """Get the matplotlib figure and and ax for one plot.""" if isinstance(mesh, PbcMesh): @@ -181,6 +206,9 @@ def get_plot_mesh( vertices_cmap_name, vertices_size, title, + forced_vertex_scale, + forced_edge_scale, + forced_face_scale, ) elif isinstance(mesh, BoundedMesh): return _get_plot_bounded_mesh( @@ -197,6 +225,9 @@ def get_plot_mesh( vertices_cmap_name, vertices_size, title, + forced_vertex_scale, + forced_edge_scale, + forced_face_scale, ) else: msg = f"Expected either a PbcMesh or a BoundedMesh. Got {mesh} instead." @@ -220,6 +251,9 @@ def _plot_pbc_mesh( vertices_cmap_name: str = "spring", vertices_size: float = 20, title: str = "", + forced_vertex_scale: tuple[float, float] | None = None, + forced_edge_scale: tuple[float, float] | None = None, + forced_face_scale: tuple[float, float] | None = None, ) -> None: """Plot the mesh and decide to save and/or show the mesh or not.""" fig, _ax = _get_plot_pbc_mesh( @@ -236,6 +270,9 @@ def _plot_pbc_mesh( vertices_cmap_name, vertices_size, title, + forced_vertex_scale, + forced_edge_scale, + forced_face_scale, ) if save: @@ -261,6 +298,9 @@ def _get_plot_pbc_mesh( vertices_cmap_name: str = "spring", vertices_size: float = 20, title: str = "", + forced_vertex_scale: tuple[float, float] | None = None, + forced_edge_scale: tuple[float, float] | None = None, + forced_face_scale: tuple[float, float] | None = None, ) -> tuple[Figure, Axes]: """Get the matplotlib figure and and ax for one plot.""" # Fates not used for pbc. @@ -269,9 +309,11 @@ def _get_plot_pbc_mesh( fig, _ = plt.subplots(layout="constrained") ax = plt.gca() - _plot_faces_pbc(mesh, fig, ax, face_plot, faces_cmap_name, face_parameters_name) - _plot_edges_pbc(mesh, fig, ax, edge_plot, edges_cmap_name, edges_width, edge_parameters_name) - _plot_vertices_pbc(mesh, fig, ax, vertex_plot, vertices_cmap_name, vertices_size, vertex_parameters_name) + _plot_faces_pbc(mesh, fig, ax, face_plot, faces_cmap_name, face_parameters_name, forced_face_scale) + _plot_edges_pbc(mesh, fig, ax, edge_plot, edges_cmap_name, edges_width, edge_parameters_name, forced_edge_scale) + _plot_vertices_pbc( + mesh, fig, ax, vertex_plot, vertices_cmap_name, vertices_size, vertex_parameters_name, forced_vertex_scale + ) ax.set_title(title) ax.set_aspect(mesh.height / mesh.width) @@ -282,7 +324,13 @@ def _get_plot_pbc_mesh( def _plot_faces_pbc( - mesh: PbcMesh, fig: Figure, ax: Axes, face_plot: FacePlot, faces_cmap_name: str, face_parameters_name: str + mesh: PbcMesh, + fig: Figure, + ax: Axes, + face_plot: FacePlot, + faces_cmap_name: str, + face_parameters_name: str, + forced_scale: tuple[float, float] | None = None, ) -> None: multicolor_cmap = _get_multicolor_face_cmap(mesh) faces_cmap = matplotlib.colormaps.get_cmap(faces_cmap_name) @@ -302,8 +350,11 @@ def _plot_faces_pbc( case FacePlot.PERIMETER: values = mesh.get_perimeter(jnp.arange(mesh.nb_faces)) cbar_label = "Perimeter of cell" - v_max = float(values.max()) - v_min = float(values.min()) + if forced_scale is None: + v_max = float(values.max()) + v_min = float(values.min()) + else: + v_min, v_max = forced_scale match face_plot: case FacePlot.MULTICOLOR | FacePlot.WHITE: pass @@ -408,6 +459,7 @@ def _plot_edges_pbc( edges_cmap_name: str, edges_width: float, edge_parameters_name: str, + forced_scale: tuple[float, float] | None = None, ) -> None: if edge_plot != EdgePlot.INVISIBLE: edge_params_cmap = matplotlib.colormaps.get_cmap(edges_cmap_name) @@ -425,8 +477,11 @@ def _plot_edges_pbc( case EdgePlot.LENGTH: values = mesh.get_length(jnp.arange(2 * mesh.nb_edges)) cbar_label = "Length of edge" - v_max = float(values.max()) - v_min = float(values.min()) + if forced_scale is None: + v_max = float(values.max()) + v_min = float(values.min()) + else: + v_min, v_max = forced_scale match edge_plot: case EdgePlot.BLACK: @@ -478,6 +533,7 @@ def _plot_vertices_pbc( vertices_cmap_name: str, vertices_size: float, vertex_parameters_name: str, + forced_scale: tuple[float, float] | None = None, ) -> None: if vertex_plot != VertexPlot.INVISIBLE: # set the correct colorbar @@ -485,8 +541,11 @@ def _plot_vertices_pbc( v_max = 1 v_min = 0 if vertex_plot == VertexPlot.VERTEX_PARAMETER: - v_max = float(mesh.vertices_params.max()) - v_min = float(mesh.vertices_params.min()) + if forced_scale is None: + v_max = float(mesh.vertices_params.max()) + v_min = float(mesh.vertices_params.min()) + else: + v_min, v_max = forced_scale cbar = add_colorbar(fig, ax, v_min, v_max, vertices_params_cmap) cbar_label = "Vertex parameter" if vertex_parameters_name == "" else vertex_parameters_name cbar.ax.set_ylabel(cbar_label, rotation=270, labelpad=13) @@ -523,6 +582,9 @@ def _plot_bounded_mesh( vertices_cmap_name: str = "spring", vertices_size: float = 20, title: str = "", + forced_vertex_scale: tuple[float, float] | None = None, + forced_edge_scale: tuple[float, float] | None = None, + forced_face_scale: tuple[float, float] | None = None, ) -> None: """Plot the mesh and decide to save and/or show the mesh or not.""" fig, _ax = _get_plot_bounded_mesh( @@ -539,6 +601,9 @@ def _plot_bounded_mesh( vertices_cmap_name, vertices_size, title, + forced_vertex_scale, + forced_edge_scale, + forced_face_scale, ) if save: @@ -564,13 +629,18 @@ def _get_plot_bounded_mesh( vertices_cmap_name: str = "spring", vertices_size: float = 20, title: str = "", + forced_vertex_scale: tuple[float, float] | None = None, + forced_edge_scale: tuple[float, float] | None = None, + forced_face_scale: tuple[float, float] | None = None, ) -> tuple[Figure, Axes]: """Get the matplotlib figure and and ax for one plot.""" fig, _ = plt.subplots(layout="constrained") ax = plt.gca() - _plot_faces_bounded(mesh, fig, ax, face_plot, faces_cmap_name, face_parameters_name) - _plot_edges_bounded(mesh, fig, ax, edge_plot, edges_cmap_name, edges_width, edge_parameters_name) - _plot_vertices_bounded(mesh, fig, ax, vertex_plot, vertices_cmap_name, vertices_size, vertex_parameters_name) + _plot_faces_bounded(mesh, fig, ax, face_plot, faces_cmap_name, face_parameters_name, forced_face_scale) + _plot_edges_bounded(mesh, fig, ax, edge_plot, edges_cmap_name, edges_width, edge_parameters_name, forced_edge_scale) + _plot_vertices_bounded( + mesh, fig, ax, vertex_plot, vertices_cmap_name, vertices_size, vertex_parameters_name, forced_vertex_scale + ) ax.set_title(title) # unlike the pbc case, here is not easy to know a priori @@ -583,7 +653,13 @@ def _get_plot_bounded_mesh( def _plot_faces_bounded( - mesh: BoundedMesh, fig: Figure, ax: Axes, face_plot: FacePlot, faces_cmap_name: str, face_parameters_name: str + mesh: BoundedMesh, + fig: Figure, + ax: Axes, + face_plot: FacePlot, + faces_cmap_name: str, + face_parameters_name: str, + forced_scale: tuple[float, float] | None = None, ) -> None: multicolor_cmap = _get_multicolor_face_cmap(mesh) faces_cmap = matplotlib.colormaps.get_cmap(faces_cmap_name) @@ -605,8 +681,11 @@ def _plot_faces_bounded( cbar_label = "Perimeter of cell" case FacePlot.FATES: values = mesh.faces[:, 1] - v_max = float(values.max()) - v_min = float(values.min()) + if forced_scale is None: + v_max = float(values.max()) + v_min = float(values.min()) + else: + v_min, v_max = forced_scale match face_plot: case FacePlot.MULTICOLOR | FacePlot.WHITE | FacePlot.FATES: pass @@ -681,6 +760,7 @@ def _plot_edges_bounded( edges_cmap_name: str, edges_width: float, edge_parameters_name: str, + forced_scale: tuple[float, float] | None = None, ) -> None: if edge_plot != EdgePlot.INVISIBLE: edge_params_cmap = matplotlib.colormaps.get_cmap(edges_cmap_name) @@ -697,8 +777,11 @@ def _plot_edges_bounded( case EdgePlot.LENGTH: values = mesh.get_length(jnp.arange(2 * mesh.nb_edges)) cbar_label = "Length of edge" - v_max = float(values.max()) - v_min = float(values.min()) + if forced_scale is None: + v_max = float(values.max()) + v_min = float(values.min()) + else: + v_min, v_max = forced_scale # set the correct colorbar if needed match edge_plot: @@ -756,6 +839,7 @@ def _plot_vertices_bounded( vertices_cmap_name: str, vertices_size: float, vertex_parameters_name: str, + forced_scale: tuple[float, float] | None = None, ) -> None: if vertex_plot != VertexPlot.INVISIBLE: # set the correct colorbar @@ -763,8 +847,11 @@ def _plot_vertices_bounded( v_max = 1 v_min = 0 if vertex_plot == VertexPlot.VERTEX_PARAMETER: - v_max = float(mesh.vertices_params.max()) - v_min = float(mesh.vertices_params.min()) + if forced_scale is None: + v_max = float(mesh.vertices_params.max()) + v_min = float(mesh.vertices_params.min()) + else: + v_min, v_max = forced_scale cbar = add_colorbar(fig, ax, v_min, v_max, vertices_params_cmap) cbar_label = "Vertex parameter" if vertex_parameters_name == "" else vertex_parameters_name cbar.ax.set_ylabel(cbar_label, rotation=270, labelpad=13) diff --git a/src/vertax/method_enum.py b/src/vertax/method_enum.py index 2df67c5..8753077 100644 --- a/src/vertax/method_enum.py +++ b/src/vertax/method_enum.py @@ -7,6 +7,14 @@ class BilevelOptimizationMethod(Enum): """Which optimization method to use in the bi-level optimization.""" AUTOMATIC_DIFFERENTIATION = "ad" + """Unrolls the inner optimization steps ; forward-mode JVP via `jax.jacfwd`, + cost scales with the number of parameters and iterations.""" EQUILIBRIUM_PROPAGATION = "ep" + """Estimates the gradient from perturbed free vs nudged equilibria ; no backdrop required. + Most efficient but depends on the perturbation size β.""" IMPLICIT_DIFFERENTIATION = "id" + """Differentiates the optimality condition ∇ₓE=0 via Implicit Function Theorem ; JVP variant ; + requires Hessian solve and sensitive to ill-conditioning.""" ADJOINT_STATE = "as" + """Differentiates the optimality condition ∇ₓE=0 via Implicit Function Theorem ; VJP variant ; + requires Hessian solve or sensitive to ill-conditioning.""" diff --git a/src/vertax/opt.py b/src/vertax/opt.py index 49b10a0..d28fbd6 100644 --- a/src/vertax/opt.py +++ b/src/vertax/opt.py @@ -8,7 +8,7 @@ import jax.numpy as jnp import numpy as np import optax -from jax import Array, grad, jacfwd, jit, jvp, lax +from jax import Array, grad, jacfwd, jit, jvp, lax, value_and_grad from numpy.typing import ArrayLike from scipy.sparse.linalg import LinearOperator, minres @@ -143,7 +143,7 @@ def _jit_minimize( step_count = jnp.array(0) should_stop = jnp.array(False) - # 2. Define the inner step function for lax.scan + # Define the inner step function for lax.scan # The inner function is defined *without* @jit because it's compiled by lax.scan/jit on minimize def scan_step( carry: tuple[Array, Array, Array, Array, Array, Array, Array, Array, Array, int, Array, Array], i: int @@ -166,7 +166,7 @@ def scan_step( # Determine if we are still running (i.e., not stopped) is_running = jnp.logical_not(should_stop) - # 1) Compute loss + # Compute loss L_current = L_in( vt[selected_verts], ht[selected_hes], @@ -176,23 +176,23 @@ def scan_step( fp[selected_faces], ) - # 2) Early stopping bookkeeping + # Early stopping bookkeeping denom = jnp.where(prev_L_values[-1] != 0, prev_L_values[-1], 1.0) rel_var = jnp.abs((L_current - prev_L_values[-1]) / denom) new_stagnation_count = jnp.where(rel_var < tolerance, stagnation_count + 1, 0) new_should_stop = (new_stagnation_count >= patience) | (i >= iterations_max - 1) - # 3) Gradient wrt chosen argnums + # Gradient wrt chosen argnums grads = grad(L_in, argnums=optimization_target.value)(vt, ht, ft, vp, hp, fp) - # 4) Optimizer update + # Optimizer update updates, new_opt_state = solver.update(grads, opt_state) - # 5) Apply updates to the chosen array on selected indices + # Apply updates to the chosen array on selected indices updates_sel = updates.at[sel].get() # type: ignore - # --- Conditional Application of Optimization Update and State --- + # Conditional application of optimization update and state arrays = [vt, ht, ft, vp, hp, fp] k = optimization_target.value new_optim = arrays[k].at[sel].set(arrays[k][sel] + updates_sel) @@ -206,7 +206,7 @@ def scan_step( stagnation_count = lax.cond(is_running, lambda: new_stagnation_count, lambda: stagnation_count) should_stop = new_should_stop # should_stop is a flag for the *next* iteration - # 6) Geometry updates (Must also be conditional/masked) + # Geometry updates (must also be conditional/masked) new_vt_pbc, new_ht_pbc, new_ft_pbc = update_pbc(vt, ht, ft, width, height) vt = lax.cond(is_running, lambda: new_vt_pbc, lambda: vt) ht = lax.cond(is_running, lambda: new_ht_pbc, lambda: ht) @@ -219,12 +219,12 @@ def scan_step( ht = lax.cond(is_running, lambda: new_ht_T1, lambda: ht) ft = lax.cond(is_running, lambda: new_ft_T1, lambda: ft) - # 7) Shift prev_L_values + # Shift prev_L_values new_prev_L_values = prev_L_values.at[1:].set(prev_L_values[:-1]) new_prev_L_values = new_prev_L_values.at[0].set(L_current) prev_L_values = lax.cond(is_running, lambda: new_prev_L_values, lambda: prev_L_values) - # 8) Update History and Step Count + # Update history and step count L_list = L_list.at[i].set(L_current) step_count = i + 1 @@ -244,8 +244,7 @@ def scan_step( ) return new_carry, None - # 3. Call lax.scan - + # Call lax.scan init_carry = ( vertTable, heTable, @@ -263,7 +262,7 @@ def scan_step( final_state, _ = lax.scan(scan_step, init_carry, xs=jnp.arange(iterations_max)) # ty:ignore[invalid-argument-type] - # 4. Unpack and return results (for slicing outside JIT) + # Unpack and return results (for slicing outside jit) vt_f, ht_f, ft_f, vp_f, hp_f, fp_f, _, _, _, step_f, _, L_hist = final_state return (vt_f, ht_f, ft_f, vp_f, hp_f, fp_f), (L_hist, step_f) # ty:ignore[invalid-return-type] @@ -377,13 +376,140 @@ def inner_opt( # Convert the JAX array step_f to a Python integer step_f = step_f_array.item() - # Now slice using standard Python/NumPy slicing + # Slice using standard Python/NumPy slicing final_L_list = L_hist_full[:step_f] # Return updated arrays and loss history return (vt_f, ht_f, ft_f), final_L_list +# ----------------------- # +# Sinkhorn implementation # +# ----------------------- # + +# def _periodic_sq_cost_matrix( +# vertTable_after: Array, +# vertTable_target: Array, +# width: float, +# height: float, +# ) -> Array: +# """Pairwise periodic squared distance, shape (n, n).""" +# sim = vertTable_after[:, :2] +# tgt = vertTable_target[:, :2] +# diff = sim[:, None, :] - tgt[None, :, :] # (n, n, 2) +# shifts = jnp.array( +# [ +# [0.0, 0.0], +# [-width, 0.0], [width, 0.0], +# [0.0, -height], [0.0, height], +# [-width, -height], [-width, height], +# [width, -height], [width, height], +# ] +# ) # (9, 2) +# shifted = diff[:, :, None, :] - shifts[None, None, :, :] # (n, n, 9, 2) +# sq = jnp.sum(shifted * shifted, axis=-1) # (n, n, 9) +# return jnp.min(sq, axis=-1) # (n, n) +# +# from ott.geometry import geometry +# from ott.problems.linear import linear_problem +# from ott.solvers.linear import sinkhorn +# def _build_t1_repair_perm_sinkhorn( +# vertTable_after: Array, +# vertTable_target: Array, +# width: float, +# height: float, +# epsilon: float = 1e-3, +# threshold: float = 1e-3, +# max_iterations: int = 1000, +# ) -> Array: +# """Vertex relabeling by entropic OT (ott-jax Sinkhorn).""" +# C = _periodic_sq_cost_matrix(vertTable_after, vertTable_target, width, height) +# geom = geometry.Geometry(cost_matrix=C, epsilon=epsilon) +# prob = linear_problem.LinearProblem(geom) +# solver = sinkhorn.Sinkhorn(threshold=threshold, max_iterations=max_iterations) +# out = solver(prob) +# return jnp.argmax(out.matrix, axis=1).astype(jnp.int32) + + +def _periodic_sq_dist(p: Array, q: Array, width: float, height: float) -> Array: + """Squared distance under PBC between two 2D points (cols 0,1). + + Mirrors `cost_v2v`'s convention: take the minimum over the 9 image shifts + (center + 8 surrounding sectors) of the target. + """ + dx = p[0] - q[0] + dy = p[1] - q[1] + candidates = jnp.array( + [ + dx * dx + dy * dy, + (dx - width) * (dx - width) + dy * dy, + (dx + width) * (dx + width) + dy * dy, + dx * dx + (dy - height) * (dy - height), + dx * dx + (dy + height) * (dy + height), + (dx - width) * (dx - width) + (dy - height) * (dy - height), + (dx - width) * (dx - width) + (dy + height) * (dy + height), + (dx + width) * (dx + width) + (dy - height) * (dy - height), + (dx + width) * (dx + width) + (dy + height) * (dy + height), + ] + ) + return jnp.min(candidates) + + +def _build_t1_repair_perm( + vertTable_after: Array, + vertTable_target: Array, + heTable_before: Array, + heTable_after: Array, + width: float, + height: float, +) -> Array: + """Build a vertex-index permutation that swaps T1-flipped pairs when it lowers v2v cost. + + A half-edge whose face index (col 5) changed across `inner_opt` was part of a T1: only + `update_T1` ever writes that column (`update_pbc` does not). For each such half-edge, + cols (3, 4) give the (a, b) vertex pair to test. Each T1 is reported twice (edge + twin); + the swap-only-if-better filter is idempotent, so duplicates are harmless. + """ + perm = jnp.arange(vertTable_after.shape[0], dtype=jnp.int32) + + t1_mask = heTable_before[:, 5] != heTable_after[:, 5] + pair_a = heTable_before[:, 3].astype(jnp.int32) + pair_b = heTable_before[:, 4].astype(jnp.int32) + + def body(i: int, perm: Array) -> Array: + a = pair_a[i] + b = pair_b[i] + ta = vertTable_target[perm[a]] + tb = vertTable_target[perm[b]] + pa = vertTable_after[a] + pb = vertTable_after[b] + cost_no_swap = _periodic_sq_dist(pa, ta, width, height) + _periodic_sq_dist(pb, tb, width, height) + cost_swap = _periodic_sq_dist(pa, tb, width, height) + _periodic_sq_dist(pb, ta, width, height) + do_swap = t1_mask[i] & (cost_swap < cost_no_swap) + new_a_val = jnp.where(do_swap, perm[b], perm[a]) + new_b_val = jnp.where(do_swap, perm[a], perm[b]) + return perm.at[a].set(new_a_val).at[b].set(new_b_val) + + return lax.fori_loop(0, heTable_before.shape[0], body, perm) + + +def _apply_perm_to_state( + perm: Array, + vertTable: Array, + heTable: Array, +) -> tuple[Array, Array]: + """Reorder vertTable rows and relabel vertex indices stored in heTable cols 3, 4. + + heTable cols 0, 1, 2, 5 reference half-edges/faces and are unaffected. + Self-canceling for any cost that reads positions by half-edge (only cost_v2v sees the swap). + """ + new_vertTable = vertTable[perm] + src = perm[heTable[:, 3].astype(jnp.int32)].astype(heTable.dtype) + tgt = perm[heTable[:, 4].astype(jnp.int32)].astype(heTable.dtype) + new_heTable = heTable.at[:, 3].set(src).at[:, 4].set(tgt) + return new_vertTable, new_heTable + + def cost_ad( vertTable: Array, heTable: Array, @@ -408,8 +534,10 @@ def cost_ad( selected_faces: Array | None = None, image_target: Array | None = None, update_t1_func: UpdateT1Func = update_T1, -) -> Array: +) -> tuple[Array, tuple[Array, Array, Array]]: """Automatic differentiation cost function.""" + heTable_before = heTable + (vertTable, heTable, faceTable), _loss = inner_opt( vertTable, heTable, @@ -431,6 +559,16 @@ def cost_ad( update_t1_func, ) + perm = _build_t1_repair_perm( + vertTable, + vertTable_target, + heTable_before, + heTable, + width, + height, + ) + vertTable, heTable = _apply_perm_to_state(perm, vertTable, heTable) + loss_out_value = L_out( vertTable, heTable, @@ -446,7 +584,7 @@ def cost_ad( image_target, ) - return loss_out_value + return loss_out_value, (vertTable, heTable, faceTable) def outer_opt( @@ -474,61 +612,11 @@ def outer_opt( selected_faces: Array | None, image_target: Array | None, update_t1_func: UpdateT1Func = update_T1, -) -> tuple[Array, Array, Array]: +) -> tuple[Array, Array, Array, Array, Array, Array]: """Outer optimization for Automatic differentiation method.""" - grad_verts = grad(cost_ad, argnums=5)( - vertTable, - heTable, - faceTable, - width, - height, - vert_params, - he_params, - face_params, - vertTable_target, - heTable_target, - faceTable_target, - L_in, - L_out, - solver_inner, - min_dist_T1, - iterations_max, - tolerance, - patience, - selected_verts, - selected_hes, - selected_faces, - image_target, - update_t1_func, - ) - - grad_hes = grad(cost_ad, argnums=6)( - vertTable, - heTable, - faceTable, - width, - height, - vert_params, - he_params, - face_params, - vertTable_target, - heTable_target, - faceTable_target, - L_in, - L_out, - solver_inner, - min_dist_T1, - iterations_max, - tolerance, - patience, - selected_verts, - selected_hes, - selected_faces, - image_target, - update_t1_func, - ) - - grad_faces = grad(cost_ad, argnums=7)( + ((_, (vertTable, heTable, faceTable)), (grad_verts, grad_hes, grad_faces)) = value_and_grad( + cost_ad, argnums=(5, 6, 7), has_aux=True + )( vertTable, heTable, faceTable, @@ -563,7 +651,7 @@ def outer_opt( new_he_params: Array = updated_params["he_params"] # type: ignore new_face_params: Array = updated_params["face_params"] # type: ignore - return new_vert_params, new_he_params, new_face_params + return vertTable, heTable, faceTable, new_vert_params, new_he_params, new_face_params ############################# @@ -793,8 +881,10 @@ def outer_eq_prop( image_target: Array | None, beta: float, update_t1_func: UpdateT1Func = update_T1, -) -> tuple[Array, Array, Array]: +) -> tuple[Array, Array, Array, Array, Array, Array]: """Outer optimization for equilibrium propagation method.""" + heTable_before = heTable + (vertTable_free, heTable_free, faceTable_free), _ = inner_eq_prop( vertTable, heTable, @@ -823,6 +913,16 @@ def outer_eq_prop( update_t1_func, ) + perm_free = _build_t1_repair_perm( + vertTable_free, + vertTable_target, + heTable_before, + heTable_free, + width, + height, + ) + vertTable_free, heTable_free = _apply_perm_to_state(perm_free, vertTable_free, heTable_free) + (vertTable_nudged, heTable_nudged, faceTable_nudged), _ = inner_eq_prop( vertTable, heTable, @@ -851,91 +951,19 @@ def outer_eq_prop( update_t1_func, ) - grad_loss_ep_free_verts = grad(_loss_ep_static, argnums=5)( - vertTable_free, - heTable_free, - faceTable_free, - width, - height, - vert_params, - he_params, - face_params, - vertTable_target, - heTable_target, - faceTable_target, - L_in, - L_out, - selected_verts, - selected_hes, - selected_faces, - image_target, - beta=-beta, - ) - - grad_loss_ep_nudged_verts = grad(_loss_ep_static, argnums=5)( + perm_nudged = _build_t1_repair_perm( vertTable_nudged, - heTable_nudged, - faceTable_nudged, - width, - height, - vert_params, - he_params, - face_params, vertTable_target, - heTable_target, - faceTable_target, - L_in, - L_out, - selected_verts, - selected_hes, - selected_faces, - image_target, - beta, - ) - - grad_loss_ep_free_hes = grad(_loss_ep_static, argnums=6)( - vertTable_free, - heTable_free, - faceTable_free, - width, - height, - vert_params, - he_params, - face_params, - vertTable_target, - heTable_target, - faceTable_target, - L_in, - L_out, - selected_verts, - selected_hes, - selected_faces, - image_target, - beta=-beta, - ) - - grad_loss_ep_nudged_hes = grad(_loss_ep_static, argnums=6)( - vertTable_nudged, + heTable_before, heTable_nudged, - faceTable_nudged, width, height, - vert_params, - he_params, - face_params, - vertTable_target, - heTable_target, - faceTable_target, - L_in, - L_out, - selected_verts, - selected_hes, - selected_faces, - image_target, - beta, ) + vertTable_nudged, heTable_nudged = _apply_perm_to_state(perm_nudged, vertTable_nudged, heTable_nudged) + + grad_ep = grad(_loss_ep_static, argnums=(5, 6, 7)) - grad_loss_ep_free_faces = grad(_loss_ep_static, argnums=7)( + grad_loss_ep_free_verts, grad_loss_ep_free_hes, grad_loss_ep_free_faces = grad_ep( vertTable_free, heTable_free, faceTable_free, @@ -956,7 +984,7 @@ def outer_eq_prop( beta=-beta, ) - grad_loss_ep_nudged_faces = grad(_loss_ep_static, argnums=7)( + grad_loss_ep_nudged_verts, grad_loss_ep_nudged_hes, grad_loss_ep_nudged_faces = grad_ep( vertTable_nudged, heTable_nudged, faceTable_nudged, @@ -990,7 +1018,7 @@ def outer_eq_prop( he_params = updated_params["he_params"] # type: ignore face_params = updated_params["face_params"] # type: ignore - return vert_params, he_params, face_params + return vertTable_free, heTable_free, faceTable_free, vert_params, he_params, face_params ########################### @@ -1023,7 +1051,7 @@ def outer_implicit( selected_faces: Array | None, image_target: Array | None, update_t1_func: UpdateT1Func = update_T1, -) -> tuple[Array, Array, Array]: +) -> tuple[Array, Array, Array, Array, Array, Array]: """Outer optimization for implicit differentiation method.""" def L_in_flatten( # noqa: N802 @@ -1037,6 +1065,8 @@ def L_in_flatten( # noqa: N802 vertTable_tmp = vertTable_flatten.reshape(len(vertTable_flatten) // 2, 2) return L_in(vertTable_tmp, heTable, faceTable, vert_params, he_params, face_params) + heTable_before = heTable + (vertTable_eq, heTable_eq, faceTable_eq), _ = inner_opt( vertTable, heTable, @@ -1058,6 +1088,16 @@ def L_in_flatten( # noqa: N802 update_t1_func, ) + perm = _build_t1_repair_perm( + vertTable_eq, + vertTable_target, + heTable_before, + heTable_eq, + width, + height, + ) + vertTable_eq, heTable_eq = _apply_perm_to_state(perm, vertTable_eq, heTable_eq) + vert_flat_eq = vertTable_eq.flatten() # H = d^2 L_in / d vertTable^2 @@ -1176,7 +1216,7 @@ def matvec(v: ArrayLike) -> Array: he_params = updated_params["he_params"] # type: ignore face_params = updated_params["face_params"] # type: ignore - return vert_params, he_params, face_params + return vertTable, heTable, faceTable, vert_params, he_params, face_params ########################## @@ -1209,7 +1249,7 @@ def outer_adjoint_state( selected_faces: Array | None, image_target: Array | None, update_t1_func: UpdateT1Func = update_T1, -) -> tuple[Array, Array, Array]: +) -> tuple[Array, Array, Array, Array, Array, Array]: """Outer optimization for adjoint state method.""" def L_in_flatten( # noqa: N802 @@ -1223,6 +1263,8 @@ def L_in_flatten( # noqa: N802 vertTable_tmp = vertTable_flatten.reshape(len(vertTable_flatten) // 2, 2) return L_in(vertTable_tmp, heTable, faceTable, vert_params, he_params, face_params) + heTable_before = heTable + (vertTable_eq, heTable_eq, faceTable_eq), _L_in_value = inner_opt( vertTable, heTable, @@ -1244,6 +1286,16 @@ def L_in_flatten( # noqa: N802 update_t1_func, ) + perm = _build_t1_repair_perm( + vertTable_eq, + vertTable_target, + heTable_before, + heTable_eq, + width, + height, + ) + vertTable_eq, heTable_eq = _apply_perm_to_state(perm, vertTable_eq, heTable_eq) + vertTable_eq_flat = vertTable_eq.flatten() G_op = grad(L_in_flatten, argnums=0) @@ -1310,7 +1362,7 @@ def matvec(v: ArrayLike) -> Array: he_params = updated_params["he_params"] # type: ignore face_params = updated_params["face_params"] # type: ignore - return vert_params, he_params, face_params + return vertTable, heTable, faceTable, vert_params, he_params, face_params ############# @@ -1349,7 +1401,7 @@ def bilevel_opt( """Bilevel optimization for PBC meshes.""" match method: case BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION: - vert_params, he_params, face_params = outer_opt( + vertTable, heTable, faceTable, vert_params, he_params, face_params = outer_opt( vertTable, heTable, faceTable, @@ -1377,7 +1429,7 @@ def bilevel_opt( ) case BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION: - vert_params, he_params, face_params = outer_eq_prop( + vertTable, heTable, faceTable, vert_params, he_params, face_params = outer_eq_prop( vertTable, heTable, faceTable, @@ -1406,7 +1458,7 @@ def bilevel_opt( ) case BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION: - vert_params, he_params, face_params = outer_implicit( + vertTable, heTable, faceTable, vert_params, he_params, face_params = outer_implicit( vertTable, heTable, faceTable, @@ -1434,7 +1486,7 @@ def bilevel_opt( ) case BilevelOptimizationMethod.ADJOINT_STATE: - vert_params, he_params, face_params = outer_adjoint_state( + vertTable, heTable, faceTable, vert_params, he_params, face_params = outer_adjoint_state( vertTable, heTable, faceTable, diff --git a/src/vertax/pbc.py b/src/vertax/pbc.py deleted file mode 100644 index dba4219..0000000 --- a/src/vertax/pbc.py +++ /dev/null @@ -1,1266 +0,0 @@ -"""Periodic Boundary Condition on a mesh.""" - -from collections.abc import Callable -from pathlib import Path -from typing import Literal, Self - -import jax -import jax.numpy as jnp -import matplotlib -import matplotlib.pyplot as plt -import numpy as np -from jax import Array -from matplotlib.axes import Axes -from matplotlib.figure import Figure -from numpy.typing import NDArray -from scipy.spatial import Voronoi - -from vertax.geo import get_area, get_length, get_length_with_offset, get_perimeter, update_pbc -from vertax.mask_analysis import find_vertices_edges_faces, mask_from_image, pad -from vertax.mesh import Mesh -from vertax.opt import ( - BilevelOptimizationMethod, - InnerLossFunction, - OuterLossFunction, - UpdateT1Func, - inner_opt, - outer_adjoint_state, - outer_eq_prop, - outer_implicit, - outer_opt, -) -from vertax.plot import EdgePlot, FacePlot, VertexPlot, add_colorbar, adjust_lightness, get_cmap -from vertax.topo import do_not_update_T1, update_T1 - - -class PbcMesh(Mesh): - """Periodic Boundary Condition on a mesh.""" - - def __init__(self) -> None: - """Do not call the constructor.""" - super().__init__() - - self.MAX_EDGES_IN_ANY_FACE: int = 20 - self._update_T1_func: UpdateT1Func = update_T1 - - @classmethod - def copy_mesh(cls, other_mesh: Self) -> Self: - """Copy all parameters from another mesh in a new mesh.""" - mesh = cls._create() - mesh.vertices = other_mesh.vertices.copy() - mesh.edges = other_mesh.edges.copy() - mesh.faces = other_mesh.faces.copy() - mesh.width = other_mesh.width - mesh.height = other_mesh.height - mesh.vertices_params = other_mesh.vertices_params.copy() - mesh.edges_params = other_mesh.edges_params.copy() - mesh.faces_params = other_mesh.faces_params.copy() - mesh.vertices_target = other_mesh.vertices_target.copy() - mesh.edges_target = other_mesh.edges_target.copy() - mesh.faces_target = other_mesh.faces_target.copy() - mesh.image_target = other_mesh.image_target.copy() - mesh.bilevel_optimization_method = other_mesh.bilevel_optimization_method - mesh.beta = other_mesh.beta - mesh.min_dist_T1 = other_mesh.min_dist_T1 - mesh.max_nb_iterations = other_mesh.max_nb_iterations - mesh.tolerance = other_mesh.tolerance - mesh.patience = other_mesh.patience - mesh.inner_solver = other_mesh.inner_solver - mesh.outer_solver = other_mesh.outer_solver - mesh.MAX_EDGES_IN_ANY_FACE = other_mesh.MAX_EDGES_IN_ANY_FACE - mesh._update_T1_func = other_mesh._update_T1_func - - return mesh - - def save_mesh_txt( - self, - directory: str, - vertices_filename: str = "vertTable.txt", - edges_filename: str = "heTable.txt", - faces_filename: str = "faceTable.txt", - ) -> None: - """Save a mesh in separate text files that can be read by numpy. - - Only save the vertices, edges and faces, not other parameters. - - Args: - directory (str): Path to the directory where to save the files. - vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt". - edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt". - faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt". - """ - dirpath = Path(directory) - dirpath.mkdir(parents=True, exist_ok=True) - vertpath = dirpath / vertices_filename - hepath = dirpath / edges_filename - facepath = dirpath / faces_filename - np.savetxt(vertpath, self.vertices) - np.savetxt(hepath, self.edges) - np.savetxt(facepath, self.faces) - - def save_mesh(self, path: str) -> None: - """Save mesh to a file. - - All PBCMesh data is saved, except for the solvers that are not saved. - - Args: - path (str): Path to the saved file. The extension is .npz, a numpy format. - """ - Path(path).parent.mkdir(parents=True, exist_ok=True) - np.savez_compressed( - path, - allow_pickle=False, - vertices=self.vertices, - edges=self.edges, - faces=self.faces, - width=self.width, - height=self.height, - vertices_params=self.vertices_params, - edges_params=self.edges_params, - faces_params=self.faces_params, - vertices_target=self.vertices_target, - edges_target=self.edges_target, - faces_target=self.faces_target, - image_target=self.image_target, - bilevel_optimization_method=self.bilevel_optimization_method.value, - beta=self.beta, - min_dist_T1=self.min_dist_T1, - max_nb_iterations=self.max_nb_iterations, - tolerance=self.tolerance, - patience=self.patience, - MAX_EDGES_IN_ANY_FACE=self.MAX_EDGES_IN_ANY_FACE, - update_t1=self.update_t1, - ) - - @classmethod - def load_mesh(cls, path: str) -> Self: - """Load a mesh from a file. - - All PBCMesh data is reloaded, except for the solvers that are not saved. - - Args: - path (str): Path to the mesh file (.npz), numpy format. - - Returns: - Mesh: the mesh loaded from the numpy .npz file. - """ - mesh_file = np.load(path) - mesh = cls._create() - mesh.vertices, mesh.edges, mesh.faces = mesh_file["vertices"], mesh_file["edges"], mesh_file["faces"] - mesh.width, mesh.height = mesh_file["width"], mesh_file["height"] - mesh.vertices_params = mesh_file["vertices_params"] - mesh.edges_params = mesh_file["edges_params"] - mesh.faces_params = mesh_file["faces_params"] - mesh.vertices_target = mesh_file["vertices_target"] - mesh.edges_target = mesh_file["edges_target"] - mesh.faces_target = mesh_file["faces_target"] - mesh.image_target = mesh_file["image_target"] - mesh.bilevel_optimization_method = BilevelOptimizationMethod(mesh_file["bilevel_optimization_method"]) - mesh.beta = mesh_file["beta"] - mesh.min_dist_T1 = mesh_file["min_dist_T1"] - mesh.max_nb_iterations = mesh_file["max_nb_iterations"] - mesh.tolerance = mesh_file["tolerance"] - mesh.patience = mesh_file["patience"] - mesh.MAX_EDGES_IN_ANY_FACE = mesh_file["MAX_EDGES_IN_ANY_FACE"] - mesh.update_t1 = mesh_file["update_t1"] - return mesh - - @classmethod - def load_mesh_txt( - cls, - directory: str, - vertices_filename: str = "vertTable.txt", - edges_filename: str = "heTable.txt", - faces_filename: str = "faceTable.txt", - ) -> Self: - """Load a mesh from text files. - - Only load the vertices, edges and faces, not other parameters. - - Args: - directory (str): Directory where the text files are stored. - vertices_filename (str, optional): Filename for the vertices table. Defaults to "vertTable.txt". - edges_filename (str, optional): Filename for the half-edges table. Defaults to "heTable.txt". - faces_filename (str, optional): Filename for the faces table. Defaults to "faceTable.txt". - - Returns: - Self: The loaded mesh. - """ - dirpath = Path(directory) - dirpath.mkdir(parents=True, exist_ok=True) - vertpath = dirpath / vertices_filename - hepath = dirpath / edges_filename - facepath = dirpath / faces_filename - - mesh = cls._create() - mesh.vertices = jnp.array(np.loadtxt(vertpath, dtype=np.float64)) - mesh.edges = jnp.array(np.loadtxt(hepath, dtype=np.int64)) - mesh.faces = jnp.array(np.loadtxt(facepath, dtype=np.int64)) - return mesh - - def get_length(self, half_edge_id: Array) -> Array: - """Get the length of an edge.""" - - def _get_length(half_edge_id: Array) -> Array: - return get_length(half_edge_id, self.vertices, self.edges, self.faces, self.width, self.height) - - return jax.vmap(_get_length)(half_edge_id) - - def get_length_with_offset(self, half_edge_id: Array) -> Array: - """Get the length of an edge along with its offsets in an array (length, offset x, offset y).""" - - def _get_length_with_offset(half_edge_id: Array) -> Array: - return get_length_with_offset(half_edge_id, self.vertices, self.edges, self.faces, self.width, self.height) - - return jax.vmap(_get_length_with_offset)(half_edge_id) - - def get_perimeter(self, face_id: Array) -> Array: - """Get the perimeter of a face.""" - - def _get_perimeter(face_id: Array) -> Array: - return get_perimeter( - face_id, self.vertices, self.edges, self.faces, self.width, self.height, self.MAX_EDGES_IN_ANY_FACE - ) - - return jax.vmap(_get_perimeter)(face_id) - - def get_area(self, face_id: Array) -> Array: - """Get the area of a face.""" - - def _get_area(face_id: Array) -> Array: - return get_area( - face_id, self.vertices, self.edges, self.faces, self.width, self.height, self.MAX_EDGES_IN_ANY_FACE - ) - - return jax.vmap(_get_area)(face_id) - - def update_boundary_conditions(self) -> None: - """Force periodic boundary conditions again after an update.""" - self.vertices, self.edges, self.faces = update_pbc( - self.vertices, self.edges, self.faces, self.width, self.height - ) - - @property - def update_t1(self) -> bool: - """Whether or not update the mesh by applying T1 transitions.""" - return self._update_T1_func != do_not_update_T1 - - @update_t1.setter - def update_t1(self, b: bool) -> None: - """Whether or not update the mesh by applying T1 transitions.""" - if b: - self._update_T1_func = update_T1 - else: - self._update_T1_func = do_not_update_T1 - - def inner_opt( - self, - loss_function_inner: InnerLossFunction, - only_on_vertices: None | list[int] = None, - only_on_edges: None | list[int] = None, - only_on_faces: None | list[int] = None, - ) -> list[float]: - """Optimize the mesh for the loss function given. - - Args: - loss_function_inner (InnerLossFunction): Loss function to optimize. - only_on_vertices (None | list[int] = None): Consider only a subset of vertices for the loss function. - All vertices if None. - only_on_edges (None | list[int] = None): Consider only a subset of edges for the loss function. - All edges if None. - only_on_faces (None | list[int] = None): Consider only a subset of faces for the loss function. - All faces if None. - - Returns: - list[float]: History of loss values during optimization. - """ - selected_vertices, selected_edges, selected_faces = None, None, None - if only_on_vertices is not None: - selected_vertices = jnp.array(only_on_vertices) - if only_on_edges is not None: - selected_edges = jnp.array(only_on_edges) - if only_on_faces is not None: - selected_faces = jnp.array(only_on_faces) - - (self.vertices, self.edges, self.faces), loss_history = inner_opt( - vertTable=self.vertices, - heTable=self.edges, - faceTable=self.faces, - width=self.width, - height=self.height, - vert_params=self.vertices_params, - he_params=self.edges_params, - face_params=self.faces_params, - L_in=loss_function_inner, - solver=self.inner_solver, - min_dist_T1=self.min_dist_T1, - iterations_max=self.max_nb_iterations, - tolerance=self.tolerance, - patience=self.patience, - selected_verts=selected_vertices, - selected_hes=selected_edges, - selected_faces=selected_faces, - update_t1_func=self._update_T1_func, - ) - return list(loss_history) - - def outer_opt( - self, - loss_function_inner: InnerLossFunction, - loss_function_outer: OuterLossFunction, - only_on_vertices: None | list[int] = None, - only_on_edges: None | list[int] = None, - only_on_faces: None | list[int] = None, - ) -> None: - """Outer optimization.""" - selected_vertices, selected_edges, selected_faces = None, None, None - if only_on_vertices is not None: - selected_vertices = jnp.array(only_on_vertices) - if only_on_edges is not None: - selected_edges = jnp.array(only_on_edges) - if only_on_faces is not None: - selected_faces = jnp.array(only_on_faces) - match self.bilevel_optimization_method: - case BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION: - self.vertices_params, self.edges_params, self.faces_params = outer_opt( - self.vertices, - self.edges, - self.faces, - self.width, - self.height, - self.vertices_params, - self.edges_params, - self.faces_params, - self.vertices_target, - self.edges_target, - self.faces_target, - loss_function_inner, - loss_function_outer, - self.inner_solver, - self.outer_solver, - self.min_dist_T1, - self.max_nb_iterations, - self.tolerance, - self.patience, - selected_vertices, - selected_edges, - selected_faces, - self.image_target, - self._update_T1_func, - ) - - case BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION: - self.vertices_params, self.edges_params, self.faces_params = outer_eq_prop( - self.vertices, - self.edges, - self.faces, - self.width, - self.height, - self.vertices_params, - self.edges_params, - self.faces_params, - self.vertices_target, - self.edges_target, - self.faces_target, - loss_function_inner, - loss_function_outer, - self.inner_solver, - self.outer_solver, - self.min_dist_T1, - self.max_nb_iterations, - self.tolerance, - self.patience, - selected_vertices, - selected_edges, - selected_faces, - self.image_target, - self.beta, - self._update_T1_func, - ) - - case BilevelOptimizationMethod.IMPLICIT_DIFFERENTIATION: - self.vertices_params, self.edges_params, self.faces_params = outer_implicit( - self.vertices, - self.edges, - self.faces, - self.width, - self.height, - self.vertices_params, - self.edges_params, - self.faces_params, - self.vertices_target, - self.edges_target, - self.faces_target, - loss_function_inner, - loss_function_outer, - self.inner_solver, - self.outer_solver, - self.min_dist_T1, - self.max_nb_iterations, - self.tolerance, - self.patience, - selected_vertices, - selected_edges, - selected_faces, - self.image_target, - self._update_T1_func, - ) - - case BilevelOptimizationMethod.ADJOINT_STATE: - self.vertices_params, self.edges_params, self.faces_params = outer_adjoint_state( - self.vertices, - self.edges, - self.faces, - self.width, - self.height, - self.vertices_params, - self.edges_params, - self.faces_params, - self.vertices_target, - self.edges_target, - self.faces_target, - loss_function_inner, - loss_function_outer, - self.inner_solver, - self.outer_solver, - self.min_dist_T1, - self.max_nb_iterations, - self.tolerance, - self.patience, - selected_vertices, - selected_edges, - selected_faces, - self.image_target, - self._update_T1_func, - ) - - case _: - msg = f"Method not recognized. Must be a BilevelOptimizationMethod. \ - Got {self.bilevel_optimization_method}." - raise ValueError(msg) - - def bilevel_opt( - self, - loss_function_inner: InnerLossFunction, - loss_function_outer: OuterLossFunction, - only_on_vertices: None | list[int] = None, - only_on_edges: None | list[int] = None, - only_on_faces: None | list[int] = None, - ) -> list[float]: - """Optimize the mesh for the loss function given. - - Args: - loss_function_inner (InnerLossFunction): Loss function to optimize. - loss_function_outer (OuterLossFunction): Loss function to optimize. - only_on_vertices (None | list[int] = None): Consider only a subset of vertices for the loss function. - All vertices if None. - only_on_edges (None | list[int] = None): Consider only a subset of edges for the loss function. - All edges if None. - only_on_faces (None | list[int] = None): Consider only a subset of faces for the loss function. - All faces if None. - - Returns: - list[float]: History of loss values during optimization. - """ - self.outer_opt(loss_function_inner, loss_function_outer, only_on_vertices, only_on_edges, only_on_faces) - return self.inner_opt(loss_function_inner, only_on_vertices, only_on_edges, only_on_faces) - - @classmethod - def periodic_voronoi_from_random_seeds(cls, nb_seeds: int, width: float, height: float, random_key: int) -> Self: - """Create a Periodic Voronoi Mesh from random seeds. - - Args: - nb_seeds (int): Number of random seeds to use. - width (float): Width of the rectangular domains the seeds will be in. - height (float): Height of the rectangular domains the seeds will be in. - random_key (int): Set the random key for reproducibility. - - Returns: - Self: The corresponding mesh. - """ - key = jax.random.PRNGKey(random_key) - seeds = jnp.array((width, height)) * jax.random.uniform(key, (nb_seeds, 2)) - return cls.periodic_voronoi_from_seeds(seeds, width, height) - - @classmethod - def periodic_voronoi_from_seeds(cls, seeds: Array, width: float, height: float) -> Self: - """Create a Periodic Voronoi Mesh from a list of seeds. - - The seeds are assumed to have positive x and y positions. - - Args: - seeds (Array[float32]): jax float array of seed positions of shape (nbSeeds, 2). - width (float): width of the box containing the seeds. - height (float): height of the box containing the seeds. - """ - ( - periodic_voronoi_vertices_idx, - periodic_voronoi_vertices_pos, - periodic_voronoi_edges, - offsets, - periodic_voronoi_faces, - ) = _make_periodic(seeds, width, height) - - vertices, edges, faces = _make_he_structure( - width, - height, - periodic_voronoi_vertices_idx, - periodic_voronoi_vertices_pos, - periodic_voronoi_edges, - offsets, - periodic_voronoi_faces, - ) - - pbc_mesh = cls._create() - pbc_mesh.vertices = jnp.array(vertices, dtype=np.float32) - pbc_mesh.edges = jnp.array(edges, dtype=np.int32) - pbc_mesh.faces = jnp.array(faces, dtype=np.int32) - pbc_mesh.width = width - pbc_mesh.height = height - - return pbc_mesh - - @classmethod - def periodic_from_image( - cls, - image: NDArray, - ) -> Self: - """Create a rudimentary mesh with periodic boundary conditions from an image. - - To do that, we perform a segmentation using Cellpose and we try to fill the holes. - The result will probably be imperfect and it will always be better if you - provide directly a mask (with no holes) with the function "periodic_from_mask". - - Args: - image (NDArray): The image which will act as a template for the mesh. - - Returns: - tuple[Array, Array, Array]: The vertices, half-edges and faces table of the mesh. - """ - return cls.periodic_from_mask(mask_from_image(image)) - - @classmethod - def periodic_from_mask( # noqa: C901 - cls, - mask: NDArray, - ) -> Self: - """Create a rudimentary mesh with periodic boundary conditions from a mask with no holes. - - Args: - mask (NDArray): The mask with no holes which will act as a template for the mesh. - - Returns: - tuple[Array, Array, Array]: The vertices, half-edges and faces table of the mesh. - """ - padded_mask = pad(mask, save=False, output_path="refined_and_padded_image.tiff") - # Find vertices, edges, faces - vertices, edges, faces = find_vertices_edges_faces(padded_mask) - - # imread tiff = Y is the first axis, X the second. - height: int = mask.shape[0] # original image length. Padded is 3 times bigger. - y_min = height / 2 - y_max = 2 * height + (height / 2) - width: int = mask.shape[1] - x_min = width / 2 - x_max = 2 * width + (width / 2) - - col0_mask = (vertices[:, 0] >= x_min) & (vertices[:, 0] < x_max) - col1_mask = (vertices[:, 1] >= y_min) & (vertices[:, 1] < y_max) - - periodic_vertices_idx = np.arange(len(vertices))[col0_mask & col1_mask] - periodic_vertices_pos = vertices[col0_mask & col1_mask] - - # store map between vertex id -> inside vertex id - inside_vertex: dict[int, int] = {idx: idx for idx in periodic_vertices_idx} - for i, vertex in enumerate(vertices): - if i not in periodic_vertices_idx: - x, y = vertex - if x < x_min: - x += 2 * width - elif x >= x_max: - x -= 2 * width - - if y < y_min: - y += 2 * height - elif y >= y_max: - y -= 2 * height - - # Find corresponding inside vertex to the outside dest vertex - for idx, pos in zip(periodic_vertices_idx, periodic_vertices_pos, strict=True): - if np.max(np.abs(pos - [x, y])) < 1: - inside_vertex[i] = idx - break - - edges_inside = [] - edges_outside = [] - offsets_inside = {} - offsets_outside = {} - visited = [] - - for e in edges: - if e[0] in periodic_vertices_idx and e[1] in periodic_vertices_idx: - edges_inside.append(tuple(sorted((e[0], e[1])))) - offsets_inside[(e[0], e[1])] = (0, 0) - offsets_inside[(e[1], e[0])] = (0, 0) - elif bool(e[0] in periodic_vertices_idx) != bool(e[1] in periodic_vertices_idx): - if e[0] in periodic_vertices_idx: - # origin in, dest out - # check x coord - if vertices[e[1]][0] < x_min: - offset_x1 = -1 - elif vertices[e[1]][0] >= x_max: - offset_x1 = 1 - else: - offset_x1 = 0 - - # Now check y coord - if vertices[e[1]][1] < y_min: - offset_y1 = -1 - elif vertices[e[1]][1] >= y_max: - offset_y1 = 1 - else: - offset_y1 = 0 - - # Find corresponding inside vertex to the outside dest vertex - if e[1] not in inside_vertex: - print(f"Error, no inside vertex found for vertex {e[1]}.") - else: - idx = inside_vertex[e[1]] - edges_outside.append(tuple(sorted((e[0], idx)))) - if (e[0], e[1]) not in visited and (e[1], e[0]) not in visited: - offsets_outside[(e[0], idx)] = (offset_x1, offset_y1) - offsets_outside[(idx, e[0])] = (-offset_x1, -offset_y1) - visited.append((e[0], e[1])) - visited.append((e[1], e[0])) - else: - # dest in, origin out - if vertices[e[0]][0] < x_min: - offset_x0 = -1 - elif vertices[e[0]][0] >= x_max: - offset_x0 = 1 - else: - offset_x0 = 0 - - if vertices[e[0]][1] < y_min: - offset_y0 = -1 - elif vertices[e[0]][1] >= y_max: - offset_y0 = 1 - else: - offset_y0 = 0 - - # Find corresponding inside vertex to the outside dest vertex - if e[0] not in inside_vertex: - print(f"Error, no inside vertex found for vertex {e[0]}.") - else: - idx = inside_vertex[e[0]] - edges_outside.append(tuple(sorted((idx, e[1])))) - if (e[0], e[1]) not in visited and (e[1], e[0]) not in visited: - offsets_outside[(idx, e[1])] = (-offset_x0, -offset_y0) - offsets_outside[(e[1], idx)] = (offset_x0, offset_y0) - visited.append((e[0], e[1])) - visited.append((e[1], e[0])) - - periodic_edges = list(set(edges_inside)) + list(set(edges_outside)) - offsets = offsets_inside | offsets_outside - - periodic_faces: list[set[int]] = [ - {inside_vertex[i] for i in face} for face in faces if any(v_id in periodic_vertices_idx for v_id in face) - ] - - vertices, edges, faces = _make_he_structure( - 2 * width, - 2 * height, - periodic_vertices_idx, # ty:ignore[invalid-argument-type] - periodic_vertices_pos, - periodic_edges, - offsets, - periodic_faces, - vertices_offset=(x_min, y_min), - ) - - pbc_mesh = cls._create() - pbc_mesh.vertices = jnp.array(vertices, dtype=np.float32) - pbc_mesh.edges = jnp.array(edges, dtype=np.int32) - pbc_mesh.faces = jnp.array(faces, dtype=np.int32) - pbc_mesh.width = 2 * width - pbc_mesh.height = 2 * height - - return pbc_mesh - - def plot( - self, - vertex_plot: VertexPlot = VertexPlot.BLACK, - edge_plot: EdgePlot = EdgePlot.BLACK, - face_plot: FacePlot = FacePlot.MULTICOLOR, - vertex_parameters_name: str = "", - edge_parameters_name: str = "", - face_parameters_name: str = "", - show: bool = True, - save: bool = False, - save_path: str = "pbc_mesh.png", - faces_cmap_name: str = "cividis", - edges_cmap_name: str = "coolwarm", - edges_width: float = 2, - vertices_cmap_name: str = "spring", - vertices_size: float = 20, - title: str = "", - ) -> None: - """Plot the mesh and decide to save and/or show the mesh or not.""" - fig, _ax = self.get_plot( - vertex_plot, - edge_plot, - face_plot, - vertex_parameters_name, - edge_parameters_name, - face_parameters_name, - faces_cmap_name, - edges_cmap_name, - edges_width, - vertices_cmap_name, - vertices_size, - title, - ) - - if save: - plt.savefig(save_path) - - if show: - plt.show() - - plt.close(fig) - - def get_plot( - self, - vertex_plot: VertexPlot = VertexPlot.BLACK, - edge_plot: EdgePlot = EdgePlot.BLACK, - face_plot: FacePlot = FacePlot.MULTICOLOR, - vertex_parameters_name: str = "", - edge_parameters_name: str = "", - face_parameters_name: str = "", - faces_cmap_name: str = "cividis", - edges_cmap_name: str = "coolwarm", - edges_width: float = 2, - vertices_cmap_name: str = "spring", - vertices_size: float = 20, - title: str = "", - ) -> tuple[Figure, Axes]: - """Get the matplotlib figure and and ax for one plot.""" - # Fates not used for pbc. - if face_plot == FacePlot.FATES: - face_plot = FacePlot.WHITE - - fig, _ = plt.subplots(layout="constrained") - ax = plt.gca() - self._plot_faces(fig, ax, face_plot, faces_cmap_name, face_parameters_name) - self._plot_edges(fig, ax, edge_plot, edges_cmap_name, edges_width, edge_parameters_name) - self._plot_vertices(fig, ax, vertex_plot, vertices_cmap_name, vertices_size, vertex_parameters_name) - - ax.set_title(title) - ax.set_aspect(self.height / self.width) - ax.set_xlim(0, self.width) - ax.set_ylim(0, self.height) - - return fig, ax - - def _plot_faces( - self, fig: Figure, ax: Axes, face_plot: FacePlot, faces_cmap_name: str, face_parameters_name: str - ) -> None: - multicolor_cmap = self._get_multicolor_face_cmap() - faces_cmap = matplotlib.colormaps.get_cmap(faces_cmap_name) - - v_max = 1 - v_min = 0 - values = jnp.array([1]) - # set the correct colorbar if needed - # Get values, min and max - cbar_label = "Face parameter" if face_parameters_name == "" else face_parameters_name - match face_plot: - case FacePlot.FACE_PARAMETER: - values = self.faces_params - case FacePlot.AREA: - values = self.get_area(jnp.arange(self.nb_faces)) - cbar_label = "Area of cell" - case FacePlot.PERIMETER: - values = self.get_perimeter(jnp.arange(self.nb_faces)) - cbar_label = "Perimeter of cell" - v_max = float(values.max()) - v_min = float(values.min()) - match face_plot: - case FacePlot.MULTICOLOR | FacePlot.WHITE: - pass - case _: - cbar = add_colorbar(fig, ax, v_min, v_max, faces_cmap) - cbar.ax.set_ylabel(cbar_label, rotation=270, labelpad=13) - cbar.ax.yaxis.set_ticks_position("left") - - # Draw each face - for face in range(len(self.faces)): - match face_plot: - # Find correct color depending on chosen colormap - case FacePlot.MULTICOLOR: - color = multicolor_cmap(face) - case FacePlot.WHITE: - color = (1, 1, 1, 1) - case _: - norm_val = 1 if v_max == v_min else (values[face] - v_min) / (v_max - v_min) - color = faces_cmap(norm_val) - # Find face's vertices and draw the corresponding polygon. - face_vertices = self._get_face_vertices(face) - self._draw_face(ax, face_vertices, color) - - def _get_face_vertices(self, face: int) -> NDArray: - """Get all vertices positions corresponding to a face.""" - start_he = self.faces[face] - he = start_he - - source_vertex_id = self.edges[he][3] - verts_sources = np.array([self.vertices[source_vertex_id]]) - # all_verts.append(self.vertices[v_source]) - - he_offset_x = self.edges[he][6] - he_offset_y = self.edges[he][7] - sum0_offsets = he_offset_x - sum1_offsets = he_offset_y - - he = self.edges[he][1] - - while he != start_he: - source_vertex_id = self.edges[he][3] - - # all_verts.append(self.vertices[v_source]) - - verts_sources = np.concatenate( - ( - verts_sources, - ( - np.array([self.vertices[source_vertex_id]]) - + np.array([sum0_offsets * self.width, sum1_offsets * self.height]) - ), - ), - axis=0, - ) - - he_offset_x = self.edges[he][6] - he_offset_y = self.edges[he][7] - sum0_offsets += he_offset_x - sum1_offsets += he_offset_y - - he = self.edges[he][1] - - source_vertex_id = self.edges[he][3] - verts_sources = np.concatenate((verts_sources, (np.array([self.vertices[source_vertex_id]]))), axis=0) - return verts_sources - - def _draw_face(self, ax: Axes, face_vertices: NDArray, color: tuple[float, float, float, float] | NDArray) -> None: - x = face_vertices[:, 0] - y = self.height - face_vertices[:, 1] - - ax.fill(x, y, color=color) - ax.fill(np.add(x, self.width), np.add(y, self.height), color=color) - ax.fill(np.add(x, -self.width), np.add(y, -self.height), color=color) - ax.fill(np.add(x, -self.width), np.add(y, self.height), color=color) - ax.fill(np.add(x, self.width), np.add(y, -self.height), color=color) - ax.fill(x, np.add(y, self.height), color=color) - ax.fill(x, np.add(y, -self.height), color=color) - ax.fill(np.add(x, self.width), y, color=color) - ax.fill(np.add(x, -self.width), y, color=color) - - def _get_multicolor_face_cmap(self) -> Callable[[int], tuple[float, float, float, float]]: - def cmap_light_hsv(n: int) -> Callable[[int], tuple[float, float, float, Literal[1]]]: - def light_hsv(i: int) -> tuple[float, float, float, Literal[1]]: - fun: Callable[[int], tuple[float, float, float, float]] = get_cmap(n, name="hsv") - return (*adjust_lightness(fun(i)[:3], 1.4), 1) - - return light_hsv - - return cmap_light_hsv(len(self.faces)) - - def _plot_edges( - self, - fig: Figure, - ax: Axes, - edge_plot: EdgePlot, - edges_cmap_name: str, - edges_width: float, - edge_parameters_name: str, - ) -> None: - if edge_plot != EdgePlot.INVISIBLE: - edge_params_cmap = matplotlib.colormaps.get_cmap(edges_cmap_name) - # set the correct colorbar - - v_max = 1 - v_min = 0 - values = jnp.array([1]) - # set the correct colorbar if needed - # Get values, min and max - cbar_label = "Edge parameter" if edge_parameters_name == "" else edge_parameters_name - match edge_plot: - case EdgePlot.EDGE_PARAMETER: - values = self.edges_params - case EdgePlot.LENGTH: - values = self.get_length(jnp.arange(2 * self.nb_edges)) - cbar_label = "Length of edge" - v_max = float(values.max()) - v_min = float(values.min()) - - match edge_plot: - case EdgePlot.BLACK: - pass - case _: - cbar = add_colorbar(fig, ax, v_min, v_max, edge_params_cmap) - cbar.ax.set_ylabel(cbar_label, rotation=270, labelpad=13) - cbar.ax.yaxis.set_ticks_position("left") - - # Draw each edge - for i, edge_entry in enumerate(self.edges): - # Find correct color depending on chosen colormap - match edge_plot: - case EdgePlot.BLACK: - color = (0, 0, 0, 1) - case _: - norm_val = 1 if v_max == v_min else (values[i] - v_min) / (v_max - v_min) - color = edge_params_cmap(norm_val) - # Draw the edge with color - self._draw_edge(ax, edge_entry, color, edges_width) - - def _draw_edge( - self, - ax: Axes, - edge_entry: tuple[int, int, int, int, int, int, int, int], - color: tuple[float, float, float, float] | NDArray, - edges_width: float, - ) -> None: - origin_id = edge_entry[3] - target_id = edge_entry[4] - he_offset_x = edge_entry[6] - he_offset_y = edge_entry[7] - - origin = self.vertices[origin_id] - target = self.vertices[target_id] + np.array([he_offset_x * self.width, he_offset_y * self.height]) - - points = np.array([origin, target]) - x = points[:, 0] - y = self.height - points[:, 1] - ax.plot(x, y, color=color, linewidth=edges_width) - - def _plot_vertices( - self, - fig: Figure, - ax: Axes, - vertex_plot: VertexPlot, - vertices_cmap_name: str, - vertices_size: float, - vertex_parameters_name: str, - ) -> None: - if vertex_plot != VertexPlot.INVISIBLE: - # set the correct colorbar - vertices_params_cmap = matplotlib.colormaps.get_cmap(vertices_cmap_name) - v_max = 1 - v_min = 0 - if vertex_plot == VertexPlot.VERTEX_PARAMETER: - v_max = float(self.vertices_params.max()) - v_min = float(self.vertices_params.min()) - cbar = add_colorbar(fig, ax, v_min, v_max, vertices_params_cmap) - cbar_label = "Vertex parameter" if vertex_parameters_name == "" else vertex_parameters_name - cbar.ax.set_ylabel(cbar_label, rotation=270, labelpad=13) - cbar.ax.yaxis.set_ticks_position("left") - # Draw each vertex - for i, vertex in enumerate(self.vertices): - # Find correct color depending on chosen colormap - match vertex_plot: - case VertexPlot.VERTEX_PARAMETER: - norm_val = 1 if v_max == v_min else (self.vertices_params[i] - v_min) / (v_max - v_min) - color = vertices_params_cmap(norm_val) - case VertexPlot.BLACK: - color = (0, 0, 0, 1) - case _: - color = (0, 0, 0, 0) - # Draw the vertex with color - ax.scatter(vertex[0], self.height - vertex[1], color=color, s=vertices_size) - - -def _make_periodic( # noqa: C901 - seeds: Array, - width: float, - height: float, -) -> tuple[ - NDArray[np.int32], - NDArray[np.float64], - list[tuple[int, int]], - dict[tuple[int, int], tuple[int, int]], - list[set[int]], -]: - n_cells = len(seeds) - - # PERIODIC VORONOI - VERTICES EDGES FACES - - if n_cells < 20: - print("\nWarning: [n_cells < 20] initial condition may not work as expected.\n") - - # add eight neighbor copies of the seeds - padded_seeds = np.concatenate( - ( - seeds, - np.add(seeds, np.full((n_cells, 2), [-width, +height])), - np.add(seeds, np.full((n_cells, 2), [0, +height])), - np.add(seeds, np.full((n_cells, 2), [width, +height])), - np.add(seeds, np.full((n_cells, 2), [-width, 0])), - np.add(seeds, np.full((n_cells, 2), [width, 0])), - np.add(seeds, np.full((n_cells, 2), [-width, -height])), - np.add(seeds, np.full((n_cells, 2), [0, -height])), - np.add(seeds, np.full((n_cells, 2), [width, -height])), - ), - axis=0, - ) - - voronoi = Voronoi(padded_seeds) - - vertices = voronoi.vertices - edges = voronoi.ridge_vertices - faces = voronoi.regions - - # original vertices and not from neighbor copies - col0_mask = (vertices[:, 0] >= 0.0) & (vertices[:, 0] <= width) - col1_mask = (vertices[:, 1] >= 0.0) & (vertices[:, 1] <= height) - - periodic_voronoi_vertices_idx: NDArray[np.int32] = np.arange(len(vertices))[col0_mask & col1_mask] # ty:ignore[invalid-assignment] - periodic_voronoi_vertices_pos: NDArray[np.float64] = vertices[col0_mask & col1_mask] - - edges_inside = [] - edges_outside = [] - offsets_inside = {} - offsets_outside = {} - visited = [] - - for e in edges: - source_in = e[0] in periodic_voronoi_vertices_idx - target_in = e[1] in periodic_voronoi_vertices_idx - if source_in and target_in: - edges_inside.append(tuple(sorted((e[0], e[1])))) - offsets_inside[(e[0], e[1])] = (0, 0) - offsets_inside[(e[1], e[0])] = (0, 0) - elif source_in: # and not target_in - if vertices[e[1]][0] < 0.0: - x = vertices[e[1]][0] + width - offset_x1 = -1 - elif vertices[e[1]][0] > width: - x = vertices[e[1]][0] - width - offset_x1 = 1 - else: - x = vertices[e[1]][0] - offset_x1 = 0 - if vertices[e[1]][1] < 0.0: - y = vertices[e[1]][1] + height - offset_y1 = -1 - elif vertices[e[1]][1] > height: - y = vertices[e[1]][1] - height - offset_y1 = 1 - else: - y = vertices[e[1]][1] - offset_y1 = 0 - for idx, pos in zip(periodic_voronoi_vertices_idx, periodic_voronoi_vertices_pos, strict=False): - if ((np.abs(pos[0] - x)) < 10**-8) and ((np.abs(pos[1] - y)) < 10**-8): - edges_outside.append(tuple(sorted((e[0], idx)))) - if (e[0], e[1]) not in visited and (e[1], e[0]) not in visited: - offsets_outside[(e[0], idx)] = (offset_x1, offset_y1) - offsets_outside[(idx, e[0])] = (-offset_x1, -offset_y1) - visited.append((e[0], e[1])) - visited.append((e[1], e[0])) - break - elif target_in: # and not source_in - if vertices[e[0]][0] < 0.0: - x = vertices[e[0]][0] + width - offset_x0 = -1 - elif vertices[e[0]][0] > width: - x = vertices[e[0]][0] - width - offset_x0 = 1 - else: - x = vertices[e[0]][0] - offset_x0 = 0 - if vertices[e[0]][1] < 0.0: - y = vertices[e[0]][1] + height - offset_y0 = -1 - elif vertices[e[0]][1] > height: - y = vertices[e[0]][1] - height - offset_y0 = 1 - else: - y = vertices[e[0]][1] - offset_y0 = 0 - for idx, pos in zip(periodic_voronoi_vertices_idx, periodic_voronoi_vertices_pos, strict=False): - if ((np.abs(pos[0] - x)) < 10**-8) and ((np.abs(pos[1] - y)) < 10**-8): - edges_outside.append(tuple(sorted((idx, e[1])))) - if (e[0], e[1]) not in visited and (e[1], e[0]) not in visited: - offsets_outside[(idx, e[1])] = (-offset_x0, -offset_y0) - offsets_outside[(e[1], idx)] = (offset_x0, offset_y0) - visited.append((e[0], e[1])) - visited.append((e[1], e[0])) - break - - periodic_voronoi_edges: list[tuple[int, int]] = list(set(edges_inside)) + list(set(edges_outside)) - - offsets: dict[tuple[int, int], tuple[int, int]] = offsets_inside | offsets_outside - - faces_inside = [] - faces_inside_outside = [] - for face in faces: - if face: - if all(item in periodic_voronoi_vertices_idx for item in face): - faces_inside.append(tuple(sorted(face))) - if any(item in face for item in periodic_voronoi_vertices_idx): - face_inside_outside = [] - for f in face: - if f in periodic_voronoi_vertices_idx: - face_inside_outside.append(f) - else: - if vertices[f][0] < 0.0: - x = vertices[f][0] + width - elif vertices[f][0] > width: - x = vertices[f][0] - width - else: - x = vertices[f][0] - if vertices[f][1] < 0.0: - y = vertices[f][1] + height - elif vertices[f][1] > height: - y = vertices[f][1] - height - else: - y = vertices[f][1] - for idx, pos in zip(periodic_voronoi_vertices_idx, periodic_voronoi_vertices_pos, strict=False): - if ((np.abs(pos[0] - x)) < 10**-8) and ((np.abs(pos[1] - y)) < 10**-8): - face_inside_outside.append(idx) - break - - faces_inside_outside.append(tuple(sorted(face_inside_outside))) - - periodic_voronoi_faces: list[set[int]] = list(set(faces_inside_outside)) - return ( - periodic_voronoi_vertices_idx, - periodic_voronoi_vertices_pos, - periodic_voronoi_edges, - offsets, - periodic_voronoi_faces, - ) - - -def _make_he_structure( # noqa: C901 - width: float, - height: float, - periodic_vertices_idx: NDArray[np.int32], - periodic_vertices_positions: NDArray[np.float64], - periodic_edges: list[tuple[int, int]], - offsets: dict[tuple[int, int], tuple[int, int]], - periodic_faces: list[set[int]], - vertices_offset: tuple[float, float] = (0, 0), -) -> tuple[NDArray[np.float64], NDArray[np.int32], NDArray[np.int32]]: - """Return half-edge structure with vertices, edges, faces. - - vertices is the position of vertices. - edges is a table of int with: - previous half-edge id in face, - next half-edge id in face, - twin half-edge id, - source vertex id, - target vertex id, - face it belongs id, - offset x, - offset y. - faces records just for each face the id of one of its edges. - """ - # HALF EDGE DATA STRUCTURE - - # Reciprocating edges - periodic_half_edges = [] - for e in periodic_edges: - periodic_half_edges.append(e) - periodic_half_edges.append((e[1], e[0])) - - # Finding clockwise (or counterclockwise) half edge set for each face - ordered_edges_periodic_faces = [] - for face in periodic_faces: - edges_face = [(f1, f2) for f1 in face for f2 in face if (f1, f2) in periodic_edges] - i = 0 - start_edge = edges_face[i] - ordered_face = [start_edge] - e = start_edge - visited = [e] - while sorted(edges_face) != sorted(visited): - if e[0] == start_edge[1] and e not in visited: - ordered_face.append(e) - start_edge = e - visited.append(e) - if e[1] == start_edge[1] and e not in visited: - ordered_face.append((e[1], e[0])) - start_edge = (e[1], e[0]) - visited.append(e) - i += 1 - e = edges_face[i % len(face)] - - order = 0 - sum0_offsets = 0 - sum1_offsets = 0 - points = [] - for e in ordered_face: - idx0 = list(periodic_vertices_idx).index(e[0]) - idx1 = list(periodic_vertices_idx).index(e[1]) - e_offsets = offsets[e] - - prev_sum0_offsets = sum0_offsets - prev_sum1_offsets = sum1_offsets - sum0_offsets += e_offsets[0] - sum1_offsets += e_offsets[1] - - order += ( - (periodic_vertices_positions[idx1][0] + sum0_offsets * width) - - (periodic_vertices_positions[idx0][0] + prev_sum0_offsets * width) - ) * ( - (periodic_vertices_positions[idx1][1] + sum1_offsets * height) - + (periodic_vertices_positions[idx0][1] + prev_sum1_offsets * height) - ) - - points.append( - ( - periodic_vertices_positions[idx0][0] + prev_sum0_offsets * width, - periodic_vertices_positions[idx0][1] + prev_sum1_offsets * height, - ) - ) - - points.append( - ( - periodic_vertices_positions[idx1][0] + sum0_offsets * width, - periodic_vertices_positions[idx1][1] + sum1_offsets * height, - ) - ) - - if order < 0: - ordered_edges_periodic_faces.append(ordered_face) - if order > 0: - new_ordered_face = [(e[1], e[0]) for e in reversed(ordered_face)] - ordered_edges_periodic_faces.append(new_ordered_face) - if order == 0: - print("\nError: no order detected for face " + str(face) + "\n") - exit() - - # VERT FACE HE TABLES - - vertTable = periodic_vertices_positions - vertices_offset - - faceTable = np.zeros(len(periodic_faces), dtype=np.int32) - for i, hedges_face in enumerate(ordered_edges_periodic_faces): - for j, he in enumerate(periodic_half_edges): - if he == hedges_face[0]: - faceTable[i] = j # he_inside - - heTable = np.zeros((len(periodic_half_edges), 8), dtype=np.int32) - for i, he in enumerate(periodic_half_edges): - for hedges_face in ordered_edges_periodic_faces: - if he in hedges_face: - idx = hedges_face.index(he) - heTable[i][0] = periodic_half_edges.index(hedges_face[(idx - 1) % len(hedges_face)]) # he_prev - heTable[i][1] = periodic_half_edges.index(hedges_face[(idx + 1) % len(hedges_face)]) # he_next - heTable[i][3] = list(periodic_vertices_idx).index(he[0]) # vert source - heTable[i][4] = list(periodic_vertices_idx).index(he[1]) # vert target - heTable[i][5] = ordered_edges_periodic_faces.index(hedges_face) # face - break - heTable[i][2] = periodic_half_edges.index((he[1], he[0])) # he twin - heTable[i][6] = offsets[he][0] # he_offset x vert target - heTable[i][7] = offsets[he][1] # he_offset y vert target - return (vertTable, heTable, faceTable) diff --git a/src/vertax/plot.py b/src/vertax/plot.py deleted file mode 100644 index 94a7485..0000000 --- a/src/vertax/plot.py +++ /dev/null @@ -1,653 +0,0 @@ -"""General plotting functions.""" - -import colorsys -from collections.abc import Callable -from enum import Enum -from pathlib import Path - -import matplotlib -import matplotlib.pyplot as plt -import numpy as np -from jax import Array -from matplotlib.axes import Axes -from matplotlib.colorbar import Colorbar -from matplotlib.colors import Colormap, to_rgb -from matplotlib.figure import Figure -from matplotlib.patches import Arc -from numpy.typing import NDArray - - -class FacePlot(Enum): - """What it is possible to show on a face.""" - - MULTICOLOR = 1 - FACE_PARAMETER = 2 - AREA = 3 - PERIMETER = 4 - WHITE = 5 - FATES = 6 - - -class EdgePlot(Enum): - """What it is possible to show on an edge.""" - - BLACK = 1 - EDGE_PARAMETER = 2 - LENGTH = 3 - INVISIBLE = 4 - - -class VertexPlot(Enum): - """What it is possible to show on a vertex.""" - - BLACK = 1 - VERTEX_PARAMETER = 2 - INVISIBLE = 3 - - -def add_colorbar(fig: Figure, ax: Axes, v_min: float, v_max: float, cmap: Colormap) -> Colorbar: - """Add a colorbar to the figure, with given min and max values and colormap.""" - fake_im = ax.imshow([[1]], vmin=v_min, vmax=v_max, cmap=cmap) - colorbar = fig.colorbar(fake_im, ax=ax, shrink=0.7) - fake_im.set_alpha(0) - return colorbar - - -def adjust_lightness(color: tuple[float, float, float], amount: float = 0.5) -> tuple[float, float, float]: - """Adjust lightness of a color.""" - color = colorsys.rgb_to_hls(*to_rgb(color)) - return colorsys.hls_to_rgb(color[0], max(0, min(1, amount * color[1])), color[2]) - - -def get_cmap(n: int, name: str = "hsv") -> Callable[[int], tuple[float, float, float, float]]: - """Returns a function that maps each index in 0, 1, ..., n-1 to a distinct RGB color. - - The keyword argument name must be a standard mpl colormap name. - """ - - def cmap_with_n_colors(i: int) -> tuple[float, float, float, float]: - cmap = matplotlib.colormaps.get_cmap(name) - return cmap(i / (n - 1)) - - return cmap_with_n_colors - - -def plot_mesh( # noqa: C901 - vertTable: Array, - heTable: Array, - faceTable: Array, - width: float, - height: float, - flip_x: bool = False, - flip_y: bool = False, - multicolor: bool = True, - lines: bool = True, - vertices: bool = True, - path: str = ".", - name: str = "-1", - save: bool = False, - show: bool = True, -) -> None: - """General function to plot a mesh with periodic boundary conditions.""" - cmap = get_cmap(len(faceTable)) - all_verts = [] - for face in range(len(faceTable)): - start_he = faceTable[face] - he = start_he - - v_source = heTable[he][3] - verts_sources = np.array([vertTable[v_source]]) - all_verts.append(vertTable[v_source]) - - he_offset_x = heTable[he][6] - he_offset_y = heTable[he][7] - sum0_offsets = he_offset_x - sum1_offsets = he_offset_y - - he = heTable[he][1] - - while he != start_he: - v_source = heTable[he][3] - - all_verts.append(vertTable[v_source]) - - verts_sources = np.concatenate( - ( - verts_sources, - (np.array([vertTable[v_source]]) + np.array([sum0_offsets * width, sum1_offsets * height])), - ), - axis=0, - ) - - he_offset_x = heTable[he][6] - he_offset_y = heTable[he][7] - sum0_offsets += he_offset_x - sum1_offsets += he_offset_y - - he = heTable[he][1] - - v_source = heTable[he][3] - verts_sources = np.concatenate((verts_sources, (np.array([vertTable[v_source]]))), axis=0) - x, y = zip(*verts_sources, strict=False) - - if flip_x: - x = tuple(np.array((width,) * len(x)) - x) - if not flip_y: - y = tuple(np.array((height,) * len(y)) - y) - - if multicolor: - plt.fill(x, y, color=cmap(face), alpha=0.5) - plt.fill(np.add(x, width), np.add(y, height), color=cmap(face), alpha=0.5) - plt.fill(np.add(x, -width), np.add(y, -height), color=cmap(face), alpha=0.5) - plt.fill(np.add(x, -width), np.add(y, height), color=cmap(face), alpha=0.5) - plt.fill(np.add(x, width), np.add(y, -height), color=cmap(face), alpha=0.5) - plt.fill(x, np.add(y, height), color=cmap(face), alpha=0.5) - plt.fill(x, np.add(y, -height), color=cmap(face), alpha=0.5) - plt.fill(np.add(x, width), y, color=cmap(face), alpha=0.5) - plt.fill(np.add(x, -width), y, color=cmap(face), alpha=0.5) - - if lines: - for i in range(0, len(x) - 1, 1): - plt.plot(x[i : i + 2], y[i : i + 2], "-", color="black") - plt.plot( - tuple(np.add(x[i : i + 2], (width, width))), - tuple(np.add(y[i : i + 2], (height, height))), - "-", - color="black", - ) - plt.plot( - tuple(np.add(x[i : i + 2], (-width, -width))), - tuple(np.add(y[i : i + 2], (-height, -height))), - "-", - color="black", - ) - plt.plot( - tuple(np.add(x[i : i + 2], (-width, -width))), - tuple(np.add(y[i : i + 2], (height, height))), - "-", - color="black", - ) - plt.plot( - tuple(np.add(x[i : i + 2], (width, width))), - tuple(np.add(y[i : i + 2], (-height, -height))), - "-", - color="black", - ) - plt.plot(x[i : i + 2], tuple(np.add(y[i : i + 2], (height, height))), "-", color="black") - plt.plot(x[i : i + 2], tuple(np.add(y[i : i + 2], (-height, -height))), "-", color="black") - plt.plot(tuple(np.add(x[i : i + 2], (width, width))), y[i : i + 2], "-", color="black") - plt.plot(tuple(np.add(x[i : i + 2], (-width, -width))), y[i : i + 2], "-", color="black") - - if vertices: - all_verts = np.array(all_verts) - plt.scatter(all_verts[:, 0], height - all_verts[:, 1], color="black") - - plt.xlim([0, width]) - plt.ylim([0, height]) - - plt.gca().set_aspect("equal") - - if save: - Path(path).mkdir(exist_ok=True) - # plt.savefig(path + str(name) + '.svg', format='svg') - plt.savefig(path + str(name) + ".png", format="png") - - if show: - plt.show() - - plt.clf() - - -def plot_mesh_selected( # noqa: C901 - vertTable: Array, - heTable: Array, - faceTable: Array, - selected_faces: Array, - width: float, - height: float, - flip_x: bool = False, - flip_y: bool = False, - multicolor: bool = True, - lines: bool = True, - vertices: bool = True, - path: str = ".", - name: str = "-1", - save: bool = False, - show: bool = True, -) -> None: - """Plot only selected faces.""" - cmap = get_cmap(len(faceTable)) - - all_verts = [] - for face in selected_faces: - face = int(face) - - start_he = faceTable[face] - he = start_he - - v_source = heTable[he][3] - - verts_sources = np.array([vertTable[v_source]]) - all_verts.append(vertTable[v_source]) - - he_offset_x = heTable[he][6] - he_offset_y = heTable[he][7] - sum0_offsets = he_offset_x - sum1_offsets = he_offset_y - - he = heTable[he][1] - - while he != start_he: - v_source = heTable[he][3] - - all_verts.append(vertTable[v_source]) - - verts_sources = np.concatenate( - ( - verts_sources, - (np.array([vertTable[v_source]]) + np.array([sum0_offsets * width, sum1_offsets * height])), - ), - axis=0, - ) - - he_offset_x = heTable[he][6] - he_offset_y = heTable[he][7] - sum0_offsets += he_offset_x - sum1_offsets += he_offset_y - - he = heTable[he][1] - - v_source = heTable[he][3] - verts_sources = np.concatenate((verts_sources, (np.array([vertTable[v_source]]))), axis=0) - - x, y = zip(*verts_sources, strict=False) - - if flip_x: - x = tuple(np.array((width,) * len(x)) - x) - if flip_y: - y = tuple(np.array((height,) * len(y)) - y) - - if multicolor: - plt.fill(x, y, color=cmap(face), alpha=0.5) - plt.fill(np.add(x, width), np.add(y, height), color=cmap(face), alpha=0.5) - plt.fill(np.add(x, -width), np.add(y, -height), color=cmap(face), alpha=0.5) - plt.fill(np.add(x, -width), np.add(y, height), color=cmap(face), alpha=0.5) - plt.fill(np.add(x, width), np.add(y, -height), color=cmap(face), alpha=0.5) - plt.fill(x, np.add(y, height), color=cmap(face), alpha=0.5) - plt.fill(x, np.add(y, -height), color=cmap(face), alpha=0.5) - plt.fill(np.add(x, width), y, color=cmap(face), alpha=0.5) - plt.fill(np.add(x, -width), y, color=cmap(face), alpha=0.5) - - if lines: - for i in range(0, len(x) - 1, 1): - plt.plot(x[i : i + 2], y[i : i + 2], "-", color="black") - plt.plot( - tuple(np.add(x[i : i + 2], (width, width))), - tuple(np.add(y[i : i + 2], (height, height))), - "-", - color="black", - ) - plt.plot( - tuple(np.add(x[i : i + 2], (-width, -width))), - tuple(np.add(y[i : i + 2], (-height, -height))), - "-", - color="black", - ) - plt.plot( - tuple(np.add(x[i : i + 2], (-width, -width))), - tuple(np.add(y[i : i + 2], (height, height))), - "-", - color="black", - ) - plt.plot( - tuple(np.add(x[i : i + 2], (width, width))), - tuple(np.add(y[i : i + 2], (-height, -height))), - "-", - color="black", - ) - plt.plot(x[i : i + 2], tuple(np.add(y[i : i + 2], (height, height))), "-", color="black") - plt.plot(x[i : i + 2], tuple(np.add(y[i : i + 2], (-height, -height))), "-", color="black") - plt.plot(tuple(np.add(x[i : i + 2], (width, width))), y[i : i + 2], "-", color="black") - plt.plot(tuple(np.add(x[i : i + 2], (-width, -width))), y[i : i + 2], "-", color="black") - - if vertices: - x_all, y_all = zip(*all_verts, strict=False) - plt.scatter(x_all, y_all, color="black") - - plt.xlim([0, width]) - plt.ylim([0, height]) - - plt.gca().set_aspect("equal") - - if save: - Path(path).mkdir(exist_ok=True) - plt.savefig(path + str(name) + ".svg", format="svg") - plt.savefig(path + str(name) + ".png", format="png") - - if show: - plt.show() - - plt.clf() - - -def plot_line_tens( # noqa: C901 - vertTable: Array, - heTable: Array, - faceTable: Array, - file_txt: str, - width: float, - height: float, - flip_x: bool = False, - flip_y: bool = False, - multicolor: bool = False, - lines: bool = True, - vertices: bool = False, - path: str = ".", - name: str = "-1", - save: bool = False, - show: bool = True, -) -> None: - """Plot mesh with line tensions.""" - - def load_last_np_array(filename: str) -> NDArray: - with Path(filename).open() as file: - content = file.read().strip() - # Split into separate arrays (assuming each starts with '[' and ends with ']') - arrays = content.split("]\n[") - last_array_text = arrays[-1].replace("[", "").replace("]", "") - # Convert string to NumPy array - last_array = np.fromstring(last_array_text, sep=" ") - return last_array - - red_intensity = load_last_np_array(file_txt) - print(red_intensity) - red_intensity = (red_intensity - np.min(red_intensity)) / (np.max(red_intensity) - np.min(red_intensity)) - - cmap = get_cmap(len(faceTable)) - - all_verts = [] - for face in range(len(faceTable)): - red_intensity_face = [] - - start_he = faceTable[face] - he = start_he - - red_intensity_face.append(red_intensity[he]) - - v_source = heTable[he][3] - - verts_sources = np.array([vertTable[v_source]]) - all_verts.append(vertTable[v_source]) - - he_offset_x = heTable[he][6] - he_offset_y = heTable[he][7] - sum0_offsets = he_offset_x - sum1_offsets = he_offset_y - - he = heTable[he][1] - - while he != start_he: - red_intensity_face.append(red_intensity[he]) - - v_source = heTable[he][3] - - all_verts.append(vertTable[v_source]) - - verts_sources = np.concatenate( - ( - verts_sources, - (np.array([vertTable[v_source]]) + np.array([sum0_offsets * width, sum1_offsets * height])), - ), - axis=0, - ) - - he_offset_x = heTable[he][6] - he_offset_y = heTable[he][7] - sum0_offsets += he_offset_x - sum1_offsets += he_offset_y - - he = heTable[he][1] - - v_source = heTable[he][3] - verts_sources = np.concatenate((verts_sources, (np.array([vertTable[v_source]]))), axis=0) - - x, y = zip(*verts_sources, strict=False) - - if flip_x: - x = tuple(np.array((width,) * len(x)) - x) - if flip_y: - y = tuple(np.array((height,) * len(y)) - y) - - if multicolor: - plt.fill(x, y, color=cmap(face), alpha=0.5) - plt.fill(np.add(x, width), np.add(y, height), color=cmap(face), alpha=0.5) - plt.fill(np.add(x, -width), np.add(y, -height), color=cmap(face), alpha=0.5) - plt.fill(np.add(x, -width), np.add(y, height), color=cmap(face), alpha=0.5) - plt.fill(np.add(x, width), np.add(y, -height), color=cmap(face), alpha=0.5) - plt.fill(x, np.add(y, height), color=cmap(face), alpha=0.5) - plt.fill(x, np.add(y, -height), color=cmap(face), alpha=0.5) - plt.fill(np.add(x, width), y, color=cmap(face), alpha=0.5) - plt.fill(np.add(x, -width), y, color=cmap(face), alpha=0.5) - - if lines: - for i in range(0, len(x) - 1, 1): - r = red_intensity_face[i] # Assumi che red_intensity sia una lista con valori tra 0 e 1 - color = (r, 0, 0) # (rosso, verde, blu) - plt.plot(x[i : i + 2], y[i : i + 2], "-", color=color, lw=0.5 + r * 3.0) - plt.plot( - tuple(np.add(x[i : i + 2], (width, width))), - tuple(np.add(y[i : i + 2], (height, height))), - "-", - color=color, - lw=0.5 + r * 3.0, - ) - plt.plot( - tuple(np.add(x[i : i + 2], (-width, -width))), - tuple(np.add(y[i : i + 2], (-height, -height))), - "-", - color=color, - lw=0.5 + r * 3.0, - ) - plt.plot( - tuple(np.add(x[i : i + 2], (-width, -width))), - tuple(np.add(y[i : i + 2], (height, height))), - "-", - color=color, - lw=0.5 + r * 3.0, - ) - plt.plot( - tuple(np.add(x[i : i + 2], (width, width))), - tuple(np.add(y[i : i + 2], (-height, -height))), - "-", - color=color, - lw=0.5 + r * 3.0, - ) - plt.plot( - x[i : i + 2], tuple(np.add(y[i : i + 2], (height, height))), "-", color=color, lw=0.5 + r * 3.0 - ) - plt.plot( - x[i : i + 2], tuple(np.add(y[i : i + 2], (-height, -height))), "-", color=color, lw=0.5 + r * 3.0 - ) - plt.plot(tuple(np.add(x[i : i + 2], (width, width))), y[i : i + 2], "-", color=color, lw=0.5 + r * 3.0) - plt.plot( - tuple(np.add(x[i : i + 2], (-width, -width))), y[i : i + 2], "-", color=color, lw=0.5 + r * 3.0 - ) - - if vertices: - x_all, y_all = zip(*all_verts, strict=False) - plt.scatter(x_all, y_all, color="black") - - plt.xlim([0, width]) - plt.ylim([0, height]) - - plt.gca().set_aspect("equal") - - if save: - Path(path).mkdir(exist_ok=True) - # plt.savefig(path + str(name) + '.svg', format='svg') - plt.savefig(path + str(name) + ".png", format="png") - - if show: - plt.show() - - plt.clf() - - -# ========== -# Bounded -# ========== -def draw_arc_N_get_points( # noqa: N802 - ang: Array, pos_source: Array, pos_target: Array, lines: bool, n: int = 100 -) -> tuple[NDArray, NDArray]: - """Draw an arc of a circle with N points.""" - edge_vector = pos_target - pos_source - edge_half_length = np.linalg.norm(edge_vector) / 2 - radius = edge_half_length / np.sin(ang) - diameter = 2 * radius - d_midpoint_to_center = np.sqrt(radius**2 - edge_half_length**2) - midpoint = (pos_source + pos_target) / 2 - unit_vector = edge_vector / np.linalg.norm(edge_vector) - unit_vector_midpoint_to_center = np.array([-unit_vector[1], unit_vector[0]]) - center = midpoint + unit_vector_midpoint_to_center * d_midpoint_to_center - ang_source = np.angle(np.dot((pos_source - center), np.array([1, 1j]))) - ang_target = np.angle(np.dot((pos_target - center), np.array([1, 1j]))) - rad2deg = 180 / np.pi - normalized_ang_source_degrees = ang_source * rad2deg - normalized_ang_target_degrees = ang_target * rad2deg - if normalized_ang_source_degrees < 0.0: - normalized_ang_source_degrees += 360 - if normalized_ang_target_degrees < 0.0: - normalized_ang_target_degrees += 360 - if lines: - surface_arc = Arc( - center, - diameter, - diameter, - theta1=normalized_ang_source_degrees, - theta2=normalized_ang_target_degrees, - color="black", - linewidth=2.0, - ) - plt.gca().add_patch(surface_arc) - tau = 2 * np.pi - if abs(ang_target - ang_source) > np.pi: - if ang_source < ang_target: - ang_source += tau - else: - ang_target += tau - intermediate_angles = np.linspace(ang_source, ang_target, n, False)[1:] - points = [pos_source] - points.extend([radius * np.array([np.cos(a), np.sin(a)]) + center for a in intermediate_angles]) - points.append(pos_target) - points.append(pos_source) - x, y = zip(*points, strict=False) - return x, y - - -def plot_bounded_mesh( # noqa: C901 - vertTable: NDArray, - angTable: NDArray, - heTable: NDArray, - faceTable: NDArray, - L_box: float, - flip_x: bool = False, - flip_y: bool = False, - multicolor: bool = True, - lines: bool = True, - vertices: bool = False, - fates: bool = False, - path: str = ".", - name: str = "-1", - save: bool = False, - show: bool = True, -) -> None: - """Plot a bounded mesh.""" - cmap = get_cmap(np.max(faceTable[:, 1]) + 1, name="viridis") if fates else get_cmap(faceTable.shape[0]) - draw_curve_threshold = 0.01 # radians. Must be above 0 to avoid overcomplicating a simple plot - - all_verts = [] - for face in range(faceTable.shape[0]): - surfaces = [] - is_surface = False - start_he = int(faceTable[face, 0]) - he = start_he - v_source = int(heTable[he][3] + heTable[he][5]) - pos_source = vertTable[v_source - 2] - if heTable[he][3] == 0: - ang = angTable[he // 2] - if ang > draw_curve_threshold: - is_surface = True - v_target = int(heTable[he][4] + heTable[he][6] - 1) - pos_target = vertTable[v_target - 2] - arcx, arcy = draw_arc_N_get_points(ang, pos_source, pos_target, lines) - if multicolor: - if fates: - plt.fill(arcx, arcy, facecolor=cmap(faceTable[face, 1]), alpha=0.5) - else: - plt.fill(arcx, arcy, facecolor=cmap(face), alpha=0.5) - verts_sources = np.array([pos_source]) - all_verts.append(pos_source) - surfaces.append(is_surface) - he = int(heTable[he][1]) - - while he != start_he: - is_surface = False - v_source = int(heTable[he][3] + heTable[he][5]) - pos_source = vertTable[v_source - 2] - if heTable[he][3] == 0: - ang = angTable[he // 2] - if ang > draw_curve_threshold: - is_surface = True - v_target = int(heTable[he][4] + heTable[he][6] - 1) - pos_target = vertTable[v_target - 2] - arcx, arcy = draw_arc_N_get_points(ang, pos_source, pos_target, lines) - if multicolor: - if fates: - plt.fill(arcx, arcy, facecolor=cmap(faceTable[face, 1]), alpha=0.5) - else: - plt.fill(arcx, arcy, facecolor=cmap(face), alpha=0.5) - all_verts.append(pos_source) - verts_sources = np.concatenate([verts_sources, pos_source[None]], axis=0) - surfaces.append(is_surface) - he = int(heTable[he][1]) - - v_source = int(heTable[he][3] + heTable[he][5]) - pos_source = vertTable[v_source - 2] - verts_sources = np.concatenate([verts_sources, pos_source[None]], axis=0) - - x, y = zip(*verts_sources, strict=False) - - if flip_x: - x = tuple(np.array((L_box,) * len(x)) - x) - if flip_y: - y = tuple(np.array((L_box,) * len(y)) - y) - - if multicolor: - if fates: - plt.fill(x, y, facecolor=cmap(faceTable[face, 1]), alpha=0.5) - else: - plt.fill(x, y, facecolor=cmap(face), alpha=0.5) - - if lines: - for i in range(0, len(x) - 1, 1): - if not surfaces[i]: - plt.plot(x[i : i + 2], y[i : i + 2], "-", color="black") - - if vertices: - x_all, y_all = zip(*all_verts, strict=False) - plt.scatter(x_all, y_all, color="black") - - # unlike the pbc case, here is not easy to know a priori what the limits of the progressively optimized cell cluster - # will be (across a stack of images) - plt.xlim([-1.5, L_box + 0.5]) - plt.ylim([-1.5, L_box + 0.5]) - plt.gca().set_aspect("equal") - - if save: - Path(path).mkdir(exist_ok=True) - plt.savefig(path + str(name) + ".pdf") # format maybe should be left as a choice - - if show: - plt.show() - - plt.clf() diff --git a/src/vertax/start.py b/src/vertax/start.py deleted file mode 100644 index 840480c..0000000 --- a/src/vertax/start.py +++ /dev/null @@ -1,796 +0,0 @@ -"""Module for mesh creation, from given seeds or an image.""" - -from __future__ import annotations - -from pathlib import Path -from typing import TYPE_CHECKING - -import jax.numpy as jnp -import numpy as np -from scipy.spatial import Voronoi - -from vertax.mask_analysis import find_vertices_edges_faces, mask_from_image, pad -from vertax.plot import plot_bounded_mesh - -if TYPE_CHECKING: - from jax import Array - from matplotlib.pylab import Generator - from numpy.typing import NDArray - - -def load_mesh(path: str) -> tuple[Array, Array, Array]: - """Load a mesh from a file. - - Args: - path (str): Path to the mesh file. - - Returns: - tuple[Array, Array, Array]: Jax arrays of vertices (2D, float), edges and faces. - """ - mesh_file = np.load(path) - return (jnp.array(mesh_file["vertices"]), jnp.array(mesh_file["edges"]), jnp.array(mesh_file["faces"])) - # return jnp.load(path + "vertTable.npy"), jnp.load(path + "heTable.npy"), jnp.load(path + "faceTable.npy") - - -def save_mesh(path: str, vertTable: Array, heTable: Array, faceTable: Array) -> None: - """Save a mesh to a file. - - Args: - path (str): Path to the saved file. - vertTable (Array): The vertices of the mesh. - heTable (Array): The half-edges of the mesh. - faceTable (Array): The faces of the mesh. - """ - Path(path).parent.mkdir(parents=True, exist_ok=True) - np.savez_compressed(path, allow_pickle=False, vertices=vertTable, edges=heTable, faces=faceTable) - - -def create_mesh_from_seeds(seeds: Array) -> tuple[Array, Array, Array]: # noqa: C901 - """Create a Periodic Voronoi Mesh from a list of seeds. - - The result is given in a Half-Edge structure. - - Args: - seeds (Array): 2D float jax array of seed positions. - - Returns: - tuple[Array, Array, Array]: The vertices, half-edges and faces table of the mesh. - """ - n_cells = len(seeds) - L_box = np.sqrt(n_cells) - - # PERIODIC VORONOI - VERTICES EDGES FACES - - if n_cells < 20: - print("\nWarning: [n_cells < 20] initial condition may not work as expected.\n") - - padded_seeds = np.concatenate( - ( - seeds, - np.add(seeds, np.full((n_cells, 2), [-L_box, +L_box])), - np.add(seeds, np.full((n_cells, 2), [0, +L_box])), - np.add(seeds, np.full((n_cells, 2), [L_box, +L_box])), - np.add(seeds, np.full((n_cells, 2), [-L_box, 0])), - np.add(seeds, np.full((n_cells, 2), [L_box, 0])), - np.add(seeds, np.full((n_cells, 2), [-L_box, -L_box])), - np.add(seeds, np.full((n_cells, 2), [0, -L_box])), - np.add(seeds, np.full((n_cells, 2), [L_box, -L_box])), - ), - axis=0, - ) - - voronoi = Voronoi(padded_seeds) - - vertices = voronoi.vertices - edges = voronoi.ridge_vertices - faces = voronoi.regions - - col0_mask = (vertices[:, 0] >= 0.0) & (vertices[:, 0] <= L_box) - col1_mask = (vertices[:, 1] >= 0.0) & (vertices[:, 1] <= L_box) - - periodic_voronoi_vertices_idx = np.arange(len(vertices))[col0_mask & col1_mask] - periodic_voronoi_vertices_pos = vertices[col0_mask & col1_mask] - - edges_inside = [] - edges_outside = [] - offsets_inside = {} - offsets_outside = {} - visited = [] - - for e in edges: - if e[0] in periodic_voronoi_vertices_idx and e[1] in periodic_voronoi_vertices_idx: - edges_inside.append(tuple(sorted((e[0], e[1])))) - offsets_inside[(e[0], e[1])] = (0, 0) - offsets_inside[(e[1], e[0])] = (0, 0) - if bool(e[0] in periodic_voronoi_vertices_idx) != bool(e[1] in periodic_voronoi_vertices_idx): - if e[0] in periodic_voronoi_vertices_idx: - if vertices[e[1]][0] < 0.0: - x = vertices[e[1]][0] + L_box - offset_x1 = -1 - elif vertices[e[1]][0] > L_box: - x = vertices[e[1]][0] - L_box - offset_x1 = 1 - else: - x = vertices[e[1]][0] - offset_x1 = 0 - if vertices[e[1]][1] < 0.0: - y = vertices[e[1]][1] + L_box - offset_y1 = -1 - elif vertices[e[1]][1] > L_box: - y = vertices[e[1]][1] - L_box - offset_y1 = 1 - else: - y = vertices[e[1]][1] - offset_y1 = 0 - for idx, pos in zip(periodic_voronoi_vertices_idx, periodic_voronoi_vertices_pos, strict=False): - if ((np.abs(pos[0] - x)) < 10**-8) and ((np.abs(pos[1] - y)) < 10**-8): - edges_outside.append(tuple(sorted((e[0], idx)))) - if (e[0], e[1]) not in visited and (e[1], e[0]) not in visited: - offsets_outside[(e[0], idx)] = (offset_x1, offset_y1) - offsets_outside[(idx, e[0])] = (-offset_x1, -offset_y1) - visited.append((e[0], e[1])) - visited.append((e[1], e[0])) - break - else: - if vertices[e[0]][0] < 0.0: - x = vertices[e[0]][0] + L_box - offset_x0 = -1 - elif vertices[e[0]][0] > L_box: - x = vertices[e[0]][0] - L_box - offset_x0 = 1 - else: - x = vertices[e[0]][0] - offset_x0 = 0 - if vertices[e[0]][1] < 0.0: - y = vertices[e[0]][1] + L_box - offset_y0 = -1 - elif vertices[e[0]][1] > L_box: - y = vertices[e[0]][1] - L_box - offset_y0 = 1 - else: - y = vertices[e[0]][1] - offset_y0 = 0 - for idx, pos in zip(periodic_voronoi_vertices_idx, periodic_voronoi_vertices_pos, strict=False): - if ((np.abs(pos[0] - x)) < 10**-8) and ((np.abs(pos[1] - y)) < 10**-8): - edges_outside.append(tuple(sorted((idx, e[1])))) - if (e[0], e[1]) not in visited and (e[1], e[0]) not in visited: - offsets_outside[(idx, e[1])] = (-offset_x0, -offset_y0) - offsets_outside[(e[1], idx)] = (offset_x0, offset_y0) - visited.append((e[0], e[1])) - visited.append((e[1], e[0])) - break - - periodic_voronoi_edges = list(set(edges_inside)) + list(set(edges_outside)) - - offsets = offsets_inside | offsets_outside - - faces_inside = [] - faces_inside_outside = [] - for face in faces: - if face: - if all(item in periodic_voronoi_vertices_idx for item in face): - faces_inside.append(tuple(sorted(face))) - if any(item in face for item in periodic_voronoi_vertices_idx): - face_inside_outside = [] - for f in face: - if f in periodic_voronoi_vertices_idx: - face_inside_outside.append(f) - else: - if vertices[f][0] < 0.0: - x = vertices[f][0] + L_box - elif vertices[f][0] > L_box: - x = vertices[f][0] - L_box - else: - x = vertices[f][0] - if vertices[f][1] < 0.0: - y = vertices[f][1] + L_box - elif vertices[f][1] > L_box: - y = vertices[f][1] - L_box - else: - y = vertices[f][1] - for idx, pos in zip(periodic_voronoi_vertices_idx, periodic_voronoi_vertices_pos, strict=False): - if ((np.abs(pos[0] - x)) < 10**-8) and ((np.abs(pos[1] - y)) < 10**-8): - face_inside_outside.append(idx) - break - - faces_inside_outside.append(tuple(sorted(face_inside_outside))) - - periodic_voronoi_faces = list(set(faces_inside_outside)) - - # HALF EDGE DATA STRUCTURE - - # Reciprocating edges - periodic_voronoi_half_edges = [] - for e in periodic_voronoi_edges: - periodic_voronoi_half_edges.append(e) - periodic_voronoi_half_edges.append((e[1], e[0])) - - # Finding clockwise (or counterclockwise) half edge set for each face - ordered_edges_periodic_voronoi_faces = [] - for face in periodic_voronoi_faces: - edges_face = [(f1, f2) for f1 in face for f2 in face if (f1, f2) in periodic_voronoi_edges] - i = 0 - start_edge = edges_face[i] - ordered_face = [start_edge] - e = start_edge - visited = [e] - while sorted(edges_face) != sorted(visited): - if e[0] == start_edge[1] and e not in visited: - ordered_face.append(e) - start_edge = e - visited.append(e) - if e[1] == start_edge[1] and e not in visited: - ordered_face.append((e[1], e[0])) - start_edge = (e[1], e[0]) - visited.append(e) - i += 1 - e = edges_face[i % len(edges_face)] - - order = 0 - sum0_offsets = 0 - sum1_offsets = 0 - points = [] - for e in ordered_face: - idx0 = list(periodic_voronoi_vertices_idx).index(e[0]) - idx1 = list(periodic_voronoi_vertices_idx).index(e[1]) - e_offsets = offsets[e] - - prev_sum0_offsets = sum0_offsets - prev_sum1_offsets = sum1_offsets - sum0_offsets += e_offsets[0] - sum1_offsets += e_offsets[1] - - order += ( - (periodic_voronoi_vertices_pos[idx1][0] + sum0_offsets * L_box) - - (periodic_voronoi_vertices_pos[idx0][0] + prev_sum0_offsets * L_box) - ) * ( - (periodic_voronoi_vertices_pos[idx1][1] + sum1_offsets * L_box) - + (periodic_voronoi_vertices_pos[idx0][1] + prev_sum1_offsets * L_box) - ) - - points.append( - ( - periodic_voronoi_vertices_pos[idx0][0] + prev_sum0_offsets * L_box, - periodic_voronoi_vertices_pos[idx0][1] + prev_sum1_offsets * L_box, - ) - ) - - points.append( - ( - periodic_voronoi_vertices_pos[idx1][0] + sum0_offsets * L_box, - periodic_voronoi_vertices_pos[idx1][1] + sum1_offsets * L_box, - ) - ) - - if order < 0: - ordered_edges_periodic_voronoi_faces.append(ordered_face) - if order > 0: - new_ordered_face = [(e[1], e[0]) for e in reversed(ordered_face)] - ordered_edges_periodic_voronoi_faces.append(new_ordered_face) - if order == 0: - print("\nError: no order detected for face " + str(face) + "\n") - exit() - - # VERT FACE HE TABLES - - vertTable = np.zeros((len(periodic_voronoi_vertices_idx), 2)) - for i, (_, pos) in enumerate(zip(periodic_voronoi_vertices_idx, periodic_voronoi_vertices_pos, strict=False)): - vertTable[i][0] = pos[0] # x pos vert - vertTable[i][1] = pos[1] # y pos vert - # vertTable[i][2] = idx_selected_he # he vert source (random among three) - - faceTable = np.zeros(len(periodic_voronoi_faces)) - for i, hedges_face in enumerate(ordered_edges_periodic_voronoi_faces): - for j, he in enumerate(periodic_voronoi_half_edges): - if he == hedges_face[0]: - faceTable[i] = j # he_inside - - heTable = np.zeros((len(periodic_voronoi_half_edges), 8)) - for i, he in enumerate(periodic_voronoi_half_edges): - for hedges_face in ordered_edges_periodic_voronoi_faces: - if he in hedges_face: - idx = hedges_face.index(he) - heTable[i][0] = periodic_voronoi_half_edges.index(hedges_face[(idx - 1) % len(hedges_face)]) # he_prev - heTable[i][1] = periodic_voronoi_half_edges.index(hedges_face[(idx + 1) % len(hedges_face)]) # he_next - heTable[i][3] = list(periodic_voronoi_vertices_idx).index(he[0]) # vert source - heTable[i][4] = list(periodic_voronoi_vertices_idx).index(he[1]) # vert target - heTable[i][5] = ordered_edges_periodic_voronoi_faces.index(hedges_face) # face - break - heTable[i][2] = periodic_voronoi_half_edges.index((he[1], he[0])) # he twin - heTable[i][6] = offsets[he][0] # he_offset x vert target - heTable[i][7] = offsets[he][1] # he_offset y vert target - - return ( - jnp.array(vertTable, dtype=np.float32), - jnp.array(heTable, dtype=np.int32), - jnp.array(faceTable, dtype=np.int32), - ) - - -def create_mesh_from_image(image: NDArray) -> tuple[Array, Array, Array]: - """Create a rudimentary mesh with periodic boundary conditions from an image. - - To do that, we perform a segmentation using Cellpose and we try to fill the holes. - The result will probably be imperfect and it will always be better if you - provide directly a mask (with no holes) with the function "create_mesh_from_mask". - - Args: - image (NDArray): The image which will act as a template for the mesh. - - Returns: - tuple[Array, Array, Array]: The vertices, half-edges and faces table of the mesh. - """ - return create_mesh_from_mask(mask_from_image(image)) - - -def create_mesh_from_mask(mask: NDArray) -> tuple[Array, Array, Array]: # noqa: C901 - """Create a rudimentary mesh with periodic boundary conditions from a mask with no holes. - - Args: - mask (NDArray): The mask with no holes which will act as a template for the mesh. - - Returns: - tuple[Array, Array, Array]: The vertices, half-edges and faces table of the mesh. - """ - padded_mask = pad(mask, save=False, output_path="refined_and_padded_image.tiff") - # Find vertices, edges, faces - vertices, edges, faces = find_vertices_edges_faces(padded_mask) - - # imread tiff = Y is the first axis, X the second. - height: int = mask.shape[0] # original image length. Padded is 3 times bigger. - y_min = height / 2 - y_max = 2 * height + (height / 2) - width: int = mask.shape[1] - x_min = width / 2 - x_max = 2 * width + (width / 2) - - col0_mask = (vertices[:, 0] >= x_min) & (vertices[:, 0] < x_max) - col1_mask = (vertices[:, 1] >= y_min) & (vertices[:, 1] < y_max) - - periodic_vertices_idx = np.arange(len(vertices))[col0_mask & col1_mask] - periodic_vertices_pos = vertices[col0_mask & col1_mask] - - # store map between vertex id -> inside vertex id - inside_vertex: dict[int, int] = {idx: idx for idx in periodic_vertices_idx} - for i, vertex in enumerate(vertices): - if i not in periodic_vertices_idx: - x, y = vertex - if x < x_min: - x += 2 * width - elif x >= x_max: - x -= 2 * width - - if y < y_min: - y += 2 * height - elif y >= y_max: - y -= 2 * height - - # Find corresponding inside vertex to the outside dest vertex - for idx, pos in zip(periodic_vertices_idx, periodic_vertices_pos, strict=True): - if np.max(np.abs(pos - [x, y])) < 1: - inside_vertex[i] = idx - break - - edges_inside = [] - edges_outside = [] - offsets_inside = {} - offsets_outside = {} - visited = [] - - for e in edges: - if e[0] in periodic_vertices_idx and e[1] in periodic_vertices_idx: - edges_inside.append(tuple(sorted((e[0], e[1])))) - offsets_inside[(e[0], e[1])] = (0, 0) - offsets_inside[(e[1], e[0])] = (0, 0) - elif bool(e[0] in periodic_vertices_idx) != bool(e[1] in periodic_vertices_idx): - if e[0] in periodic_vertices_idx: - # origin in, dest out - # check x coord - if vertices[e[1]][0] < x_min: - offset_x1 = -1 - elif vertices[e[1]][0] >= x_max: - offset_x1 = 1 - else: - offset_x1 = 0 - - # Now check y coord - if vertices[e[1]][1] < y_min: - offset_y1 = -1 - elif vertices[e[1]][1] >= y_max: - offset_y1 = 1 - else: - offset_y1 = 0 - - # Find corresponding inside vertex to the outside dest vertex - if e[1] not in inside_vertex: - print(f"Error, no inside vertex found for vertex {e[1]}.") - else: - idx = inside_vertex[e[1]] - edges_outside.append(tuple(sorted((e[0], idx)))) - if (e[0], e[1]) not in visited and (e[1], e[0]) not in visited: - offsets_outside[(e[0], idx)] = (offset_x1, offset_y1) - offsets_outside[(idx, e[0])] = (-offset_x1, -offset_y1) - visited.append((e[0], e[1])) - visited.append((e[1], e[0])) - else: - # dest in, origin out - if vertices[e[0]][0] < x_min: - offset_x0 = -1 - elif vertices[e[0]][0] >= x_max: - offset_x0 = 1 - else: - offset_x0 = 0 - - if vertices[e[0]][1] < y_min: - offset_y0 = -1 - elif vertices[e[0]][1] >= y_max: - offset_y0 = 1 - else: - offset_y0 = 0 - - # Find corresponding inside vertex to the outside dest vertex - if e[0] not in inside_vertex: - print(f"Error, no inside vertex found for vertex {e[0]}.") - else: - idx = inside_vertex[e[0]] - edges_outside.append(tuple(sorted((idx, e[1])))) - if (e[0], e[1]) not in visited and (e[1], e[0]) not in visited: - offsets_outside[(idx, e[1])] = (-offset_x0, -offset_y0) - offsets_outside[(e[1], idx)] = (offset_x0, offset_y0) - visited.append((e[0], e[1])) - visited.append((e[1], e[0])) - - periodic_edges = list(set(edges_inside)) + list(set(edges_outside)) - offsets = offsets_inside | offsets_outside - - periodic_faces: list[set[int]] = [ - {inside_vertex[i] for i in face} for face in faces if any(v_id in periodic_vertices_idx for v_id in face) - ] - - # HALF EDGE DATA STRUCTURE - - # Reciprocating edges - periodic_half_edges = [] - for e in periodic_edges: - periodic_half_edges.append(e) - periodic_half_edges.append((e[1], e[0])) - - # Finding clockwise (or counterclockwise) half edge set for each face - ordered_edges_periodic_faces = [] - for k, face in enumerate(periodic_faces): - edges_face = [(f1, f2) for f1 in face for f2 in face if (f1, f2) in periodic_edges] - i = 0 - start_edge = edges_face[0] - ordered_face = [start_edge] - e = start_edge - visited = [e] - - while sorted(edges_face) != sorted(visited): - if e[0] == start_edge[1] and e not in visited: - ordered_face.append(e) - start_edge = e - visited.append(e) - if e[1] == start_edge[1] and e not in visited: - ordered_face.append((e[1], e[0])) - start_edge = (e[1], e[0]) - visited.append(e) - i += 1 - e = edges_face[i % len(edges_face)] - order = 0 - sum0_offsets = 0 - sum1_offsets = 0 - points = [] - for e in ordered_face: - idx0 = list(periodic_vertices_idx).index(e[0]) - idx1 = list(periodic_vertices_idx).index(e[1]) - e_offsets = offsets[e] - prev_sum0_offsets = sum0_offsets - prev_sum1_offsets = sum1_offsets - sum0_offsets += e_offsets[0] - sum1_offsets += e_offsets[1] - order += ( - (periodic_vertices_pos[idx1][0] + sum0_offsets * (2 * width)) - - (periodic_vertices_pos[idx0][0] + prev_sum0_offsets * (2 * width)) - ) * ( - (periodic_vertices_pos[idx1][1] + sum1_offsets * (2 * height)) - + (periodic_vertices_pos[idx0][1] + prev_sum1_offsets * (2 * height)) - ) - points.append( - ( - periodic_vertices_pos[idx0][0] + prev_sum0_offsets * (2 * width), - periodic_vertices_pos[idx0][1] + prev_sum1_offsets * (2 * height), - ) - ) - points.append( - ( - periodic_vertices_pos[idx1][0] + sum0_offsets * (2 * width), - periodic_vertices_pos[idx1][1] + sum1_offsets * (2 * height), - ) - ) - if order < 0: - ordered_edges_periodic_faces.append(ordered_face) - if order > 0: - new_ordered_face = [(e[1], e[0]) for e in reversed(ordered_face)] - ordered_edges_periodic_faces.append(new_ordered_face) - if order == 0: - print(f"f\n{k} face Error: no order detected for face " + str(face) + "\n") - print(vertices[np.array(face)]) - exit() - - # VERT FACE HE TABLES - vertTable = periodic_vertices_pos - [x_min, y_min] - - faceTable = np.zeros(len(periodic_faces)) - for i, hedges_face in enumerate(ordered_edges_periodic_faces): - for j, he in enumerate(periodic_half_edges): - if he == hedges_face[0]: - faceTable[i] = j # he_inside - - heTable = np.zeros((len(periodic_half_edges), 8)) - for i, he in enumerate(periodic_half_edges): - for hedges_face in ordered_edges_periodic_faces: - if he in hedges_face: - idx = hedges_face.index(he) - heTable[i][0] = periodic_half_edges.index(hedges_face[(idx - 1) % len(hedges_face)]) # he_prev - heTable[i][1] = periodic_half_edges.index(hedges_face[(idx + 1) % len(hedges_face)]) # he_next - heTable[i][3] = list(periodic_vertices_idx).index(he[0]) # vert source - heTable[i][4] = list(periodic_vertices_idx).index(he[1]) # vert target - heTable[i][5] = ordered_edges_periodic_faces.index(hedges_face) # face - break - heTable[i][2] = periodic_half_edges.index((he[1], he[0])) # he twin - heTable[i][6] = offsets[he][0] # he_offset x vert target - heTable[i][7] = offsets[he][1] # he_offset y vert target - - return ( - jnp.array(vertTable, dtype=np.float32), - jnp.array(heTable, dtype=np.int32), - jnp.array(faceTable, dtype=np.int32), - ) - - -# ========== -# Bounded -# ========== - - -def load_bounded_mesh(path: str) -> tuple[Array, Array, Array, Array]: - """Load a mesh from a file. - - Args: - path (str): Path to the mesh file. - - Returns: - tuple[Array, Array, Array]: Jax arrays of vertices (2D, float), edges and faces. - """ - mesh_file = np.load(path) - return ( - jnp.array(mesh_file["vertices"]), - jnp.array(mesh_file["angles"]), - jnp.array(mesh_file["edges"]), - jnp.array(mesh_file["faces"]), - ) - - -def save_bounded_mesh(path: str, vertTable: Array, angTable: Array, heTable: Array, faceTable: Array) -> None: - """Save a bounded mesh to a file. - - Args: - path (str): Path to the saved file. - vertTable (Array): The vertices of the mesh. - angTable (Array): The angles of the mesh. - heTable (Array): The half-edges of the mesh. - faceTable (Array): The faces of the mesh. - """ - Path(path).parent.mkdir(parents=True, exist_ok=True) - np.savez_compressed(path, allow_pickle=False, vertices=vertTable, angles=angTable, edges=heTable, faces=faceTable) - - -def create_bounded_mesh_from_seeds( # noqa: C901 - seeds: Array, path: str = "./", show: bool = False, rng: Generator | None = None -) -> tuple[Array, Array, Array, Array]: # rng=numpy's random number generator - """Create a bounded Mesh from a list of seeds. - - The seeds are assumed to have positive x and y positions. - - Args: - seeds (Array[float32]): jax float array of seed positions of shape (nbSeeds, 2). - path (str): where to save the mesh data. - show (bool): whether to plot the mesh or not. - rng (Generator|None): random number generator to add new seeds if needed, decide on cell fates... - """ - n_cells = len(seeds) # starting number of seeds must be equal to the desired number of cells (faces) - L_box = np.sqrt(n_cells) - if rng is None: - rng = np.random.default_rng() - - while True: - success = 0 - voronoi = Voronoi(seeds) - - vertices = voronoi.vertices - edges = voronoi.ridge_vertices - faces = voronoi.regions - - inbound_faces = [] - inbound_vertices = np.zeros(vertices.shape[0], dtype=np.int32) - for face in faces: - if face and all(item > -1 for item in face): # the face must not be an empty list - face_vertices_positions = vertices[face] - if np.all(face_vertices_positions < L_box) and np.all(face_vertices_positions > 0): - inbound_faces.append(face) - inbound_vertices[face] += 1 - - # getting rid of faces connected to a single other inbound face - # (these can be problematic and lead to many special cases later on) - while True: - num_infaces = len(inbound_faces) - del_count = 0 - for i, face in enumerate(reversed(inbound_faces)): - if np.sum(inbound_vertices[face] > 1) <= 2: - inbound_vertices[face] -= 1 - del inbound_faces[num_infaces - i - 1] - del_count += 1 - if del_count == 0: - break - - if num_infaces < n_cells: - success = 1 - elif num_infaces > n_cells: - success = 2 - else: - for i, face in enumerate(inbound_faces): - useful_vertices = [] - extra_edges = [] - last_useful = -1 - incomplete_new_edge = False - new_edge = [] # now there - for vertex in face: - if inbound_vertices[vertex] == 1: - if not incomplete_new_edge: - # new_edge = [] # previously here - new_edge.append(last_useful) - incomplete_new_edge = True - else: - useful_vertices.append(vertex) - last_useful = vertex - if incomplete_new_edge: - new_edge.append(vertex) - extra_edges.append(new_edge) - incomplete_new_edge = False - if extra_edges and extra_edges[0][0] == -1: - extra_edges[0][0] = useful_vertices[-1] - elif incomplete_new_edge: - new_edge.append(useful_vertices[0]) - extra_edges.append(new_edge) - edges.extend(extra_edges) - inbound_faces[i] = tuple(sorted(useful_vertices)) - useful_vertices_set = set(np.where(inbound_vertices > 1)[0]) - - # HALF EDGE DATA STRUCTURE - - # reciprocating edges - - useful_edges = [tuple(sorted(e)) for e in edges if set(e).issubset(useful_vertices_set)] - - # failing to abide by the following relation results in disconnected topologies - if len(useful_edges) != (n_cells - 1) * 3: - success = 2 - else: - half_edges = [] - for e in useful_edges: - half_edges.append(e) - half_edges.append((e[1], e[0])) - - # finding clockwise (or counterclockwise) half edge set for each face - - ordered_edges_inbound_faces = [] - for face in inbound_faces: - ### I think scipy give you everything already ordered for finite faces - edges_face = [(f1, f2) for f1 in face for f2 in face if (f1, f2) in useful_edges] - - i = 0 - start_edge = edges_face[i] - ordered_face = [start_edge] - e = start_edge - visited = [e] - while sorted(edges_face) != sorted(visited): - if e[0] == start_edge[1] and e not in visited: - ordered_face.append(e) - start_edge = e - visited.append(e) - if e[1] == start_edge[1] and e not in visited: - ordered_face.append((e[1], e[0])) - start_edge = (e[1], e[0]) - visited.append(e) - i += 1 - e = edges_face[i % len(edges_face)] - - order = 0 - for e in ordered_face: - idx0 = e[0] - idx1 = e[1] - - order += (vertices[idx1][0] - vertices[idx0][0]) * (vertices[idx1][1] + vertices[idx0][1]) - - if order < 0: - ordered_edges_inbound_faces.append(ordered_face) - if order > 0: - ordered_edges_inbound_faces.append([(e[1], e[0]) for e in reversed(ordered_face)]) - if order == 0: - print("\nError: no order detected for face " + str(face) + "\n") - exit() - - useful_vertices_list = list(useful_vertices_set) - vertTable = np.zeros((len(useful_vertices_list), 2)) - for i, idx in enumerate(useful_vertices_list): - pos = vertices[idx] - vertTable[i][0] = pos[0] # x pos vert - vertTable[i][1] = pos[1] # y pos vert - - faceTable = np.zeros((len(inbound_faces), 1), dtype=np.int32) - for i, hedges_face in enumerate(ordered_edges_inbound_faces): - for j, he in enumerate(half_edges): - if he == hedges_face[0]: - faceTable[i] = j # he_inside - faceTable = _fate_selection(faceTable, 2, rng) - - L_he = len(half_edges) - heTable = np.zeros((L_he, 8), dtype=np.int32) - heTable[:, 4] = 1 - heTable[:, 6] = 1 - relevant_twins = [] - for i, he in enumerate(half_edges): - belongs_to_any_face = False - for hedges_face in ordered_edges_inbound_faces: - if he in hedges_face: - idx = hedges_face.index(he) - heTable[i][0] = half_edges.index(hedges_face[(idx - 1) % len(hedges_face)]) # he_prev - heTable[i][1] = half_edges.index(hedges_face[(idx + 1) % len(hedges_face)]) # he_next - heTable[i][3] = useful_vertices_list.index(he[0]) + 2 # vert source inner edges - heTable[i][4] = useful_vertices_list.index(he[1]) + 2 # vert target inner edges - heTable[i][7] = ordered_edges_inbound_faces.index(hedges_face) # face - belongs_to_any_face = True - break - twin_idx = half_edges.index((he[1], he[0])) - heTable[i][2] = twin_idx # he twin - if not belongs_to_any_face: - relevant_twins.append(twin_idx) - - angTable = np.ones(L_he // 2) - for tidx in relevant_twins: - angTable[tidx // 2] = rng.random() * (np.pi / 2 - 0.018) + 0.017 - heTable[tidx][5] = heTable[tidx][3] # vert source surface edges - heTable[tidx][6] = heTable[tidx][4] # vert target surface edges - heTable[tidx][3] = 0 - heTable[tidx][4] = 1 - - if show: - plot_bounded_mesh( - vertTable, - angTable, - heTable, - faceTable, - L_box, - path=path + "simulation_init/", - name="simulation_init", - save=True, - ) - - vertTable = jnp.array(vertTable) - angTable = jnp.array(angTable) - faceTable = jnp.array(faceTable) - heTable = jnp.array(heTable) - - return vertTable, angTable, heTable, faceTable - - seeds = np.vstack([seeds, L_box * rng.random((1, 2))]) if success == 1 else L_box * rng.random((n_cells, 2)) # type: ignore - - -def _fate_selection(faceTable: NDArray, n_fates: int, rng: Generator) -> NDArray: - n_cells = faceTable.size - n_cells_per_fate = n_cells // n_fates - n_cells_left = n_cells % n_fates - cell_fates = np.repeat(np.arange(n_fates), n_cells_per_fate) - cell_fates = np.concatenate([cell_fates, np.arange(n_cells_left)]) - rng.shuffle(cell_fates) - return np.hstack([faceTable, cell_fates[:, None]]) diff --git a/src/vertax/topo.py b/src/vertax/topo.py index 5d42a1c..53c17f4 100644 --- a/src/vertax/topo.py +++ b/src/vertax/topo.py @@ -70,7 +70,7 @@ def body_fun(idx: Array, state: tuple[Array, Array, Array]) -> Array: # check distance distance = get_length(he_idx, vertTable_new, heTable_new, faceTable_new, width, height) - # check if the two faces that share the hes are triangles + # check if the two faces that share the hes are triangles. We'll avoid a T1 transition in that case. he_prev = he[0] twin_he_prev = twin_he[0] should_update = heTable_new[he_prev, 0] != he[1] @@ -126,10 +126,6 @@ def update_state(_state: tuple[Array, Array, Array]) -> tuple[Array, Array, Arra heTable_new = heTable_new.at[next_twin_next_he_idx, 6].add(-twin_he[6]) heTable_new = heTable_new.at[next_twin_next_he_idx, 7].add(-twin_he[7]) - ## vertTable - vertTable_new = vertTable_new.at[he[3], 2].set(he_idx) - vertTable_new = vertTable_new.at[twin_he[3], 2].set(twin_he_idx) - # he x_source_he = vertTable_new[he[3], 0] y_source_he = vertTable_new[he[3], 1] diff --git a/tests/correlation/base_mesh.npz b/tests/correlation/base_mesh.npz new file mode 100644 index 0000000..8c294d9 Binary files /dev/null and b/tests/correlation/base_mesh.npz differ diff --git a/tests/correlation/input/areas_init.txt b/tests/correlation/input/areas_init.txt new file mode 100644 index 0000000..759b65b --- /dev/null +++ b/tests/correlation/input/areas_init.txt @@ -0,0 +1,20 @@ +0 1.0 +1 1.0 +2 1.0 +3 1.0 +4 1.0 +5 1.0 +6 1.0 +7 1.0 +8 1.0 +9 1.0 +10 1.0 +11 1.0 +12 1.0 +13 1.0 +14 1.0 +15 1.0 +16 1.0 +17 1.0 +18 1.0 +19 1.0 diff --git a/tests/correlation/input/faceTable.npy b/tests/correlation/input/faceTable.npy new file mode 100644 index 0000000..eb0dfe9 Binary files /dev/null and b/tests/correlation/input/faceTable.npy differ diff --git a/tests/correlation/input/heTable.npy b/tests/correlation/input/heTable.npy new file mode 100644 index 0000000..e4e18cf Binary files /dev/null and b/tests/correlation/input/heTable.npy differ diff --git a/tests/correlation/input/line_tensions_init.txt b/tests/correlation/input/line_tensions_init.txt new file mode 100644 index 0000000..0bfd7ee --- /dev/null +++ b/tests/correlation/input/line_tensions_init.txt @@ -0,0 +1,120 @@ +0 1.2070553 +1 1.2070553 +2 1.373384 +3 1.373384 +4 1.1258662 +5 1.1258662 +6 1.3617237 +7 1.3617237 +8 1.1125065 +9 1.1125065 +10 1.318263 +11 1.318263 +12 0.95024586 +13 0.95024586 +14 1.1663989 +15 1.1663989 +16 1.1761924 +17 1.1761924 +18 1.4368774 +19 1.4368774 +20 1.2870216 +21 1.2870216 +22 1.3000536 +23 1.3000536 +24 1.2598435 +25 1.2598435 +26 1.0686067 +27 1.0686067 +28 1.0122381 +29 1.0122381 +30 1.6257486 +31 1.6257486 +32 1.4126803 +33 1.4126803 +34 1.1005847 +35 1.1005847 +36 1.2517841 +37 1.2517841 +38 0.94126415 +39 0.94126415 +40 1.2727275 +41 1.2727275 +42 0.670189 +43 0.670189 +44 0.8674402 +45 0.8674402 +46 1.3810965 +47 1.3810965 +48 1.4322762 +49 1.4322762 +50 1.0768814 +51 1.0768814 +52 1.3963214 +53 1.3963214 +54 0.9946632 +55 0.9946632 +56 1.0505323 +57 1.0505323 +58 1.0364997 +59 1.0364997 +60 1.1312494 +61 1.1312494 +62 1.6354012 +63 1.6354012 +64 1.1062915 +65 1.1062915 +66 1.34666 +67 1.34666 +68 1.2882904 +69 1.2882904 +70 1.22048 +71 1.22048 +72 1.0148377 +73 1.0148377 +74 1.2783688 +75 1.2783688 +76 1.3628558 +77 1.3628558 +78 1.088237 +79 1.088237 +80 1.5097132 +81 1.5097132 +82 1.3748161 +83 1.3748161 +84 1.3306218 +85 1.3306218 +86 1.2487597 +87 1.2487597 +88 1.4228945 +89 1.4228945 +90 1.2248588 +91 1.2248588 +92 1.4989245 +93 1.4989245 +94 1.1682049 +95 1.1682049 +96 1.3928055 +97 1.3928055 +98 1.0245054 +99 1.0245054 +100 1.2448698 +101 1.2448698 +102 0.953361 +103 0.953361 +104 1.0012536 +105 1.0012536 +106 1.0583911 +107 1.0583911 +108 1.1086955 +109 1.1086955 +110 1.4897367 +111 1.4897367 +112 1.3020321 +113 1.3020321 +114 1.5497851 +115 1.5497851 +116 1.0366948 +117 1.0366948 +118 1.4906743 +119 1.4906743 diff --git a/tests/correlation/input/vertTable.npy b/tests/correlation/input/vertTable.npy new file mode 100644 index 0000000..bb56436 Binary files /dev/null and b/tests/correlation/input/vertTable.npy differ diff --git a/tests/correlation/target/areas_target.txt b/tests/correlation/target/areas_target.txt new file mode 100644 index 0000000..759b65b --- /dev/null +++ b/tests/correlation/target/areas_target.txt @@ -0,0 +1,20 @@ +0 1.0 +1 1.0 +2 1.0 +3 1.0 +4 1.0 +5 1.0 +6 1.0 +7 1.0 +8 1.0 +9 1.0 +10 1.0 +11 1.0 +12 1.0 +13 1.0 +14 1.0 +15 1.0 +16 1.0 +17 1.0 +18 1.0 +19 1.0 diff --git a/tests/correlation/target/faceTable.npy b/tests/correlation/target/faceTable.npy new file mode 100644 index 0000000..eb0dfe9 Binary files /dev/null and b/tests/correlation/target/faceTable.npy differ diff --git a/tests/correlation/target/heTable.npy b/tests/correlation/target/heTable.npy new file mode 100644 index 0000000..47806d4 Binary files /dev/null and b/tests/correlation/target/heTable.npy differ diff --git a/tests/correlation/target/line_tensions_target.txt b/tests/correlation/target/line_tensions_target.txt new file mode 100644 index 0000000..ac1087b --- /dev/null +++ b/tests/correlation/target/line_tensions_target.txt @@ -0,0 +1,120 @@ +0 1.1906192 +1 1.1906192 +2 1.017332 +3 1.017332 +4 1.3207054 +5 1.3207054 +6 1.0616173 +7 1.0616173 +8 1.0205314 +9 1.0205314 +10 1.2739587 +11 1.2739587 +12 1.1592704 +13 1.1592704 +14 1.1485702 +15 1.1485702 +16 1.2769383 +17 1.2769383 +18 1.3720714 +19 1.3720714 +20 1.2822359 +21 1.2822359 +22 1.1936418 +23 1.1936418 +24 1.3022501 +25 1.3022501 +26 1.2443542 +27 1.2443542 +28 1.2808212 +29 1.2808212 +30 1.2033348 +31 1.2033348 +32 1.245045 +33 1.245045 +34 1.3494123 +35 1.3494123 +36 1.1960801 +37 1.1960801 +38 1.2285239 +39 1.2285239 +40 1.2501651 +41 1.2501651 +42 1.211172 +43 1.211172 +44 1.2574801 +45 1.2574801 +46 1.3949764 +47 1.3949764 +48 1.1188174 +49 1.1188174 +50 1.1663274 +51 1.1663274 +52 1.0817256 +53 1.0817256 +54 1.1406814 +55 1.1406814 +56 1.2019912 +57 1.2019912 +58 1.1227577 +59 1.1227577 +60 1.2112304 +61 1.2112304 +62 1.2413229 +63 1.2413229 +64 1.2296276 +65 1.2296276 +66 1.109159 +67 1.109159 +68 1.2114552 +69 1.2114552 +70 1.1198659 +71 1.1198659 +72 1.1236866 +73 1.1236866 +74 1.1237485 +75 1.1237485 +76 1.1073829 +77 1.1073829 +78 1.394489 +79 1.394489 +80 1.275705 +81 1.275705 +82 1.0322375 +83 1.0322375 +84 1.1951075 +85 1.1951075 +86 1.1569325 +87 1.1569325 +88 1.1006788 +89 1.1006788 +90 1.1215417 +91 1.1215417 +92 1.1633496 +93 1.1633496 +94 1.140002 +95 1.140002 +96 1.3191448 +97 1.3191448 +98 1.0277653 +99 1.0277653 +100 1.1872518 +101 1.1872518 +102 1.2968003 +103 1.2968003 +104 1.2248905 +105 1.2248905 +106 1.2747576 +107 1.2747576 +108 1.2983505 +109 1.2983505 +110 1.1821405 +111 1.1821405 +112 1.1954105 +113 1.1954105 +114 1.1715908 +115 1.1715908 +116 1.0611538 +117 1.0611538 +118 1.3598497 +119 1.3598497 diff --git a/tests/correlation/target/vertTable.npy b/tests/correlation/target/vertTable.npy new file mode 100644 index 0000000..22d5300 Binary files /dev/null and b/tests/correlation/target/vertTable.npy differ diff --git a/tests/correlation/target_mesh.npz b/tests/correlation/target_mesh.npz new file mode 100644 index 0000000..752df33 Binary files /dev/null and b/tests/correlation/target_mesh.npz differ diff --git a/tests/correlation/test_e2.py b/tests/correlation/test_e2.py new file mode 100644 index 0000000..70b232b --- /dev/null +++ b/tests/correlation/test_e2.py @@ -0,0 +1,319 @@ +"""Test the whole pipeline of bilevel optimization with the new API.""" + +from __future__ import annotations + +import math +from pathlib import Path +from time import perf_counter +from typing import TYPE_CHECKING + +import jax +import jax.numpy as jnp +import matplotlib.pyplot as plt +import numpy as np +import optax +from jax import vmap +from numpy.typing import NDArray + +from vertax import PbcBilevelOptimizer, PbcMesh, plot_mesh +from vertax.cost import cost_v2v +from vertax.geo import get_area, get_length +from vertax.method_enum import BilevelOptimizationMethod + +if TYPE_CHECKING: + from jax import Array + + +def load_geograph(path: str) -> tuple[Array, Array, Array]: + """Load a mesh the old way.""" + return jnp.load(path + "vertTable.npy"), jnp.load(path + "heTable.npy"), jnp.load(path + "faceTable.npy") + + +def translate_base_mesh() -> None: + """Translate old mesh data to new version.""" + vertices, edges, faces = load_geograph("tests/correlation/input/") + vertices = vertices[:, :2] + mesh = PbcMesh.create_empty() + mesh.vertices = vertices + mesh.edges = edges.reshape(-1, 8) + mesh.faces = faces + mesh.width = math.sqrt(20) + mesh.height = math.sqrt(20) + mesh.vertices_params = jnp.asarray([0.0]) + init_path = "tests/correlation/input/line_tensions_init.txt" + init_data = np.loadtxt(init_path) + init_values = init_data[:, 1] + + he_params = jnp.asarray(init_values[::2]) + mesh.edges_params = he_params + mesh.faces_params = jnp.asarray([0.0 for i in range(20)]) + mesh.save_mesh("tests/correlation/base_mesh.npz") + + +def translate_target_mesh() -> None: + """Translate old mesh data to new version (target mesh).""" + vertices, edges, faces = load_geograph("tests/correlation/target/") + vertices = vertices[:, :2] + mesh = PbcMesh.create_empty() + mesh.vertices = vertices + mesh.edges = edges.reshape(-1, 8) + mesh.faces = faces + mesh.width = math.sqrt(20) + mesh.height = math.sqrt(20) + mesh.save_mesh("tests/correlation/target_mesh.npz") + + +def load_target_mesh() -> PbcMesh: + """Load target mesh.""" + return PbcMesh.load_mesh("tests/correlation/target_mesh.npz") + + +def load_base_mesh() -> PbcMesh: + """Load the base PBC mesh for correlation experiments.""" + return PbcMesh.load_mesh("tests/correlation/base_mesh.npz") + + +def create_optimizer() -> PbcBilevelOptimizer: + """Get the optimizer for the experiments.""" + bop = PbcBilevelOptimizer() + bop.min_dist_T1 = 0.005 + bop.max_nb_iterations = 1000 + bop.tolerance = 0.00001 + bop.patience = 5 + bop.inner_solver = optax.sgd(learning_rate=0.01) + bop.outer_solver = optax.adam(learning_rate=0.0001, nesterov=True) + bop.bilevel_optimization_method = BilevelOptimizationMethod.AUTOMATIC_DIFFERENTIATION + bop.loss_function_outer = cost_v2v + return bop + + +def load_areas_target() -> Array: + """Load target area for energy.""" + init_path_target = "tests/correlation/target/areas_target.txt" + init_data_target = np.loadtxt(init_path_target) + init_values_target = init_data_target[:, 1] + return jnp.asarray(init_values_target) + + +def load_tensions_target() -> Array: + """Load line tensions target for energy.""" + init_path_target = "tests/correlation/target/line_tensions_target.txt" + init_data_target = np.loadtxt(init_path_target) + init_values_target = init_data_target[:, 1] + return jnp.asarray(init_values_target) + + +def _load_and_sort(target_path: str, init_path: str) -> tuple[NDArray, NDArray]: + target_data = np.loadtxt(target_path) + init_data = np.loadtxt(init_path) + + target_values = target_data[:, 1] + init_values = init_data[:, 1] + + sorted_indices = np.argsort(target_values) + sorted_target_values = target_values[sorted_indices] + corresponding_init_values = init_values[sorted_indices] + + return sorted_target_values, corresponding_init_values + + +def _expected_result() -> None: + nb_epochs = 2_400 + target1, init1 = _load_and_sort( + "OLD_E2_working/OLD_E2_working/2_inference_multiple/energy_line_tensions_COUPLED/target/line_tensions_target.txt", + "OLD_E2_working/OLD_E2_working/2_inference_multiple/energy_line_tensions_COUPLED/starting_0.1/line_tensions_init.txt", + ) + _, axes = plt.subplots(1, 2, figsize=(14, 6), sharey=True) + + # Plot for Initial Condition + axes[0].scatter(target1, init1, color="blue", alpha=0.7) + axes[0].plot([0.8, 1.7], [0.8, 1.7], color="red", linestyle="--", linewidth=1) # Bisecting line + axes[0].set_title("Initial Condition") + axes[0].set_xlabel("Sorted Target Values") + axes[0].set_ylabel("Corresponding Simulation Values") + axes[0].set_xlim(0.65, 1.85) + axes[0].set_ylim(0.65, 1.85) + axes[1].set_xlim(0.65, 1.85) + axes[1].set_ylim(0.65, 1.85) + + # Calculate and display correlation coefficient for Initial Condition + corr1 = np.corrcoef(target1, init1)[0, 1] + axes[0].text( + 0.05, 0.95, f"Corr. coeff. = {corr1:.2f}", transform=axes[0].transAxes, fontsize=12, verticalalignment="top" + ) + + target2, init2 = _load_and_sort( + "OLD_E2_working/OLD_E2_working/2_inference_multiple/energy_line_tensions_COUPLED/target/line_tensions_target.txt", + f"OLD_E2_working/OLD_E2_working/2_inference_multiple/bilevel_opt_lines_LESS_COUPLED_0.1/configurations/{nb_epochs}/line_tensions_final.txt", + ) + + # Plot for Final Condition + axes[1].scatter(target2, init2, color="green", alpha=0.7) + axes[1].plot([0.8, 1.7], [0.8, 1.7], color="red", linestyle="--", linewidth=1) # Bisecting line + axes[1].set_title("Final Condition") + axes[1].set_xlabel("Sorted Target Values") + axes[1].set_ylabel("Corresponding Simulation Values") + + # # Ensure both subplots share the same x and y limits + # x_min = min(min(target1)-0.05, min(init1)-0.05, min(target2)-0.05, min(init2)-0.05) + # x_max = max(max(target1)+0.05, max(init1)+0.05, max(target2)+0.05, max(init2)+0.05) + # y_min = x_min + # y_max = x_max + + # Calculate and display correlation coefficient for Final Condition + corr2 = np.corrcoef(target2, init2)[0, 1] + axes[1].text( + 0.05, 0.95, f"Corr. coeff. = {corr2:.2f}", transform=axes[1].transAxes, fontsize=12, verticalalignment="top" + ) + + plt.show() + + +def test_pearson_e2() -> None: + """Check identical result of a standard test with previous results (november 2025).""" + t_start = perf_counter() + nb_epochs = 10000 + MAX_EDGES_IN_ANY_FACE = 20 + areas_target = load_areas_target() + + n_cells = 20 + width = math.sqrt(n_cells) + height = width + + # target_mesh = load_target_mesh() + + bop = create_optimizer() + + mesh_target = PbcMesh.from_random_seeds(nb_seeds=n_cells, width=width, height=height, random_key=1290) + + # Initial condition (parameters) + mesh_target.vertices_params = jnp.asarray([0.0 for _ in range(mesh_target.nb_vertices)]) + + mu_tensions = 2.1 + std_tensions = 0.3 + key = jax.random.PRNGKey(543) # change the seed for different results + target_he_params = mu_tensions + std_tensions * jax.random.normal(key, shape=(mesh_target.nb_edges,)) + # Set mesh parameters + mesh_target.edges_params = jnp.repeat(target_he_params, 2) + + mesh_target.faces_params = jnp.asarray([1.0 for _ in range(mesh_target.nb_faces)]) + + he_params_reference = target_he_params[0] + + mesh = PbcMesh.copy_mesh(mesh_target) + key = jax.random.PRNGKey(125) # change the seed for different results + # base_he_params = target_he_params + std_tensions * jax.random.normal(key, shape=(mesh.nb_edges,)) + base_he_params = mu_tensions + std_tensions * jax.random.normal(key, shape=(mesh.nb_edges,)) + # Set mesh parameters + mesh.edges_params = jnp.repeat(base_he_params, 2) + + # Energy functions : Note that they use the width and height parameters now, defined earlier + def area_part(face: Array, _face_param: Array, vertTable: Array, heTable: Array, faceTable: Array) -> Array: + a = get_area(face, vertTable, heTable, faceTable, width, height, MAX_EDGES_IN_ANY_FACE) + # return (a - face_param) ** 2 + return (a - areas_target[face]) ** 2 + + def hedge_part(he: Array, he_param: Array, vertTable: Array, heTable: Array, faceTable: Array) -> Array: + edge_lengths = get_length(he, vertTable, heTable, faceTable, width, height) + return he_param * edge_lengths + + # It is important to define the energy function with this exact signature, + # even though the "_vert_params" is unused, we still keep it. + def energy( + vertTable: Array, heTable: Array, faceTable: Array, _vert_params: Array, he_params: Array, face_params: Array + ) -> Array: + K_areas = 20 + + def mapped_areas_part(face: Array, face_param: Array) -> Array: + return area_part(face, face_param, vertTable, heTable, faceTable) + + def mapped_hedges_part(he: Array, he_param: Array) -> Array: + return hedge_part(he, he_param, vertTable, heTable, faceTable) + + areas_part = vmap(mapped_areas_part)(jnp.arange(len(faceTable)), face_params) + hedges_part = vmap(mapped_hedges_part)(jnp.arange(2, len(heTable)), he_params[2:]) + return ( + (2 * he_params_reference * get_length(0, vertTable, heTable, faceTable, width, height)) + + jnp.sum(hedges_part) + + (0.5 * K_areas) * jnp.sum(areas_part) + ) + + # Energy minimization (init cond equilibrium) + bop.loss_function_inner = energy + bop.inner_optimization(mesh_target) + bop.inner_optimization(mesh) + plot_mesh(mesh, show=False, save=True, save_path="tests/correlation/results/base_mesh.png", title="Base mesh") + plot_mesh( + mesh_target, show=False, save=True, save_path="tests/correlation/results/target_mesh.png", title="Target mesh" + ) + mesh_target.save_mesh("tests/correlation/results/target_mesh.npz") + + bop.vertices_target = mesh_target.vertices.copy() + bop.edges_target = mesh_target.edges.copy() + bop.faces_target = mesh_target.faces.copy() + + def pearson_correlation(mesh: PbcMesh, _bop: PbcBilevelOptimizer) -> float: + return float(jnp.corrcoef(mesh.edges_params, mesh_target.edges_params)[0, 1]) + + bop.add_custom_metric("Pearson correlation", pearson_correlation) + bop.do_n_bilevel_optimization( + nb_epochs, + mesh, + report_every=10, + save_plotmesh_every=100, + save_mesh_data_every=100, + also_report_to_stdout=True, + save_folder="tests/correlation/results", + ) + # for j in range(epochs + 1): + # t1 = perf_counter() + # print( + # "epoch: " + # + str(j) + # + "/" + # + str(epochs) + # + "\t cost: " + # + str( + # cost_v2v( + # pbc_mesh.vertices, + # pbc_mesh.edges, + # pbc_mesh.faces, + # pbc_mesh.width, + # pbc_mesh.height, + # bilevel_optimizer.vertices_target, + # bilevel_optimizer.edges_target, + # bilevel_optimizer.faces_target, + # ) + # ) + # ) + # + # bilevel_optimizer.bilevel_optimization(pbc_mesh) + # print(perf_counter() - t1) + # pearson_corr = float(jnp.corrcoef(pbc_mesh.edges_params, pbc_mesh_target.edges_params)[0, 1]) + # print("Pearson", pearson_corr) + # np_corr = np.corrcoef(pbc_mesh.edges_params, pbc_mesh_target.edges_params)[0, 1] + # print("Pearson np", np_corr) + + t_end = perf_counter() + elapsed_times = t_end - t_start + print(f"Test correlation took {elapsed_times:.2f} s.") + + +def read_result() -> None: + """Demonstrates how to extract tension data from saved meshes.""" + # First get all mesh filenames. + mesh_filenames = Path("tests/correlation/results/meshes_data/").glob("mesh*.npz") + # Load mesh file and extract in particular the tensions (edges params) + tensions = [PbcMesh.load_mesh(str(filename)).edges_params for filename in mesh_filenames] + print(tensions[0]) + print(tensions[-1]) + print(f"{len(tensions)} tension arrays acquired.") + + +if __name__ == "__main__": + # translate_base_mesh() + # translate_target_mesh() + # test_pearson_e2() + read_result() + # _expected_result() + # print(load_base_mesh().edges_params) diff --git a/tests/correlation/test_e2_t1.py b/tests/correlation/test_e2_t1.py new file mode 100644 index 0000000..cbb91f0 --- /dev/null +++ b/tests/correlation/test_e2_t1.py @@ -0,0 +1,342 @@ +"""Test the whole pipeline of bilevel optimization with the new API.""" + +from __future__ import annotations + +import math +from pathlib import Path +from time import perf_counter +from typing import TYPE_CHECKING + +import jax +import jax.numpy as jnp +import matplotlib.pyplot as plt +import numpy as np +import optax +from jax import vmap +from numpy.typing import NDArray + +from vertax import EdgePlot, FacePlot, PbcBilevelOptimizer, PbcMesh, cost_v2v, plot_mesh +from vertax.geo import get_area, get_length +from vertax.method_enum import BilevelOptimizationMethod + +if TYPE_CHECKING: + from jax import Array + + +def load_geograph(path: str) -> tuple[Array, Array, Array]: + """Load a mesh the old way.""" + return jnp.load(path + "vertTable.npy"), jnp.load(path + "heTable.npy"), jnp.load(path + "faceTable.npy") + + +def translate_base_mesh() -> None: + """Translate old mesh data to new version.""" + vertices, edges, faces = load_geograph("tests/correlation/input/") + vertices = vertices[:, :2] + mesh = PbcMesh.create_empty() + mesh.vertices = vertices + mesh.edges = edges.reshape(-1, 8) + mesh.faces = faces + mesh.width = math.sqrt(20) + mesh.height = math.sqrt(20) + mesh.vertices_params = jnp.asarray([0.0]) + init_path = "tests/correlation/input/line_tensions_init.txt" + init_data = np.loadtxt(init_path) + init_values = init_data[:, 1] + + he_params = jnp.asarray(init_values[::2]) + mesh.edges_params = he_params + mesh.faces_params = jnp.asarray([0.0 for i in range(20)]) + mesh.save_mesh("tests/correlation/base_mesh.npz") + + +def translate_target_mesh() -> None: + """Translate old mesh data to new version (target mesh).""" + vertices, edges, faces = load_geograph("tests/correlation/target/") + vertices = vertices[:, :2] + mesh = PbcMesh.create_empty() + mesh.vertices = vertices + mesh.edges = edges.reshape(-1, 8) + mesh.faces = faces + mesh.width = math.sqrt(20) + mesh.height = math.sqrt(20) + mesh.save_mesh("tests/correlation/target_mesh.npz") + + +def load_target_mesh() -> PbcMesh: + """Load target mesh.""" + return PbcMesh.load_mesh("tests/correlation/target_mesh.npz") + + +def load_base_mesh() -> PbcMesh: + """Load the base PBC mesh for correlation experiments.""" + return PbcMesh.load_mesh("tests/correlation/base_mesh.npz") + + +def create_optimizer() -> PbcBilevelOptimizer: + """Get the optimizer for the experiments.""" + bop = PbcBilevelOptimizer() + bop.min_dist_T1 = 0.05 + bop.max_nb_iterations = 1000 + bop.tolerance = 0.00001 + bop.patience = 5 + bop.inner_solver = optax.sgd(learning_rate=0.01) + bop.outer_solver = optax.adam(learning_rate=0.0001, nesterov=True) + bop.bilevel_optimization_method = BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION + bop.loss_function_outer = cost_v2v + # bop.loss_function_outer = cost_v2v_ias + return bop + + +def load_areas_target() -> Array: + """Load target area for energy.""" + init_path_target = "tests/correlation/target/areas_target.txt" + init_data_target = np.loadtxt(init_path_target) + init_values_target = init_data_target[:, 1] + return jnp.asarray(init_values_target) + + +def load_tensions_target() -> Array: + """Load line tensions target for energy.""" + init_path_target = "tests/correlation/target/line_tensions_target.txt" + init_data_target = np.loadtxt(init_path_target) + init_values_target = init_data_target[:, 1] + return jnp.asarray(init_values_target) + + +def _load_and_sort(target_path: str, init_path: str) -> tuple[NDArray, NDArray]: + target_data = np.loadtxt(target_path) + init_data = np.loadtxt(init_path) + + target_values = target_data[:, 1] + init_values = init_data[:, 1] + + sorted_indices = np.argsort(target_values) + sorted_target_values = target_values[sorted_indices] + corresponding_init_values = init_values[sorted_indices] + + return sorted_target_values, corresponding_init_values + + +def _expected_result() -> None: + nb_epochs = 2_400 + target1, init1 = _load_and_sort( + "OLD_E2_working/OLD_E2_working/2_inference_multiple/energy_line_tensions_COUPLED/target/line_tensions_target.txt", + "OLD_E2_working/OLD_E2_working/2_inference_multiple/energy_line_tensions_COUPLED/starting_0.1/line_tensions_init.txt", + ) + _, axes = plt.subplots(1, 2, figsize=(14, 6), sharey=True) + + # Plot for Initial Condition + axes[0].scatter(target1, init1, color="blue", alpha=0.7) + axes[0].plot([0.8, 1.7], [0.8, 1.7], color="red", linestyle="--", linewidth=1) # Bisecting line + axes[0].set_title("Initial Condition") + axes[0].set_xlabel("Sorted Target Values") + axes[0].set_ylabel("Corresponding Simulation Values") + axes[0].set_xlim(0.65, 1.85) + axes[0].set_ylim(0.65, 1.85) + axes[1].set_xlim(0.65, 1.85) + axes[1].set_ylim(0.65, 1.85) + + # Calculate and display correlation coefficient for Initial Condition + corr1 = np.corrcoef(target1, init1)[0, 1] + axes[0].text( + 0.05, 0.95, f"Corr. coeff. = {corr1:.2f}", transform=axes[0].transAxes, fontsize=12, verticalalignment="top" + ) + + target2, init2 = _load_and_sort( + "OLD_E2_working/OLD_E2_working/2_inference_multiple/energy_line_tensions_COUPLED/target/line_tensions_target.txt", + f"OLD_E2_working/OLD_E2_working/2_inference_multiple/bilevel_opt_lines_LESS_COUPLED_0.1/configurations/{nb_epochs}/line_tensions_final.txt", + ) + + # Plot for Final Condition + axes[1].scatter(target2, init2, color="green", alpha=0.7) + axes[1].plot([0.8, 1.7], [0.8, 1.7], color="red", linestyle="--", linewidth=1) # Bisecting line + axes[1].set_title("Final Condition") + axes[1].set_xlabel("Sorted Target Values") + axes[1].set_ylabel("Corresponding Simulation Values") + + # # Ensure both subplots share the same x and y limits + # x_min = min(min(target1)-0.05, min(init1)-0.05, min(target2)-0.05, min(init2)-0.05) + # x_max = max(max(target1)+0.05, max(init1)+0.05, max(target2)+0.05, max(init2)+0.05) + # y_min = x_min + # y_max = x_max + + # Calculate and display correlation coefficient for Final Condition + corr2 = np.corrcoef(target2, init2)[0, 1] + axes[1].text( + 0.05, 0.95, f"Corr. coeff. = {corr2:.2f}", transform=axes[1].transAxes, fontsize=12, verticalalignment="top" + ) + + plt.show() + + +def test_pearson_e2_t1() -> None: + """Check identical result of a standard test with previous results (november 2025).""" + t_start = perf_counter() + Path("tests/correlation/results").mkdir(exist_ok=True) + nb_epochs = 10000 + MAX_EDGES_IN_ANY_FACE = 20 + areas_target = load_areas_target() + + n_cells = 20 + width = math.sqrt(n_cells) + height = width + + # target_mesh = load_target_mesh() + + bop = create_optimizer() + + mesh_target = PbcMesh.from_random_seeds(nb_seeds=n_cells, width=width, height=height, random_key=1290) + + # Initial condition (parameters) + mesh_target.vertices_params = jnp.asarray([0.0 for _ in range(mesh_target.nb_vertices)]) + + mu_tensions = 1.0 + std_tensions = 0.2 + key = jax.random.PRNGKey(643517) # change the seed for different results + target_he_params = mu_tensions + std_tensions * jax.random.normal(key, shape=(mesh_target.nb_edges,)) + # Set mesh parameters + mesh_target.edges_params = jnp.repeat(target_he_params, 2) + + mesh_target.faces_params = jnp.asarray([1.0 for _ in range(mesh_target.nb_faces)]) + + mesh = PbcMesh.copy_mesh(mesh_target) + key = jax.random.PRNGKey(238487) # change the seed for different results + # base_he_params = target_he_params + std_tensions * jax.random.normal(key, shape=(mesh.nb_edges,)) + base_he_params = mu_tensions + std_tensions * jax.random.normal(key, shape=(mesh.nb_edges,)) + # Set mesh parameters + mesh.edges_params = jnp.repeat(base_he_params, 2) + + # Energy functions : Note that they use the width and height parameters now, defined earlier + def area_part(face: Array, _face_param: Array, vertTable: Array, heTable: Array, faceTable: Array) -> Array: + a = get_area(face, vertTable, heTable, faceTable, width, height, MAX_EDGES_IN_ANY_FACE) + # return (a - face_param) ** 2 + return (a - areas_target[face]) ** 2 + + def hedge_part(he: Array, he_param: Array, vertTable: Array, heTable: Array, faceTable: Array) -> Array: + edge_lengths = get_length(he, vertTable, heTable, faceTable, width, height) + return he_param * edge_lengths + + # It is important to define the energy function with this exact signature, + # even though the "_vert_params" is unused, we still keep it. + def energy( + vertTable: Array, heTable: Array, faceTable: Array, _vert_params: Array, he_params: Array, face_params: Array + ) -> Array: + K_areas = 20 + + def mapped_areas_part(face: Array, face_param: Array) -> Array: + return area_part(face, face_param, vertTable, heTable, faceTable) + + def mapped_hedges_part(he: Array, he_param: Array) -> Array: + return hedge_part(he, he_param, vertTable, heTable, faceTable) + + areas_part = vmap(mapped_areas_part)(jnp.arange(len(faceTable)), face_params) + hedges_part = vmap(mapped_hedges_part)(jnp.arange(len(heTable)), he_params) + soft_mean_part = (jnp.mean(he_params) - 1) ** 2 + return jnp.sum(hedges_part) + (0.5 * K_areas) * jnp.sum(areas_part) + soft_mean_part + + # Energy minimization (init cond equilibrium) + bop.loss_function_inner = energy + bop.inner_optimization(mesh_target) + bop.inner_optimization(mesh) + plot_mesh(mesh, show=False, save=True, save_path="tests/correlation/results/base_mesh.png", title="Base mesh") + plot_mesh( + mesh_target, show=False, save=True, save_path="tests/correlation/results/target_mesh.png", title="Target mesh" + ) + mesh_target.save_mesh("tests/correlation/results/target_mesh.npz") + + bop.vertices_target = mesh_target.vertices.copy() + bop.edges_target = mesh_target.edges.copy() + bop.faces_target = mesh_target.faces.copy() + + def pearson_correlation(mesh: PbcMesh, _bop: PbcBilevelOptimizer) -> float: + return float(jnp.corrcoef(mesh.edges_params, mesh_target.edges_params)[0, 1]) + + bop.add_custom_metric("Pearson correlation", pearson_correlation) + bop.do_n_bilevel_optimization( + nb_epochs, + mesh, + report_every=10, + save_plotmesh_every=100, + save_mesh_data_every=100, + also_report_to_stdout=True, + save_folder="tests/correlation/results", + ) + # for j in range(epochs + 1): + # t1 = perf_counter() + # print( + # "epoch: " + # + str(j) + # + "/" + # + str(epochs) + # + "\t cost: " + # + str( + # cost_v2v( + # pbc_mesh.vertices, + # pbc_mesh.edges, + # pbc_mesh.faces, + # pbc_mesh.width, + # pbc_mesh.height, + # bilevel_optimizer.vertices_target, + # bilevel_optimizer.edges_target, + # bilevel_optimizer.faces_target, + # ) + # ) + # ) + # + # bilevel_optimizer.bilevel_optimization(pbc_mesh) + # print(perf_counter() - t1) + # pearson_corr = float(jnp.corrcoef(pbc_mesh.edges_params, pbc_mesh_target.edges_params)[0, 1]) + # print("Pearson", pearson_corr) + # np_corr = np.corrcoef(pbc_mesh.edges_params, pbc_mesh_target.edges_params)[0, 1] + # print("Pearson np", np_corr) + + t_end = perf_counter() + elapsed_times = t_end - t_start + print(f"Test correlation took {elapsed_times:.2f} s.") + + +def read_result() -> None: + """Demonstrates how to extract tension data from saved meshes.""" + # First get all mesh filenames. + mesh_filenames = Path("tests/correlation/results/meshes_data/").glob("mesh*.npz") + # Load mesh file and extract in particular the tensions (edges params) + tensions = [PbcMesh.load_mesh(str(filename)).edges_params for filename in mesh_filenames] + print(tensions[0]) + print(tensions[-1]) + print(f"{len(tensions)} tension arrays acquired.") + + +def show_tensions() -> None: + """Find why an edge doesn't do good with IAS.""" + for i in range(10): + step = 1200 + 100 * i + mesh = PbcMesh.load_mesh(f"tests/correlation/results_part1/meshes_data/mesh_epoch_{step}.npz") + plot_mesh( + mesh, + edge_plot=EdgePlot.EDGE_PARAMETER, + face_plot=FacePlot.WHITE, + edge_parameters_name="Tension", + title=f"Step {step} tensions", + show=False, + save=True, + save_path=f"tests/correlation/results_part1/tensions/mesh_epoch_{step}.png", + # forced_edge_scale=(0,2.) + ) + # target_mesh = PbcMesh.load_mesh("tests/correlation/results_part1/target_mesh.npz") + # plot_mesh( + # target_mesh, + # edge_plot=EdgePlot.EDGE_PARAMETER, + # face_plot=FacePlot.WHITE, + # edge_parameters_name="Tension", + # title="Target Mesh tensions", + # ) + + +if __name__ == "__main__": + # show_tensions() + translate_base_mesh() + translate_target_mesh() + test_pearson_e2_t1() + # read_result() + # _expected_result() + # print(load_base_mesh().edges_params) diff --git a/tests/correlation/test_e2_t1_part2.py b/tests/correlation/test_e2_t1_part2.py new file mode 100644 index 0000000..744dd19 --- /dev/null +++ b/tests/correlation/test_e2_t1_part2.py @@ -0,0 +1,246 @@ +"""Test the whole pipeline of bilevel optimization with the new API, part 2 only v2v.""" + +from __future__ import annotations + +import math +from pathlib import Path +from time import perf_counter +from typing import TYPE_CHECKING + +import jax +import jax.numpy as jnp +import numpy as np +import optax +from jax import vmap + +from vertax import PbcBilevelOptimizer, PbcMesh, cost_v2v, plot_mesh +from vertax.geo import get_area, get_length +from vertax.method_enum import BilevelOptimizationMethod + +if TYPE_CHECKING: + from jax import Array + + +def load_geograph(path: str) -> tuple[Array, Array, Array]: + """Load a mesh the old way.""" + return jnp.load(path + "vertTable.npy"), jnp.load(path + "heTable.npy"), jnp.load(path + "faceTable.npy") + + +def translate_base_mesh() -> None: + """Translate old mesh data to new version.""" + vertices, edges, faces = load_geograph("tests/correlation/input/") + vertices = vertices[:, :2] + mesh = PbcMesh.create_empty() + mesh.vertices = vertices + mesh.edges = edges.reshape(-1, 8) + mesh.faces = faces + mesh.width = math.sqrt(20) + mesh.height = math.sqrt(20) + mesh.vertices_params = jnp.asarray([0.0]) + init_path = "tests/correlation/input/line_tensions_init.txt" + init_data = np.loadtxt(init_path) + init_values = init_data[:, 1] + + he_params = jnp.asarray(init_values[::2]) + mesh.edges_params = he_params + mesh.faces_params = jnp.asarray([0.0 for i in range(20)]) + mesh.save_mesh("tests/correlation/base_mesh.npz") + + +def translate_target_mesh() -> None: + """Translate old mesh data to new version (target mesh).""" + vertices, edges, faces = load_geograph("tests/correlation/target/") + vertices = vertices[:, :2] + mesh = PbcMesh.create_empty() + mesh.vertices = vertices + mesh.edges = edges.reshape(-1, 8) + mesh.faces = faces + mesh.width = math.sqrt(20) + mesh.height = math.sqrt(20) + mesh.save_mesh("tests/correlation/target_mesh.npz") + + +def load_target_mesh() -> PbcMesh: + """Load target mesh.""" + return PbcMesh.load_mesh("tests/correlation/target_mesh.npz") + + +def load_base_mesh() -> PbcMesh: + """Load the base PBC mesh for correlation experiments.""" + return PbcMesh.load_mesh("tests/correlation/base_mesh.npz") + + +def create_optimizer() -> PbcBilevelOptimizer: + """Get the optimizer for the experiments.""" + bop = PbcBilevelOptimizer() + bop.min_dist_T1 = 0.05 + bop.max_nb_iterations = 1000 + bop.tolerance = 0.00001 + bop.patience = 5 + bop.inner_solver = optax.sgd(learning_rate=0.01) + bop.outer_solver = optax.adam(learning_rate=0.0001, nesterov=True) + bop.bilevel_optimization_method = BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION + bop.loss_function_outer = cost_v2v + # bop.loss_function_outer = cost_v2v_ias + return bop + + +def load_areas_target() -> Array: + """Load target area for energy.""" + init_path_target = "tests/correlation/target/areas_target.txt" + init_data_target = np.loadtxt(init_path_target) + init_values_target = init_data_target[:, 1] + return jnp.asarray(init_values_target) + + +def load_tensions_target() -> Array: + """Load line tensions target for energy.""" + init_path_target = "tests/correlation/target/line_tensions_target.txt" + init_data_target = np.loadtxt(init_path_target) + init_values_target = init_data_target[:, 1] + return jnp.asarray(init_values_target) + + +def test_pearson_e2_t1() -> None: + """Check identical result of a standard test with previous results (november 2025).""" + t_start = perf_counter() + Path("tests/correlation/results").mkdir(exist_ok=True) + nb_epochs = 10000 + MAX_EDGES_IN_ANY_FACE = 20 + areas_target = load_areas_target() + + n_cells = 20 + width = math.sqrt(n_cells) + height = width + + # target_mesh = load_target_mesh() + + bop = create_optimizer() + + mesh_target = PbcMesh.from_random_seeds(nb_seeds=n_cells, width=width, height=height, random_key=1290) + + # Initial condition (parameters) + mesh_target.vertices_params = jnp.asarray([0.0 for _ in range(mesh_target.nb_vertices)]) + + mu_tensions = 1.0 + std_tensions = 0.2 + key = jax.random.PRNGKey(643517) # change the seed for different results + target_he_params = mu_tensions + std_tensions * jax.random.normal(key, shape=(mesh_target.nb_edges,)) + # Set mesh parameters + mesh_target.edges_params = jnp.repeat(target_he_params, 2) + + mesh_target.faces_params = jnp.asarray([1.0 for _ in range(mesh_target.nb_faces)]) + + he_params_reference = target_he_params[0] + + mesh = PbcMesh.load_mesh("tests/correlation/results_part1/meshes_data/mesh_epoch_1500.npz") + + # Energy functions : Note that they use the width and height parameters now, defined earlier + def area_part(face: Array, _face_param: Array, vertTable: Array, heTable: Array, faceTable: Array) -> Array: + a = get_area(face, vertTable, heTable, faceTable, width, height, MAX_EDGES_IN_ANY_FACE) + # return (a - face_param) ** 2 + return (a - areas_target[face]) ** 2 + + def hedge_part(he: Array, he_param: Array, vertTable: Array, heTable: Array, faceTable: Array) -> Array: + edge_lengths = get_length(he, vertTable, heTable, faceTable, width, height) + return he_param * edge_lengths + + # It is important to define the energy function with this exact signature, + # even though the "_vert_params" is unused, we still keep it. + def energy( + vertTable: Array, heTable: Array, faceTable: Array, _vert_params: Array, he_params: Array, face_params: Array + ) -> Array: + K_areas = 20 + + def mapped_areas_part(face: Array, face_param: Array) -> Array: + return area_part(face, face_param, vertTable, heTable, faceTable) + + def mapped_hedges_part(he: Array, he_param: Array) -> Array: + return hedge_part(he, he_param, vertTable, heTable, faceTable) + + areas_part = vmap(mapped_areas_part)(jnp.arange(len(faceTable)), face_params) + hedges_part = vmap(mapped_hedges_part)(jnp.arange(2, len(heTable)), he_params[2:]) + return ( + (2 * he_params_reference * get_length(0, vertTable, heTable, faceTable, width, height)) + + jnp.sum(hedges_part) + + (0.5 * K_areas) * jnp.sum(areas_part) + ) + + # Energy minimization (init cond equilibrium) + bop.loss_function_inner = energy + bop.inner_optimization(mesh_target) + plot_mesh(mesh, show=False, save=True, save_path="tests/correlation/results/base_mesh.png", title="Base mesh") + plot_mesh( + mesh_target, show=False, save=True, save_path="tests/correlation/results/target_mesh.png", title="Target mesh" + ) + mesh_target.save_mesh("tests/correlation/results/target_mesh.npz") + + bop.vertices_target = mesh_target.vertices.copy() + bop.edges_target = mesh_target.edges.copy() + bop.faces_target = mesh_target.faces.copy() + + def pearson_correlation(mesh: PbcMesh, _bop: PbcBilevelOptimizer) -> float: + return float(jnp.corrcoef(mesh.edges_params, mesh_target.edges_params)[0, 1]) + + bop.add_custom_metric("Pearson correlation", pearson_correlation) + bop.do_n_bilevel_optimization( + nb_epochs, + mesh, + report_every=10, + save_plotmesh_every=100, + save_mesh_data_every=100, + also_report_to_stdout=True, + save_folder="tests/correlation/results", + ) + # for j in range(epochs + 1): + # t1 = perf_counter() + # print( + # "epoch: " + # + str(j) + # + "/" + # + str(epochs) + # + "\t cost: " + # + str( + # cost_v2v( + # pbc_mesh.vertices, + # pbc_mesh.edges, + # pbc_mesh.faces, + # pbc_mesh.width, + # pbc_mesh.height, + # bilevel_optimizer.vertices_target, + # bilevel_optimizer.edges_target, + # bilevel_optimizer.faces_target, + # ) + # ) + # ) + # + # bilevel_optimizer.bilevel_optimization(pbc_mesh) + # print(perf_counter() - t1) + # pearson_corr = float(jnp.corrcoef(pbc_mesh.edges_params, pbc_mesh_target.edges_params)[0, 1]) + # print("Pearson", pearson_corr) + # np_corr = np.corrcoef(pbc_mesh.edges_params, pbc_mesh_target.edges_params)[0, 1] + # print("Pearson np", np_corr) + + t_end = perf_counter() + elapsed_times = t_end - t_start + print(f"Test correlation took {elapsed_times:.2f} s.") + + +def read_result() -> None: + """Demonstrates how to extract tension data from saved meshes.""" + # First get all mesh filenames. + mesh_filenames = Path("tests/correlation/results/meshes_data/").glob("mesh*.npz") + # Load mesh file and extract in particular the tensions (edges params) + tensions = [PbcMesh.load_mesh(str(filename)).edges_params for filename in mesh_filenames] + print(tensions[0]) + print(tensions[-1]) + print(f"{len(tensions)} tension arrays acquired.") + + +if __name__ == "__main__": + translate_base_mesh() + translate_target_mesh() + test_pearson_e2_t1() + # read_result() + # _expected_result() + # print(load_base_mesh().edges_params) diff --git a/tests/test_cpu_vs_gpu.py b/tests/test_cpu_vs_gpu.py index 40820a5..5ba6773 100644 --- a/tests/test_cpu_vs_gpu.py +++ b/tests/test_cpu_vs_gpu.py @@ -49,7 +49,7 @@ def perform_bilevel_opt(n_cells: int, n_epochs: int) -> float: # noqa: C901 MAX_EDGES_IN_ANY_FACE = 20 # Set periodic boundary mesh and some of its properties - pbc_mesh = PbcMesh.periodic_voronoi_from_random_seeds(nb_seeds=n_cells, width=width, height=height, random_key=0) + pbc_mesh = PbcMesh.from_random_seeds(nb_seeds=n_cells, width=width, height=height, random_key=0) # Note: those are base values so the following can be omitted bilevel_optimizer = PbcBilevelOptimizer() bilevel_optimizer.min_dist_T1 = 0.005 diff --git a/tests/test_forward_modeling.py b/tests/test_forward_modeling.py index 1d90db4..17d475b 100644 --- a/tests/test_forward_modeling.py +++ b/tests/test_forward_modeling.py @@ -27,7 +27,7 @@ def test_forward_modeling_for_regressions() -> None: L_box = jnp.sqrt(n_cells) width = float(L_box) height = float(L_box) - pbc_mesh = PbcMesh.periodic_voronoi_from_random_seeds(nb_seeds=n_cells, width=width, height=height, random_key=1) + pbc_mesh = PbcMesh.from_random_seeds(nb_seeds=n_cells, width=width, height=height, random_key=1) # A mesh is not only vertices, edges and faces. We can attach parameters to them. pbc_mesh.vertices_params = jnp.asarray([0.0]) pbc_mesh.edges_params = jnp.asarray([0.0]) diff --git a/tests/test_forward_modeling_bounded.py b/tests/test_forward_modeling_bounded.py index cde1955..b807a9c 100644 --- a/tests/test_forward_modeling_bounded.py +++ b/tests/test_forward_modeling_bounded.py @@ -6,7 +6,7 @@ from numpy.testing import assert_allclose from vertax import BoundedBilevelOptimizer, BoundedMesh -from vertax.energy import energy_bounded +from vertax.energy import energy_line_tensions_bounded def test_regression() -> None: @@ -34,7 +34,7 @@ def test_regression() -> None: bilevel_optimizer.patience = 5 # Energy minimization - bilevel_optimizer.loss_function_inner = energy_bounded + bilevel_optimizer.loss_function_inner = energy_line_tensions_bounded bilevel_optimizer.inner_optimization(mesh=bounded_mesh) saved_path = "tests/reference_result_test_forward_modeling_bounded.npz" diff --git a/tests/test_inverse_modeling.py b/tests/test_inverse_modeling.py index 32e6507..fddc28e 100644 --- a/tests/test_inverse_modeling.py +++ b/tests/test_inverse_modeling.py @@ -33,7 +33,7 @@ def test_inverse_modeling_for_regressions() -> None: # noqa: C901 MAX_EDGES_IN_ANY_FACE = 20 # Set periodic boundary mesh and some of its properties - pbc_mesh = PbcMesh.periodic_voronoi_from_random_seeds(nb_seeds=n_cells, width=width, height=height, random_key=0) + pbc_mesh = PbcMesh.from_random_seeds(nb_seeds=n_cells, width=width, height=height, random_key=0) # Initial condition (parameters) pbc_mesh.vertices_params = jnp.asarray([0.0 for _ in range(pbc_mesh.nb_vertices)]) diff --git a/tests/test_inverse_modeling_bounded.py b/tests/test_inverse_modeling_bounded.py index 7300eab..9c9558b 100644 --- a/tests/test_inverse_modeling_bounded.py +++ b/tests/test_inverse_modeling_bounded.py @@ -8,7 +8,7 @@ from vertax import BoundedBilevelOptimizer, BoundedMesh from vertax.cost import cost_ratio -from vertax.energy import energy_bounded +from vertax.energy import energy_line_tensions_bounded from vertax.method_enum import BilevelOptimizationMethod @@ -40,7 +40,7 @@ def test_regression() -> None: bilevel_optimizer.bilevel_optimization_method = BilevelOptimizationMethod.EQUILIBRIUM_PROPAGATION # Energy minimization - bilevel_optimizer.loss_function_inner = energy_bounded + bilevel_optimizer.loss_function_inner = energy_line_tensions_bounded bilevel_optimizer.inner_optimization(mesh=bounded_mesh) bilevel_optimizer.loss_function_outer = cost_ratio diff --git a/tests/test_mesh.py b/tests/test_mesh.py deleted file mode 100644 index 1894fe8..0000000 --- a/tests/test_mesh.py +++ /dev/null @@ -1,91 +0,0 @@ -"""Private test module to test implementation of a new Mesh class.""" - -import os - -os.environ["JAX_PLATFORM_NAME"] = "cpu" -import jax.numpy as jnp -import jax.random -import pytest -from numpy.testing import assert_array_equal -from tifffile import imread - -from vertax import PbcMesh, plot_mesh -from vertax.start import create_mesh_from_image, create_mesh_from_seeds - - -def test_private_constructor_mesh() -> None: - """Check that a PBCMesh has a private constructor.""" - with pytest.raises(TypeError): - PbcMesh() - - -def test_mesh_can_be_privately_created() -> None: - """Check that a PBCMesh has a private _create function.""" - my_mesh = PbcMesh._create() - assert hasattr(my_mesh, "vertices") - assert hasattr(my_mesh, "edges") - assert hasattr(my_mesh, "faces") - - -def test_compare_pbc_mesh_with_create_mesh_from_seeds() -> None: - """Compare the two from_seeds functions. They should be the same.""" - # Initial condition - n_cells = 100 - key = jax.random.PRNGKey(1) - L_box = jnp.sqrt(n_cells) - width = float(L_box) - height = float(L_box) - seeds = L_box * jax.random.uniform(key, (n_cells, 2)) - vertTable, heTable, faceTable = create_mesh_from_seeds(seeds) - - my_mesh = PbcMesh.periodic_voronoi_from_random_seeds(n_cells, width, height, random_key=1) - - assert_array_equal(my_mesh.vertices, vertTable) - assert_array_equal(my_mesh.edges, heTable) - assert_array_equal(my_mesh.faces, faceTable) - - -@pytest.mark.long -def test_compare_pbc_mesh_with_create_mesh_from_image() -> None: - """Compare the two from_seeds functions. They should be the same.""" - plot = False - # imread tiff = Y is the first axis, X the second. - img = imread("tests/test_image.tif") # [:-101, :] # non rect, odd and pair dimensions - print("create mesh from image...") - vertTable, heTable, faceTable = create_mesh_from_image(img) - if plot: - plot_mesh( - vertTable, - heTable, - faceTable, - width=2 * img.shape[1], - height=2 * img.shape[0], - path="ref_image", - save=True, - show=False, - ) - - print("PBC now...") - my_mesh = PbcMesh.periodic_from_image(img) - if plot: - plot_mesh( - my_mesh.vertices, - my_mesh.edges, - my_mesh.faces, - width=2 * img.shape[1], - height=2 * img.shape[0], - path="pbc_image", - save=True, - show=False, - ) - - assert_array_equal(my_mesh.vertices, vertTable) - assert_array_equal(my_mesh.edges, heTable) - assert_array_equal(my_mesh.faces, faceTable) - - -if __name__ == "__main__": - test_private_constructor_mesh() - test_mesh_can_be_privately_created() - test_compare_pbc_mesh_with_create_mesh_from_seeds() - test_compare_pbc_mesh_with_create_mesh_from_image() diff --git a/tests/test_n_bilevel_opt.py b/tests/test_n_bilevel_opt.py index a554f5a..b1e732a 100644 --- a/tests/test_n_bilevel_opt.py +++ b/tests/test_n_bilevel_opt.py @@ -29,7 +29,7 @@ def test_n_bilevel_opt() -> None: # noqa: C901 MAX_EDGES_IN_ANY_FACE = 20 # Set periodic boundary mesh and some of its properties - pbc_mesh = PbcMesh.periodic_voronoi_from_random_seeds(nb_seeds=n_cells, width=width, height=height, random_key=0) + pbc_mesh = PbcMesh.from_random_seeds(nb_seeds=n_cells, width=width, height=height, random_key=0) # Note: those are base values so the following can be omitted bilevel_optimizer = PbcBilevelOptimizer() bilevel_optimizer.min_dist_T1 = 0.005 diff --git a/tests/test_n_bilevel_opt_bounded.py b/tests/test_n_bilevel_opt_bounded.py index af87b8f..63838d6 100644 --- a/tests/test_n_bilevel_opt_bounded.py +++ b/tests/test_n_bilevel_opt_bounded.py @@ -10,7 +10,7 @@ from vertax import BoundedBilevelOptimizer, BoundedMesh from vertax.cost import cost_ratio -from vertax.energy import energy_bounded +from vertax.energy import energy_line_tensions_bounded from vertax.method_enum import BilevelOptimizationMethod if TYPE_CHECKING: @@ -48,7 +48,7 @@ def test_n_bilevel_opt() -> None: bounded_mesh.faces_params = jnp.asarray([0.0 for _ in range(bounded_mesh.nb_faces)]) # Energy minimization (init cond equilibrium) - bilevel_optimizer.loss_function_inner = energy_bounded + bilevel_optimizer.loss_function_inner = energy_line_tensions_bounded bilevel_optimizer.inner_optimization(mesh=bounded_mesh) # If you want to select only a subset of vertices, edges, and faces, it's possible: # pbc_mesh.inner_opt( @@ -60,7 +60,7 @@ def test_n_bilevel_opt() -> None: def energy_metric(mesh: BoundedMesh, _bilevel_opt: BoundedBilevelOptimizer) -> float: return float( - energy_bounded( + energy_line_tensions_bounded( mesh.vertices, mesh.angles, mesh.edges, diff --git a/tests/test_plot.py b/tests/test_plot.py index 0c30ab1..405d440 100644 --- a/tests/test_plot.py +++ b/tests/test_plot.py @@ -34,9 +34,10 @@ def test_plot() -> None: face_plot=FacePlot.PERIMETER, vertex_parameters_name="Random vertex parameter", title="Too much colorbar is possible !", + forced_edge_scale=(-1, 1), ) - pbc_mesh = PbcMesh.periodic_voronoi_from_random_seeds(nb_seeds=n_cells, width=width, height=height, random_key=1) + pbc_mesh = PbcMesh.from_random_seeds(nb_seeds=n_cells, width=width, height=height, random_key=1) rng = np.random.default_rng(1337) pbc_mesh.vertices_params = jnp.array(rng.random(pbc_mesh.nb_vertices) * 82 + 14) pbc_mesh.edges_params = jnp.array(rng.random(pbc_mesh.nb_edges) * 3 + 1) @@ -48,6 +49,8 @@ def test_plot() -> None: face_plot=FacePlot.AREA, vertex_parameters_name="Random vertex parameter", title="Too much colorbar is possible !", + forced_vertex_scale=(0, 100), + forced_face_scale=(0, 10), ) diff --git a/tests/test_plot_rectangular.py b/tests/test_plot_rectangular.py index 636b772..e55fc48 100644 --- a/tests/test_plot_rectangular.py +++ b/tests/test_plot_rectangular.py @@ -11,7 +11,7 @@ def show_rectangular_mesh() -> None: height = 10 # Initial condition - mesh = PbcMesh.periodic_voronoi_from_random_seeds(n_cells, width, height, random_key=1) + mesh = PbcMesh.from_random_seeds(n_cells, width, height, random_key=1) plot_mesh(mesh) diff --git a/typos.toml b/typos.toml new file mode 100644 index 0000000..fab5d09 --- /dev/null +++ b/typos.toml @@ -0,0 +1,4 @@ +[default] + +[default.extend-words] +arange = "arange" diff --git a/uv.lock b/uv.lock index b96cf41..900c839 100644 --- a/uv.lock +++ b/uv.lock @@ -2,36 +2,36 @@ version = 1 revision = 3 requires-python = ">=3.11" resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", - "python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform == 'darwin'", + "python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", "(python_full_version >= '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", "(python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "python_full_version < '3.12' and sys_platform == 'darwin'", - "python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version < '3.12' and sys_platform == 'win32'", "python_full_version < '3.12' and sys_platform == 'emscripten'", + "python_full_version < '3.12' and sys_platform == 'darwin'", + "python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", "(python_full_version < '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", ] [[package]] name = "absl-py" -version = "2.3.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/10/2a/c93173ffa1b39c1d0395b7e842bbdc62e556ca9d8d3b5572926f3e4ca752/absl_py-2.3.1.tar.gz", hash = "sha256:a97820526f7fbfd2ec1bce83f3f25e3a14840dac0d8e02a0b71cd75db3f77fc9", size = 116588, upload-time = "2025-07-03T09:31:44.05Z" } +version = "2.4.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/64/c7/8de93764ad66968d19329a7e0c147a2bb3c7054c554d4a119111b8f9440f/absl_py-2.4.0.tar.gz", hash = "sha256:8c6af82722b35cf71e0f4d1d47dcaebfff286e27110a99fc359349b247dfb5d4", size = 116543, upload-time = "2026-01-28T10:17:05.322Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/8f/aa/ba0014cc4659328dc818a28827be78e6d97312ab0cb98105a770924dc11e/absl_py-2.3.1-py3-none-any.whl", hash = "sha256:eeecf07f0c2a93ace0772c92e596ace6d3d3996c042b2128459aaae2a76de11d", size = 135811, upload-time = "2025-07-03T09:31:42.253Z" }, + { url = "https://files.pythonhosted.org/packages/18/a6/907a406bb7d359e6a63f99c313846d9eec4f7e6f7437809e03aa00fa3074/absl_py-2.4.0-py3-none-any.whl", hash = "sha256:88476fd881ca8aab94ffa78b7b6c632a782ab3ba1cd19c9bd423abc4fb4cd28d", size = 135750, upload-time = "2026-01-28T10:17:04.19Z" }, ] [[package]] name = "annotated-types" version = "0.7.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } sdist = { url = "https://files.pythonhosted.org/packages/ee/67/531ea369ba64dcff5ec9c3402f9f51bf748cec26dde048a2f973a4eea7f5/annotated_types-0.7.0.tar.gz", hash = "sha256:aff07c09a53a08bc8cfccb9c85b05f1aa9a2a6f23728d790723543408344ce89", size = 16081, upload-time = "2024-05-20T21:33:25.928Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/78/b6/6307fbef88d9b5ee7421e68d78a9f162e0da4900bc5f5793f6d3d0e34fb8/annotated_types-0.7.0-py3-none-any.whl", hash = "sha256:1f02e8b43a8fbbc3f3e0d4f0f4bfc8131bcb4eebe8849b8e5c773f3a1c582a53", size = 13643, upload-time = "2024-05-20T21:33:24.1Z" }, @@ -40,7 +40,7 @@ wheels = [ [[package]] name = "cellpose" version = "3.1.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "fastremap" }, { name = "imagecodecs" }, @@ -63,7 +63,7 @@ wheels = [ [[package]] name = "chex" version = "0.1.90" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "absl-py" }, { name = "jax" }, @@ -81,7 +81,7 @@ wheels = [ [[package]] name = "cloudpickle" version = "3.1.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } sdist = { url = "https://files.pythonhosted.org/packages/27/fb/576f067976d320f5f0114a8d9fa1215425441bb35627b1993e5afd8111e5/cloudpickle-3.1.2.tar.gz", hash = "sha256:7fda9eb655c9c230dab534f1983763de5835249750e85fbcef43aaa30a9a2414", size = 22330, upload-time = "2025-11-03T09:25:26.604Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/88/39/799be3f2f0f38cc727ee3b4f1445fe6d5e4133064ec2e4115069418a5bb6/cloudpickle-3.1.2-py3-none-any.whl", hash = "sha256:9acb47f6afd73f60dc1df93bb801b472f05ff42fa6c84167d25cb206be1fbf4a", size = 22228, upload-time = "2025-11-03T09:25:25.534Z" }, @@ -90,7 +90,7 @@ wheels = [ [[package]] name = "colorama" version = "0.4.6" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697, upload-time = "2022-10-25T02:36:22.414Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335, upload-time = "2022-10-25T02:36:20.889Z" }, @@ -99,7 +99,7 @@ wheels = [ [[package]] name = "contourpy" version = "1.3.3" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "numpy" }, ] @@ -178,10 +178,81 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" }, ] +[[package]] +name = "cuda-bindings" +version = "13.2.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +dependencies = [ + { name = "cuda-pathfinder", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/e0/a9/3a8241c6e19483ac1f1dcf5c10238205dcb8a6e9d0d4d4709240dff28ff4/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:721104c603f059780d287969be3d194a18d0cc3b713ed9049065a1107706759d", size = 5730273, upload-time = "2026-03-11T00:12:37.18Z" }, + { url = "https://files.pythonhosted.org/packages/e9/94/2748597f47bb1600cd466b20cab4159f1530a3a33fe7f70fee199b3abb9e/cuda_bindings-13.2.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1eba9504ac70667dd48313395fe05157518fd6371b532790e96fbb31bbb5a5e1", size = 6313924, upload-time = "2026-03-11T00:12:39.462Z" }, + { url = "https://files.pythonhosted.org/packages/52/c8/b2589d68acf7e3d63e2be330b84bc25712e97ed799affbca7edd7eae25d6/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e865447abfb83d6a98ad5130ed3c70b1fc295ae3eeee39fd07b4ddb0671b6788", size = 5722404, upload-time = "2026-03-11T00:12:44.041Z" }, + { url = "https://files.pythonhosted.org/packages/1f/92/f899f7bbb5617bb65ec52a6eac1e9a1447a86b916c4194f8a5001b8cde0c/cuda_bindings-13.2.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46d8776a55d6d5da9dd6e9858fba2efcda2abe6743871dee47dd06eb8cb6d955", size = 6320619, upload-time = "2026-03-11T00:12:45.939Z" }, + { url = "https://files.pythonhosted.org/packages/df/93/eef988860a3ca985f82c4f3174fc0cdd94e07331ba9a92e8e064c260337f/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6629ca2df6f795b784752409bcaedbd22a7a651b74b56a165ebc0c9dcbd504d0", size = 5614610, upload-time = "2026-03-11T00:12:50.337Z" }, + { url = "https://files.pythonhosted.org/packages/18/23/6db3aba46864aee357ab2415135b3fe3da7e9f1fa0221fa2a86a5968099c/cuda_bindings-13.2.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7dca0da053d3b4cc4869eff49c61c03f3c5dbaa0bcd712317a358d5b8f3f385d", size = 6149914, upload-time = "2026-03-11T00:12:52.374Z" }, + { url = "https://files.pythonhosted.org/packages/c0/87/87a014f045b77c6de5c8527b0757fe644417b184e5367db977236a141602/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a6464b30f46692d6c7f65d4a0e0450d81dd29de3afc1bb515653973d01c2cd6e", size = 5685673, upload-time = "2026-03-11T00:12:56.371Z" }, + { url = "https://files.pythonhosted.org/packages/ee/5e/c0fe77a73aaefd3fff25ffaccaac69c5a63eafdf8b9a4c476626ef0ac703/cuda_bindings-13.2.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4af9f3e1be603fa12d5ad6cfca7844c9d230befa9792b5abdf7dd79979c3626", size = 6191386, upload-time = "2026-03-11T00:12:58.965Z" }, + { url = "https://files.pythonhosted.org/packages/5f/58/ed2c3b39c8dd5f96aa7a4abef0d47a73932c7a988e30f5fa428f00ed0da1/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:df850a1ff8ce1b3385257b08e47b70e959932f5f432d0a4e46a355962b4e4771", size = 5507469, upload-time = "2026-03-11T00:13:04.063Z" }, + { url = "https://files.pythonhosted.org/packages/1f/01/0c941b112ceeb21439b05895eace78ca1aa2eaaf695c8521a068fd9b4c00/cuda_bindings-13.2.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8a16384c6494e5485f39314b0b4afb04bee48d49edb16d5d8593fd35bbd231b", size = 6059693, upload-time = "2026-03-11T00:13:06.003Z" }, +] + +[[package]] +name = "cuda-pathfinder" +version = "1.5.3" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d3/d6/ac63065d33dd700fee7ebd7d287332401b54e31b9346e142f871e1f0b116/cuda_pathfinder-1.5.3-py3-none-any.whl", hash = "sha256:dff021123aedbb4117cc7ec81717bbfe198fb4e8b5f1ee57e0e084fec5c8577d", size = 49991, upload-time = "2026-04-14T20:09:27.037Z" }, +] + +[[package]] +name = "cuda-toolkit" +version = "13.0.2" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/57/b2/453099f5f3b698d7d0eab38916aac44c7f76229f451709e2eb9db6615dcd/cuda_toolkit-13.0.2-py2.py3-none-any.whl", hash = "sha256:b198824cf2f54003f50d64ada3a0f184b42ca0846c1c94192fa269ecd97a66eb", size = 2364, upload-time = "2025-12-19T23:24:07.328Z" }, +] + +[package.optional-dependencies] +cublas = [ + { name = "nvidia-cublas", marker = "sys_platform == 'linux'" }, +] +cudart = [ + { name = "nvidia-cuda-runtime", marker = "sys_platform == 'linux'" }, +] +cufft = [ + { name = "nvidia-cufft", marker = "sys_platform == 'linux'" }, +] +cufile = [ + { name = "nvidia-cufile", marker = "sys_platform == 'linux'" }, +] +cupti = [ + { name = "nvidia-cuda-cupti", marker = "sys_platform == 'linux'" }, +] +curand = [ + { name = "nvidia-curand", marker = "sys_platform == 'linux'" }, +] +cusolver = [ + { name = "nvidia-cusolver", marker = "sys_platform == 'linux'" }, +] +cusparse = [ + { name = "nvidia-cusparse", marker = "sys_platform == 'linux'" }, +] +nvjitlink = [ + { name = "nvidia-nvjitlink", marker = "sys_platform == 'linux'" }, +] +nvrtc = [ + { name = "nvidia-cuda-nvrtc", marker = "sys_platform == 'linux'" }, +] +nvtx = [ + { name = "nvidia-nvtx", marker = "sys_platform == 'linux'" }, +] + [[package]] name = "cycler" version = "0.12.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } sdist = { url = "https://files.pythonhosted.org/packages/a9/95/a3dbbb5028f35eafb79008e7522a75244477d2838f38cbb722248dabc2a8/cycler-0.12.1.tar.gz", hash = "sha256:88bb128f02ba341da8ef447245a9e138fae777f6a23943da4540077d3601eb1c", size = 7615, upload-time = "2023-10-07T05:32:18.335Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/e7/05/c19819d5e3d95294a6f5947fb9b9629efb316b96de511b418c53d245aae6/cycler-0.12.1-py3-none-any.whl", hash = "sha256:85cef7cff222d8644161529808465972e51340599459b8ac3ccbac5a854e0d30", size = 8321, upload-time = "2023-10-07T05:32:16.783Z" }, @@ -189,11 +260,11 @@ wheels = [ [[package]] name = "etils" -version = "1.13.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/9b/a0/522bbff0f3cdd37968f90dd7f26c7aa801ed87f5ba335f156de7f2b88a48/etils-1.13.0.tar.gz", hash = "sha256:a5b60c71f95bcd2d43d4e9fb3dc3879120c1f60472bb5ce19f7a860b1d44f607", size = 106368, upload-time = "2025-07-15T10:29:10.563Z" } +version = "1.14.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/ce/6e067242fde898841922ac6fc82b0bb2fe35c38e995880bdffdfbe30182a/etils-1.14.0.tar.gz", hash = "sha256:8136e7f4c4173cd0af0ca5481c4475152f0b8686192951eefa60ee8711e1ede4", size = 108127, upload-time = "2026-03-04T17:41:36.291Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/98/87b5946356095738cb90a6df7b35ff69ac5750f6e783d5fbcc5cb3b6cbd7/etils-1.13.0-py3-none-any.whl", hash = "sha256:d9cd4f40fbe77ad6613b7348a18132cc511237b6c076dbb89105c0b520a4c6bb", size = 170603, upload-time = "2025-07-15T10:29:09.076Z" }, + { url = "https://files.pythonhosted.org/packages/5a/3d/589663aeeacd59bb2f3e8596bfd3e81cf0fb18d70bb433199041f469771b/etils-1.14.0-py3-none-any.whl", hash = "sha256:b5df7341f54dbe1405a4450b2741207b4a8c279780402b45f87202b94dfc52b4", size = 172934, upload-time = "2026-03-04T17:41:35.01Z" }, ] [package.optional-dependencies] @@ -203,186 +274,167 @@ epy = [ [[package]] name = "fastremap" -version = "1.17.7" -source = { registry = "https://pypi.org/simple" } +version = "1.18.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/97/c8/d581816df8ee7ab70cd2dd8ee4e60169ceab8062224cc090863e6715f33d/fastremap-1.17.7.tar.gz", hash = "sha256:42776172867d8f2b3348754cf29405ba878af4b06917f12a969514d3097910dc", size = 50034, upload-time = "2025-09-29T23:28:13.031Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/7f/98bc1ab6ab9b389a72ed1a97dc34eb57a8e6beb473117c8942481f74e6ca/fastremap-1.17.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4610492ea19f1cc916a05e9195b67de11dc98a18e905de1abf821b2ca2ca1fac", size = 811862, upload-time = "2025-09-29T23:27:24.546Z" }, - { url = "https://files.pythonhosted.org/packages/f7/b6/b88d2a30f50708249bb0414f0581d0f7ccb3785b1a3ca6588565920988f2/fastremap-1.17.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d6130394fc92777d08ca992e70ff6307fe1ef928d2831140ff63ab27f36b6600", size = 655315, upload-time = "2025-09-29T23:27:26.006Z" }, - { url = "https://files.pythonhosted.org/packages/f9/fd/70d7e5ee9b77c3ddbe6d9c479202cf04a0f178c399d94af5993520dab51a/fastremap-1.17.7-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f77a4e48fc9614027d318d23399d91a89b62c56d97880055c538fd42c43fd6", size = 7496837, upload-time = "2025-09-29T23:27:27.344Z" }, - { url = "https://files.pythonhosted.org/packages/ff/5a/3ae0f6425c816ac74e130244c152cc5b7d7c13d5c5ff299af074f0456208/fastremap-1.17.7-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74635d268aa40ef7063319c997c1cbb70d52deeb08a3a61146a6151306c394ea", size = 7621372, upload-time = "2025-09-29T23:27:29.088Z" }, - { url = "https://files.pythonhosted.org/packages/65/9a/193ca90273394cc93d98c9b7a587d134655910e14e12d7813d97d48ed13d/fastremap-1.17.7-cp311-cp311-win32.whl", hash = "sha256:f72d6db9550d9f1308cf78e71ca1bbbedea66048439b0fe688addaedf05c37ff", size = 490649, upload-time = "2025-09-29T23:27:31.719Z" }, - { url = "https://files.pythonhosted.org/packages/71/4b/7a03f72620945f08b40285ff3640e2b0a86f80218c519c8e4c4a557ca645/fastremap-1.17.7-cp311-cp311-win_amd64.whl", hash = "sha256:67cf58fada99981ec1a5b4f3368e1b4c1c4d0f22efaa036748f97475c37ce1f3", size = 685345, upload-time = "2025-09-29T23:27:30.662Z" }, - { url = "https://files.pythonhosted.org/packages/13/87/443b137c927f1c9cea7e4c290d6d49a78b7139382a8abe6cb138a6f11e8c/fastremap-1.17.7-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:682f11e7e8daea113c252938c8d98d28b8cee164121f1f3dcdafd0657b4a065b", size = 784852, upload-time = "2025-09-29T23:27:32.876Z" }, - { url = "https://files.pythonhosted.org/packages/a2/73/6cc98c650cc1b625d52bcc2c41c6b2690c33b678de5c6b0774d4d49cdcee/fastremap-1.17.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71f370e256a052dabc5cb14a65bb6e070f700ed976db7dc10450014f54e773c3", size = 661224, upload-time = "2025-09-29T23:27:33.903Z" }, - { url = "https://files.pythonhosted.org/packages/10/73/566bed66cb33472fee3b3d3269438b1b026e85a99a6c5252f8e13acc8fbc/fastremap-1.17.7-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7df1b9ad1659f1820349bb12d8bf76291c4896146d5230ecad5b9c75f2635ab", size = 7272028, upload-time = "2025-09-29T23:27:35.587Z" }, - { url = "https://files.pythonhosted.org/packages/85/be/4c9efaaaa19d0cf5a438fe8055969461d3096d874d3732c36e71ad87a2a0/fastremap-1.17.7-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8bc165a003337c41ed19b0ee20c16c3c8342fcab0726e7072c3c2cf1bf613104", size = 7452828, upload-time = "2025-09-29T23:27:37.339Z" }, - { url = "https://files.pythonhosted.org/packages/35/f0/66d0c8cbdce800c59b60d8257ec77be294b21501bb4d5f94e817ea20f1a7/fastremap-1.17.7-cp312-cp312-win32.whl", hash = "sha256:e278071af4d68a52531efdb861addfaf86e33115e9a53a2703abd3d395ada300", size = 468626, upload-time = "2025-09-29T23:27:40.167Z" }, - { url = "https://files.pythonhosted.org/packages/d6/bf/da8e48bc2c1a89180a739557ba8e15278e2de685a3ce91436c5a5d47cf70/fastremap-1.17.7-cp312-cp312-win_amd64.whl", hash = "sha256:8f10a84cedb56e37627fb0bec570eb5ec9668a1e3c00ac2c93ca13008cc41230", size = 642404, upload-time = "2025-09-29T23:27:38.882Z" }, - { url = "https://files.pythonhosted.org/packages/01/9b/aff83fe7dda6d45ab5d4be8eecfe384761c2575203ee82071ac4bfee8917/fastremap-1.17.7-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:15a09bc4aa504bf630605ed6fb98b9661c179dbd38aec35436c39a2e42d064d0", size = 783563, upload-time = "2025-09-29T23:27:41.104Z" }, - { url = "https://files.pythonhosted.org/packages/a6/b4/0a4d7c54f2e4e862f4dce47bd5c0be78c59f166b9a7acc0fc86b1d4d20cd/fastremap-1.17.7-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:189315aaf5c9c5dd38f6f478f5029ed699155b3bed4159c7fc2d8c3d990d91a5", size = 658491, upload-time = "2025-09-29T23:27:42.328Z" }, - { url = "https://files.pythonhosted.org/packages/c1/9b/71c09beb8513c548ce80f19c70584b3e679cd0b60ef8f1dfc17b22063add/fastremap-1.17.7-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2abdf378fa79cdf182375706a07de8df3dacb55ee3f97a28febd464b0e892afe", size = 7140699, upload-time = "2025-09-29T23:27:43.671Z" }, - { url = "https://files.pythonhosted.org/packages/a6/1d/2eeeeae1af1fa5caeaf831c7fa08480f46b9acd475055ec50babb02946fd/fastremap-1.17.7-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46d44c6c25ca7a8e309d18475be8253ba22350f97f107068e44938f234792f43", size = 7324073, upload-time = "2025-09-29T23:27:45.181Z" }, - { url = "https://files.pythonhosted.org/packages/7f/6c/8f5571ea8ee150a11c0816d00aa4a2564d7bd2ffac1d4c471be8cc54d061/fastremap-1.17.7-cp313-cp313-win32.whl", hash = "sha256:bbcbc4aecb1da7d08469a2306fb9dc08f33695d6be7295385aaee4dd762e2faa", size = 475953, upload-time = "2025-09-29T23:27:47.9Z" }, - { url = "https://files.pythonhosted.org/packages/49/bb/ea8373f6f8836de1a5fa2169660b8d82d95df6faa87e7340818b3a8ff18d/fastremap-1.17.7-cp313-cp313-win_amd64.whl", hash = "sha256:f56e4f02f47865ad86b1d05161bee7fbc88e95a4a18ba3dcc7bbdf66153e4e3c", size = 641894, upload-time = "2025-09-29T23:27:46.498Z" }, - { url = "https://files.pythonhosted.org/packages/b9/c5/779f0dec11a2d2c43839f74198da670b2b84556349656b6e3f5d8ec38924/fastremap-1.17.7-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:0e0cdc4fb04c80fa7b41165a5a25ceb365a32210ba1aa06aa4df8bc120b8c441", size = 789962, upload-time = "2025-09-29T23:27:49.109Z" }, - { url = "https://files.pythonhosted.org/packages/fc/9a/6af0706bea8364344532de92dc01e0a06ab8cfeb4c0321075f0183e08446/fastremap-1.17.7-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8f9161aecea0c8ea7f84efec87f42af0cfca48710d8e4886401db631fbe7a40c", size = 663790, upload-time = "2025-09-29T23:27:50.42Z" }, - { url = "https://files.pythonhosted.org/packages/7f/18/a621d576c6a06840b94c09bc8540f87ab2e269fba3ac7855f570520db43a/fastremap-1.17.7-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:85fabb943477e79059dada3d730731d055ce65cbd7780bff627e4fed88b506a8", size = 7117601, upload-time = "2025-09-29T23:27:52.021Z" }, - { url = "https://files.pythonhosted.org/packages/2e/11/54dabf43a2d62edb380986784d6963cd956978f2c449ef81eaf9eff02da1/fastremap-1.17.7-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:21bc6a09d8025c7da7b44dff34f236d552473ae68c6aa2688d76f0b9b222b300", size = 7218897, upload-time = "2025-09-29T23:27:53.581Z" }, - { url = "https://files.pythonhosted.org/packages/00/6b/e67393a16d4c8596aeb3ec20505fc7f3c5609fb7ef1fcf77515c61599457/fastremap-1.17.7-cp314-cp314-win32.whl", hash = "sha256:7dc1c37c1307f02ce364d694a13815f80f3319849e41383011e7bf35fbd0d53f", size = 479906, upload-time = "2025-09-29T23:27:55.938Z" }, - { url = "https://files.pythonhosted.org/packages/4b/02/70a43c8a76c23dc20f78a1d7041e2077dc7d118799a142183dc84bfa0561/fastremap-1.17.7-cp314-cp314-win_amd64.whl", hash = "sha256:5c4dabeaf0b8e2a5213e86ba23aedfb30583e9d74879fd2195149cd107338917", size = 653262, upload-time = "2025-09-29T23:27:54.825Z" }, - { url = "https://files.pythonhosted.org/packages/06/f6/2778fc7f52b8b98ae401425d4a08f0414d4f8c99357af69704d2220b81f3/fastremap-1.17.7-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:0594239f6e2297150792ba93d7c1fe415e16689bf7df4e80edb897a46c273561", size = 842870, upload-time = "2025-09-29T23:27:56.947Z" }, - { url = "https://files.pythonhosted.org/packages/33/3e/4ff380e1c0f8af9fd6a874f7f594404ea8d811c88f175bf44a7ee166bae4/fastremap-1.17.7-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d79f7376d3ce15fedc9cf594ff5bc1cadbde4a00443a4adbef8adc9b34b10969", size = 731944, upload-time = "2025-09-29T23:27:58.546Z" }, - { url = "https://files.pythonhosted.org/packages/f2/29/c89dd6b6f49e31329d46e177f83375c816f9a7ba31f569685ffbb9294b1b/fastremap-1.17.7-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6d24c6cdcc0604a9e512ea0440e0705b326286e8457cfba5870a7d590fc85c7", size = 7307438, upload-time = "2025-09-29T23:27:59.859Z" }, - { url = "https://files.pythonhosted.org/packages/70/c0/7cd2e62c4b84410fa9b82b28ac7518ca842871d00e3d8c70ed295d0d8cd0/fastremap-1.17.7-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a5f2205bd4fdf4fa34780aaa4ac7174de9448604cd684ed158dfcbb20105676c", size = 7118873, upload-time = "2025-09-29T23:28:01.804Z" }, - { url = "https://files.pythonhosted.org/packages/5f/14/4953d7585378347f6026ef61947d6bca4c5d3eecbafc19ee0840a3efa003/fastremap-1.17.7-cp314-cp314t-win32.whl", hash = "sha256:30a2d1ac3c75a5668ba19c631098334bf33bb40837cd8c778786a5645bbb0dd4", size = 587944, upload-time = "2025-09-29T23:28:04.037Z" }, - { url = "https://files.pythonhosted.org/packages/27/b1/35a320f03a9556e0aa6091554da89787d48643146b472e5b24971375d852/fastremap-1.17.7-cp314-cp314t-win_amd64.whl", hash = "sha256:eab0c6d093f6dd78ede950fcf4653fe562ce5c741ee1b0f6da19254663ce724b", size = 811544, upload-time = "2025-09-29T23:28:03.084Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/00/b5/c33627109b34520315593c7415cb4e81125114082be07bd4c4e507ab6e17/fastremap-1.18.0.tar.gz", hash = "sha256:8e46acf8ffc7e9733852cc68659b0776ab951bb7e584c6df4f2c14f7bcebea5f", size = 50725, upload-time = "2026-03-17T18:52:28.969Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/42/c4/431d88fe0549508eca857021feea8f1950519a1188bc9b66af18b4751dbc/fastremap-1.18.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0a9d019fc31ac6f429741bcfe787b6783837fd5133743207eb1ed3885a146277", size = 716517, upload-time = "2026-03-17T18:51:43.596Z" }, + { url = "https://files.pythonhosted.org/packages/5a/42/71ade8de53758f232531a7a3f324c5219ec0136f75b7d47e8ee5a330f2f1/fastremap-1.18.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e19d76fbfed962651be43a021aceea77f07a33140fb61f1ba1febf448dc4c1a5", size = 566581, upload-time = "2026-03-17T18:51:44.943Z" }, + { url = "https://files.pythonhosted.org/packages/10/c1/863c70a10817d3dae30131f427c325ce8e23e0edf98f95f391f8b432db22/fastremap-1.18.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a648e39a90fe5aad56b89a90831276c321ef183d9eb6f62cb4fc2e4ebdd107a", size = 6835194, upload-time = "2026-03-17T18:51:47.977Z" }, + { url = "https://files.pythonhosted.org/packages/e0/36/ab68e1287a29f21cb06dfc07ac2c8d25fbacd63170510c96acd83af1e97f/fastremap-1.18.0-cp311-cp311-win32.whl", hash = "sha256:d23aaf3c795910fd9668dfaf9bca69d2d50815298acc0ca17794ae27d7ee3c96", size = 432502, upload-time = "2026-03-17T18:51:50.942Z" }, + { url = "https://files.pythonhosted.org/packages/91/17/abc2f751b8e39fdc34aadb27bc25ba73e458eb131f0ed53b1d0c212a86b2/fastremap-1.18.0-cp311-cp311-win_amd64.whl", hash = "sha256:e83f9060c979a26d634056e2541c6d650c8306fb7951aede28495f4985565386", size = 622401, upload-time = "2026-03-17T18:51:49.841Z" }, + { url = "https://files.pythonhosted.org/packages/83/53/ed1bd08dd6ae711b2b9e7bc73349d8e979526b1be1013ed2cfb8185bb03c/fastremap-1.18.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2b0078af791b9f0cd92e093f0901c226eae9e2b73d09b2cf4f52dd853977130e", size = 679892, upload-time = "2026-03-17T18:51:52.604Z" }, + { url = "https://files.pythonhosted.org/packages/8b/bd/42883df958b4bdf7c67e58791ade6b67ed63ca831907e7eb039f3da99e55/fastremap-1.18.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:170b70625c0ba8dab77696a5279fbfab06cef1937ba33c5b3e529fbdb0229ae8", size = 565563, upload-time = "2026-03-17T18:51:54.063Z" }, + { url = "https://files.pythonhosted.org/packages/b8/87/5af5dc3b691f83a1647dfac7899a5e48b245d005c6a87998a71a6c17fa57/fastremap-1.18.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3d56dc77d191b15706ec3266bd7756431b0b90a5bf767b6753e55ab00a545645", size = 6777342, upload-time = "2026-03-17T18:51:56.4Z" }, + { url = "https://files.pythonhosted.org/packages/52/02/19c85df9129f8e0bfbba43847ff38b644cb58d9891884deed5316bad188e/fastremap-1.18.0-cp312-cp312-win32.whl", hash = "sha256:68b5e5e087d4d75a5db15bd660239aa739a96653a6c49dce092dac0f94e0c356", size = 412274, upload-time = "2026-03-17T18:51:59.323Z" }, + { url = "https://files.pythonhosted.org/packages/41/d3/385b65b289000c0624bc3afc3098109c2f5b19f440745a9ebaf37a98b0c2/fastremap-1.18.0-cp312-cp312-win_amd64.whl", hash = "sha256:ca2cdaca466f97454d46cce3fea15070b8b28a1e230ec9843d0e92ce4b9a0ef2", size = 578549, upload-time = "2026-03-17T18:51:58.332Z" }, + { url = "https://files.pythonhosted.org/packages/f5/93/10fac690bd697fe1398435bd59dfef98a58b7c852b6768ed5d0b9bde6681/fastremap-1.18.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1c55207cd905adeaf7a24c38d49afde28b16dd3846a60508b5593f11bb7ef9f9", size = 679327, upload-time = "2026-03-17T18:52:00.433Z" }, + { url = "https://files.pythonhosted.org/packages/d7/53/46ecb06e02d23e03480d81b24609e1c38ba15eda630c67fdbda62ad5902e/fastremap-1.18.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1b793657d1b7215f5d806c3f92df18d0a1b2de3dc659ec462a0933ed2f862107", size = 564713, upload-time = "2026-03-17T18:52:01.525Z" }, + { url = "https://files.pythonhosted.org/packages/1b/5c/b7935dccd06916a1fb5bd8bc4e5ac92f89659307b838abb18b8d632410f5/fastremap-1.18.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:38fb274f9ad29cca5b72f4dedb92a196591657ac239e9b29c9951121d1f8862c", size = 6734169, upload-time = "2026-03-17T18:52:03.704Z" }, + { url = "https://files.pythonhosted.org/packages/ab/0b/49e8afc40de774a373d1404c6103784ea40f6873e163a4c08e426112547b/fastremap-1.18.0-cp313-cp313-win32.whl", hash = "sha256:82717feb7426e2b413b27fad10b22c4c744a4e0f6acb50969338848bb0846f4d", size = 415925, upload-time = "2026-03-17T18:52:06.556Z" }, + { url = "https://files.pythonhosted.org/packages/0e/c1/19b4a311cf74388a109bf400cb905a81db8a245a8abd8b2aa80de542ff5e/fastremap-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:c7f4b442f634942b1fbd6cc820e90cbb70b0f0ba2356ef488378dcbc11f7233e", size = 578688, upload-time = "2026-03-17T18:52:05.098Z" }, + { url = "https://files.pythonhosted.org/packages/de/51/eee7af4696e848cb589f06b7bf2af573cd22bf7d29969ba4c3fe933614d1/fastremap-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:534c2148f5853d25e68a5470590aba0d76e9d11b4b34d436b4884db7971c4a86", size = 678321, upload-time = "2026-03-17T18:52:07.858Z" }, + { url = "https://files.pythonhosted.org/packages/5a/42/5459e5913dda9ec805b695f42b9d9ee91391a66560f83ca8758cd875b32f/fastremap-1.18.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e0623ea36e9465d4fcd6ba82380372242b06d4c23f31a1192d7f2fd2420c615e", size = 573517, upload-time = "2026-03-17T18:52:08.919Z" }, + { url = "https://files.pythonhosted.org/packages/f7/83/4235a25c069d2fb7775d0e8cb0ecba3654f68dce6f0678301bf5eba0e573/fastremap-1.18.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a528d87f2d8aa4a3dcf3b66b96570706d44e896c202e241e8e31298621b90bbb", size = 6640855, upload-time = "2026-03-17T18:52:11.093Z" }, + { url = "https://files.pythonhosted.org/packages/61/8b/17090e4fca0b146a640924a6ba680db7534c0db9bf28fb67ddf2d1d3902a/fastremap-1.18.0-cp314-cp314-win32.whl", hash = "sha256:17f99a67dc63fc086f16ff6371dcf8748be3703995fcc047e9452eb5fcca54d9", size = 422083, upload-time = "2026-03-17T18:52:13.696Z" }, + { url = "https://files.pythonhosted.org/packages/02/d8/f59b750f705f9da030239115d62eb620e7d64eb83100cbecb0ff4c35fc5e/fastremap-1.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:9b120390a80d39ac97c72d62c928b7098ac48960c4e782fe26b153747990c88f", size = 591199, upload-time = "2026-03-17T18:52:12.457Z" }, + { url = "https://files.pythonhosted.org/packages/bc/c6/01954f9798eaf3fbc24781ed96d5e4e750228764ecc3369aa36a84d8278f/fastremap-1.18.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:34ca03930e01fa8811300e2964ad7f97aff3bf11f57be3fbe5915714b9b856cb", size = 730524, upload-time = "2026-03-17T18:52:14.706Z" }, + { url = "https://files.pythonhosted.org/packages/66/64/1282cb3dca1ed4209d18d3b64971b7beb5b99dff7d21166098cb766e9c85/fastremap-1.18.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c65418660acdd8bd1f522a7bdd72c2bfb834ef9c754de1fa23989fdfc1cf2a7b", size = 627714, upload-time = "2026-03-17T18:52:16.21Z" }, + { url = "https://files.pythonhosted.org/packages/fb/d0/267b721686690ce854495a4625bf76ea2ef78b6ccd115038ffd87ddeafa9/fastremap-1.18.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4f427bc6731d4b32fb8fb032fa593fd815a049493db366ec9a346a94939d06eb", size = 6514147, upload-time = "2026-03-17T18:52:18.209Z" }, + { url = "https://files.pythonhosted.org/packages/05/ec/5a14de31d04780c2804d8a629a8ce94e705b475799c9eb3b0e02ca59675d/fastremap-1.18.0-cp314-cp314t-win32.whl", hash = "sha256:87dcaccd0760ec12052ab6962c5783657fd7cd6e1c8cc78efca7bf657a3b18cd", size = 530743, upload-time = "2026-03-17T18:52:20.626Z" }, + { url = "https://files.pythonhosted.org/packages/22/c5/94b15a4dff7343a0f052b54b2e2e9a105d9b3719b51cf8e58fdeda06098b/fastremap-1.18.0-cp314-cp314t-win_amd64.whl", hash = "sha256:8865b25d157f174d2c17c824dd07bf5017de5824cb26706a2603ead66d95f292", size = 739811, upload-time = "2026-03-17T18:52:19.588Z" }, ] [[package]] name = "filelock" -version = "3.20.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/58/46/0028a82567109b5ef6e4d2a1f04a583fb513e6cf9527fcdd09afd817deeb/filelock-3.20.0.tar.gz", hash = "sha256:711e943b4ec6be42e1d4e6690b48dc175c822967466bb31c0c293f34334c13f4", size = 18922, upload-time = "2025-10-08T18:03:50.056Z" } +version = "3.29.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/b5/fe/997687a931ab51049acce6fa1f23e8f01216374ea81374ddee763c493db5/filelock-3.29.0.tar.gz", hash = "sha256:69974355e960702e789734cb4871f884ea6fe50bd8404051a3530bc07809cf90", size = 57571, upload-time = "2026-04-19T15:39:10.068Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/76/91/7216b27286936c16f5b4d0c530087e4a54eead683e6b0b73dd0c64844af6/filelock-3.20.0-py3-none-any.whl", hash = "sha256:339b4732ffda5cd79b13f4e2711a31b0365ce445d95d243bb996273d072546a2", size = 16054, upload-time = "2025-10-08T18:03:48.35Z" }, + { url = "https://files.pythonhosted.org/packages/81/47/dd9a212ef6e343a6857485ffe25bba537304f1913bdbed446a23f7f592e1/filelock-3.29.0-py3-none-any.whl", hash = "sha256:96f5f6344709aa1572bbf631c640e4ebeeb519e08da902c39a001882f30ac258", size = 39812, upload-time = "2026-04-19T15:39:08.752Z" }, ] [[package]] name = "fonttools" -version = "4.60.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4b/42/97a13e47a1e51a5a7142475bbcf5107fe3a68fc34aef331c897d5fb98ad0/fonttools-4.60.1.tar.gz", hash = "sha256:ef00af0439ebfee806b25f24c8f92109157ff3fac5731dc7867957812e87b8d9", size = 3559823, upload-time = "2025-09-29T21:13:27.129Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ea/85/639aa9bface1537e0fb0f643690672dde0695a5bbbc90736bc571b0b1941/fonttools-4.60.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7b4c32e232a71f63a5d00259ca3d88345ce2a43295bb049d21061f338124246f", size = 2831872, upload-time = "2025-09-29T21:11:20.329Z" }, - { url = "https://files.pythonhosted.org/packages/6b/47/3c63158459c95093be9618794acb1067b3f4d30dcc5c3e8114b70e67a092/fonttools-4.60.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3630e86c484263eaac71d117085d509cbcf7b18f677906824e4bace598fb70d2", size = 2356990, upload-time = "2025-09-29T21:11:22.754Z" }, - { url = "https://files.pythonhosted.org/packages/94/dd/1934b537c86fcf99f9761823f1fc37a98fbd54568e8e613f29a90fed95a9/fonttools-4.60.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5c1015318e4fec75dd4943ad5f6a206d9727adf97410d58b7e32ab644a807914", size = 5042189, upload-time = "2025-09-29T21:11:25.061Z" }, - { url = "https://files.pythonhosted.org/packages/d2/d2/9f4e4c4374dd1daa8367784e1bd910f18ba886db1d6b825b12edf6db3edc/fonttools-4.60.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e6c58beb17380f7c2ea181ea11e7db8c0ceb474c9dd45f48e71e2cb577d146a1", size = 4978683, upload-time = "2025-09-29T21:11:27.693Z" }, - { url = "https://files.pythonhosted.org/packages/cc/c4/0fb2dfd1ecbe9a07954cc13414713ed1eab17b1c0214ef07fc93df234a47/fonttools-4.60.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec3681a0cb34c255d76dd9d865a55f260164adb9fa02628415cdc2d43ee2c05d", size = 5021372, upload-time = "2025-09-29T21:11:30.257Z" }, - { url = "https://files.pythonhosted.org/packages/0c/d5/495fc7ae2fab20223cc87179a8f50f40f9a6f821f271ba8301ae12bb580f/fonttools-4.60.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f4b5c37a5f40e4d733d3bbaaef082149bee5a5ea3156a785ff64d949bd1353fa", size = 5132562, upload-time = "2025-09-29T21:11:32.737Z" }, - { url = "https://files.pythonhosted.org/packages/bc/fa/021dab618526323c744e0206b3f5c8596a2e7ae9aa38db5948a131123e83/fonttools-4.60.1-cp311-cp311-win32.whl", hash = "sha256:398447f3d8c0c786cbf1209711e79080a40761eb44b27cdafffb48f52bcec258", size = 2230288, upload-time = "2025-09-29T21:11:35.015Z" }, - { url = "https://files.pythonhosted.org/packages/bb/78/0e1a6d22b427579ea5c8273e1c07def2f325b977faaf60bb7ddc01456cb1/fonttools-4.60.1-cp311-cp311-win_amd64.whl", hash = "sha256:d066ea419f719ed87bc2c99a4a4bfd77c2e5949cb724588b9dd58f3fd90b92bf", size = 2278184, upload-time = "2025-09-29T21:11:37.434Z" }, - { url = "https://files.pythonhosted.org/packages/e3/f7/a10b101b7a6f8836a5adb47f2791f2075d044a6ca123f35985c42edc82d8/fonttools-4.60.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:7b0c6d57ab00dae9529f3faf187f2254ea0aa1e04215cf2f1a8ec277c96661bc", size = 2832953, upload-time = "2025-09-29T21:11:39.616Z" }, - { url = "https://files.pythonhosted.org/packages/ed/fe/7bd094b59c926acf2304d2151354ddbeb74b94812f3dc943c231db09cb41/fonttools-4.60.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:839565cbf14645952d933853e8ade66a463684ed6ed6c9345d0faf1f0e868877", size = 2352706, upload-time = "2025-09-29T21:11:41.826Z" }, - { url = "https://files.pythonhosted.org/packages/c0/ca/4bb48a26ed95a1e7eba175535fe5805887682140ee0a0d10a88e1de84208/fonttools-4.60.1-cp312-cp312-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:8177ec9676ea6e1793c8a084a90b65a9f778771998eb919d05db6d4b1c0b114c", size = 4923716, upload-time = "2025-09-29T21:11:43.893Z" }, - { url = "https://files.pythonhosted.org/packages/b8/9f/2cb82999f686c1d1ddf06f6ae1a9117a880adbec113611cc9d22b2fdd465/fonttools-4.60.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:996a4d1834524adbb423385d5a629b868ef9d774670856c63c9a0408a3063401", size = 4968175, upload-time = "2025-09-29T21:11:46.439Z" }, - { url = "https://files.pythonhosted.org/packages/18/79/be569699e37d166b78e6218f2cde8c550204f2505038cdd83b42edc469b9/fonttools-4.60.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a46b2f450bc79e06ef3b6394f0c68660529ed51692606ad7f953fc2e448bc903", size = 4911031, upload-time = "2025-09-29T21:11:48.977Z" }, - { url = "https://files.pythonhosted.org/packages/cc/9f/89411cc116effaec5260ad519162f64f9c150e5522a27cbb05eb62d0c05b/fonttools-4.60.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ec722ee589e89a89f5b7574f5c45604030aa6ae24cb2c751e2707193b466fed", size = 5062966, upload-time = "2025-09-29T21:11:54.344Z" }, - { url = "https://files.pythonhosted.org/packages/62/a1/f888221934b5731d46cb9991c7a71f30cb1f97c0ef5fcf37f8da8fce6c8e/fonttools-4.60.1-cp312-cp312-win32.whl", hash = "sha256:b2cf105cee600d2de04ca3cfa1f74f1127f8455b71dbad02b9da6ec266e116d6", size = 2218750, upload-time = "2025-09-29T21:11:56.601Z" }, - { url = "https://files.pythonhosted.org/packages/88/8f/a55b5550cd33cd1028601df41acd057d4be20efa5c958f417b0c0613924d/fonttools-4.60.1-cp312-cp312-win_amd64.whl", hash = "sha256:992775c9fbe2cf794786fa0ffca7f09f564ba3499b8fe9f2f80bd7197db60383", size = 2267026, upload-time = "2025-09-29T21:11:58.852Z" }, - { url = "https://files.pythonhosted.org/packages/7c/5b/cdd2c612277b7ac7ec8c0c9bc41812c43dc7b2d5f2b0897e15fdf5a1f915/fonttools-4.60.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:6f68576bb4bbf6060c7ab047b1574a1ebe5c50a17de62830079967b211059ebb", size = 2825777, upload-time = "2025-09-29T21:12:01.22Z" }, - { url = "https://files.pythonhosted.org/packages/d6/8a/de9cc0540f542963ba5e8f3a1f6ad48fa211badc3177783b9d5cadf79b5d/fonttools-4.60.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:eedacb5c5d22b7097482fa834bda0dafa3d914a4e829ec83cdea2a01f8c813c4", size = 2348080, upload-time = "2025-09-29T21:12:03.785Z" }, - { url = "https://files.pythonhosted.org/packages/2d/8b/371ab3cec97ee3fe1126b3406b7abd60c8fec8975fd79a3c75cdea0c3d83/fonttools-4.60.1-cp313-cp313-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:b33a7884fabd72bdf5f910d0cf46be50dce86a0362a65cfc746a4168c67eb96c", size = 4903082, upload-time = "2025-09-29T21:12:06.382Z" }, - { url = "https://files.pythonhosted.org/packages/04/05/06b1455e4bc653fcb2117ac3ef5fa3a8a14919b93c60742d04440605d058/fonttools-4.60.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2409d5fb7b55fd70f715e6d34e7a6e4f7511b8ad29a49d6df225ee76da76dd77", size = 4960125, upload-time = "2025-09-29T21:12:09.314Z" }, - { url = "https://files.pythonhosted.org/packages/8e/37/f3b840fcb2666f6cb97038793606bdd83488dca2d0b0fc542ccc20afa668/fonttools-4.60.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c8651e0d4b3bdeda6602b85fdc2abbefc1b41e573ecb37b6779c4ca50753a199", size = 4901454, upload-time = "2025-09-29T21:12:11.931Z" }, - { url = "https://files.pythonhosted.org/packages/fd/9e/eb76f77e82f8d4a46420aadff12cec6237751b0fb9ef1de373186dcffb5f/fonttools-4.60.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:145daa14bf24824b677b9357c5e44fd8895c2a8f53596e1b9ea3496081dc692c", size = 5044495, upload-time = "2025-09-29T21:12:15.241Z" }, - { url = "https://files.pythonhosted.org/packages/f8/b3/cede8f8235d42ff7ae891bae8d619d02c8ac9fd0cfc450c5927a6200c70d/fonttools-4.60.1-cp313-cp313-win32.whl", hash = "sha256:2299df884c11162617a66b7c316957d74a18e3758c0274762d2cc87df7bc0272", size = 2217028, upload-time = "2025-09-29T21:12:17.96Z" }, - { url = "https://files.pythonhosted.org/packages/75/4d/b022c1577807ce8b31ffe055306ec13a866f2337ecee96e75b24b9b753ea/fonttools-4.60.1-cp313-cp313-win_amd64.whl", hash = "sha256:a3db56f153bd4c5c2b619ab02c5db5192e222150ce5a1bc10f16164714bc39ac", size = 2266200, upload-time = "2025-09-29T21:12:20.14Z" }, - { url = "https://files.pythonhosted.org/packages/9a/83/752ca11c1aa9a899b793a130f2e466b79ea0cf7279c8d79c178fc954a07b/fonttools-4.60.1-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:a884aef09d45ba1206712c7dbda5829562d3fea7726935d3289d343232ecb0d3", size = 2822830, upload-time = "2025-09-29T21:12:24.406Z" }, - { url = "https://files.pythonhosted.org/packages/57/17/bbeab391100331950a96ce55cfbbff27d781c1b85ebafb4167eae50d9fe3/fonttools-4.60.1-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:8a44788d9d91df72d1a5eac49b31aeb887a5f4aab761b4cffc4196c74907ea85", size = 2345524, upload-time = "2025-09-29T21:12:26.819Z" }, - { url = "https://files.pythonhosted.org/packages/3d/2e/d4831caa96d85a84dd0da1d9f90d81cec081f551e0ea216df684092c6c97/fonttools-4.60.1-cp314-cp314-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e852d9dda9f93ad3651ae1e3bb770eac544ec93c3807888798eccddf84596537", size = 4843490, upload-time = "2025-09-29T21:12:29.123Z" }, - { url = "https://files.pythonhosted.org/packages/49/13/5e2ea7c7a101b6fc3941be65307ef8df92cbbfa6ec4804032baf1893b434/fonttools-4.60.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:154cb6ee417e417bf5f7c42fe25858c9140c26f647c7347c06f0cc2d47eff003", size = 4944184, upload-time = "2025-09-29T21:12:31.414Z" }, - { url = "https://files.pythonhosted.org/packages/0c/2b/cf9603551c525b73fc47c52ee0b82a891579a93d9651ed694e4e2cd08bb8/fonttools-4.60.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:5664fd1a9ea7f244487ac8f10340c4e37664675e8667d6fee420766e0fb3cf08", size = 4890218, upload-time = "2025-09-29T21:12:33.936Z" }, - { url = "https://files.pythonhosted.org/packages/fd/2f/933d2352422e25f2376aae74f79eaa882a50fb3bfef3c0d4f50501267101/fonttools-4.60.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:583b7f8e3c49486e4d489ad1deacfb8d5be54a8ef34d6df824f6a171f8511d99", size = 4999324, upload-time = "2025-09-29T21:12:36.637Z" }, - { url = "https://files.pythonhosted.org/packages/38/99/234594c0391221f66216bc2c886923513b3399a148defaccf81dc3be6560/fonttools-4.60.1-cp314-cp314-win32.whl", hash = "sha256:66929e2ea2810c6533a5184f938502cfdaea4bc3efb7130d8cc02e1c1b4108d6", size = 2220861, upload-time = "2025-09-29T21:12:39.108Z" }, - { url = "https://files.pythonhosted.org/packages/3e/1d/edb5b23726dde50fc4068e1493e4fc7658eeefcaf75d4c5ffce067d07ae5/fonttools-4.60.1-cp314-cp314-win_amd64.whl", hash = "sha256:f3d5be054c461d6a2268831f04091dc82753176f6ea06dc6047a5e168265a987", size = 2270934, upload-time = "2025-09-29T21:12:41.339Z" }, - { url = "https://files.pythonhosted.org/packages/fb/da/1392aaa2170adc7071fe7f9cfd181a5684a7afcde605aebddf1fb4d76df5/fonttools-4.60.1-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:b6379e7546ba4ae4b18f8ae2b9bc5960936007a1c0e30b342f662577e8bc3299", size = 2894340, upload-time = "2025-09-29T21:12:43.774Z" }, - { url = "https://files.pythonhosted.org/packages/bf/a7/3b9f16e010d536ce567058b931a20b590d8f3177b2eda09edd92e392375d/fonttools-4.60.1-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:9d0ced62b59e0430b3690dbc5373df1c2aa7585e9a8ce38eff87f0fd993c5b01", size = 2375073, upload-time = "2025-09-29T21:12:46.437Z" }, - { url = "https://files.pythonhosted.org/packages/9b/b5/e9bcf51980f98e59bb5bb7c382a63c6f6cac0eec5f67de6d8f2322382065/fonttools-4.60.1-cp314-cp314t-manylinux1_x86_64.manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:875cb7764708b3132637f6c5fb385b16eeba0f7ac9fa45a69d35e09b47045801", size = 4849758, upload-time = "2025-09-29T21:12:48.694Z" }, - { url = "https://files.pythonhosted.org/packages/e3/dc/1d2cf7d1cba82264b2f8385db3f5960e3d8ce756b4dc65b700d2c496f7e9/fonttools-4.60.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a184b2ea57b13680ab6d5fbde99ccef152c95c06746cb7718c583abd8f945ccc", size = 5085598, upload-time = "2025-09-29T21:12:51.081Z" }, - { url = "https://files.pythonhosted.org/packages/5d/4d/279e28ba87fb20e0c69baf72b60bbf1c4d873af1476806a7b5f2b7fac1ff/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:026290e4ec76583881763fac284aca67365e0be9f13a7fb137257096114cb3bc", size = 4957603, upload-time = "2025-09-29T21:12:53.423Z" }, - { url = "https://files.pythonhosted.org/packages/78/d4/ff19976305e0c05aa3340c805475abb00224c954d3c65e82c0a69633d55d/fonttools-4.60.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f0e8817c7d1a0c2eedebf57ef9a9896f3ea23324769a9a2061a80fe8852705ed", size = 4974184, upload-time = "2025-09-29T21:12:55.962Z" }, - { url = "https://files.pythonhosted.org/packages/63/22/8553ff6166f5cd21cfaa115aaacaa0dc73b91c079a8cfd54a482cbc0f4f5/fonttools-4.60.1-cp314-cp314t-win32.whl", hash = "sha256:1410155d0e764a4615774e5c2c6fc516259fe3eca5882f034eb9bfdbee056259", size = 2282241, upload-time = "2025-09-29T21:12:58.179Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cb/fa7b4d148e11d5a72761a22e595344133e83a9507a4c231df972e657579b/fonttools-4.60.1-cp314-cp314t-win_amd64.whl", hash = "sha256:022beaea4b73a70295b688f817ddc24ed3e3418b5036ffcd5658141184ef0d0c", size = 2345760, upload-time = "2025-09-29T21:13:00.375Z" }, - { url = "https://files.pythonhosted.org/packages/c7/93/0dd45cd283c32dea1545151d8c3637b4b8c53cdb3a625aeb2885b184d74d/fonttools-4.60.1-py3-none-any.whl", hash = "sha256:906306ac7afe2156fcf0042173d6ebbb05416af70f6b370967b47f8f00103bbb", size = 1143175, upload-time = "2025-09-29T21:13:24.134Z" }, +version = "4.62.1" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/9a/08/7012b00a9a5874311b639c3920270c36ee0c445b69d9989a85e5c92ebcb0/fonttools-4.62.1.tar.gz", hash = "sha256:e54c75fd6041f1122476776880f7c3c3295ffa31962dc6ebe2543c00dca58b5d", size = 3580737, upload-time = "2026-03-13T13:54:25.52Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/39/23ff32561ec8d45a4d48578b4d241369d9270dc50926c017570e60893701/fonttools-4.62.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:40975849bac44fb0b9253d77420c6d8b523ac4dcdcefeff6e4d706838a5b80f7", size = 2871039, upload-time = "2026-03-13T13:52:33.127Z" }, + { url = "https://files.pythonhosted.org/packages/24/7f/66d3f8a9338a9b67fe6e1739f47e1cd5cee78bd3bc1206ef9b0b982289a5/fonttools-4.62.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9dde91633f77fa576879a0c76b1d89de373cae751a98ddf0109d54e173b40f14", size = 2416346, upload-time = "2026-03-13T13:52:35.676Z" }, + { url = "https://files.pythonhosted.org/packages/aa/53/5276ceba7bff95da7793a07c5284e1da901cf00341ce5e2f3273056c0cca/fonttools-4.62.1-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6acb4109f8bee00fec985c8c7afb02299e35e9c94b57287f3ea542f28bd0b0a7", size = 5100897, upload-time = "2026-03-13T13:52:38.102Z" }, + { url = "https://files.pythonhosted.org/packages/cc/a1/40a5c4d8e28b0851d53a8eeeb46fbd73c325a2a9a165f290a5ed90e6c597/fonttools-4.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1c5c25671ce8805e0d080e2ffdeca7f1e86778c5cbfbeae86d7f866d8830517b", size = 5071078, upload-time = "2026-03-13T13:52:41.305Z" }, + { url = "https://files.pythonhosted.org/packages/e3/be/d378fca4c65ea1956fee6d90ace6e861776809cbbc5af22388a090c3c092/fonttools-4.62.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a5d8825e1140f04e6c99bb7d37a9e31c172f3bc208afbe02175339e699c710e1", size = 5076908, upload-time = "2026-03-13T13:52:44.122Z" }, + { url = "https://files.pythonhosted.org/packages/f8/d9/ae6a1d0693a4185a84605679c8a1f719a55df87b9c6e8e817bfdd9ef5936/fonttools-4.62.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:268abb1cb221e66c014acc234e872b7870d8b5d4657a83a8f4205094c32d2416", size = 5202275, upload-time = "2026-03-13T13:52:46.591Z" }, + { url = "https://files.pythonhosted.org/packages/54/6c/af95d9c4efb15cabff22642b608342f2bd67137eea6107202d91b5b03184/fonttools-4.62.1-cp311-cp311-win32.whl", hash = "sha256:942b03094d7edbb99bdf1ae7e9090898cad7bf9030b3d21f33d7072dbcb51a53", size = 2293075, upload-time = "2026-03-13T13:52:48.711Z" }, + { url = "https://files.pythonhosted.org/packages/d3/97/bf54c5b3f2be34e1f143e6db838dfdc54f2ffa3e68c738934c82f3b2a08d/fonttools-4.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:e8514f4924375f77084e81467e63238b095abda5107620f49421c368a6017ed2", size = 2344593, upload-time = "2026-03-13T13:52:50.725Z" }, + { url = "https://files.pythonhosted.org/packages/47/d4/dbacced3953544b9a93088cc10ef2b596d348c983d5c67a404fa41ec51ba/fonttools-4.62.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:90365821debbd7db678809c7491ca4acd1e0779b9624cdc6ddaf1f31992bf974", size = 2870219, upload-time = "2026-03-13T13:52:53.664Z" }, + { url = "https://files.pythonhosted.org/packages/66/9e/a769c8e99b81e5a87ab7e5e7236684de4e96246aae17274e5347d11ebd78/fonttools-4.62.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:12859ff0b47dd20f110804c3e0d0970f7b832f561630cd879969011541a464a9", size = 2414891, upload-time = "2026-03-13T13:52:56.493Z" }, + { url = "https://files.pythonhosted.org/packages/69/64/f19a9e3911968c37e1e620e14dfc5778299e1474f72f4e57c5ec771d9489/fonttools-4.62.1-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9c125ffa00c3d9003cdaaf7f2c79e6e535628093e14b5de1dccb08859b680936", size = 5033197, upload-time = "2026-03-13T13:52:59.179Z" }, + { url = "https://files.pythonhosted.org/packages/9b/8a/99c8b3c3888c5c474c08dbfd7c8899786de9604b727fcefb055b42c84bba/fonttools-4.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:149f7d84afca659d1a97e39a4778794a2f83bf344c5ee5134e09995086cc2392", size = 4988768, upload-time = "2026-03-13T13:53:02.761Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c6/0f904540d3e6ab463c1243a0d803504826a11604c72dd58c2949796a1762/fonttools-4.62.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:0aa72c43a601cfa9273bb1ae0518f1acadc01ee181a6fc60cd758d7fdadffc04", size = 4971512, upload-time = "2026-03-13T13:53:05.678Z" }, + { url = "https://files.pythonhosted.org/packages/29/0b/5cbef6588dc9bd6b5c9ad6a4d5a8ca384d0cea089da31711bbeb4f9654a6/fonttools-4.62.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:19177c8d96c7c36359266e571c5173bcee9157b59cfc8cb0153c5673dc5a3a7d", size = 5122723, upload-time = "2026-03-13T13:53:08.662Z" }, + { url = "https://files.pythonhosted.org/packages/4a/47/b3a5342d381595ef439adec67848bed561ab7fdb1019fa522e82101b7d9c/fonttools-4.62.1-cp312-cp312-win32.whl", hash = "sha256:a24decd24d60744ee8b4679d38e88b8303d86772053afc29b19d23bb8207803c", size = 2281278, upload-time = "2026-03-13T13:53:10.998Z" }, + { url = "https://files.pythonhosted.org/packages/28/b1/0c2ab56a16f409c6c8a68816e6af707827ad5d629634691ff60a52879792/fonttools-4.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e7863e10b3de72376280b515d35b14f5eeed639d1aa7824f4cf06779ec65e42", size = 2331414, upload-time = "2026-03-13T13:53:13.992Z" }, + { url = "https://files.pythonhosted.org/packages/3b/56/6f389de21c49555553d6a5aeed5ac9767631497ac836c4f076273d15bd72/fonttools-4.62.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:c22b1014017111c401469e3acc5433e6acf6ebcc6aa9efb538a533c800971c79", size = 2865155, upload-time = "2026-03-13T13:53:16.132Z" }, + { url = "https://files.pythonhosted.org/packages/03/c5/0e3966edd5ec668d41dfe418787726752bc07e2f5fd8c8f208615e61fa89/fonttools-4.62.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:68959f5fc58ed4599b44aad161c2837477d7f35f5f79402d97439974faebfebe", size = 2412802, upload-time = "2026-03-13T13:53:18.878Z" }, + { url = "https://files.pythonhosted.org/packages/52/94/e6ac4b44026de7786fe46e3bfa0c87e51d5d70a841054065d49cd62bb909/fonttools-4.62.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef46db46c9447103b8f3ff91e8ba009d5fe181b1920a83757a5762551e32bb68", size = 5013926, upload-time = "2026-03-13T13:53:21.379Z" }, + { url = "https://files.pythonhosted.org/packages/e2/98/8b1e801939839d405f1f122e7d175cebe9aeb4e114f95bfc45e3152af9a7/fonttools-4.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6706d1cb1d5e6251a97ad3c1b9347505c5615c112e66047abbef0f8545fa30d1", size = 4964575, upload-time = "2026-03-13T13:53:23.857Z" }, + { url = "https://files.pythonhosted.org/packages/46/76/7d051671e938b1881670528fec69cc4044315edd71a229c7fd712eaa5119/fonttools-4.62.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2e7abd2b1e11736f58c1de27819e1955a53267c21732e78243fa2fa2e5c1e069", size = 4953693, upload-time = "2026-03-13T13:53:26.569Z" }, + { url = "https://files.pythonhosted.org/packages/1f/ae/b41f8628ec0be3c1b934fc12b84f4576a5c646119db4d3bdd76a217c90b5/fonttools-4.62.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:403d28ce06ebfc547fbcb0cb8b7f7cc2f7a2d3e1a67ba9a34b14632df9e080f9", size = 5094920, upload-time = "2026-03-13T13:53:29.329Z" }, + { url = "https://files.pythonhosted.org/packages/f2/f6/53a1e9469331a23dcc400970a27a4caa3d9f6edbf5baab0260285238b884/fonttools-4.62.1-cp313-cp313-win32.whl", hash = "sha256:93c316e0f5301b2adbe6a5f658634307c096fd5aae60a5b3412e4f3e1728ab24", size = 2279928, upload-time = "2026-03-13T13:53:32.352Z" }, + { url = "https://files.pythonhosted.org/packages/38/60/35186529de1db3c01f5ad625bde07c1f576305eab6d86bbda4c58445f721/fonttools-4.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:7aa21ff53e28a9c2157acbc44e5b401149d3c9178107130e82d74ceb500e5056", size = 2330514, upload-time = "2026-03-13T13:53:34.991Z" }, + { url = "https://files.pythonhosted.org/packages/36/f0/2888cdac391807d68d90dcb16ef858ddc1b5309bfc6966195a459dd326e2/fonttools-4.62.1-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:fa1d16210b6b10a826d71bed68dd9ec24a9e218d5a5e2797f37c573e7ec215ca", size = 2864442, upload-time = "2026-03-13T13:53:37.509Z" }, + { url = "https://files.pythonhosted.org/packages/4b/b2/e521803081f8dc35990816b82da6360fa668a21b44da4b53fc9e77efcd62/fonttools-4.62.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:aa69d10ed420d8121118e628ad47d86e4caa79ba37f968597b958f6cceab7eca", size = 2410901, upload-time = "2026-03-13T13:53:40.55Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/8c3511ff06e53110039358dbbdc1a65d72157a054638387aa2ada300a8b8/fonttools-4.62.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bd13b7999d59c5eb1c2b442eb2d0c427cb517a0b7a1f5798fc5c9e003f5ff782", size = 4999608, upload-time = "2026-03-13T13:53:42.798Z" }, + { url = "https://files.pythonhosted.org/packages/28/63/cd0c3b26afe60995a5295f37c246a93d454023726c3261cfbb3559969bb9/fonttools-4.62.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8d337fdd49a79b0d51c4da87bc38169d21c3abbf0c1aa9367eff5c6656fb6dae", size = 4912726, upload-time = "2026-03-13T13:53:45.405Z" }, + { url = "https://files.pythonhosted.org/packages/70/b9/ac677cb07c24c685cf34f64e140617d58789d67a3dd524164b63648c6114/fonttools-4.62.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d241cdc4a67b5431c6d7f115fdf63335222414995e3a1df1a41e1182acd4bcc7", size = 4951422, upload-time = "2026-03-13T13:53:48.326Z" }, + { url = "https://files.pythonhosted.org/packages/e6/10/11c08419a14b85b7ca9a9faca321accccc8842dd9e0b1c8a72908de05945/fonttools-4.62.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:c05557a78f8fa514da0f869556eeda40887a8abc77c76ee3f74cf241778afd5a", size = 5060979, upload-time = "2026-03-13T13:53:51.366Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3c/12eea4a4cf054e7ab058ed5ceada43b46809fce2bf319017c4d63ae55bb4/fonttools-4.62.1-cp314-cp314-win32.whl", hash = "sha256:49a445d2f544ce4a69338694cad575ba97b9a75fff02720da0882d1a73f12800", size = 2283733, upload-time = "2026-03-13T13:53:53.606Z" }, + { url = "https://files.pythonhosted.org/packages/6b/67/74b070029043186b5dd13462c958cb7c7f811be0d2e634309d9a1ffb1505/fonttools-4.62.1-cp314-cp314-win_amd64.whl", hash = "sha256:1eecc128c86c552fb963fe846ca4e011b1be053728f798185a1687502f6d398e", size = 2335663, upload-time = "2026-03-13T13:53:56.23Z" }, + { url = "https://files.pythonhosted.org/packages/42/c5/4d2ed3ca6e33617fc5624467da353337f06e7f637707478903c785bd8e20/fonttools-4.62.1-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:1596aeaddf7f78e21e68293c011316a25267b3effdaccaf4d59bc9159d681b82", size = 2947288, upload-time = "2026-03-13T13:53:59.397Z" }, + { url = "https://files.pythonhosted.org/packages/1f/e9/7ab11ddfda48ed0f89b13380e5595ba572619c27077be0b2c447a63ff351/fonttools-4.62.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8f8fca95d3bb3208f59626a4b0ea6e526ee51f5a8ad5d91821c165903e8d9260", size = 2449023, upload-time = "2026-03-13T13:54:01.642Z" }, + { url = "https://files.pythonhosted.org/packages/b2/10/a800fa090b5e8819942e54e19b55fc7c21fe14a08757c3aa3ca8db358939/fonttools-4.62.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ee91628c08e76f77b533d65feb3fbe6d9dad699f95be51cf0d022db94089cdc4", size = 5137599, upload-time = "2026-03-13T13:54:04.495Z" }, + { url = "https://files.pythonhosted.org/packages/37/dc/8ccd45033fffd74deb6912fa1ca524643f584b94c87a16036855b498a1ed/fonttools-4.62.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5f37df1cac61d906e7b836abe356bc2f34c99d4477467755c216b72aa3dc748b", size = 4920933, upload-time = "2026-03-13T13:54:07.557Z" }, + { url = "https://files.pythonhosted.org/packages/99/eb/e618adefb839598d25ac8136cd577925d6c513dc0d931d93b8af956210f0/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:92bb00a947e666169c99b43753c4305fc95a890a60ef3aeb2a6963e07902cc87", size = 5016232, upload-time = "2026-03-13T13:54:10.611Z" }, + { url = "https://files.pythonhosted.org/packages/d9/5f/9b5c9bfaa8ec82def8d8168c4f13615990d6ce5996fe52bd49bfb5e05134/fonttools-4.62.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:bdfe592802ef939a0e33106ea4a318eeb17822c7ee168c290273cbd5fabd746c", size = 5042987, upload-time = "2026-03-13T13:54:13.569Z" }, + { url = "https://files.pythonhosted.org/packages/90/aa/dfbbe24c6a6afc5c203d90cc0343e24bcbb09e76d67c4d6eef8c2558d7ba/fonttools-4.62.1-cp314-cp314t-win32.whl", hash = "sha256:b820fcb92d4655513d8402d5b219f94481c4443d825b4372c75a2072aa4b357a", size = 2348021, upload-time = "2026-03-13T13:54:16.98Z" }, + { url = "https://files.pythonhosted.org/packages/13/6f/ae9c4e4dd417948407b680855c2c7790efb52add6009aaecff1e3bc50e8e/fonttools-4.62.1-cp314-cp314t-win_amd64.whl", hash = "sha256:59b372b4f0e113d3746b88985f1c796e7bf830dd54b28374cd85c2b8acd7583e", size = 2414147, upload-time = "2026-03-13T13:54:19.416Z" }, + { url = "https://files.pythonhosted.org/packages/fd/ba/56147c165442cc5ba7e82ecf301c9a68353cede498185869e6e02b4c264f/fonttools-4.62.1-py3-none-any.whl", hash = "sha256:7487782e2113861f4ddcc07c3436450659e3caa5e470b27dc2177cade2d8e7fd", size = 1152647, upload-time = "2026-03-13T13:54:22.735Z" }, ] [[package]] name = "fsspec" -version = "2025.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/de/e0/bab50af11c2d75c9c4a2a26a5254573c0bd97cea152254401510950486fa/fsspec-2025.9.0.tar.gz", hash = "sha256:19fd429483d25d28b65ec68f9f4adc16c17ea2c7c7bf54ec61360d478fb19c19", size = 304847, upload-time = "2025-09-02T19:10:49.215Z" } +version = "2026.3.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e1/cf/b50ddf667c15276a9ab15a70ef5f257564de271957933ffea49d2cdbcdfb/fsspec-2026.3.0.tar.gz", hash = "sha256:1ee6a0e28677557f8c2f994e3eea77db6392b4de9cd1f5d7a9e87a0ae9d01b41", size = 313547, upload-time = "2026-03-27T19:11:14.892Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/47/71/70db47e4f6ce3e5c37a607355f80da8860a33226be640226ac52cb05ef2e/fsspec-2025.9.0-py3-none-any.whl", hash = "sha256:530dc2a2af60a414a832059574df4a6e10cce927f6f4a78209390fe38955cfb7", size = 199289, upload-time = "2025-09-02T19:10:47.708Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1f/5f4a3cd9e4440e9d9bc78ad0a91a1c8d46b4d429d5239ebe6793c9fe5c41/fsspec-2026.3.0-py3-none-any.whl", hash = "sha256:d2ceafaad1b3457968ed14efa28798162f1638dbb5d2a6868a2db002a5ee39a4", size = 202595, upload-time = "2026-03-27T19:11:13.595Z" }, ] [[package]] name = "imagecodecs" -version = "2025.8.2" -source = { registry = "https://pypi.org/simple" } +version = "2026.1.14" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6b/d1/bac78c9bdecbc12bcaa96363dd6d7b358de0b4af512d08c16f6792e7a6ea/imagecodecs-2025.8.2.tar.gz", hash = "sha256:2af272aac90c370326a7e2fffcbbbd32d42de07576959a2a98d60110267dfe6c", size = 9490135, upload-time = "2025-08-03T06:08:38.148Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3f/af/85a9fbfd3404d9e7050052907086a0d78004b953716e104c6c67afe01a7a/imagecodecs-2025.8.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:be6524a5aaaa22684390d7450b647e4c2ffcf284eb2433ca344518292eb484ba", size = 12518594, upload-time = "2025-08-03T06:06:37.981Z" }, - { url = "https://files.pythonhosted.org/packages/e1/b2/ad5c38b500a6bc12762c0a16529a8123abfa12b23fb937e74357ffad9bca/imagecodecs-2025.8.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:d588e51fe0ff35d759c310e209c12c76f5db35893de3e95c8e7fa69d78244ca5", size = 10168537, upload-time = "2025-08-03T06:06:40.788Z" }, - { url = "https://files.pythonhosted.org/packages/3c/8e/58a58f851043325f955bfc30eef14a23bc15dd02c343f7bf4028c27fd005/imagecodecs-2025.8.2-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e13144b6af43bf75024a92f1522af6d8fd0ef61c6e0fcd01ae028961df93963a", size = 25522552, upload-time = "2025-08-03T06:06:44.527Z" }, - { url = "https://files.pythonhosted.org/packages/d6/7e/4eb0dc145b7abfbff4139251326946b44d375ae0ab29c775a58adee63a0f/imagecodecs-2025.8.2-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e993b0b1601d19ee1aadb10fceb93524d88b874ee728f82d3f9b40bddccd223", size = 26369822, upload-time = "2025-08-03T06:06:48.308Z" }, - { url = "https://files.pythonhosted.org/packages/ee/1b/05baebc43671f7f6d1d169841438cd689a12e257a021eab6fa5ebc00b5e1/imagecodecs-2025.8.2-cp311-cp311-win32.whl", hash = "sha256:a1ec2006cbc25bf273616c90478338d44f37aafa50935dcbc18767775b416722", size = 18567263, upload-time = "2025-08-03T06:06:51.692Z" }, - { url = "https://files.pythonhosted.org/packages/10/27/0254f64a1d759f22f2c3add691ef915bba016aa7b3630192bbaa9ce1d65d/imagecodecs-2025.8.2-cp311-cp311-win_amd64.whl", hash = "sha256:369a2e0784787ec2ea56676de5fdb0bdaff3202679f45b27e51261f0960923e1", size = 22686127, upload-time = "2025-08-03T06:06:55.182Z" }, - { url = "https://files.pythonhosted.org/packages/72/89/3a43d23e27da6e145148b96886b9f1b32b864cc4dc5f5356e585e2eef863/imagecodecs-2025.8.2-cp311-cp311-win_arm64.whl", hash = "sha256:5fa688d3521edeb192179c9808182f00064fb123af11c989a5d509e36a178ebe", size = 18003465, upload-time = "2025-08-03T06:06:58.387Z" }, - { url = "https://files.pythonhosted.org/packages/3b/2e/b129b2bf95a4332b86452dd93829cb6ab1e203d86a488fc8639e9f08db7d/imagecodecs-2025.8.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:32ce9707c85cf90e5fb4d9e076ec78288f0f1c084954376f798397743bc4d4e5", size = 12480416, upload-time = "2025-08-03T06:07:02.511Z" }, - { url = "https://files.pythonhosted.org/packages/dc/36/b1d3117c34f5eebfd7a92314042338dce608654173c71b1fb707e416aef5/imagecodecs-2025.8.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b7cd0c41ea83f249db2f4ee04c7c3f23bd6fbef89a58bdaa7d2bf11421ffc15", size = 10127356, upload-time = "2025-08-03T06:07:04.771Z" }, - { url = "https://files.pythonhosted.org/packages/e6/41/9ee8f13c4e3425c1aed0b9c5bca5547b968da20cb60af094fafcb52be05f/imagecodecs-2025.8.2-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:10f421048206ffb6763b1eb28f2205535801794e9f2c612bbcc219d14b9e44bc", size = 25765528, upload-time = "2025-08-03T06:07:08.044Z" }, - { url = "https://files.pythonhosted.org/packages/d6/1a/f12736807a179a4bd5a26387f2edf00cc3f972139b61f2833f6fbe9b9685/imagecodecs-2025.8.2-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f14a5eb8151e32316028c09212eed1d14334460e8adbbef819c1d8ec6ab7e633", size = 26718026, upload-time = "2025-08-03T06:07:13.54Z" }, - { url = "https://files.pythonhosted.org/packages/9c/cd/b0367017443af901279952817900e7f496998fb11de24d91cccdb4fa3dc3/imagecodecs-2025.8.2-cp312-cp312-win32.whl", hash = "sha256:e5cb491fa698d055643d3519bab36a3da8d4144ac759430464e2f75f945f3325", size = 18546766, upload-time = "2025-08-03T06:07:16.836Z" }, - { url = "https://files.pythonhosted.org/packages/c4/97/5eb48564f5fdbe3ef83a07ba45d301d45bf85af382a051de34e0a3d6af85/imagecodecs-2025.8.2-cp312-cp312-win_amd64.whl", hash = "sha256:7371169cff1b98f07a983b28fffd2d334cf40a61fce3c876dda930149081bdeb", size = 22674367, upload-time = "2025-08-03T06:07:20.208Z" }, - { url = "https://files.pythonhosted.org/packages/2b/b9/a1a70b5d2250937cd747ceff94ddb9cb70d6f1b9fef9f4164d88c63dd3cb/imagecodecs-2025.8.2-cp312-cp312-win_arm64.whl", hash = "sha256:bf0a97cd58810d5d7ecd983332f1f85a0df991080a774f7caadb1add172165d0", size = 17981844, upload-time = "2025-08-03T06:07:23.219Z" }, - { url = "https://files.pythonhosted.org/packages/f2/69/77a5c020030dbba1a566264b9201f091534b4b10e9ac5725b7bd40895a8b/imagecodecs-2025.8.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:06a1b54847cbf595ed58879936eabdab990f1780b80d03a8d4edb57e61c768b0", size = 12454348, upload-time = "2025-08-03T06:07:26.135Z" }, - { url = "https://files.pythonhosted.org/packages/f9/5d/c5dd7f0706dc48d38deefe4cba05ac78236b662a8ef86f9c0cc569b9db96/imagecodecs-2025.8.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbf3abd281034e29034b773407c27eff35dd94832a9b6e3c97490db079e3c0a8", size = 10103114, upload-time = "2025-08-03T06:07:28.657Z" }, - { url = "https://files.pythonhosted.org/packages/60/40/8b656577120f758ce4b177917b57c76f15e695ff0e63584f641db2063bbe/imagecodecs-2025.8.2-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:248bb2ea3e43690196acdb1dae5aa40213dbd00c47255295d57da80770ceeaa7", size = 25602557, upload-time = "2025-08-03T06:07:32.399Z" }, - { url = "https://files.pythonhosted.org/packages/8a/de/6c1cf78cc0ecc45d98a0eb0d8920df7b90719f8643c7ed9b1bb700f95890/imagecodecs-2025.8.2-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:52230fcd0c331b0167ccd14d7ce764bb780006b65bf69761d8bde6863419fdbf", size = 26544468, upload-time = "2025-08-03T06:07:36.096Z" }, - { url = "https://files.pythonhosted.org/packages/51/14/bda4974256e31eca65e33446026c91d54d1124aa59ce042fc326836597ec/imagecodecs-2025.8.2-cp313-cp313-win32.whl", hash = "sha256:151f9e0879ed8a025b19fcffbf4785dacf29002d5fec94d318e84ba154ddd54c", size = 18534480, upload-time = "2025-08-03T06:07:39.901Z" }, - { url = "https://files.pythonhosted.org/packages/a6/bb/ada8851ee56ab835562fb96f764f055e16a5d43a81ebc95207f6b2f3c1d4/imagecodecs-2025.8.2-cp313-cp313-win_amd64.whl", hash = "sha256:d167c265caaf36f9f090e55b68a7c0ec4e5b436923cdc047358743f450534154", size = 22662134, upload-time = "2025-08-03T06:07:43.845Z" }, - { url = "https://files.pythonhosted.org/packages/7c/b6/687ad4e137fe637213540cf71bf6a3957cc48667ce6d96d6d9dcb8409305/imagecodecs-2025.8.2-cp313-cp313-win_arm64.whl", hash = "sha256:37199e19d61c7a6c0cf901859d7a81c4449d18047a15ca9d2ebe17176c8d1b69", size = 17968181, upload-time = "2025-08-03T06:07:47.254Z" }, - { url = "https://files.pythonhosted.org/packages/63/5f/2be51d6ea6e6cae13d8d4ce77d5076ef72e492f670368bb193db35e146ce/imagecodecs-2025.8.2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:7a3c89b5f5c946d5649892e15b5c89aeca357d048331c3a4ae89009320d2704a", size = 12447596, upload-time = "2025-08-03T06:07:49.804Z" }, - { url = "https://files.pythonhosted.org/packages/6e/75/d9cd579b1fd5fdef5742567236adb49df15beaf8f66219412f21fb86a64c/imagecodecs-2025.8.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:3a4d2249e4d25a57da7da336c68efc72a7e15f9271dc6c13c322947f30383b8e", size = 10107274, upload-time = "2025-08-03T06:07:52.787Z" }, - { url = "https://files.pythonhosted.org/packages/57/46/011e41f99f2301091f902afd917a4a8079056510dfcb8b8529d96e4232c8/imagecodecs-2025.8.2-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:31ebfca1bd01c6e4db25c0a91301ddc9aeca1dc63642db689543a9700a5869e8", size = 25579702, upload-time = "2025-08-03T06:07:56.392Z" }, - { url = "https://files.pythonhosted.org/packages/bd/9b/b2d38a3902b2a61cfdbe1bca7aa939b77e32349d00aba7a4014d1dfd8cb9/imagecodecs-2025.8.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:be84bd8f3cf1552efc92f2465af5ab40a14a788d48482ef3a702e9dae5f4cd0b", size = 26372160, upload-time = "2025-08-03T06:08:00.079Z" }, - { url = "https://files.pythonhosted.org/packages/c2/96/a22ce5b43a93d12b21f2ec9e374cd4a7edf9347630c7ed90bef7c4ca9f5d/imagecodecs-2025.8.2-cp314-cp314-win32.whl", hash = "sha256:ce57af27547c42dfb888562a1a22dc51a6103c20b3fb69ac4c26121acc741ade", size = 18802212, upload-time = "2025-08-03T06:08:03.341Z" }, - { url = "https://files.pythonhosted.org/packages/b3/0c/d364bff2ffb8f2864e9f7fa6dd05b57c25a4c341ba1e2f08b3dea1dc8a6d/imagecodecs-2025.8.2-cp314-cp314-win_amd64.whl", hash = "sha256:b93d77293c0aa9e661d42f3203b13ea135d5bf9f0936fbbe90780ed1c67322d3", size = 23052776, upload-time = "2025-08-03T06:08:06.975Z" }, - { url = "https://files.pythonhosted.org/packages/7d/4b/54d1e3a2b9d11c8a875c98b543dc5527c259c174b05987b1e79ccfc107b9/imagecodecs-2025.8.2-cp314-cp314-win_arm64.whl", hash = "sha256:d7a53983d4df035761dc652e44c912092bddc5b115d6f5f612df301ce94fcd55", size = 18374009, upload-time = "2025-08-03T06:08:10.074Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/af/75/cedaa3dba300df85712515b9c9e13d848ea9557b796b6c44b50bd361571e/imagecodecs-2026.1.14.tar.gz", hash = "sha256:e37ef5116d41ba90b1c9d1d7121846671fd65c271f0c15ef24208353fa79b283", size = 9527808, upload-time = "2026-01-14T04:24:31.234Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f6/b2/c36b8633c3303ed8d80c8d8490c8488baa7f9e6f46fd11688cb9e68eae5e/imagecodecs-2026.1.14-cp311-abi3-macosx_10_14_x86_64.whl", hash = "sha256:b94c57922816eb025d443f4594f1235d80f0f56b4b48aa9b60bf9d679ea49415", size = 12861649, upload-time = "2026-01-14T04:23:45.235Z" }, + { url = "https://files.pythonhosted.org/packages/26/79/71780373cef2ec5d9d2f111010ff0a16de0788fdc9a8f26f3cce05d0ed38/imagecodecs-2026.1.14-cp311-abi3-macosx_11_0_arm64.whl", hash = "sha256:bd72bdff628d6c32c71f086e488f483abbf84816b05d439964d980af49f2a9c5", size = 10700009, upload-time = "2026-01-14T04:23:48.108Z" }, + { url = "https://files.pythonhosted.org/packages/84/b4/48fc1a9b2379941a752d046b6d9217a1e82c09ed11184a18245cbb0d9c8b/imagecodecs-2026.1.14-cp311-abi3-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e92f8b3bddf632c23d3a832f35e5a2c2326eb0e2ae1ebce419789cce63e5c30", size = 23748325, upload-time = "2026-01-14T04:23:51.644Z" }, + { url = "https://files.pythonhosted.org/packages/02/e1/8a299b91a4abee7c299c0c6625f9c0985c623fd4b6b41b5a5fe92508bb18/imagecodecs-2026.1.14-cp311-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8a78451926905459f42e827c207d80e1601abf68eaf0e38fd65ddd3c346d1f47", size = 24716561, upload-time = "2026-01-14T04:23:55.275Z" }, + { url = "https://files.pythonhosted.org/packages/81/af/f03fa2a60617a46814fea5523f8d53a703aabbb316e029b21efe8fe04f9f/imagecodecs-2026.1.14-cp311-abi3-win32.whl", hash = "sha256:6bbf7defac9f71e0401305440f7e94160201789e03ee75e6e5709bc904742429", size = 17224233, upload-time = "2026-01-14T04:23:58.261Z" }, + { url = "https://files.pythonhosted.org/packages/b6/22/ae01473653dbad9e10a1632b5d8ae6473de0f3ec2b82c912836661d501a9/imagecodecs-2026.1.14-cp311-abi3-win_amd64.whl", hash = "sha256:13ec4659d05010aa072644f100d0e1e1fcc61d7eaa960923b8216272682e6c9a", size = 21544843, upload-time = "2026-01-14T04:24:02.387Z" }, + { url = "https://files.pythonhosted.org/packages/54/6e/86fa1a07aee2ea39acfa04e372a144a72b4cdc80f40a11a3ee312c12d312/imagecodecs-2026.1.14-cp311-abi3-win_arm64.whl", hash = "sha256:2a6102f3b99c66e090a619f8a0204e6e95c01399854ffa09e4a9de476dc1671a", size = 16893409, upload-time = "2026-01-14T04:24:05.764Z" }, + { url = "https://files.pythonhosted.org/packages/4e/a3/f92989c2de762ee4db9f5454bbafa540cfa13d28006b4aeb01936f9a4a8a/imagecodecs-2026.1.14-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:455915b32ae1ecccaeb9dad32ad1499aa2e6928b6b853e3635cb32af9aa56857", size = 13202548, upload-time = "2026-01-14T04:24:08.515Z" }, + { url = "https://files.pythonhosted.org/packages/bb/da/9fb0e446f5af6fa21f44a1f1d4950e538345d7e8931b5983a543a24b9c38/imagecodecs-2026.1.14-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c189f5d842f1f2524c27c307220c5f2cf201af44dab74b1f7476645f72eb2e87", size = 11004937, upload-time = "2026-01-14T04:24:11.054Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e9/0400722f5bdaaa9bc13ad77c78606cfb767f2b609cb136cc81d81c380807/imagecodecs-2026.1.14-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ca3325e68dffeb29383f4a28957ac61d3c0ade1b244ebf70797bdaf009ad1578", size = 27442756, upload-time = "2026-01-14T04:24:15.247Z" }, + { url = "https://files.pythonhosted.org/packages/6e/f1/f39cacf55d5e7175f1e7b5222178e7c56d6ca1a49e61011adb1150d85aec/imagecodecs-2026.1.14-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6819b8365cf28e2a85dcb04fb9c1f1acb63cf5da61495910cd751a7e2548e1d", size = 28006255, upload-time = "2026-01-14T04:24:19.458Z" }, + { url = "https://files.pythonhosted.org/packages/ad/58/b098490e93435ef4311c473e30588e80f966f6d676f1343804f8ab3fcff1/imagecodecs-2026.1.14-cp314-cp314t-win32.whl", hash = "sha256:ae422def59fab163ba0cb79470e10aa64b8a62b29e74d50ecd0edab4349444c2", size = 17909361, upload-time = "2026-01-14T04:24:22.548Z" }, + { url = "https://files.pythonhosted.org/packages/b3/35/cb1fc0ce35dd61475672af65607fe2c3cc30d7844d617067a3e649b8366e/imagecodecs-2026.1.14-cp314-cp314t-win_amd64.whl", hash = "sha256:9b11ced125e7b8da2a9d1a30505827cdb27f7023ccee4cd4cc5e3b352dce8574", size = 22496362, upload-time = "2026-01-14T04:24:25.874Z" }, + { url = "https://files.pythonhosted.org/packages/04/f7/2dfe80928bf0c6c6e7411954fa008a71d0b08e72fe1028fe9a8a8116588a/imagecodecs-2026.1.14-cp314-cp314t-win_arm64.whl", hash = "sha256:d4c19df6142481d2fda62d91542c078aecd647f120da52cc79019ed6938e587d", size = 17534288, upload-time = "2026-01-14T04:24:28.911Z" }, ] [[package]] name = "imageio" -version = "2.37.0" -source = { registry = "https://pypi.org/simple" } +version = "2.37.3" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "numpy" }, { name = "pillow" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/0c/47/57e897fb7094afb2d26e8b2e4af9a45c7cf1a405acdeeca001fdf2c98501/imageio-2.37.0.tar.gz", hash = "sha256:71b57b3669666272c818497aebba2b4c5f20d5b37c81720e5e1a56d59c492996", size = 389963, upload-time = "2025-01-20T02:42:37.089Z" } +sdist = { url = "https://files.pythonhosted.org/packages/b1/84/93bcd1300216ea50811cee96873b84a1bebf8d0489ffaf7f2a3756bab866/imageio-2.37.3.tar.gz", hash = "sha256:bbb37efbfc4c400fcd534b367b91fcd66d5da639aaa138034431a1c5e0a41451", size = 389673, upload-time = "2026-03-09T11:31:12.573Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/cb/bd/b394387b598ed84d8d0fa90611a90bee0adc2021820ad5729f7ced74a8e2/imageio-2.37.0-py3-none-any.whl", hash = "sha256:11efa15b87bc7871b61590326b2d635439acc321cf7f8ce996f812543ce10eed", size = 315796, upload-time = "2025-01-20T02:42:34.931Z" }, + { url = "https://files.pythonhosted.org/packages/49/fa/391e437a34e55095173dca5f24070d89cbc233ff85bf1c29c93248c6588d/imageio-2.37.3-py3-none-any.whl", hash = "sha256:46f5bb8522cd421c0f5ae104d8268f569d856b29eb1a13b92829d1970f32c9f0", size = 317646, upload-time = "2026-03-09T11:31:10.771Z" }, ] [[package]] name = "importlib-resources" -version = "6.5.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/cf/8c/f834fbf984f691b4f7ff60f50b514cc3de5cc08abfc3295564dd89c5e2e7/importlib_resources-6.5.2.tar.gz", hash = "sha256:185f87adef5bcc288449d98fb4fba07cea78bc036455dd44c5fc4a2fe78fed2c", size = 44693, upload-time = "2025-01-03T18:51:56.698Z" } +version = "7.1.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/06/b56dfa750b44e86157093bc8fca0ab81dccbf5260510de4eaf1cb69b5b99/importlib_resources-7.1.0.tar.gz", hash = "sha256:0722d4c6212489c530f2a145a34c0a7a3b4721bc96a15fada5930e2a0b760708", size = 44985, upload-time = "2026-04-12T16:36:09.232Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a4/ed/1f1afb2e9e7f38a545d628f864d562a5ae64fe6f7a10e28ffb9b185b4e89/importlib_resources-6.5.2-py3-none-any.whl", hash = "sha256:789cfdc3ed28c78b67a06acb8126751ced69a3d5f79c095a98298cd8a760ccec", size = 37461, upload-time = "2025-01-03T18:51:54.306Z" }, + { url = "https://files.pythonhosted.org/packages/8a/db/55a262f3606bebcae07cc14095338471ad7c0bbcaa37707e6f0ee49725b7/importlib_resources-7.1.0-py3-none-any.whl", hash = "sha256:1bd7b48b4088eddb2cd16382150bb515af0bd2c70128194392725f82ad2c96a1", size = 37232, upload-time = "2026-04-12T16:36:08.219Z" }, ] [[package]] name = "iniconfig" -version = "2.1.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/97/ebf4da567aa6827c909642694d71c9fcf53e5b504f2d96afea02718862f3/iniconfig-2.1.0.tar.gz", hash = "sha256:3abbd2e30b36733fee78f9c7f7308f2d0050e88f0087fd25c2645f63c773e1c7", size = 4793, upload-time = "2025-03-19T20:09:59.721Z" } +version = "2.3.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503, upload-time = "2025-10-18T21:55:43.219Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/2c/e1/e6716421ea10d38022b952c159d5161ca1193197fb744506875fbb87ea7b/iniconfig-2.1.0-py3-none-any.whl", hash = "sha256:9deba5723312380e77435581c6bf4935c94cbfab9b1ed33ef8d238ea168eb760", size = 6050, upload-time = "2025-03-19T20:10:01.071Z" }, + { url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484, upload-time = "2025-10-18T21:55:41.639Z" }, ] [[package]] name = "jax" -version = "0.4.34" -source = { registry = "https://pypi.org/simple" } +version = "0.4.35" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "jaxlib" }, { name = "ml-dtypes" }, @@ -390,15 +442,15 @@ dependencies = [ { name = "opt-einsum" }, { name = "scipy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/19/6a/cacfcdf77841a4562e555ef35e0dbc5f8ca79c9f1010aaa4cf3973e79c69/jax-0.4.34.tar.gz", hash = "sha256:44196854f40c5f9cea3142824b9f1051f85afc3fcf7593ec5479fc8db01c58db", size = 1848472, upload-time = "2024-10-04T14:37:12.698Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e3/34/21da583b9596e72bb8e95b6197dee0a44b96b9ea2c147fccabd43ca5515b/jax-0.4.35.tar.gz", hash = "sha256:c0c986993026b10bf6f607fecb7417377460254640766ce40f1fef3fd139c12e", size = 1861189, upload-time = "2024-10-22T20:56:36.379Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/06/f3/c499d358dd7f267a63d7d38ef54aadad82e28d2c28bafff15360c3091946/jax-0.4.34-py3-none-any.whl", hash = "sha256:b957ca1fc91f7343f91a186af9f19c7f342c946f95a8c11c7f1e5cdfe2e58d9e", size = 2144294, upload-time = "2024-10-04T14:37:10.265Z" }, + { url = "https://files.pythonhosted.org/packages/62/20/6c57c50c0ccc645fea1895950f1e5cd02f961ee44b3ffe83617fa46b0c1d/jax-0.4.35-py3-none-any.whl", hash = "sha256:fa99e909a31424abfec750019a6dd36f6acc18a6e7d40e2c0086b932cc351325", size = 2158621, upload-time = "2024-10-22T20:56:34.071Z" }, ] [[package]] name = "jaxlib" version = "0.4.34" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "ml-dtypes" }, { name = "numpy" }, @@ -425,7 +477,7 @@ wheels = [ [[package]] name = "jaxopt" version = "0.8.3" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "jax" }, { name = "jaxlib" }, @@ -440,7 +492,7 @@ wheels = [ [[package]] name = "jinja2" version = "3.1.6" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "markupsafe" }, ] @@ -451,133 +503,154 @@ wheels = [ [[package]] name = "kiwisolver" -version = "1.4.9" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5c/3c/85844f1b0feb11ee581ac23fe5fce65cd049a200c1446708cc1b7f922875/kiwisolver-1.4.9.tar.gz", hash = "sha256:c3b22c26c6fd6811b0ae8363b95ca8ce4ea3c202d3d0975b2914310ceb1bcc4d", size = 97564, upload-time = "2025-08-10T21:27:49.279Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6f/ab/c80b0d5a9d8a1a65f4f815f2afff9798b12c3b9f31f1d304dd233dd920e2/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:eb14a5da6dc7642b0f3a18f13654847cd8b7a2550e2645a5bda677862b03ba16", size = 124167, upload-time = "2025-08-10T21:25:53.403Z" }, - { url = "https://files.pythonhosted.org/packages/a0/c0/27fe1a68a39cf62472a300e2879ffc13c0538546c359b86f149cc19f6ac3/kiwisolver-1.4.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:39a219e1c81ae3b103643d2aedb90f1ef22650deb266ff12a19e7773f3e5f089", size = 66579, upload-time = "2025-08-10T21:25:54.79Z" }, - { url = "https://files.pythonhosted.org/packages/31/a2/a12a503ac1fd4943c50f9822678e8015a790a13b5490354c68afb8489814/kiwisolver-1.4.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2405a7d98604b87f3fc28b1716783534b1b4b8510d8142adca34ee0bc3c87543", size = 65309, upload-time = "2025-08-10T21:25:55.76Z" }, - { url = "https://files.pythonhosted.org/packages/66/e1/e533435c0be77c3f64040d68d7a657771194a63c279f55573188161e81ca/kiwisolver-1.4.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dc1ae486f9abcef254b5618dfb4113dd49f94c68e3e027d03cf0143f3f772b61", size = 1435596, upload-time = "2025-08-10T21:25:56.861Z" }, - { url = "https://files.pythonhosted.org/packages/67/1e/51b73c7347f9aabdc7215aa79e8b15299097dc2f8e67dee2b095faca9cb0/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8a1f570ce4d62d718dce3f179ee78dac3b545ac16c0c04bb363b7607a949c0d1", size = 1246548, upload-time = "2025-08-10T21:25:58.246Z" }, - { url = "https://files.pythonhosted.org/packages/21/aa/72a1c5d1e430294f2d32adb9542719cfb441b5da368d09d268c7757af46c/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:cb27e7b78d716c591e88e0a09a2139c6577865d7f2e152488c2cc6257f460872", size = 1263618, upload-time = "2025-08-10T21:25:59.857Z" }, - { url = "https://files.pythonhosted.org/packages/a3/af/db1509a9e79dbf4c260ce0cfa3903ea8945f6240e9e59d1e4deb731b1a40/kiwisolver-1.4.9-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:15163165efc2f627eb9687ea5f3a28137217d217ac4024893d753f46bce9de26", size = 1317437, upload-time = "2025-08-10T21:26:01.105Z" }, - { url = "https://files.pythonhosted.org/packages/e0/f2/3ea5ee5d52abacdd12013a94130436e19969fa183faa1e7c7fbc89e9a42f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:bdee92c56a71d2b24c33a7d4c2856bd6419d017e08caa7802d2963870e315028", size = 2195742, upload-time = "2025-08-10T21:26:02.675Z" }, - { url = "https://files.pythonhosted.org/packages/6f/9b/1efdd3013c2d9a2566aa6a337e9923a00590c516add9a1e89a768a3eb2fc/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:412f287c55a6f54b0650bd9b6dce5aceddb95864a1a90c87af16979d37c89771", size = 2290810, upload-time = "2025-08-10T21:26:04.009Z" }, - { url = "https://files.pythonhosted.org/packages/fb/e5/cfdc36109ae4e67361f9bc5b41323648cb24a01b9ade18784657e022e65f/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2c93f00dcba2eea70af2be5f11a830a742fe6b579a1d4e00f47760ef13be247a", size = 2461579, upload-time = "2025-08-10T21:26:05.317Z" }, - { url = "https://files.pythonhosted.org/packages/62/86/b589e5e86c7610842213994cdea5add00960076bef4ae290c5fa68589cac/kiwisolver-1.4.9-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f117e1a089d9411663a3207ba874f31be9ac8eaa5b533787024dc07aeb74f464", size = 2268071, upload-time = "2025-08-10T21:26:06.686Z" }, - { url = "https://files.pythonhosted.org/packages/3b/c6/f8df8509fd1eee6c622febe54384a96cfaf4d43bf2ccec7a0cc17e4715c9/kiwisolver-1.4.9-cp311-cp311-win_amd64.whl", hash = "sha256:be6a04e6c79819c9a8c2373317d19a96048e5a3f90bec587787e86a1153883c2", size = 73840, upload-time = "2025-08-10T21:26:07.94Z" }, - { url = "https://files.pythonhosted.org/packages/e2/2d/16e0581daafd147bc11ac53f032a2b45eabac897f42a338d0a13c1e5c436/kiwisolver-1.4.9-cp311-cp311-win_arm64.whl", hash = "sha256:0ae37737256ba2de764ddc12aed4956460277f00c4996d51a197e72f62f5eec7", size = 65159, upload-time = "2025-08-10T21:26:09.048Z" }, - { url = "https://files.pythonhosted.org/packages/86/c9/13573a747838aeb1c76e3267620daa054f4152444d1f3d1a2324b78255b5/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:ac5a486ac389dddcc5bef4f365b6ae3ffff2c433324fb38dd35e3fab7c957999", size = 123686, upload-time = "2025-08-10T21:26:10.034Z" }, - { url = "https://files.pythonhosted.org/packages/51/ea/2ecf727927f103ffd1739271ca19c424d0e65ea473fbaeea1c014aea93f6/kiwisolver-1.4.9-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f2ba92255faa7309d06fe44c3a4a97efe1c8d640c2a79a5ef728b685762a6fd2", size = 66460, upload-time = "2025-08-10T21:26:11.083Z" }, - { url = "https://files.pythonhosted.org/packages/5b/5a/51f5464373ce2aeb5194508298a508b6f21d3867f499556263c64c621914/kiwisolver-1.4.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4a2899935e724dd1074cb568ce7ac0dce28b2cd6ab539c8e001a8578eb106d14", size = 64952, upload-time = "2025-08-10T21:26:12.058Z" }, - { url = "https://files.pythonhosted.org/packages/70/90/6d240beb0f24b74371762873e9b7f499f1e02166a2d9c5801f4dbf8fa12e/kiwisolver-1.4.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f6008a4919fdbc0b0097089f67a1eb55d950ed7e90ce2cc3e640abadd2757a04", size = 1474756, upload-time = "2025-08-10T21:26:13.096Z" }, - { url = "https://files.pythonhosted.org/packages/12/42/f36816eaf465220f683fb711efdd1bbf7a7005a2473d0e4ed421389bd26c/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:67bb8b474b4181770f926f7b7d2f8c0248cbcb78b660fdd41a47054b28d2a752", size = 1276404, upload-time = "2025-08-10T21:26:14.457Z" }, - { url = "https://files.pythonhosted.org/packages/2e/64/bc2de94800adc830c476dce44e9b40fd0809cddeef1fde9fcf0f73da301f/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2327a4a30d3ee07d2fbe2e7933e8a37c591663b96ce42a00bc67461a87d7df77", size = 1294410, upload-time = "2025-08-10T21:26:15.73Z" }, - { url = "https://files.pythonhosted.org/packages/5f/42/2dc82330a70aa8e55b6d395b11018045e58d0bb00834502bf11509f79091/kiwisolver-1.4.9-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a08b491ec91b1d5053ac177afe5290adacf1f0f6307d771ccac5de30592d198", size = 1343631, upload-time = "2025-08-10T21:26:17.045Z" }, - { url = "https://files.pythonhosted.org/packages/22/fd/f4c67a6ed1aab149ec5a8a401c323cee7a1cbe364381bb6c9c0d564e0e20/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:d8fc5c867c22b828001b6a38d2eaeb88160bf5783c6cb4a5e440efc981ce286d", size = 2224963, upload-time = "2025-08-10T21:26:18.737Z" }, - { url = "https://files.pythonhosted.org/packages/45/aa/76720bd4cb3713314677d9ec94dcc21ced3f1baf4830adde5bb9b2430a5f/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:3b3115b2581ea35bb6d1f24a4c90af37e5d9b49dcff267eeed14c3893c5b86ab", size = 2321295, upload-time = "2025-08-10T21:26:20.11Z" }, - { url = "https://files.pythonhosted.org/packages/80/19/d3ec0d9ab711242f56ae0dc2fc5d70e298bb4a1f9dfab44c027668c673a1/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:858e4c22fb075920b96a291928cb7dea5644e94c0ee4fcd5af7e865655e4ccf2", size = 2487987, upload-time = "2025-08-10T21:26:21.49Z" }, - { url = "https://files.pythonhosted.org/packages/39/e9/61e4813b2c97e86b6fdbd4dd824bf72d28bcd8d4849b8084a357bc0dd64d/kiwisolver-1.4.9-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:ed0fecd28cc62c54b262e3736f8bb2512d8dcfdc2bcf08be5f47f96bf405b145", size = 2291817, upload-time = "2025-08-10T21:26:22.812Z" }, - { url = "https://files.pythonhosted.org/packages/a0/41/85d82b0291db7504da3c2defe35c9a8a5c9803a730f297bd823d11d5fb77/kiwisolver-1.4.9-cp312-cp312-win_amd64.whl", hash = "sha256:f68208a520c3d86ea51acf688a3e3002615a7f0238002cccc17affecc86a8a54", size = 73895, upload-time = "2025-08-10T21:26:24.37Z" }, - { url = "https://files.pythonhosted.org/packages/e2/92/5f3068cf15ee5cb624a0c7596e67e2a0bb2adee33f71c379054a491d07da/kiwisolver-1.4.9-cp312-cp312-win_arm64.whl", hash = "sha256:2c1a4f57df73965f3f14df20b80ee29e6a7930a57d2d9e8491a25f676e197c60", size = 64992, upload-time = "2025-08-10T21:26:25.732Z" }, - { url = "https://files.pythonhosted.org/packages/31/c1/c2686cda909742ab66c7388e9a1a8521a59eb89f8bcfbee28fc980d07e24/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:a5d0432ccf1c7ab14f9949eec60c5d1f924f17c037e9f8b33352fa05799359b8", size = 123681, upload-time = "2025-08-10T21:26:26.725Z" }, - { url = "https://files.pythonhosted.org/packages/ca/f0/f44f50c9f5b1a1860261092e3bc91ecdc9acda848a8b8c6abfda4a24dd5c/kiwisolver-1.4.9-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efb3a45b35622bb6c16dbfab491a8f5a391fe0e9d45ef32f4df85658232ca0e2", size = 66464, upload-time = "2025-08-10T21:26:27.733Z" }, - { url = "https://files.pythonhosted.org/packages/2d/7a/9d90a151f558e29c3936b8a47ac770235f436f2120aca41a6d5f3d62ae8d/kiwisolver-1.4.9-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1a12cf6398e8a0a001a059747a1cbf24705e18fe413bc22de7b3d15c67cffe3f", size = 64961, upload-time = "2025-08-10T21:26:28.729Z" }, - { url = "https://files.pythonhosted.org/packages/e9/e9/f218a2cb3a9ffbe324ca29a9e399fa2d2866d7f348ec3a88df87fc248fc5/kiwisolver-1.4.9-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b67e6efbf68e077dd71d1a6b37e43e1a99d0bff1a3d51867d45ee8908b931098", size = 1474607, upload-time = "2025-08-10T21:26:29.798Z" }, - { url = "https://files.pythonhosted.org/packages/d9/28/aac26d4c882f14de59041636292bc838db8961373825df23b8eeb807e198/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5656aa670507437af0207645273ccdfee4f14bacd7f7c67a4306d0dcaeaf6eed", size = 1276546, upload-time = "2025-08-10T21:26:31.401Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ad/8bfc1c93d4cc565e5069162f610ba2f48ff39b7de4b5b8d93f69f30c4bed/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bfc08add558155345129c7803b3671cf195e6a56e7a12f3dde7c57d9b417f525", size = 1294482, upload-time = "2025-08-10T21:26:32.721Z" }, - { url = "https://files.pythonhosted.org/packages/da/f1/6aca55ff798901d8ce403206d00e033191f63d82dd708a186e0ed2067e9c/kiwisolver-1.4.9-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:40092754720b174e6ccf9e845d0d8c7d8e12c3d71e7fc35f55f3813e96376f78", size = 1343720, upload-time = "2025-08-10T21:26:34.032Z" }, - { url = "https://files.pythonhosted.org/packages/d1/91/eed031876c595c81d90d0f6fc681ece250e14bf6998c3d7c419466b523b7/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:497d05f29a1300d14e02e6441cf0f5ee81c1ff5a304b0d9fb77423974684e08b", size = 2224907, upload-time = "2025-08-10T21:26:35.824Z" }, - { url = "https://files.pythonhosted.org/packages/e9/ec/4d1925f2e49617b9cca9c34bfa11adefad49d00db038e692a559454dfb2e/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdd1a81a1860476eb41ac4bc1e07b3f07259e6d55bbf739b79c8aaedcf512799", size = 2321334, upload-time = "2025-08-10T21:26:37.534Z" }, - { url = "https://files.pythonhosted.org/packages/43/cb/450cd4499356f68802750c6ddc18647b8ea01ffa28f50d20598e0befe6e9/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:e6b93f13371d341afee3be9f7c5964e3fe61d5fa30f6a30eb49856935dfe4fc3", size = 2488313, upload-time = "2025-08-10T21:26:39.191Z" }, - { url = "https://files.pythonhosted.org/packages/71/67/fc76242bd99f885651128a5d4fa6083e5524694b7c88b489b1b55fdc491d/kiwisolver-1.4.9-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d75aa530ccfaa593da12834b86a0724f58bff12706659baa9227c2ccaa06264c", size = 2291970, upload-time = "2025-08-10T21:26:40.828Z" }, - { url = "https://files.pythonhosted.org/packages/75/bd/f1a5d894000941739f2ae1b65a32892349423ad49c2e6d0771d0bad3fae4/kiwisolver-1.4.9-cp313-cp313-win_amd64.whl", hash = "sha256:dd0a578400839256df88c16abddf9ba14813ec5f21362e1fe65022e00c883d4d", size = 73894, upload-time = "2025-08-10T21:26:42.33Z" }, - { url = "https://files.pythonhosted.org/packages/95/38/dce480814d25b99a391abbddadc78f7c117c6da34be68ca8b02d5848b424/kiwisolver-1.4.9-cp313-cp313-win_arm64.whl", hash = "sha256:d4188e73af84ca82468f09cadc5ac4db578109e52acb4518d8154698d3a87ca2", size = 64995, upload-time = "2025-08-10T21:26:43.889Z" }, - { url = "https://files.pythonhosted.org/packages/e2/37/7d218ce5d92dadc5ebdd9070d903e0c7cf7edfe03f179433ac4d13ce659c/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:5a0f2724dfd4e3b3ac5a82436a8e6fd16baa7d507117e4279b660fe8ca38a3a1", size = 126510, upload-time = "2025-08-10T21:26:44.915Z" }, - { url = "https://files.pythonhosted.org/packages/23/b0/e85a2b48233daef4b648fb657ebbb6f8367696a2d9548a00b4ee0eb67803/kiwisolver-1.4.9-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:1b11d6a633e4ed84fc0ddafd4ebfd8ea49b3f25082c04ad12b8315c11d504dc1", size = 67903, upload-time = "2025-08-10T21:26:45.934Z" }, - { url = "https://files.pythonhosted.org/packages/44/98/f2425bc0113ad7de24da6bb4dae1343476e95e1d738be7c04d31a5d037fd/kiwisolver-1.4.9-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:61874cdb0a36016354853593cffc38e56fc9ca5aa97d2c05d3dcf6922cd55a11", size = 66402, upload-time = "2025-08-10T21:26:47.101Z" }, - { url = "https://files.pythonhosted.org/packages/98/d8/594657886df9f34c4177cc353cc28ca7e6e5eb562d37ccc233bff43bbe2a/kiwisolver-1.4.9-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:60c439763a969a6af93b4881db0eed8fadf93ee98e18cbc35bc8da868d0c4f0c", size = 1582135, upload-time = "2025-08-10T21:26:48.665Z" }, - { url = "https://files.pythonhosted.org/packages/5c/c6/38a115b7170f8b306fc929e166340c24958347308ea3012c2b44e7e295db/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:92a2f997387a1b79a75e7803aa7ded2cfbe2823852ccf1ba3bcf613b62ae3197", size = 1389409, upload-time = "2025-08-10T21:26:50.335Z" }, - { url = "https://files.pythonhosted.org/packages/bf/3b/e04883dace81f24a568bcee6eb3001da4ba05114afa622ec9b6fafdc1f5e/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:a31d512c812daea6d8b3be3b2bfcbeb091dbb09177706569bcfc6240dcf8b41c", size = 1401763, upload-time = "2025-08-10T21:26:51.867Z" }, - { url = "https://files.pythonhosted.org/packages/9f/80/20ace48e33408947af49d7d15c341eaee69e4e0304aab4b7660e234d6288/kiwisolver-1.4.9-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:52a15b0f35dad39862d376df10c5230155243a2c1a436e39eb55623ccbd68185", size = 1453643, upload-time = "2025-08-10T21:26:53.592Z" }, - { url = "https://files.pythonhosted.org/packages/64/31/6ce4380a4cd1f515bdda976a1e90e547ccd47b67a1546d63884463c92ca9/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a30fd6fdef1430fd9e1ba7b3398b5ee4e2887783917a687d86ba69985fb08748", size = 2330818, upload-time = "2025-08-10T21:26:55.051Z" }, - { url = "https://files.pythonhosted.org/packages/fa/e9/3f3fcba3bcc7432c795b82646306e822f3fd74df0ee81f0fa067a1f95668/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:cc9617b46837c6468197b5945e196ee9ca43057bb7d9d1ae688101e4e1dddf64", size = 2419963, upload-time = "2025-08-10T21:26:56.421Z" }, - { url = "https://files.pythonhosted.org/packages/99/43/7320c50e4133575c66e9f7dadead35ab22d7c012a3b09bb35647792b2a6d/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:0ab74e19f6a2b027ea4f845a78827969af45ce790e6cb3e1ebab71bdf9f215ff", size = 2594639, upload-time = "2025-08-10T21:26:57.882Z" }, - { url = "https://files.pythonhosted.org/packages/65/d6/17ae4a270d4a987ef8a385b906d2bdfc9fce502d6dc0d3aea865b47f548c/kiwisolver-1.4.9-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dba5ee5d3981160c28d5490f0d1b7ed730c22470ff7f6cc26cfcfaacb9896a07", size = 2391741, upload-time = "2025-08-10T21:26:59.237Z" }, - { url = "https://files.pythonhosted.org/packages/2a/8f/8f6f491d595a9e5912971f3f863d81baddccc8a4d0c3749d6a0dd9ffc9df/kiwisolver-1.4.9-cp313-cp313t-win_arm64.whl", hash = "sha256:0749fd8f4218ad2e851e11cc4dc05c7cbc0cbc4267bdfdb31782e65aace4ee9c", size = 68646, upload-time = "2025-08-10T21:27:00.52Z" }, - { url = "https://files.pythonhosted.org/packages/6b/32/6cc0fbc9c54d06c2969faa9c1d29f5751a2e51809dd55c69055e62d9b426/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:9928fe1eb816d11ae170885a74d074f57af3a0d65777ca47e9aeb854a1fba386", size = 123806, upload-time = "2025-08-10T21:27:01.537Z" }, - { url = "https://files.pythonhosted.org/packages/b2/dd/2bfb1d4a4823d92e8cbb420fe024b8d2167f72079b3bb941207c42570bdf/kiwisolver-1.4.9-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:d0005b053977e7b43388ddec89fa567f43d4f6d5c2c0affe57de5ebf290dc552", size = 66605, upload-time = "2025-08-10T21:27:03.335Z" }, - { url = "https://files.pythonhosted.org/packages/f7/69/00aafdb4e4509c2ca6064646cba9cd4b37933898f426756adb2cb92ebbed/kiwisolver-1.4.9-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2635d352d67458b66fd0667c14cb1d4145e9560d503219034a18a87e971ce4f3", size = 64925, upload-time = "2025-08-10T21:27:04.339Z" }, - { url = "https://files.pythonhosted.org/packages/43/dc/51acc6791aa14e5cb6d8a2e28cefb0dc2886d8862795449d021334c0df20/kiwisolver-1.4.9-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:767c23ad1c58c9e827b649a9ab7809fd5fd9db266a9cf02b0e926ddc2c680d58", size = 1472414, upload-time = "2025-08-10T21:27:05.437Z" }, - { url = "https://files.pythonhosted.org/packages/3d/bb/93fa64a81db304ac8a246f834d5094fae4b13baf53c839d6bb6e81177129/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:72d0eb9fba308b8311685c2268cf7d0a0639a6cd027d8128659f72bdd8a024b4", size = 1281272, upload-time = "2025-08-10T21:27:07.063Z" }, - { url = "https://files.pythonhosted.org/packages/70/e6/6df102916960fb8d05069d4bd92d6d9a8202d5a3e2444494e7cd50f65b7a/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:f68e4f3eeca8fb22cc3d731f9715a13b652795ef657a13df1ad0c7dc0e9731df", size = 1298578, upload-time = "2025-08-10T21:27:08.452Z" }, - { url = "https://files.pythonhosted.org/packages/7c/47/e142aaa612f5343736b087864dbaebc53ea8831453fb47e7521fa8658f30/kiwisolver-1.4.9-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:d84cd4061ae292d8ac367b2c3fa3aad11cb8625a95d135fe93f286f914f3f5a6", size = 1345607, upload-time = "2025-08-10T21:27:10.125Z" }, - { url = "https://files.pythonhosted.org/packages/54/89/d641a746194a0f4d1a3670fb900d0dbaa786fb98341056814bc3f058fa52/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:a60ea74330b91bd22a29638940d115df9dc00af5035a9a2a6ad9399ffb4ceca5", size = 2230150, upload-time = "2025-08-10T21:27:11.484Z" }, - { url = "https://files.pythonhosted.org/packages/aa/6b/5ee1207198febdf16ac11f78c5ae40861b809cbe0e6d2a8d5b0b3044b199/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:ce6a3a4e106cf35c2d9c4fa17c05ce0b180db622736845d4315519397a77beaf", size = 2325979, upload-time = "2025-08-10T21:27:12.917Z" }, - { url = "https://files.pythonhosted.org/packages/fc/ff/b269eefd90f4ae14dcc74973d5a0f6d28d3b9bb1afd8c0340513afe6b39a/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:77937e5e2a38a7b48eef0585114fe7930346993a88060d0bf886086d2aa49ef5", size = 2491456, upload-time = "2025-08-10T21:27:14.353Z" }, - { url = "https://files.pythonhosted.org/packages/fc/d4/10303190bd4d30de547534601e259a4fbf014eed94aae3e5521129215086/kiwisolver-1.4.9-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:24c175051354f4a28c5d6a31c93906dc653e2bf234e8a4bbfb964892078898ce", size = 2294621, upload-time = "2025-08-10T21:27:15.808Z" }, - { url = "https://files.pythonhosted.org/packages/28/e0/a9a90416fce5c0be25742729c2ea52105d62eda6c4be4d803c2a7be1fa50/kiwisolver-1.4.9-cp314-cp314-win_amd64.whl", hash = "sha256:0763515d4df10edf6d06a3c19734e2566368980d21ebec439f33f9eb936c07b7", size = 75417, upload-time = "2025-08-10T21:27:17.436Z" }, - { url = "https://files.pythonhosted.org/packages/1f/10/6949958215b7a9a264299a7db195564e87900f709db9245e4ebdd3c70779/kiwisolver-1.4.9-cp314-cp314-win_arm64.whl", hash = "sha256:0e4e2bf29574a6a7b7f6cb5fa69293b9f96c928949ac4a53ba3f525dffb87f9c", size = 66582, upload-time = "2025-08-10T21:27:18.436Z" }, - { url = "https://files.pythonhosted.org/packages/ec/79/60e53067903d3bc5469b369fe0dfc6b3482e2133e85dae9daa9527535991/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_universal2.whl", hash = "sha256:d976bbb382b202f71c67f77b0ac11244021cfa3f7dfd9e562eefcea2df711548", size = 126514, upload-time = "2025-08-10T21:27:19.465Z" }, - { url = "https://files.pythonhosted.org/packages/25/d1/4843d3e8d46b072c12a38c97c57fab4608d36e13fe47d47ee96b4d61ba6f/kiwisolver-1.4.9-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:2489e4e5d7ef9a1c300a5e0196e43d9c739f066ef23270607d45aba368b91f2d", size = 67905, upload-time = "2025-08-10T21:27:20.51Z" }, - { url = "https://files.pythonhosted.org/packages/8c/ae/29ffcbd239aea8b93108de1278271ae764dfc0d803a5693914975f200596/kiwisolver-1.4.9-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e2ea9f7ab7fbf18fffb1b5434ce7c69a07582f7acc7717720f1d69f3e806f90c", size = 66399, upload-time = "2025-08-10T21:27:21.496Z" }, - { url = "https://files.pythonhosted.org/packages/a1/ae/d7ba902aa604152c2ceba5d352d7b62106bedbccc8e95c3934d94472bfa3/kiwisolver-1.4.9-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b34e51affded8faee0dfdb705416153819d8ea9250bbbf7ea1b249bdeb5f1122", size = 1582197, upload-time = "2025-08-10T21:27:22.604Z" }, - { url = "https://files.pythonhosted.org/packages/f2/41/27c70d427eddb8bc7e4f16420a20fefc6f480312122a59a959fdfe0445ad/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d8aacd3d4b33b772542b2e01beb50187536967b514b00003bdda7589722d2a64", size = 1390125, upload-time = "2025-08-10T21:27:24.036Z" }, - { url = "https://files.pythonhosted.org/packages/41/42/b3799a12bafc76d962ad69083f8b43b12bf4fe78b097b12e105d75c9b8f1/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:7cf974dd4e35fa315563ac99d6287a1024e4dc2077b8a7d7cd3d2fb65d283134", size = 1402612, upload-time = "2025-08-10T21:27:25.773Z" }, - { url = "https://files.pythonhosted.org/packages/d2/b5/a210ea073ea1cfaca1bb5c55a62307d8252f531beb364e18aa1e0888b5a0/kiwisolver-1.4.9-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:85bd218b5ecfbee8c8a82e121802dcb519a86044c9c3b2e4aef02fa05c6da370", size = 1453990, upload-time = "2025-08-10T21:27:27.089Z" }, - { url = "https://files.pythonhosted.org/packages/5f/ce/a829eb8c033e977d7ea03ed32fb3c1781b4fa0433fbadfff29e39c676f32/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:0856e241c2d3df4efef7c04a1e46b1936b6120c9bcf36dd216e3acd84bc4fb21", size = 2331601, upload-time = "2025-08-10T21:27:29.343Z" }, - { url = "https://files.pythonhosted.org/packages/e0/4b/b5e97eb142eb9cd0072dacfcdcd31b1c66dc7352b0f7c7255d339c0edf00/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:9af39d6551f97d31a4deebeac6f45b156f9755ddc59c07b402c148f5dbb6482a", size = 2422041, upload-time = "2025-08-10T21:27:30.754Z" }, - { url = "https://files.pythonhosted.org/packages/40/be/8eb4cd53e1b85ba4edc3a9321666f12b83113a178845593307a3e7891f44/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:bb4ae2b57fc1d8cbd1cf7b1d9913803681ffa903e7488012be5b76dedf49297f", size = 2594897, upload-time = "2025-08-10T21:27:32.803Z" }, - { url = "https://files.pythonhosted.org/packages/99/dd/841e9a66c4715477ea0abc78da039832fbb09dac5c35c58dc4c41a407b8a/kiwisolver-1.4.9-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:aedff62918805fb62d43a4aa2ecd4482c380dc76cd31bd7c8878588a61bd0369", size = 2391835, upload-time = "2025-08-10T21:27:34.23Z" }, - { url = "https://files.pythonhosted.org/packages/0c/28/4b2e5c47a0da96896fdfdb006340ade064afa1e63675d01ea5ac222b6d52/kiwisolver-1.4.9-cp314-cp314t-win_amd64.whl", hash = "sha256:1fa333e8b2ce4d9660f2cda9c0e1b6bafcfb2457a9d259faa82289e73ec24891", size = 79988, upload-time = "2025-08-10T21:27:35.587Z" }, - { url = "https://files.pythonhosted.org/packages/80/be/3578e8afd18c88cdf9cb4cffde75a96d2be38c5a903f1ed0ceec061bd09e/kiwisolver-1.4.9-cp314-cp314t-win_arm64.whl", hash = "sha256:4a48a2ce79d65d363597ef7b567ce3d14d68783d2b2263d98db3d9477805ba32", size = 70260, upload-time = "2025-08-10T21:27:36.606Z" }, - { url = "https://files.pythonhosted.org/packages/a3/0f/36d89194b5a32c054ce93e586d4049b6c2c22887b0eb229c61c68afd3078/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:720e05574713db64c356e86732c0f3c5252818d05f9df320f0ad8380641acea5", size = 60104, upload-time = "2025-08-10T21:27:43.287Z" }, - { url = "https://files.pythonhosted.org/packages/52/ba/4ed75f59e4658fd21fe7dde1fee0ac397c678ec3befba3fe6482d987af87/kiwisolver-1.4.9-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:17680d737d5335b552994a2008fab4c851bcd7de33094a82067ef3a576ff02fa", size = 58592, upload-time = "2025-08-10T21:27:44.314Z" }, - { url = "https://files.pythonhosted.org/packages/33/01/a8ea7c5ea32a9b45ceeaee051a04c8ed4320f5add3c51bfa20879b765b70/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:85b5352f94e490c028926ea567fc569c52ec79ce131dadb968d3853e809518c2", size = 80281, upload-time = "2025-08-10T21:27:45.369Z" }, - { url = "https://files.pythonhosted.org/packages/da/e3/dbd2ecdce306f1d07a1aaf324817ee993aab7aee9db47ceac757deabafbe/kiwisolver-1.4.9-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:464415881e4801295659462c49461a24fb107c140de781d55518c4b80cb6790f", size = 78009, upload-time = "2025-08-10T21:27:46.376Z" }, - { url = "https://files.pythonhosted.org/packages/da/e9/0d4add7873a73e462aeb45c036a2dead2562b825aa46ba326727b3f31016/kiwisolver-1.4.9-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:fb940820c63a9590d31d88b815e7a3aa5915cad3ce735ab45f0c730b39547de1", size = 73929, upload-time = "2025-08-10T21:27:48.236Z" }, +version = "1.5.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/67/9c61eccb13f0bdca9307614e782fec49ffdde0f7a2314935d489fa93cd9c/kiwisolver-1.5.0.tar.gz", hash = "sha256:d4193f3d9dc3f6f79aaed0e5637f45d98850ebf01f7ca20e69457f3e8946b66a", size = 103482, upload-time = "2026-03-09T13:15:53.382Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/12/dd/a495a9c104be1c476f0386e714252caf2b7eca883915422a64c50b88c6f5/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9eed0f7edbb274413b6ee781cca50541c8c0facd3d6fd289779e494340a2b85c", size = 122798, upload-time = "2026-03-09T13:12:58.963Z" }, + { url = "https://files.pythonhosted.org/packages/11/60/37b4047a2af0cf5ef6d8b4b26e91829ae6fc6a2d1f74524bcb0e7cd28a32/kiwisolver-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c4923e404d6bcd91b6779c009542e5647fef32e4a5d75e115e3bbac6f2335eb", size = 66216, upload-time = "2026-03-09T13:13:00.155Z" }, + { url = "https://files.pythonhosted.org/packages/0a/aa/510dc933d87767584abfe03efa445889996c70c2990f6f87c3ebaa0a18c5/kiwisolver-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0df54df7e686afa55e6f21fb86195224a6d9beb71d637e8d7920c95cf0f89aac", size = 63911, upload-time = "2026-03-09T13:13:01.671Z" }, + { url = "https://files.pythonhosted.org/packages/80/46/bddc13df6c2a40741e0cc7865bb1c9ed4796b6760bd04ce5fae3928ef917/kiwisolver-1.5.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2517e24d7315eb51c10664cdb865195df38ab74456c677df67bb47f12d088a27", size = 1438209, upload-time = "2026-03-09T13:13:03.385Z" }, + { url = "https://files.pythonhosted.org/packages/fd/d6/76621246f5165e5372f02f5e6f3f48ea336a8f9e96e43997d45b240ed8cd/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ff710414307fefa903e0d9bdf300972f892c23477829f49504e59834f4195398", size = 1248888, upload-time = "2026-03-09T13:13:05.231Z" }, + { url = "https://files.pythonhosted.org/packages/b2/c1/31559ec6fb39a5b48035ce29bb63ade628f321785f38c384dee3e2c08bc1/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:6176c1811d9d5a04fa391c490cc44f451e240697a16977f11c6f722efb9041db", size = 1266304, upload-time = "2026-03-09T13:13:06.743Z" }, + { url = "https://files.pythonhosted.org/packages/5e/ef/1cb8276f2d29cc6a41e0a042f27946ca347d3a4a75acf85d0a16aa6dcc82/kiwisolver-1.5.0-cp311-cp311-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:50847dca5d197fcbd389c805aa1a1cf32f25d2e7273dc47ab181a517666b68cc", size = 1319650, upload-time = "2026-03-09T13:13:08.607Z" }, + { url = "https://files.pythonhosted.org/packages/4c/e4/5ba3cecd7ce6236ae4a80f67e5d5531287337d0e1f076ca87a5abe4cd5d0/kiwisolver-1.5.0-cp311-cp311-manylinux_2_39_riscv64.whl", hash = "sha256:01808c6d15f4c3e8559595d6d1fe6411c68e4a3822b4b9972b44473b24f4e679", size = 970949, upload-time = "2026-03-09T13:13:10.299Z" }, + { url = "https://files.pythonhosted.org/packages/5a/69/dc61f7ae9a2f071f26004ced87f078235b5507ab6e5acd78f40365655034/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1f9f4121ec58628c96baa3de1a55a4e3a333c5102c8e94b64e23bf7b2083309", size = 2199125, upload-time = "2026-03-09T13:13:11.841Z" }, + { url = "https://files.pythonhosted.org/packages/e5/7b/abbe0f1b5afa85f8d084b73e90e5f801c0939eba16ac2e49af7c61a6c28d/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:b7d335370ae48a780c6e6a6bbfa97342f563744c39c35562f3f367665f5c1de2", size = 2293783, upload-time = "2026-03-09T13:13:14.399Z" }, + { url = "https://files.pythonhosted.org/packages/8a/80/5908ae149d96d81580d604c7f8aefd0e98f4fd728cf172f477e9f2a81744/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:800ee55980c18545af444d93fdd60c56b580db5cc54867d8cbf8a1dc0829938c", size = 1960726, upload-time = "2026-03-09T13:13:16.047Z" }, + { url = "https://files.pythonhosted.org/packages/84/08/a78cb776f8c085b7143142ce479859cfec086bd09ee638a317040b6ef420/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c438f6ca858697c9ab67eb28246c92508af972e114cac34e57a6d4ba17a3ac08", size = 2464738, upload-time = "2026-03-09T13:13:17.897Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e1/65584da5356ed6cb12c63791a10b208860ac40a83de165cb6a6751a686e3/kiwisolver-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:8c63c91f95173f9c2a67c7c526b2cea976828a0e7fced9cdcead2802dc10f8a4", size = 2270718, upload-time = "2026-03-09T13:13:19.421Z" }, + { url = "https://files.pythonhosted.org/packages/be/6c/28f17390b62b8f2f520e2915095b3c94d88681ecf0041e75389d9667f202/kiwisolver-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:beb7f344487cdcb9e1efe4b7a29681b74d34c08f0043a327a74da852a6749e7b", size = 73480, upload-time = "2026-03-09T13:13:20.818Z" }, + { url = "https://files.pythonhosted.org/packages/d8/0e/2ee5debc4f77a625778fec5501ff3e8036fe361b7ee28ae402a485bb9694/kiwisolver-1.5.0-cp311-cp311-win_arm64.whl", hash = "sha256:ad4ae4ffd1ee9cd11357b4c66b612da9888f4f4daf2f36995eda64bd45370cac", size = 64930, upload-time = "2026-03-09T13:13:21.997Z" }, + { url = "https://files.pythonhosted.org/packages/4d/b2/818b74ebea34dabe6d0c51cb1c572e046730e64844da6ed646d5298c40ce/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:4e9750bc21b886308024f8a54ccb9a2cc38ac9fa813bf4348434e3d54f337ff9", size = 123158, upload-time = "2026-03-09T13:13:23.127Z" }, + { url = "https://files.pythonhosted.org/packages/bf/d9/405320f8077e8e1c5c4bd6adc45e1e6edf6d727b6da7f2e2533cf58bff71/kiwisolver-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:72ec46b7eba5b395e0a7b63025490d3214c11013f4aacb4f5e8d6c3041829588", size = 66388, upload-time = "2026-03-09T13:13:24.765Z" }, + { url = "https://files.pythonhosted.org/packages/99/9f/795fedf35634f746151ca8839d05681ceb6287fbed6cc1c9bf235f7887c2/kiwisolver-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ed3a984b31da7481b103f68776f7128a89ef26ed40f4dc41a2223cda7fb24819", size = 64068, upload-time = "2026-03-09T13:13:25.878Z" }, + { url = "https://files.pythonhosted.org/packages/c4/13/680c54afe3e65767bed7ec1a15571e1a2f1257128733851ade24abcefbcc/kiwisolver-1.5.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:bb5136fb5352d3f422df33f0c879a1b0c204004324150cc3b5e3c4f310c9049f", size = 1477934, upload-time = "2026-03-09T13:13:27.166Z" }, + { url = "https://files.pythonhosted.org/packages/c8/2f/cebfcdb60fd6a9b0f6b47a9337198bcbad6fbe15e68189b7011fd914911f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b2af221f268f5af85e776a73d62b0845fc8baf8ef0abfae79d29c77d0e776aaf", size = 1278537, upload-time = "2026-03-09T13:13:28.707Z" }, + { url = "https://files.pythonhosted.org/packages/f2/0d/9b782923aada3fafb1d6b84e13121954515c669b18af0c26e7d21f579855/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:b0f172dc8ffaccb8522d7c5d899de00133f2f1ca7b0a49b7da98e901de87bf2d", size = 1296685, upload-time = "2026-03-09T13:13:30.528Z" }, + { url = "https://files.pythonhosted.org/packages/27/70/83241b6634b04fe44e892688d5208332bde130f38e610c0418f9ede47ded/kiwisolver-1.5.0-cp312-cp312-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:6ab8ba9152203feec73758dad83af9a0bbe05001eb4639e547207c40cfb52083", size = 1346024, upload-time = "2026-03-09T13:13:32.818Z" }, + { url = "https://files.pythonhosted.org/packages/e4/db/30ed226fb271ae1a6431fc0fe0edffb2efe23cadb01e798caeb9f2ceae8f/kiwisolver-1.5.0-cp312-cp312-manylinux_2_39_riscv64.whl", hash = "sha256:cdee07c4d7f6d72008d3f73b9bf027f4e11550224c7c50d8df1ae4a37c1402a6", size = 987241, upload-time = "2026-03-09T13:13:34.435Z" }, + { url = "https://files.pythonhosted.org/packages/ec/bd/c314595208e4c9587652d50959ead9e461995389664e490f4dce7ff0f782/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7c60d3c9b06fb23bd9c6139281ccbdc384297579ae037f08ae90c69f6845c0b1", size = 2227742, upload-time = "2026-03-09T13:13:36.4Z" }, + { url = "https://files.pythonhosted.org/packages/c1/43/0499cec932d935229b5543d073c2b87c9c22846aab48881e9d8d6e742a2d/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:e315e5ec90d88e140f57696ff85b484ff68bb311e36f2c414aa4286293e6dee0", size = 2323966, upload-time = "2026-03-09T13:13:38.204Z" }, + { url = "https://files.pythonhosted.org/packages/3d/6f/79b0d760907965acfd9d61826a3d41f8f093c538f55cd2633d3f0db269f6/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:1465387ac63576c3e125e5337a6892b9e99e0627d52317f3ca79e6930d889d15", size = 1977417, upload-time = "2026-03-09T13:13:39.966Z" }, + { url = "https://files.pythonhosted.org/packages/ab/31/01d0537c41cb75a551a438c3c7a80d0c60d60b81f694dac83dd436aec0d0/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:530a3fd64c87cffa844d4b6b9768774763d9caa299e9b75d8eca6a4423b31314", size = 2491238, upload-time = "2026-03-09T13:13:41.698Z" }, + { url = "https://files.pythonhosted.org/packages/e4/34/8aefdd0be9cfd00a44509251ba864f5caf2991e36772e61c408007e7f417/kiwisolver-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:1d9daea4ea6b9be74fe2f01f7fbade8d6ffab263e781274cffca0dba9be9eec9", size = 2294947, upload-time = "2026-03-09T13:13:43.343Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/0348374369ca588f8fe9c338fae49fa4e16eeb10ffb3d012f23a54578a9e/kiwisolver-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:f18c2d9782259a6dc132fdc7a63c168cbc74b35284b6d75c673958982a378384", size = 73569, upload-time = "2026-03-09T13:13:45.792Z" }, + { url = "https://files.pythonhosted.org/packages/28/26/192b26196e2316e2bd29deef67e37cdf9870d9af8e085e521afff0fed526/kiwisolver-1.5.0-cp312-cp312-win_arm64.whl", hash = "sha256:f7c7553b13f69c1b29a5bde08ddc6d9d0c8bfb84f9ed01c30db25944aeb852a7", size = 64997, upload-time = "2026-03-09T13:13:46.878Z" }, + { url = "https://files.pythonhosted.org/packages/9d/69/024d6711d5ba575aa65d5538042e99964104e97fa153a9f10bc369182bc2/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:fd40bb9cd0891c4c3cb1ddf83f8bbfa15731a248fdc8162669405451e2724b09", size = 123166, upload-time = "2026-03-09T13:13:48.032Z" }, + { url = "https://files.pythonhosted.org/packages/ce/48/adbb40df306f587054a348831220812b9b1d787aff714cfbc8556e38fccd/kiwisolver-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0e1403fd7c26d77c1f03e096dc58a5c726503fa0db0456678b8668f76f521e3", size = 66395, upload-time = "2026-03-09T13:13:49.365Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3a/d0a972b34e1c63e2409413104216cd1caa02c5a37cb668d1687d466c1c45/kiwisolver-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dda366d548e89a90d88a86c692377d18d8bd64b39c1fb2b92cb31370e2896bbd", size = 64065, upload-time = "2026-03-09T13:13:50.562Z" }, + { url = "https://files.pythonhosted.org/packages/2b/0a/7b98e1e119878a27ba8618ca1e18b14f992ff1eda40f47bccccf4de44121/kiwisolver-1.5.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:332b4f0145c30b5f5ad9374881133e5aa64320428a57c2c2b61e9d891a51c2f3", size = 1477903, upload-time = "2026-03-09T13:13:52.084Z" }, + { url = "https://files.pythonhosted.org/packages/18/d8/55638d89ffd27799d5cc3d8aa28e12f4ce7a64d67b285114dbedc8ea4136/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0c50b89ffd3e1a911c69a1dd3de7173c0cd10b130f56222e57898683841e4f96", size = 1278751, upload-time = "2026-03-09T13:13:54.673Z" }, + { url = "https://files.pythonhosted.org/packages/b8/97/b4c8d0d18421ecceba20ad8701358453b88e32414e6f6950b5a4bad54e65/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:4db576bb8c3ef9365f8b40fe0f671644de6736ae2c27a2c62d7d8a1b4329f099", size = 1296793, upload-time = "2026-03-09T13:13:56.287Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/f862f94b6389d8957448ec9df59450b81bec4abb318805375c401a1e6892/kiwisolver-1.5.0-cp313-cp313-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:0b85aad90cea8ac6797a53b5d5f2e967334fa4d1149f031c4537569972596cb8", size = 1346041, upload-time = "2026-03-09T13:13:58.269Z" }, + { url = "https://files.pythonhosted.org/packages/a3/6a/f1650af35821eaf09de398ec0bc2aefc8f211f0cda50204c9f1673741ba9/kiwisolver-1.5.0-cp313-cp313-manylinux_2_39_riscv64.whl", hash = "sha256:d36ca54cb4c6c4686f7cbb7b817f66f5911c12ddb519450bbe86707155028f87", size = 987292, upload-time = "2026-03-09T13:13:59.871Z" }, + { url = "https://files.pythonhosted.org/packages/de/19/d7fb82984b9238115fe629c915007be608ebd23dc8629703d917dbfaffd4/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:38f4a703656f493b0ad185211ccfca7f0386120f022066b018eb5296d8613e23", size = 2227865, upload-time = "2026-03-09T13:14:01.401Z" }, + { url = "https://files.pythonhosted.org/packages/7f/b9/46b7f386589fd222dac9e9de9c956ce5bcefe2ee73b4e79891381dda8654/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3ac2360e93cb41be81121755c6462cff3beaa9967188c866e5fce5cf13170859", size = 2324369, upload-time = "2026-03-09T13:14:02.972Z" }, + { url = "https://files.pythonhosted.org/packages/92/8b/95e237cf3d9c642960153c769ddcbe278f182c8affb20cecc1cc983e7cc5/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:c95cab08d1965db3d84a121f1c7ce7479bdd4072c9b3dafd8fecce48a2e6b902", size = 1977989, upload-time = "2026-03-09T13:14:04.503Z" }, + { url = "https://files.pythonhosted.org/packages/1b/95/980c9df53501892784997820136c01f62bc1865e31b82b9560f980c0e649/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:fc20894c3d21194d8041a28b65622d5b86db786da6e3cfe73f0c762951a61167", size = 2491645, upload-time = "2026-03-09T13:14:06.106Z" }, + { url = "https://files.pythonhosted.org/packages/cb/32/900647fd0840abebe1561792c6b31e6a7c0e278fc3973d30572a965ca14c/kiwisolver-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7a32f72973f0f950c1920475d5c5ea3d971b81b6f0ec53b8d0a956cc965f22e0", size = 2295237, upload-time = "2026-03-09T13:14:08.891Z" }, + { url = "https://files.pythonhosted.org/packages/be/8a/be60e3bbcf513cc5a50f4a3e88e1dcecebb79c1ad607a7222877becaa101/kiwisolver-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:0bf3acf1419fa93064a4c2189ac0b58e3be7872bf6ee6177b0d4c63dc4cea276", size = 73573, upload-time = "2026-03-09T13:14:12.327Z" }, + { url = "https://files.pythonhosted.org/packages/4d/d2/64be2e429eb4fca7f7e1c52a91b12663aeaf25de3895e5cca0f47ef2a8d0/kiwisolver-1.5.0-cp313-cp313-win_arm64.whl", hash = "sha256:fa8eb9ecdb7efb0b226acec134e0d709e87a909fa4971a54c0c4f6e88635484c", size = 64998, upload-time = "2026-03-09T13:14:13.469Z" }, + { url = "https://files.pythonhosted.org/packages/b0/69/ce68dd0c85755ae2de490bf015b62f2cea5f6b14ff00a463f9d0774449ff/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_universal2.whl", hash = "sha256:db485b3847d182b908b483b2ed133c66d88d49cacf98fd278fadafe11b4478d1", size = 125700, upload-time = "2026-03-09T13:14:14.636Z" }, + { url = "https://files.pythonhosted.org/packages/74/aa/937aac021cf9d4349990d47eb319309a51355ed1dbdc9c077cdc9224cb11/kiwisolver-1.5.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:be12f931839a3bdfe28b584db0e640a65a8bcbc24560ae3fdb025a449b3d754e", size = 67537, upload-time = "2026-03-09T13:14:15.808Z" }, + { url = "https://files.pythonhosted.org/packages/ee/20/3a87fbece2c40ad0f6f0aefa93542559159c5f99831d596050e8afae7a9f/kiwisolver-1.5.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:16b85d37c2cbb3253226d26e64663f755d88a03439a9c47df6246b35defbdfb7", size = 65514, upload-time = "2026-03-09T13:14:18.035Z" }, + { url = "https://files.pythonhosted.org/packages/f0/7f/f943879cda9007c45e1f7dba216d705c3a18d6b35830e488b6c6a4e7cdf0/kiwisolver-1.5.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4432b835675f0ea7414aab3d37d119f7226d24869b7a829caeab49ebda407b0c", size = 1584848, upload-time = "2026-03-09T13:14:19.745Z" }, + { url = "https://files.pythonhosted.org/packages/37/f8/4d4f85cc1870c127c88d950913370dd76138482161cd07eabbc450deff01/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b0feb50971481a2cc44d94e88bdb02cdd497618252ae226b8eb1201b957e368", size = 1391542, upload-time = "2026-03-09T13:14:21.54Z" }, + { url = "https://files.pythonhosted.org/packages/04/0b/65dd2916c84d252b244bd405303220f729e7c17c9d7d33dca6feeff9ffc4/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:56fa888f10d0f367155e76ce849fa1166fc9730d13bd2d65a2aa13b6f5424489", size = 1404447, upload-time = "2026-03-09T13:14:23.205Z" }, + { url = "https://files.pythonhosted.org/packages/39/5c/2606a373247babce9b1d056c03a04b65f3cf5290a8eac5d7bdead0a17e21/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:940dda65d5e764406b9fb92761cbf462e4e63f712ab60ed98f70552e496f3bf1", size = 1455918, upload-time = "2026-03-09T13:14:24.74Z" }, + { url = "https://files.pythonhosted.org/packages/d5/d1/c6078b5756670658e9192a2ef11e939c92918833d2745f85cd14a6004bdf/kiwisolver-1.5.0-cp313-cp313t-manylinux_2_39_riscv64.whl", hash = "sha256:89fc958c702ee9a745e4700378f5d23fddbc46ff89e8fdbf5395c24d5c1452a3", size = 1072856, upload-time = "2026-03-09T13:14:26.597Z" }, + { url = "https://files.pythonhosted.org/packages/cb/c8/7def6ddf16eb2b3741d8b172bdaa9af882b03c78e9b0772975408801fa63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9027d773c4ff81487181a925945743413f6069634d0b122d0b37684ccf4f1e18", size = 2333580, upload-time = "2026-03-09T13:14:28.237Z" }, + { url = "https://files.pythonhosted.org/packages/9e/87/2ac1fce0eb1e616fcd3c35caa23e665e9b1948bb984f4764790924594128/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_ppc64le.whl", hash = "sha256:5b233ea3e165e43e35dba1d2b8ecc21cf070b45b65ae17dd2747d2713d942021", size = 2423018, upload-time = "2026-03-09T13:14:30.018Z" }, + { url = "https://files.pythonhosted.org/packages/67/13/c6700ccc6cc218716bfcda4935e4b2997039869b4ad8a94f364c5a3b8e63/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:ce9bf03dad3b46408c08649c6fbd6ca28a9fce0eb32fdfffa6775a13103b5310", size = 2062804, upload-time = "2026-03-09T13:14:32.888Z" }, + { url = "https://files.pythonhosted.org/packages/1b/bd/877056304626943ff0f1f44c08f584300c199b887cb3176cd7e34f1515f1/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_s390x.whl", hash = "sha256:fc4d3f1fb9ca0ae9f97b095963bc6326f1dbfd3779d6679a1e016b9baaa153d3", size = 2597482, upload-time = "2026-03-09T13:14:34.971Z" }, + { url = "https://files.pythonhosted.org/packages/75/19/c60626c47bf0f8ac5dcf72c6c98e266d714f2fbbfd50cf6dab5ede3aaa50/kiwisolver-1.5.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:f443b4825c50a51ee68585522ab4a1d1257fac65896f282b4c6763337ac9f5d2", size = 2394328, upload-time = "2026-03-09T13:14:36.816Z" }, + { url = "https://files.pythonhosted.org/packages/47/84/6a6d5e5bb8273756c27b7d810d47f7ef2f1f9b9fd23c9ee9a3f8c75c9cef/kiwisolver-1.5.0-cp313-cp313t-win_arm64.whl", hash = "sha256:893ff3a711d1b515ba9da14ee090519bad4610ed1962fbe298a434e8c5f8db53", size = 68410, upload-time = "2026-03-09T13:14:38.695Z" }, + { url = "https://files.pythonhosted.org/packages/e4/d7/060f45052f2a01ad5762c8fdecd6d7a752b43400dc29ff75cd47225a40fd/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:8df31fe574b8b3993cc61764f40941111b25c2d9fea13d3ce24a49907cd2d615", size = 123231, upload-time = "2026-03-09T13:14:41.323Z" }, + { url = "https://files.pythonhosted.org/packages/c2/a7/78da680eadd06ff35edef6ef68a1ad273bad3e2a0936c9a885103230aece/kiwisolver-1.5.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1d49a49ac4cbfb7c1375301cd1ec90169dfeae55ff84710d782260ce77a75a02", size = 66489, upload-time = "2026-03-09T13:14:42.534Z" }, + { url = "https://files.pythonhosted.org/packages/49/b2/97980f3ad4fae37dd7fe31626e2bf75fbf8bdf5d303950ec1fab39a12da8/kiwisolver-1.5.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0cbe94b69b819209a62cb27bdfa5dc2a8977d8de2f89dfd97ba4f53ed3af754e", size = 64063, upload-time = "2026-03-09T13:14:44.759Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f9/b06c934a6aa8bc91f566bd2a214fd04c30506c2d9e2b6b171953216a65b6/kiwisolver-1.5.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:80aa065ffd378ff784822a6d7c3212f2d5f5e9c3589614b5c228b311fd3063ac", size = 1475913, upload-time = "2026-03-09T13:14:46.247Z" }, + { url = "https://files.pythonhosted.org/packages/6b/f0/f768ae564a710135630672981231320bc403cf9152b5596ec5289de0f106/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e7f886f47ab881692f278ae901039a234e4025a68e6dfab514263a0b1c4ae05", size = 1282782, upload-time = "2026-03-09T13:14:48.458Z" }, + { url = "https://files.pythonhosted.org/packages/e2/9f/1de7aad00697325f05238a5f2eafbd487fb637cc27a558b5367a5f37fb7f/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:5060731cc3ed12ca3a8b57acd4aeca5bbc2f49216dd0bec1650a1acd89486bcd", size = 1300815, upload-time = "2026-03-09T13:14:50.721Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c2/297f25141d2e468e0ce7f7a7b92e0cf8918143a0cbd3422c1ad627e85a06/kiwisolver-1.5.0-cp314-cp314-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:7a4aa69609f40fce3cbc3f87b2061f042eee32f94b8f11db707b66a26461591a", size = 1347925, upload-time = "2026-03-09T13:14:52.304Z" }, + { url = "https://files.pythonhosted.org/packages/b9/d3/f4c73a02eb41520c47610207b21afa8cdd18fdbf64ffd94674ae21c4812d/kiwisolver-1.5.0-cp314-cp314-manylinux_2_39_riscv64.whl", hash = "sha256:d168fda2dbff7b9b5f38e693182d792a938c31db4dac3a80a4888de603c99554", size = 991322, upload-time = "2026-03-09T13:14:54.637Z" }, + { url = "https://files.pythonhosted.org/packages/7b/46/d3f2efef7732fcda98d22bf4ad5d3d71d545167a852ca710a494f4c15343/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:413b820229730d358efd838ecbab79902fe97094565fdc80ddb6b0a18c18a581", size = 2232857, upload-time = "2026-03-09T13:14:56.471Z" }, + { url = "https://files.pythonhosted.org/packages/3f/ec/2d9756bf2b6d26ae4349b8d3662fb3993f16d80c1f971c179ce862b9dbae/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:5124d1ea754509b09e53738ec185584cc609aae4a3b510aaf4ed6aa047ef9303", size = 2329376, upload-time = "2026-03-09T13:14:58.072Z" }, + { url = "https://files.pythonhosted.org/packages/8f/9f/876a0a0f2260f1bde92e002b3019a5fabc35e0939c7d945e0fa66185eb20/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:e4415a8db000bf49a6dd1c478bf70062eaacff0f462b92b0ba68791a905861f9", size = 1982549, upload-time = "2026-03-09T13:14:59.668Z" }, + { url = "https://files.pythonhosted.org/packages/6c/4f/ba3624dfac23a64d54ac4179832860cb537c1b0af06024936e82ca4154a0/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:d618fd27420381a4f6044faa71f46d8bfd911bd077c555f7138ed88729bfbe79", size = 2494680, upload-time = "2026-03-09T13:15:01.364Z" }, + { url = "https://files.pythonhosted.org/packages/39/b7/97716b190ab98911b20d10bf92eca469121ec483b8ce0edd314f51bc85af/kiwisolver-1.5.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5092eb5b1172947f57d6ea7d89b2f29650414e4293c47707eb499ec07a0ac796", size = 2297905, upload-time = "2026-03-09T13:15:03.925Z" }, + { url = "https://files.pythonhosted.org/packages/a3/36/4e551e8aa55c9188bca9abb5096805edbf7431072b76e2298e34fd3a3008/kiwisolver-1.5.0-cp314-cp314-win_amd64.whl", hash = "sha256:d76e2d8c75051d58177e762164d2e9ab92886534e3a12e795f103524f221dd8e", size = 75086, upload-time = "2026-03-09T13:15:07.775Z" }, + { url = "https://files.pythonhosted.org/packages/70/15/9b90f7df0e31a003c71649cf66ef61c3c1b862f48c81007fa2383c8bd8d7/kiwisolver-1.5.0-cp314-cp314-win_arm64.whl", hash = "sha256:fa6248cd194edff41d7ea9425ced8ca3a6f838bfb295f6f1d6e6bb694a8518df", size = 66577, upload-time = "2026-03-09T13:15:09.139Z" }, + { url = "https://files.pythonhosted.org/packages/17/01/7dc8c5443ff42b38e72731643ed7cf1ed9bf01691ae5cdca98501999ed83/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:d1ffeb80b5676463d7a7d56acbe8e37a20ce725570e09549fe738e02ca6b7e1e", size = 125794, upload-time = "2026-03-09T13:15:10.525Z" }, + { url = "https://files.pythonhosted.org/packages/46/8a/b4ebe46ebaac6a303417fab10c2e165c557ddaff558f9699d302b256bc53/kiwisolver-1.5.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:bc4d8e252f532ab46a1de9349e2d27b91fce46736a9eedaa37beaca66f574ed4", size = 67646, upload-time = "2026-03-09T13:15:12.016Z" }, + { url = "https://files.pythonhosted.org/packages/60/35/10a844afc5f19d6f567359bf4789e26661755a2f36200d5d1ed8ad0126e5/kiwisolver-1.5.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6783e069732715ad0c3ce96dbf21dbc2235ab0593f2baf6338101f70371f4028", size = 65511, upload-time = "2026-03-09T13:15:13.311Z" }, + { url = "https://files.pythonhosted.org/packages/f8/8a/685b297052dd041dcebce8e8787b58923b6e78acc6115a0dc9189011c44b/kiwisolver-1.5.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:e7c4c09a490dc4d4a7f8cbee56c606a320f9dc28cf92a7157a39d1ce7676a657", size = 1584858, upload-time = "2026-03-09T13:15:15.103Z" }, + { url = "https://files.pythonhosted.org/packages/9e/80/04865e3d4638ac5bddec28908916df4a3075b8c6cc101786a96803188b96/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a075bd7bd19c70cf67c8badfa36cf7c5d8de3c9ddb8420c51e10d9c50e94920", size = 1392539, upload-time = "2026-03-09T13:15:16.661Z" }, + { url = "https://files.pythonhosted.org/packages/ba/01/77a19cacc0893fa13fafa46d1bba06fb4dc2360b3292baf4b56d8e067b24/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:bdd3e53429ff02aa319ba59dfe4ceeec345bf46cf180ec2cf6fd5b942e7975e9", size = 1405310, upload-time = "2026-03-09T13:15:18.229Z" }, + { url = "https://files.pythonhosted.org/packages/53/39/bcaf5d0cca50e604cfa9b4e3ae1d64b50ca1ae5b754122396084599ef903/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_24_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cdcb35dc9d807259c981a85531048ede628eabcffb3239adf3d17463518992d", size = 1456244, upload-time = "2026-03-09T13:15:20.444Z" }, + { url = "https://files.pythonhosted.org/packages/d0/7a/72c187abc6975f6978c3e39b7cf67aeb8b3c0a8f9790aa7fd412855e9e1f/kiwisolver-1.5.0-cp314-cp314t-manylinux_2_39_riscv64.whl", hash = "sha256:70d593af6a6ca332d1df73d519fddb5148edb15cd90d5f0155e3746a6d4fcc65", size = 1073154, upload-time = "2026-03-09T13:15:22.039Z" }, + { url = "https://files.pythonhosted.org/packages/c7/ca/cf5b25783ebbd59143b4371ed0c8428a278abe68d6d0104b01865b1bbd0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:377815a8616074cabbf3f53354e1d040c35815a134e01d7614b7692e4bf8acfa", size = 2334377, upload-time = "2026-03-09T13:15:23.741Z" }, + { url = "https://files.pythonhosted.org/packages/4a/e5/b1f492adc516796e88751282276745340e2a72dcd0d36cf7173e0daf3210/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_ppc64le.whl", hash = "sha256:0255a027391d52944eae1dbb5d4cc5903f57092f3674e8e544cdd2622826b3f0", size = 2425288, upload-time = "2026-03-09T13:15:25.789Z" }, + { url = "https://files.pythonhosted.org/packages/e6/e5/9b21fbe91a61b8f409d74a26498706e97a48008bfcd1864373d32a6ba31c/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:012b1eb16e28718fa782b5e61dc6f2da1f0792ca73bd05d54de6cb9561665fc9", size = 2063158, upload-time = "2026-03-09T13:15:27.63Z" }, + { url = "https://files.pythonhosted.org/packages/b1/02/83f47986138310f95ea95531f851b2a62227c11cbc3e690ae1374fe49f0f/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_s390x.whl", hash = "sha256:0e3aafb33aed7479377e5e9a82e9d4bf87063741fc99fc7ae48b0f16e32bdd6f", size = 2597260, upload-time = "2026-03-09T13:15:29.421Z" }, + { url = "https://files.pythonhosted.org/packages/07/18/43a5f24608d8c313dd189cf838c8e68d75b115567c6279de7796197cfb6a/kiwisolver-1.5.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:e7a116ae737f0000343218c4edf5bd45893bfeaff0993c0b215d7124c9f77646", size = 2394403, upload-time = "2026-03-09T13:15:31.517Z" }, + { url = "https://files.pythonhosted.org/packages/3b/b5/98222136d839b8afabcaa943b09bd05888c2d36355b7e448550211d1fca4/kiwisolver-1.5.0-cp314-cp314t-win_amd64.whl", hash = "sha256:1dd9b0b119a350976a6d781e7278ec7aca0b201e1a9e2d23d9804afecb6ca681", size = 79687, upload-time = "2026-03-09T13:15:33.204Z" }, + { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, + { url = "https://files.pythonhosted.org/packages/1c/fa/2910df836372d8761bb6eff7d8bdcb1613b5c2e03f260efe7abe34d388a7/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_10_13_x86_64.whl", hash = "sha256:5ae8e62c147495b01a0f4765c878e9bfdf843412446a247e28df59936e99e797", size = 130262, upload-time = "2026-03-09T13:15:35.629Z" }, + { url = "https://files.pythonhosted.org/packages/0f/41/c5f71f9f00aabcc71fee8b7475e3f64747282580c2fe748961ba29b18385/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f6764a4ccab3078db14a632420930f6186058750df066b8ea2a7106df91d3203", size = 138036, upload-time = "2026-03-09T13:15:36.894Z" }, + { url = "https://files.pythonhosted.org/packages/fa/06/7399a607f434119c6e1fdc8ec89a8d51ccccadf3341dee4ead6bd14caaf5/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c31c13da98624f957b0fb1b5bae5383b2333c2c3f6793d9825dd5ce79b525cb7", size = 194295, upload-time = "2026-03-09T13:15:38.22Z" }, + { url = "https://files.pythonhosted.org/packages/b5/91/53255615acd2a1eaca307ede3c90eb550bae9c94581f8c00081b6b1c8f44/kiwisolver-1.5.0-graalpy312-graalpy250_312_native-win_amd64.whl", hash = "sha256:1f1489f769582498610e015a8ef2d36f28f505ab3096d0e16b4858a9ec214f57", size = 75987, upload-time = "2026-03-09T13:15:39.65Z" }, + { url = "https://files.pythonhosted.org/packages/e9/eb/5fcbbbf9a0e2c3a35effb88831a483345326bbc3a030a3b5b69aee647f84/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:ec4c85dc4b687c7f7f15f553ff26a98bfe8c58f5f7f0ac8905f0ba4c7be60232", size = 59532, upload-time = "2026-03-09T13:15:47.047Z" }, + { url = "https://files.pythonhosted.org/packages/c3/9b/e17104555bb4db148fd52327feea1e96be4b88e8e008b029002c281a21ab/kiwisolver-1.5.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:12e91c215a96e39f57989c8912ae761286ac5a9584d04030ceb3368a357f017a", size = 57420, upload-time = "2026-03-09T13:15:48.199Z" }, + { url = "https://files.pythonhosted.org/packages/48/44/2b5b95b7aa39fb2d8d9d956e0f3d5d45aef2ae1d942d4c3ffac2f9cfed1a/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:be4a51a55833dc29ab5d7503e7bcb3b3af3402d266018137127450005cdfe737", size = 79892, upload-time = "2026-03-09T13:15:49.694Z" }, + { url = "https://files.pythonhosted.org/packages/52/7d/7157f9bba6b455cfb4632ed411e199fc8b8977642c2b12082e1bd9e6d173/kiwisolver-1.5.0-pp311-pypy311_pp73-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:daae526907e262de627d8f70058a0f64acc9e2641c164c99c8f594b34a799a16", size = 77603, upload-time = "2026-03-09T13:15:50.945Z" }, + { url = "https://files.pythonhosted.org/packages/0a/dd/8050c947d435c8d4bc94e3252f4d8bb8a76cfb424f043a8680be637a57f1/kiwisolver-1.5.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:59cd8683f575d96df5bb48f6add94afc055012c29e28124fcae2b63661b9efb1", size = 73558, upload-time = "2026-03-09T13:15:52.112Z" }, ] [[package]] name = "lazy-loader" -version = "0.4" -source = { registry = "https://pypi.org/simple" } +version = "0.5" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "packaging" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/6f/6b/c875b30a1ba490860c93da4cabf479e03f584eba06fe5963f6f6644653d8/lazy_loader-0.4.tar.gz", hash = "sha256:47c75182589b91a4e1a85a136c074285a5ad4d9f39c63e0d7fb76391c4574cd1", size = 15431, upload-time = "2024-04-05T13:03:12.261Z" } +sdist = { url = "https://files.pythonhosted.org/packages/49/ac/21a1f8aa3777f5658576777ea76bfb124b702c520bbe90edf4ae9915eafa/lazy_loader-0.5.tar.gz", hash = "sha256:717f9179a0dbed357012ddad50a5ad3d5e4d9a0b8712680d4e687f5e6e6ed9b3", size = 15294, upload-time = "2026-03-06T15:45:09.054Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/83/60/d497a310bde3f01cb805196ac61b7ad6dc5dcf8dce66634dc34364b20b4f/lazy_loader-0.4-py3-none-any.whl", hash = "sha256:342aa8e14d543a154047afb4ba8ef17f5563baad3fc610d7b15b213b0f119efc", size = 12097, upload-time = "2024-04-05T13:03:10.514Z" }, + { url = "https://files.pythonhosted.org/packages/8a/a1/8d812e53a5da1687abb10445275d41a8b13adb781bbf7196ddbcf8d88505/lazy_loader-0.5-py3-none-any.whl", hash = "sha256:ab0ea149e9c554d4ffeeb21105ac60bed7f3b4fd69b1d2360a4add51b170b005", size = 8044, upload-time = "2026-03-06T15:45:07.668Z" }, ] [[package]] name = "llvmlite" -version = "0.45.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/99/8d/5baf1cef7f9c084fb35a8afbde88074f0d6a727bc63ef764fe0e7543ba40/llvmlite-0.45.1.tar.gz", hash = "sha256:09430bb9d0bb58fc45a45a57c7eae912850bedc095cd0810a57de109c69e1c32", size = 185600, upload-time = "2025-10-01T17:59:52.046Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/04/ad/9bdc87b2eb34642c1cfe6bcb4f5db64c21f91f26b010f263e7467e7536a3/llvmlite-0.45.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:60f92868d5d3af30b4239b50e1717cb4e4e54f6ac1c361a27903b318d0f07f42", size = 43043526, upload-time = "2025-10-01T18:03:15.051Z" }, - { url = "https://files.pythonhosted.org/packages/a5/ea/c25c6382f452a943b4082da5e8c1665ce29a62884e2ec80608533e8e82d5/llvmlite-0.45.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:98baab513e19beb210f1ef39066288784839a44cd504e24fff5d17f1b3cf0860", size = 37253118, upload-time = "2025-10-01T18:04:06.783Z" }, - { url = "https://files.pythonhosted.org/packages/fe/af/85fc237de98b181dbbe8647324331238d6c52a3554327ccdc83ced28efba/llvmlite-0.45.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:3adc2355694d6a6fbcc024d59bb756677e7de506037c878022d7b877e7613a36", size = 56288209, upload-time = "2025-10-01T18:01:00.168Z" }, - { url = "https://files.pythonhosted.org/packages/0a/df/3daf95302ff49beff4230065e3178cd40e71294968e8d55baf4a9e560814/llvmlite-0.45.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2f3377a6db40f563058c9515dedcc8a3e562d8693a106a28f2ddccf2c8fcf6ca", size = 55140958, upload-time = "2025-10-01T18:02:11.199Z" }, - { url = "https://files.pythonhosted.org/packages/a4/56/4c0d503fe03bac820ecdeb14590cf9a248e120f483bcd5c009f2534f23f0/llvmlite-0.45.1-cp311-cp311-win_amd64.whl", hash = "sha256:f9c272682d91e0d57f2a76c6d9ebdfccc603a01828cdbe3d15273bdca0c3363a", size = 38132232, upload-time = "2025-10-01T18:04:52.181Z" }, - { url = "https://files.pythonhosted.org/packages/e2/7c/82cbd5c656e8991bcc110c69d05913be2229302a92acb96109e166ae31fb/llvmlite-0.45.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:28e763aba92fe9c72296911e040231d486447c01d4f90027c8e893d89d49b20e", size = 43043524, upload-time = "2025-10-01T18:03:30.666Z" }, - { url = "https://files.pythonhosted.org/packages/9d/bc/5314005bb2c7ee9f33102c6456c18cc81745d7055155d1218f1624463774/llvmlite-0.45.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:1a53f4b74ee9fd30cb3d27d904dadece67a7575198bd80e687ee76474620735f", size = 37253123, upload-time = "2025-10-01T18:04:18.177Z" }, - { url = "https://files.pythonhosted.org/packages/96/76/0f7154952f037cb320b83e1c952ec4a19d5d689cf7d27cb8a26887d7bbc1/llvmlite-0.45.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b3796b1b1e1c14dcae34285d2f4ea488402fbd2c400ccf7137603ca3800864f", size = 56288211, upload-time = "2025-10-01T18:01:24.079Z" }, - { url = "https://files.pythonhosted.org/packages/00/b1/0b581942be2683ceb6862d558979e87387e14ad65a1e4db0e7dd671fa315/llvmlite-0.45.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:779e2f2ceefef0f4368548685f0b4adde34e5f4b457e90391f570a10b348d433", size = 55140958, upload-time = "2025-10-01T18:02:30.482Z" }, - { url = "https://files.pythonhosted.org/packages/33/94/9ba4ebcf4d541a325fd8098ddc073b663af75cc8b065b6059848f7d4dce7/llvmlite-0.45.1-cp312-cp312-win_amd64.whl", hash = "sha256:9e6c9949baf25d9aa9cd7cf0f6d011b9ca660dd17f5ba2b23bdbdb77cc86b116", size = 38132231, upload-time = "2025-10-01T18:05:03.664Z" }, - { url = "https://files.pythonhosted.org/packages/1d/e2/c185bb7e88514d5025f93c6c4092f6120c6cea8fe938974ec9860fb03bbb/llvmlite-0.45.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:d9ea9e6f17569a4253515cc01dade70aba536476e3d750b2e18d81d7e670eb15", size = 43043524, upload-time = "2025-10-01T18:03:43.249Z" }, - { url = "https://files.pythonhosted.org/packages/09/b8/b5437b9ecb2064e89ccf67dccae0d02cd38911705112dd0dcbfa9cd9a9de/llvmlite-0.45.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:c9f3cadee1630ce4ac18ea38adebf2a4f57a89bd2740ce83746876797f6e0bfb", size = 37253121, upload-time = "2025-10-01T18:04:30.557Z" }, - { url = "https://files.pythonhosted.org/packages/f7/97/ad1a907c0173a90dd4df7228f24a3ec61058bc1a9ff8a0caec20a0cc622e/llvmlite-0.45.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:57c48bf2e1083eedbc9406fb83c4e6483017879714916fe8be8a72a9672c995a", size = 56288210, upload-time = "2025-10-01T18:01:40.26Z" }, - { url = "https://files.pythonhosted.org/packages/32/d8/c99c8ac7a326e9735401ead3116f7685a7ec652691aeb2615aa732b1fc4a/llvmlite-0.45.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3aa3dfceda4219ae39cf18806c60eeb518c1680ff834b8b311bd784160b9ce40", size = 55140957, upload-time = "2025-10-01T18:02:46.244Z" }, - { url = "https://files.pythonhosted.org/packages/09/56/ed35668130e32dbfad2eb37356793b0a95f23494ab5be7d9bf5cb75850ee/llvmlite-0.45.1-cp313-cp313-win_amd64.whl", hash = "sha256:080e6f8d0778a8239cd47686d402cb66eb165e421efa9391366a9b7e5810a38b", size = 38132232, upload-time = "2025-10-01T18:05:14.477Z" }, +version = "0.47.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/88/a8952b6d5c21e74cbf158515b779666f692846502623e9e3c39d8e8ba25f/llvmlite-0.47.0.tar.gz", hash = "sha256:62031ce968ec74e95092184d4b0e857e444f8fdff0b8f9213707699570c33ccc", size = 193614, upload-time = "2026-03-31T18:29:53.497Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/34/0b/b9d1911cfefa61399821dfb37f486d83e0f42630a8d12f7194270c417002/llvmlite-0.47.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:74090f0dcfd6f24ebbef3f21f11e38111c4d7e6919b54c4416e1e357c3446b07", size = 37232770, upload-time = "2026-03-31T18:28:26.765Z" }, + { url = "https://files.pythonhosted.org/packages/46/27/5799b020e4cdfb25a7c951c06a96397c135efcdc21b78d853bbd9c814c7d/llvmlite-0.47.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ca14f02e29134e837982497959a8e2193d6035235de1cb41a9cb2bd6da4eedbb", size = 56275177, upload-time = "2026-03-31T18:28:31.01Z" }, + { url = "https://files.pythonhosted.org/packages/7e/51/48a53fedf01cb1f3f43ef200be17ebf83c8d9a04018d3783c1a226c342c2/llvmlite-0.47.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12a69d4bb05f402f30477e21eeabe81911e7c251cecb192bed82cd83c9db10d8", size = 55128631, upload-time = "2026-03-31T18:28:36.046Z" }, + { url = "https://files.pythonhosted.org/packages/a2/50/59227d06bdc96e23322713c381af4e77420949d8cd8a042c79e0043096cc/llvmlite-0.47.0-cp311-cp311-win_amd64.whl", hash = "sha256:c37d6eb7aaabfa83ab9c2ff5b5cdb95a5e6830403937b2c588b7490724e05327", size = 38138400, upload-time = "2026-03-31T18:28:40.076Z" }, + { url = "https://files.pythonhosted.org/packages/fa/48/4b7fe0e34c169fa2f12532916133e0b219d2823b540733651b34fdac509a/llvmlite-0.47.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:306a265f408c259067257a732c8e159284334018b4083a9e35f67d19792b164f", size = 37232769, upload-time = "2026-03-31T18:28:43.735Z" }, + { url = "https://files.pythonhosted.org/packages/e6/4b/e3f2cd17822cf772a4a51a0a8080b0032e6d37b2dbe8cfb724eac4e31c52/llvmlite-0.47.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5853bf26160857c0c2573415ff4efe01c4c651e59e2c55c2a088740acfee51cd", size = 56275178, upload-time = "2026-03-31T18:28:48.342Z" }, + { url = "https://files.pythonhosted.org/packages/b6/55/a3b4a543185305a9bdf3d9759d53646ed96e55e7dfd43f53e7a421b8fbae/llvmlite-0.47.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:003bcf7fa579e14db59c1a1e113f93ab8a06b56a4be31c7f08264d1d4072d077", size = 55128632, upload-time = "2026-03-31T18:28:52.901Z" }, + { url = "https://files.pythonhosted.org/packages/2f/f5/d281ae0f79378a5a91f308ea9fdb9f9cc068fddd09629edc0725a5a8fde1/llvmlite-0.47.0-cp312-cp312-win_amd64.whl", hash = "sha256:f3079f25bdc24cd9d27c4b2b5e68f5f60c4fdb7e8ad5ee2b9b006007558f9df7", size = 38138692, upload-time = "2026-03-31T18:28:57.147Z" }, + { url = "https://files.pythonhosted.org/packages/77/6f/4615353e016799f80fa52ccb270a843c413b22361fadda2589b2922fb9b0/llvmlite-0.47.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:a3c6a735d4e1041808434f9d440faa3d78d9b4af2ee64d05a66f351883b6ceec", size = 37232771, upload-time = "2026-03-31T18:29:01.324Z" }, + { url = "https://files.pythonhosted.org/packages/31/b8/69f5565f1a280d032525878a86511eebed0645818492feeb169dfb20ae8e/llvmlite-0.47.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2699a74321189e812d476a43d6d7f652f51811e7b5aad9d9bba842a1c7927acb", size = 56275178, upload-time = "2026-03-31T18:29:05.748Z" }, + { url = "https://files.pythonhosted.org/packages/d6/da/b32cafcb926fb0ce2aa25553bf32cb8764af31438f40e2481df08884c947/llvmlite-0.47.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6c6951e2b29930227963e53ee152441f0e14be92e9d4231852102d986c761e40", size = 55128632, upload-time = "2026-03-31T18:29:11.235Z" }, + { url = "https://files.pythonhosted.org/packages/46/9f/4898b44e4042c60fafcb1162dfb7014f6f15b1ec19bf29cfea6bf26df90d/llvmlite-0.47.0-cp313-cp313-win_amd64.whl", hash = "sha256:c2e9adf8698d813a9a5efb2d4370caf344dbc1e145019851fee6a6f319ba760e", size = 38138695, upload-time = "2026-03-31T18:29:15.43Z" }, + { url = "https://files.pythonhosted.org/packages/1c/d4/33c8af00f0bf6f552d74f3a054f648af2c5bc6bece97972f3bfadce4f5ec/llvmlite-0.47.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:de966c626c35c9dff5ae7bf12db25637738d0df83fc370cf793bc94d43d92d14", size = 37232773, upload-time = "2026-03-31T18:29:19.453Z" }, + { url = "https://files.pythonhosted.org/packages/64/1d/a760e993e0c0ba6db38d46b9f48f6c7dceb8ac838824997fb9e25f97bc04/llvmlite-0.47.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ddbccff2aeaff8670368340a158abefc032fe9b3ccf7d9c496639263d00151aa", size = 56275176, upload-time = "2026-03-31T18:29:24.149Z" }, + { url = "https://files.pythonhosted.org/packages/84/3b/e679bc3b29127182a7f4aa2d2e9e5bea42adb93fb840484147d59c236299/llvmlite-0.47.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d4a7b778a2e144fc64468fb9bf509ac1226c9813a00b4d7afea5d988c4e22fca", size = 55128631, upload-time = "2026-03-31T18:29:29.536Z" }, + { url = "https://files.pythonhosted.org/packages/be/f7/19e2a09c62809c9e63bbd14ce71fb92c6ff7b7b3045741bb00c781efc3c9/llvmlite-0.47.0-cp314-cp314-win_amd64.whl", hash = "sha256:694e3c2cdc472ed2bd8bd4555ca002eec4310961dd58ef791d508f57b5cc4c94", size = 39153826, upload-time = "2026-03-31T18:29:33.681Z" }, + { url = "https://files.pythonhosted.org/packages/40/a1/581a8c707b5e80efdbbe1dd94527404d33fe50bceb71f39d5a7e11bd57b7/llvmlite-0.47.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:92ec8a169a20b473c1c54d4695e371bde36489fc1efa3688e11e99beba0abf9c", size = 37232772, upload-time = "2026-03-31T18:29:37.952Z" }, + { url = "https://files.pythonhosted.org/packages/11/03/16090dd6f74ba2b8b922276047f15962fbeea0a75d5601607edb301ba945/llvmlite-0.47.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fa1cbd800edd3b20bc141521f7fd45a6185a5b84109aa6855134e81397ffe72b", size = 56275178, upload-time = "2026-03-31T18:29:42.58Z" }, + { url = "https://files.pythonhosted.org/packages/f5/cb/0abf1dd4c5286a95ffe0c1d8c67aec06b515894a0dd2ac97f5e27b82ab0b/llvmlite-0.47.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f6725179b89f03b17dabe236ff3422cb8291b4c1bf40af152826dfd34e350ae8", size = 55128632, upload-time = "2026-03-31T18:29:46.939Z" }, + { url = "https://files.pythonhosted.org/packages/4f/79/d3bbab197e86e0ff4f9c07122895b66a3e0d024247fcff7f12c473cb36d9/llvmlite-0.47.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6842cf6f707ec4be3d985a385ad03f72b2d724439e118fcbe99b2929964f0453", size = 39153839, upload-time = "2026-03-31T18:29:51.004Z" }, ] [[package]] name = "markdown-it-py" version = "4.0.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "mdurl" }, ] @@ -586,10 +659,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/94/54/e7d793b573f298e1c9013b8c4dade17d481164aa517d1d7148619c2cedbf/markdown_it_py-4.0.0-py3-none-any.whl", hash = "sha256:87327c59b172c5011896038353a81343b6754500a08cd7a4973bb48c6d578147", size = 87321, upload-time = "2025-08-11T12:57:51.923Z" }, ] +[[package]] +name = "markdown2" +version = "2.5.5" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/e4/ae/07d4a5fcaa5509221287d289323d75ac8eda5a5a4ac9de2accf7bbcc2b88/markdown2-2.5.5.tar.gz", hash = "sha256:001547e68f6e7fcf0f1cb83f7e82f48aa7d48b2c6a321f0cd20a853a8a2d1664", size = 157249, upload-time = "2026-03-02T20:46:53.411Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/43/af/4b3891eb0a49d6cfd5cbf3e9bf514c943afc2b0f13e2c57cc57cd88ecc21/markdown2-2.5.5-py3-none-any.whl", hash = "sha256:be798587e09d1f52d2e4d96a649c4b82a778c75f9929aad52a2c95747fa26941", size = 56250, upload-time = "2026-03-02T20:46:52.032Z" }, +] + [[package]] name = "markupsafe" version = "3.0.3" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/08/db/fefacb2136439fc8dd20e797950e749aa1f4997ed584c62cfb8ef7c2be0e/markupsafe-3.0.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1cc7ea17a6824959616c525620e387f6dd30fec8cb44f649e31712db02123dad", size = 11631, upload-time = "2025-09-27T18:36:18.185Z" }, @@ -663,7 +745,7 @@ wheels = [ [[package]] name = "matplotlib" version = "3.8.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "contourpy" }, { name = "cycler" }, @@ -694,7 +776,7 @@ wheels = [ [[package]] name = "mdurl" version = "0.1.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } sdist = { url = "https://files.pythonhosted.org/packages/d6/54/cfe61301667036ec958cb99bd3efefba235e65cdeb9c84d24a8293ba1d90/mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba", size = 8729, upload-time = "2022-08-14T12:40:10.846Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, @@ -703,7 +785,7 @@ wheels = [ [[package]] name = "ml-dtypes" version = "0.4.1" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "numpy" }, ] @@ -722,7 +804,7 @@ wheels = [ [[package]] name = "mpmath" version = "1.3.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e0/47/dd32fa426cc72114383ac549964eecb20ecfd886d1e5ccf5340b55b02f57/mpmath-1.3.0.tar.gz", hash = "sha256:7a28eb2a9774d00c7bc92411c19a89209d5da7c4c9a9e227be8330a23a25b91f", size = 508106, upload-time = "2023-03-07T16:47:11.061Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/43/e3/7d92a15f894aa0c9c4b49b8ee9ac9850d6e63b03c9c32c0367a13ae62209/mpmath-1.3.0-py3-none-any.whl", hash = "sha256:a0b2b9fe80bbcd81a6647ff13108738cfb482d481d826cc0e02f5b35e5c88d2c", size = 536198, upload-time = "2023-03-07T16:47:09.197Z" }, @@ -731,7 +813,7 @@ wheels = [ [[package]] name = "natsort" version = "8.4.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } sdist = { url = "https://files.pythonhosted.org/packages/e2/a9/a0c57aee75f77794adaf35322f8b6404cbd0f89ad45c87197a937764b7d0/natsort-8.4.0.tar.gz", hash = "sha256:45312c4a0e5507593da193dedd04abb1469253b601ecaf63445ad80f0a1ea581", size = 76575, upload-time = "2023-06-20T04:17:19.925Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/ef/82/7a9d0550484a62c6da82858ee9419f3dd1ccc9aa1c26a1e43da3ecd20b0d/natsort-8.4.0-py3-none-any.whl", hash = "sha256:4732914fb471f56b5cce04d7bae6f164a592c7712e1c85f9ef585e197299521c", size = 38268, upload-time = "2023-06-20T04:17:17.522Z" }, @@ -739,44 +821,49 @@ wheels = [ [[package]] name = "networkx" -version = "3.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/6c/4f/ccdb8ad3a38e583f214547fd2f7ff1fc160c43a75af88e6aec213404b96a/networkx-3.5.tar.gz", hash = "sha256:d4c6f9cf81f52d69230866796b82afbccdec3db7ae4fbd1b65ea750feed50037", size = 2471065, upload-time = "2025-05-29T11:35:07.804Z" } +version = "3.6.1" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/6a/51/63fe664f3908c97be9d2e4f1158eb633317598cfa6e1fc14af5383f17512/networkx-3.6.1.tar.gz", hash = "sha256:26b7c357accc0c8cde558ad486283728b65b6a95d85ee1cd66bafab4c8168509", size = 2517025, upload-time = "2025-12-08T17:02:39.908Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/eb/8d/776adee7bbf76365fdd7f2552710282c79a4ead5d2a46408c9043a2b70ba/networkx-3.5-py3-none-any.whl", hash = "sha256:0030d386a9a06dee3565298b4a734b68589749a544acbb6c412dc9e2489ec6ec", size = 2034406, upload-time = "2025-05-29T11:35:04.961Z" }, + { url = "https://files.pythonhosted.org/packages/9e/c9/b2622292ea83fbb4ec318f5b9ab867d0a28ab43c5717bb85b0a5f6b3b0a4/networkx-3.6.1-py3-none-any.whl", hash = "sha256:d47fbf302e7d9cbbb9e2555a0d267983d2aa476bac30e90dfbe5669bd57f3762", size = 2068504, upload-time = "2025-12-08T17:02:38.159Z" }, ] [[package]] name = "numba" -version = "0.62.1" -source = { registry = "https://pypi.org/simple" } +version = "0.65.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "llvmlite" }, { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/20/33dbdbfe60e5fd8e3dbfde299d106279a33d9f8308346022316781368591/numba-0.62.1.tar.gz", hash = "sha256:7b774242aa890e34c21200a1fc62e5b5757d5286267e71103257f4e2af0d5161", size = 2749817, upload-time = "2025-09-29T10:46:31.551Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/dd/5f/8b3491dd849474f55e33c16ef55678ace1455c490555337899c35826836c/numba-0.62.1-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:f43e24b057714e480fe44bc6031de499e7cf8150c63eb461192caa6cc8530bc8", size = 2684279, upload-time = "2025-09-29T10:43:37.213Z" }, - { url = "https://files.pythonhosted.org/packages/bf/18/71969149bfeb65a629e652b752b80167fe8a6a6f6e084f1f2060801f7f31/numba-0.62.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:57cbddc53b9ee02830b828a8428757f5c218831ccc96490a314ef569d8342b7b", size = 2687330, upload-time = "2025-09-29T10:43:59.601Z" }, - { url = "https://files.pythonhosted.org/packages/0e/7d/403be3fecae33088027bc8a95dc80a2fda1e3beff3e0e5fc4374ada3afbe/numba-0.62.1-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:604059730c637c7885386521bb1b0ddcbc91fd56131a6dcc54163d6f1804c872", size = 3739727, upload-time = "2025-09-29T10:42:45.922Z" }, - { url = "https://files.pythonhosted.org/packages/e0/c3/3d910d08b659a6d4c62ab3cd8cd93c4d8b7709f55afa0d79a87413027ff6/numba-0.62.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d6c540880170bee817011757dc9049dba5a29db0c09b4d2349295991fe3ee55f", size = 3445490, upload-time = "2025-09-29T10:43:12.692Z" }, - { url = "https://files.pythonhosted.org/packages/5b/82/9d425c2f20d9f0a37f7cb955945a553a00fa06a2b025856c3550227c5543/numba-0.62.1-cp311-cp311-win_amd64.whl", hash = "sha256:03de6d691d6b6e2b76660ba0f38f37b81ece8b2cc524a62f2a0cfae2bfb6f9da", size = 2745550, upload-time = "2025-09-29T10:44:20.571Z" }, - { url = "https://files.pythonhosted.org/packages/5e/fa/30fa6873e9f821c0ae755915a3ca444e6ff8d6a7b6860b669a3d33377ac7/numba-0.62.1-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:1b743b32f8fa5fff22e19c2e906db2f0a340782caf024477b97801b918cf0494", size = 2685346, upload-time = "2025-09-29T10:43:43.677Z" }, - { url = "https://files.pythonhosted.org/packages/a9/d5/504ce8dc46e0dba2790c77e6b878ee65b60fe3e7d6d0006483ef6fde5a97/numba-0.62.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:90fa21b0142bcf08ad8e32a97d25d0b84b1e921bc9423f8dda07d3652860eef6", size = 2688139, upload-time = "2025-09-29T10:44:04.894Z" }, - { url = "https://files.pythonhosted.org/packages/50/5f/6a802741176c93f2ebe97ad90751894c7b0c922b52ba99a4395e79492205/numba-0.62.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6ef84d0ac19f1bf80431347b6f4ce3c39b7ec13f48f233a48c01e2ec06ecbc59", size = 3796453, upload-time = "2025-09-29T10:42:52.771Z" }, - { url = "https://files.pythonhosted.org/packages/7e/df/efd21527d25150c4544eccc9d0b7260a5dec4b7e98b5a581990e05a133c0/numba-0.62.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9315cc5e441300e0ca07c828a627d92a6802bcbf27c5487f31ae73783c58da53", size = 3496451, upload-time = "2025-09-29T10:43:19.279Z" }, - { url = "https://files.pythonhosted.org/packages/80/44/79bfdab12a02796bf4f1841630355c82b5a69933b1d50eb15c7fa37dabe8/numba-0.62.1-cp312-cp312-win_amd64.whl", hash = "sha256:44e3aa6228039992f058f5ebfcfd372c83798e9464297bdad8cc79febcf7891e", size = 2745552, upload-time = "2025-09-29T10:44:26.399Z" }, - { url = "https://files.pythonhosted.org/packages/22/76/501ea2c07c089ef1386868f33dff2978f43f51b854e34397b20fc55e0a58/numba-0.62.1-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:b72489ba8411cc9fdcaa2458d8f7677751e94f0109eeb53e5becfdc818c64afb", size = 2685766, upload-time = "2025-09-29T10:43:49.161Z" }, - { url = "https://files.pythonhosted.org/packages/80/68/444986ed95350c0611d5c7b46828411c222ce41a0c76707c36425d27ce29/numba-0.62.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:44a1412095534a26fb5da2717bc755b57da5f3053965128fe3dc286652cc6a92", size = 2688741, upload-time = "2025-09-29T10:44:10.07Z" }, - { url = "https://files.pythonhosted.org/packages/78/7e/bf2e3634993d57f95305c7cee4c9c6cb3c9c78404ee7b49569a0dfecfe33/numba-0.62.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8c9460b9e936c5bd2f0570e20a0a5909ee6e8b694fd958b210e3bde3a6dba2d7", size = 3804576, upload-time = "2025-09-29T10:42:59.53Z" }, - { url = "https://files.pythonhosted.org/packages/e8/b6/8a1723fff71f63bbb1354bdc60a1513a068acc0f5322f58da6f022d20247/numba-0.62.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:728f91a874192df22d74e3fd42c12900b7ce7190b1aad3574c6c61b08313e4c5", size = 3503367, upload-time = "2025-09-29T10:43:26.326Z" }, - { url = "https://files.pythonhosted.org/packages/9c/ec/9d414e7a80d6d1dc4af0e07c6bfe293ce0b04ea4d0ed6c45dad9bd6e72eb/numba-0.62.1-cp313-cp313-win_amd64.whl", hash = "sha256:bbf3f88b461514287df66bc8d0307e949b09f2b6f67da92265094e8fa1282dd8", size = 2745529, upload-time = "2025-09-29T10:44:31.738Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/49/61/7299643b9c18d669e04be7c5bcb64d985070d07553274817b45b049e7bfe/numba-0.65.0.tar.gz", hash = "sha256:edad0d9f6682e93624c00125a471ae4df186175d71fd604c983c377cdc03e68b", size = 2764131, upload-time = "2026-04-01T03:52:01.946Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ba/ce/d67c499703eb5479ce02420e8ccd65c5753d87d2e16d563f152d71405346/numba-0.65.0-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:28e547d0b18024f19cbaf9de02fc5c145790213d9be8a2c95b43f93ec162b9e4", size = 2680228, upload-time = "2026-04-01T03:51:25.401Z" }, + { url = "https://files.pythonhosted.org/packages/c1/a7/11e2b24251d57cf41fc9ad83f378d890d61a890e3f8eb6338b39833f67a4/numba-0.65.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:032b0b8e879512cd424d79eed6d772a1399c6387ded184c2cf3cc22c08d750a6", size = 3744674, upload-time = "2026-04-01T03:51:27.311Z" }, + { url = "https://files.pythonhosted.org/packages/fe/0b/7c63eb742859a6243f42288441f65ac9dac96ea59f409e43b713aafbe867/numba-0.65.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af143d823624033a128b5950c0aaf9ffc2386dfe954eb757119cf0432335534c", size = 3450620, upload-time = "2026-04-01T03:51:29.092Z" }, + { url = "https://files.pythonhosted.org/packages/53/ff/1371cbbe955be340a46093a10b61462437e0fadc7a63290473a0e584cb03/numba-0.65.0-cp311-cp311-win_amd64.whl", hash = "sha256:15d159578e59a39df246b83480f78d7794b0fca40153b5684d3849a99c48a0fb", size = 2747081, upload-time = "2026-04-01T03:51:30.785Z" }, + { url = "https://files.pythonhosted.org/packages/6c/2f/8bd31a1ea43c01ac215283d83aa5f8d5acbe7a36c85b82f1757bfe9ccb31/numba-0.65.0-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:b27ee4847e1bfb17e9604d100417ee7c1d10f15a6711c6213404b3da13a0b2aa", size = 2680705, upload-time = "2026-04-01T03:51:32.597Z" }, + { url = "https://files.pythonhosted.org/packages/73/36/88406bd58600cc696417b8e5dd6a056478da808f3eaf48d18e2421e0c2d9/numba-0.65.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a52d92ffd297c10364bce60cd1fcb88f99284ab5df085f2c6bcd1cb33b529a6f", size = 3801411, upload-time = "2026-04-01T03:51:34.321Z" }, + { url = "https://files.pythonhosted.org/packages/0c/61/ce753a1d7646dd477e16d15e89473703faebb8995d2f71d7ad69a540b565/numba-0.65.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:da8e371e328c06d0010c3d8b44b21858652831b85bcfba78cb22c042e22dbd8e", size = 3501622, upload-time = "2026-04-01T03:51:36.348Z" }, + { url = "https://files.pythonhosted.org/packages/7d/86/db87a5393f1b1fabef53ac3ba4e6b938bb27e40a04ad7cc512098fcae032/numba-0.65.0-cp312-cp312-win_amd64.whl", hash = "sha256:59bb9f2bb9f1238dfd8e927ba50645c18ae769fef4f3d58ea0ea22a2683b91f5", size = 2749979, upload-time = "2026-04-01T03:51:37.88Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f8/eee0f1ff456218db036bfc9023995ec1f85a9dc8f2422f1594f6a87829e0/numba-0.65.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:c6334094563a456a695c812e6846288376ca02327cf246cdcc83e1bb27862367", size = 2680679, upload-time = "2026-04-01T03:51:39.491Z" }, + { url = "https://files.pythonhosted.org/packages/1b/8f/3d116e4b8e92f6abace431afa4b2b944f4d65bdee83af886f5c4b263df95/numba-0.65.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b8a9008411615c69d083d1dcf477f75a5aa727b30beb16e139799e2be945cdfd", size = 3809537, upload-time = "2026-04-01T03:51:41.42Z" }, + { url = "https://files.pythonhosted.org/packages/b5/2c/6a3ca4128e253cb67affe06deb47688f51ce968f5111e2a06d010e6f1fa6/numba-0.65.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:af96c0cba53664efcb361528b8c75e011a6556c859c7e08424c2715201c6cf7a", size = 3508615, upload-time = "2026-04-01T03:51:43.444Z" }, + { url = "https://files.pythonhosted.org/packages/96/0e/267f9a36fb282c104a971d7eecb685b411c47dce2a740fe69cf5fc2945d9/numba-0.65.0-cp313-cp313-win_amd64.whl", hash = "sha256:6254e73b9c929dc736a1fbd3d6f5680789709a5067cae1fa7198707385129c04", size = 2749938, upload-time = "2026-04-01T03:51:45.218Z" }, + { url = "https://files.pythonhosted.org/packages/56/a4/90edb01e9176053578e343d7a7276bc28356741ee67059aed8ed2c1a4e59/numba-0.65.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:ee336b398a6fca51b1f626034de99f50cb1bd87d537a166275158a3cee744b82", size = 2680878, upload-time = "2026-04-01T03:51:46.91Z" }, + { url = "https://files.pythonhosted.org/packages/24/8d/e12d6ff4b9119db3cbf7b2db1ce257576441bd3c76388c786dea74f20b02/numba-0.65.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:05c0a9fdf75d85f57dee47b719e8d6415707b80aae45d75f63f9dc1b935c29f7", size = 3778456, upload-time = "2026-04-01T03:51:48.552Z" }, + { url = "https://files.pythonhosted.org/packages/17/89/abcd83e76f6a773276fe76244140671bcc5bf820f6e2ae1a15362ae4c8c9/numba-0.65.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:583680e0e8faf124d362df23b4b593f3221a8996341a63d1b664c122401bec2f", size = 3478464, upload-time = "2026-04-01T03:51:50.527Z" }, + { url = "https://files.pythonhosted.org/packages/73/5b/fbce55ce3d933afbc7ade04df826853e4a846aaa47d58d2fbb669b8f2d08/numba-0.65.0-cp314-cp314-win_amd64.whl", hash = "sha256:add297d3e1c08dd884f44100152612fa41e66a51d15fdf91307f9dde31d06830", size = 2752012, upload-time = "2026-04-01T03:51:52.691Z" }, + { url = "https://files.pythonhosted.org/packages/1e/ab/af705f4257d9388fb2fd6d7416573e98b6ca9c786e8b58f02720978557bd/numba-0.65.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:194a243ba53a9157c8538cbb3166ec015d785a8c5d584d06cdd88bee902233c7", size = 2683961, upload-time = "2026-04-01T03:51:54.281Z" }, + { url = "https://files.pythonhosted.org/packages/ff/e5/8267b0adb0c01b52b553df5062fbbb42c30ed5362d08b85cc913a36f838f/numba-0.65.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c7fa502960f7a2f3f5cb025bc7bff888a3551277b92431bfdc5ba2f11a375749", size = 3816373, upload-time = "2026-04-01T03:51:56.18Z" }, + { url = "https://files.pythonhosted.org/packages/b0/f5/b8397ca360971669a93706b9274592b6864e4367a37d498fbbcb62aa2d48/numba-0.65.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5046c63f783ca3eb6195f826a50797465e7c4ce811daa17c9bea47e310c9b964", size = 3532782, upload-time = "2026-04-01T03:51:58.387Z" }, + { url = "https://files.pythonhosted.org/packages/f5/21/1e73fa16bf0393ebb74c5bb208d712152ffdfc84600a8e93a3180317856e/numba-0.65.0-cp314-cp314t-win_amd64.whl", hash = "sha256:46fd679ae4f68c7a5d5721efbd29ecee0b0f3013211591891d79b51bfdf73113", size = 2757611, upload-time = "2026-04-01T03:52:00.083Z" }, ] [[package]] name = "numpy" version = "1.26.4" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } sdist = { url = "https://files.pythonhosted.org/packages/65/6e/09db70a523a96d25e115e71cc56a6f9031e7b8cd166c1ac8438307c14058/numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010", size = 15786129, upload-time = "2024-02-06T00:26:44.495Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/11/57/baae43d14fe163fa0e4c47f307b6b2511ab8d7d30177c491960504252053/numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71", size = 20630554, upload-time = "2024-02-05T23:51:50.149Z" }, @@ -798,152 +885,167 @@ wheels = [ ] [[package]] -name = "nvidia-cublas-cu12" -version = "12.8.4.1" -source = { registry = "https://pypi.org/simple" } +name = "nvidia-cublas" +version = "13.1.0.3" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" }, + { url = "https://files.pythonhosted.org/packages/e1/a5/fce49e2ae977e0ccc084e5adafceb4f0ac0c8333cb6863501618a7277f67/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:c86fc7f7ae36d7528288c5d88098edcb7b02c633d262e7ddbb86b0ad91be5df2", size = 542851226, upload-time = "2025-10-09T08:59:04.818Z" }, + { url = "https://files.pythonhosted.org/packages/e7/44/423ac00af4dd95a5aeb27207e2c0d9b7118702149bf4704c3ddb55bb7429/nvidia_cublas-13.1.0.3-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:ee8722c1f0145ab246bccb9e452153b5e0515fd094c3678df50b2a0888b8b171", size = 423133236, upload-time = "2025-10-09T08:59:32.536Z" }, ] [[package]] -name = "nvidia-cuda-cupti-cu12" -version = "12.8.90" -source = { registry = "https://pypi.org/simple" } +name = "nvidia-cuda-cupti" +version = "13.0.85" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" }, + { url = "https://files.pythonhosted.org/packages/2a/2a/80353b103fc20ce05ef51e928daed4b6015db4aaa9162ed0997090fe2250/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_aarch64.whl", hash = "sha256:796bd679890ee55fb14a94629b698b6db54bcfd833d391d5e94017dd9d7d3151", size = 10310827, upload-time = "2025-09-04T08:26:42.012Z" }, + { url = "https://files.pythonhosted.org/packages/33/6d/737d164b4837a9bbd202f5ae3078975f0525a55730fe871d8ed4e3b952b0/nvidia_cuda_cupti-13.0.85-py3-none-manylinux_2_25_x86_64.whl", hash = "sha256:4eb01c08e859bf924d222250d2e8f8b8ff6d3db4721288cf35d14252a4d933c8", size = 10715597, upload-time = "2025-09-04T08:26:51.312Z" }, ] [[package]] -name = "nvidia-cuda-nvrtc-cu12" -version = "12.8.93" -source = { registry = "https://pypi.org/simple" } +name = "nvidia-cuda-nvrtc" +version = "13.0.88" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" }, + { url = "https://files.pythonhosted.org/packages/c3/68/483a78f5e8f31b08fb1bb671559968c0ca3a065ac7acabfc7cee55214fd6/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:ad9b6d2ead2435f11cbb6868809d2adeeee302e9bb94bcf0539c7a40d80e8575", size = 90215200, upload-time = "2025-09-04T08:28:44.204Z" }, + { url = "https://files.pythonhosted.org/packages/b7/dc/6bb80850e0b7edd6588d560758f17e0550893a1feaf436807d64d2da040f/nvidia_cuda_nvrtc-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:d27f20a0ca67a4bb34268a5e951033496c5b74870b868bacd046b1b8e0c3267b", size = 43015449, upload-time = "2025-09-04T08:28:20.239Z" }, ] [[package]] -name = "nvidia-cuda-runtime-cu12" -version = "12.8.90" -source = { registry = "https://pypi.org/simple" } +name = "nvidia-cuda-runtime" +version = "13.0.96" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" }, + { url = "https://files.pythonhosted.org/packages/87/4f/17d7b9b8e285199c58ce28e31b5c5bbaa4d8271af06a89b6405258245de2/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ef9bcbe90493a2b9d810e43d249adb3d02e98dd30200d86607d8d02687c43f55", size = 2261060, upload-time = "2025-10-09T08:55:15.78Z" }, + { url = "https://files.pythonhosted.org/packages/2e/24/d1558f3b68b1d26e706813b1d10aa1d785e4698c425af8db8edc3dced472/nvidia_cuda_runtime-13.0.96-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f82250d7782aa23b6cfe765ecc7db554bd3c2870c43f3d1821f1d18aebf0548", size = 2243632, upload-time = "2025-10-09T08:55:36.117Z" }, ] [[package]] -name = "nvidia-cudnn-cu12" -version = "9.10.2.21" -source = { registry = "https://pypi.org/simple" } +name = "nvidia-cudnn-cu13" +version = "9.19.0.56" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "nvidia-cublas", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, + { url = "https://files.pythonhosted.org/packages/f1/84/26025437c1e6b61a707442184fa0c03d083b661adf3a3eecfd6d21677740/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:6ed29ffaee1176c612daf442e4dd6cfeb6a0caa43ddcbeb59da94953030b1be4", size = 433781201, upload-time = "2026-02-03T20:40:53.805Z" }, + { url = "https://files.pythonhosted.org/packages/a3/22/0b4b932655d17a6da1b92fa92ab12844b053bb2ac2475e179ba6f043da1e/nvidia_cudnn_cu13-9.19.0.56-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:d20e1734305e9d68889a96e3f35094d733ff1f83932ebe462753973e53a572bf", size = 366066321, upload-time = "2026-02-03T20:44:52.837Z" }, ] [[package]] -name = "nvidia-cufft-cu12" -version = "11.3.3.83" -source = { registry = "https://pypi.org/simple" } +name = "nvidia-cufft" +version = "12.0.0.61" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, + { url = "https://files.pythonhosted.org/packages/8b/ae/f417a75c0259e85c1d2f83ca4e960289a5f814ed0cea74d18c353d3e989d/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2708c852ef8cd89d1d2068bdbece0aa188813a0c934db3779b9b1faa8442e5f5", size = 214053554, upload-time = "2025-09-04T08:31:38.196Z" }, + { url = "https://files.pythonhosted.org/packages/a8/2f/7b57e29836ea8714f81e9898409196f47d772d5ddedddf1592eadb8ab743/nvidia_cufft-12.0.0.61-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:6c44f692dce8fd5ffd3e3df134b6cdb9c2f72d99cf40b62c32dde45eea9ddad3", size = 214085489, upload-time = "2025-09-04T08:31:56.044Z" }, ] [[package]] -name = "nvidia-cufile-cu12" -version = "1.13.1.3" -source = { registry = "https://pypi.org/simple" } +name = "nvidia-cufile" +version = "1.15.1.6" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" }, + { url = "https://files.pythonhosted.org/packages/3f/70/4f193de89a48b71714e74602ee14d04e4019ad36a5a9f20c425776e72cd6/nvidia_cufile-1.15.1.6-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:08a3ecefae5a01c7f5117351c64f17c7c62efa5fffdbe24fc7d298da19cd0b44", size = 1223672, upload-time = "2025-09-04T08:32:22.779Z" }, + { url = "https://files.pythonhosted.org/packages/ab/73/cc4a14c9813a8a0d509417cf5f4bdaba76e924d58beb9864f5a7baceefbf/nvidia_cufile-1.15.1.6-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:bdc0deedc61f548bddf7733bdc216456c2fdb101d020e1ab4b88d232d5e2f6d1", size = 1136992, upload-time = "2025-09-04T08:32:14.119Z" }, ] [[package]] -name = "nvidia-curand-cu12" -version = "10.3.9.90" -source = { registry = "https://pypi.org/simple" } +name = "nvidia-curand" +version = "10.4.0.35" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" }, + { url = "https://files.pythonhosted.org/packages/1e/72/7c2ae24fb6b63a32e6ae5d241cc65263ea18d08802aaae087d9f013335a2/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:133df5a7509c3e292aaa2b477afd0194f06ce4ea24d714d616ff36439cee349a", size = 61962106, upload-time = "2025-08-04T10:21:41.128Z" }, + { url = "https://files.pythonhosted.org/packages/a5/9f/be0a41ca4a4917abf5cb9ae0daff1a6060cc5de950aec0396de9f3b52bc5/nvidia_curand-10.4.0.35-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:1aee33a5da6e1db083fe2b90082def8915f30f3248d5896bcec36a579d941bfc", size = 59544258, upload-time = "2025-08-04T10:22:03.992Z" }, ] [[package]] -name = "nvidia-cusolver-cu12" -version = "11.7.3.90" -source = { registry = "https://pypi.org/simple" } +name = "nvidia-cusolver" +version = "12.0.4.66" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ - { name = "nvidia-cublas-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "nvidia-cusparse-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, - { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "nvidia-cublas", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-cusparse", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, + { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c3/b30c9e935fc01e3da443ec0116ed1b2a009bb867f5324d3f2d7e533e776b/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_aarch64.whl", hash = "sha256:02c2457eaa9e39de20f880f4bd8820e6a1cfb9f9a34f820eb12a155aa5bc92d2", size = 223467760, upload-time = "2025-09-04T08:33:04.222Z" }, + { url = "https://files.pythonhosted.org/packages/5f/67/cba3777620cdacb99102da4042883709c41c709f4b6323c10781a9c3aa34/nvidia_cusolver-12.0.4.66-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:0a759da5dea5c0ea10fd307de75cdeb59e7ea4fcb8add0924859b944babf1112", size = 200941980, upload-time = "2025-09-04T08:33:22.767Z" }, ] [[package]] -name = "nvidia-cusparse-cu12" -version = "12.5.8.93" -source = { registry = "https://pypi.org/simple" } +name = "nvidia-cusparse" +version = "12.6.3.3" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ - { name = "nvidia-nvjitlink-cu12", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')" }, + { name = "nvidia-nvjitlink", marker = "sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'win32'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, + { url = "https://files.pythonhosted.org/packages/f8/94/5c26f33738ae35276672f12615a64bd008ed5be6d1ebcb23579285d960a9/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:80bcc4662f23f1054ee334a15c72b8940402975e0eab63178fc7e670aa59472c", size = 162155568, upload-time = "2025-09-04T08:33:42.864Z" }, + { url = "https://files.pythonhosted.org/packages/fa/18/623c77619c31d62efd55302939756966f3ecc8d724a14dab2b75f1508850/nvidia_cusparse-12.6.3.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:2b3c89c88d01ee0e477cb7f82ef60a11a4bcd57b6b87c33f789350b59759360b", size = 145942937, upload-time = "2025-09-04T08:33:58.029Z" }, ] [[package]] -name = "nvidia-cusparselt-cu12" -version = "0.7.1" -source = { registry = "https://pypi.org/simple" } +name = "nvidia-cusparselt-cu13" +version = "0.8.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" }, + { url = "https://files.pythonhosted.org/packages/46/10/8dcd1175260706a2fc92a16a52e306b71d4c1ea0b0cc4a9484183399818a/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_aarch64.whl", hash = "sha256:400c6ed1cf6780fc6efedd64ec9f1345871767e6a1a0a552a1ea0578117ea77c", size = 220791277, upload-time = "2025-08-13T19:22:40.982Z" }, + { url = "https://files.pythonhosted.org/packages/fd/53/43b0d71f4e702fa9733f8b4571fdca50a8813f1e450b656c239beff12315/nvidia_cusparselt_cu13-0.8.0-py3-none-manylinux2014_x86_64.whl", hash = "sha256:25e30a8a7323935d4ad0340b95a0b69926eee755767e8e0b1cf8dd85b197d3fd", size = 169884119, upload-time = "2025-08-13T19:23:41.967Z" }, ] [[package]] name = "nvidia-ml-py" -version = "13.580.82" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/dd/6c/4a533f2c0185027c465adb6063086bc3728301e95f483665bfa9ebafb2d3/nvidia_ml_py-13.580.82.tar.gz", hash = "sha256:0c028805dc53a0e2a6985ea801888197765ac2ef8f1c9e29a7bf0d3616a5efc7", size = 47999, upload-time = "2025-09-11T16:44:56.267Z" } +version = "13.595.45" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ce/49/c29f6e30d8662d2e94fef17739ea7309cc76aba269922ae999e4cc07f268/nvidia_ml_py-13.595.45.tar.gz", hash = "sha256:c9f34897fe0441ff35bc8f35baf80f830a20b0f4e6ce71e0a325bc0e66acf079", size = 50780, upload-time = "2026-03-19T16:59:44.956Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/7f/96/d6d25a4c307d6645f4a9b91d620c0151c544ad38b5e371313a87d2761004/nvidia_ml_py-13.580.82-py3-none-any.whl", hash = "sha256:4361db337b0c551e2d101936dae2e9a60f957af26818e8c0c3a1f32b8db8d0a7", size = 49008, upload-time = "2025-09-11T16:44:54.915Z" }, + { url = "https://files.pythonhosted.org/packages/8a/24/fc256107d23597fa33d319505ce77160fa1a2349c096d01901ffc7cb7fc4/nvidia_ml_py-13.595.45-py3-none-any.whl", hash = "sha256:b65a7977f503d56154b14d683710125ef93594adb63fbf7e559336e3318f1376", size = 51776, upload-time = "2026-03-19T16:59:43.603Z" }, ] [[package]] -name = "nvidia-nccl-cu12" -version = "2.27.5" -source = { registry = "https://pypi.org/simple" } +name = "nvidia-nccl-cu13" +version = "2.28.9" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/89/f7a07dc961b60645dbbf42e80f2bc85ade7feb9a491b11a1e973aa00071f/nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ad730cf15cb5d25fe849c6e6ca9eb5b76db16a80f13f425ac68d8e2e55624457", size = 322348229, upload-time = "2025-06-26T04:11:28.385Z" }, + { url = "https://files.pythonhosted.org/packages/39/55/1920646a2e43ffd4fc958536b276197ed740e9e0c54105b4bb3521591fc7/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_aarch64.whl", hash = "sha256:01c873ba1626b54caa12272ed228dc5b2781545e0ae8ba3f432a8ef1c6d78643", size = 196561677, upload-time = "2025-11-18T05:49:03.45Z" }, + { url = "https://files.pythonhosted.org/packages/b0/b4/878fefaad5b2bcc6fcf8d474a25e3e3774bc5133e4b58adff4d0bca238bc/nvidia_nccl_cu13-2.28.9-py3-none-manylinux_2_18_x86_64.whl", hash = "sha256:e4553a30f34195f3fa1da02a6da3d6337d28f2003943aa0a3d247bbc25fefc42", size = 196493177, upload-time = "2025-11-18T05:49:17.677Z" }, ] [[package]] -name = "nvidia-nvjitlink-cu12" -version = "12.8.93" -source = { registry = "https://pypi.org/simple" } +name = "nvidia-nvjitlink" +version = "13.0.88" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" }, + { url = "https://files.pythonhosted.org/packages/56/7a/123e033aaff487c77107195fa5a2b8686795ca537935a24efae476c41f05/nvidia_nvjitlink-13.0.88-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:13a74f429e23b921c1109976abefacc69835f2f433ebd323d3946e11d804e47b", size = 40713933, upload-time = "2025-09-04T08:35:43.553Z" }, + { url = "https://files.pythonhosted.org/packages/ab/2c/93c5250e64df4f894f1cbb397c6fd71f79813f9fd79d7cd61de3f97b3c2d/nvidia_nvjitlink-13.0.88-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e931536ccc7d467a98ba1d8b89ff7fa7f1fa3b13f2b0069118cd7f47bff07d0c", size = 38768748, upload-time = "2025-09-04T08:35:20.008Z" }, ] [[package]] -name = "nvidia-nvshmem-cu12" -version = "3.3.20" -source = { registry = "https://pypi.org/simple" } +name = "nvidia-nvshmem-cu13" +version = "3.4.5" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3b/6c/99acb2f9eb85c29fc6f3a7ac4dccfd992e22666dd08a642b303311326a97/nvidia_nvshmem_cu12-3.3.20-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d00f26d3f9b2e3c3065be895e3059d6479ea5c638a3f38c9fec49b1b9dd7c1e5", size = 124657145, upload-time = "2025-08-04T20:25:19.995Z" }, + { url = "https://files.pythonhosted.org/packages/dc/0f/05cc9c720236dcd2db9c1ab97fff629e96821be2e63103569da0c9b72f19/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:6dc2a197f38e5d0376ad52cd1a2a3617d3cdc150fd5966f4aee9bcebb1d68fe9", size = 60215947, upload-time = "2025-09-06T00:32:20.022Z" }, + { url = "https://files.pythonhosted.org/packages/3c/35/a9bf80a609e74e3b000fef598933235c908fcefcef9026042b8e6dfde2a9/nvidia_nvshmem_cu13-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:290f0a2ee94c9f3687a02502f3b9299a9f9fe826e6d0287ee18482e78d495b80", size = 60412546, upload-time = "2025-09-06T00:32:41.564Z" }, ] [[package]] -name = "nvidia-nvtx-cu12" -version = "12.8.90" -source = { registry = "https://pypi.org/simple" } +name = "nvidia-nvtx" +version = "13.0.85" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/c2/f3/d86c845465a2723ad7e1e5c36dcd75ddb82898b3f53be47ebd429fb2fa5d/nvidia_nvtx-13.0.85-py3-none-manylinux1_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4936d1d6780fbe68db454f5e72a42ff64d1fd6397df9f363ae786930fd5c1cd4", size = 148047, upload-time = "2025-09-04T08:29:01.761Z" }, + { url = "https://files.pythonhosted.org/packages/a8/64/3708a90d1ebe202ffdeb7185f878a3c84d15c2b2c31858da2ce0583e2def/nvidia_nvtx-13.0.85-py3-none-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cb7780edb6b14107373c835bf8b72e7a178bac7367e23da7acb108f973f157a6", size = 148878, upload-time = "2025-09-04T08:28:53.627Z" }, ] [[package]] name = "opencv-python-headless" version = "4.11.0.86" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "numpy" }, ] @@ -960,7 +1062,7 @@ wheels = [ [[package]] name = "opt-einsum" version = "3.4.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } sdist = { url = "https://files.pythonhosted.org/packages/8c/b9/2ac072041e899a52f20cf9510850ff58295003aa75525e58343591b0cbfb/opt_einsum-3.4.0.tar.gz", hash = "sha256:96ca72f1b886d148241348783498194c577fa30a8faac108586b14f1ba4473ac", size = 63004, upload-time = "2024-09-26T14:33:24.483Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/23/cd/066e86230ae37ed0be70aae89aabf03ca8d9f39c8aea0dec8029455b5540/opt_einsum-3.4.0-py3-none-any.whl", hash = "sha256:69bb92469f86a1565195ece4ac0323943e83477171b91d24c35afe028a90d7cd", size = 71932, upload-time = "2024-09-26T14:33:23.039Z" }, @@ -969,7 +1071,7 @@ wheels = [ [[package]] name = "optax" version = "0.2.4" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "absl-py" }, { name = "chex" }, @@ -985,22 +1087,22 @@ wheels = [ [[package]] name = "packaging" -version = "25.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/a1/d4/1fc4078c65507b51b96ca8f8c3ba19e6a61c8253c72794544580a7b6c24d/packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f", size = 165727, upload-time = "2025-04-19T11:48:59.673Z" } +version = "26.1" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519, upload-time = "2026-04-14T21:12:49.362Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/20/12/38679034af332785aac8774540895e234f4d07f7545804097de4b666afd8/packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484", size = 66469, upload-time = "2025-04-19T11:48:57.875Z" }, + { url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831, upload-time = "2026-04-14T21:12:47.56Z" }, ] [[package]] name = "pandas" version = "2.3.3" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } resolution-markers = [ - "python_full_version >= '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version >= '3.14' and sys_platform == 'win32'", "python_full_version >= '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.14' and sys_platform == 'darwin'", + "python_full_version >= '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", "(python_full_version >= '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", ] dependencies = [ @@ -1055,18 +1157,18 @@ wheels = [ [[package]] name = "pandas" -version = "3.0.0" -source = { registry = "https://pypi.org/simple" } +version = "3.0.2" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } resolution-markers = [ - "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'darwin'", - "python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'win32'", "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'emscripten'", + "python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine == 'aarch64' and sys_platform == 'linux'", "(python_full_version >= '3.12' and python_full_version < '3.14' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version >= '3.12' and python_full_version < '3.14' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", - "python_full_version < '3.12' and sys_platform == 'darwin'", - "python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version < '3.12' and sys_platform == 'win32'", "python_full_version < '3.12' and sys_platform == 'emscripten'", + "python_full_version < '3.12' and sys_platform == 'darwin'", + "python_full_version < '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", "(python_full_version < '3.12' and platform_machine != 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.12' and sys_platform != 'darwin' and sys_platform != 'emscripten' and sys_platform != 'linux' and sys_platform != 'win32')", ] dependencies = [ @@ -1074,148 +1176,163 @@ dependencies = [ { name = "python-dateutil", marker = "python_full_version < '3.14'" }, { name = "tzdata", marker = "(python_full_version < '3.14' and sys_platform == 'emscripten') or (python_full_version < '3.14' and sys_platform == 'win32')" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/de/da/b1dc0481ab8d55d0f46e343cfe67d4551a0e14fcee52bd38ca1bd73258d8/pandas-3.0.0.tar.gz", hash = "sha256:0facf7e87d38f721f0af46fe70d97373a37701b1c09f7ed7aeeb292ade5c050f", size = 4633005, upload-time = "2026-01-21T15:52:04.726Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/46/1e/b184654a856e75e975a6ee95d6577b51c271cd92cb2b020c9378f53e0032/pandas-3.0.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d64ce01eb9cdca96a15266aa679ae50212ec52757c79204dbc7701a222401850", size = 10313247, upload-time = "2026-01-21T15:50:15.775Z" }, - { url = "https://files.pythonhosted.org/packages/dd/5e/e04a547ad0f0183bf151fd7c7a477468e3b85ff2ad231c566389e6cc9587/pandas-3.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:613e13426069793aa1ec53bdcc3b86e8d32071daea138bbcf4fa959c9cdaa2e2", size = 9913131, upload-time = "2026-01-21T15:50:18.611Z" }, - { url = "https://files.pythonhosted.org/packages/a2/93/bb77bfa9fc2aba9f7204db807d5d3fb69832ed2854c60ba91b4c65ba9219/pandas-3.0.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0192fee1f1a8e743b464a6607858ee4b071deb0b118eb143d71c2a1d170996d5", size = 10741925, upload-time = "2026-01-21T15:50:21.058Z" }, - { url = "https://files.pythonhosted.org/packages/62/fb/89319812eb1d714bfc04b7f177895caeba8ab4a37ef6712db75ed786e2e0/pandas-3.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0b853319dec8d5e0c8b875374c078ef17f2269986a78168d9bd57e49bf650ae", size = 11245979, upload-time = "2026-01-21T15:50:23.413Z" }, - { url = "https://files.pythonhosted.org/packages/a9/63/684120486f541fc88da3862ed31165b3b3e12b6a1c7b93be4597bc84e26c/pandas-3.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:707a9a877a876c326ae2cb640fbdc4ef63b0a7b9e2ef55c6df9942dcee8e2af9", size = 11756337, upload-time = "2026-01-21T15:50:25.932Z" }, - { url = "https://files.pythonhosted.org/packages/39/92/7eb0ad232312b59aec61550c3c81ad0743898d10af5df7f80bc5e5065416/pandas-3.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:afd0aa3d0b5cda6e0b8ffc10dbcca3b09ef3cbcd3fe2b27364f85fdc04e1989d", size = 12325517, upload-time = "2026-01-21T15:50:27.952Z" }, - { url = "https://files.pythonhosted.org/packages/51/27/bf9436dd0a4fc3130acec0828951c7ef96a0631969613a9a35744baf27f6/pandas-3.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:113b4cca2614ff7e5b9fee9b6f066618fe73c5a83e99d721ffc41217b2bf57dd", size = 9881576, upload-time = "2026-01-21T15:50:30.149Z" }, - { url = "https://files.pythonhosted.org/packages/e7/2b/c618b871fce0159fd107516336e82891b404e3f340821853c2fc28c7830f/pandas-3.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:c14837eba8e99a8da1527c0280bba29b0eb842f64aa94982c5e21227966e164b", size = 9140807, upload-time = "2026-01-21T15:50:32.308Z" }, - { url = "https://files.pythonhosted.org/packages/0b/38/db33686f4b5fa64d7af40d96361f6a4615b8c6c8f1b3d334eee46ae6160e/pandas-3.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:9803b31f5039b3c3b10cc858c5e40054adb4b29b4d81cb2fd789f4121c8efbcd", size = 10334013, upload-time = "2026-01-21T15:50:34.771Z" }, - { url = "https://files.pythonhosted.org/packages/a5/7b/9254310594e9774906bacdd4e732415e1f86ab7dbb4b377ef9ede58cd8ec/pandas-3.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:14c2a4099cd38a1d18ff108168ea417909b2dea3bd1ebff2ccf28ddb6a74d740", size = 9874154, upload-time = "2026-01-21T15:50:36.67Z" }, - { url = "https://files.pythonhosted.org/packages/63/d4/726c5a67a13bc66643e66d2e9ff115cead482a44fc56991d0c4014f15aaf/pandas-3.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d257699b9a9960e6125686098d5714ac59d05222bef7a5e6af7a7fd87c650801", size = 10384433, upload-time = "2026-01-21T15:50:39.132Z" }, - { url = "https://files.pythonhosted.org/packages/bf/2e/9211f09bedb04f9832122942de8b051804b31a39cfbad199a819bb88d9f3/pandas-3.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:69780c98f286076dcafca38d8b8eee1676adf220199c0a39f0ecbf976b68151a", size = 10864519, upload-time = "2026-01-21T15:50:41.043Z" }, - { url = "https://files.pythonhosted.org/packages/00/8d/50858522cdc46ac88b9afdc3015e298959a70a08cd21e008a44e9520180c/pandas-3.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4a66384f017240f3858a4c8a7cf21b0591c3ac885cddb7758a589f0f71e87ebb", size = 11394124, upload-time = "2026-01-21T15:50:43.377Z" }, - { url = "https://files.pythonhosted.org/packages/86/3f/83b2577db02503cd93d8e95b0f794ad9d4be0ba7cb6c8bcdcac964a34a42/pandas-3.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:be8c515c9bc33989d97b89db66ea0cececb0f6e3c2a87fcc8b69443a6923e95f", size = 11920444, upload-time = "2026-01-21T15:50:45.932Z" }, - { url = "https://files.pythonhosted.org/packages/64/2d/4f8a2f192ed12c90a0aab47f5557ece0e56b0370c49de9454a09de7381b2/pandas-3.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:a453aad8c4f4e9f166436994a33884442ea62aa8b27d007311e87521b97246e1", size = 9730970, upload-time = "2026-01-21T15:50:47.962Z" }, - { url = "https://files.pythonhosted.org/packages/d4/64/ff571be435cf1e643ca98d0945d76732c0b4e9c37191a89c8550b105eed1/pandas-3.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:da768007b5a33057f6d9053563d6b74dd6d029c337d93c6d0d22a763a5c2ecc0", size = 9041950, upload-time = "2026-01-21T15:50:50.422Z" }, - { url = "https://files.pythonhosted.org/packages/6f/fa/7f0ac4ca8877c57537aaff2a842f8760e630d8e824b730eb2e859ffe96ca/pandas-3.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b78d646249b9a2bc191040988c7bb524c92fa8534fb0898a0741d7e6f2ffafa6", size = 10307129, upload-time = "2026-01-21T15:50:52.877Z" }, - { url = "https://files.pythonhosted.org/packages/6f/11/28a221815dcea4c0c9414dfc845e34a84a6a7dabc6da3194498ed5ba4361/pandas-3.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:bc9cba7b355cb4162442a88ce495e01cb605f17ac1e27d6596ac963504e0305f", size = 9850201, upload-time = "2026-01-21T15:50:54.807Z" }, - { url = "https://files.pythonhosted.org/packages/ba/da/53bbc8c5363b7e5bd10f9ae59ab250fc7a382ea6ba08e4d06d8694370354/pandas-3.0.0-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3c9a1a149aed3b6c9bf246033ff91e1b02d529546c5d6fb6b74a28fea0cf4c70", size = 10354031, upload-time = "2026-01-21T15:50:57.463Z" }, - { url = "https://files.pythonhosted.org/packages/f7/a3/51e02ebc2a14974170d51e2410dfdab58870ea9bcd37cda15bd553d24dc4/pandas-3.0.0-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95683af6175d884ee89471842acfca29172a85031fccdabc35e50c0984470a0e", size = 10861165, upload-time = "2026-01-21T15:50:59.32Z" }, - { url = "https://files.pythonhosted.org/packages/a5/fe/05a51e3cac11d161472b8297bd41723ea98013384dd6d76d115ce3482f9b/pandas-3.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:1fbbb5a7288719e36b76b4f18d46ede46e7f916b6c8d9915b756b0a6c3f792b3", size = 11359359, upload-time = "2026-01-21T15:51:02.014Z" }, - { url = "https://files.pythonhosted.org/packages/ee/56/ba620583225f9b85a4d3e69c01df3e3870659cc525f67929b60e9f21dcd1/pandas-3.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8e8b9808590fa364416b49b2a35c1f4cf2785a6c156935879e57f826df22038e", size = 11912907, upload-time = "2026-01-21T15:51:05.175Z" }, - { url = "https://files.pythonhosted.org/packages/c9/8c/c6638d9f67e45e07656b3826405c5cc5f57f6fd07c8b2572ade328c86e22/pandas-3.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:98212a38a709feb90ae658cb6227ea3657c22ba8157d4b8f913cd4c950de5e7e", size = 9732138, upload-time = "2026-01-21T15:51:07.569Z" }, - { url = "https://files.pythonhosted.org/packages/7b/bf/bd1335c3bf1770b6d8fed2799993b11c4971af93bb1b729b9ebbc02ca2ec/pandas-3.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:177d9df10b3f43b70307a149d7ec49a1229a653f907aa60a48f1877d0e6be3be", size = 9033568, upload-time = "2026-01-21T15:51:09.484Z" }, - { url = "https://files.pythonhosted.org/packages/8e/c6/f5e2171914d5e29b9171d495344097d54e3ffe41d2d85d8115baba4dc483/pandas-3.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2713810ad3806767b89ad3b7b69ba153e1c6ff6d9c20f9c2140379b2a98b6c98", size = 10741936, upload-time = "2026-01-21T15:51:11.693Z" }, - { url = "https://files.pythonhosted.org/packages/51/88/9a0164f99510a1acb9f548691f022c756c2314aad0d8330a24616c14c462/pandas-3.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:15d59f885ee5011daf8335dff47dcb8a912a27b4ad7826dc6cbe809fd145d327", size = 10393884, upload-time = "2026-01-21T15:51:14.197Z" }, - { url = "https://files.pythonhosted.org/packages/e0/53/b34d78084d88d8ae2b848591229da8826d1e65aacf00b3abe34023467648/pandas-3.0.0-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:24e6547fb64d2c92665dd2adbfa4e85fa4fd70a9c070e7cfb03b629a0bbab5eb", size = 10310740, upload-time = "2026-01-21T15:51:16.093Z" }, - { url = "https://files.pythonhosted.org/packages/5b/d3/bee792e7c3d6930b74468d990604325701412e55d7aaf47460a22311d1a5/pandas-3.0.0-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:48ee04b90e2505c693d3f8e8f524dab8cb8aaf7ddcab52c92afa535e717c4812", size = 10700014, upload-time = "2026-01-21T15:51:18.818Z" }, - { url = "https://files.pythonhosted.org/packages/55/db/2570bc40fb13aaed1cbc3fbd725c3a60ee162477982123c3adc8971e7ac1/pandas-3.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:66f72fb172959af42a459e27a8d8d2c7e311ff4c1f7db6deb3b643dbc382ae08", size = 11323737, upload-time = "2026-01-21T15:51:20.784Z" }, - { url = "https://files.pythonhosted.org/packages/bc/2e/297ac7f21c8181b62a4cccebad0a70caf679adf3ae5e83cb676194c8acc3/pandas-3.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4a4a400ca18230976724a5066f20878af785f36c6756e498e94c2a5e5d57779c", size = 11771558, upload-time = "2026-01-21T15:51:22.977Z" }, - { url = "https://files.pythonhosted.org/packages/0a/46/e1c6876d71c14332be70239acce9ad435975a80541086e5ffba2f249bcf6/pandas-3.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:940eebffe55528074341a5a36515f3e4c5e25e958ebbc764c9502cfc35ba3faa", size = 10473771, upload-time = "2026-01-21T15:51:25.285Z" }, - { url = "https://files.pythonhosted.org/packages/c0/db/0270ad9d13c344b7a36fa77f5f8344a46501abf413803e885d22864d10bf/pandas-3.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:597c08fb9fef0edf1e4fa2f9828dd27f3d78f9b8c9b4a748d435ffc55732310b", size = 10312075, upload-time = "2026-01-21T15:51:28.5Z" }, - { url = "https://files.pythonhosted.org/packages/09/9f/c176f5e9717f7c91becfe0f55a52ae445d3f7326b4a2cf355978c51b7913/pandas-3.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:447b2d68ac5edcbf94655fe909113a6dba6ef09ad7f9f60c80477825b6c489fe", size = 9900213, upload-time = "2026-01-21T15:51:30.955Z" }, - { url = "https://files.pythonhosted.org/packages/d9/e7/63ad4cc10b257b143e0a5ebb04304ad806b4e1a61c5da25f55896d2ca0f4/pandas-3.0.0-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:debb95c77ff3ed3ba0d9aa20c3a2f19165cc7956362f9873fce1ba0a53819d70", size = 10428768, upload-time = "2026-01-21T15:51:33.018Z" }, - { url = "https://files.pythonhosted.org/packages/9e/0e/4e4c2d8210f20149fd2248ef3fff26623604922bd564d915f935a06dd63d/pandas-3.0.0-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fedabf175e7cd82b69b74c30adbaa616de301291a5231138d7242596fc296a8d", size = 10882954, upload-time = "2026-01-21T15:51:35.287Z" }, - { url = "https://files.pythonhosted.org/packages/c6/60/c9de8ac906ba1f4d2250f8a951abe5135b404227a55858a75ad26f84db47/pandas-3.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:412d1a89aab46889f3033a386912efcdfa0f1131c5705ff5b668dda88305e986", size = 11430293, upload-time = "2026-01-21T15:51:37.57Z" }, - { url = "https://files.pythonhosted.org/packages/a1/69/806e6637c70920e5787a6d6896fd707f8134c2c55cd761e7249a97b7dc5a/pandas-3.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e979d22316f9350c516479dd3a92252be2937a9531ed3a26ec324198a99cdd49", size = 11952452, upload-time = "2026-01-21T15:51:39.618Z" }, - { url = "https://files.pythonhosted.org/packages/cb/de/918621e46af55164c400ab0ef389c9d969ab85a43d59ad1207d4ddbe30a5/pandas-3.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:083b11415b9970b6e7888800c43c82e81a06cd6b06755d84804444f0007d6bb7", size = 9851081, upload-time = "2026-01-21T15:51:41.758Z" }, - { url = "https://files.pythonhosted.org/packages/91/a1/3562a18dd0bd8c73344bfa26ff90c53c72f827df119d6d6b1dacc84d13e3/pandas-3.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:5db1e62cb99e739fa78a28047e861b256d17f88463c76b8dafc7c1338086dca8", size = 9174610, upload-time = "2026-01-21T15:51:44.312Z" }, - { url = "https://files.pythonhosted.org/packages/ce/26/430d91257eaf366f1737d7a1c158677caaf6267f338ec74e3a1ec444111c/pandas-3.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:697b8f7d346c68274b1b93a170a70974cdc7d7354429894d5927c1effdcccd73", size = 10761999, upload-time = "2026-01-21T15:51:46.899Z" }, - { url = "https://files.pythonhosted.org/packages/ec/1a/954eb47736c2b7f7fe6a9d56b0cb6987773c00faa3c6451a43db4beb3254/pandas-3.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8cb3120f0d9467ed95e77f67a75e030b67545bcfa08964e349252d674171def2", size = 10410279, upload-time = "2026-01-21T15:51:48.89Z" }, - { url = "https://files.pythonhosted.org/packages/20/fc/b96f3a5a28b250cd1b366eb0108df2501c0f38314a00847242abab71bb3a/pandas-3.0.0-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:33fd3e6baa72899746b820c31e4b9688c8e1b7864d7aec2de7ab5035c285277a", size = 10330198, upload-time = "2026-01-21T15:51:51.015Z" }, - { url = "https://files.pythonhosted.org/packages/90/b3/d0e2952f103b4fbef1ef22d0c2e314e74fc9064b51cee30890b5e3286ee6/pandas-3.0.0-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8942e333dc67ceda1095227ad0febb05a3b36535e520154085db632c40ad084", size = 10728513, upload-time = "2026-01-21T15:51:53.387Z" }, - { url = "https://files.pythonhosted.org/packages/76/81/832894f286df828993dc5fd61c63b231b0fb73377e99f6c6c369174cf97e/pandas-3.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:783ac35c4d0fe0effdb0d67161859078618b1b6587a1af15928137525217a721", size = 11345550, upload-time = "2026-01-21T15:51:55.329Z" }, - { url = "https://files.pythonhosted.org/packages/34/a0/ed160a00fb4f37d806406bc0a79a8b62fe67f29d00950f8d16203ff3409b/pandas-3.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:125eb901e233f155b268bbef9abd9afb5819db74f0e677e89a61b246228c71ac", size = 11799386, upload-time = "2026-01-21T15:51:57.457Z" }, - { url = "https://files.pythonhosted.org/packages/36/c8/2ac00d7255252c5e3cf61b35ca92ca25704b0188f7454ca4aec08a33cece/pandas-3.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b86d113b6c109df3ce0ad5abbc259fe86a1bd4adfd4a31a89da42f84f65509bb", size = 10873041, upload-time = "2026-01-21T15:52:00.034Z" }, - { url = "https://files.pythonhosted.org/packages/e6/3f/a80ac00acbc6b35166b42850e98a4f466e2c0d9c64054161ba9620f95680/pandas-3.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:1c39eab3ad38f2d7a249095f0a3d8f8c22cc0f847e98ccf5bbe732b272e2d9fa", size = 9441003, upload-time = "2026-01-21T15:52:02.281Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/da/99/b342345300f13440fe9fe385c3c481e2d9a595ee3bab4d3219247ac94e9a/pandas-3.0.2.tar.gz", hash = "sha256:f4753e73e34c8d83221ba58f232433fca2748be8b18dbca02d242ed153945043", size = 4645855, upload-time = "2026-03-31T06:48:30.816Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/97/35/6411db530c618e0e0005187e35aa02ce60ae4c4c4d206964a2f978217c27/pandas-3.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a727a73cbdba2f7458dc82449e2315899d5140b449015d822f515749a46cbbe0", size = 10326926, upload-time = "2026-03-31T06:46:08.29Z" }, + { url = "https://files.pythonhosted.org/packages/c4/d3/b7da1d5d7dbdc5ef52ed7debd2b484313b832982266905315dad5a0bf0b1/pandas-3.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dbbd4aa20ca51e63b53bbde6a0fa4254b1aaabb74d2f542df7a7959feb1d760c", size = 9926987, upload-time = "2026-03-31T06:46:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/52/77/9b1c2d6070b5dbe239a7bc889e21bfa58720793fb902d1e070695d87c6d0/pandas-3.0.2-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:339dda302bd8369dedeae979cb750e484d549b563c3f54f3922cb8ff4978c5eb", size = 10757067, upload-time = "2026-03-31T06:46:14.903Z" }, + { url = "https://files.pythonhosted.org/packages/20/17/ec40d981705654853726e7ac9aea9ddbb4a5d9cf54d8472222f4f3de06c2/pandas-3.0.2-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:61c2fd96d72b983a9891b2598f286befd4ad262161a609c92dc1652544b46b76", size = 11258787, upload-time = "2026-03-31T06:46:17.683Z" }, + { url = "https://files.pythonhosted.org/packages/90/e3/3f1126d43d3702ca8773871a81c9f15122a1f412342cc56284ffda5b1f70/pandas-3.0.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c934008c733b8bbea273ea308b73b3156f0181e5b72960790b09c18a2794fe1e", size = 11771616, upload-time = "2026-03-31T06:46:20.532Z" }, + { url = "https://files.pythonhosted.org/packages/2e/cf/0f4e268e1f5062e44a6bda9f925806721cd4c95c2b808a4c82ebe914f96b/pandas-3.0.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:60a80bb4feacbef5e1447a3f82c33209c8b7e07f28d805cfd1fb951e5cb443aa", size = 12337623, upload-time = "2026-03-31T06:46:23.754Z" }, + { url = "https://files.pythonhosted.org/packages/44/a0/97a6339859d4acb2536efb24feb6708e82f7d33b2ed7e036f2983fcced82/pandas-3.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:ed72cb3f45190874eb579c64fa92d9df74e98fd63e2be7f62bce5ace0ade61df", size = 9897372, upload-time = "2026-03-31T06:46:26.703Z" }, + { url = "https://files.pythonhosted.org/packages/8f/eb/781516b808a99ddf288143cec46b342b3016c3414d137da1fdc3290d8860/pandas-3.0.2-cp311-cp311-win_arm64.whl", hash = "sha256:f12b1a9e332c01e09510586f8ca9b108fd631fd656af82e452d7315ef6df5f9f", size = 9154922, upload-time = "2026-03-31T06:46:30.284Z" }, + { url = "https://files.pythonhosted.org/packages/f3/b0/c20bd4d6d3f736e6bd6b55794e9cd0a617b858eaad27c8f410ea05d953b7/pandas-3.0.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:232a70ebb568c0c4d2db4584f338c1577d81e3af63292208d615907b698a0f18", size = 10347921, upload-time = "2026-03-31T06:46:33.36Z" }, + { url = "https://files.pythonhosted.org/packages/35/d0/4831af68ce30cc2d03c697bea8450e3225a835ef497d0d70f31b8cdde965/pandas-3.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:970762605cff1ca0d3f71ed4f3a769ea8f85fc8e6348f6e110b8fea7e6eb5a14", size = 9888127, upload-time = "2026-03-31T06:46:36.253Z" }, + { url = "https://files.pythonhosted.org/packages/61/a9/16ea9346e1fc4a96e2896242d9bc674764fb9049b0044c0132502f7a771e/pandas-3.0.2-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aff4e6f4d722e0652707d7bcb190c445fe58428500c6d16005b02401764b1b3d", size = 10399577, upload-time = "2026-03-31T06:46:39.224Z" }, + { url = "https://files.pythonhosted.org/packages/c4/a8/3a61a721472959ab0ce865ef05d10b0d6bfe27ce8801c99f33d4fa996e65/pandas-3.0.2-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ef8b27695c3d3dc78403c9a7d5e59a62d5464a7e1123b4e0042763f7104dc74f", size = 10880030, upload-time = "2026-03-31T06:46:42.412Z" }, + { url = "https://files.pythonhosted.org/packages/da/65/7225c0ea4d6ce9cb2160a7fb7f39804871049f016e74782e5dade4d14109/pandas-3.0.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f8d68083e49e16b84734eb1a4dcae4259a75c90fb6e2251ab9a00b61120c06ab", size = 11409468, upload-time = "2026-03-31T06:46:45.2Z" }, + { url = "https://files.pythonhosted.org/packages/fa/5b/46e7c76032639f2132359b5cf4c785dd8cf9aea5ea64699eac752f02b9db/pandas-3.0.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:32cc41f310ebd4a296d93515fcac312216adfedb1894e879303987b8f1e2b97d", size = 11936381, upload-time = "2026-03-31T06:46:48.293Z" }, + { url = "https://files.pythonhosted.org/packages/7b/8b/721a9cff6fa6a91b162eb51019c6243b82b3226c71bb6c8ef4a9bd65cbc6/pandas-3.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:a4785e1d6547d8427c5208b748ae2efb64659a21bd82bf440d4262d02bfa02a4", size = 9744993, upload-time = "2026-03-31T06:46:51.488Z" }, + { url = "https://files.pythonhosted.org/packages/d5/18/7f0bd34ae27b28159aa80f2a6799f47fda34f7fb938a76e20c7b7fe3b200/pandas-3.0.2-cp312-cp312-win_arm64.whl", hash = "sha256:08504503f7101300107ecdc8df73658e4347586db5cfdadabc1592e9d7e7a0fd", size = 9056118, upload-time = "2026-03-31T06:46:54.548Z" }, + { url = "https://files.pythonhosted.org/packages/bf/ca/3e639a1ea6fcd0617ca4e8ca45f62a74de33a56ae6cd552735470b22c8d3/pandas-3.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:b5918ba197c951dec132b0c5929a00c0bf05d5942f590d3c10a807f6e15a57d3", size = 10321105, upload-time = "2026-03-31T06:46:57.327Z" }, + { url = "https://files.pythonhosted.org/packages/0b/77/dbc82ff2fb0e63c6564356682bf201edff0ba16c98630d21a1fb312a8182/pandas-3.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:d606a041c89c0a474a4702d532ab7e73a14fe35c8d427b972a625c8e46373668", size = 9864088, upload-time = "2026-03-31T06:46:59.935Z" }, + { url = "https://files.pythonhosted.org/packages/5c/2b/341f1b04bbca2e17e13cd3f08c215b70ef2c60c5356ef1e8c6857449edc7/pandas-3.0.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:710246ba0616e86891b58ab95f2495143bb2bc83ab6b06747c74216f583a6ac9", size = 10369066, upload-time = "2026-03-31T06:47:02.792Z" }, + { url = "https://files.pythonhosted.org/packages/12/c5/cbb1ffefb20a93d3f0e1fdcda699fb84976210d411b008f97f48bf6ce27e/pandas-3.0.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5d3cfe227c725b1f3dff4278b43d8c784656a42a9325b63af6b1492a8232209e", size = 10876780, upload-time = "2026-03-31T06:47:06.205Z" }, + { url = "https://files.pythonhosted.org/packages/98/fe/2249ae5e0a69bd0ddf17353d0a5d26611d70970111f5b3600cdc8be883e7/pandas-3.0.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:c3b723df9087a9a9a840e263ebd9f88b64a12075d1bf2ea401a5a42f254f084d", size = 11375181, upload-time = "2026-03-31T06:47:09.383Z" }, + { url = "https://files.pythonhosted.org/packages/de/64/77a38b09e70b6464883b8d7584ab543e748e42c1b5d337a2ee088e0df741/pandas-3.0.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a3096110bf9eac0070b7208465f2740e2d8a670d5cb6530b5bb884eca495fd39", size = 11928899, upload-time = "2026-03-31T06:47:12.686Z" }, + { url = "https://files.pythonhosted.org/packages/5e/52/42855bf626868413f761addd574acc6195880ae247a5346477a4361c3acb/pandas-3.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:07a10f5c36512eead51bc578eb3354ad17578b22c013d89a796ab5eee90cd991", size = 9746574, upload-time = "2026-03-31T06:47:15.64Z" }, + { url = "https://files.pythonhosted.org/packages/88/39/21304ae06a25e8bf9fc820d69b29b2c495b2ae580d1e143146c309941760/pandas-3.0.2-cp313-cp313-win_arm64.whl", hash = "sha256:5fdbfa05931071aba28b408e59226186b01eb5e92bea2ab78b65863ca3228d84", size = 9047156, upload-time = "2026-03-31T06:47:18.595Z" }, + { url = "https://files.pythonhosted.org/packages/72/20/7defa8b27d4f330a903bb68eea33be07d839c5ea6bdda54174efcec0e1d2/pandas-3.0.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:dbc20dea3b9e27d0e66d74c42b2d0c1bed9c2ffe92adea33633e3bedeb5ac235", size = 10756238, upload-time = "2026-03-31T06:47:22.012Z" }, + { url = "https://files.pythonhosted.org/packages/e9/95/49433c14862c636afc0e9b2db83ff16b3ad92959364e52b2955e44c8e94c/pandas-3.0.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:b75c347eff42497452116ce05ef461822d97ce5b9ff8df6edacb8076092c855d", size = 10408520, upload-time = "2026-03-31T06:47:25.197Z" }, + { url = "https://files.pythonhosted.org/packages/3b/f8/462ad2b5881d6b8ec8e5f7ed2ea1893faa02290d13870a1600fe72ad8efc/pandas-3.0.2-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d1478075142e83a5571782ad007fb201ed074bdeac7ebcc8890c71442e96adf7", size = 10324154, upload-time = "2026-03-31T06:47:28.097Z" }, + { url = "https://files.pythonhosted.org/packages/0a/65/d1e69b649cbcddda23ad6e4c40ef935340f6f652a006e5cbc3555ac8adb3/pandas-3.0.2-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5880314e69e763d4c8b27937090de570f1fb8d027059a7ada3f7f8e98bdcb677", size = 10714449, upload-time = "2026-03-31T06:47:30.85Z" }, + { url = "https://files.pythonhosted.org/packages/47/a4/85b59bc65b8190ea3689882db6cdf32a5003c0ccd5a586c30fdcc3ffc4fc/pandas-3.0.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:b5329e26898896f06035241a626d7c335daa479b9bbc82be7c2742d048e41172", size = 11338475, upload-time = "2026-03-31T06:47:34.026Z" }, + { url = "https://files.pythonhosted.org/packages/1e/c4/bc6966c6e38e5d9478b935272d124d80a589511ed1612a5d21d36f664c68/pandas-3.0.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:81526c4afd31971f8b62671442a4b2b51e0aa9acc3819c9f0f12a28b6fcf85f1", size = 11786568, upload-time = "2026-03-31T06:47:36.941Z" }, + { url = "https://files.pythonhosted.org/packages/e8/74/09298ca9740beed1d3504e073d67e128aa07e5ca5ca2824b0c674c0b8676/pandas-3.0.2-cp313-cp313t-win_amd64.whl", hash = "sha256:7cadd7e9a44ec13b621aec60f9150e744cfc7a3dd32924a7e2f45edff31823b0", size = 10488652, upload-time = "2026-03-31T06:47:40.612Z" }, + { url = "https://files.pythonhosted.org/packages/bb/40/c6ea527147c73b24fc15c891c3fcffe9c019793119c5742b8784a062c7db/pandas-3.0.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:db0dbfd2a6cdf3770aa60464d50333d8f3d9165b2f2671bcc299b72de5a6677b", size = 10326084, upload-time = "2026-03-31T06:47:43.834Z" }, + { url = "https://files.pythonhosted.org/packages/95/25/bdb9326c3b5455f8d4d3549fce7abcf967259de146fe2cf7a82368141948/pandas-3.0.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:0555c5882688a39317179ab4a0ed41d3ebc8812ab14c69364bbee8fb7a3f6288", size = 9914146, upload-time = "2026-03-31T06:47:46.67Z" }, + { url = "https://files.pythonhosted.org/packages/8d/77/3a227ff3337aa376c60d288e1d61c5d097131d0ac71f954d90a8f369e422/pandas-3.0.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:01f31a546acd5574ef77fe199bc90b55527c225c20ccda6601cf6b0fd5ed597c", size = 10444081, upload-time = "2026-03-31T06:47:49.681Z" }, + { url = "https://files.pythonhosted.org/packages/15/88/3cdd54fa279341afa10acf8d2b503556b1375245dccc9315659f795dd2e9/pandas-3.0.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:deeca1b5a931fdf0c2212c8a659ade6d3b1edc21f0914ce71ef24456ca7a6535", size = 10897535, upload-time = "2026-03-31T06:47:53.033Z" }, + { url = "https://files.pythonhosted.org/packages/06/9d/98cc7a7624f7932e40f434299260e2917b090a579d75937cb8a57b9d2de3/pandas-3.0.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:0f48afd9bb13300ffb5a3316973324c787054ba6665cda0da3fbd67f451995db", size = 11446992, upload-time = "2026-03-31T06:47:56.193Z" }, + { url = "https://files.pythonhosted.org/packages/9a/cd/19ff605cc3760e80602e6826ddef2824d8e7050ed80f2e11c4b079741dc3/pandas-3.0.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6c4d8458b97a35717b62469a4ea0e85abd5ed8687277f5ccfc67f8a5126f8c53", size = 11968257, upload-time = "2026-03-31T06:47:59.137Z" }, + { url = "https://files.pythonhosted.org/packages/db/60/aba6a38de456e7341285102bede27514795c1eaa353bc0e7638b6b785356/pandas-3.0.2-cp314-cp314-win_amd64.whl", hash = "sha256:b35d14bb5d8285d9494fe93815a9e9307c0876e10f1e8e89ac5b88f728ec8dcf", size = 9865893, upload-time = "2026-03-31T06:48:02.038Z" }, + { url = "https://files.pythonhosted.org/packages/08/71/e5ec979dd2e8a093dacb8864598c0ff59a0cee0bbcdc0bfec16a51684d4f/pandas-3.0.2-cp314-cp314-win_arm64.whl", hash = "sha256:63d141b56ef686f7f0d714cfb8de4e320475b86bf4b620aa0b7da89af8cbdbbb", size = 9188644, upload-time = "2026-03-31T06:48:05.045Z" }, + { url = "https://files.pythonhosted.org/packages/f1/6c/7b45d85db19cae1eb524f2418ceaa9d85965dcf7b764ed151386b7c540f0/pandas-3.0.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:140f0cffb1fa2524e874dde5b477d9defe10780d8e9e220d259b2c0874c89d9d", size = 10776246, upload-time = "2026-03-31T06:48:07.789Z" }, + { url = "https://files.pythonhosted.org/packages/a8/3e/7b00648b086c106e81766f25322b48aa8dfa95b55e621dbdf2fdd413a117/pandas-3.0.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ae37e833ff4fed0ba352f6bdd8b73ba3ab3256a85e54edfd1ab51ae40cca0af8", size = 10424801, upload-time = "2026-03-31T06:48:10.897Z" }, + { url = "https://files.pythonhosted.org/packages/da/6e/558dd09a71b53b4008e7fc8a98ec6d447e9bfb63cdaeea10e5eb9b2dabe8/pandas-3.0.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4d888a5c678a419a5bb41a2a93818e8ed9fd3172246555c0b37b7cc27027effd", size = 10345643, upload-time = "2026-03-31T06:48:13.7Z" }, + { url = "https://files.pythonhosted.org/packages/be/e3/921c93b4d9a280409451dc8d07b062b503bbec0531d2627e73a756e99a82/pandas-3.0.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b444dc64c079e84df91baa8bf613d58405645461cabca929d9178f2cd392398d", size = 10743641, upload-time = "2026-03-31T06:48:16.659Z" }, + { url = "https://files.pythonhosted.org/packages/56/ca/fd17286f24fa3b4d067965d8d5d7e14fe557dd4f979a0b068ac0deaf8228/pandas-3.0.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:4544c7a54920de8eeacaa1466a6b7268ecfbc9bc64ab4dbb89c6bbe94d5e0660", size = 11361993, upload-time = "2026-03-31T06:48:19.475Z" }, + { url = "https://files.pythonhosted.org/packages/e4/a5/2f6ed612056819de445a433ca1f2821ac3dab7f150d569a59e9cc105de1d/pandas-3.0.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:734be7551687c00fbd760dc0522ed974f82ad230d4a10f54bf51b80d44a08702", size = 11815274, upload-time = "2026-03-31T06:48:22.695Z" }, + { url = "https://files.pythonhosted.org/packages/00/2f/b622683e99ec3ce00b0854bac9e80868592c5b051733f2cf3a868e5fea26/pandas-3.0.2-cp314-cp314t-win_amd64.whl", hash = "sha256:57a07209bebcbcf768d2d13c9b78b852f9a15978dac41b9e6421a81ad4cdd276", size = 10888530, upload-time = "2026-03-31T06:48:25.806Z" }, + { url = "https://files.pythonhosted.org/packages/cb/2b/f8434233fab2bd66a02ec014febe4e5adced20e2693e0e90a07d118ed30e/pandas-3.0.2-cp314-cp314t-win_arm64.whl", hash = "sha256:5371b72c2d4d415d08765f32d689217a43227484e81b2305b52076e328f6f482", size = 9455341, upload-time = "2026-03-31T06:48:28.418Z" }, +] + +[[package]] +name = "pdoc" +version = "16.0.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +dependencies = [ + { name = "jinja2" }, + { name = "markdown2" }, + { name = "markupsafe" }, + { name = "pygments" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/ac/fe/ab3f34a5fb08c6b698439a2c2643caf8fef0d61a86dd3fdcd5501c670ab8/pdoc-16.0.0.tar.gz", hash = "sha256:fdadc40cc717ec53919e3cd720390d4e3bcd40405cb51c4918c119447f913514", size = 111890, upload-time = "2025-10-27T16:02:16.345Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/16/a1/56a17b7f9e18c2bb8df73f3833345d97083b344708b97bab148fdd7e0b82/pdoc-16.0.0-py3-none-any.whl", hash = "sha256:070b51de2743b9b1a4e0ab193a06c9e6c12cf4151cf9137656eebb16e8556628", size = 100014, upload-time = "2025-10-27T16:02:15.007Z" }, ] [[package]] name = "pillow" -version = "12.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5a/b0/cace85a1b0c9775a9f8f5d5423c8261c858760e2466c79b2dd184638b056/pillow-12.0.0.tar.gz", hash = "sha256:87d4f8125c9988bfbed67af47dd7a953e2fc7b0cc1e7800ec6d2080d490bb353", size = 47008828, upload-time = "2025-10-15T18:24:14.008Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0e/5a/a2f6773b64edb921a756eb0729068acad9fc5208a53f4a349396e9436721/pillow-12.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0fd00cac9c03256c8b2ff58f162ebcd2587ad3e1f2e397eab718c47e24d231cc", size = 5289798, upload-time = "2025-10-15T18:21:47.763Z" }, - { url = "https://files.pythonhosted.org/packages/2e/05/069b1f8a2e4b5a37493da6c5868531c3f77b85e716ad7a590ef87d58730d/pillow-12.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a3475b96f5908b3b16c47533daaa87380c491357d197564e0ba34ae75c0f3257", size = 4650589, upload-time = "2025-10-15T18:21:49.515Z" }, - { url = "https://files.pythonhosted.org/packages/61/e3/2c820d6e9a36432503ead175ae294f96861b07600a7156154a086ba7111a/pillow-12.0.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:110486b79f2d112cf6add83b28b627e369219388f64ef2f960fef9ebaf54c642", size = 6230472, upload-time = "2025-10-15T18:21:51.052Z" }, - { url = "https://files.pythonhosted.org/packages/4f/89/63427f51c64209c5e23d4d52071c8d0f21024d3a8a487737caaf614a5795/pillow-12.0.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5269cc1caeedb67e6f7269a42014f381f45e2e7cd42d834ede3c703a1d915fe3", size = 8033887, upload-time = "2025-10-15T18:21:52.604Z" }, - { url = "https://files.pythonhosted.org/packages/f6/1b/c9711318d4901093c15840f268ad649459cd81984c9ec9887756cca049a5/pillow-12.0.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:aa5129de4e174daccbc59d0a3b6d20eaf24417d59851c07ebb37aeb02947987c", size = 6343964, upload-time = "2025-10-15T18:21:54.619Z" }, - { url = "https://files.pythonhosted.org/packages/41/1e/db9470f2d030b4995083044cd8738cdd1bf773106819f6d8ba12597d5352/pillow-12.0.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bee2a6db3a7242ea309aa7ee8e2780726fed67ff4e5b40169f2c940e7eb09227", size = 7034756, upload-time = "2025-10-15T18:21:56.151Z" }, - { url = "https://files.pythonhosted.org/packages/cc/b0/6177a8bdd5ee4ed87cba2de5a3cc1db55ffbbec6176784ce5bb75aa96798/pillow-12.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:90387104ee8400a7b4598253b4c406f8958f59fcf983a6cea2b50d59f7d63d0b", size = 6458075, upload-time = "2025-10-15T18:21:57.759Z" }, - { url = "https://files.pythonhosted.org/packages/bc/5e/61537aa6fa977922c6a03253a0e727e6e4a72381a80d63ad8eec350684f2/pillow-12.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:bc91a56697869546d1b8f0a3ff35224557ae7f881050e99f615e0119bf934b4e", size = 7125955, upload-time = "2025-10-15T18:21:59.372Z" }, - { url = "https://files.pythonhosted.org/packages/1f/3d/d5033539344ee3cbd9a4d69e12e63ca3a44a739eb2d4c8da350a3d38edd7/pillow-12.0.0-cp311-cp311-win32.whl", hash = "sha256:27f95b12453d165099c84f8a8bfdfd46b9e4bda9e0e4b65f0635430027f55739", size = 6298440, upload-time = "2025-10-15T18:22:00.982Z" }, - { url = "https://files.pythonhosted.org/packages/4d/42/aaca386de5cc8bd8a0254516957c1f265e3521c91515b16e286c662854c4/pillow-12.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:b583dc9070312190192631373c6c8ed277254aa6e6084b74bdd0a6d3b221608e", size = 6999256, upload-time = "2025-10-15T18:22:02.617Z" }, - { url = "https://files.pythonhosted.org/packages/ba/f1/9197c9c2d5708b785f631a6dfbfa8eb3fb9672837cb92ae9af812c13b4ed/pillow-12.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:759de84a33be3b178a64c8ba28ad5c135900359e85fb662bc6e403ad4407791d", size = 2436025, upload-time = "2025-10-15T18:22:04.598Z" }, - { url = "https://files.pythonhosted.org/packages/2c/90/4fcce2c22caf044e660a198d740e7fbc14395619e3cb1abad12192c0826c/pillow-12.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:53561a4ddc36facb432fae7a9d8afbfaf94795414f5cdc5fc52f28c1dca90371", size = 5249377, upload-time = "2025-10-15T18:22:05.993Z" }, - { url = "https://files.pythonhosted.org/packages/fd/e0/ed960067543d080691d47d6938ebccbf3976a931c9567ab2fbfab983a5dd/pillow-12.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:71db6b4c1653045dacc1585c1b0d184004f0d7e694c7b34ac165ca70c0838082", size = 4650343, upload-time = "2025-10-15T18:22:07.718Z" }, - { url = "https://files.pythonhosted.org/packages/e7/a1/f81fdeddcb99c044bf7d6faa47e12850f13cee0849537a7d27eeab5534d4/pillow-12.0.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2fa5f0b6716fc88f11380b88b31fe591a06c6315e955c096c35715788b339e3f", size = 6232981, upload-time = "2025-10-15T18:22:09.287Z" }, - { url = "https://files.pythonhosted.org/packages/88/e1/9098d3ce341a8750b55b0e00c03f1630d6178f38ac191c81c97a3b047b44/pillow-12.0.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:82240051c6ca513c616f7f9da06e871f61bfd7805f566275841af15015b8f98d", size = 8041399, upload-time = "2025-10-15T18:22:10.872Z" }, - { url = "https://files.pythonhosted.org/packages/a7/62/a22e8d3b602ae8cc01446d0c57a54e982737f44b6f2e1e019a925143771d/pillow-12.0.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:55f818bd74fe2f11d4d7cbc65880a843c4075e0ac7226bc1a23261dbea531953", size = 6347740, upload-time = "2025-10-15T18:22:12.769Z" }, - { url = "https://files.pythonhosted.org/packages/4f/87/424511bdcd02c8d7acf9f65caa09f291a519b16bd83c3fb3374b3d4ae951/pillow-12.0.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b87843e225e74576437fd5b6a4c2205d422754f84a06942cfaf1dc32243e45a8", size = 7040201, upload-time = "2025-10-15T18:22:14.813Z" }, - { url = "https://files.pythonhosted.org/packages/dc/4d/435c8ac688c54d11755aedfdd9f29c9eeddf68d150fe42d1d3dbd2365149/pillow-12.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:c607c90ba67533e1b2355b821fef6764d1dd2cbe26b8c1005ae84f7aea25ff79", size = 6462334, upload-time = "2025-10-15T18:22:16.375Z" }, - { url = "https://files.pythonhosted.org/packages/2b/f2/ad34167a8059a59b8ad10bc5c72d4d9b35acc6b7c0877af8ac885b5f2044/pillow-12.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:21f241bdd5080a15bc86d3466a9f6074a9c2c2b314100dd896ac81ee6db2f1ba", size = 7134162, upload-time = "2025-10-15T18:22:17.996Z" }, - { url = "https://files.pythonhosted.org/packages/0c/b1/a7391df6adacf0a5c2cf6ac1cf1fcc1369e7d439d28f637a847f8803beb3/pillow-12.0.0-cp312-cp312-win32.whl", hash = "sha256:dd333073e0cacdc3089525c7df7d39b211bcdf31fc2824e49d01c6b6187b07d0", size = 6298769, upload-time = "2025-10-15T18:22:19.923Z" }, - { url = "https://files.pythonhosted.org/packages/a2/0b/d87733741526541c909bbf159e338dcace4f982daac6e5a8d6be225ca32d/pillow-12.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:9fe611163f6303d1619bbcb653540a4d60f9e55e622d60a3108be0d5b441017a", size = 7001107, upload-time = "2025-10-15T18:22:21.644Z" }, - { url = "https://files.pythonhosted.org/packages/bc/96/aaa61ce33cc98421fb6088af2a03be4157b1e7e0e87087c888e2370a7f45/pillow-12.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:7dfb439562f234f7d57b1ac6bc8fe7f838a4bd49c79230e0f6a1da93e82f1fad", size = 2436012, upload-time = "2025-10-15T18:22:23.621Z" }, - { url = "https://files.pythonhosted.org/packages/62/f2/de993bb2d21b33a98d031ecf6a978e4b61da207bef02f7b43093774c480d/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:0869154a2d0546545cde61d1789a6524319fc1897d9ee31218eae7a60ccc5643", size = 4045493, upload-time = "2025-10-15T18:22:25.758Z" }, - { url = "https://files.pythonhosted.org/packages/0e/b6/bc8d0c4c9f6f111a783d045310945deb769b806d7574764234ffd50bc5ea/pillow-12.0.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a7921c5a6d31b3d756ec980f2f47c0cfdbce0fc48c22a39347a895f41f4a6ea4", size = 4120461, upload-time = "2025-10-15T18:22:27.286Z" }, - { url = "https://files.pythonhosted.org/packages/5d/57/d60d343709366a353dc56adb4ee1e7d8a2cc34e3fbc22905f4167cfec119/pillow-12.0.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:1ee80a59f6ce048ae13cda1abf7fbd2a34ab9ee7d401c46be3ca685d1999a399", size = 3576912, upload-time = "2025-10-15T18:22:28.751Z" }, - { url = "https://files.pythonhosted.org/packages/a4/a4/a0a31467e3f83b94d37568294b01d22b43ae3c5d85f2811769b9c66389dd/pillow-12.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c50f36a62a22d350c96e49ad02d0da41dbd17ddc2e29750dbdba4323f85eb4a5", size = 5249132, upload-time = "2025-10-15T18:22:30.641Z" }, - { url = "https://files.pythonhosted.org/packages/83/06/48eab21dd561de2914242711434c0c0eb992ed08ff3f6107a5f44527f5e9/pillow-12.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5193fde9a5f23c331ea26d0cf171fbf67e3f247585f50c08b3e205c7aeb4589b", size = 4650099, upload-time = "2025-10-15T18:22:32.73Z" }, - { url = "https://files.pythonhosted.org/packages/fc/bd/69ed99fd46a8dba7c1887156d3572fe4484e3f031405fcc5a92e31c04035/pillow-12.0.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:bde737cff1a975b70652b62d626f7785e0480918dece11e8fef3c0cf057351c3", size = 6230808, upload-time = "2025-10-15T18:22:34.337Z" }, - { url = "https://files.pythonhosted.org/packages/ea/94/8fad659bcdbf86ed70099cb60ae40be6acca434bbc8c4c0d4ef356d7e0de/pillow-12.0.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:a6597ff2b61d121172f5844b53f21467f7082f5fb385a9a29c01414463f93b07", size = 8037804, upload-time = "2025-10-15T18:22:36.402Z" }, - { url = "https://files.pythonhosted.org/packages/20/39/c685d05c06deecfd4e2d1950e9a908aa2ca8bc4e6c3b12d93b9cafbd7837/pillow-12.0.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b817e7035ea7f6b942c13aa03bb554fc44fea70838ea21f8eb31c638326584e", size = 6345553, upload-time = "2025-10-15T18:22:38.066Z" }, - { url = "https://files.pythonhosted.org/packages/38/57/755dbd06530a27a5ed74f8cb0a7a44a21722ebf318edbe67ddbd7fb28f88/pillow-12.0.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f4f1231b7dec408e8670264ce63e9c71409d9583dd21d32c163e25213ee2a344", size = 7037729, upload-time = "2025-10-15T18:22:39.769Z" }, - { url = "https://files.pythonhosted.org/packages/ca/b6/7e94f4c41d238615674d06ed677c14883103dce1c52e4af16f000338cfd7/pillow-12.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e51b71417049ad6ab14c49608b4a24d8fb3fe605e5dfabfe523b58064dc3d27", size = 6459789, upload-time = "2025-10-15T18:22:41.437Z" }, - { url = "https://files.pythonhosted.org/packages/9c/14/4448bb0b5e0f22dd865290536d20ec8a23b64e2d04280b89139f09a36bb6/pillow-12.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:d120c38a42c234dc9a8c5de7ceaaf899cf33561956acb4941653f8bdc657aa79", size = 7130917, upload-time = "2025-10-15T18:22:43.152Z" }, - { url = "https://files.pythonhosted.org/packages/dd/ca/16c6926cc1c015845745d5c16c9358e24282f1e588237a4c36d2b30f182f/pillow-12.0.0-cp313-cp313-win32.whl", hash = "sha256:4cc6b3b2efff105c6a1656cfe59da4fdde2cda9af1c5e0b58529b24525d0a098", size = 6302391, upload-time = "2025-10-15T18:22:44.753Z" }, - { url = "https://files.pythonhosted.org/packages/6d/2a/dd43dcfd6dae9b6a49ee28a8eedb98c7d5ff2de94a5d834565164667b97b/pillow-12.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:4cf7fed4b4580601c4345ceb5d4cbf5a980d030fd5ad07c4d2ec589f95f09905", size = 7007477, upload-time = "2025-10-15T18:22:46.838Z" }, - { url = "https://files.pythonhosted.org/packages/77/f0/72ea067f4b5ae5ead653053212af05ce3705807906ba3f3e8f58ddf617e6/pillow-12.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:9f0b04c6b8584c2c193babcccc908b38ed29524b29dd464bc8801bf10d746a3a", size = 2435918, upload-time = "2025-10-15T18:22:48.399Z" }, - { url = "https://files.pythonhosted.org/packages/f5/5e/9046b423735c21f0487ea6cb5b10f89ea8f8dfbe32576fe052b5ba9d4e5b/pillow-12.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7fa22993bac7b77b78cae22bad1e2a987ddf0d9015c63358032f84a53f23cdc3", size = 5251406, upload-time = "2025-10-15T18:22:49.905Z" }, - { url = "https://files.pythonhosted.org/packages/12/66/982ceebcdb13c97270ef7a56c3969635b4ee7cd45227fa707c94719229c5/pillow-12.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f135c702ac42262573fe9714dfe99c944b4ba307af5eb507abef1667e2cbbced", size = 4653218, upload-time = "2025-10-15T18:22:51.587Z" }, - { url = "https://files.pythonhosted.org/packages/16/b3/81e625524688c31859450119bf12674619429cab3119eec0e30a7a1029cb/pillow-12.0.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:c85de1136429c524e55cfa4e033b4a7940ac5c8ee4d9401cc2d1bf48154bbc7b", size = 6266564, upload-time = "2025-10-15T18:22:53.215Z" }, - { url = "https://files.pythonhosted.org/packages/98/59/dfb38f2a41240d2408096e1a76c671d0a105a4a8471b1871c6902719450c/pillow-12.0.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:38df9b4bfd3db902c9c2bd369bcacaf9d935b2fff73709429d95cc41554f7b3d", size = 8069260, upload-time = "2025-10-15T18:22:54.933Z" }, - { url = "https://files.pythonhosted.org/packages/dc/3d/378dbea5cd1874b94c312425ca77b0f47776c78e0df2df751b820c8c1d6c/pillow-12.0.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7d87ef5795da03d742bf49439f9ca4d027cde49c82c5371ba52464aee266699a", size = 6379248, upload-time = "2025-10-15T18:22:56.605Z" }, - { url = "https://files.pythonhosted.org/packages/84/b0/d525ef47d71590f1621510327acec75ae58c721dc071b17d8d652ca494d8/pillow-12.0.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:aff9e4d82d082ff9513bdd6acd4f5bd359f5b2c870907d2b0a9c5e10d40c88fe", size = 7066043, upload-time = "2025-10-15T18:22:58.53Z" }, - { url = "https://files.pythonhosted.org/packages/61/2c/aced60e9cf9d0cde341d54bf7932c9ffc33ddb4a1595798b3a5150c7ec4e/pillow-12.0.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:8d8ca2b210ada074d57fcee40c30446c9562e542fc46aedc19baf758a93532ee", size = 6490915, upload-time = "2025-10-15T18:23:00.582Z" }, - { url = "https://files.pythonhosted.org/packages/ef/26/69dcb9b91f4e59f8f34b2332a4a0a951b44f547c4ed39d3e4dcfcff48f89/pillow-12.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:99a7f72fb6249302aa62245680754862a44179b545ded638cf1fef59befb57ef", size = 7157998, upload-time = "2025-10-15T18:23:02.627Z" }, - { url = "https://files.pythonhosted.org/packages/61/2b/726235842220ca95fa441ddf55dd2382b52ab5b8d9c0596fe6b3f23dafe8/pillow-12.0.0-cp313-cp313t-win32.whl", hash = "sha256:4078242472387600b2ce8d93ade8899c12bf33fa89e55ec89fe126e9d6d5d9e9", size = 6306201, upload-time = "2025-10-15T18:23:04.709Z" }, - { url = "https://files.pythonhosted.org/packages/c0/3d/2afaf4e840b2df71344ababf2f8edd75a705ce500e5dc1e7227808312ae1/pillow-12.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:2c54c1a783d6d60595d3514f0efe9b37c8808746a66920315bfd34a938d7994b", size = 7013165, upload-time = "2025-10-15T18:23:06.46Z" }, - { url = "https://files.pythonhosted.org/packages/6f/75/3fa09aa5cf6ed04bee3fa575798ddf1ce0bace8edb47249c798077a81f7f/pillow-12.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:26d9f7d2b604cd23aba3e9faf795787456ac25634d82cd060556998e39c6fa47", size = 2437834, upload-time = "2025-10-15T18:23:08.194Z" }, - { url = "https://files.pythonhosted.org/packages/54/2a/9a8c6ba2c2c07b71bec92cf63e03370ca5e5f5c5b119b742bcc0cde3f9c5/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:beeae3f27f62308f1ddbcfb0690bf44b10732f2ef43758f169d5e9303165d3f9", size = 4045531, upload-time = "2025-10-15T18:23:10.121Z" }, - { url = "https://files.pythonhosted.org/packages/84/54/836fdbf1bfb3d66a59f0189ff0b9f5f666cee09c6188309300df04ad71fa/pillow-12.0.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:d4827615da15cd59784ce39d3388275ec093ae3ee8d7f0c089b76fa87af756c2", size = 4120554, upload-time = "2025-10-15T18:23:12.14Z" }, - { url = "https://files.pythonhosted.org/packages/0d/cd/16aec9f0da4793e98e6b54778a5fbce4f375c6646fe662e80600b8797379/pillow-12.0.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:3e42edad50b6909089750e65c91aa09aaf1e0a71310d383f11321b27c224ed8a", size = 3576812, upload-time = "2025-10-15T18:23:13.962Z" }, - { url = "https://files.pythonhosted.org/packages/f6/b7/13957fda356dc46339298b351cae0d327704986337c3c69bb54628c88155/pillow-12.0.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:e5d8efac84c9afcb40914ab49ba063d94f5dbdf5066db4482c66a992f47a3a3b", size = 5252689, upload-time = "2025-10-15T18:23:15.562Z" }, - { url = "https://files.pythonhosted.org/packages/fc/f5/eae31a306341d8f331f43edb2e9122c7661b975433de5e447939ae61c5da/pillow-12.0.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:266cd5f2b63ff316d5a1bba46268e603c9caf5606d44f38c2873c380950576ad", size = 4650186, upload-time = "2025-10-15T18:23:17.379Z" }, - { url = "https://files.pythonhosted.org/packages/86/62/2a88339aa40c4c77e79108facbd307d6091e2c0eb5b8d3cf4977cfca2fe6/pillow-12.0.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:58eea5ebe51504057dd95c5b77d21700b77615ab0243d8152793dc00eb4faf01", size = 6230308, upload-time = "2025-10-15T18:23:18.971Z" }, - { url = "https://files.pythonhosted.org/packages/c7/33/5425a8992bcb32d1cb9fa3dd39a89e613d09a22f2c8083b7bf43c455f760/pillow-12.0.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f13711b1a5ba512d647a0e4ba79280d3a9a045aaf7e0cc6fbe96b91d4cdf6b0c", size = 8039222, upload-time = "2025-10-15T18:23:20.909Z" }, - { url = "https://files.pythonhosted.org/packages/d8/61/3f5d3b35c5728f37953d3eec5b5f3e77111949523bd2dd7f31a851e50690/pillow-12.0.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6846bd2d116ff42cba6b646edf5bf61d37e5cbd256425fa089fee4ff5c07a99e", size = 6346657, upload-time = "2025-10-15T18:23:23.077Z" }, - { url = "https://files.pythonhosted.org/packages/3a/be/ee90a3d79271227e0f0a33c453531efd6ed14b2e708596ba5dd9be948da3/pillow-12.0.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c98fa880d695de164b4135a52fd2e9cd7b7c90a9d8ac5e9e443a24a95ef9248e", size = 7038482, upload-time = "2025-10-15T18:23:25.005Z" }, - { url = "https://files.pythonhosted.org/packages/44/34/a16b6a4d1ad727de390e9bd9f19f5f669e079e5826ec0f329010ddea492f/pillow-12.0.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:fa3ed2a29a9e9d2d488b4da81dcb54720ac3104a20bf0bd273f1e4648aff5af9", size = 6461416, upload-time = "2025-10-15T18:23:27.009Z" }, - { url = "https://files.pythonhosted.org/packages/b6/39/1aa5850d2ade7d7ba9f54e4e4c17077244ff7a2d9e25998c38a29749eb3f/pillow-12.0.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d034140032870024e6b9892c692fe2968493790dd57208b2c37e3fb35f6df3ab", size = 7131584, upload-time = "2025-10-15T18:23:29.752Z" }, - { url = "https://files.pythonhosted.org/packages/bf/db/4fae862f8fad0167073a7733973bfa955f47e2cac3dc3e3e6257d10fab4a/pillow-12.0.0-cp314-cp314-win32.whl", hash = "sha256:1b1b133e6e16105f524a8dec491e0586d072948ce15c9b914e41cdadd209052b", size = 6400621, upload-time = "2025-10-15T18:23:32.06Z" }, - { url = "https://files.pythonhosted.org/packages/2b/24/b350c31543fb0107ab2599464d7e28e6f856027aadda995022e695313d94/pillow-12.0.0-cp314-cp314-win_amd64.whl", hash = "sha256:8dc232e39d409036af549c86f24aed8273a40ffa459981146829a324e0848b4b", size = 7142916, upload-time = "2025-10-15T18:23:34.71Z" }, - { url = "https://files.pythonhosted.org/packages/0f/9b/0ba5a6fd9351793996ef7487c4fdbde8d3f5f75dbedc093bb598648fddf0/pillow-12.0.0-cp314-cp314-win_arm64.whl", hash = "sha256:d52610d51e265a51518692045e372a4c363056130d922a7351429ac9f27e70b0", size = 2523836, upload-time = "2025-10-15T18:23:36.967Z" }, - { url = "https://files.pythonhosted.org/packages/f5/7a/ceee0840aebc579af529b523d530840338ecf63992395842e54edc805987/pillow-12.0.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1979f4566bb96c1e50a62d9831e2ea2d1211761e5662afc545fa766f996632f6", size = 5255092, upload-time = "2025-10-15T18:23:38.573Z" }, - { url = "https://files.pythonhosted.org/packages/44/76/20776057b4bfd1aef4eeca992ebde0f53a4dce874f3ae693d0ec90a4f79b/pillow-12.0.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b2e4b27a6e15b04832fe9bf292b94b5ca156016bbc1ea9c2c20098a0320d6cf6", size = 4653158, upload-time = "2025-10-15T18:23:40.238Z" }, - { url = "https://files.pythonhosted.org/packages/82/3f/d9ff92ace07be8836b4e7e87e6a4c7a8318d47c2f1463ffcf121fc57d9cb/pillow-12.0.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:fb3096c30df99fd01c7bf8e544f392103d0795b9f98ba71a8054bcbf56b255f1", size = 6267882, upload-time = "2025-10-15T18:23:42.434Z" }, - { url = "https://files.pythonhosted.org/packages/9f/7a/4f7ff87f00d3ad33ba21af78bfcd2f032107710baf8280e3722ceec28cda/pillow-12.0.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7438839e9e053ef79f7112c881cef684013855016f928b168b81ed5835f3e75e", size = 8071001, upload-time = "2025-10-15T18:23:44.29Z" }, - { url = "https://files.pythonhosted.org/packages/75/87/fcea108944a52dad8cca0715ae6247e271eb80459364a98518f1e4f480c1/pillow-12.0.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5d5c411a8eaa2299322b647cd932586b1427367fd3184ffbb8f7a219ea2041ca", size = 6380146, upload-time = "2025-10-15T18:23:46.065Z" }, - { url = "https://files.pythonhosted.org/packages/91/52/0d31b5e571ef5fd111d2978b84603fce26aba1b6092f28e941cb46570745/pillow-12.0.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d7e091d464ac59d2c7ad8e7e08105eaf9dafbc3883fd7265ffccc2baad6ac925", size = 7067344, upload-time = "2025-10-15T18:23:47.898Z" }, - { url = "https://files.pythonhosted.org/packages/7b/f4/2dd3d721f875f928d48e83bb30a434dee75a2531bca839bb996bb0aa5a91/pillow-12.0.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:792a2c0be4dcc18af9d4a2dfd8a11a17d5e25274a1062b0ec1c2d79c76f3e7f8", size = 6491864, upload-time = "2025-10-15T18:23:49.607Z" }, - { url = "https://files.pythonhosted.org/packages/30/4b/667dfcf3d61fc309ba5a15b141845cece5915e39b99c1ceab0f34bf1d124/pillow-12.0.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:afbefa430092f71a9593a99ab6a4e7538bc9eabbf7bf94f91510d3503943edc4", size = 7158911, upload-time = "2025-10-15T18:23:51.351Z" }, - { url = "https://files.pythonhosted.org/packages/a2/2f/16cabcc6426c32218ace36bf0d55955e813f2958afddbf1d391849fee9d1/pillow-12.0.0-cp314-cp314t-win32.whl", hash = "sha256:3830c769decf88f1289680a59d4f4c46c72573446352e2befec9a8512104fa52", size = 6408045, upload-time = "2025-10-15T18:23:53.177Z" }, - { url = "https://files.pythonhosted.org/packages/35/73/e29aa0c9c666cf787628d3f0dcf379f4791fba79f4936d02f8b37165bdf8/pillow-12.0.0-cp314-cp314t-win_amd64.whl", hash = "sha256:905b0365b210c73afb0ebe9101a32572152dfd1c144c7e28968a331b9217b94a", size = 7148282, upload-time = "2025-10-15T18:23:55.316Z" }, - { url = "https://files.pythonhosted.org/packages/c1/70/6b41bdcddf541b437bbb9f47f94d2db5d9ddef6c37ccab8c9107743748a4/pillow-12.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:99353a06902c2e43b43e8ff74ee65a7d90307d82370604746738a1e0661ccca7", size = 2525630, upload-time = "2025-10-15T18:23:57.149Z" }, - { url = "https://files.pythonhosted.org/packages/1d/b3/582327e6c9f86d037b63beebe981425d6811104cb443e8193824ef1a2f27/pillow-12.0.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:b22bd8c974942477156be55a768f7aa37c46904c175be4e158b6a86e3a6b7ca8", size = 5215068, upload-time = "2025-10-15T18:23:59.594Z" }, - { url = "https://files.pythonhosted.org/packages/fd/d6/67748211d119f3b6540baf90f92fae73ae51d5217b171b0e8b5f7e5d558f/pillow-12.0.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:805ebf596939e48dbb2e4922a1d3852cfc25c38160751ce02da93058b48d252a", size = 4614994, upload-time = "2025-10-15T18:24:01.669Z" }, - { url = "https://files.pythonhosted.org/packages/2d/e1/f8281e5d844c41872b273b9f2c34a4bf64ca08905668c8ae730eedc7c9fa/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:cae81479f77420d217def5f54b5b9d279804d17e982e0f2fa19b1d1e14ab5197", size = 5246639, upload-time = "2025-10-15T18:24:03.403Z" }, - { url = "https://files.pythonhosted.org/packages/94/5a/0d8ab8ffe8a102ff5df60d0de5af309015163bf710c7bb3e8311dd3b3ad0/pillow-12.0.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:aeaefa96c768fc66818730b952a862235d68825c178f1b3ffd4efd7ad2edcb7c", size = 6986839, upload-time = "2025-10-15T18:24:05.344Z" }, - { url = "https://files.pythonhosted.org/packages/20/2e/3434380e8110b76cd9eb00a363c484b050f949b4bbe84ba770bb8508a02c/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:09f2d0abef9e4e2f349305a4f8cc784a8a6c2f58a8c4892eea13b10a943bd26e", size = 5313505, upload-time = "2025-10-15T18:24:07.137Z" }, - { url = "https://files.pythonhosted.org/packages/57/ca/5a9d38900d9d74785141d6580950fe705de68af735ff6e727cb911b64740/pillow-12.0.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bdee52571a343d721fb2eb3b090a82d959ff37fc631e3f70422e0c2e029f3e76", size = 5963654, upload-time = "2025-10-15T18:24:09.579Z" }, - { url = "https://files.pythonhosted.org/packages/95/7e/f896623c3c635a90537ac093c6a618ebe1a90d87206e42309cb5d98a1b9e/pillow-12.0.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b290fd8aa38422444d4b50d579de197557f182ef1068b75f5aa8558638b8d0a5", size = 6997850, upload-time = "2025-10-15T18:24:11.495Z" }, +version = "12.2.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/8c/21/c2bcdd5906101a30244eaffc1b6e6ce71a31bd0742a01eb89e660ebfac2d/pillow-12.2.0.tar.gz", hash = "sha256:a830b1a40919539d07806aa58e1b114df53ddd43213d9c8b75847eee6c0182b5", size = 46987819, upload-time = "2026-04-01T14:46:17.687Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/68/e1/748f5663efe6edcfc4e74b2b93edfb9b8b99b67f21a854c3ae416500a2d9/pillow-12.2.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:8be29e59487a79f173507c30ddf57e733a357f67881430449bb32614075a40ab", size = 5354347, upload-time = "2026-04-01T14:42:44.255Z" }, + { url = "https://files.pythonhosted.org/packages/47/a1/d5ff69e747374c33a3b53b9f98cca7889fce1fd03d79cdc4e1bccc6c5a87/pillow-12.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:71cde9a1e1551df7d34a25462fc60325e8a11a82cc2e2f54578e5e9a1e153d65", size = 4695873, upload-time = "2026-04-01T14:42:46.452Z" }, + { url = "https://files.pythonhosted.org/packages/df/21/e3fbdf54408a973c7f7f89a23b2cb97a7ef30c61ab4142af31eee6aebc88/pillow-12.2.0-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f490f9368b6fc026f021db16d7ec2fbf7d89e2edb42e8ec09d2c60505f5729c7", size = 6280168, upload-time = "2026-04-01T14:42:49.228Z" }, + { url = "https://files.pythonhosted.org/packages/d3/f1/00b7278c7dd52b17ad4329153748f87b6756ec195ff786c2bdf12518337d/pillow-12.2.0-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:8bd7903a5f2a4545f6fd5935c90058b89d30045568985a71c79f5fd6edf9b91e", size = 8088188, upload-time = "2026-04-01T14:42:51.735Z" }, + { url = "https://files.pythonhosted.org/packages/ad/cf/220a5994ef1b10e70e85748b75649d77d506499352be135a4989c957b701/pillow-12.2.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3997232e10d2920a68d25191392e3a4487d8183039e1c74c2297f00ed1c50705", size = 6394401, upload-time = "2026-04-01T14:42:54.343Z" }, + { url = "https://files.pythonhosted.org/packages/e9/bd/e51a61b1054f09437acfbc2ff9106c30d1eb76bc1453d428399946781253/pillow-12.2.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e74473c875d78b8e9d5da2a70f7099549f9eb37ded4e2f6a463e60125bccd176", size = 7079655, upload-time = "2026-04-01T14:42:56.954Z" }, + { url = "https://files.pythonhosted.org/packages/6b/3d/45132c57d5fb4b5744567c3817026480ac7fc3ce5d4c47902bc0e7f6f853/pillow-12.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:56a3f9c60a13133a98ecff6197af34d7824de9b7b38c3654861a725c970c197b", size = 6503105, upload-time = "2026-04-01T14:42:59.847Z" }, + { url = "https://files.pythonhosted.org/packages/7d/2e/9df2fc1e82097b1df3dce58dc43286aa01068e918c07574711fcc53e6fb4/pillow-12.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:90e6f81de50ad6b534cab6e5aef77ff6e37722b2f5d908686f4a5c9eba17a909", size = 7203402, upload-time = "2026-04-01T14:43:02.664Z" }, + { url = "https://files.pythonhosted.org/packages/bd/2e/2941e42858ebb67e50ae741473de81c2984e6eff7b397017623c676e2e8d/pillow-12.2.0-cp311-cp311-win32.whl", hash = "sha256:8c984051042858021a54926eb597d6ee3012393ce9c181814115df4c60b9a808", size = 6378149, upload-time = "2026-04-01T14:43:05.274Z" }, + { url = "https://files.pythonhosted.org/packages/69/42/836b6f3cd7f3e5fa10a1f1a5420447c17966044c8fbf589cc0452d5502db/pillow-12.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e6b2a0c538fc200b38ff9eb6628228b77908c319a005815f2dde585a0664b60", size = 7082626, upload-time = "2026-04-01T14:43:08.557Z" }, + { url = "https://files.pythonhosted.org/packages/c2/88/549194b5d6f1f494b485e493edc6693c0a16f4ada488e5bd974ed1f42fad/pillow-12.2.0-cp311-cp311-win_arm64.whl", hash = "sha256:9a8a34cc89c67a65ea7437ce257cea81a9dad65b29805f3ecee8c8fe8ff25ffe", size = 2463531, upload-time = "2026-04-01T14:43:10.743Z" }, + { url = "https://files.pythonhosted.org/packages/58/be/7482c8a5ebebbc6470b3eb791812fff7d5e0216c2be3827b30b8bb6603ed/pillow-12.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:2d192a155bbcec180f8564f693e6fd9bccff5a7af9b32e2e4bf8c9c69dbad6b5", size = 5308279, upload-time = "2026-04-01T14:43:13.246Z" }, + { url = "https://files.pythonhosted.org/packages/d8/95/0a351b9289c2b5cbde0bacd4a83ebc44023e835490a727b2a3bd60ddc0f4/pillow-12.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:f3f40b3c5a968281fd507d519e444c35f0ff171237f4fdde090dd60699458421", size = 4695490, upload-time = "2026-04-01T14:43:15.584Z" }, + { url = "https://files.pythonhosted.org/packages/de/af/4e8e6869cbed569d43c416fad3dc4ecb944cb5d9492defaed89ddd6fe871/pillow-12.2.0-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:03e7e372d5240cc23e9f07deca4d775c0817bffc641b01e9c3af208dbd300987", size = 6284462, upload-time = "2026-04-01T14:43:18.268Z" }, + { url = "https://files.pythonhosted.org/packages/e9/9e/c05e19657fd57841e476be1ab46c4d501bffbadbafdc31a6d665f8b737b6/pillow-12.2.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:b86024e52a1b269467a802258c25521e6d742349d760728092e1bc2d135b4d76", size = 8094744, upload-time = "2026-04-01T14:43:20.716Z" }, + { url = "https://files.pythonhosted.org/packages/2b/54/1789c455ed10176066b6e7e6da1b01e50e36f94ba584dc68d9eebfe9156d/pillow-12.2.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7371b48c4fa448d20d2714c9a1f775a81155050d383333e0a6c15b1123dda005", size = 6398371, upload-time = "2026-04-01T14:43:23.443Z" }, + { url = "https://files.pythonhosted.org/packages/43/e3/fdc657359e919462369869f1c9f0e973f353f9a9ee295a39b1fea8ee1a77/pillow-12.2.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:62f5409336adb0663b7caa0da5c7d9e7bdbaae9ce761d34669420c2a801b2780", size = 7087215, upload-time = "2026-04-01T14:43:26.758Z" }, + { url = "https://files.pythonhosted.org/packages/8b/f8/2f6825e441d5b1959d2ca5adec984210f1ec086435b0ed5f52c19b3b8a6e/pillow-12.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:01afa7cf67f74f09523699b4e88c73fb55c13346d212a59a2db1f86b0a63e8c5", size = 6509783, upload-time = "2026-04-01T14:43:29.56Z" }, + { url = "https://files.pythonhosted.org/packages/67/f9/029a27095ad20f854f9dba026b3ea6428548316e057e6fc3545409e86651/pillow-12.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fc3d34d4a8fbec3e88a79b92e5465e0f9b842b628675850d860b8bd300b159f5", size = 7212112, upload-time = "2026-04-01T14:43:32.091Z" }, + { url = "https://files.pythonhosted.org/packages/be/42/025cfe05d1be22dbfdb4f264fe9de1ccda83f66e4fc3aac94748e784af04/pillow-12.2.0-cp312-cp312-win32.whl", hash = "sha256:58f62cc0f00fd29e64b29f4fd923ffdb3859c9f9e6105bfc37ba1d08994e8940", size = 6378489, upload-time = "2026-04-01T14:43:34.601Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7b/25a221d2c761c6a8ae21bfa3874988ff2583e19cf8a27bf2fee358df7942/pillow-12.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:7f84204dee22a783350679a0333981df803dac21a0190d706a50475e361c93f5", size = 7084129, upload-time = "2026-04-01T14:43:37.213Z" }, + { url = "https://files.pythonhosted.org/packages/10/e1/542a474affab20fd4a0f1836cb234e8493519da6b76899e30bcc5d990b8b/pillow-12.2.0-cp312-cp312-win_arm64.whl", hash = "sha256:af73337013e0b3b46f175e79492d96845b16126ddf79c438d7ea7ff27783a414", size = 2463612, upload-time = "2026-04-01T14:43:39.421Z" }, + { url = "https://files.pythonhosted.org/packages/4a/01/53d10cf0dbad820a8db274d259a37ba50b88b24768ddccec07355382d5ad/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:8297651f5b5679c19968abefd6bb84d95fe30ef712eb1b2d9b2d31ca61267f4c", size = 4100837, upload-time = "2026-04-01T14:43:41.506Z" }, + { url = "https://files.pythonhosted.org/packages/0f/98/f3a6657ecb698c937f6c76ee564882945f29b79bad496abcba0e84659ec5/pillow-12.2.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:50d8520da2a6ce0af445fa6d648c4273c3eeefbc32d7ce049f22e8b5c3daecc2", size = 4176528, upload-time = "2026-04-01T14:43:43.773Z" }, + { url = "https://files.pythonhosted.org/packages/69/bc/8986948f05e3ea490b8442ea1c1d4d990b24a7e43d8a51b2c7d8b1dced36/pillow-12.2.0-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:766cef22385fa1091258ad7e6216792b156dc16d8d3fa607e7545b2b72061f1c", size = 3640401, upload-time = "2026-04-01T14:43:45.87Z" }, + { url = "https://files.pythonhosted.org/packages/34/46/6c717baadcd62bc8ed51d238d521ab651eaa74838291bda1f86fe1f864c9/pillow-12.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:5d2fd0fa6b5d9d1de415060363433f28da8b1526c1c129020435e186794b3795", size = 5308094, upload-time = "2026-04-01T14:43:48.438Z" }, + { url = "https://files.pythonhosted.org/packages/71/43/905a14a8b17fdb1ccb58d282454490662d2cb89a6bfec26af6d3520da5ec/pillow-12.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:56b25336f502b6ed02e889f4ece894a72612fe885889a6e8c4c80239ff6e5f5f", size = 4695402, upload-time = "2026-04-01T14:43:51.292Z" }, + { url = "https://files.pythonhosted.org/packages/73/dd/42107efcb777b16fa0393317eac58f5b5cf30e8392e266e76e51cff28c3d/pillow-12.2.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:f1c943e96e85df3d3478f7b691f229887e143f81fedab9b20205349ab04d73ed", size = 6280005, upload-time = "2026-04-01T14:43:54.242Z" }, + { url = "https://files.pythonhosted.org/packages/a8/68/b93e09e5e8549019e61acf49f65b1a8530765a7f812c77a7461bca7e4494/pillow-12.2.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:03f6fab9219220f041c74aeaa2939ff0062bd5c364ba9ce037197f4c6d498cd9", size = 8090669, upload-time = "2026-04-01T14:43:57.335Z" }, + { url = "https://files.pythonhosted.org/packages/4b/6e/3ccb54ce8ec4ddd1accd2d89004308b7b0b21c4ac3d20fa70af4760a4330/pillow-12.2.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5cdfebd752ec52bf5bb4e35d9c64b40826bc5b40a13df7c3cda20a2c03a0f5ed", size = 6395194, upload-time = "2026-04-01T14:43:59.864Z" }, + { url = "https://files.pythonhosted.org/packages/67/ee/21d4e8536afd1a328f01b359b4d3997b291ffd35a237c877b331c1c3b71c/pillow-12.2.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eedf4b74eda2b5a4b2b2fb4c006d6295df3bf29e459e198c90ea48e130dc75c3", size = 7082423, upload-time = "2026-04-01T14:44:02.74Z" }, + { url = "https://files.pythonhosted.org/packages/78/5f/e9f86ab0146464e8c133fe85df987ed9e77e08b29d8d35f9f9f4d6f917ba/pillow-12.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:00a2865911330191c0b818c59103b58a5e697cae67042366970a6b6f1b20b7f9", size = 6505667, upload-time = "2026-04-01T14:44:05.381Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1e/409007f56a2fdce61584fd3acbc2bbc259857d555196cedcadc68c015c82/pillow-12.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:1e1757442ed87f4912397c6d35a0db6a7b52592156014706f17658ff58bbf795", size = 7208580, upload-time = "2026-04-01T14:44:08.39Z" }, + { url = "https://files.pythonhosted.org/packages/23/c4/7349421080b12fb35414607b8871e9534546c128a11965fd4a7002ccfbee/pillow-12.2.0-cp313-cp313-win32.whl", hash = "sha256:144748b3af2d1b358d41286056d0003f47cb339b8c43a9ea42f5fea4d8c66b6e", size = 6375896, upload-time = "2026-04-01T14:44:11.197Z" }, + { url = "https://files.pythonhosted.org/packages/3f/82/8a3739a5e470b3c6cbb1d21d315800d8e16bff503d1f16b03a4ec3212786/pillow-12.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:390ede346628ccc626e5730107cde16c42d3836b89662a115a921f28440e6a3b", size = 7081266, upload-time = "2026-04-01T14:44:13.947Z" }, + { url = "https://files.pythonhosted.org/packages/c3/25/f968f618a062574294592f668218f8af564830ccebdd1fa6200f598e65c5/pillow-12.2.0-cp313-cp313-win_arm64.whl", hash = "sha256:8023abc91fba39036dbce14a7d6535632f99c0b857807cbbbf21ecc9f4717f06", size = 2463508, upload-time = "2026-04-01T14:44:16.312Z" }, + { url = "https://files.pythonhosted.org/packages/4d/a4/b342930964e3cb4dce5038ae34b0eab4653334995336cd486c5a8c25a00c/pillow-12.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:042db20a421b9bafecc4b84a8b6e444686bd9d836c7fd24542db3e7df7baad9b", size = 5309927, upload-time = "2026-04-01T14:44:18.89Z" }, + { url = "https://files.pythonhosted.org/packages/9f/de/23198e0a65a9cf06123f5435a5d95cea62a635697f8f03d134d3f3a96151/pillow-12.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:dd025009355c926a84a612fecf58bb315a3f6814b17ead51a8e48d3823d9087f", size = 4698624, upload-time = "2026-04-01T14:44:21.115Z" }, + { url = "https://files.pythonhosted.org/packages/01/a6/1265e977f17d93ea37aa28aa81bad4fa597933879fac2520d24e021c8da3/pillow-12.2.0-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:88ddbc66737e277852913bd1e07c150cc7bb124539f94c4e2df5344494e0a612", size = 6321252, upload-time = "2026-04-01T14:44:23.663Z" }, + { url = "https://files.pythonhosted.org/packages/3c/83/5982eb4a285967baa70340320be9f88e57665a387e3a53a7f0db8231a0cd/pillow-12.2.0-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d362d1878f00c142b7e1a16e6e5e780f02be8195123f164edf7eddd911eefe7c", size = 8126550, upload-time = "2026-04-01T14:44:26.772Z" }, + { url = "https://files.pythonhosted.org/packages/4e/48/6ffc514adce69f6050d0753b1a18fd920fce8cac87620d5a31231b04bfc5/pillow-12.2.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2c727a6d53cb0018aadd8018c2b938376af27914a68a492f59dfcaca650d5eea", size = 6433114, upload-time = "2026-04-01T14:44:29.615Z" }, + { url = "https://files.pythonhosted.org/packages/36/a3/f9a77144231fb8d40ee27107b4463e205fa4677e2ca2548e14da5cf18dce/pillow-12.2.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:efd8c21c98c5cc60653bcb311bef2ce0401642b7ce9d09e03a7da87c878289d4", size = 7115667, upload-time = "2026-04-01T14:44:32.773Z" }, + { url = "https://files.pythonhosted.org/packages/c1/fc/ac4ee3041e7d5a565e1c4fd72a113f03b6394cc72ab7089d27608f8aaccb/pillow-12.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9f08483a632889536b8139663db60f6724bfcb443c96f1b18855860d7d5c0fd4", size = 6538966, upload-time = "2026-04-01T14:44:35.252Z" }, + { url = "https://files.pythonhosted.org/packages/c0/a8/27fb307055087f3668f6d0a8ccb636e7431d56ed0750e07a60547b1e083e/pillow-12.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:dac8d77255a37e81a2efcbd1fc05f1c15ee82200e6c240d7e127e25e365c39ea", size = 7238241, upload-time = "2026-04-01T14:44:37.875Z" }, + { url = "https://files.pythonhosted.org/packages/ad/4b/926ab182c07fccae9fcb120043464e1ff1564775ec8864f21a0ebce6ac25/pillow-12.2.0-cp313-cp313t-win32.whl", hash = "sha256:ee3120ae9dff32f121610bb08e4313be87e03efeadfc6c0d18f89127e24d0c24", size = 6379592, upload-time = "2026-04-01T14:44:40.336Z" }, + { url = "https://files.pythonhosted.org/packages/c2/c4/f9e476451a098181b30050cc4c9a3556b64c02cf6497ea421ac047e89e4b/pillow-12.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:325ca0528c6788d2a6c3d40e3568639398137346c3d6e66bb61db96b96511c98", size = 7085542, upload-time = "2026-04-01T14:44:43.251Z" }, + { url = "https://files.pythonhosted.org/packages/00/a4/285f12aeacbe2d6dc36c407dfbbe9e96d4a80b0fb710a337f6d2ad978c75/pillow-12.2.0-cp313-cp313t-win_arm64.whl", hash = "sha256:2e5a76d03a6c6dcef67edabda7a52494afa4035021a79c8558e14af25313d453", size = 2465765, upload-time = "2026-04-01T14:44:45.996Z" }, + { url = "https://files.pythonhosted.org/packages/bf/98/4595daa2365416a86cb0d495248a393dfc84e96d62ad080c8546256cb9c0/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:3adc9215e8be0448ed6e814966ecf3d9952f0ea40eb14e89a102b87f450660d8", size = 4100848, upload-time = "2026-04-01T14:44:48.48Z" }, + { url = "https://files.pythonhosted.org/packages/0b/79/40184d464cf89f6663e18dfcf7ca21aae2491fff1a16127681bf1fa9b8cf/pillow-12.2.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:6a9adfc6d24b10f89588096364cc726174118c62130c817c2837c60cf08a392b", size = 4176515, upload-time = "2026-04-01T14:44:51.353Z" }, + { url = "https://files.pythonhosted.org/packages/b0/63/703f86fd4c422a9cf722833670f4f71418fb116b2853ff7da722ea43f184/pillow-12.2.0-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:6a6e67ea2e6feda684ed370f9a1c52e7a243631c025ba42149a2cc5934dec295", size = 3640159, upload-time = "2026-04-01T14:44:53.588Z" }, + { url = "https://files.pythonhosted.org/packages/71/e0/fb22f797187d0be2270f83500aab851536101b254bfa1eae10795709d283/pillow-12.2.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2bb4a8d594eacdfc59d9e5ad972aa8afdd48d584ffd5f13a937a664c3e7db0ed", size = 5312185, upload-time = "2026-04-01T14:44:56.039Z" }, + { url = "https://files.pythonhosted.org/packages/ba/8c/1a9e46228571de18f8e28f16fabdfc20212a5d019f3e3303452b3f0a580d/pillow-12.2.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:80b2da48193b2f33ed0c32c38140f9d3186583ce7d516526d462645fd98660ae", size = 4695386, upload-time = "2026-04-01T14:44:58.663Z" }, + { url = "https://files.pythonhosted.org/packages/70/62/98f6b7f0c88b9addd0e87c217ded307b36be024d4ff8869a812b241d1345/pillow-12.2.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:22db17c68434de69d8ecfc2fe821569195c0c373b25cccb9cbdacf2c6e53c601", size = 6280384, upload-time = "2026-04-01T14:45:01.5Z" }, + { url = "https://files.pythonhosted.org/packages/5e/03/688747d2e91cfbe0e64f316cd2e8005698f76ada3130d0194664174fa5de/pillow-12.2.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7b14cc0106cd9aecda615dd6903840a058b4700fcb817687d0ee4fc8b6e389be", size = 8091599, upload-time = "2026-04-01T14:45:04.5Z" }, + { url = "https://files.pythonhosted.org/packages/f6/35/577e22b936fcdd66537329b33af0b4ccfefaeabd8aec04b266528cddb33c/pillow-12.2.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8cbeb542b2ebc6fcdacabf8aca8c1a97c9b3ad3927d46b8723f9d4f033288a0f", size = 6396021, upload-time = "2026-04-01T14:45:07.117Z" }, + { url = "https://files.pythonhosted.org/packages/11/8d/d2532ad2a603ca2b93ad9f5135732124e57811d0168155852f37fbce2458/pillow-12.2.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bfd07bc812fbd20395212969e41931001fd59eb55a60658b0e5710872e95286", size = 7083360, upload-time = "2026-04-01T14:45:09.763Z" }, + { url = "https://files.pythonhosted.org/packages/5e/26/d325f9f56c7e039034897e7380e9cc202b1e368bfd04d4cbe6a441f02885/pillow-12.2.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:9aba9a17b623ef750a4d11b742cbafffeb48a869821252b30ee21b5e91392c50", size = 6507628, upload-time = "2026-04-01T14:45:12.378Z" }, + { url = "https://files.pythonhosted.org/packages/5f/f7/769d5632ffb0988f1c5e7660b3e731e30f7f8ec4318e94d0a5d674eb65a4/pillow-12.2.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:deede7c263feb25dba4e82ea23058a235dcc2fe1f6021025dc71f2b618e26104", size = 7209321, upload-time = "2026-04-01T14:45:15.122Z" }, + { url = "https://files.pythonhosted.org/packages/6a/7a/c253e3c645cd47f1aceea6a8bacdba9991bf45bb7dfe927f7c893e89c93c/pillow-12.2.0-cp314-cp314-win32.whl", hash = "sha256:632ff19b2778e43162304d50da0181ce24ac5bb8180122cbe1bf4673428328c7", size = 6479723, upload-time = "2026-04-01T14:45:17.797Z" }, + { url = "https://files.pythonhosted.org/packages/cd/8b/601e6566b957ca50e28725cb6c355c59c2c8609751efbecd980db44e0349/pillow-12.2.0-cp314-cp314-win_amd64.whl", hash = "sha256:4e6c62e9d237e9b65fac06857d511e90d8461a32adcc1b9065ea0c0fa3a28150", size = 7217400, upload-time = "2026-04-01T14:45:20.529Z" }, + { url = "https://files.pythonhosted.org/packages/d6/94/220e46c73065c3e2951bb91c11a1fb636c8c9ad427ac3ce7d7f3359b9b2f/pillow-12.2.0-cp314-cp314-win_arm64.whl", hash = "sha256:b1c1fbd8a5a1af3412a0810d060a78b5136ec0836c8a4ef9aa11807f2a22f4e1", size = 2554835, upload-time = "2026-04-01T14:45:23.162Z" }, + { url = "https://files.pythonhosted.org/packages/b6/ab/1b426a3974cb0e7da5c29ccff4807871d48110933a57207b5a676cccc155/pillow-12.2.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:57850958fe9c751670e49b2cecf6294acc99e562531f4bd317fa5ddee2068463", size = 5314225, upload-time = "2026-04-01T14:45:25.637Z" }, + { url = "https://files.pythonhosted.org/packages/19/1e/dce46f371be2438eecfee2a1960ee2a243bbe5e961890146d2dee1ff0f12/pillow-12.2.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:d5d38f1411c0ed9f97bcb49b7bd59b6b7c314e0e27420e34d99d844b9ce3b6f3", size = 4698541, upload-time = "2026-04-01T14:45:28.355Z" }, + { url = "https://files.pythonhosted.org/packages/55/c3/7fbecf70adb3a0c33b77a300dc52e424dc22ad8cdc06557a2e49523b703d/pillow-12.2.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5c0a9f29ca8e79f09de89293f82fc9b0270bb4af1d58bc98f540cc4aedf03166", size = 6322251, upload-time = "2026-04-01T14:45:30.924Z" }, + { url = "https://files.pythonhosted.org/packages/1c/3c/7fbc17cfb7e4fe0ef1642e0abc17fc6c94c9f7a16be41498e12e2ba60408/pillow-12.2.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1610dd6c61621ae1cf811bef44d77e149ce3f7b95afe66a4512f8c59f25d9ebe", size = 8127807, upload-time = "2026-04-01T14:45:33.908Z" }, + { url = "https://files.pythonhosted.org/packages/ff/c3/a8ae14d6defd2e448493ff512fae903b1e9bd40b72efb6ec55ce0048c8ce/pillow-12.2.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0a34329707af4f73cf1782a36cd2289c0368880654a2c11f027bcee9052d35dd", size = 6433935, upload-time = "2026-04-01T14:45:36.623Z" }, + { url = "https://files.pythonhosted.org/packages/6e/32/2880fb3a074847ac159d8f902cb43278a61e85f681661e7419e6596803ed/pillow-12.2.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8e9c4f5b3c546fa3458a29ab22646c1c6c787ea8f5ef51300e5a60300736905e", size = 7116720, upload-time = "2026-04-01T14:45:39.258Z" }, + { url = "https://files.pythonhosted.org/packages/46/87/495cc9c30e0129501643f24d320076f4cc54f718341df18cc70ec94c44e1/pillow-12.2.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:fb043ee2f06b41473269765c2feae53fc2e2fbf96e5e22ca94fb5ad677856f06", size = 6540498, upload-time = "2026-04-01T14:45:41.879Z" }, + { url = "https://files.pythonhosted.org/packages/18/53/773f5edca692009d883a72211b60fdaf8871cbef075eaa9d577f0a2f989e/pillow-12.2.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f278f034eb75b4e8a13a54a876cc4a5ab39173d2cdd93a638e1b467fc545ac43", size = 7239413, upload-time = "2026-04-01T14:45:44.705Z" }, + { url = "https://files.pythonhosted.org/packages/c9/e4/4b64a97d71b2a83158134abbb2f5bd3f8a2ea691361282f010998f339ec7/pillow-12.2.0-cp314-cp314t-win32.whl", hash = "sha256:6bb77b2dcb06b20f9f4b4a8454caa581cd4dd0643a08bacf821216a16d9c8354", size = 6482084, upload-time = "2026-04-01T14:45:47.568Z" }, + { url = "https://files.pythonhosted.org/packages/ba/13/306d275efd3a3453f72114b7431c877d10b1154014c1ebbedd067770d629/pillow-12.2.0-cp314-cp314t-win_amd64.whl", hash = "sha256:6562ace0d3fb5f20ed7290f1f929cae41b25ae29528f2af1722966a0a02e2aa1", size = 7225152, upload-time = "2026-04-01T14:45:50.032Z" }, + { url = "https://files.pythonhosted.org/packages/ff/6e/cf826fae916b8658848d7b9f38d88da6396895c676e8086fc0988073aaf8/pillow-12.2.0-cp314-cp314t-win_arm64.whl", hash = "sha256:aa88ccfe4e32d362816319ed727a004423aab09c5cea43c01a4b435643fa34eb", size = 2556579, upload-time = "2026-04-01T14:45:52.529Z" }, + { url = "https://files.pythonhosted.org/packages/4e/b7/2437044fb910f499610356d1352e3423753c98e34f915252aafecc64889f/pillow-12.2.0-pp311-pypy311_pp73-macosx_10_15_x86_64.whl", hash = "sha256:0538bd5e05efec03ae613fd89c4ce0368ecd2ba239cc25b9f9be7ed426b0af1f", size = 5273969, upload-time = "2026-04-01T14:45:55.538Z" }, + { url = "https://files.pythonhosted.org/packages/f6/f4/8316e31de11b780f4ac08ef3654a75555e624a98db1056ecb2122d008d5a/pillow-12.2.0-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:394167b21da716608eac917c60aa9b969421b5dcbbe02ae7f013e7b85811c69d", size = 4659674, upload-time = "2026-04-01T14:45:58.093Z" }, + { url = "https://files.pythonhosted.org/packages/d4/37/664fca7201f8bb2aa1d20e2c3d5564a62e6ae5111741966c8319ca802361/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:5d04bfa02cc2d23b497d1e90a0f927070043f6cbf303e738300532379a4b4e0f", size = 5288479, upload-time = "2026-04-01T14:46:01.141Z" }, + { url = "https://files.pythonhosted.org/packages/49/62/5b0ed78fce87346be7a5cfcfaaad91f6a1f98c26f86bdbafa2066c647ef6/pillow-12.2.0-pp311-pypy311_pp73-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0c838a5125cee37e68edec915651521191cef1e6aa336b855f495766e77a366e", size = 7032230, upload-time = "2026-04-01T14:46:03.874Z" }, + { url = "https://files.pythonhosted.org/packages/c3/28/ec0fc38107fc32536908034e990c47914c57cd7c5a3ece4d8d8f7ffd7e27/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4a6c9fa44005fa37a91ebfc95d081e8079757d2e904b27103f4f5fa6f0bf78c0", size = 5355404, upload-time = "2026-04-01T14:46:06.33Z" }, + { url = "https://files.pythonhosted.org/packages/5e/8b/51b0eddcfa2180d60e41f06bd6d0a62202b20b59c68f5a132e615b75aecf/pillow-12.2.0-pp311-pypy311_pp73-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:25373b66e0dd5905ed63fa3cae13c82fbddf3079f2c8bf15c6fb6a35586324c1", size = 6002215, upload-time = "2026-04-01T14:46:08.83Z" }, + { url = "https://files.pythonhosted.org/packages/bc/60/5382c03e1970de634027cee8e1b7d39776b778b81812aaf45b694dfe9e28/pillow-12.2.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:bfa9c230d2fe991bed5318a5f119bd6780cda2915cca595393649fc118ab895e", size = 7080946, upload-time = "2026-04-01T14:46:11.734Z" }, ] [[package]] name = "pluggy" version = "1.6.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412, upload-time = "2025-05-15T12:30:07.975Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538, upload-time = "2025-05-15T12:30:06.134Z" }, @@ -1223,164 +1340,171 @@ wheels = [ [[package]] name = "psutil" -version = "7.1.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e1/88/bdd0a41e5857d5d703287598cbf08dad90aed56774ea52ae071bae9071b6/psutil-7.1.3.tar.gz", hash = "sha256:6c86281738d77335af7aec228328e944b30930899ea760ecf33a4dba66be5e74", size = 489059, upload-time = "2025-11-02T12:25:54.619Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bd/93/0c49e776b8734fef56ec9c5c57f923922f2cf0497d62e0f419465f28f3d0/psutil-7.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0005da714eee687b4b8decd3d6cc7c6db36215c9e74e5ad2264b90c3df7d92dc", size = 239751, upload-time = "2025-11-02T12:25:58.161Z" }, - { url = "https://files.pythonhosted.org/packages/6f/8d/b31e39c769e70780f007969815195a55c81a63efebdd4dbe9e7a113adb2f/psutil-7.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:19644c85dcb987e35eeeaefdc3915d059dac7bd1167cdcdbf27e0ce2df0c08c0", size = 240368, upload-time = "2025-11-02T12:26:00.491Z" }, - { url = "https://files.pythonhosted.org/packages/62/61/23fd4acc3c9eebbf6b6c78bcd89e5d020cfde4acf0a9233e9d4e3fa698b4/psutil-7.1.3-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:95ef04cf2e5ba0ab9eaafc4a11eaae91b44f4ef5541acd2ee91d9108d00d59a7", size = 287134, upload-time = "2025-11-02T12:26:02.613Z" }, - { url = "https://files.pythonhosted.org/packages/30/1c/f921a009ea9ceb51aa355cb0cc118f68d354db36eae18174bab63affb3e6/psutil-7.1.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1068c303be3a72f8e18e412c5b2a8f6d31750fb152f9cb106b54090296c9d251", size = 289904, upload-time = "2025-11-02T12:26:05.207Z" }, - { url = "https://files.pythonhosted.org/packages/a6/82/62d68066e13e46a5116df187d319d1724b3f437ddd0f958756fc052677f4/psutil-7.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:18349c5c24b06ac5612c0428ec2a0331c26443d259e2a0144a9b24b4395b58fa", size = 249642, upload-time = "2025-11-02T12:26:07.447Z" }, - { url = "https://files.pythonhosted.org/packages/df/ad/c1cd5fe965c14a0392112f68362cfceb5230819dbb5b1888950d18a11d9f/psutil-7.1.3-cp313-cp313t-win_arm64.whl", hash = "sha256:c525ffa774fe4496282fb0b1187725793de3e7c6b29e41562733cae9ada151ee", size = 245518, upload-time = "2025-11-02T12:26:09.719Z" }, - { url = "https://files.pythonhosted.org/packages/2e/bb/6670bded3e3236eb4287c7bcdc167e9fae6e1e9286e437f7111caed2f909/psutil-7.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:b403da1df4d6d43973dc004d19cee3b848e998ae3154cc8097d139b77156c353", size = 239843, upload-time = "2025-11-02T12:26:11.968Z" }, - { url = "https://files.pythonhosted.org/packages/b8/66/853d50e75a38c9a7370ddbeefabdd3d3116b9c31ef94dc92c6729bc36bec/psutil-7.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:ad81425efc5e75da3f39b3e636293360ad8d0b49bed7df824c79764fb4ba9b8b", size = 240369, upload-time = "2025-11-02T12:26:14.358Z" }, - { url = "https://files.pythonhosted.org/packages/41/bd/313aba97cb5bfb26916dc29cf0646cbe4dd6a89ca69e8c6edce654876d39/psutil-7.1.3-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8f33a3702e167783a9213db10ad29650ebf383946e91bc77f28a5eb083496bc9", size = 288210, upload-time = "2025-11-02T12:26:16.699Z" }, - { url = "https://files.pythonhosted.org/packages/c2/fa/76e3c06e760927a0cfb5705eb38164254de34e9bd86db656d4dbaa228b04/psutil-7.1.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:fac9cd332c67f4422504297889da5ab7e05fd11e3c4392140f7370f4208ded1f", size = 291182, upload-time = "2025-11-02T12:26:18.848Z" }, - { url = "https://files.pythonhosted.org/packages/0f/1d/5774a91607035ee5078b8fd747686ebec28a962f178712de100d00b78a32/psutil-7.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:3792983e23b69843aea49c8f5b8f115572c5ab64c153bada5270086a2123c7e7", size = 250466, upload-time = "2025-11-02T12:26:21.183Z" }, - { url = "https://files.pythonhosted.org/packages/00/ca/e426584bacb43a5cb1ac91fae1937f478cd8fbe5e4ff96574e698a2c77cd/psutil-7.1.3-cp314-cp314t-win_arm64.whl", hash = "sha256:31d77fcedb7529f27bb3a0472bea9334349f9a04160e8e6e5020f22c59893264", size = 245756, upload-time = "2025-11-02T12:26:23.148Z" }, - { url = "https://files.pythonhosted.org/packages/ef/94/46b9154a800253e7ecff5aaacdf8ebf43db99de4a2dfa18575b02548654e/psutil-7.1.3-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:2bdbcd0e58ca14996a42adf3621a6244f1bb2e2e528886959c72cf1e326677ab", size = 238359, upload-time = "2025-11-02T12:26:25.284Z" }, - { url = "https://files.pythonhosted.org/packages/68/3a/9f93cff5c025029a36d9a92fef47220ab4692ee7f2be0fba9f92813d0cb8/psutil-7.1.3-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:bc31fa00f1fbc3c3802141eede66f3a2d51d89716a194bf2cd6fc68310a19880", size = 239171, upload-time = "2025-11-02T12:26:27.23Z" }, - { url = "https://files.pythonhosted.org/packages/ce/b1/5f49af514f76431ba4eea935b8ad3725cdeb397e9245ab919dbc1d1dc20f/psutil-7.1.3-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3bb428f9f05c1225a558f53e30ccbad9930b11c3fc206836242de1091d3e7dd3", size = 263261, upload-time = "2025-11-02T12:26:29.48Z" }, - { url = "https://files.pythonhosted.org/packages/e0/95/992c8816a74016eb095e73585d747e0a8ea21a061ed3689474fabb29a395/psutil-7.1.3-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:56d974e02ca2c8eb4812c3f76c30e28836fffc311d55d979f1465c1feeb2b68b", size = 264635, upload-time = "2025-11-02T12:26:31.74Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/c3ed1a622b6ae2fd3c945a366e64eb35247a31e4db16cf5095e269e8eb3c/psutil-7.1.3-cp37-abi3-win_amd64.whl", hash = "sha256:f39c2c19fe824b47484b96f9692932248a54c43799a84282cfe58d05a6449efd", size = 247633, upload-time = "2025-11-02T12:26:33.887Z" }, - { url = "https://files.pythonhosted.org/packages/c9/ad/33b2ccec09bf96c2b2ef3f9a6f66baac8253d7565d8839e024a6b905d45d/psutil-7.1.3-cp37-abi3-win_arm64.whl", hash = "sha256:bd0d69cee829226a761e92f28140bec9a5ee9d5b4fb4b0cc589068dbfff559b1", size = 244608, upload-time = "2025-11-02T12:26:36.136Z" }, +version = "7.2.2" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/aa/c6/d1ddf4abb55e93cebc4f2ed8b5d6dbad109ecb8d63748dd2b20ab5e57ebe/psutil-7.2.2.tar.gz", hash = "sha256:0746f5f8d406af344fd547f1c8daa5f5c33dbc293bb8d6a16d80b4bb88f59372", size = 493740, upload-time = "2026-01-28T18:14:54.428Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/51/08/510cbdb69c25a96f4ae523f733cdc963ae654904e8db864c07585ef99875/psutil-7.2.2-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:2edccc433cbfa046b980b0df0171cd25bcaeb3a68fe9022db0979e7aa74a826b", size = 130595, upload-time = "2026-01-28T18:14:57.293Z" }, + { url = "https://files.pythonhosted.org/packages/d6/f5/97baea3fe7a5a9af7436301f85490905379b1c6f2dd51fe3ecf24b4c5fbf/psutil-7.2.2-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e78c8603dcd9a04c7364f1a3e670cea95d51ee865e4efb3556a3a63adef958ea", size = 131082, upload-time = "2026-01-28T18:14:59.732Z" }, + { url = "https://files.pythonhosted.org/packages/37/d6/246513fbf9fa174af531f28412297dd05241d97a75911ac8febefa1a53c6/psutil-7.2.2-cp313-cp313t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1a571f2330c966c62aeda00dd24620425d4b0cc86881c89861fbc04549e5dc63", size = 181476, upload-time = "2026-01-28T18:15:01.884Z" }, + { url = "https://files.pythonhosted.org/packages/b8/b5/9182c9af3836cca61696dabe4fd1304e17bc56cb62f17439e1154f225dd3/psutil-7.2.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:917e891983ca3c1887b4ef36447b1e0873e70c933afc831c6b6da078ba474312", size = 184062, upload-time = "2026-01-28T18:15:04.436Z" }, + { url = "https://files.pythonhosted.org/packages/16/ba/0756dca669f5a9300d0cbcbfae9a4c30e446dfc7440ffe43ded5724bfd93/psutil-7.2.2-cp313-cp313t-win_amd64.whl", hash = "sha256:ab486563df44c17f5173621c7b198955bd6b613fb87c71c161f827d3fb149a9b", size = 139893, upload-time = "2026-01-28T18:15:06.378Z" }, + { url = "https://files.pythonhosted.org/packages/1c/61/8fa0e26f33623b49949346de05ec1ddaad02ed8ba64af45f40a147dbfa97/psutil-7.2.2-cp313-cp313t-win_arm64.whl", hash = "sha256:ae0aefdd8796a7737eccea863f80f81e468a1e4cf14d926bd9b6f5f2d5f90ca9", size = 135589, upload-time = "2026-01-28T18:15:08.03Z" }, + { url = "https://files.pythonhosted.org/packages/81/69/ef179ab5ca24f32acc1dac0c247fd6a13b501fd5534dbae0e05a1c48b66d/psutil-7.2.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:eed63d3b4d62449571547b60578c5b2c4bcccc5387148db46e0c2313dad0ee00", size = 130664, upload-time = "2026-01-28T18:15:09.469Z" }, + { url = "https://files.pythonhosted.org/packages/7b/64/665248b557a236d3fa9efc378d60d95ef56dd0a490c2cd37dafc7660d4a9/psutil-7.2.2-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:7b6d09433a10592ce39b13d7be5a54fbac1d1228ed29abc880fb23df7cb694c9", size = 131087, upload-time = "2026-01-28T18:15:11.724Z" }, + { url = "https://files.pythonhosted.org/packages/d5/2e/e6782744700d6759ebce3043dcfa661fb61e2fb752b91cdeae9af12c2178/psutil-7.2.2-cp314-cp314t-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1fa4ecf83bcdf6e6c8f4449aff98eefb5d0604bf88cb883d7da3d8d2d909546a", size = 182383, upload-time = "2026-01-28T18:15:13.445Z" }, + { url = "https://files.pythonhosted.org/packages/57/49/0a41cefd10cb7505cdc04dab3eacf24c0c2cb158a998b8c7b1d27ee2c1f5/psutil-7.2.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e452c464a02e7dc7822a05d25db4cde564444a67e58539a00f929c51eddda0cf", size = 185210, upload-time = "2026-01-28T18:15:16.002Z" }, + { url = "https://files.pythonhosted.org/packages/dd/2c/ff9bfb544f283ba5f83ba725a3c5fec6d6b10b8f27ac1dc641c473dc390d/psutil-7.2.2-cp314-cp314t-win_amd64.whl", hash = "sha256:c7663d4e37f13e884d13994247449e9f8f574bc4655d509c3b95e9ec9e2b9dc1", size = 141228, upload-time = "2026-01-28T18:15:18.385Z" }, + { url = "https://files.pythonhosted.org/packages/f2/fc/f8d9c31db14fcec13748d373e668bc3bed94d9077dbc17fb0eebc073233c/psutil-7.2.2-cp314-cp314t-win_arm64.whl", hash = "sha256:11fe5a4f613759764e79c65cf11ebdf26e33d6dd34336f8a337aa2996d71c841", size = 136284, upload-time = "2026-01-28T18:15:19.912Z" }, + { url = "https://files.pythonhosted.org/packages/e7/36/5ee6e05c9bd427237b11b3937ad82bb8ad2752d72c6969314590dd0c2f6e/psutil-7.2.2-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ed0cace939114f62738d808fdcecd4c869222507e266e574799e9c0faa17d486", size = 129090, upload-time = "2026-01-28T18:15:22.168Z" }, + { url = "https://files.pythonhosted.org/packages/80/c4/f5af4c1ca8c1eeb2e92ccca14ce8effdeec651d5ab6053c589b074eda6e1/psutil-7.2.2-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:1a7b04c10f32cc88ab39cbf606e117fd74721c831c98a27dc04578deb0c16979", size = 129859, upload-time = "2026-01-28T18:15:23.795Z" }, + { url = "https://files.pythonhosted.org/packages/b5/70/5d8df3b09e25bce090399cf48e452d25c935ab72dad19406c77f4e828045/psutil-7.2.2-cp36-abi3-manylinux2010_x86_64.manylinux_2_12_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:076a2d2f923fd4821644f5ba89f059523da90dc9014e85f8e45a5774ca5bc6f9", size = 155560, upload-time = "2026-01-28T18:15:25.976Z" }, + { url = "https://files.pythonhosted.org/packages/63/65/37648c0c158dc222aba51c089eb3bdfa238e621674dc42d48706e639204f/psutil-7.2.2-cp36-abi3-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b0726cecd84f9474419d67252add4ac0cd9811b04d61123054b9fb6f57df6e9e", size = 156997, upload-time = "2026-01-28T18:15:27.794Z" }, + { url = "https://files.pythonhosted.org/packages/8e/13/125093eadae863ce03c6ffdbae9929430d116a246ef69866dad94da3bfbc/psutil-7.2.2-cp36-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:fd04ef36b4a6d599bbdb225dd1d3f51e00105f6d48a28f006da7f9822f2606d8", size = 148972, upload-time = "2026-01-28T18:15:29.342Z" }, + { url = "https://files.pythonhosted.org/packages/04/78/0acd37ca84ce3ddffaa92ef0f571e073faa6d8ff1f0559ab1272188ea2be/psutil-7.2.2-cp36-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b58fabe35e80b264a4e3bb23e6b96f9e45a3df7fb7eed419ac0e5947c61e47cc", size = 148266, upload-time = "2026-01-28T18:15:31.597Z" }, + { url = "https://files.pythonhosted.org/packages/b4/90/e2159492b5426be0c1fef7acba807a03511f97c5f86b3caeda6ad92351a7/psutil-7.2.2-cp37-abi3-win_amd64.whl", hash = "sha256:eb7e81434c8d223ec4a219b5fc1c47d0417b12be7ea866e24fb5ad6e84b3d988", size = 137737, upload-time = "2026-01-28T18:15:33.849Z" }, + { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] [[package]] name = "pydantic" -version = "2.12.4" -source = { registry = "https://pypi.org/simple" } +version = "2.13.3" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "annotated-types" }, { name = "pydantic-core" }, { name = "typing-extensions" }, { name = "typing-inspection" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/96/ad/a17bc283d7d81837c061c49e3eaa27a45991759a1b7eae1031921c6bd924/pydantic-2.12.4.tar.gz", hash = "sha256:0f8cb9555000a4b5b617f66bfd2566264c4984b27589d3b845685983e8ea85ac", size = 821038, upload-time = "2025-11-05T10:50:08.59Z" } +sdist = { url = "https://files.pythonhosted.org/packages/d9/e4/40d09941a2cebcb20609b86a559817d5b9291c49dd6f8c87e5feffbe703a/pydantic-2.13.3.tar.gz", hash = "sha256:af09e9d1d09f4e7fe37145c1f577e1d61ceb9a41924bf0094a36506285d0a84d", size = 844068, upload-time = "2026-04-20T14:46:43.632Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/82/2f/e68750da9b04856e2a7ec56fc6f034a5a79775e9b9a81882252789873798/pydantic-2.12.4-py3-none-any.whl", hash = "sha256:92d3d202a745d46f9be6df459ac5a064fdaa3c1c4cd8adcfa332ccf3c05f871e", size = 463400, upload-time = "2025-11-05T10:50:06.732Z" }, + { url = "https://files.pythonhosted.org/packages/f3/0a/fd7d723f8f8153418fb40cf9c940e82004fce7e987026b08a68a36dd3fe7/pydantic-2.13.3-py3-none-any.whl", hash = "sha256:6db14ac8dfc9a1e57f87ea2c0de670c251240f43cb0c30a5130e9720dc612927", size = 471981, upload-time = "2026-04-20T14:46:41.402Z" }, ] [[package]] name = "pydantic-core" -version = "2.41.5" -source = { registry = "https://pypi.org/simple" } +version = "2.46.3" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "typing-extensions" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/71/70/23b021c950c2addd24ec408e9ab05d59b035b39d97cdc1130e1bce647bb6/pydantic_core-2.41.5.tar.gz", hash = "sha256:08daa51ea16ad373ffd5e7606252cc32f07bc72b28284b6bc9c6df804816476e", size = 460952, upload-time = "2025-11-04T13:43:49.098Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/72/74a989dd9f2084b3d9530b0915fdda64ac48831c30dbf7c72a41a5232db8/pydantic_core-2.41.5-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a3a52f6156e73e7ccb0f8cced536adccb7042be67cb45f9562e12b319c119da6", size = 2105873, upload-time = "2025-11-04T13:39:31.373Z" }, - { url = "https://files.pythonhosted.org/packages/12/44/37e403fd9455708b3b942949e1d7febc02167662bf1a7da5b78ee1ea2842/pydantic_core-2.41.5-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7f3bf998340c6d4b0c9a2f02d6a400e51f123b59565d74dc60d252ce888c260b", size = 1899826, upload-time = "2025-11-04T13:39:32.897Z" }, - { url = "https://files.pythonhosted.org/packages/33/7f/1d5cab3ccf44c1935a359d51a8a2a9e1a654b744b5e7f80d41b88d501eec/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:378bec5c66998815d224c9ca994f1e14c0c21cb95d2f52b6021cc0b2a58f2a5a", size = 1917869, upload-time = "2025-11-04T13:39:34.469Z" }, - { url = "https://files.pythonhosted.org/packages/6e/6a/30d94a9674a7fe4f4744052ed6c5e083424510be1e93da5bc47569d11810/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:e7b576130c69225432866fe2f4a469a85a54ade141d96fd396dffcf607b558f8", size = 2063890, upload-time = "2025-11-04T13:39:36.053Z" }, - { url = "https://files.pythonhosted.org/packages/50/be/76e5d46203fcb2750e542f32e6c371ffa9b8ad17364cf94bb0818dbfb50c/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6cb58b9c66f7e4179a2d5e0f849c48eff5c1fca560994d6eb6543abf955a149e", size = 2229740, upload-time = "2025-11-04T13:39:37.753Z" }, - { url = "https://files.pythonhosted.org/packages/d3/ee/fed784df0144793489f87db310a6bbf8118d7b630ed07aa180d6067e653a/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:88942d3a3dff3afc8288c21e565e476fc278902ae4d6d134f1eeda118cc830b1", size = 2350021, upload-time = "2025-11-04T13:39:40.94Z" }, - { url = "https://files.pythonhosted.org/packages/c8/be/8fed28dd0a180dca19e72c233cbf58efa36df055e5b9d90d64fd1740b828/pydantic_core-2.41.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f31d95a179f8d64d90f6831d71fa93290893a33148d890ba15de25642c5d075b", size = 2066378, upload-time = "2025-11-04T13:39:42.523Z" }, - { url = "https://files.pythonhosted.org/packages/b0/3b/698cf8ae1d536a010e05121b4958b1257f0b5522085e335360e53a6b1c8b/pydantic_core-2.41.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:c1df3d34aced70add6f867a8cf413e299177e0c22660cc767218373d0779487b", size = 2175761, upload-time = "2025-11-04T13:39:44.553Z" }, - { url = "https://files.pythonhosted.org/packages/b8/ba/15d537423939553116dea94ce02f9c31be0fa9d0b806d427e0308ec17145/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4009935984bd36bd2c774e13f9a09563ce8de4abaa7226f5108262fa3e637284", size = 2146303, upload-time = "2025-11-04T13:39:46.238Z" }, - { url = "https://files.pythonhosted.org/packages/58/7f/0de669bf37d206723795f9c90c82966726a2ab06c336deba4735b55af431/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:34a64bc3441dc1213096a20fe27e8e128bd3ff89921706e83c0b1ac971276594", size = 2340355, upload-time = "2025-11-04T13:39:48.002Z" }, - { url = "https://files.pythonhosted.org/packages/e5/de/e7482c435b83d7e3c3ee5ee4451f6e8973cff0eb6007d2872ce6383f6398/pydantic_core-2.41.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c9e19dd6e28fdcaa5a1de679aec4141f691023916427ef9bae8584f9c2fb3b0e", size = 2319875, upload-time = "2025-11-04T13:39:49.705Z" }, - { url = "https://files.pythonhosted.org/packages/fe/e6/8c9e81bb6dd7560e33b9053351c29f30c8194b72f2d6932888581f503482/pydantic_core-2.41.5-cp311-cp311-win32.whl", hash = "sha256:2c010c6ded393148374c0f6f0bf89d206bf3217f201faa0635dcd56bd1520f6b", size = 1987549, upload-time = "2025-11-04T13:39:51.842Z" }, - { url = "https://files.pythonhosted.org/packages/11/66/f14d1d978ea94d1bc21fc98fcf570f9542fe55bfcc40269d4e1a21c19bf7/pydantic_core-2.41.5-cp311-cp311-win_amd64.whl", hash = "sha256:76ee27c6e9c7f16f47db7a94157112a2f3a00e958bc626e2f4ee8bec5c328fbe", size = 2011305, upload-time = "2025-11-04T13:39:53.485Z" }, - { url = "https://files.pythonhosted.org/packages/56/d8/0e271434e8efd03186c5386671328154ee349ff0354d83c74f5caaf096ed/pydantic_core-2.41.5-cp311-cp311-win_arm64.whl", hash = "sha256:4bc36bbc0b7584de96561184ad7f012478987882ebf9f9c389b23f432ea3d90f", size = 1972902, upload-time = "2025-11-04T13:39:56.488Z" }, - { url = "https://files.pythonhosted.org/packages/5f/5d/5f6c63eebb5afee93bcaae4ce9a898f3373ca23df3ccaef086d0233a35a7/pydantic_core-2.41.5-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:f41a7489d32336dbf2199c8c0a215390a751c5b014c2c1c5366e817202e9cdf7", size = 2110990, upload-time = "2025-11-04T13:39:58.079Z" }, - { url = "https://files.pythonhosted.org/packages/aa/32/9c2e8ccb57c01111e0fd091f236c7b371c1bccea0fa85247ac55b1e2b6b6/pydantic_core-2.41.5-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:070259a8818988b9a84a449a2a7337c7f430a22acc0859c6b110aa7212a6d9c0", size = 1896003, upload-time = "2025-11-04T13:39:59.956Z" }, - { url = "https://files.pythonhosted.org/packages/68/b8/a01b53cb0e59139fbc9e4fda3e9724ede8de279097179be4ff31f1abb65a/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e96cea19e34778f8d59fe40775a7a574d95816eb150850a85a7a4c8f4b94ac69", size = 1919200, upload-time = "2025-11-04T13:40:02.241Z" }, - { url = "https://files.pythonhosted.org/packages/38/de/8c36b5198a29bdaade07b5985e80a233a5ac27137846f3bc2d3b40a47360/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ed2e99c456e3fadd05c991f8f437ef902e00eedf34320ba2b0842bd1c3ca3a75", size = 2052578, upload-time = "2025-11-04T13:40:04.401Z" }, - { url = "https://files.pythonhosted.org/packages/00/b5/0e8e4b5b081eac6cb3dbb7e60a65907549a1ce035a724368c330112adfdd/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:65840751b72fbfd82c3c640cff9284545342a4f1eb1586ad0636955b261b0b05", size = 2208504, upload-time = "2025-11-04T13:40:06.072Z" }, - { url = "https://files.pythonhosted.org/packages/77/56/87a61aad59c7c5b9dc8caad5a41a5545cba3810c3e828708b3d7404f6cef/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:e536c98a7626a98feb2d3eaf75944ef6f3dbee447e1f841eae16f2f0a72d8ddc", size = 2335816, upload-time = "2025-11-04T13:40:07.835Z" }, - { url = "https://files.pythonhosted.org/packages/0d/76/941cc9f73529988688a665a5c0ecff1112b3d95ab48f81db5f7606f522d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eceb81a8d74f9267ef4081e246ffd6d129da5d87e37a77c9bde550cb04870c1c", size = 2075366, upload-time = "2025-11-04T13:40:09.804Z" }, - { url = "https://files.pythonhosted.org/packages/d3/43/ebef01f69baa07a482844faaa0a591bad1ef129253ffd0cdaa9d8a7f72d3/pydantic_core-2.41.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d38548150c39b74aeeb0ce8ee1d8e82696f4a4e16ddc6de7b1d8823f7de4b9b5", size = 2171698, upload-time = "2025-11-04T13:40:12.004Z" }, - { url = "https://files.pythonhosted.org/packages/b1/87/41f3202e4193e3bacfc2c065fab7706ebe81af46a83d3e27605029c1f5a6/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:c23e27686783f60290e36827f9c626e63154b82b116d7fe9adba1fda36da706c", size = 2132603, upload-time = "2025-11-04T13:40:13.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7d/4c00df99cb12070b6bccdef4a195255e6020a550d572768d92cc54dba91a/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:482c982f814460eabe1d3bb0adfdc583387bd4691ef00b90575ca0d2b6fe2294", size = 2329591, upload-time = "2025-11-04T13:40:15.672Z" }, - { url = "https://files.pythonhosted.org/packages/cc/6a/ebf4b1d65d458f3cda6a7335d141305dfa19bdc61140a884d165a8a1bbc7/pydantic_core-2.41.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:bfea2a5f0b4d8d43adf9d7b8bf019fb46fdd10a2e5cde477fbcb9d1fa08c68e1", size = 2319068, upload-time = "2025-11-04T13:40:17.532Z" }, - { url = "https://files.pythonhosted.org/packages/49/3b/774f2b5cd4192d5ab75870ce4381fd89cf218af999515baf07e7206753f0/pydantic_core-2.41.5-cp312-cp312-win32.whl", hash = "sha256:b74557b16e390ec12dca509bce9264c3bbd128f8a2c376eaa68003d7f327276d", size = 1985908, upload-time = "2025-11-04T13:40:19.309Z" }, - { url = "https://files.pythonhosted.org/packages/86/45/00173a033c801cacf67c190fef088789394feaf88a98a7035b0e40d53dc9/pydantic_core-2.41.5-cp312-cp312-win_amd64.whl", hash = "sha256:1962293292865bca8e54702b08a4f26da73adc83dd1fcf26fbc875b35d81c815", size = 2020145, upload-time = "2025-11-04T13:40:21.548Z" }, - { url = "https://files.pythonhosted.org/packages/f9/22/91fbc821fa6d261b376a3f73809f907cec5ca6025642c463d3488aad22fb/pydantic_core-2.41.5-cp312-cp312-win_arm64.whl", hash = "sha256:1746d4a3d9a794cacae06a5eaaccb4b8643a131d45fbc9af23e353dc0a5ba5c3", size = 1976179, upload-time = "2025-11-04T13:40:23.393Z" }, - { url = "https://files.pythonhosted.org/packages/87/06/8806241ff1f70d9939f9af039c6c35f2360cf16e93c2ca76f184e76b1564/pydantic_core-2.41.5-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:941103c9be18ac8daf7b7adca8228f8ed6bb7a1849020f643b3a14d15b1924d9", size = 2120403, upload-time = "2025-11-04T13:40:25.248Z" }, - { url = "https://files.pythonhosted.org/packages/94/02/abfa0e0bda67faa65fef1c84971c7e45928e108fe24333c81f3bfe35d5f5/pydantic_core-2.41.5-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:112e305c3314f40c93998e567879e887a3160bb8689ef3d2c04b6cc62c33ac34", size = 1896206, upload-time = "2025-11-04T13:40:27.099Z" }, - { url = "https://files.pythonhosted.org/packages/15/df/a4c740c0943e93e6500f9eb23f4ca7ec9bf71b19e608ae5b579678c8d02f/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0cbaad15cb0c90aa221d43c00e77bb33c93e8d36e0bf74760cd00e732d10a6a0", size = 1919307, upload-time = "2025-11-04T13:40:29.806Z" }, - { url = "https://files.pythonhosted.org/packages/9a/e3/6324802931ae1d123528988e0e86587c2072ac2e5394b4bc2bc34b61ff6e/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:03ca43e12fab6023fc79d28ca6b39b05f794ad08ec2feccc59a339b02f2b3d33", size = 2063258, upload-time = "2025-11-04T13:40:33.544Z" }, - { url = "https://files.pythonhosted.org/packages/c9/d4/2230d7151d4957dd79c3044ea26346c148c98fbf0ee6ebd41056f2d62ab5/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:dc799088c08fa04e43144b164feb0c13f9a0bc40503f8df3e9fde58a3c0c101e", size = 2214917, upload-time = "2025-11-04T13:40:35.479Z" }, - { url = "https://files.pythonhosted.org/packages/e6/9f/eaac5df17a3672fef0081b6c1bb0b82b33ee89aa5cec0d7b05f52fd4a1fa/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:97aeba56665b4c3235a0e52b2c2f5ae9cd071b8a8310ad27bddb3f7fb30e9aa2", size = 2332186, upload-time = "2025-11-04T13:40:37.436Z" }, - { url = "https://files.pythonhosted.org/packages/cf/4e/35a80cae583a37cf15604b44240e45c05e04e86f9cfd766623149297e971/pydantic_core-2.41.5-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:406bf18d345822d6c21366031003612b9c77b3e29ffdb0f612367352aab7d586", size = 2073164, upload-time = "2025-11-04T13:40:40.289Z" }, - { url = "https://files.pythonhosted.org/packages/bf/e3/f6e262673c6140dd3305d144d032f7bd5f7497d3871c1428521f19f9efa2/pydantic_core-2.41.5-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b93590ae81f7010dbe380cdeab6f515902ebcbefe0b9327cc4804d74e93ae69d", size = 2179146, upload-time = "2025-11-04T13:40:42.809Z" }, - { url = "https://files.pythonhosted.org/packages/75/c7/20bd7fc05f0c6ea2056a4565c6f36f8968c0924f19b7d97bbfea55780e73/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:01a3d0ab748ee531f4ea6c3e48ad9dac84ddba4b0d82291f87248f2f9de8d740", size = 2137788, upload-time = "2025-11-04T13:40:44.752Z" }, - { url = "https://files.pythonhosted.org/packages/3a/8d/34318ef985c45196e004bc46c6eab2eda437e744c124ef0dbe1ff2c9d06b/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:6561e94ba9dacc9c61bce40e2d6bdc3bfaa0259d3ff36ace3b1e6901936d2e3e", size = 2340133, upload-time = "2025-11-04T13:40:46.66Z" }, - { url = "https://files.pythonhosted.org/packages/9c/59/013626bf8c78a5a5d9350d12e7697d3d4de951a75565496abd40ccd46bee/pydantic_core-2.41.5-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:915c3d10f81bec3a74fbd4faebe8391013ba61e5a1a8d48c4455b923bdda7858", size = 2324852, upload-time = "2025-11-04T13:40:48.575Z" }, - { url = "https://files.pythonhosted.org/packages/1a/d9/c248c103856f807ef70c18a4f986693a46a8ffe1602e5d361485da502d20/pydantic_core-2.41.5-cp313-cp313-win32.whl", hash = "sha256:650ae77860b45cfa6e2cdafc42618ceafab3a2d9a3811fcfbd3bbf8ac3c40d36", size = 1994679, upload-time = "2025-11-04T13:40:50.619Z" }, - { url = "https://files.pythonhosted.org/packages/9e/8b/341991b158ddab181cff136acd2552c9f35bd30380422a639c0671e99a91/pydantic_core-2.41.5-cp313-cp313-win_amd64.whl", hash = "sha256:79ec52ec461e99e13791ec6508c722742ad745571f234ea6255bed38c6480f11", size = 2019766, upload-time = "2025-11-04T13:40:52.631Z" }, - { url = "https://files.pythonhosted.org/packages/73/7d/f2f9db34af103bea3e09735bb40b021788a5e834c81eedb541991badf8f5/pydantic_core-2.41.5-cp313-cp313-win_arm64.whl", hash = "sha256:3f84d5c1b4ab906093bdc1ff10484838aca54ef08de4afa9de0f5f14d69639cd", size = 1981005, upload-time = "2025-11-04T13:40:54.734Z" }, - { url = "https://files.pythonhosted.org/packages/ea/28/46b7c5c9635ae96ea0fbb779e271a38129df2550f763937659ee6c5dbc65/pydantic_core-2.41.5-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:3f37a19d7ebcdd20b96485056ba9e8b304e27d9904d233d7b1015db320e51f0a", size = 2119622, upload-time = "2025-11-04T13:40:56.68Z" }, - { url = "https://files.pythonhosted.org/packages/74/1a/145646e5687e8d9a1e8d09acb278c8535ebe9e972e1f162ed338a622f193/pydantic_core-2.41.5-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:1d1d9764366c73f996edd17abb6d9d7649a7eb690006ab6adbda117717099b14", size = 1891725, upload-time = "2025-11-04T13:40:58.807Z" }, - { url = "https://files.pythonhosted.org/packages/23/04/e89c29e267b8060b40dca97bfc64a19b2a3cf99018167ea1677d96368273/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:25e1c2af0fce638d5f1988b686f3b3ea8cd7de5f244ca147c777769e798a9cd1", size = 1915040, upload-time = "2025-11-04T13:41:00.853Z" }, - { url = "https://files.pythonhosted.org/packages/84/a3/15a82ac7bd97992a82257f777b3583d3e84bdb06ba6858f745daa2ec8a85/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:506d766a8727beef16b7adaeb8ee6217c64fc813646b424d0804d67c16eddb66", size = 2063691, upload-time = "2025-11-04T13:41:03.504Z" }, - { url = "https://files.pythonhosted.org/packages/74/9b/0046701313c6ef08c0c1cf0e028c67c770a4e1275ca73131563c5f2a310a/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4819fa52133c9aa3c387b3328f25c1facc356491e6135b459f1de698ff64d869", size = 2213897, upload-time = "2025-11-04T13:41:05.804Z" }, - { url = "https://files.pythonhosted.org/packages/8a/cd/6bac76ecd1b27e75a95ca3a9a559c643b3afcd2dd62086d4b7a32a18b169/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b761d210c9ea91feda40d25b4efe82a1707da2ef62901466a42492c028553a2", size = 2333302, upload-time = "2025-11-04T13:41:07.809Z" }, - { url = "https://files.pythonhosted.org/packages/4c/d2/ef2074dc020dd6e109611a8be4449b98cd25e1b9b8a303c2f0fca2f2bcf7/pydantic_core-2.41.5-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:22f0fb8c1c583a3b6f24df2470833b40207e907b90c928cc8d3594b76f874375", size = 2064877, upload-time = "2025-11-04T13:41:09.827Z" }, - { url = "https://files.pythonhosted.org/packages/18/66/e9db17a9a763d72f03de903883c057b2592c09509ccfe468187f2a2eef29/pydantic_core-2.41.5-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:2782c870e99878c634505236d81e5443092fba820f0373997ff75f90f68cd553", size = 2180680, upload-time = "2025-11-04T13:41:12.379Z" }, - { url = "https://files.pythonhosted.org/packages/d3/9e/3ce66cebb929f3ced22be85d4c2399b8e85b622db77dad36b73c5387f8f8/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:0177272f88ab8312479336e1d777f6b124537d47f2123f89cb37e0accea97f90", size = 2138960, upload-time = "2025-11-04T13:41:14.627Z" }, - { url = "https://files.pythonhosted.org/packages/a6/62/205a998f4327d2079326b01abee48e502ea739d174f0a89295c481a2272e/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:63510af5e38f8955b8ee5687740d6ebf7c2a0886d15a6d65c32814613681bc07", size = 2339102, upload-time = "2025-11-04T13:41:16.868Z" }, - { url = "https://files.pythonhosted.org/packages/3c/0d/f05e79471e889d74d3d88f5bd20d0ed189ad94c2423d81ff8d0000aab4ff/pydantic_core-2.41.5-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:e56ba91f47764cc14f1daacd723e3e82d1a89d783f0f5afe9c364b8bb491ccdb", size = 2326039, upload-time = "2025-11-04T13:41:18.934Z" }, - { url = "https://files.pythonhosted.org/packages/ec/e1/e08a6208bb100da7e0c4b288eed624a703f4d129bde2da475721a80cab32/pydantic_core-2.41.5-cp314-cp314-win32.whl", hash = "sha256:aec5cf2fd867b4ff45b9959f8b20ea3993fc93e63c7363fe6851424c8a7e7c23", size = 1995126, upload-time = "2025-11-04T13:41:21.418Z" }, - { url = "https://files.pythonhosted.org/packages/48/5d/56ba7b24e9557f99c9237e29f5c09913c81eeb2f3217e40e922353668092/pydantic_core-2.41.5-cp314-cp314-win_amd64.whl", hash = "sha256:8e7c86f27c585ef37c35e56a96363ab8de4e549a95512445b85c96d3e2f7c1bf", size = 2015489, upload-time = "2025-11-04T13:41:24.076Z" }, - { url = "https://files.pythonhosted.org/packages/4e/bb/f7a190991ec9e3e0ba22e4993d8755bbc4a32925c0b5b42775c03e8148f9/pydantic_core-2.41.5-cp314-cp314-win_arm64.whl", hash = "sha256:e672ba74fbc2dc8eea59fb6d4aed6845e6905fc2a8afe93175d94a83ba2a01a0", size = 1977288, upload-time = "2025-11-04T13:41:26.33Z" }, - { url = "https://files.pythonhosted.org/packages/92/ed/77542d0c51538e32e15afe7899d79efce4b81eee631d99850edc2f5e9349/pydantic_core-2.41.5-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:8566def80554c3faa0e65ac30ab0932b9e3a5cd7f8323764303d468e5c37595a", size = 2120255, upload-time = "2025-11-04T13:41:28.569Z" }, - { url = "https://files.pythonhosted.org/packages/bb/3d/6913dde84d5be21e284439676168b28d8bbba5600d838b9dca99de0fad71/pydantic_core-2.41.5-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:b80aa5095cd3109962a298ce14110ae16b8c1aece8b72f9dafe81cf597ad80b3", size = 1863760, upload-time = "2025-11-04T13:41:31.055Z" }, - { url = "https://files.pythonhosted.org/packages/5a/f0/e5e6b99d4191da102f2b0eb9687aaa7f5bea5d9964071a84effc3e40f997/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3006c3dd9ba34b0c094c544c6006cc79e87d8612999f1a5d43b769b89181f23c", size = 1878092, upload-time = "2025-11-04T13:41:33.21Z" }, - { url = "https://files.pythonhosted.org/packages/71/48/36fb760642d568925953bcc8116455513d6e34c4beaa37544118c36aba6d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:72f6c8b11857a856bcfa48c86f5368439f74453563f951e473514579d44aa612", size = 2053385, upload-time = "2025-11-04T13:41:35.508Z" }, - { url = "https://files.pythonhosted.org/packages/20/25/92dc684dd8eb75a234bc1c764b4210cf2646479d54b47bf46061657292a8/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5cb1b2f9742240e4bb26b652a5aeb840aa4b417c7748b6f8387927bc6e45e40d", size = 2218832, upload-time = "2025-11-04T13:41:37.732Z" }, - { url = "https://files.pythonhosted.org/packages/e2/09/f53e0b05023d3e30357d82eb35835d0f6340ca344720a4599cd663dca599/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd3d54f38609ff308209bd43acea66061494157703364ae40c951f83ba99a1a9", size = 2327585, upload-time = "2025-11-04T13:41:40Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4e/2ae1aa85d6af35a39b236b1b1641de73f5a6ac4d5a7509f77b814885760c/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ff4321e56e879ee8d2a879501c8e469414d948f4aba74a2d4593184eb326660", size = 2041078, upload-time = "2025-11-04T13:41:42.323Z" }, - { url = "https://files.pythonhosted.org/packages/cd/13/2e215f17f0ef326fc72afe94776edb77525142c693767fc347ed6288728d/pydantic_core-2.41.5-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0d2568a8c11bf8225044aa94409e21da0cb09dcdafe9ecd10250b2baad531a9", size = 2173914, upload-time = "2025-11-04T13:41:45.221Z" }, - { url = "https://files.pythonhosted.org/packages/02/7a/f999a6dcbcd0e5660bc348a3991c8915ce6599f4f2c6ac22f01d7a10816c/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:a39455728aabd58ceabb03c90e12f71fd30fa69615760a075b9fec596456ccc3", size = 2129560, upload-time = "2025-11-04T13:41:47.474Z" }, - { url = "https://files.pythonhosted.org/packages/3a/b1/6c990ac65e3b4c079a4fb9f5b05f5b013afa0f4ed6780a3dd236d2cbdc64/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:239edca560d05757817c13dc17c50766136d21f7cd0fac50295499ae24f90fdf", size = 2329244, upload-time = "2025-11-04T13:41:49.992Z" }, - { url = "https://files.pythonhosted.org/packages/d9/02/3c562f3a51afd4d88fff8dffb1771b30cfdfd79befd9883ee094f5b6c0d8/pydantic_core-2.41.5-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:2a5e06546e19f24c6a96a129142a75cee553cc018ffee48a460059b1185f4470", size = 2331955, upload-time = "2025-11-04T13:41:54.079Z" }, - { url = "https://files.pythonhosted.org/packages/5c/96/5fb7d8c3c17bc8c62fdb031c47d77a1af698f1d7a406b0f79aaa1338f9ad/pydantic_core-2.41.5-cp314-cp314t-win32.whl", hash = "sha256:b4ececa40ac28afa90871c2cc2b9ffd2ff0bf749380fbdf57d165fd23da353aa", size = 1988906, upload-time = "2025-11-04T13:41:56.606Z" }, - { url = "https://files.pythonhosted.org/packages/22/ed/182129d83032702912c2e2d8bbe33c036f342cc735737064668585dac28f/pydantic_core-2.41.5-cp314-cp314t-win_amd64.whl", hash = "sha256:80aa89cad80b32a912a65332f64a4450ed00966111b6615ca6816153d3585a8c", size = 1981607, upload-time = "2025-11-04T13:41:58.889Z" }, - { url = "https://files.pythonhosted.org/packages/9f/ed/068e41660b832bb0b1aa5b58011dea2a3fe0ba7861ff38c4d4904c1c1a99/pydantic_core-2.41.5-cp314-cp314t-win_arm64.whl", hash = "sha256:35b44f37a3199f771c3eaa53051bc8a70cd7b54f333531c59e29fd4db5d15008", size = 1974769, upload-time = "2025-11-04T13:42:01.186Z" }, - { url = "https://files.pythonhosted.org/packages/11/72/90fda5ee3b97e51c494938a4a44c3a35a9c96c19bba12372fb9c634d6f57/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:b96d5f26b05d03cc60f11a7761a5ded1741da411e7fe0909e27a5e6a0cb7b034", size = 2115441, upload-time = "2025-11-04T13:42:39.557Z" }, - { url = "https://files.pythonhosted.org/packages/1f/53/8942f884fa33f50794f119012dc6a1a02ac43a56407adaac20463df8e98f/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:634e8609e89ceecea15e2d61bc9ac3718caaaa71963717bf3c8f38bfde64242c", size = 1930291, upload-time = "2025-11-04T13:42:42.169Z" }, - { url = "https://files.pythonhosted.org/packages/79/c8/ecb9ed9cd942bce09fc888ee960b52654fbdbede4ba6c2d6e0d3b1d8b49c/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:93e8740d7503eb008aa2df04d3b9735f845d43ae845e6dcd2be0b55a2da43cd2", size = 1948632, upload-time = "2025-11-04T13:42:44.564Z" }, - { url = "https://files.pythonhosted.org/packages/2e/1b/687711069de7efa6af934e74f601e2a4307365e8fdc404703afc453eab26/pydantic_core-2.41.5-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f15489ba13d61f670dcc96772e733aad1a6f9c429cc27574c6cdaed82d0146ad", size = 2138905, upload-time = "2025-11-04T13:42:47.156Z" }, - { url = "https://files.pythonhosted.org/packages/09/32/59b0c7e63e277fa7911c2fc70ccfb45ce4b98991e7ef37110663437005af/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:7da7087d756b19037bc2c06edc6c170eeef3c3bafcb8f532ff17d64dc427adfd", size = 2110495, upload-time = "2025-11-04T13:42:49.689Z" }, - { url = "https://files.pythonhosted.org/packages/aa/81/05e400037eaf55ad400bcd318c05bb345b57e708887f07ddb2d20e3f0e98/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:aabf5777b5c8ca26f7824cb4a120a740c9588ed58df9b2d196ce92fba42ff8dc", size = 1915388, upload-time = "2025-11-04T13:42:52.215Z" }, - { url = "https://files.pythonhosted.org/packages/6e/0d/e3549b2399f71d56476b77dbf3cf8937cec5cd70536bdc0e374a421d0599/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c007fe8a43d43b3969e8469004e9845944f1a80e6acd47c150856bb87f230c56", size = 1942879, upload-time = "2025-11-04T13:42:56.483Z" }, - { url = "https://files.pythonhosted.org/packages/f7/07/34573da085946b6a313d7c42f82f16e8920bfd730665de2d11c0c37a74b5/pydantic_core-2.41.5-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76d0819de158cd855d1cbb8fcafdf6f5cf1eb8e470abe056d5d161106e38062b", size = 2139017, upload-time = "2025-11-04T13:42:59.471Z" }, - { url = "https://files.pythonhosted.org/packages/5f/9b/1b3f0e9f9305839d7e84912f9e8bfbd191ed1b1ef48083609f0dabde978c/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:b2379fa7ed44ddecb5bfe4e48577d752db9fc10be00a6b7446e9663ba143de26", size = 2101980, upload-time = "2025-11-04T13:43:25.97Z" }, - { url = "https://files.pythonhosted.org/packages/a4/ed/d71fefcb4263df0da6a85b5d8a7508360f2f2e9b3bf5814be9c8bccdccc1/pydantic_core-2.41.5-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:266fb4cbf5e3cbd0b53669a6d1b039c45e3ce651fd5442eff4d07c2cc8d66808", size = 1923865, upload-time = "2025-11-04T13:43:28.763Z" }, - { url = "https://files.pythonhosted.org/packages/ce/3a/626b38db460d675f873e4444b4bb030453bbe7b4ba55df821d026a0493c4/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:58133647260ea01e4d0500089a8c4f07bd7aa6ce109682b1426394988d8aaacc", size = 2134256, upload-time = "2025-11-04T13:43:31.71Z" }, - { url = "https://files.pythonhosted.org/packages/83/d9/8412d7f06f616bbc053d30cb4e5f76786af3221462ad5eee1f202021eb4e/pydantic_core-2.41.5-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:287dad91cfb551c363dc62899a80e9e14da1f0e2b6ebde82c806612ca2a13ef1", size = 2174762, upload-time = "2025-11-04T13:43:34.744Z" }, - { url = "https://files.pythonhosted.org/packages/55/4c/162d906b8e3ba3a99354e20faa1b49a85206c47de97a639510a0e673f5da/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:03b77d184b9eb40240ae9fd676ca364ce1085f203e1b1256f8ab9984dca80a84", size = 2143141, upload-time = "2025-11-04T13:43:37.701Z" }, - { url = "https://files.pythonhosted.org/packages/1f/f2/f11dd73284122713f5f89fc940f370d035fa8e1e078d446b3313955157fe/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:a668ce24de96165bb239160b3d854943128f4334822900534f2fe947930e5770", size = 2330317, upload-time = "2025-11-04T13:43:40.406Z" }, - { url = "https://files.pythonhosted.org/packages/88/9d/b06ca6acfe4abb296110fb1273a4d848a0bfb2ff65f3ee92127b3244e16b/pydantic_core-2.41.5-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:f14f8f046c14563f8eb3f45f499cc658ab8d10072961e07225e507adb700e93f", size = 2316992, upload-time = "2025-11-04T13:43:43.602Z" }, - { url = "https://files.pythonhosted.org/packages/36/c7/cfc8e811f061c841d7990b0201912c3556bfeb99cdcb7ed24adc8d6f8704/pydantic_core-2.41.5-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:56121965f7a4dc965bff783d70b907ddf3d57f6eba29b6d2e5dabfaf07799c51", size = 2145302, upload-time = "2025-11-04T13:43:46.64Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/2a/ef/f7abb56c49382a246fd2ce9c799691e3c3e7175ec74b14d99e798bcddb1a/pydantic_core-2.46.3.tar.gz", hash = "sha256:41c178f65b8c29807239d47e6050262eb6bf84eb695e41101e62e38df4a5bc2c", size = 471412, upload-time = "2026-04-20T14:40:56.672Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/22/a2/1ba90a83e85a3f94c796b184f3efde9c72f2830dcda493eea8d59ba78e6d/pydantic_core-2.46.3-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:ab124d49d0459b2373ecf54118a45c28a1e6d4192a533fbc915e70f556feb8e5", size = 2106740, upload-time = "2026-04-20T14:41:20.932Z" }, + { url = "https://files.pythonhosted.org/packages/b6/f6/99ae893c89a0b9d3daec9f95487aa676709aa83f67643b3f0abaf4ab628a/pydantic_core-2.46.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:cca67d52a5c7a16aed2b3999e719c4bcf644074eac304a5d3d62dd70ae7d4b2c", size = 1948293, upload-time = "2026-04-20T14:43:42.115Z" }, + { url = "https://files.pythonhosted.org/packages/3e/b8/2e8e636dc9e3f16c2e16bf0849e24be82c5ee82c603c65fc0326666328fc/pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c024e08c0ba23e6fd68c771a521e9d6a792f2ebb0fa734296b36394dc30390e", size = 1973222, upload-time = "2026-04-20T14:41:57.841Z" }, + { url = "https://files.pythonhosted.org/packages/34/36/0e730beec4d83c5306f417afbd82ff237d9a21e83c5edf675f31ed84c1fe/pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:6645ce7eec4928e29a1e3b3d5c946621d105d3e79f0c9cddf07c2a9770949287", size = 2053852, upload-time = "2026-04-20T14:40:43.077Z" }, + { url = "https://files.pythonhosted.org/packages/4b/f0/3071131f47e39136a17814576e0fada9168569f7f8c0e6ac4d1ede6a4958/pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a712c7118e6c5ea96562f7b488435172abb94a3c53c22c9efc1412264a45cbbe", size = 2221134, upload-time = "2026-04-20T14:43:03.349Z" }, + { url = "https://files.pythonhosted.org/packages/2f/a9/a2dc023eec5aa4b02a467874bad32e2446957d2adcab14e107eab502e978/pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:69a868ef3ff206343579021c40faf3b1edc64b1cc508ff243a28b0a514ccb050", size = 2279785, upload-time = "2026-04-20T14:41:19.285Z" }, + { url = "https://files.pythonhosted.org/packages/0a/44/93f489d16fb63fbd41c670441536541f6e8cfa1e5a69f40bc9c5d30d8c90/pydantic_core-2.46.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cc7e8c32db809aa0f6ea1d6869ebc8518a65d5150fdfad8bcae6a49ae32a22e2", size = 2089404, upload-time = "2026-04-20T14:43:10.108Z" }, + { url = "https://files.pythonhosted.org/packages/2a/78/8692e3aa72b2d004f7a5d937f1dfdc8552ba26caf0bec75f342c40f00dec/pydantic_core-2.46.3-cp311-cp311-manylinux_2_31_riscv64.whl", hash = "sha256:3481bd1341dc85779ee506bc8e1196a277ace359d89d28588a9468c3ecbe63fa", size = 2114898, upload-time = "2026-04-20T14:44:51.475Z" }, + { url = "https://files.pythonhosted.org/packages/6a/62/e83133f2e7832532060175cebf1f13748f4c7e7e7165cdd1f611f174494b/pydantic_core-2.46.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8690eba565c6d68ffd3a8655525cbdd5246510b44a637ee2c6c03a7ebfe64d3c", size = 2157856, upload-time = "2026-04-20T14:43:46.64Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ec/6a500e3ad7718ee50583fae79c8651f5d37e3abce1fa9ae177ae65842c53/pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4de88889d7e88d50d40ee5b39d5dac0bcaef9ba91f7e536ac064e6b2834ecccf", size = 2180168, upload-time = "2026-04-20T14:42:00.302Z" }, + { url = "https://files.pythonhosted.org/packages/d8/53/8267811054b1aa7fc1dc7ded93812372ef79a839f5e23558136a6afbfde1/pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_armv7l.whl", hash = "sha256:e480080975c1ef7f780b8f99ed72337e7cc5efea2e518a20a692e8e7b278eb8b", size = 2322885, upload-time = "2026-04-20T14:41:05.253Z" }, + { url = "https://files.pythonhosted.org/packages/c8/c1/1c0acdb3aa0856ddc4ecc55214578f896f2de16f400cf51627eb3c26c1c4/pydantic_core-2.46.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:de3a5c376f8cd94da9a1b8fd3dd1c16c7a7b216ed31dc8ce9fd7a22bf13b836e", size = 2360328, upload-time = "2026-04-20T14:41:43.991Z" }, + { url = "https://files.pythonhosted.org/packages/f0/d0/ef39cd0f4a926814f360e71c1adeab48ad214d9727e4deb48eedfb5bce1a/pydantic_core-2.46.3-cp311-cp311-win32.whl", hash = "sha256:fc331a5314ffddd5385b9ee9d0d2fee0b13c27e0e02dad71b1ae5d6561f51eeb", size = 1979464, upload-time = "2026-04-20T14:43:12.215Z" }, + { url = "https://files.pythonhosted.org/packages/18/9c/f41951b0d858e343f1cf09398b2a7b3014013799744f2c4a8ad6a3eec4f2/pydantic_core-2.46.3-cp311-cp311-win_amd64.whl", hash = "sha256:b5b9c6cf08a8a5e502698f5e153056d12c34b8fb30317e0c5fd06f45162a6346", size = 2070837, upload-time = "2026-04-20T14:41:47.707Z" }, + { url = "https://files.pythonhosted.org/packages/9f/1e/264a17cd582f6ed50950d4d03dd5fefd84e570e238afe1cb3e25cf238769/pydantic_core-2.46.3-cp311-cp311-win_arm64.whl", hash = "sha256:5dfd51cf457482f04ec49491811a2b8fd5b843b64b11eecd2d7a1ee596ea78a6", size = 2053647, upload-time = "2026-04-20T14:42:27.535Z" }, + { url = "https://files.pythonhosted.org/packages/4b/cb/5b47425556ecc1f3fe18ed2a0083188aa46e1dd812b06e406475b3a5d536/pydantic_core-2.46.3-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:b11b59b3eee90a80a36701ddb4576d9ae31f93f05cb9e277ceaa09e6bf074a67", size = 2101946, upload-time = "2026-04-20T14:40:52.581Z" }, + { url = "https://files.pythonhosted.org/packages/a1/4f/2fb62c2267cae99b815bbf4a7b9283812c88ca3153ef29f7707200f1d4e5/pydantic_core-2.46.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:af8653713055ea18a3abc1537fe2ebc42f5b0bbb768d1eb79fd74eb47c0ac089", size = 1951612, upload-time = "2026-04-20T14:42:42.996Z" }, + { url = "https://files.pythonhosted.org/packages/50/6e/b7348fd30d6556d132cddd5bd79f37f96f2601fe0608afac4f5fb01ec0b3/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:75a519dab6d63c514f3a81053e5266c549679e4aa88f6ec57f2b7b854aceb1b0", size = 1977027, upload-time = "2026-04-20T14:42:02.001Z" }, + { url = "https://files.pythonhosted.org/packages/82/11/31d60ee2b45540d3fb0b29302a393dbc01cd771c473f5b5147bcd353e593/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:a6cd87cb1575b1ad05ba98894c5b5c96411ef678fa2f6ed2576607095b8d9789", size = 2063008, upload-time = "2026-04-20T14:44:17.952Z" }, + { url = "https://files.pythonhosted.org/packages/8a/db/3a9d1957181b59258f44a2300ab0f0be9d1e12d662a4f57bb31250455c52/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f80a55484b8d843c8ada81ebf70a682f3f00a3d40e378c06cf17ecb44d280d7d", size = 2233082, upload-time = "2026-04-20T14:40:57.934Z" }, + { url = "https://files.pythonhosted.org/packages/9c/e1/3277c38792aeb5cfb18c2f0c5785a221d9ff4e149abbe1184d53d5f72273/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3861f1731b90c50a3266316b9044f5c9b405eecb8e299b0a7120596334e4fe9c", size = 2304615, upload-time = "2026-04-20T14:42:12.584Z" }, + { url = "https://files.pythonhosted.org/packages/5e/d5/e3d9717c9eba10855325650afd2a9cba8e607321697f18953af9d562da2f/pydantic_core-2.46.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fb528e295ed31570ac3dcc9bfdd6e0150bc11ce6168ac87a8082055cf1a67395", size = 2094380, upload-time = "2026-04-20T14:43:05.522Z" }, + { url = "https://files.pythonhosted.org/packages/a1/20/abac35dedcbfd66c6f0b03e4e3564511771d6c9b7ede10a362d03e110d9b/pydantic_core-2.46.3-cp312-cp312-manylinux_2_31_riscv64.whl", hash = "sha256:367508faa4973b992b271ba1494acaab36eb7e8739d1e47be5035fb1ea225396", size = 2135429, upload-time = "2026-04-20T14:41:55.549Z" }, + { url = "https://files.pythonhosted.org/packages/6c/a5/41bfd1df69afad71b5cf0535055bccc73022715ad362edbc124bc1e021d7/pydantic_core-2.46.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:5ad3c826fe523e4becf4fe39baa44286cff85ef137c729a2c5e269afbfd0905d", size = 2174582, upload-time = "2026-04-20T14:41:45.96Z" }, + { url = "https://files.pythonhosted.org/packages/79/65/38d86ea056b29b2b10734eb23329b7a7672ca604df4f2b6e9c02d4ee22fe/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ec638c5d194ef8af27db69f16c954a09797c0dc25015ad6123eb2c73a4d271ca", size = 2187533, upload-time = "2026-04-20T14:40:55.367Z" }, + { url = "https://files.pythonhosted.org/packages/b6/55/a1129141678a2026badc539ad1dee0a71d06f54c2f06a4bd68c030ac781b/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_armv7l.whl", hash = "sha256:28ed528c45446062ee66edb1d33df5d88828ae167de76e773a3c7f64bd14e976", size = 2332985, upload-time = "2026-04-20T14:44:13.05Z" }, + { url = "https://files.pythonhosted.org/packages/d7/60/cb26f4077719f709e54819f4e8e1d43f4091f94e285eb6bd21e1190a7b7c/pydantic_core-2.46.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aed19d0c783886d5bd86d80ae5030006b45e28464218747dcf83dabfdd092c7b", size = 2373670, upload-time = "2026-04-20T14:41:53.421Z" }, + { url = "https://files.pythonhosted.org/packages/6b/7e/c3f21882bdf1d8d086876f81b5e296206c69c6082551d776895de7801fa0/pydantic_core-2.46.3-cp312-cp312-win32.whl", hash = "sha256:06d5d8820cbbdb4147578c1fe7ffcd5b83f34508cb9f9ab76e807be7db6ff0a4", size = 1966722, upload-time = "2026-04-20T14:44:30.588Z" }, + { url = "https://files.pythonhosted.org/packages/57/be/6b5e757b859013ebfbd7adba02f23b428f37c86dcbf78b5bb0b4ffd36e99/pydantic_core-2.46.3-cp312-cp312-win_amd64.whl", hash = "sha256:c3212fda0ee959c1dd04c60b601ec31097aaa893573a3a1abd0a47bcac2968c1", size = 2072970, upload-time = "2026-04-20T14:42:54.248Z" }, + { url = "https://files.pythonhosted.org/packages/bf/f8/a989b21cc75e9a32d24192ef700eea606521221a89faa40c919ce884f2b1/pydantic_core-2.46.3-cp312-cp312-win_arm64.whl", hash = "sha256:f1f8338dd7a7f31761f1f1a3c47503a9a3b34eea3c8b01fa6ee96408affb5e72", size = 2035963, upload-time = "2026-04-20T14:44:20.4Z" }, + { url = "https://files.pythonhosted.org/packages/9b/3c/9b5e8eb9821936d065439c3b0fb1490ffa64163bfe7e1595985a47896073/pydantic_core-2.46.3-cp313-cp313-macosx_10_12_x86_64.whl", hash = "sha256:12bc98de041458b80c86c56b24df1d23832f3e166cbaff011f25d187f5c62c37", size = 2102109, upload-time = "2026-04-20T14:41:24.219Z" }, + { url = "https://files.pythonhosted.org/packages/91/97/1c41d1f5a19f241d8069f1e249853bcce378cdb76eec8ab636d7bc426280/pydantic_core-2.46.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:85348b8f89d2c3508b65b16c3c33a4da22b8215138d8b996912bb1532868885f", size = 1951820, upload-time = "2026-04-20T14:42:14.236Z" }, + { url = "https://files.pythonhosted.org/packages/30/b4/d03a7ae14571bc2b6b3c7b122441154720619afe9a336fa3a95434df5e2f/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1105677a6df914b1fb71a81b96c8cce7726857e1717d86001f29be06a25ee6f8", size = 1977785, upload-time = "2026-04-20T14:42:31.648Z" }, + { url = "https://files.pythonhosted.org/packages/ae/0c/4086f808834b59e3c8f1aa26df8f4b6d998cdcf354a143d18ef41529d1fe/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:87082cd65669a33adeba5470769e9704c7cf026cc30afb9cc77fd865578ebaad", size = 2062761, upload-time = "2026-04-20T14:40:37.093Z" }, + { url = "https://files.pythonhosted.org/packages/fa/71/a649be5a5064c2df0db06e0a512c2281134ed2fcc981f52a657936a7527c/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:60e5f66e12c4f5212d08522963380eaaeac5ebd795826cfd19b2dfb0c7a52b9c", size = 2232989, upload-time = "2026-04-20T14:42:59.254Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/7756e75763e810b3a710f4724441d1ecc5883b94aacb07ca71c5fb5cfb69/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b6cdf19bf84128d5e7c37e8a73a0c5c10d51103a650ac585d42dd6ae233f2b7f", size = 2303975, upload-time = "2026-04-20T14:41:32.287Z" }, + { url = "https://files.pythonhosted.org/packages/6c/35/68a762e0c1e31f35fa0dac733cbd9f5b118042853698de9509c8e5bf128b/pydantic_core-2.46.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031bb17f4885a43773c8c763089499f242aee2ea85cf17154168775dccdecf35", size = 2095325, upload-time = "2026-04-20T14:42:47.685Z" }, + { url = "https://files.pythonhosted.org/packages/77/bf/1bf8c9a8e91836c926eae5e3e51dce009bf495a60ca56060689d3df3f340/pydantic_core-2.46.3-cp313-cp313-manylinux_2_31_riscv64.whl", hash = "sha256:bcf2a8b2982a6673693eae7348ef3d8cf3979c1d63b54fca7c397a635cc68687", size = 2133368, upload-time = "2026-04-20T14:41:22.766Z" }, + { url = "https://files.pythonhosted.org/packages/e5/50/87d818d6bab915984995157ceb2380f5aac4e563dddbed6b56f0ed057aba/pydantic_core-2.46.3-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:28e8cf2f52d72ced402a137145923a762cbb5081e48b34312f7a0c8f55928ec3", size = 2173908, upload-time = "2026-04-20T14:42:52.044Z" }, + { url = "https://files.pythonhosted.org/packages/91/88/a311fb306d0bd6185db41fa14ae888fb81d0baf648a761ae760d30819d33/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:17eaface65d9fc5abb940003020309c1bf7a211f5f608d7870297c367e6f9022", size = 2186422, upload-time = "2026-04-20T14:43:29.55Z" }, + { url = "https://files.pythonhosted.org/packages/8f/79/28fd0d81508525ab2054fef7c77a638c8b5b0afcbbaeee493cf7c3fef7e1/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_armv7l.whl", hash = "sha256:93fd339f23408a07e98950a89644f92c54d8729719a40b30c0a30bb9ebc55d23", size = 2332709, upload-time = "2026-04-20T14:42:16.134Z" }, + { url = "https://files.pythonhosted.org/packages/b3/21/795bf5fe5c0f379308b8ef19c50dedab2e7711dbc8d0c2acf08f1c7daa05/pydantic_core-2.46.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:23cbdb3aaa74dfe0837975dbf69b469753bbde8eacace524519ffdb6b6e89eb7", size = 2372428, upload-time = "2026-04-20T14:41:10.974Z" }, + { url = "https://files.pythonhosted.org/packages/45/b3/ed14c659cbe7605e3ef063077680a64680aec81eb1a04763a05190d49b7f/pydantic_core-2.46.3-cp313-cp313-win32.whl", hash = "sha256:610eda2e3838f401105e6326ca304f5da1e15393ae25dacae5c5c63f2c275b13", size = 1965601, upload-time = "2026-04-20T14:41:42.128Z" }, + { url = "https://files.pythonhosted.org/packages/ef/bb/adb70d9a762ddd002d723fbf1bd492244d37da41e3af7b74ad212609027e/pydantic_core-2.46.3-cp313-cp313-win_amd64.whl", hash = "sha256:68cc7866ed863db34351294187f9b729964c371ba33e31c26f478471c52e1ed0", size = 2071517, upload-time = "2026-04-20T14:43:36.096Z" }, + { url = "https://files.pythonhosted.org/packages/52/eb/66faefabebfe68bd7788339c9c9127231e680b11906368c67ce112fdb47f/pydantic_core-2.46.3-cp313-cp313-win_arm64.whl", hash = "sha256:f64b5537ac62b231572879cd08ec05600308636a5d63bcbdb15063a466977bec", size = 2035802, upload-time = "2026-04-20T14:43:38.507Z" }, + { url = "https://files.pythonhosted.org/packages/7f/db/a7bcb4940183fda36022cd18ba8dd12f2dff40740ec7b58ce7457befa416/pydantic_core-2.46.3-cp314-cp314-macosx_10_12_x86_64.whl", hash = "sha256:afa3aa644f74e290cdede48a7b0bee37d1c35e71b05105f6b340d484af536d9b", size = 2097614, upload-time = "2026-04-20T14:44:38.374Z" }, + { url = "https://files.pythonhosted.org/packages/24/35/e4066358a22e3e99519db370494c7528f5a2aa1367370e80e27e20283543/pydantic_core-2.46.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:ced3310e51aa425f7f77da8bbbb5212616655bedbe82c70944320bc1dbe5e018", size = 1951896, upload-time = "2026-04-20T14:40:53.996Z" }, + { url = "https://files.pythonhosted.org/packages/87/92/37cf4049d1636996e4b888c05a501f40a43ff218983a551d57f9d5e14f0d/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e29908922ce9da1a30b4da490bd1d3d82c01dcfdf864d2a74aacee674d0bfa34", size = 1979314, upload-time = "2026-04-20T14:41:49.446Z" }, + { url = "https://files.pythonhosted.org/packages/d8/36/9ff4d676dfbdfb2d591cf43f3d90ded01e15b1404fd101180ed2d62a2fd3/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:0c9ff69140423eea8ed2d5477df3ba037f671f5e897d206d921bc9fdc39613e7", size = 2056133, upload-time = "2026-04-20T14:42:23.574Z" }, + { url = "https://files.pythonhosted.org/packages/bc/f0/405b442a4d7ba855b06eec8b2bf9c617d43b8432d099dfdc7bf999293495/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b675ab0a0d5b1c8fdb81195dc5bcefea3f3c240871cdd7ff9a2de8aa50772eb2", size = 2228726, upload-time = "2026-04-20T14:44:22.816Z" }, + { url = "https://files.pythonhosted.org/packages/e7/f8/65cd92dd5a0bd89ba277a98ecbfaf6fc36bbd3300973c7a4b826d6ab1391/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0087084960f209a9a4af50ecd1fb063d9ad3658c07bb81a7a53f452dacbfb2ba", size = 2301214, upload-time = "2026-04-20T14:44:48.792Z" }, + { url = "https://files.pythonhosted.org/packages/fd/86/ef96a4c6e79e7a2d0410826a68fbc0eccc0fd44aa733be199d5fcac3bb87/pydantic_core-2.46.3-cp314-cp314-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ed42e6cc8e1b0e2b9b96e2276bad70ae625d10d6d524aed0c93de974ae029f9f", size = 2099927, upload-time = "2026-04-20T14:41:40.196Z" }, + { url = "https://files.pythonhosted.org/packages/6d/53/269caf30e0096e0a8a8f929d1982a27b3879872cca2d917d17c2f9fdf4fe/pydantic_core-2.46.3-cp314-cp314-manylinux_2_31_riscv64.whl", hash = "sha256:f1771ce258afb3e4201e67d154edbbae712a76a6081079fe247c2f53c6322c22", size = 2128789, upload-time = "2026-04-20T14:41:15.868Z" }, + { url = "https://files.pythonhosted.org/packages/00/b0/1a6d9b6a587e118482910c244a1c5acf4d192604174132efd12bf0ac486f/pydantic_core-2.46.3-cp314-cp314-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:a7610b6a5242a6c736d8ad47fd5fff87fcfe8f833b281b1c409c3d6835d9227f", size = 2173815, upload-time = "2026-04-20T14:44:25.152Z" }, + { url = "https://files.pythonhosted.org/packages/87/56/e7e00d4041a7e62b5a40815590114db3b535bf3ca0bf4dca9f16cef25246/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_aarch64.whl", hash = "sha256:ff5e7783bcc5476e1db448bf268f11cb257b1c276d3e89f00b5727be86dd0127", size = 2181608, upload-time = "2026-04-20T14:41:28.933Z" }, + { url = "https://files.pythonhosted.org/packages/e8/22/4bd23c3d41f7c185d60808a1de83c76cf5aeabf792f6c636a55c3b1ec7f9/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_armv7l.whl", hash = "sha256:9d2e32edcc143bc01e95300671915d9ca052d4f745aa0a49c48d4803f8a85f2c", size = 2326968, upload-time = "2026-04-20T14:42:03.962Z" }, + { url = "https://files.pythonhosted.org/packages/24/ac/66cd45129e3915e5ade3b292cb3bc7fd537f58f8f8dbdaba6170f7cabb74/pydantic_core-2.46.3-cp314-cp314-musllinux_1_1_x86_64.whl", hash = "sha256:6e42d83d1c6b87fa56b521479cff237e626a292f3b31b6345c15a99121b454c1", size = 2369842, upload-time = "2026-04-20T14:41:35.52Z" }, + { url = "https://files.pythonhosted.org/packages/a2/51/dd4248abb84113615473aa20d5545b7c4cd73c8644003b5259686f93996c/pydantic_core-2.46.3-cp314-cp314-win32.whl", hash = "sha256:07bc6d2a28c3adb4f7c6ae46aa4f2d2929af127f587ed44057af50bf1ce0f505", size = 1959661, upload-time = "2026-04-20T14:41:00.042Z" }, + { url = "https://files.pythonhosted.org/packages/20/eb/59980e5f1ae54a3b86372bd9f0fa373ea2d402e8cdcd3459334430f91e91/pydantic_core-2.46.3-cp314-cp314-win_amd64.whl", hash = "sha256:8940562319bc621da30714617e6a7eaa6b98c84e8c685bcdc02d7ed5e7c7c44e", size = 2071686, upload-time = "2026-04-20T14:43:16.471Z" }, + { url = "https://files.pythonhosted.org/packages/8c/db/1cf77e5247047dfee34bc01fa9bca134854f528c8eb053e144298893d370/pydantic_core-2.46.3-cp314-cp314-win_arm64.whl", hash = "sha256:5dcbbcf4d22210ced8f837c96db941bdb078f419543472aca5d9a0bb7cddc7df", size = 2026907, upload-time = "2026-04-20T14:43:31.732Z" }, + { url = "https://files.pythonhosted.org/packages/57/c0/b3df9f6a543276eadba0a48487b082ca1f201745329d97dbfa287034a230/pydantic_core-2.46.3-cp314-cp314t-macosx_10_12_x86_64.whl", hash = "sha256:d0fe3dce1e836e418f912c1ad91c73357d03e556a4d286f441bf34fed2dbeecf", size = 2095047, upload-time = "2026-04-20T14:42:37.982Z" }, + { url = "https://files.pythonhosted.org/packages/66/57/886a938073b97556c168fd99e1a7305bb363cd30a6d2c76086bf0587b32a/pydantic_core-2.46.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:9ce92e58abc722dac1bf835a6798a60b294e48eb0e625ec9fd994b932ac5feee", size = 1934329, upload-time = "2026-04-20T14:43:49.655Z" }, + { url = "https://files.pythonhosted.org/packages/0b/7c/b42eaa5c34b13b07ecb51da21761297a9b8eb43044c864a035999998f328/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03e6467f0f5ab796a486146d1b887b2dc5e5f9b3288898c1b1c3ad974e53e4a", size = 1974847, upload-time = "2026-04-20T14:42:10.737Z" }, + { url = "https://files.pythonhosted.org/packages/e6/9b/92b42db6543e7de4f99ae977101a2967b63122d4b6cf7773812da2d7d5b5/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2798b6ba041b9d70acfb9071a2ea13c8456dd1e6a5555798e41ba7b0790e329c", size = 2041742, upload-time = "2026-04-20T14:40:44.262Z" }, + { url = "https://files.pythonhosted.org/packages/0f/19/46fbe1efabb5aa2834b43b9454e70f9a83ad9c338c1291e48bdc4fecf167/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9be3e221bdc6d69abf294dcf7aff6af19c31a5cdcc8f0aa3b14be29df4bd03b1", size = 2236235, upload-time = "2026-04-20T14:41:27.307Z" }, + { url = "https://files.pythonhosted.org/packages/77/da/b3f95bc009ad60ec53120f5d16c6faa8cabdbe8a20d83849a1f2b8728148/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f13936129ce841f2a5ddf6f126fea3c43cd128807b5a59588c37cf10178c2e64", size = 2282633, upload-time = "2026-04-20T14:44:33.271Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6e/401336117722e28f32fb8220df676769d28ebdf08f2f4469646d404c43a3/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28b5f2ef03416facccb1c6ef744c69793175fd27e44ef15669201601cf423acb", size = 2109679, upload-time = "2026-04-20T14:44:41.065Z" }, + { url = "https://files.pythonhosted.org/packages/fc/53/b289f9bc8756a32fe718c46f55afaeaf8d489ee18d1a1e7be1db73f42cc4/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_31_riscv64.whl", hash = "sha256:830d1247d77ad23852314f069e9d7ddafeec5f684baf9d7e7065ed46a049c4e6", size = 2108342, upload-time = "2026-04-20T14:42:50.144Z" }, + { url = "https://files.pythonhosted.org/packages/10/5b/8292fc7c1f9111f1b2b7c1b0dcf1179edcd014fc3ea4517499f50b829d71/pydantic_core-2.46.3-cp314-cp314t-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:d0793c90c1a3c74966e7975eaef3ed30ebdff3260a0f815a62a22adc17e4c01c", size = 2157208, upload-time = "2026-04-20T14:42:08.133Z" }, + { url = "https://files.pythonhosted.org/packages/2b/9e/f80044e9ec07580f057a89fc131f78dda7a58751ddf52bbe05eaf31db50f/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_aarch64.whl", hash = "sha256:d2d0aead851b66f5245ec0c4fb2612ef457f8bbafefdf65a2bf9d6bac6140f47", size = 2167237, upload-time = "2026-04-20T14:42:25.412Z" }, + { url = "https://files.pythonhosted.org/packages/f8/84/6781a1b037f3b96be9227edbd1101f6d3946746056231bf4ac48cdff1a8d/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_armv7l.whl", hash = "sha256:2f40e4246676beb31c5ce77c38a55ca4e465c6b38d11ea1bd935420568e0b1ab", size = 2312540, upload-time = "2026-04-20T14:40:40.313Z" }, + { url = "https://files.pythonhosted.org/packages/3e/db/19c0839feeb728e7df03255581f198dfdf1c2aeb1e174a8420b63c5252e5/pydantic_core-2.46.3-cp314-cp314t-musllinux_1_1_x86_64.whl", hash = "sha256:cf489cf8986c543939aeee17a09c04d6ffb43bfef8ca16fcbcc5cfdcbed24dba", size = 2369556, upload-time = "2026-04-20T14:41:09.427Z" }, + { url = "https://files.pythonhosted.org/packages/e0/15/3228774cb7cd45f5f721ddf1b2242747f4eb834d0c491f0c02d606f09fed/pydantic_core-2.46.3-cp314-cp314t-win32.whl", hash = "sha256:ffe0883b56cfc05798bf994164d2b2ff03efe2d22022a2bb080f3b626176dd56", size = 1949756, upload-time = "2026-04-20T14:41:25.717Z" }, + { url = "https://files.pythonhosted.org/packages/b8/2a/c79cf53fd91e5a87e30d481809f52f9a60dd221e39de66455cf04deaad37/pydantic_core-2.46.3-cp314-cp314t-win_amd64.whl", hash = "sha256:706d9d0ce9cf4593d07270d8e9f53b161f90c57d315aeec4fb4fd7a8b10240d8", size = 2051305, upload-time = "2026-04-20T14:43:18.627Z" }, + { url = "https://files.pythonhosted.org/packages/0b/db/d8182a7f1d9343a032265aae186eb063fe26ca4c40f256b21e8da4498e89/pydantic_core-2.46.3-cp314-cp314t-win_arm64.whl", hash = "sha256:77706aeb41df6a76568434701e0917da10692da28cb69d5fb6919ce5fdb07374", size = 2026310, upload-time = "2026-04-20T14:41:01.778Z" }, + { url = "https://files.pythonhosted.org/packages/66/7f/03dbad45cd3aa9083fbc93c210ae8b005af67e4136a14186950a747c6874/pydantic_core-2.46.3-graalpy311-graalpy242_311_native-macosx_10_12_x86_64.whl", hash = "sha256:9715525891ed524a0a1eb6d053c74d4d4ad5017677fb00af0b7c2644a31bae46", size = 2105683, upload-time = "2026-04-20T14:42:19.779Z" }, + { url = "https://files.pythonhosted.org/packages/26/22/4dc186ac8ea6b257e9855031f51b62a9637beac4d68ac06bee02f046f836/pydantic_core-2.46.3-graalpy311-graalpy242_311_native-macosx_11_0_arm64.whl", hash = "sha256:9d2f400712a99a013aff420ef1eb9be077f8189a36c1e3ef87660b4e1088a874", size = 1940052, upload-time = "2026-04-20T14:43:59.274Z" }, + { url = "https://files.pythonhosted.org/packages/0d/ca/d376391a5aff1f2e8188960d7873543608130a870961c2b6b5236627c116/pydantic_core-2.46.3-graalpy311-graalpy242_311_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd2aab0e2e9dc2daf36bd2686c982535d5e7b1d930a1344a7bb6e82baab42a76", size = 1988172, upload-time = "2026-04-20T14:41:17.469Z" }, + { url = "https://files.pythonhosted.org/packages/0e/6b/523b9f85c23788755d6ab949329de692a2e3a584bc6beb67fef5e035aa9d/pydantic_core-2.46.3-graalpy311-graalpy242_311_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e9d76736da5f362fabfeea6a69b13b7f2be405c6d6966f06b2f6bfff7e64531", size = 2128596, upload-time = "2026-04-20T14:40:41.707Z" }, + { url = "https://files.pythonhosted.org/packages/34/42/f426db557e8ab2791bc7562052299944a118655496fbff99914e564c0a94/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_10_12_x86_64.whl", hash = "sha256:b12dd51f1187c2eb489af8e20f880362db98e954b54ab792fa5d92e8bcc6b803", size = 2091877, upload-time = "2026-04-20T14:43:27.091Z" }, + { url = "https://files.pythonhosted.org/packages/5c/4f/86a832a9d14df58e663bfdf4627dc00d3317c2bd583c4fb23390b0f04b8e/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-macosx_11_0_arm64.whl", hash = "sha256:f00a0961b125f1a47af7bcc17f00782e12f4cd056f83416006b30111d941dfa3", size = 1932428, upload-time = "2026-04-20T14:40:45.781Z" }, + { url = "https://files.pythonhosted.org/packages/11/1a/fe857968954d93fb78e0d4b6df5c988c74c4aaa67181c60be7cfe327c0ca/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57697d7c056aca4bbb680200f96563e841a6386ac1129370a0102592f4dddff5", size = 1997550, upload-time = "2026-04-20T14:44:02.425Z" }, + { url = "https://files.pythonhosted.org/packages/17/eb/9d89ad2d9b0ba8cd65393d434471621b98912abb10fbe1df08e480ba57b5/pydantic_core-2.46.3-graalpy312-graalpy250_312_native-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd35aa21299def8db7ef4fe5c4ff862941a9a158ca7b63d61e66fe67d30416b4", size = 2137657, upload-time = "2026-04-20T14:42:45.149Z" }, + { url = "https://files.pythonhosted.org/packages/1f/da/99d40830684f81dec901cac521b5b91c095394cc1084b9433393cde1c2df/pydantic_core-2.46.3-pp311-pypy311_pp73-macosx_10_12_x86_64.whl", hash = "sha256:13afdd885f3d71280cf286b13b310ee0f7ccfefd1dbbb661514a474b726e2f25", size = 2107973, upload-time = "2026-04-20T14:42:06.175Z" }, + { url = "https://files.pythonhosted.org/packages/99/a5/87024121818d75bbb2a98ddbaf638e40e7a18b5e0f5492c9ca4b1b316107/pydantic_core-2.46.3-pp311-pypy311_pp73-macosx_11_0_arm64.whl", hash = "sha256:f91c0aff3e3ee0928edd1232c57f643a7a003e6edf1860bc3afcdc749cb513f3", size = 1947191, upload-time = "2026-04-20T14:43:14.319Z" }, + { url = "https://files.pythonhosted.org/packages/60/62/0c1acfe10945b83a6a59d19fbaa92f48825381509e5701b855c08f13db76/pydantic_core-2.46.3-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6529d1d128321a58d30afcc97b49e98836542f68dd41b33c2e972bb9e5290536", size = 2123791, upload-time = "2026-04-20T14:43:22.766Z" }, + { url = "https://files.pythonhosted.org/packages/75/3e/3b2393b4c8f44285561dc30b00cf307a56a2eff7c483a824db3b8221ca51/pydantic_core-2.46.3-pp311-pypy311_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:975c267cff4f7e7272eacbe50f6cc03ca9a3da4c4fbd66fffd89c94c1e311aa1", size = 2153197, upload-time = "2026-04-20T14:44:27.932Z" }, + { url = "https://files.pythonhosted.org/packages/ba/75/5af02fb35505051eee727c061f2881c555ab4f8ddb2d42da715a42c9731b/pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:2b8e4f2bbdf71415c544b4b1138b8060db7b6611bc927e8064c769f64bed651c", size = 2181073, upload-time = "2026-04-20T14:43:20.729Z" }, + { url = "https://files.pythonhosted.org/packages/10/92/7e0e1bd9ca3c68305db037560ca2876f89b2647deb2f8b6319005de37505/pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_armv7l.whl", hash = "sha256:e61ea8e9fff9606d09178f577ff8ccdd7206ff73d6552bcec18e1033c4254b85", size = 2315886, upload-time = "2026-04-20T14:44:04.826Z" }, + { url = "https://files.pythonhosted.org/packages/b8/d8/101655f27eaf3e44558ead736b2795d12500598beed4683f279396fa186e/pydantic_core-2.46.3-pp311-pypy311_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:b504bda01bafc69b6d3c7a0c7f039dcf60f47fab70e06fe23f57b5c75bdc82b8", size = 2360528, upload-time = "2026-04-20T14:40:47.431Z" }, + { url = "https://files.pythonhosted.org/packages/07/0f/1c34a74c8d07136f0d729ffe5e1fdab04fbdaa7684f61a92f92511a84a15/pydantic_core-2.46.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:b00b76f7142fc60c762ce579bd29c8fa44aaa56592dd3c54fab3928d0d4ca6ff", size = 2184144, upload-time = "2026-04-20T14:42:57Z" }, ] [[package]] name = "pygments" -version = "2.19.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/b0/77/a5b8c569bf593b0140bde72ea885a803b82086995367bf2037de0159d924/pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887", size = 4968631, upload-time = "2025-06-21T13:39:12.283Z" } +version = "2.20.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991, upload-time = "2026-03-29T13:29:33.898Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/21/705964c7812476f378728bdf590ca4b771ec72385c533964653c68e86bdc/pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b", size = 1225217, upload-time = "2025-06-21T13:39:07.939Z" }, + { url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151, upload-time = "2026-03-29T13:29:30.038Z" }, ] [[package]] name = "pyparsing" -version = "3.2.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f2/a5/181488fc2b9d093e3972d2a472855aae8a03f000592dbfce716a512b3359/pyparsing-3.2.5.tar.gz", hash = "sha256:2df8d5b7b2802ef88e8d016a2eb9c7aeaa923529cd251ed0fe4608275d4105b6", size = 1099274, upload-time = "2025-09-21T04:11:06.277Z" } +version = "3.3.2" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f3/91/9c6ee907786a473bf81c5f53cf703ba0957b23ab84c264080fb5a450416f/pyparsing-3.3.2.tar.gz", hash = "sha256:c777f4d763f140633dcb6d8a3eda953bf7a214dc4eff598413c070bcdc117cbc", size = 6851574, upload-time = "2026-01-21T03:57:59.36Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/10/5e/1aa9a93198c6b64513c9d7752de7422c06402de6600a8767da1524f9570b/pyparsing-3.2.5-py3-none-any.whl", hash = "sha256:e38a4f02064cf41fe6593d328d0512495ad1f3d8a91c4f73fc401b3079a59a5e", size = 113890, upload-time = "2025-09-21T04:11:04.117Z" }, + { url = "https://files.pythonhosted.org/packages/10/bd/c038d7cc38edc1aa5bf91ab8068b63d4308c66c4c8bb3cbba7dfbc049f9c/pyparsing-3.3.2-py3-none-any.whl", hash = "sha256:850ba148bd908d7e2411587e247a1e4f0327839c40e2e5e6d05a007ecc69911d", size = 122781, upload-time = "2026-01-21T03:57:55.912Z" }, ] [[package]] name = "pytest" -version = "8.4.2" -source = { registry = "https://pypi.org/simple" } +version = "9.0.3" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, { name = "iniconfig" }, @@ -1388,15 +1512,15 @@ dependencies = [ { name = "pluggy" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a3/5c/00a0e072241553e1a7496d638deababa67c5058571567b92a7eaa258397c/pytest-8.4.2.tar.gz", hash = "sha256:86c0d0b93306b961d58d62a4db4879f27fe25513d4b969df351abdddb3c30e01", size = 1519618, upload-time = "2025-09-04T14:34:22.711Z" } +sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165, upload-time = "2026-04-07T17:16:18.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" }, + { url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249, upload-time = "2026-04-07T17:16:16.13Z" }, ] [[package]] name = "python-dateutil" version = "2.9.0.post0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "six" }, ] @@ -1407,42 +1531,42 @@ wheels = [ [[package]] name = "pytz" -version = "2025.2" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/f8/bf/abbd3cdfb8fbc7fb3d4d38d320f2441b1e7cbe29be4f23797b4a2b5d8aac/pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3", size = 320884, upload-time = "2025-03-25T02:25:00.538Z" } +version = "2026.1.post1" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/56/db/b8721d71d945e6a8ac63c0fc900b2067181dbb50805958d4d4661cf7d277/pytz-2026.1.post1.tar.gz", hash = "sha256:3378dde6a0c3d26719182142c56e60c7f9af7e968076f31aae569d72a0358ee1", size = 321088, upload-time = "2026-03-03T07:47:50.683Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, + { url = "https://files.pythonhosted.org/packages/10/99/781fe0c827be2742bcc775efefccb3b048a3a9c6ce9aec0cbf4a101677e5/pytz-2026.1.post1-py2.py3-none-any.whl", hash = "sha256:f2fd16142fda348286a75e1a524be810bb05d444e5a081f37f7affc635035f7a", size = 510489, upload-time = "2026-03-03T07:47:49.167Z" }, ] [[package]] name = "rich" -version = "14.2.0" -source = { registry = "https://pypi.org/simple" } +version = "15.0.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "markdown-it-py" }, { name = "pygments" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/fb/d2/8920e102050a0de7bfabeb4c4614a49248cf8d5d7a8d01885fbb24dc767a/rich-14.2.0.tar.gz", hash = "sha256:73ff50c7c0c1c77c8243079283f4edb376f0f6442433aecb8ce7e6d0b92d1fe4", size = 219990, upload-time = "2025-10-09T14:16:53.064Z" } +sdist = { url = "https://files.pythonhosted.org/packages/c0/8f/0722ca900cc807c13a6a0c696dacf35430f72e0ec571c4275d2371fca3e9/rich-15.0.0.tar.gz", hash = "sha256:edd07a4824c6b40189fb7ac9bc4c52536e9780fbbfbddf6f1e2502c31b068c36", size = 230680, upload-time = "2026-04-12T08:24:00.75Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/25/7a/b0178788f8dc6cafce37a212c99565fa1fe7872c70c6c9c1e1a372d9d88f/rich-14.2.0-py3-none-any.whl", hash = "sha256:76bc51fe2e57d2b1be1f96c524b890b816e334ab4c1e45888799bfaab0021edd", size = 243393, upload-time = "2025-10-09T14:16:51.245Z" }, + { url = "https://files.pythonhosted.org/packages/82/3b/64d4899d73f91ba49a8c18a8ff3f0ea8f1c1d75481760df8c68ef5235bf5/rich-15.0.0-py3-none-any.whl", hash = "sha256:33bd4ef74232fb73fe9279a257718407f169c09b78a87ad3d296f548e27de0bb", size = 310654, upload-time = "2026-04-12T08:24:02.83Z" }, ] [[package]] name = "roifile" -version = "2025.5.10" -source = { registry = "https://pypi.org/simple" } +version = "2026.2.10" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/ce/ee/8ab570959011aac7d5705b7c284ab90734aed297672711444e21c11ab4d0/roifile-2025.5.10.tar.gz", hash = "sha256:8e5d1f799695ac59f52d4d62bb9016de74ef2493b4d83e24ce7fb25e19f53cce", size = 18768, upload-time = "2025-05-10T19:22:33.042Z" } +sdist = { url = "https://files.pythonhosted.org/packages/59/cf/49d9a139e292415d669dccd0c283d99a4d027b9edbd533d7f56db8ff85e0/roifile-2026.2.10.tar.gz", hash = "sha256:87d2b685b00a4ca15125187727e634ae5c00b902362de302b592f892259f3751", size = 27311, upload-time = "2026-02-11T19:54:35.027Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/9b/f689742c06f377e33090d78e370d013e7e062ab8ac407f03f1105ddfc934/roifile-2025.5.10-py3-none-any.whl", hash = "sha256:92ef64a775167542cd98c4beb7c5f867412c572d1a111f03ee0fede7fdf64155", size = 17557, upload-time = "2025-05-10T19:22:25.256Z" }, + { url = "https://files.pythonhosted.org/packages/34/78/3cc84d58e13234861adf91d2897254cd84d3d4a90534d91c960b672e67d1/roifile-2026.2.10-py3-none-any.whl", hash = "sha256:5928a616b58a8ff1c9f89a0590db3616ccbf069c6692f8ecec2a7349f8bbb6ec", size = 21201, upload-time = "2026-02-11T19:54:34.137Z" }, ] [[package]] name = "scalene" version = "1.5.55" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "cloudpickle" }, { name = "jinja2" }, @@ -1470,7 +1594,7 @@ wheels = [ [[package]] name = "scikit-image" version = "0.24.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "imageio" }, { name = "lazy-loader" }, @@ -1497,88 +1621,88 @@ wheels = [ [[package]] name = "scipy" -version = "1.16.2" -source = { registry = "https://pypi.org/simple" } +version = "1.17.1" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "numpy" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/4c/3b/546a6f0bfe791bbb7f8d591613454d15097e53f906308ec6f7c1ce588e8e/scipy-1.16.2.tar.gz", hash = "sha256:af029b153d243a80afb6eabe40b0a07f8e35c9adc269c019f364ad747f826a6b", size = 30580599, upload-time = "2025-09-11T17:48:08.271Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/0b/ef/37ed4b213d64b48422df92560af7300e10fe30b5d665dd79932baebee0c6/scipy-1.16.2-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:6ab88ea43a57da1af33292ebd04b417e8e2eaf9d5aa05700be8d6e1b6501cd92", size = 36619956, upload-time = "2025-09-11T17:39:20.5Z" }, - { url = "https://files.pythonhosted.org/packages/85/ab/5c2eba89b9416961a982346a4d6a647d78c91ec96ab94ed522b3b6baf444/scipy-1.16.2-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c95e96c7305c96ede73a7389f46ccd6c659c4da5ef1b2789466baeaed3622b6e", size = 28931117, upload-time = "2025-09-11T17:39:29.06Z" }, - { url = "https://files.pythonhosted.org/packages/80/d1/eed51ab64d227fe60229a2d57fb60ca5898cfa50ba27d4f573e9e5f0b430/scipy-1.16.2-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:87eb178db04ece7c698220d523c170125dbffebb7af0345e66c3554f6f60c173", size = 20921997, upload-time = "2025-09-11T17:39:34.892Z" }, - { url = "https://files.pythonhosted.org/packages/be/7c/33ea3e23bbadde96726edba6bf9111fb1969d14d9d477ffa202c67bec9da/scipy-1.16.2-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:4e409eac067dcee96a57fbcf424c13f428037827ec7ee3cb671ff525ca4fc34d", size = 23523374, upload-time = "2025-09-11T17:39:40.846Z" }, - { url = "https://files.pythonhosted.org/packages/96/0b/7399dc96e1e3f9a05e258c98d716196a34f528eef2ec55aad651ed136d03/scipy-1.16.2-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:e574be127bb760f0dad24ff6e217c80213d153058372362ccb9555a10fc5e8d2", size = 33583702, upload-time = "2025-09-11T17:39:49.011Z" }, - { url = "https://files.pythonhosted.org/packages/1a/bc/a5c75095089b96ea72c1bd37a4497c24b581ec73db4ef58ebee142ad2d14/scipy-1.16.2-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f5db5ba6188d698ba7abab982ad6973265b74bb40a1efe1821b58c87f73892b9", size = 35883427, upload-time = "2025-09-11T17:39:57.406Z" }, - { url = "https://files.pythonhosted.org/packages/ab/66/e25705ca3d2b87b97fe0a278a24b7f477b4023a926847935a1a71488a6a6/scipy-1.16.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:ec6e74c4e884104ae006d34110677bfe0098203a3fec2f3faf349f4cb05165e3", size = 36212940, upload-time = "2025-09-11T17:40:06.013Z" }, - { url = "https://files.pythonhosted.org/packages/d6/fd/0bb911585e12f3abdd603d721d83fc1c7492835e1401a0e6d498d7822b4b/scipy-1.16.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:912f46667d2d3834bc3d57361f854226475f695eb08c08a904aadb1c936b6a88", size = 38865092, upload-time = "2025-09-11T17:40:15.143Z" }, - { url = "https://files.pythonhosted.org/packages/d6/73/c449a7d56ba6e6f874183759f8483cde21f900a8be117d67ffbb670c2958/scipy-1.16.2-cp311-cp311-win_amd64.whl", hash = "sha256:91e9e8a37befa5a69e9cacbe0bcb79ae5afb4a0b130fd6db6ee6cc0d491695fa", size = 38687626, upload-time = "2025-09-11T17:40:24.041Z" }, - { url = "https://files.pythonhosted.org/packages/68/72/02f37316adf95307f5d9e579023c6899f89ff3a051fa079dbd6faafc48e5/scipy-1.16.2-cp311-cp311-win_arm64.whl", hash = "sha256:f3bf75a6dcecab62afde4d1f973f1692be013110cad5338007927db8da73249c", size = 25503506, upload-time = "2025-09-11T17:40:30.703Z" }, - { url = "https://files.pythonhosted.org/packages/b7/8d/6396e00db1282279a4ddd507c5f5e11f606812b608ee58517ce8abbf883f/scipy-1.16.2-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:89d6c100fa5c48472047632e06f0876b3c4931aac1f4291afc81a3644316bb0d", size = 36646259, upload-time = "2025-09-11T17:40:39.329Z" }, - { url = "https://files.pythonhosted.org/packages/3b/93/ea9edd7e193fceb8eef149804491890bde73fb169c896b61aa3e2d1e4e77/scipy-1.16.2-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:ca748936cd579d3f01928b30a17dc474550b01272d8046e3e1ee593f23620371", size = 28888976, upload-time = "2025-09-11T17:40:46.82Z" }, - { url = "https://files.pythonhosted.org/packages/91/4d/281fddc3d80fd738ba86fd3aed9202331180b01e2c78eaae0642f22f7e83/scipy-1.16.2-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:fac4f8ce2ddb40e2e3d0f7ec36d2a1e7f92559a2471e59aec37bd8d9de01fec0", size = 20879905, upload-time = "2025-09-11T17:40:52.545Z" }, - { url = "https://files.pythonhosted.org/packages/69/40/b33b74c84606fd301b2915f0062e45733c6ff5708d121dd0deaa8871e2d0/scipy-1.16.2-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:033570f1dcefd79547a88e18bccacff025c8c647a330381064f561d43b821232", size = 23553066, upload-time = "2025-09-11T17:40:59.014Z" }, - { url = "https://files.pythonhosted.org/packages/55/a7/22c739e2f21a42cc8f16bc76b47cff4ed54fbe0962832c589591c2abec34/scipy-1.16.2-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ea3421209bf00c8a5ef2227de496601087d8f638a2363ee09af059bd70976dc1", size = 33336407, upload-time = "2025-09-11T17:41:06.796Z" }, - { url = "https://files.pythonhosted.org/packages/53/11/a0160990b82999b45874dc60c0c183d3a3a969a563fffc476d5a9995c407/scipy-1.16.2-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f66bd07ba6f84cd4a380b41d1bf3c59ea488b590a2ff96744845163309ee8e2f", size = 35673281, upload-time = "2025-09-11T17:41:15.055Z" }, - { url = "https://files.pythonhosted.org/packages/96/53/7ef48a4cfcf243c3d0f1643f5887c81f29fdf76911c4e49331828e19fc0a/scipy-1.16.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5e9feab931bd2aea4a23388c962df6468af3d808ddf2d40f94a81c5dc38f32ef", size = 36004222, upload-time = "2025-09-11T17:41:23.868Z" }, - { url = "https://files.pythonhosted.org/packages/49/7f/71a69e0afd460049d41c65c630c919c537815277dfea214031005f474d78/scipy-1.16.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:03dfc75e52f72cf23ec2ced468645321407faad8f0fe7b1f5b49264adbc29cb1", size = 38664586, upload-time = "2025-09-11T17:41:31.021Z" }, - { url = "https://files.pythonhosted.org/packages/34/95/20e02ca66fb495a95fba0642fd48e0c390d0ece9b9b14c6e931a60a12dea/scipy-1.16.2-cp312-cp312-win_amd64.whl", hash = "sha256:0ce54e07bbb394b417457409a64fd015be623f36e330ac49306433ffe04bc97e", size = 38550641, upload-time = "2025-09-11T17:41:36.61Z" }, - { url = "https://files.pythonhosted.org/packages/92/ad/13646b9beb0a95528ca46d52b7babafbe115017814a611f2065ee4e61d20/scipy-1.16.2-cp312-cp312-win_arm64.whl", hash = "sha256:2a8ffaa4ac0df81a0b94577b18ee079f13fecdb924df3328fc44a7dc5ac46851", size = 25456070, upload-time = "2025-09-11T17:41:41.3Z" }, - { url = "https://files.pythonhosted.org/packages/c1/27/c5b52f1ee81727a9fc457f5ac1e9bf3d6eab311805ea615c83c27ba06400/scipy-1.16.2-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:84f7bf944b43e20b8a894f5fe593976926744f6c185bacfcbdfbb62736b5cc70", size = 36604856, upload-time = "2025-09-11T17:41:47.695Z" }, - { url = "https://files.pythonhosted.org/packages/32/a9/15c20d08e950b540184caa8ced675ba1128accb0e09c653780ba023a4110/scipy-1.16.2-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5c39026d12edc826a1ef2ad35ad1e6d7f087f934bb868fc43fa3049c8b8508f9", size = 28864626, upload-time = "2025-09-11T17:41:52.642Z" }, - { url = "https://files.pythonhosted.org/packages/4c/fc/ea36098df653cca26062a627c1a94b0de659e97127c8491e18713ca0e3b9/scipy-1.16.2-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e52729ffd45b68777c5319560014d6fd251294200625d9d70fd8626516fc49f5", size = 20855689, upload-time = "2025-09-11T17:41:57.886Z" }, - { url = "https://files.pythonhosted.org/packages/dc/6f/d0b53be55727f3e6d7c72687ec18ea6d0047cf95f1f77488b99a2bafaee1/scipy-1.16.2-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:024dd4a118cccec09ca3209b7e8e614931a6ffb804b2a601839499cb88bdf925", size = 23512151, upload-time = "2025-09-11T17:42:02.303Z" }, - { url = "https://files.pythonhosted.org/packages/11/85/bf7dab56e5c4b1d3d8eef92ca8ede788418ad38a7dc3ff50262f00808760/scipy-1.16.2-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:7a5dc7ee9c33019973a470556081b0fd3c9f4c44019191039f9769183141a4d9", size = 33329824, upload-time = "2025-09-11T17:42:07.549Z" }, - { url = "https://files.pythonhosted.org/packages/da/6a/1a927b14ddc7714111ea51f4e568203b2bb6ed59bdd036d62127c1a360c8/scipy-1.16.2-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:c2275ff105e508942f99d4e3bc56b6ef5e4b3c0af970386ca56b777608ce95b7", size = 35681881, upload-time = "2025-09-11T17:42:13.255Z" }, - { url = "https://files.pythonhosted.org/packages/c1/5f/331148ea5780b4fcc7007a4a6a6ee0a0c1507a796365cc642d4d226e1c3a/scipy-1.16.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:af80196eaa84f033e48444d2e0786ec47d328ba00c71e4299b602235ffef9acb", size = 36006219, upload-time = "2025-09-11T17:42:18.765Z" }, - { url = "https://files.pythonhosted.org/packages/46/3a/e991aa9d2aec723b4a8dcfbfc8365edec5d5e5f9f133888067f1cbb7dfc1/scipy-1.16.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:9fb1eb735fe3d6ed1f89918224e3385fbf6f9e23757cacc35f9c78d3b712dd6e", size = 38682147, upload-time = "2025-09-11T17:42:25.177Z" }, - { url = "https://files.pythonhosted.org/packages/a1/57/0f38e396ad19e41b4c5db66130167eef8ee620a49bc7d0512e3bb67e0cab/scipy-1.16.2-cp313-cp313-win_amd64.whl", hash = "sha256:fda714cf45ba43c9d3bae8f2585c777f64e3f89a2e073b668b32ede412d8f52c", size = 38520766, upload-time = "2025-09-11T17:43:25.342Z" }, - { url = "https://files.pythonhosted.org/packages/1b/a5/85d3e867b6822d331e26c862a91375bb7746a0b458db5effa093d34cdb89/scipy-1.16.2-cp313-cp313-win_arm64.whl", hash = "sha256:2f5350da923ccfd0b00e07c3e5cfb316c1c0d6c1d864c07a72d092e9f20db104", size = 25451169, upload-time = "2025-09-11T17:43:30.198Z" }, - { url = "https://files.pythonhosted.org/packages/09/d9/60679189bcebda55992d1a45498de6d080dcaf21ce0c8f24f888117e0c2d/scipy-1.16.2-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:53d8d2ee29b925344c13bda64ab51785f016b1b9617849dac10897f0701b20c1", size = 37012682, upload-time = "2025-09-11T17:42:30.677Z" }, - { url = "https://files.pythonhosted.org/packages/83/be/a99d13ee4d3b7887a96f8c71361b9659ba4ef34da0338f14891e102a127f/scipy-1.16.2-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:9e05e33657efb4c6a9d23bd8300101536abd99c85cca82da0bffff8d8764d08a", size = 29389926, upload-time = "2025-09-11T17:42:35.845Z" }, - { url = "https://files.pythonhosted.org/packages/bf/0a/130164a4881cec6ca8c00faf3b57926f28ed429cd6001a673f83c7c2a579/scipy-1.16.2-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:7fe65b36036357003b3ef9d37547abeefaa353b237e989c21027b8ed62b12d4f", size = 21381152, upload-time = "2025-09-11T17:42:40.07Z" }, - { url = "https://files.pythonhosted.org/packages/47/a6/503ffb0310ae77fba874e10cddfc4a1280bdcca1d13c3751b8c3c2996cf8/scipy-1.16.2-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6406d2ac6d40b861cccf57f49592f9779071655e9f75cd4f977fa0bdd09cb2e4", size = 23914410, upload-time = "2025-09-11T17:42:44.313Z" }, - { url = "https://files.pythonhosted.org/packages/fa/c7/1147774bcea50d00c02600aadaa919facbd8537997a62496270133536ed6/scipy-1.16.2-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff4dc42bd321991fbf611c23fc35912d690f731c9914bf3af8f417e64aca0f21", size = 33481880, upload-time = "2025-09-11T17:42:49.325Z" }, - { url = "https://files.pythonhosted.org/packages/6a/74/99d5415e4c3e46b2586f30cdbecb95e101c7192628a484a40dd0d163811a/scipy-1.16.2-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:654324826654d4d9133e10675325708fb954bc84dae6e9ad0a52e75c6b1a01d7", size = 35791425, upload-time = "2025-09-11T17:42:54.711Z" }, - { url = "https://files.pythonhosted.org/packages/1b/ee/a6559de7c1cc710e938c0355d9d4fbcd732dac4d0d131959d1f3b63eb29c/scipy-1.16.2-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:63870a84cd15c44e65220eaed2dac0e8f8b26bbb991456a033c1d9abfe8a94f8", size = 36178622, upload-time = "2025-09-11T17:43:00.375Z" }, - { url = "https://files.pythonhosted.org/packages/4e/7b/f127a5795d5ba8ece4e0dce7d4a9fb7cb9e4f4757137757d7a69ab7d4f1a/scipy-1.16.2-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:fa01f0f6a3050fa6a9771a95d5faccc8e2f5a92b4a2e5440a0fa7264a2398472", size = 38783985, upload-time = "2025-09-11T17:43:06.661Z" }, - { url = "https://files.pythonhosted.org/packages/3e/9f/bc81c1d1e033951eb5912cd3750cc005943afa3e65a725d2443a3b3c4347/scipy-1.16.2-cp313-cp313t-win_amd64.whl", hash = "sha256:116296e89fba96f76353a8579820c2512f6e55835d3fad7780fece04367de351", size = 38631367, upload-time = "2025-09-11T17:43:14.44Z" }, - { url = "https://files.pythonhosted.org/packages/d6/5e/2cc7555fd81d01814271412a1d59a289d25f8b63208a0a16c21069d55d3e/scipy-1.16.2-cp313-cp313t-win_arm64.whl", hash = "sha256:98e22834650be81d42982360382b43b17f7ba95e0e6993e2a4f5b9ad9283a94d", size = 25787992, upload-time = "2025-09-11T17:43:19.745Z" }, - { url = "https://files.pythonhosted.org/packages/8b/ac/ad8951250516db71619f0bd3b2eb2448db04b720a003dd98619b78b692c0/scipy-1.16.2-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:567e77755019bb7461513c87f02bb73fb65b11f049aaaa8ca17cfaa5a5c45d77", size = 36595109, upload-time = "2025-09-11T17:43:35.713Z" }, - { url = "https://files.pythonhosted.org/packages/ff/f6/5779049ed119c5b503b0f3dc6d6f3f68eefc3a9190d4ad4c276f854f051b/scipy-1.16.2-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:17d9bb346194e8967296621208fcdfd39b55498ef7d2f376884d5ac47cec1a70", size = 28859110, upload-time = "2025-09-11T17:43:40.814Z" }, - { url = "https://files.pythonhosted.org/packages/82/09/9986e410ae38bf0a0c737ff8189ac81a93b8e42349aac009891c054403d7/scipy-1.16.2-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:0a17541827a9b78b777d33b623a6dcfe2ef4a25806204d08ead0768f4e529a88", size = 20850110, upload-time = "2025-09-11T17:43:44.981Z" }, - { url = "https://files.pythonhosted.org/packages/0d/ad/485cdef2d9215e2a7df6d61b81d2ac073dfacf6ae24b9ae87274c4e936ae/scipy-1.16.2-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:d7d4c6ba016ffc0f9568d012f5f1eb77ddd99412aea121e6fa8b4c3b7cbad91f", size = 23497014, upload-time = "2025-09-11T17:43:49.074Z" }, - { url = "https://files.pythonhosted.org/packages/a7/74/f6a852e5d581122b8f0f831f1d1e32fb8987776ed3658e95c377d308ed86/scipy-1.16.2-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:9702c4c023227785c779cba2e1d6f7635dbb5b2e0936cdd3a4ecb98d78fd41eb", size = 33401155, upload-time = "2025-09-11T17:43:54.661Z" }, - { url = "https://files.pythonhosted.org/packages/d9/f5/61d243bbc7c6e5e4e13dde9887e84a5cbe9e0f75fd09843044af1590844e/scipy-1.16.2-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d1cdf0ac28948d225decdefcc45ad7dd91716c29ab56ef32f8e0d50657dffcc7", size = 35691174, upload-time = "2025-09-11T17:44:00.101Z" }, - { url = "https://files.pythonhosted.org/packages/03/99/59933956331f8cc57e406cdb7a483906c74706b156998f322913e789c7e1/scipy-1.16.2-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:70327d6aa572a17c2941cdfb20673f82e536e91850a2e4cb0c5b858b690e1548", size = 36070752, upload-time = "2025-09-11T17:44:05.619Z" }, - { url = "https://files.pythonhosted.org/packages/c6/7d/00f825cfb47ee19ef74ecf01244b43e95eae74e7e0ff796026ea7cd98456/scipy-1.16.2-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:5221c0b2a4b58aa7c4ed0387d360fd90ee9086d383bb34d9f2789fafddc8a936", size = 38701010, upload-time = "2025-09-11T17:44:11.322Z" }, - { url = "https://files.pythonhosted.org/packages/e4/9f/b62587029980378304ba5a8563d376c96f40b1e133daacee76efdcae32de/scipy-1.16.2-cp314-cp314-win_amd64.whl", hash = "sha256:f5a85d7b2b708025af08f060a496dd261055b617d776fc05a1a1cc69e09fe9ff", size = 39360061, upload-time = "2025-09-11T17:45:09.814Z" }, - { url = "https://files.pythonhosted.org/packages/82/04/7a2f1609921352c7fbee0815811b5050582f67f19983096c4769867ca45f/scipy-1.16.2-cp314-cp314-win_arm64.whl", hash = "sha256:2cc73a33305b4b24556957d5857d6253ce1e2dcd67fa0ff46d87d1670b3e1e1d", size = 26126914, upload-time = "2025-09-11T17:45:14.73Z" }, - { url = "https://files.pythonhosted.org/packages/51/b9/60929ce350c16b221928725d2d1d7f86cf96b8bc07415547057d1196dc92/scipy-1.16.2-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:9ea2a3fed83065d77367775d689401a703d0f697420719ee10c0780bcab594d8", size = 37013193, upload-time = "2025-09-11T17:44:16.757Z" }, - { url = "https://files.pythonhosted.org/packages/2a/41/ed80e67782d4bc5fc85a966bc356c601afddd175856ba7c7bb6d9490607e/scipy-1.16.2-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7280d926f11ca945c3ef92ba960fa924e1465f8d07ce3a9923080363390624c4", size = 29390172, upload-time = "2025-09-11T17:44:21.783Z" }, - { url = "https://files.pythonhosted.org/packages/c4/a3/2f673ace4090452696ccded5f5f8efffb353b8f3628f823a110e0170b605/scipy-1.16.2-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:8afae1756f6a1fe04636407ef7dbece33d826a5d462b74f3d0eb82deabefd831", size = 21381326, upload-time = "2025-09-11T17:44:25.982Z" }, - { url = "https://files.pythonhosted.org/packages/42/bf/59df61c5d51395066c35836b78136accf506197617c8662e60ea209881e1/scipy-1.16.2-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:5c66511f29aa8d233388e7416a3f20d5cae7a2744d5cee2ecd38c081f4e861b3", size = 23915036, upload-time = "2025-09-11T17:44:30.527Z" }, - { url = "https://files.pythonhosted.org/packages/91/c3/edc7b300dc16847ad3672f1a6f3f7c5d13522b21b84b81c265f4f2760d4a/scipy-1.16.2-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:efe6305aeaa0e96b0ccca5ff647a43737d9a092064a3894e46c414db84bc54ac", size = 33484341, upload-time = "2025-09-11T17:44:35.981Z" }, - { url = "https://files.pythonhosted.org/packages/26/c7/24d1524e72f06ff141e8d04b833c20db3021020563272ccb1b83860082a9/scipy-1.16.2-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7f3a337d9ae06a1e8d655ee9d8ecb835ea5ddcdcbd8d23012afa055ab014f374", size = 35790840, upload-time = "2025-09-11T17:44:41.76Z" }, - { url = "https://files.pythonhosted.org/packages/aa/b7/5aaad984eeedd56858dc33d75efa59e8ce798d918e1033ef62d2708f2c3d/scipy-1.16.2-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:bab3605795d269067d8ce78a910220262711b753de8913d3deeaedb5dded3bb6", size = 36174716, upload-time = "2025-09-11T17:44:47.316Z" }, - { url = "https://files.pythonhosted.org/packages/fd/c2/e276a237acb09824822b0ada11b028ed4067fdc367a946730979feacb870/scipy-1.16.2-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:b0348d8ddb55be2a844c518cd8cc8deeeb8aeba707cf834db5758fc89b476a2c", size = 38790088, upload-time = "2025-09-11T17:44:53.011Z" }, - { url = "https://files.pythonhosted.org/packages/c6/b4/5c18a766e8353015439f3780f5fc473f36f9762edc1a2e45da3ff5a31b21/scipy-1.16.2-cp314-cp314t-win_amd64.whl", hash = "sha256:26284797e38b8a75e14ea6631d29bda11e76ceaa6ddb6fdebbfe4c4d90faf2f9", size = 39457455, upload-time = "2025-09-11T17:44:58.899Z" }, - { url = "https://files.pythonhosted.org/packages/97/30/2f9a5243008f76dfc5dee9a53dfb939d9b31e16ce4bd4f2e628bfc5d89d2/scipy-1.16.2-cp314-cp314t-win_arm64.whl", hash = "sha256:d2a4472c231328d4de38d5f1f68fdd6d28a615138f842580a8a321b5845cf779", size = 26448374, upload-time = "2025-09-11T17:45:03.45Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/7a/97/5a3609c4f8d58b039179648e62dd220f89864f56f7357f5d4f45c29eb2cc/scipy-1.17.1.tar.gz", hash = "sha256:95d8e012d8cb8816c226aef832200b1d45109ed4464303e997c5b13122b297c0", size = 30573822, upload-time = "2026-02-23T00:26:24.851Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/75/b4ce781849931fef6fd529afa6b63711d5a733065722d0c3e2724af9e40a/scipy-1.17.1-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:1f95b894f13729334fb990162e911c9e5dc1ab390c58aa6cbecb389c5b5e28ec", size = 31613675, upload-time = "2026-02-23T00:16:00.13Z" }, + { url = "https://files.pythonhosted.org/packages/f7/58/bccc2861b305abdd1b8663d6130c0b3d7cc22e8d86663edbc8401bfd40d4/scipy-1.17.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:e18f12c6b0bc5a592ed23d3f7b891f68fd7f8241d69b7883769eb5d5dfb52696", size = 28162057, upload-time = "2026-02-23T00:16:09.456Z" }, + { url = "https://files.pythonhosted.org/packages/6d/ee/18146b7757ed4976276b9c9819108adbc73c5aad636e5353e20746b73069/scipy-1.17.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:a3472cfbca0a54177d0faa68f697d8ba4c80bbdc19908c3465556d9f7efce9ee", size = 20334032, upload-time = "2026-02-23T00:16:17.358Z" }, + { url = "https://files.pythonhosted.org/packages/ec/e6/cef1cf3557f0c54954198554a10016b6a03b2ec9e22a4e1df734936bd99c/scipy-1.17.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:766e0dc5a616d026a3a1cffa379af959671729083882f50307e18175797b3dfd", size = 22709533, upload-time = "2026-02-23T00:16:25.791Z" }, + { url = "https://files.pythonhosted.org/packages/4d/60/8804678875fc59362b0fb759ab3ecce1f09c10a735680318ac30da8cd76b/scipy-1.17.1-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:744b2bf3640d907b79f3fd7874efe432d1cf171ee721243e350f55234b4cec4c", size = 33062057, upload-time = "2026-02-23T00:16:36.931Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/af933f0f6e0767995b4e2d705a0665e454d1c19402aa7e895de3951ebb04/scipy-1.17.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:43af8d1f3bea642559019edfe64e9b11192a8978efbd1539d7bc2aaa23d92de4", size = 35349300, upload-time = "2026-02-23T00:16:49.108Z" }, + { url = "https://files.pythonhosted.org/packages/b4/3d/7ccbbdcbb54c8fdc20d3b6930137c782a163fa626f0aef920349873421ba/scipy-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cd96a1898c0a47be4520327e01f874acfd61fb48a9420f8aa9f6483412ffa444", size = 35127333, upload-time = "2026-02-23T00:17:01.293Z" }, + { url = "https://files.pythonhosted.org/packages/e8/19/f926cb11c42b15ba08e3a71e376d816ac08614f769b4f47e06c3580c836a/scipy-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4eb6c25dd62ee8d5edf68a8e1c171dd71c292fdae95d8aeb3dd7d7de4c364082", size = 37741314, upload-time = "2026-02-23T00:17:12.576Z" }, + { url = "https://files.pythonhosted.org/packages/95/da/0d1df507cf574b3f224ccc3d45244c9a1d732c81dcb26b1e8a766ae271a8/scipy-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:d30e57c72013c2a4fe441c2fcb8e77b14e152ad48b5464858e07e2ad9fbfceff", size = 36607512, upload-time = "2026-02-23T00:17:23.424Z" }, + { url = "https://files.pythonhosted.org/packages/68/7f/bdd79ceaad24b671543ffe0ef61ed8e659440eb683b66f033454dcee90eb/scipy-1.17.1-cp311-cp311-win_arm64.whl", hash = "sha256:9ecb4efb1cd6e8c4afea0daa91a87fbddbce1b99d2895d151596716c0b2e859d", size = 24599248, upload-time = "2026-02-23T00:17:34.561Z" }, + { url = "https://files.pythonhosted.org/packages/35/48/b992b488d6f299dbe3f11a20b24d3dda3d46f1a635ede1c46b5b17a7b163/scipy-1.17.1-cp312-cp312-macosx_10_14_x86_64.whl", hash = "sha256:35c3a56d2ef83efc372eaec584314bd0ef2e2f0d2adb21c55e6ad5b344c0dcb8", size = 31610954, upload-time = "2026-02-23T00:17:49.855Z" }, + { url = "https://files.pythonhosted.org/packages/b2/02/cf107b01494c19dc100f1d0b7ac3cc08666e96ba2d64db7626066cee895e/scipy-1.17.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:fcb310ddb270a06114bb64bbe53c94926b943f5b7f0842194d585c65eb4edd76", size = 28172662, upload-time = "2026-02-23T00:18:01.64Z" }, + { url = "https://files.pythonhosted.org/packages/cf/a9/599c28631bad314d219cf9ffd40e985b24d603fc8a2f4ccc5ae8419a535b/scipy-1.17.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:cc90d2e9c7e5c7f1a482c9875007c095c3194b1cfedca3c2f3291cdc2bc7c086", size = 20344366, upload-time = "2026-02-23T00:18:12.015Z" }, + { url = "https://files.pythonhosted.org/packages/35/f5/906eda513271c8deb5af284e5ef0206d17a96239af79f9fa0aebfe0e36b4/scipy-1.17.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:c80be5ede8f3f8eded4eff73cc99a25c388ce98e555b17d31da05287015ffa5b", size = 22704017, upload-time = "2026-02-23T00:18:21.502Z" }, + { url = "https://files.pythonhosted.org/packages/da/34/16f10e3042d2f1d6b66e0428308ab52224b6a23049cb2f5c1756f713815f/scipy-1.17.1-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e19ebea31758fac5893a2ac360fedd00116cbb7628e650842a6691ba7ca28a21", size = 32927842, upload-time = "2026-02-23T00:18:35.367Z" }, + { url = "https://files.pythonhosted.org/packages/01/8e/1e35281b8ab6d5d72ebe9911edcdffa3f36b04ed9d51dec6dd140396e220/scipy-1.17.1-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:02ae3b274fde71c5e92ac4d54bc06c42d80e399fec704383dcd99b301df37458", size = 35235890, upload-time = "2026-02-23T00:18:49.188Z" }, + { url = "https://files.pythonhosted.org/packages/c5/5c/9d7f4c88bea6e0d5a4f1bc0506a53a00e9fcb198de372bfe4d3652cef482/scipy-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8a604bae87c6195d8b1045eddece0514d041604b14f2727bbc2b3020172045eb", size = 35003557, upload-time = "2026-02-23T00:18:54.74Z" }, + { url = "https://files.pythonhosted.org/packages/65/94/7698add8f276dbab7a9de9fb6b0e02fc13ee61d51c7c3f85ac28b65e1239/scipy-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:f590cd684941912d10becc07325a3eeb77886fe981415660d9265c4c418d0bea", size = 37625856, upload-time = "2026-02-23T00:19:00.307Z" }, + { url = "https://files.pythonhosted.org/packages/a2/84/dc08d77fbf3d87d3ee27f6a0c6dcce1de5829a64f2eae85a0ecc1f0daa73/scipy-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:41b71f4a3a4cab9d366cd9065b288efc4d4f3c0b37a91a8e0947fb5bd7f31d87", size = 36549682, upload-time = "2026-02-23T00:19:07.67Z" }, + { url = "https://files.pythonhosted.org/packages/bc/98/fe9ae9ffb3b54b62559f52dedaebe204b408db8109a8c66fdd04869e6424/scipy-1.17.1-cp312-cp312-win_arm64.whl", hash = "sha256:f4115102802df98b2b0db3cce5cb9b92572633a1197c77b7553e5203f284a5b3", size = 24547340, upload-time = "2026-02-23T00:19:12.024Z" }, + { url = "https://files.pythonhosted.org/packages/76/27/07ee1b57b65e92645f219b37148a7e7928b82e2b5dbeccecb4dff7c64f0b/scipy-1.17.1-cp313-cp313-macosx_10_14_x86_64.whl", hash = "sha256:5e3c5c011904115f88a39308379c17f91546f77c1667cea98739fe0fccea804c", size = 31590199, upload-time = "2026-02-23T00:19:17.192Z" }, + { url = "https://files.pythonhosted.org/packages/ec/ae/db19f8ab842e9b724bf5dbb7db29302a91f1e55bc4d04b1025d6d605a2c5/scipy-1.17.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:6fac755ca3d2c3edcb22f479fceaa241704111414831ddd3bc6056e18516892f", size = 28154001, upload-time = "2026-02-23T00:19:22.241Z" }, + { url = "https://files.pythonhosted.org/packages/5b/58/3ce96251560107b381cbd6e8413c483bbb1228a6b919fa8652b0d4090e7f/scipy-1.17.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:7ff200bf9d24f2e4d5dc6ee8c3ac64d739d3a89e2326ba68aaf6c4a2b838fd7d", size = 20325719, upload-time = "2026-02-23T00:19:26.329Z" }, + { url = "https://files.pythonhosted.org/packages/b2/83/15087d945e0e4d48ce2377498abf5ad171ae013232ae31d06f336e64c999/scipy-1.17.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:4b400bdc6f79fa02a4d86640310dde87a21fba0c979efff5248908c6f15fad1b", size = 22683595, upload-time = "2026-02-23T00:19:30.304Z" }, + { url = "https://files.pythonhosted.org/packages/b4/e0/e58fbde4a1a594c8be8114eb4aac1a55bcd6587047efc18a61eb1f5c0d30/scipy-1.17.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2b64ca7d4aee0102a97f3ba22124052b4bd2152522355073580bf4845e2550b6", size = 32896429, upload-time = "2026-02-23T00:19:35.536Z" }, + { url = "https://files.pythonhosted.org/packages/f5/5f/f17563f28ff03c7b6799c50d01d5d856a1d55f2676f537ca8d28c7f627cd/scipy-1.17.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:581b2264fc0aa555f3f435a5944da7504ea3a065d7029ad60e7c3d1ae09c5464", size = 35203952, upload-time = "2026-02-23T00:19:42.259Z" }, + { url = "https://files.pythonhosted.org/packages/8d/a5/9afd17de24f657fdfe4df9a3f1ea049b39aef7c06000c13db1530d81ccca/scipy-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:beeda3d4ae615106d7094f7e7cef6218392e4465cc95d25f900bebabfded0950", size = 34979063, upload-time = "2026-02-23T00:19:47.547Z" }, + { url = "https://files.pythonhosted.org/packages/8b/13/88b1d2384b424bf7c924f2038c1c409f8d88bb2a8d49d097861dd64a57b2/scipy-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:6609bc224e9568f65064cfa72edc0f24ee6655b47575954ec6339534b2798369", size = 37598449, upload-time = "2026-02-23T00:19:53.238Z" }, + { url = "https://files.pythonhosted.org/packages/35/e5/d6d0e51fc888f692a35134336866341c08655d92614f492c6860dc45bb2c/scipy-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:37425bc9175607b0268f493d79a292c39f9d001a357bebb6b88fdfaff13f6448", size = 36510943, upload-time = "2026-02-23T00:20:50.89Z" }, + { url = "https://files.pythonhosted.org/packages/2a/fd/3be73c564e2a01e690e19cc618811540ba5354c67c8680dce3281123fb79/scipy-1.17.1-cp313-cp313-win_arm64.whl", hash = "sha256:5cf36e801231b6a2059bf354720274b7558746f3b1a4efb43fcf557ccd484a87", size = 24545621, upload-time = "2026-02-23T00:20:55.871Z" }, + { url = "https://files.pythonhosted.org/packages/6f/6b/17787db8b8114933a66f9dcc479a8272e4b4da75fe03b0c282f7b0ade8cd/scipy-1.17.1-cp313-cp313t-macosx_10_14_x86_64.whl", hash = "sha256:d59c30000a16d8edc7e64152e30220bfbd724c9bbb08368c054e24c651314f0a", size = 31936708, upload-time = "2026-02-23T00:19:58.694Z" }, + { url = "https://files.pythonhosted.org/packages/38/2e/524405c2b6392765ab1e2b722a41d5da33dc5c7b7278184a8ad29b6cb206/scipy-1.17.1-cp313-cp313t-macosx_12_0_arm64.whl", hash = "sha256:010f4333c96c9bb1a4516269e33cb5917b08ef2166d5556ca2fd9f082a9e6ea0", size = 28570135, upload-time = "2026-02-23T00:20:03.934Z" }, + { url = "https://files.pythonhosted.org/packages/fd/c3/5bd7199f4ea8556c0c8e39f04ccb014ac37d1468e6cfa6a95c6b3562b76e/scipy-1.17.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:2ceb2d3e01c5f1d83c4189737a42d9cb2fc38a6eeed225e7515eef71ad301dce", size = 20741977, upload-time = "2026-02-23T00:20:07.935Z" }, + { url = "https://files.pythonhosted.org/packages/d9/b8/8ccd9b766ad14c78386599708eb745f6b44f08400a5fd0ade7cf89b6fc93/scipy-1.17.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:844e165636711ef41f80b4103ed234181646b98a53c8f05da12ca5ca289134f6", size = 23029601, upload-time = "2026-02-23T00:20:12.161Z" }, + { url = "https://files.pythonhosted.org/packages/6d/a0/3cb6f4d2fb3e17428ad2880333cac878909ad1a89f678527b5328b93c1d4/scipy-1.17.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:158dd96d2207e21c966063e1635b1063cd7787b627b6f07305315dd73d9c679e", size = 33019667, upload-time = "2026-02-23T00:20:17.208Z" }, + { url = "https://files.pythonhosted.org/packages/f3/c3/2d834a5ac7bf3a0c806ad1508efc02dda3c8c61472a56132d7894c312dea/scipy-1.17.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74cbb80d93260fe2ffa334efa24cb8f2f0f622a9b9febf8b483c0b865bfb3475", size = 35264159, upload-time = "2026-02-23T00:20:23.087Z" }, + { url = "https://files.pythonhosted.org/packages/4d/77/d3ed4becfdbd217c52062fafe35a72388d1bd82c2d0ba5ca19d6fcc93e11/scipy-1.17.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:dbc12c9f3d185f5c737d801da555fb74b3dcfa1a50b66a1a93e09190f41fab50", size = 35102771, upload-time = "2026-02-23T00:20:28.636Z" }, + { url = "https://files.pythonhosted.org/packages/bd/12/d19da97efde68ca1ee5538bb261d5d2c062f0c055575128f11a2730e3ac1/scipy-1.17.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:94055a11dfebe37c656e70317e1996dc197e1a15bbcc351bcdd4610e128fe1ca", size = 37665910, upload-time = "2026-02-23T00:20:34.743Z" }, + { url = "https://files.pythonhosted.org/packages/06/1c/1172a88d507a4baaf72c5a09bb6c018fe2ae0ab622e5830b703a46cc9e44/scipy-1.17.1-cp313-cp313t-win_amd64.whl", hash = "sha256:e30bdeaa5deed6bc27b4cc490823cd0347d7dae09119b8803ae576ea0ce52e4c", size = 36562980, upload-time = "2026-02-23T00:20:40.575Z" }, + { url = "https://files.pythonhosted.org/packages/70/b0/eb757336e5a76dfa7911f63252e3b7d1de00935d7705cf772db5b45ec238/scipy-1.17.1-cp313-cp313t-win_arm64.whl", hash = "sha256:a720477885a9d2411f94a93d16f9d89bad0f28ca23c3f8daa521e2dcc3f44d49", size = 24856543, upload-time = "2026-02-23T00:20:45.313Z" }, + { url = "https://files.pythonhosted.org/packages/cf/83/333afb452af6f0fd70414dc04f898647ee1423979ce02efa75c3b0f2c28e/scipy-1.17.1-cp314-cp314-macosx_10_14_x86_64.whl", hash = "sha256:a48a72c77a310327f6a3a920092fa2b8fd03d7deaa60f093038f22d98e096717", size = 31584510, upload-time = "2026-02-23T00:21:01.015Z" }, + { url = "https://files.pythonhosted.org/packages/ed/a6/d05a85fd51daeb2e4ea71d102f15b34fedca8e931af02594193ae4fd25f7/scipy-1.17.1-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:45abad819184f07240d8a696117a7aacd39787af9e0b719d00285549ed19a1e9", size = 28170131, upload-time = "2026-02-23T00:21:05.888Z" }, + { url = "https://files.pythonhosted.org/packages/db/7b/8624a203326675d7746a254083a187398090a179335b2e4a20e2ddc46e83/scipy-1.17.1-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:3fd1fcdab3ea951b610dc4cef356d416d5802991e7e32b5254828d342f7b7e0b", size = 20342032, upload-time = "2026-02-23T00:21:09.904Z" }, + { url = "https://files.pythonhosted.org/packages/c9/35/2c342897c00775d688d8ff3987aced3426858fd89d5a0e26e020b660b301/scipy-1.17.1-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:7bdf2da170b67fdf10bca777614b1c7d96ae3ca5794fd9587dce41eb2966e866", size = 22678766, upload-time = "2026-02-23T00:21:14.313Z" }, + { url = "https://files.pythonhosted.org/packages/ef/f2/7cdb8eb308a1a6ae1e19f945913c82c23c0c442a462a46480ce487fdc0ac/scipy-1.17.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:adb2642e060a6549c343603a3851ba76ef0b74cc8c079a9a58121c7ec9fe2350", size = 32957007, upload-time = "2026-02-23T00:21:19.663Z" }, + { url = "https://files.pythonhosted.org/packages/0b/2e/7eea398450457ecb54e18e9d10110993fa65561c4f3add5e8eccd2b9cd41/scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:eee2cfda04c00a857206a4330f0c5e3e56535494e30ca445eb19ec624ae75118", size = 35221333, upload-time = "2026-02-23T00:21:25.278Z" }, + { url = "https://files.pythonhosted.org/packages/d9/77/5b8509d03b77f093a0d52e606d3c4f79e8b06d1d38c441dacb1e26cacf46/scipy-1.17.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:d2650c1fb97e184d12d8ba010493ee7b322864f7d3d00d3f9bb97d9c21de4068", size = 35042066, upload-time = "2026-02-23T00:21:31.358Z" }, + { url = "https://files.pythonhosted.org/packages/f9/df/18f80fb99df40b4070328d5ae5c596f2f00fffb50167e31439e932f29e7d/scipy-1.17.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:08b900519463543aa604a06bec02461558a6e1cef8fdbb8098f77a48a83c8118", size = 37612763, upload-time = "2026-02-23T00:21:37.247Z" }, + { url = "https://files.pythonhosted.org/packages/4b/39/f0e8ea762a764a9dc52aa7dabcfad51a354819de1f0d4652b6a1122424d6/scipy-1.17.1-cp314-cp314-win_amd64.whl", hash = "sha256:3877ac408e14da24a6196de0ddcace62092bfc12a83823e92e49e40747e52c19", size = 37290984, upload-time = "2026-02-23T00:22:35.023Z" }, + { url = "https://files.pythonhosted.org/packages/7c/56/fe201e3b0f93d1a8bcf75d3379affd228a63d7e2d80ab45467a74b494947/scipy-1.17.1-cp314-cp314-win_arm64.whl", hash = "sha256:f8885db0bc2bffa59d5c1b72fad7a6a92d3e80e7257f967dd81abb553a90d293", size = 25192877, upload-time = "2026-02-23T00:22:39.798Z" }, + { url = "https://files.pythonhosted.org/packages/96/ad/f8c414e121f82e02d76f310f16db9899c4fcde36710329502a6b2a3c0392/scipy-1.17.1-cp314-cp314t-macosx_10_14_x86_64.whl", hash = "sha256:1cc682cea2ae55524432f3cdff9e9a3be743d52a7443d0cba9017c23c87ae2f6", size = 31949750, upload-time = "2026-02-23T00:21:42.289Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b0/c741e8865d61b67c81e255f4f0a832846c064e426636cd7de84e74d209be/scipy-1.17.1-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:2040ad4d1795a0ae89bfc7e8429677f365d45aa9fd5e4587cf1ea737f927b4a1", size = 28585858, upload-time = "2026-02-23T00:21:47.706Z" }, + { url = "https://files.pythonhosted.org/packages/ed/1b/3985219c6177866628fa7c2595bfd23f193ceebbe472c98a08824b9466ff/scipy-1.17.1-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:131f5aaea57602008f9822e2115029b55d4b5f7c070287699fe45c661d051e39", size = 20757723, upload-time = "2026-02-23T00:21:52.039Z" }, + { url = "https://files.pythonhosted.org/packages/c0/19/2a04aa25050d656d6f7b9e7b685cc83d6957fb101665bfd9369ca6534563/scipy-1.17.1-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:9cdc1a2fcfd5c52cfb3045feb399f7b3ce822abdde3a193a6b9a60b3cb5854ca", size = 23043098, upload-time = "2026-02-23T00:21:56.185Z" }, + { url = "https://files.pythonhosted.org/packages/86/f1/3383beb9b5d0dbddd030335bf8a8b32d4317185efe495374f134d8be6cce/scipy-1.17.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6e3dcd57ab780c741fde8dc68619de988b966db759a3c3152e8e9142c26295ad", size = 33030397, upload-time = "2026-02-23T00:22:01.404Z" }, + { url = "https://files.pythonhosted.org/packages/41/68/8f21e8a65a5a03f25a79165ec9d2b28c00e66dc80546cf5eb803aeeff35b/scipy-1.17.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a9956e4d4f4a301ebf6cde39850333a6b6110799d470dbbb1e25326ac447f52a", size = 35281163, upload-time = "2026-02-23T00:22:07.024Z" }, + { url = "https://files.pythonhosted.org/packages/84/8d/c8a5e19479554007a5632ed7529e665c315ae7492b4f946b0deb39870e39/scipy-1.17.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:a4328d245944d09fd639771de275701ccadf5f781ba0ff092ad141e017eccda4", size = 35116291, upload-time = "2026-02-23T00:22:12.585Z" }, + { url = "https://files.pythonhosted.org/packages/52/52/e57eceff0e342a1f50e274264ed47497b59e6a4e3118808ee58ddda7b74a/scipy-1.17.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:a77cbd07b940d326d39a1d1b37817e2ee4d79cb30e7338f3d0cddffae70fcaa2", size = 37682317, upload-time = "2026-02-23T00:22:18.513Z" }, + { url = "https://files.pythonhosted.org/packages/11/2f/b29eafe4a3fbc3d6de9662b36e028d5f039e72d345e05c250e121a230dd4/scipy-1.17.1-cp314-cp314t-win_amd64.whl", hash = "sha256:eb092099205ef62cd1782b006658db09e2fed75bffcae7cc0d44052d8aa0f484", size = 37345327, upload-time = "2026-02-23T00:22:24.442Z" }, + { url = "https://files.pythonhosted.org/packages/07/39/338d9219c4e87f3e708f18857ecd24d22a0c3094752393319553096b98af/scipy-1.17.1-cp314-cp314t-win_arm64.whl", hash = "sha256:200e1050faffacc162be6a486a984a0497866ec54149a01270adc8a59b7c7d21", size = 25489165, upload-time = "2026-02-23T00:22:29.563Z" }, ] [[package]] name = "setuptools" -version = "80.9.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz", hash = "sha256:f36b47402ecde768dbfafc46e8e4207b4360c654f1f3bb84475f0a28628fb19c", size = 1319958, upload-time = "2025-05-27T00:56:51.443Z" } +version = "81.0.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299, upload-time = "2026-02-06T21:10:39.601Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a3/dc/17031897dae0efacfea57dfd3a82fdd2a2aeb58e0ff71b77b87e44edc772/setuptools-80.9.0-py3-none-any.whl", hash = "sha256:062d34222ad13e0cc312a4c02d73f059e86a4acbfbdea8f8f76b28c99f306922", size = 1201486, upload-time = "2025-05-27T00:56:49.664Z" }, + { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, ] [[package]] name = "six" version = "1.17.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } sdist = { url = "https://files.pythonhosted.org/packages/94/e7/b2c673351809dca68a0e064b6af791aa332cf192da575fd474ed7d6f16a2/six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81", size = 34031, upload-time = "2024-12-04T17:35:28.174Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, @@ -1587,7 +1711,7 @@ wheels = [ [[package]] name = "sympy" version = "1.14.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "mpmath" }, ] @@ -1599,7 +1723,7 @@ wheels = [ [[package]] name = "tifffile" version = "2024.12.12" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "numpy" }, ] @@ -1610,98 +1734,95 @@ wheels = [ [[package]] name = "toolz" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/8a/0b/d80dfa675bf592f636d1ea0b835eab4ec8df6e9415d8cfd766df54456123/toolz-1.0.0.tar.gz", hash = "sha256:2c86e3d9a04798ac556793bced838816296a2f085017664e4995cb40a1047a02", size = 66790, upload-time = "2024-10-04T16:17:04.001Z" } +version = "1.1.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/11/d6/114b492226588d6ff54579d95847662fc69196bdeec318eb45393b24c192/toolz-1.1.0.tar.gz", hash = "sha256:27a5c770d068c110d9ed9323f24f1543e83b2f300a687b7891c1a6d56b697b5b", size = 52613, upload-time = "2025-10-17T04:03:21.661Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/03/98/eb27cc78ad3af8e302c9d8ff4977f5026676e130d28dd7578132a457170c/toolz-1.0.0-py3-none-any.whl", hash = "sha256:292c8f1c4e7516bf9086f8850935c799a874039c8bcf959d47b600e4c44a6236", size = 56383, upload-time = "2024-10-04T16:17:01.533Z" }, + { url = "https://files.pythonhosted.org/packages/fb/12/5911ae3eeec47800503a238d971e51722ccea5feb8569b735184d5fcdbc0/toolz-1.1.0-py3-none-any.whl", hash = "sha256:15ccc861ac51c53696de0a5d6d4607f99c210739caf987b5d2054f3efed429d8", size = 58093, upload-time = "2025-10-17T04:03:20.435Z" }, ] [[package]] name = "torch" -version = "2.9.0" -source = { registry = "https://pypi.org/simple" } +version = "2.11.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ + { name = "cuda-bindings", marker = "sys_platform == 'linux'" }, + { name = "cuda-toolkit", extra = ["cublas", "cudart", "cufft", "cufile", "cupti", "curand", "cusolver", "cusparse", "nvjitlink", "nvrtc", "nvtx"], marker = "sys_platform == 'linux'" }, { name = "filelock" }, { name = "fsspec" }, { name = "jinja2" }, { name = "networkx" }, - { name = "nvidia-cublas-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-cupti-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-nvrtc-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvshmem-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "setuptools", marker = "python_full_version >= '3.12'" }, + { name = "nvidia-cudnn-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-cusparselt-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nccl-cu13", marker = "sys_platform == 'linux'" }, + { name = "nvidia-nvshmem-cu13", marker = "sys_platform == 'linux'" }, + { name = "setuptools" }, { name = "sympy" }, - { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "triton", marker = "sys_platform == 'linux'" }, { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/58/fe/334225e6330e672b36aef23d77451fa906ea12881570c08638a91331a212/torch-2.9.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:c596708b5105d0b199215acf0c9be7c1db5f1680d88eddadf4b75a299259a677", size = 104230578, upload-time = "2025-10-15T15:46:08.182Z" }, - { url = "https://files.pythonhosted.org/packages/05/cc/49566caaa218872ec9a2912456f470ff92649894a4bc2e5274aa9ef87c4a/torch-2.9.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:51de31219c97c51cf4bf2be94d622e3deb5dcc526c6dc00e97c17eaec0fc1d67", size = 899815990, upload-time = "2025-10-15T15:48:03.336Z" }, - { url = "https://files.pythonhosted.org/packages/74/25/e9ab21d5925b642d008f139d4a3c9664fc9ee1faafca22913c080cc4c0a5/torch-2.9.0-cp311-cp311-win_amd64.whl", hash = "sha256:dd515c70059afd95f48b8192733764c08ca37a1d19803af6401b5ecad7c8676e", size = 109313698, upload-time = "2025-10-15T15:46:12.425Z" }, - { url = "https://files.pythonhosted.org/packages/b3/b7/205ef3e94de636feffd64b28bb59a0dfac0771221201b9871acf9236f5ca/torch-2.9.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:614a185e4986326d526a91210c8fc1397e76e8cfafa78baf6296a790e53a9eec", size = 74463678, upload-time = "2025-10-15T15:46:29.779Z" }, - { url = "https://files.pythonhosted.org/packages/d1/d3/3985739f3b8e88675127bf70f82b3a48ae083e39cda56305dbd90398fec0/torch-2.9.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:e5f7af1dc4c0a7c4a260c2534f41ddaf209714f7c89145e644c44712fbd6b642", size = 104107898, upload-time = "2025-10-15T15:46:20.883Z" }, - { url = "https://files.pythonhosted.org/packages/a5/4b/f4bb2e6c25d0272f798cd6d7a04ed315da76cec68c602d87040c7847287f/torch-2.9.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:01cff95ecd9a212ea2f141db28acccdceb6a4c54f64e6c51091146f5e2a772c6", size = 899738273, upload-time = "2025-10-15T15:50:04.188Z" }, - { url = "https://files.pythonhosted.org/packages/66/11/c1c5ba6691cda6279087c35bd626536e4fd29521fe740abf5008377a9a02/torch-2.9.0-cp312-cp312-win_amd64.whl", hash = "sha256:4582b162f541651f0cb184d3e291c05c2f556c7117c64a9873e2ee158d40062b", size = 109280887, upload-time = "2025-10-15T15:46:26.228Z" }, - { url = "https://files.pythonhosted.org/packages/dd/5f/b85bd8c05312d71de9402bf5868d217c38827cfd09d8f8514e5be128a52b/torch-2.9.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:33f58e9a102a91259af289d50525c30323b5c9ae1d31322b6447c0814da68695", size = 74478983, upload-time = "2025-10-15T15:46:39.406Z" }, - { url = "https://files.pythonhosted.org/packages/c2/1c/90eb13833cdf4969ea9707586d7b57095c3b6e2b223a7256bf111689bcb8/torch-2.9.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:c30a17fc83eeab346913e237c64b15b5ba6407fff812f6c541e322e19bc9ea0e", size = 104111330, upload-time = "2025-10-15T15:46:35.238Z" }, - { url = "https://files.pythonhosted.org/packages/0e/21/2254c54b8d523592c25ef4434769aa23e29b1e6bf5f4c0ad9e27bf442927/torch-2.9.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:8f25033b8667b57857dfd01458fbf2a9e6a6df1f8def23aef0dc46292f6aa642", size = 899750243, upload-time = "2025-10-15T15:48:57.459Z" }, - { url = "https://files.pythonhosted.org/packages/b7/a5/5cb94fa4fd1e78223455c23c200f30f6dc10c6d4a2bcc8f6e7f2a2588370/torch-2.9.0-cp313-cp313-win_amd64.whl", hash = "sha256:d037f1b4ffd25013be4a7bf3651a0a910c68554956c7b2c92ebe87c76475dece", size = 109284513, upload-time = "2025-10-15T15:46:45.061Z" }, - { url = "https://files.pythonhosted.org/packages/66/e8/fc414d8656250ee46120b44836ffbb3266343db424b3e18ca79ebbf69d4f/torch-2.9.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:e4e5b5cba837a2a8d1a497ba9a58dae46fa392593eaa13b871c42f71847503a5", size = 74830362, upload-time = "2025-10-15T15:46:48.983Z" }, - { url = "https://files.pythonhosted.org/packages/ed/5f/9474c98fc5ae0cd04b9466035428cd360e6611a86b8352a0fc2fa504acdc/torch-2.9.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:64693568f5dc4dbd5f880a478b1cea0201cc6b510d91d1bc54fea86ac5d1a637", size = 104144940, upload-time = "2025-10-15T15:47:29.076Z" }, - { url = "https://files.pythonhosted.org/packages/2d/5a/8e0c1cf57830172c109d4bd6be2708cabeaf550983eee7029291322447a0/torch-2.9.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:f8ed31ddd7d10bfb3fbe0b9fe01b1243577f13d75e6f4a0839a283915ce3791e", size = 899744054, upload-time = "2025-10-15T15:48:29.864Z" }, - { url = "https://files.pythonhosted.org/packages/6d/28/82c28b30fcb4b7c9cdd995763d18bbb830d6521356712faebbad92ffa61d/torch-2.9.0-cp313-cp313t-win_amd64.whl", hash = "sha256:eff527d4e4846e6f70d2afd8058b73825761203d66576a7e04ea2ecfebcb4ab8", size = 109517546, upload-time = "2025-10-15T15:47:33.395Z" }, - { url = "https://files.pythonhosted.org/packages/ff/c3/a91f96ec74347fa5fd24453fa514bc61c61ecc79196fa760b012a1873d96/torch-2.9.0-cp313-none-macosx_11_0_arm64.whl", hash = "sha256:f8877779cf56d1ce431a7636703bdb13307f5960bb1af49716d8b179225e0e6a", size = 74480732, upload-time = "2025-10-15T15:47:38.002Z" }, - { url = "https://files.pythonhosted.org/packages/5c/73/9f70af34b334a7e0ef496ceec96b7ec767bd778ea35385ce6f77557534d1/torch-2.9.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:7e614fae699838038d888729f82b687c03413c5989ce2a9481f9a7e7a396e0bb", size = 74433037, upload-time = "2025-10-15T15:47:41.894Z" }, - { url = "https://files.pythonhosted.org/packages/b7/84/37cf88625901934c97109e583ecc21777d21c6f54cda97a7e5bbad1ee2f2/torch-2.9.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:dfb5b8cd310ba3436c7e14e8b7833ef658cf3045e50d2bdaed23c8fc517065eb", size = 104116482, upload-time = "2025-10-15T15:47:46.266Z" }, - { url = "https://files.pythonhosted.org/packages/56/8e/ca8b17866943a8d4f4664d402ea84210aa274588b4c5d89918f5caa24eec/torch-2.9.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:b3d29524993a478e46f5d598b249cd824b7ed98d7fba538bd9c4cde6c803948f", size = 899746916, upload-time = "2025-10-15T15:50:40.294Z" }, - { url = "https://files.pythonhosted.org/packages/43/65/3b17c0fbbdab6501c5b320a52a648628d0d44e7379f64e27d9eef701b6bf/torch-2.9.0-cp314-cp314-win_amd64.whl", hash = "sha256:71c7578984f5ec0eb645eb4816ac8435fcf3e3e2ae1901bcd2f519a9cafb5125", size = 109275151, upload-time = "2025-10-15T15:49:20.715Z" }, - { url = "https://files.pythonhosted.org/packages/83/36/74f8c051f785500396e42f93542422422dfd874a174f21f8d955d36e5d64/torch-2.9.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:71d9309aee457bbe0b164bce2111cd911c4ed4e847e65d5077dbbcd3aba6befc", size = 74823353, upload-time = "2025-10-15T15:49:16.59Z" }, - { url = "https://files.pythonhosted.org/packages/62/51/dc3b4e2f9ba98ae27238f0153ca098bf9340b2dafcc67fde645d496dfc2a/torch-2.9.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:c08fb654d783899e204a32cca758a7ce8a45b2d78eeb89517cc937088316f78e", size = 104140340, upload-time = "2025-10-15T15:50:19.67Z" }, - { url = "https://files.pythonhosted.org/packages/c0/8d/b00657f8141ac16af7bb6cda2e67de18499a3263b78d516b9a93fcbc98e3/torch-2.9.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:ec8feb0099b2daa5728fbc7abb0b05730fd97e0f359ff8bda09865aaa7bd7d4b", size = 899731750, upload-time = "2025-10-15T15:49:36.673Z" }, - { url = "https://files.pythonhosted.org/packages/fc/29/bd361e0cbb2c79ce6450f42643aaf6919956f89923a50571b0ebfe92d142/torch-2.9.0-cp314-cp314t-win_amd64.whl", hash = "sha256:695ba920f234ad4170c9c50e28d56c848432f8f530e6bc7f88fcb15ddf338e75", size = 109503850, upload-time = "2025-10-15T15:50:24.118Z" }, + { url = "https://files.pythonhosted.org/packages/ae/0d/98b410492609e34a155fa8b121b55c7dca229f39636851c3a9ec20edea21/torch-2.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7b6a60d48062809f58595509c524b88e6ddec3ebe25833d6462eeab81e5f2ce4", size = 80529712, upload-time = "2026-03-23T18:12:02.608Z" }, + { url = "https://files.pythonhosted.org/packages/84/03/acea680005f098f79fd70c1d9d5ccc0cb4296ec2af539a0450108232fc0c/torch-2.11.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:d91aac77f24082809d2c5a93f52a5f085032740a1ebc9252a7b052ef5a4fddc6", size = 419718178, upload-time = "2026-03-23T18:10:46.675Z" }, + { url = "https://files.pythonhosted.org/packages/8c/8b/d7be22fbec9ffee6cff31a39f8750d4b3a65d349a286cf4aec74c2375662/torch-2.11.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:7aa2f9bbc6d4595ba72138026b2074be1233186150e9292865e04b7a63b8c67a", size = 530604548, upload-time = "2026-03-23T18:10:03.569Z" }, + { url = "https://files.pythonhosted.org/packages/d1/bd/9912d30b68845256aabbb4a40aeefeef3c3b20db5211ccda653544ada4b6/torch-2.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:73e24aaf8f36ab90d95cd1761208b2eb70841c2a9ca1a3f9061b39fc5331b708", size = 114519675, upload-time = "2026-03-23T18:11:52.995Z" }, + { url = "https://files.pythonhosted.org/packages/6f/8b/69e3008d78e5cee2b30183340cc425081b78afc5eff3d080daab0adda9aa/torch-2.11.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4b5866312ee6e52ea625cd211dcb97d6a2cdc1131a5f15cc0d87eec948f6dd34", size = 80606338, upload-time = "2026-03-23T18:11:34.781Z" }, + { url = "https://files.pythonhosted.org/packages/13/16/42e5915ebe4868caa6bac83a8ed59db57f12e9a61b7d749d584776ed53d5/torch-2.11.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f99924682ef0aa6a4ab3b1b76f40dc6e273fca09f367d15a524266db100a723f", size = 419731115, upload-time = "2026-03-23T18:11:06.944Z" }, + { url = "https://files.pythonhosted.org/packages/1a/c9/82638ef24d7877510f83baf821f5619a61b45568ce21c0a87a91576510aa/torch-2.11.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:0f68f4ac6d95d12e896c3b7a912b5871619542ec54d3649cf48cc1edd4dd2756", size = 530712279, upload-time = "2026-03-23T18:10:31.481Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ff/6756f1c7ee302f6d202120e0f4f05b432b839908f9071157302cedfc5232/torch-2.11.0-cp312-cp312-win_amd64.whl", hash = "sha256:fbf39280699d1b869f55eac536deceaa1b60bd6788ba74f399cc67e60a5fab10", size = 114556047, upload-time = "2026-03-23T18:10:55.931Z" }, + { url = "https://files.pythonhosted.org/packages/87/89/5ea6722763acee56b045435fb84258db7375c48165ec8be7880ab2b281c5/torch-2.11.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1e6debd97ccd3205bbb37eb806a9d8219e1139d15419982c09e23ef7d4369d18", size = 80606801, upload-time = "2026-03-23T18:10:18.649Z" }, + { url = "https://files.pythonhosted.org/packages/32/d1/8ed2173589cbfe744ed54e5a73efc107c0085ba5777ee93a5f4c1ab90553/torch-2.11.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:63a68fa59de8f87acc7e85a5478bb2dddbb3392b7593ec3e78827c793c4b73fd", size = 419732382, upload-time = "2026-03-23T18:08:30.835Z" }, + { url = "https://files.pythonhosted.org/packages/3d/e1/b73f7c575a4b8f87a5928f50a1e35416b5e27295d8be9397d5293e7e8d4c/torch-2.11.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:cc89b9b173d9adfab59fd227f0ab5e5516d9a52b658ae41d64e59d2e55a418db", size = 530711509, upload-time = "2026-03-23T18:08:47.213Z" }, + { url = "https://files.pythonhosted.org/packages/66/82/3e3fcdd388fbe54e29fd3f991f36846ff4ac90b0d0181e9c8f7236565f82/torch-2.11.0-cp313-cp313-win_amd64.whl", hash = "sha256:4dda3b3f52d121063a731ddb835f010dc137b920d7fec2778e52f60d8e4bf0cd", size = 114555842, upload-time = "2026-03-23T18:09:52.111Z" }, + { url = "https://files.pythonhosted.org/packages/db/38/8ac78069621b8c2b4979c2f96dc8409ef5e9c4189f6aac629189a78677ca/torch-2.11.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:8b394322f49af4362d4f80e424bcaca7efcd049619af03a4cf4501520bdf0fb4", size = 80959574, upload-time = "2026-03-23T18:10:14.214Z" }, + { url = "https://files.pythonhosted.org/packages/6d/6c/56bfb37073e7136e6dd86bfc6af7339946dd684e0ecf2155ac0eee687ae1/torch-2.11.0-cp313-cp313t-manylinux_2_28_aarch64.whl", hash = "sha256:2658f34ce7e2dabf4ec73b45e2ca68aedad7a5be87ea756ad656eaf32bf1e1ea", size = 419732324, upload-time = "2026-03-23T18:09:36.604Z" }, + { url = "https://files.pythonhosted.org/packages/07/f4/1b666b6d61d3394cca306ea543ed03a64aad0a201b6cd159f1d41010aeb1/torch-2.11.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:98bb213c3084cfe176302949bdc360074b18a9da7ab59ef2edc9d9f742504778", size = 530596026, upload-time = "2026-03-23T18:09:20.842Z" }, + { url = "https://files.pythonhosted.org/packages/48/6b/30d1459fa7e4b67e9e3fe1685ca1d8bb4ce7c62ef436c3a615963c6c866c/torch-2.11.0-cp313-cp313t-win_amd64.whl", hash = "sha256:a97b94bbf62992949b4730c6cd2cc9aee7b335921ee8dc207d930f2ed09ae2db", size = 114793702, upload-time = "2026-03-23T18:09:47.304Z" }, + { url = "https://files.pythonhosted.org/packages/26/0d/8603382f61abd0db35841148ddc1ffd607bf3100b11c6e1dab6d2fc44e72/torch-2.11.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:01018087326984a33b64e04c8cb5c2795f9120e0d775ada1f6638840227b04d7", size = 80573442, upload-time = "2026-03-23T18:09:10.117Z" }, + { url = "https://files.pythonhosted.org/packages/c7/86/7cd7c66cb9cec6be330fff36db5bd0eef386d80c031b581ec81be1d4b26c/torch-2.11.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:2bb3cc54bd0dea126b0060bb1ec9de0f9c7f7342d93d436646516b0330cd5be7", size = 419749385, upload-time = "2026-03-23T18:07:33.77Z" }, + { url = "https://files.pythonhosted.org/packages/47/e8/b98ca2d39b2e0e4730c0ee52537e488e7008025bc77ca89552ff91021f7c/torch-2.11.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:4dc8b3809469b6c30b411bb8c4cad3828efd26236153d9beb6a3ec500f211a60", size = 530716756, upload-time = "2026-03-23T18:07:50.02Z" }, + { url = "https://files.pythonhosted.org/packages/78/88/d4a4cda8362f8a30d1ed428564878c3cafb0d87971fbd3947d4c84552095/torch-2.11.0-cp314-cp314-win_amd64.whl", hash = "sha256:2b4e811728bd0cc58fb2b0948fe939a1ee2bf1422f6025be2fca4c7bd9d79718", size = 114552300, upload-time = "2026-03-23T18:09:05.617Z" }, + { url = "https://files.pythonhosted.org/packages/bf/46/4419098ed6d801750f26567b478fc185c3432e11e2cad712bc6b4c2ab0d0/torch-2.11.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:8245477871c3700d4370352ffec94b103cfcb737229445cf9946cddb7b2ca7cd", size = 80959460, upload-time = "2026-03-23T18:09:00.818Z" }, + { url = "https://files.pythonhosted.org/packages/fd/66/54a56a4a6ceaffb567231994a9745821d3af922a854ed33b0b3a278e0a99/torch-2.11.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:ab9a8482f475f9ba20e12db84b0e55e2f58784bdca43a854a6ccd3fd4b9f75e6", size = 419735835, upload-time = "2026-03-23T18:07:18.974Z" }, + { url = "https://files.pythonhosted.org/packages/b1/e7/0b6665f533aa9e337662dc190425abc0af1fe3234088f4454c52393ded61/torch-2.11.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:563ed3d25542d7e7bbc5b235ccfacfeb97fb470c7fee257eae599adb8005c8a2", size = 530613405, upload-time = "2026-03-23T18:08:07.014Z" }, + { url = "https://files.pythonhosted.org/packages/cf/bf/c8d12a2c86dbfd7f40fb2f56fbf5a505ccf2d9ce131eb559dfc7c51e1a04/torch-2.11.0-cp314-cp314t-win_amd64.whl", hash = "sha256:b2a43985ff5ef6ddd923bbcf99943e5f58059805787c5c9a2622bf05ca2965b0", size = 114792991, upload-time = "2026-03-23T18:08:19.216Z" }, ] [[package]] name = "tqdm" -version = "4.67.1" -source = { registry = "https://pypi.org/simple" } +version = "4.67.3" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "colorama", marker = "sys_platform == 'win32'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/a8/4b/29b4ef32e036bb34e4ab51796dd745cdba7ed47ad142a9f4a1eb8e0c744d/tqdm-4.67.1.tar.gz", hash = "sha256:f8aef9c52c08c13a65f30ea34f4e5aac3fd1a34959879d7e59e63027286627f2", size = 169737, upload-time = "2024-11-24T20:12:22.481Z" } +sdist = { url = "https://files.pythonhosted.org/packages/09/a9/6ba95a270c6f1fbcd8dac228323f2777d886cb206987444e4bce66338dd4/tqdm-4.67.3.tar.gz", hash = "sha256:7d825f03f89244ef73f1d4ce193cb1774a8179fd96f31d7e1dcde62092b960bb", size = 169598, upload-time = "2026-02-03T17:35:53.048Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d0/30/dc54f88dd4a2b5dc8a0279bdd7270e735851848b762aeb1c1184ed1f6b14/tqdm-4.67.1-py3-none-any.whl", hash = "sha256:26445eca388f82e72884e0d580d5464cd801a3ea01e63e5601bdff9ba6a48de2", size = 78540, upload-time = "2024-11-24T20:12:19.698Z" }, + { url = "https://files.pythonhosted.org/packages/16/e1/3079a9ff9b8e11b846c6ac5c8b5bfb7ff225eee721825310c91b3b50304f/tqdm-4.67.3-py3-none-any.whl", hash = "sha256:ee1e4c0e59148062281c49d80b25b67771a127c85fc9676d3be5f243206826bf", size = 78374, upload-time = "2026-02-03T17:35:50.982Z" }, ] [[package]] name = "triton" -version = "3.5.0" -source = { registry = "https://pypi.org/simple" } +version = "3.6.0" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/3d/78/949a04391c21956c816523678f0e5fa308eb5b1e7622d88c4e4ef5fceca0/triton-3.5.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f34bfa21c5b3a203c0f0eab28dcc1e49bd1f67d22724e77fb6665a659200a4ec", size = 170433488, upload-time = "2025-10-13T16:37:57.132Z" }, - { url = "https://files.pythonhosted.org/packages/f5/3a/e991574f3102147b642e49637e0281e9bb7c4ba254edb2bab78247c85e01/triton-3.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c9e71db82261c4ffa3921cd050cd5faa18322d2d405c30eb56084afaff3b0833", size = 170476535, upload-time = "2025-10-13T16:38:05.18Z" }, - { url = "https://files.pythonhosted.org/packages/6c/29/10728de8a6e932e517c10773486b8e99f85d1b1d9dd87d9a9616e1fef4a1/triton-3.5.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e6bb9aa5519c084a333acdba443789e50012a4b851cd486c54f0b8dc2a8d3a12", size = 170487289, upload-time = "2025-10-13T16:38:11.662Z" }, - { url = "https://files.pythonhosted.org/packages/5c/38/db80e48b9220c9bce872b0f616ad0446cdf554a40b85c7865cbca99ab3c2/triton-3.5.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c83f2343e1a220a716c7b3ab9fccfcbe3ad4020d189549200e2d2e8d5868bed9", size = 170577179, upload-time = "2025-10-13T16:38:17.865Z" }, - { url = "https://files.pythonhosted.org/packages/ff/60/1810655d1d856c9a4fcc90ee8966d85f552d98c53a6589f95ab2cbe27bb8/triton-3.5.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:da0fa67ccd76c3dcfb0bffe1b1c57c685136a6bd33d141c24d9655d4185b1289", size = 170487949, upload-time = "2025-10-13T16:38:24.881Z" }, - { url = "https://files.pythonhosted.org/packages/fb/b7/1dec8433ac604c061173d0589d99217fe7bf90a70bdc375e745d044b8aad/triton-3.5.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:317fe477ea8fd4524a6a8c499fb0a36984a56d0b75bf9c9cb6133a1c56d5a6e7", size = 170580176, upload-time = "2025-10-13T16:38:31.14Z" }, + { url = "https://files.pythonhosted.org/packages/0f/2c/96f92f3c60387e14cc45aed49487f3486f89ea27106c1b1376913c62abe4/triton-3.6.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:49df5ef37379c0c2b5c0012286f80174fcf0e073e5ade1ca9a86c36814553651", size = 176081190, upload-time = "2026-01-20T16:16:00.523Z" }, + { url = "https://files.pythonhosted.org/packages/e0/12/b05ba554d2c623bffa59922b94b0775673de251f468a9609bc9e45de95e9/triton-3.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e323d608e3a9bfcc2d9efcc90ceefb764a82b99dea12a86d643c72539ad5d3", size = 188214640, upload-time = "2026-01-20T16:00:35.869Z" }, + { url = "https://files.pythonhosted.org/packages/17/5d/08201db32823bdf77a0e2b9039540080b2e5c23a20706ddba942924ebcd6/triton-3.6.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:374f52c11a711fd062b4bfbb201fd9ac0a5febd28a96fb41b4a0f51dde3157f4", size = 176128243, upload-time = "2026-01-20T16:16:07.857Z" }, + { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, + { url = "https://files.pythonhosted.org/packages/3c/12/34d71b350e89a204c2c7777a9bba0dcf2f19a5bfdd70b57c4dbc5ffd7154/triton-3.6.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:448e02fe6dc898e9e5aa89cf0ee5c371e99df5aa5e8ad976a80b93334f3494fd", size = 176133521, upload-time = "2026-01-20T16:16:13.321Z" }, + { url = "https://files.pythonhosted.org/packages/f9/0b/37d991d8c130ce81a8728ae3c25b6e60935838e9be1b58791f5997b24a54/triton-3.6.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:10c7f76c6e72d2ef08df639e3d0d30729112f47a56b0c81672edc05ee5116ac9", size = 188289450, upload-time = "2026-01-20T16:00:49.136Z" }, + { url = "https://files.pythonhosted.org/packages/ce/4e/41b0c8033b503fd3cfcd12392cdd256945026a91ff02452bef40ec34bee7/triton-3.6.0-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1722e172d34e32abc3eb7711d0025bb69d7959ebea84e3b7f7a341cd7ed694d6", size = 176276087, upload-time = "2026-01-20T16:16:18.989Z" }, + { url = "https://files.pythonhosted.org/packages/35/f8/9c66bfc55361ec6d0e4040a0337fb5924ceb23de4648b8a81ae9d33b2b38/triton-3.6.0-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d002e07d7180fd65e622134fbd980c9a3d4211fb85224b56a0a0efbd422ab72f", size = 188400296, upload-time = "2026-01-20T16:00:56.042Z" }, + { url = "https://files.pythonhosted.org/packages/49/55/5ecf0dcaa0f2fbbd4420f7ef227ee3cb172e91e5fede9d0ecaddc43363b4/triton-3.6.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ef5523241e7d1abca00f1d240949eebdd7c673b005edbbce0aca95b8191f1d43", size = 176138577, upload-time = "2026-01-20T16:16:25.426Z" }, + { url = "https://files.pythonhosted.org/packages/df/3d/9e7eee57b37c80cec63322c0231bb6da3cfe535a91d7a4d64896fcb89357/triton-3.6.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a17a5d5985f0ac494ed8a8e54568f092f7057ef60e1b0fa09d3fd1512064e803", size = 188273063, upload-time = "2026-01-20T16:01:07.278Z" }, + { url = "https://files.pythonhosted.org/packages/48/db/56ee649cab5eaff4757541325aca81f52d02d4a7cd3506776cad2451e060/triton-3.6.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0b3a97e8ed304dfa9bd23bb41ca04cdf6b2e617d5e782a8653d616037a5d537d", size = 176274804, upload-time = "2026-01-20T16:16:31.528Z" }, + { url = "https://files.pythonhosted.org/packages/f6/56/6113c23ff46c00aae423333eb58b3e60bdfe9179d542781955a5e1514cb3/triton-3.6.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:46bd1c1af4b6704e554cad2eeb3b0a6513a980d470ccfa63189737340c7746a7", size = 188397994, upload-time = "2026-01-20T16:01:14.236Z" }, ] [[package]] name = "typing-extensions" version = "4.15.0" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } sdist = { url = "https://files.pythonhosted.org/packages/72/94/1a15dd82efb362ac84269196e94cf00f187f7ed21c242792a923cdb1c61f/typing_extensions-4.15.0.tar.gz", hash = "sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466", size = 109391, upload-time = "2025-08-25T13:49:26.313Z" } wheels = [ { url = "https://files.pythonhosted.org/packages/18/67/36e9267722cc04a6b9f15c7f3441c2363321a3ea07da7ae0c0707beb2a9c/typing_extensions-4.15.0-py3-none-any.whl", hash = "sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548", size = 44614, upload-time = "2025-08-25T13:49:24.86Z" }, @@ -1710,7 +1831,7 @@ wheels = [ [[package]] name = "typing-inspection" version = "0.4.2" -source = { registry = "https://pypi.org/simple" } +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } dependencies = [ { name = "typing-extensions" }, ] @@ -1721,11 +1842,11 @@ wheels = [ [[package]] name = "tzdata" -version = "2025.3" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/5e/a7/c202b344c5ca7daf398f3b8a477eeb205cf3b6f32e7ec3a6bac0629ca975/tzdata-2025.3.tar.gz", hash = "sha256:de39c2ca5dc7b0344f2eba86f49d614019d29f060fc4ebc8a417896a620b56a7", size = 196772, upload-time = "2025-12-13T17:45:35.667Z" } +version = "2026.1" +source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/19/f5/cd531b2d15a671a40c0f66cf06bc3570a12cd56eef98960068ebbad1bf5a/tzdata-2026.1.tar.gz", hash = "sha256:67658a1903c75917309e753fdc349ac0efd8c27db7a0cb406a25be4840f87f98", size = 197639, upload-time = "2026-04-03T11:25:22.002Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/c7/b0/003792df09decd6849a5e39c28b513c06e84436a54440380862b5aeff25d/tzdata-2025.3-py2.py3-none-any.whl", hash = "sha256:06a47e5700f3081aab02b2e513160914ff0694bce9947d6b76ebd6bf57cfc5d1", size = 348521, upload-time = "2025-12-13T17:45:33.889Z" }, + { url = "https://files.pythonhosted.org/packages/b0/70/d460bd685a170790ec89317e9bd33047988e4bce507b831f5db771e142de/tzdata-2026.1-py2.py3-none-any.whl", hash = "sha256:4b1d2be7ac37ceafd7327b961aa3a54e467efbdb563a23655fbfe0d39cfc42a9", size = 348952, upload-time = "2026-04-03T11:25:20.313Z" }, ] [[package]] @@ -1741,8 +1862,8 @@ dependencies = [ { name = "matplotlib" }, { name = "numpy" }, { name = "optax" }, - { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.14'" }, - { name = "pandas", version = "3.0.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.14'" }, + { name = "pandas", version = "2.3.3", source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" }, marker = "python_full_version >= '3.14'" }, + { name = "pandas", version = "3.0.2", source = { registry = "https://gitlab.college-de-france.fr/api/v4/projects/145/packages/pypi/simple" }, marker = "python_full_version < '3.14'" }, { name = "scikit-image" }, { name = "scipy" }, { name = "tifffile" }, @@ -1750,6 +1871,7 @@ dependencies = [ [package.dev-dependencies] dev = [ + { name = "pdoc" }, { name = "pytest" }, { name = "scalene" }, ] @@ -1772,6 +1894,7 @@ requires-dist = [ [package.metadata.requires-dev] dev = [ + { name = "pdoc", specifier = ">=16.0.0" }, { name = "pytest", specifier = ">=8.4.2" }, { name = "scalene", specifier = ">=1.5.55" }, ]