From 425fc0e4d5c74320af442ec31f4620759b441ea8 Mon Sep 17 00:00:00 2001 From: Michal Klein Date: Thu, 10 Nov 2022 18:07:51 -0800 Subject: [PATCH 01/34] Reorganize repo structure --- {ott/examples => examples}/fairness/config.py | 0 {ott/examples => examples}/fairness/data.py | 0 {ott/examples => examples}/fairness/losses.py | 0 {ott/examples => examples}/fairness/main.py | 0 {ott/examples => examples}/fairness/models.py | 0 {ott/examples => examples}/fairness/train.py | 0 .../soft_error/config.py | 0 {ott/examples => examples}/soft_error/data.py | 0 .../soft_error/losses.py | 0 {ott/examples => examples}/soft_error/main.py | 0 .../examples => examples}/soft_error/model.py | 0 .../examples => examples}/soft_error/train.py | 0 ott/__init__.py | 2 +- ott/core/__init__.py | 44 ---- ott/core/_math_utils.py | 33 --- ott/core/momentum.py | 71 ------ ott/core/problems.py | 93 ------- ott/geometry/__init__.py | 7 +- ott/geometry/costs.py | 18 +- ott/geometry/geometry.py | 9 +- ott/geometry/graph.py | 4 +- ott/geometry/grid.py | 16 +- ott/geometry/pointcloud.py | 15 +- ott/initializers/__init__.py | 1 + ott/initializers/linear/__init__.py | 0 .../linear}/initializers.py | 240 ++---------------- .../linear}/initializers_lr.py | 44 ++-- ott/initializers/nn/__init__.py | 0 ott/initializers/nn/initializers.py | 188 ++++++++++++++ ott/initializers/nn/layers.py | 39 +++ ott/initializers/quadratic/__init__.py | 0 .../quadratic/initializers.py} | 42 +-- ott/math/__init__.py | 0 ott/{core => math}/decomposition.py | 0 ott/{core => math}/fixed_point_loop.py | 2 +- .../implicit_differentiation.py | 4 +- ott/{geometry => math}/matrix_square_root.py | 2 +- ott/{core => math}/potentials.py | 0 ott/{core => math}/unbalanced_functions.py | 0 ott/{geometry/ops.py => math/utils.py} | 56 ++-- ott/problems/__init__.py | 1 + ott/problems/linear/__init__.py | 2 + ott/problems/linear/barycenter_problem.py | 182 +++++++++++++ .../linear/linear_problem.py} | 2 + ott/problems/quadratic/__init__.py | 3 + .../quadratic/barycenter_problem.py} | 208 ++------------- ott/problems/quadratic/quadratic_costs.py | 34 +++ .../quadratic/quadratic_problem.py} | 82 ++---- ott/solvers/__init__.py | 1 + ott/solvers/linear/__init__.py | 0 .../linear/acceleration.py} | 86 +++++-- .../linear}/continuous_barycenter.py | 23 +- .../linear}/discrete_barycenter.py | 20 +- ott/{core => solvers/linear}/sinkhorn.py | 80 +++--- ott/{core => solvers/linear}/sinkhorn_lr.py | 47 ++-- ott/solvers/nn/__init__.py | 1 + ott/{core => solvers/nn}/icnn.py | 8 +- ott/{core => solvers/nn}/layers.py | 23 +- ott/{core => solvers/nn}/neuraldual.py | 5 +- ott/solvers/quadratic/__init__.py | 0 .../quadratic}/gromov_wasserstein.py | 41 ++- .../quadratic}/gw_barycenter.py | 27 +- .../gaussian_mixture/gaussian_mixture_pair.py | 2 +- ott/tools/gaussian_mixture/scale_tril.py | 3 +- ott/tools/k_means.py | 2 +- ott/tools/segment_sinkhorn.py | 3 +- ott/tools/sinkhorn_divergence.py | 4 +- ott/tools/transport.py | 91 ++++++- ott/typing.py | 22 ++ ott/utils/__init__.py | 0 ott/{core => utils}/dataclasses.py | 0 ott/{core => utils}/segment.py | 0 ott/{core => utils}/was_solver.py | 2 +- 73 files changed, 964 insertions(+), 971 deletions(-) rename {ott/examples => examples}/fairness/config.py (100%) rename {ott/examples => examples}/fairness/data.py (100%) rename {ott/examples => examples}/fairness/losses.py (100%) rename {ott/examples => examples}/fairness/main.py (100%) rename {ott/examples => examples}/fairness/models.py (100%) rename {ott/examples => examples}/fairness/train.py (100%) rename {ott/examples => examples}/soft_error/config.py (100%) rename {ott/examples => examples}/soft_error/data.py (100%) rename {ott/examples => examples}/soft_error/losses.py (100%) rename {ott/examples => examples}/soft_error/main.py (100%) rename {ott/examples => examples}/soft_error/model.py (100%) rename {ott/examples => examples}/soft_error/train.py (100%) delete mode 100644 ott/core/__init__.py delete mode 100644 ott/core/_math_utils.py delete mode 100644 ott/core/momentum.py delete mode 100644 ott/core/problems.py create mode 100644 ott/initializers/__init__.py create mode 100644 ott/initializers/linear/__init__.py rename ott/{core => initializers/linear}/initializers.py (52%) rename ott/{core => initializers/linear}/initializers_lr.py (94%) create mode 100644 ott/initializers/nn/__init__.py create mode 100644 ott/initializers/nn/initializers.py create mode 100644 ott/initializers/nn/layers.py create mode 100644 ott/initializers/quadratic/__init__.py rename ott/{core/quad_initializers.py => initializers/quadratic/initializers.py} (82%) create mode 100644 ott/math/__init__.py rename ott/{core => math}/decomposition.py (100%) rename ott/{core => math}/fixed_point_loop.py (99%) rename ott/{core => math}/implicit_differentiation.py (98%) rename ott/{geometry => math}/matrix_square_root.py (99%) rename ott/{core => math}/potentials.py (100%) rename ott/{core => math}/unbalanced_functions.py (100%) rename ott/{geometry/ops.py => math/utils.py} (62%) create mode 100644 ott/problems/__init__.py create mode 100644 ott/problems/linear/__init__.py create mode 100644 ott/problems/linear/barycenter_problem.py rename ott/{core/linear_problems.py => problems/linear/linear_problem.py} (99%) create mode 100644 ott/problems/quadratic/__init__.py rename ott/{core/bar_problems.py => problems/quadratic/barycenter_problem.py} (57%) create mode 100644 ott/problems/quadratic/quadratic_costs.py rename ott/{core/quad_problems.py => problems/quadratic/quadratic_problem.py} (90%) create mode 100644 ott/solvers/__init__.py create mode 100644 ott/solvers/linear/__init__.py rename ott/{core/anderson.py => solvers/linear/acceleration.py} (62%) rename ott/{core => solvers/linear}/continuous_barycenter.py (89%) rename ott/{core => solvers/linear}/discrete_barycenter.py (94%) rename ott/{core => solvers/linear}/sinkhorn.py (94%) rename ott/{core => solvers/linear}/sinkhorn_lr.py (94%) create mode 100644 ott/solvers/nn/__init__.py rename ott/{core => solvers/nn}/icnn.py (97%) rename ott/{core => solvers/nn}/layers.py (86%) rename ott/{core => solvers/nn}/neuraldual.py (99%) create mode 100644 ott/solvers/quadratic/__init__.py rename ott/{core => solvers/quadratic}/gromov_wasserstein.py (95%) rename ott/{core => solvers/quadratic}/gw_barycenter.py (93%) create mode 100644 ott/typing.py create mode 100644 ott/utils/__init__.py rename ott/{core => utils}/dataclasses.py (100%) rename ott/{core => utils}/segment.py (100%) rename ott/{core => utils}/was_solver.py (98%) diff --git a/ott/examples/fairness/config.py b/examples/fairness/config.py similarity index 100% rename from ott/examples/fairness/config.py rename to examples/fairness/config.py diff --git a/ott/examples/fairness/data.py b/examples/fairness/data.py similarity index 100% rename from ott/examples/fairness/data.py rename to examples/fairness/data.py diff --git a/ott/examples/fairness/losses.py b/examples/fairness/losses.py similarity index 100% rename from ott/examples/fairness/losses.py rename to examples/fairness/losses.py diff --git a/ott/examples/fairness/main.py b/examples/fairness/main.py similarity index 100% rename from ott/examples/fairness/main.py rename to examples/fairness/main.py diff --git a/ott/examples/fairness/models.py b/examples/fairness/models.py similarity index 100% rename from ott/examples/fairness/models.py rename to examples/fairness/models.py diff --git a/ott/examples/fairness/train.py b/examples/fairness/train.py similarity index 100% rename from ott/examples/fairness/train.py rename to examples/fairness/train.py diff --git a/ott/examples/soft_error/config.py b/examples/soft_error/config.py similarity index 100% rename from ott/examples/soft_error/config.py rename to examples/soft_error/config.py diff --git a/ott/examples/soft_error/data.py b/examples/soft_error/data.py similarity index 100% rename from ott/examples/soft_error/data.py rename to examples/soft_error/data.py diff --git a/ott/examples/soft_error/losses.py b/examples/soft_error/losses.py similarity index 100% rename from ott/examples/soft_error/losses.py rename to examples/soft_error/losses.py diff --git a/ott/examples/soft_error/main.py b/examples/soft_error/main.py similarity index 100% rename from ott/examples/soft_error/main.py rename to examples/soft_error/main.py diff --git a/ott/examples/soft_error/model.py b/examples/soft_error/model.py similarity index 100% rename from ott/examples/soft_error/model.py rename to examples/soft_error/model.py diff --git a/ott/examples/soft_error/train.py b/examples/soft_error/train.py similarity index 100% rename from ott/examples/soft_error/train.py rename to examples/soft_error/train.py diff --git a/ott/__init__.py b/ott/__init__.py index df1438bef..8015c263b 100644 --- a/ott/__init__.py +++ b/ott/__init__.py @@ -12,5 +12,5 @@ # See the License for the specific language governing permissions and # limitations under the License. """OTT library.""" -from . import core, geometry, tools +from . import geometry, initializers, math, problems, solvers, tools, utils from ._version import __version__ diff --git a/ott/core/__init__.py b/ott/core/__init__.py deleted file mode 100644 index 4a39d58fc..000000000 --- a/ott/core/__init__.py +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2022 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""OTT core libraries: the engine behind most computations happening in OTT.""" - -# pytype: disable=import-error # kwargs-checking -from . import ( - anderson, - bar_problems, - continuous_barycenter, - dataclasses, - decomposition, - discrete_barycenter, - gromov_wasserstein, - gw_barycenter, - implicit_differentiation, - initializers, - initializers_lr, - linear_problems, - momentum, - potentials, - quad_initializers, - quad_problems, - sinkhorn, - sinkhorn_lr, -) - -# from . import neuraldual -from .implicit_differentiation import ImplicitDiff -from .linear_problems import LinearProblem -from .sinkhorn import Sinkhorn -from .sinkhorn_lr import LRSinkhorn - -# pytype: enable=import-error # kwargs-checking diff --git a/ott/core/_math_utils.py b/ott/core/_math_utils.py deleted file mode 100644 index 7a269a77b..000000000 --- a/ott/core/_math_utils.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import Optional, Union - -import jax.experimental.sparse as jesp -import jax.numpy as jnp - -__all__ = ["safe_log", "kl", "js"] - -Sparse_t = Union[jesp.CSR, jesp.CSC, jesp.COO, jesp.BCOO] - - -def safe_log(x: jnp.ndarray, *, eps: Optional[float] = None) -> jnp.ndarray: - if eps is None: - eps = jnp.finfo(x.dtype).tiny - return jnp.where(x > 0., jnp.log(x), jnp.log(eps)) - - -def kl(p: jnp.ndarray, q: jnp.ndarray) -> float: - """Kullback-Leilbler divergence.""" - return jnp.vdot(p, (safe_log(p) - safe_log(q))) - - -def js(p: jnp.ndarray, q: jnp.ndarray, *, c: float = 0.5) -> float: - """Jensen-Shannon divergence.""" - return c * (kl(p, q) + kl(q, p)) - - -def sparse_scale(c: float, mat: Sparse_t) -> Sparse_t: - """Scale a sparse matrix by a constant.""" - if isinstance(mat, jesp.BCOO): - # most feature complete, defer to original impl. - return c * mat - (data, *children), aux_data = mat.tree_flatten() - return type(mat).tree_unflatten(aux_data, [c * data] + children) diff --git a/ott/core/momentum.py b/ott/core/momentum.py deleted file mode 100644 index 380df4fc1..000000000 --- a/ott/core/momentum.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright 2022 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Functions related to momemtum.""" - -from typing import TYPE_CHECKING - -import jax -import jax.numpy as jnp - -from ott.core import dataclasses - -if TYPE_CHECKING: - from ott.core import sinkhorn - - -@dataclasses.register_pytree_node -class Momentum: - """Momentum for Sinkhorn updates, either constant or adaptive.""" - - start: int = 0 - error_threshold: float = jnp.inf - value: float = 1.0 - inner_iterations: int = 1 - - def weight(self, state: "sinkhorn.SinkhornState", iteration: int) -> float: - """Compute momentum term if needed, using previously seen errors.""" - if self.start == 0: - return self.value - idx = self.start // self.inner_iterations - - weight = jax.lax.cond( - jnp.logical_and( - iteration >= self.start, - state.errors[idx - 1, -1] < self.error_threshold - ), lambda state: self.lehmann(state), lambda state: self.value, state - ) - return weight - - def lehmann(self, state: "sinkhorn.SinkhornState") -> float: - """Momentum formula :cite:`lehmann:21`, eq. 5.""" - idx = self.start // self.inner_iterations - error_ratio = jnp.minimum( - state.errors[idx - 1, -1] / state.errors[idx - 2, -1], 0.99 - ) - power = 1.0 / self.inner_iterations - return 2.0 / (1.0 + jnp.sqrt(1.0 - error_ratio ** power)) - - def __call__( - self, - weight: float, - value: jnp.ndarray, - new_value: jnp.ndarray, - lse_mode: bool = True - ) -> jnp.ndarray: - if lse_mode: - value = jnp.where(jnp.isfinite(value), value, 0.0) - return (1.0 - weight) * value + weight * new_value - else: - value = jnp.where(value > 0.0, value, 1.0) - return value ** (1.0 - weight) * new_value ** weight diff --git a/ott/core/problems.py b/ott/core/problems.py deleted file mode 100644 index e60b4deb1..000000000 --- a/ott/core/problems.py +++ /dev/null @@ -1,93 +0,0 @@ -# Copyright 2022 The OTT Authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Utility to make a problem class from arrays.""" -from typing import Any, Optional, Union - -import jax.numpy as jnp -import numpy as np - -from ott.core import linear_problems, quad_problems -from ott.geometry import geometry, pointcloud - - -def make( - *args: Union[jnp.ndarray, geometry.Geometry, linear_problems.LinearProblem, - quad_problems.QuadraticProblem], - a: Optional[jnp.ndarray] = None, - b: Optional[jnp.ndarray] = None, - tau_a: float = 1.0, - tau_b: float = 1.0, - objective: Optional[str] = None, - gw_unbalanced_correction: Optional[bool] = True, - fused_penalty: Optional[float] = None, - scale_cost: Optional[Union[bool, float, str]] = False, - **kwargs: Any, -): - """Make a problem from arrays, assuming PointCloud geometries.""" - if isinstance(args[0], (jnp.ndarray, np.ndarray)): - x = args[0] - y = args[1] if len(args) > 1 else args[0] - if ((objective == 'linear') or - (objective is None and x.shape[1] == y.shape[1])): # noqa: E129 - geom_xy = pointcloud.PointCloud(x, y, **kwargs) - return linear_problems.LinearProblem( - geom_xy, a=a, b=b, tau_a=tau_a, tau_b=tau_b - ) - elif ((objective == 'quadratic') or - (objective is None and x.shape[1] != y.shape[1])): - geom_xx = pointcloud.PointCloud(x, x, **kwargs) - geom_yy = pointcloud.PointCloud(y, y, **kwargs) - return quad_problems.QuadraticProblem( - geom_xx=geom_xx, - geom_yy=geom_yy, - geom_xy=None, - scale_cost=scale_cost, - a=a, - b=b, - tau_a=tau_a, - tau_b=tau_b, - gw_unbalanced_correction=gw_unbalanced_correction - ) - elif objective == 'fused': - geom_xx = pointcloud.PointCloud(x, x, **kwargs) - geom_yy = pointcloud.PointCloud(y, y, **kwargs) - geom_xy = pointcloud.PointCloud(x, y, **kwargs) - return quad_problems.QuadraticProblem( - geom_xx=geom_xx, - geom_yy=geom_yy, - geom_xy=geom_xy, - fused_penalty=fused_penalty, - scale_cost=scale_cost, - a=a, - b=b, - tau_a=tau_a, - tau_b=tau_b, - gw_unbalanced_correction=gw_unbalanced_correction - ) - else: - raise ValueError(f'Unknown transport problem `{objective}`') - elif isinstance(args[0], geometry.Geometry): - if len(args) == 1: - return linear_problems.LinearProblem( - *args, a=a, b=b, tau_a=tau_a, tau_b=tau_b - ) - return quad_problems.QuadraticProblem( - *args, a=a, b=b, tau_a=tau_a, tau_b=tau_b, scale_cost=scale_cost - ) - elif isinstance( - args[0], (linear_problems.LinearProblem, quad_problems.QuadraticProblem) - ): - return args[0] - else: - raise ValueError('Cannot instantiate a transport problem.') diff --git a/ott/geometry/__init__.py b/ott/geometry/__init__.py index 38b4ada53..7309ab155 100644 --- a/ott/geometry/__init__.py +++ b/ott/geometry/__init__.py @@ -12,9 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. """OTT ground geometries: Classes and cost functions to instantiate them.""" -from . import costs, low_rank, ops -from .epsilon_scheduler import Epsilon -from .geometry import Geometry -from .graph import Graph -from .grid import Grid -from .pointcloud import PointCloud +from . import costs, epsilon_scheduler, geometry, graph, grid, pointcloud diff --git a/ott/geometry/costs.py b/ott/geometry/costs.py index fae64a596..1af441d73 100644 --- a/ott/geometry/costs.py +++ b/ott/geometry/costs.py @@ -17,13 +17,12 @@ import abc import functools import math -from typing import Any, Callable, Optional, Union +from typing import Any, Callable, Optional, Tuple, Union import jax import jax.numpy as jnp -from ott.core import fixed_point_loop -from ott.geometry import matrix_square_root +from ott.math import fixed_point_loop, matrix_square_root @jax.tree_util.register_pytree_node_class @@ -32,9 +31,11 @@ class CostFn(abc.ABC): Cost functions evaluate a function on a pair of inputs. For convenience, that function is split into two norms -- evaluated on each input separately -- - followed by a pairwise cost that involves both inputs, as in + followed by a pairwise cost that involves both inputs, as in: - c(x,y) = norm(x) + norm(y) + pairwise(x,y) + .. math:: + + c(x,y) = norm(x) + norm(y) + pairwise(x,y) If the norm function is not implemented, that value is handled as a 0. """ @@ -46,6 +47,7 @@ class CostFn(abc.ABC): def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> float: pass + @abc.abstractmethod def barycenter(self, weights: jnp.ndarray, xs: jnp.ndarray) -> float: pass @@ -340,6 +342,7 @@ def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> float: log_m_pi += -0.5 * ldet_c_ab # If all logdet signs are 1, output value, nan otherwise. + # TODO(michalk8): use lax.cond return jnp.where( sldet_c == 1 and sldet_c_ab == 1 and sldet_ab == 1 and sldet_t_ab == 1, 2 * sig2 * mass_x * mass_y - 2 * (sig2 + gam) * jnp.exp(log_m_pi), @@ -355,7 +358,8 @@ def tree_unflatten(cls, aux_data, children): return cls(aux_data[0], aux_data[1], aux_data[2], **aux_data[3]) -def x_to_means_and_covs(x: jnp.ndarray, dimension: jnp.ndarray) -> jnp.ndarray: +def x_to_means_and_covs(x: jnp.ndarray, + dimension: int) -> Tuple[jnp.ndarray, jnp.ndarray]: """Extract means and covariance matrices of Gaussians from raveled vector. Args: @@ -367,7 +371,7 @@ def x_to_means_and_covs(x: jnp.ndarray, dimension: jnp.ndarray) -> jnp.ndarray: covariances: [num_gaussians, dimension] array that holds the covariances. """ x = jnp.atleast_2d(x) - means = x[:, 0:dimension] + means = x[:, :dimension] covariances = jnp.reshape( x[:, dimension:dimension + dimension ** 2], (-1, dimension, dimension) ) diff --git a/ott/geometry/geometry.py b/ott/geometry/geometry.py index dc59740b7..ff3d5000c 100644 --- a/ott/geometry/geometry.py +++ b/ott/geometry/geometry.py @@ -25,7 +25,8 @@ import jax.scipy as jsp from typing_extensions import Literal -from ott.geometry import epsilon_scheduler, ops +from ott.geometry import epsilon_scheduler +from ott.math import utils @jax.tree_util.register_pytree_node_class @@ -410,12 +411,12 @@ def _softmax( if vec is not None: if axis == 0: vec = vec.reshape((-1, 1)) - lse_output = ops.logsumexp( + lse_output = utils.logsumexp( self._center(f, g) / eps, b=vec, axis=axis, return_sign=True ) return eps * lse_output[0], lse_output[1] else: - lse_output = ops.logsumexp( + lse_output = utils.logsumexp( self._center(f, g) / eps, axis=axis, return_sign=False ) return eps * lse_output, jnp.array([1.0]) @@ -639,7 +640,7 @@ def to_LRCGeometry( Useful when this geometry is used in the linear term of fused GW. Returns: - The low-rank geometry. + Low-rank geometry. """ from ott.geometry import low_rank diff --git a/ott/geometry/graph.py b/ott/geometry/graph.py index b7e6e9024..8cd6c05a7 100644 --- a/ott/geometry/graph.py +++ b/ott/geometry/graph.py @@ -5,9 +5,9 @@ import jax.numpy as jnp from typing_extensions import Literal -from ott.core import _math_utils as mu -from ott.core import decomposition, fixed_point_loop from ott.geometry import geometry +from ott.math import decomposition, fixed_point_loop +from ott.math import utils as mu __all__ = ["Graph"] diff --git a/ott/geometry/grid.py b/ott/geometry/grid.py index 25e9809eb..88bd67f0a 100644 --- a/ott/geometry/grid.py +++ b/ott/geometry/grid.py @@ -21,19 +21,20 @@ import jax.numpy as jnp import numpy as np -from ott.geometry import costs, geometry, ops, pointcloud +from ott.geometry import costs, geometry, pointcloud +from ott.math import utils @jax.tree_util.register_pytree_node_class class Grid(geometry.Geometry): - r"""Class describing the geometry of points taken in a cartestian product. + r"""Class describing the geometry of points taken in a Cartesian product. This class implements a geometry in which probability measures are supported on a :math:`d`-dimensional cartesian grid, a cartesian product of :math:`d` lists of values, each list being itself of size :math:`n_i`. The transportation cost between points in the grid is assumed to be separable, - namely a sum of coordinate-wise cost functions, as in + namely a sum of coordinate-wise cost functions, as in: .. math:: @@ -52,7 +53,7 @@ class Grid(geometry.Geometry): Args: x : list of arrays of varying sizes, describing the locations of the grid. - Locations are provided as a list of jnp.ndarrays, that is :math:`d` + Locations are provided as a list of arrays, that is :math:`d` vectors of (possibly varying) size :math:`n_i`. The resulting grid is the Cartesian product of these vectors. grid_size: tuple of integers describing grid sizes, namely @@ -201,14 +202,13 @@ def _apply_lse_kernel_one_dimension(self, dimension, f, g, eps, vec=None): if vec is not None: vec = jnp.transpose(vec, indices) - softmax_res, softmax_sgn = ops.logsumexp( + softmax_res, softmax_sgn = utils.logsumexp( centered_cost, b=vec, axis=1, return_sign=True ) return eps * jnp.transpose(softmax_res, indices), jnp.transpose(softmax_sgn, indices) - else: - softmax_res = ops.logsumexp(centered_cost, axis=1) - return eps * jnp.transpose(softmax_res, indices), None + softmax_res = utils.logsumexp(centered_cost, axis=1) + return eps * jnp.transpose(softmax_res, indices), None def _apply_cost_to_vec( self, vec: jnp.ndarray, axis: int = 0, fn=None diff --git a/ott/geometry/pointcloud.py b/ott/geometry/pointcloud.py index 9b518090e..9e153f267 100644 --- a/ott/geometry/pointcloud.py +++ b/ott/geometry/pointcloud.py @@ -21,19 +21,20 @@ import jax.numpy as jnp from typing_extensions import Literal -from ott.geometry import costs, geometry, low_rank, ops +from ott.geometry import costs, geometry, low_rank +from ott.math import utils @jax.tree_util.register_pytree_node_class class PointCloud(geometry.Geometry): - """Defines geometry for 2 point clouds (possibly 1 vs itself) using CostFn. + """Defines geometry for 2 point clouds (possibly 1 vs itself). Creates a geometry, specifying a cost function passed as CostFn type object. - When the number of points is large, setting the `online` flag to `True` - implies that cost and kernel matrices used to update potentials or scalings + When the number of points is large, setting the ``batch_size`` flag implies + that cost and kernel matrices used to update potentials or scalings will be recomputed on the fly, rather than stored in memory. More precisely, - when setting `online`, the cost function will be partially cached by storing - norm values for each point in both point clouds, but the pairwise cost + when setting ``batch_size``, the cost function will be partially cached by + storing norm values for each point in both point clouds, but the pairwise cost function evaluations won't be. The sum of norms + the pairwise cost term is raised to `power`. @@ -737,7 +738,7 @@ def _apply_lse_kernel_xy( x, y, norm_x, norm_y, f, g, eps, vec, cost_fn, cost_pow, scale_cost ): c = _cost(x, y, norm_x, norm_y, cost_fn, cost_pow, scale_cost) - return ops.logsumexp((f + g - c) / eps, b=vec, return_sign=True, axis=-1) + return utils.logsumexp((f + g - c) / eps, b=vec, return_sign=True, axis=-1) def _transport_from_potentials_xy( diff --git a/ott/initializers/__init__.py b/ott/initializers/__init__.py new file mode 100644 index 000000000..15cfac006 --- /dev/null +++ b/ott/initializers/__init__.py @@ -0,0 +1 @@ +from . import linear, nn, quadratic diff --git a/ott/initializers/linear/__init__.py b/ott/initializers/linear/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ott/core/initializers.py b/ott/initializers/linear/initializers.py similarity index 52% rename from ott/core/initializers.py rename to ott/initializers/linear/initializers.py index 700aa47a5..2b10ed6e2 100644 --- a/ott/core/initializers.py +++ b/ott/initializers/linear/initializers.py @@ -12,44 +12,40 @@ # See the License for the specific language governing permissions and # limitations under the License. """Sinkhorn initializers.""" -import functools -from abc import ABC, abstractmethod +import abc from typing import Any, Dict, Optional, Sequence, Tuple import jax import jax.numpy as jnp -import optax -from flax import linen as nn -from flax.training import train_state -from ott.core import linear_problems, sinkhorn -from ott.geometry import geometry, pointcloud +from ott.geometry import pointcloud +from ott.problems.linear import linear_problem __all__ = [ - "DefaultInitializer", "GaussianInitializer", "SortingInitializer", - "MetaInitializer" + "SinkhornInitializer", "DefaultInitializer", "GaussianInitializer", + "SortingInitializer" ] @jax.tree_util.register_pytree_node_class -class SinkhornInitializer(ABC): +class SinkhornInitializer(abc.ABC): """Base class for Sinkhorn initializers.""" - @abstractmethod + @abc.abstractmethod def init_dual_a( - self, ot_prob: linear_problems.LinearProblem, lse_mode: bool + self, ot_prob: linear_problem.LinearProblem, lse_mode: bool ) -> jnp.ndarray: """Initialization for Sinkhorn potential/scaling f_u.""" - @abstractmethod + @abc.abstractmethod def init_dual_b( - self, ot_prob: linear_problems.LinearProblem, lse_mode: bool + self, ot_prob: linear_problem.LinearProblem, lse_mode: bool ) -> jnp.ndarray: """Initialization for Sinkhorn potential/scaling g_v.""" def __call__( self, - ot_prob: linear_problems.LinearProblem, + ot_prob: linear_problem.LinearProblem, a: Optional[jnp.ndarray], b: Optional[jnp.ndarray], lse_mode: bool, @@ -101,7 +97,7 @@ class DefaultInitializer(SinkhornInitializer): """Default initialization of Sinkhorn dual potentials/primal scalings.""" def init_dual_a( - self, ot_prob: linear_problems.LinearProblem, lse_mode: bool + self, ot_prob: linear_problem.LinearProblem, lse_mode: bool ) -> jnp.ndarray: """Initialize Sinkhorn potential/scaling f_u. @@ -117,7 +113,7 @@ def init_dual_a( return init_dual_a def init_dual_b( - self, ot_prob: linear_problems.LinearProblem, lse_mode: bool + self, ot_prob: linear_problem.LinearProblem, lse_mode: bool ) -> jnp.ndarray: """Initialize Sinkhorn potential/scaling g_v. @@ -145,7 +141,7 @@ class GaussianInitializer(DefaultInitializer): def init_dual_a( self, - ot_prob: linear_problems.LinearProblem, + ot_prob: linear_problem.LinearProblem, lse_mode: bool, ) -> jnp.ndarray: """Gaussian initialization function. @@ -247,7 +243,7 @@ def cond_fn(state: Tuple[jnp.ndarray, float, int]) -> bool: def init_dual_a( self, - ot_prob: linear_problems.LinearProblem, + ot_prob: linear_problem.LinearProblem, lse_mode: bool, init_f: Optional[jnp.ndarray] = None, ) -> jnp.ndarray: @@ -285,212 +281,6 @@ def init_dual_a( return f_u -@jax.tree_util.register_pytree_node_class -class MetaInitializer(DefaultInitializer): - """Meta OT Initializer with a fixed geometry :cite:`amos:22`. - - This initializer consists of a predictive model that outputs the - :math:`f` duals to solve the entropy-regularized OT problem given - input probability weights ``a`` and ``b``, and a given (assumed to be - fixed) geometry ``geom``. - The model's parameters are learned using a training set of OT - instances (multiple pairs of probability weights), that assume the - **same** geometry ``geom`` is used throughout, both for training and - evaluation. The meta model defaults to the MLP in - :class:`~ott.core.initializers.MetaMLP` and, with batched problem - instances passed into :meth:`update`. - - **Sample training usage.** The following code shows a simple - example of using ``update`` to train the model, where - ``a`` and ``b`` are the weights of the measures and - ``geom`` is the fixed geometry. - - .. code-block:: python - - meta_initializer = init_lib.MetaInitializer(geom=geom) - while training(): - a, b = sample_batch() - loss, init_f, meta_initializer.state = meta_initializer.update( - meta_initializer.state, a=a, b=b) - - Args: - geom: The fixed geometry of the problem instances. - meta_model: The model to predict the potential :math:`f` from the measures. - opt: The optimizer to update the parameters. - rng: The PRNG key to use for initializing the model. - state: The training state of the model to start from. - """ - - def __init__( - self, - geom: geometry.Geometry, - meta_model: Optional[nn.Module] = None, - opt: optax.GradientTransformation = optax.adam(learning_rate=1e-3), - rng: jax.random.PRNGKeyArray = jax.random.PRNGKey(0), - state: Optional[train_state.TrainState] = None - ): - self.geom = geom - self.dtype = geom.x.dtype - self.opt = opt - self.rng = rng - - na, nb = geom.shape - self.meta_model = MetaMLP( - potential_size=na - ) if meta_model is None else meta_model - - if state is None: - # Initialize the model's training state. - a_placeholder = jnp.zeros(na, dtype=self.dtype) - b_placeholder = jnp.zeros(nb, dtype=self.dtype) - params = self.meta_model.init(rng, a_placeholder, b_placeholder)['params'] - self.state = train_state.TrainState.create( - apply_fn=self.meta_model.apply, params=params, tx=opt - ) - else: - self.state = state - - self.update_impl = self._get_update_fn() - - def update( - self, state: train_state.TrainState, a: jnp.ndarray, b: jnp.ndarray - ) -> Tuple[jnp.ndarray, jnp.ndarray, train_state.TrainState]: - r"""Update the meta model with the dual objective. - - The goal is for the model to match the optimal duals, i.e., - :math:`\hat f_\theta \approx f^\star`. - This can be done by training the predictions of :math:`\hat f_\theta` - to optimize the dual objective, which :math:`f^\star` also optimizes for. - The overall learning setup can thus be written as: - - .. math:: - \min_\theta\; {\mathbb E}_{(\alpha,\beta)\sim{\mathcal{D}}}\; - J(\hat f_\theta(a, b); \alpha, \beta), - - where :math:`a,b` are the probabilities of the measures :math:`\alpha,\beta`, - :math:`\mathcal{D}` is a meta distribution of optimal transport problems, - - .. math:: - -J(f; \alpha, \beta, c) := \langle f, a\rangle + \langle g, b \rangle - - \varepsilon\left\langle \exp\{f/\varepsilon\}, K\exp\{g/\varepsilon\}\right\rangle - - is the entropic dual objective, - and :math:`K_{i,j} := -C_{i,j}/\varepsilon` is the *Gibbs kernel*. - - Args: - state: Optimizer state of the meta model. - a: Probabilites of the :math:`\alpha` measure's atoms. - b: Probabilites of the :math:`\beta` measure's atoms. - - Returns: - The training loss, :math:`f`, and updated state. - """ - return self.update_impl(state, a, b) - - def init_dual_a( - self, ot_prob: linear_problems.LinearProblem, lse_mode: bool - ) -> jnp.ndarray: - # Detect if the problem is batched. - assert ot_prob.a.ndim in (1, 2) and ot_prob.b.ndim in (1, 2) - vmap_a_val = 0 if ot_prob.a.ndim == 2 else None - vmap_b_val = 0 if ot_prob.b.ndim == 2 else None - - if vmap_a_val is not None or vmap_b_val is not None: - compute_f_maybe_batch = jax.vmap( - self._compute_f, in_axes=(vmap_a_val, vmap_b_val, None) - ) - else: - compute_f_maybe_batch = self._compute_f - - init_f = compute_f_maybe_batch(ot_prob.a, ot_prob.b, self.state.params) - f_u = init_f if lse_mode else ot_prob.geom.scaling_from_potential(init_f) - return f_u - - def _get_update_fn(self): - """Return the implementation (and jitted) update function.""" - - def dual_obj_loss_single(params, a, b): - f_pred = self._compute_f(a, b, params) - g_pred = self.geom.update_potential( - f_pred, jnp.zeros_like(b), jnp.log(b), 0, axis=0 - ) - g_pred = jnp.where(jnp.isfinite(g_pred), g_pred, 0.) - - ot_prob = linear_problems.LinearProblem(geom=self.geom, a=a, b=b) - dual_obj = sinkhorn.ent_reg_cost(f_pred, g_pred, ot_prob, lse_mode=True) - loss = -dual_obj - return loss, f_pred - - def loss_batch(params, a, b): - loss_fn = functools.partial(dual_obj_loss_single, params=params) - loss, f_pred = jax.vmap(loss_fn)(a=a, b=b) - return jnp.mean(loss), f_pred - - @jax.jit - def update(state, a, b): - a = jnp.atleast_2d(a) - b = jnp.atleast_2d(b) - grad_fn = jax.value_and_grad(loss_batch, has_aux=True) - (loss, init_f), grads = grad_fn(state.params, a, b) - return loss, init_f, state.apply_gradients(grads=grads) - - return update - - def _compute_f(self, a, b, params): - r"""Predict the optimal :math:`f` potential. - - Args: - a: Probabilites of the :math:`\alpha` measure's atoms. - b: Probabilites of the :math:`\beta` measure's atoms. - params: The parameters of the Meta model. - - Returns: - The :math:`f` potential. - """ - return self.meta_model.apply({'params': params}, a, b) - - def tree_flatten(self) -> Tuple[Sequence[Any], Dict[str, Any]]: - return [self.geom, self.meta_model, self.opt], { - 'rng': self.rng, - 'state': self.state - } - - -class MetaMLP(nn.Module): - r"""A Meta MLP potential for :class:`~ott.core.initializers.MetaInitializer`. - - This provides an MLP :math:`\hat f_\theta(a, b)` that maps from the probabilities - of the measures to the optimal dual potentials :math:`f`. - - Args: - potential_size: The dimensionality of :math:`f`. - num_hidden_units: The number of hidden units in each layer. - num_hidden_layers: The number of hidden layers. - """ - - potential_size: int - num_hidden_units: int = 512 - num_hidden_layers: int = 3 - - @nn.compact - def __call__(self, a: jnp.ndarray, b: jnp.ndarray) -> jnp.ndarray: - r"""Make a prediction. - - Args: - a: Probabilites of the :math:`\alpha` measure's atoms. - b: Probabilites of the :math:`\beta` measure's atoms. - - Returns: - The :math:`f` potential. - """ - dtype = a.dtype - z = jnp.concatenate((a, b)) - for _ in range(self.num_hidden_layers): - z = nn.relu(nn.Dense(self.num_hidden_units, dtype=dtype)(z)) - f = nn.Dense(self.potential_size, dtype=dtype)(z) - return f - - def _vectorized_update( f: jnp.ndarray, modified_cost: jnp.ndarray ) -> jnp.ndarray: diff --git a/ott/core/initializers_lr.py b/ott/initializers/linear/initializers_lr.py similarity index 94% rename from ott/core/initializers_lr.py rename to ott/initializers/linear/initializers_lr.py index 641f1b284..2999bf850 100644 --- a/ott/core/initializers_lr.py +++ b/ott/initializers/linear/initializers_lr.py @@ -1,5 +1,5 @@ +import abc import functools -from abc import ABC, abstractmethod from typing import ( TYPE_CHECKING, Any, @@ -17,8 +17,9 @@ from jax import numpy as jnp from typing_extensions import Literal -from ott.core import _math_utils as mu from ott.geometry import geometry, low_rank, pointcloud +from ott.math import fixed_point_loop +from ott.math import utils as mu __all__ = [ "RandomInitializer", "Rank2Initializer", "KMeansInitializer", @@ -26,23 +27,21 @@ ] if TYPE_CHECKING: - from ott.core import ( - gromov_wasserstein, - linear_problems, - quad_problems, - sinkhorn, - sinkhorn_lr, - ) - Problem_t = Union[linear_problems.LinearProblem, - quad_problems.QuadraticProblem] + from ott.problems.linear import linear_problem + from ott.problems.quadratic import quadratic_problem + from ott.solvers.linear import sinkhorn, sinkhorn_lr + from ott.solvers.quadratic import gromov_wasserstein + + Problem_t = Union[linear_problem.LinearProblem, + quadratic_problem.QuadraticProblem] else: Problem_t = "Union[linear_problems.LinearProblem, " \ "quad_problems.QuadraticProblem]" @jax.tree_util.register_pytree_node_class -class LRInitializer(ABC): - """Low-rank initializer for linear/quadratic problems. +class LRInitializer(abc.ABC): + """Base class for low-rank initializers. Args: rank: Rank of the factorization. @@ -53,7 +52,7 @@ def __init__(self, rank: int, **kwargs: Any): self._rank = rank self._kwargs = kwargs - @abstractmethod + @abc.abstractmethod def init_q( self, ot_prob: Problem_t, @@ -67,13 +66,14 @@ def init_q( Args: ot_prob: OT problem. key: Random key for seeding. + init_g: Initial value for :math:`g` factor. kwargs: Additional keyword arguments. Returns: Array of shape ``[n, rank]``. """ - @abstractmethod + @abc.abstractmethod def init_r( self, ot_prob: Problem_t, @@ -87,13 +87,14 @@ def init_r( Args: ot_prob: Linear OT problem. key: Random key for seeding. + init_g: Initial value for :math:`g` factor. kwargs: Additional keyword arguments. Returns: Array of shape ``[m, rank]``. """ - @abstractmethod + @abc.abstractmethod def init_g( self, ot_prob: Problem_t, @@ -130,7 +131,7 @@ def from_solver( Returns: The low-rank initializer. """ - from ott.core import gromov_wasserstein + from ott.solvers.quadratic import gromov_wasserstein if isinstance(solver, gromov_wasserstein.GromovWasserstein): assert solver.is_low_rank, "GW solver is not low-rank." @@ -382,7 +383,9 @@ def _compute_factor( which: Literal["q", "r"], **kwargs: Any, ) -> jnp.ndarray: - from ott.core import linear_problems, quad_problems, sinkhorn + from ott.problems import linear as linear_problems + from ott.problems import quadratic as quad_problems + from ott.solvers.linear import sinkhorn from ott.tools import k_means del kwargs @@ -512,7 +515,9 @@ def _compute_factor( which: Literal["q", "r"], **kwargs: Any, ) -> jnp.ndarray: - from ott.core import fixed_point_loop, linear_problems, sinkhorn + from ott.problems import linear as linear_problems + from ott.problems import quadratic as quad_problems + from ott.solvers.linear import sinkhorn def init_fn() -> GeneralizedKMeansInitializer.State: n = geom.shape[0] @@ -611,7 +616,6 @@ def body_fn( ) del kwargs - from ott.core import quad_problems if isinstance(ot_prob, quad_problems.QuadraticProblem): geom = ot_prob.geom_xx if which == "q" else ot_prob.geom_yy diff --git a/ott/initializers/nn/__init__.py b/ott/initializers/nn/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ott/initializers/nn/initializers.py b/ott/initializers/nn/initializers.py new file mode 100644 index 000000000..05f173b42 --- /dev/null +++ b/ott/initializers/nn/initializers.py @@ -0,0 +1,188 @@ +import functools +from typing import Any, Dict, Optional, Sequence, Tuple + +import jax +import jax.numpy as jnp +import optax +from flax import linen as nn +from flax.training import train_state + +from ott.geometry import geometry +from ott.initializers.linear import DefaultInitializer +from ott.initializers.nn import layers +from ott.problems.linear import linear_problem +from ott.solvers.linear import sinkhorn + +# TODO(michalk8): add Charlotte's initializer? +__all__ = ["MetaInitializer"] + + +@jax.tree_util.register_pytree_node_class +class MetaInitializer(DefaultInitializer): + """Meta OT Initializer with a fixed geometry :cite:`amos:22`. + + This initializer consists of a predictive model that outputs the + :math:`f` duals to solve the entropy-regularized OT problem given + input probability weights ``a`` and ``b``, and a given (assumed to be + fixed) geometry ``geom``. + The model's parameters are learned using a training set of OT + instances (multiple pairs of probability weights), that assume the + **same** geometry ``geom`` is used throughout, both for training and + evaluation. The meta model defaults to the MLP in + :class:`~ott.core.initializers.MetaMLP` and, with batched problem + instances passed into :meth:`update`. + + **Sample training usage.** The following code shows a simple + example of using ``update`` to train the model, where + ``a`` and ``b`` are the weights of the measures and + ``geom`` is the fixed geometry. + + .. code-block:: python + + meta_initializer = init_lib.MetaInitializer(geom=geom) + while training(): + a, b = sample_batch() + loss, init_f, meta_initializer.state = meta_initializer.update( + meta_initializer.state, a=a, b=b) + + Args: + geom: The fixed geometry of the problem instances. + meta_model: The model to predict the potential :math:`f` from the measures. + opt: The optimizer to update the parameters. + rng: The PRNG key to use for initializing the model. + state: The training state of the model to start from. + """ + + def __init__( + self, + geom: geometry.Geometry, + meta_model: Optional[nn.Module] = None, + opt: optax.GradientTransformation = optax.adam(learning_rate=1e-3), + rng: jax.random.PRNGKeyArray = jax.random.PRNGKey(0), + state: Optional[train_state.TrainState] = None + ): + self.geom = geom + self.dtype = geom.x.dtype + self.opt = opt + self.rng = rng + + na, nb = geom.shape + self.meta_model = layers.MetaMLP( + potential_size=na + ) if meta_model is None else meta_model + + if state is None: + # Initialize the model's training state. + a_placeholder = jnp.zeros(na, dtype=self.dtype) + b_placeholder = jnp.zeros(nb, dtype=self.dtype) + params = self.meta_model.init(rng, a_placeholder, b_placeholder)['params'] + self.state = train_state.TrainState.create( + apply_fn=self.meta_model.apply, params=params, tx=opt + ) + else: + self.state = state + + self.update_impl = self._get_update_fn() + + def update( + self, state: train_state.TrainState, a: jnp.ndarray, b: jnp.ndarray + ) -> Tuple[jnp.ndarray, jnp.ndarray, train_state.TrainState]: + r"""Update the meta model with the dual objective. + + The goal is for the model to match the optimal duals, i.e., + :math:`\hat f_\theta \approx f^\star`. + This can be done by training the predictions of :math:`\hat f_\theta` + to optimize the dual objective, which :math:`f^\star` also optimizes for. + The overall learning setup can thus be written as: + + .. math:: + \min_\theta\; {\mathbb E}_{(\alpha,\beta)\sim{\mathcal{D}}}\; + J(\hat f_\theta(a, b); \alpha, \beta), + + where :math:`a,b` are the probabilities of the measures :math:`\alpha,\beta`, + :math:`\mathcal{D}` is a meta distribution of optimal transport problems, + + .. math:: + -J(f; \alpha, \beta, c) := \langle f, a\rangle + \langle g, b \rangle - + \varepsilon\left\langle \exp\{f/\varepsilon\}, K\exp\{g/\varepsilon\}\right\rangle + + is the entropic dual objective, + and :math:`K_{i,j} := -C_{i,j}/\varepsilon` is the *Gibbs kernel*. + + Args: + state: Optimizer state of the meta model. + a: Probabilites of the :math:`\alpha` measure's atoms. + b: Probabilites of the :math:`\beta` measure's atoms. + + Returns: + The training loss, :math:`f`, and updated state. + """ + return self.update_impl(state, a, b) + + def init_dual_a( + self, ot_prob: linear_problem.LinearProblem, lse_mode: bool + ) -> jnp.ndarray: + # Detect if the problem is batched. + assert ot_prob.a.ndim in (1, 2) and ot_prob.b.ndim in (1, 2) + vmap_a_val = 0 if ot_prob.a.ndim == 2 else None + vmap_b_val = 0 if ot_prob.b.ndim == 2 else None + + if vmap_a_val is not None or vmap_b_val is not None: + compute_f_maybe_batch = jax.vmap( + self._compute_f, in_axes=(vmap_a_val, vmap_b_val, None) + ) + else: + compute_f_maybe_batch = self._compute_f + + init_f = compute_f_maybe_batch(ot_prob.a, ot_prob.b, self.state.params) + f_u = init_f if lse_mode else ot_prob.geom.scaling_from_potential(init_f) + return f_u + + def _get_update_fn(self): + """Return the implementation (and jitted) update function.""" + + def dual_obj_loss_single(params, a, b): + f_pred = self._compute_f(a, b, params) + g_pred = self.geom.update_potential( + f_pred, jnp.zeros_like(b), jnp.log(b), 0, axis=0 + ) + g_pred = jnp.where(jnp.isfinite(g_pred), g_pred, 0.) + + ot_prob = linear_problem.LinearProblem(geom=self.geom, a=a, b=b) + dual_obj = sinkhorn.ent_reg_cost(f_pred, g_pred, ot_prob, lse_mode=True) + loss = -dual_obj + return loss, f_pred + + def loss_batch(params, a, b): + loss_fn = functools.partial(dual_obj_loss_single, params=params) + loss, f_pred = jax.vmap(loss_fn)(a=a, b=b) + return jnp.mean(loss), f_pred + + @jax.jit + def update(state, a, b): + a = jnp.atleast_2d(a) + b = jnp.atleast_2d(b) + grad_fn = jax.value_and_grad(loss_batch, has_aux=True) + (loss, init_f), grads = grad_fn(state.params, a, b) + return loss, init_f, state.apply_gradients(grads=grads) + + return update + + def _compute_f(self, a, b, params): + r"""Predict the optimal :math:`f` potential. + + Args: + a: Probabilites of the :math:`\alpha` measure's atoms. + b: Probabilites of the :math:`\beta` measure's atoms. + params: The parameters of the Meta model. + + Returns: + The :math:`f` potential. + """ + return self.meta_model.apply({'params': params}, a, b) + + def tree_flatten(self) -> Tuple[Sequence[Any], Dict[str, Any]]: + return [self.geom, self.meta_model, self.opt], { + 'rng': self.rng, + 'state': self.state + } diff --git a/ott/initializers/nn/layers.py b/ott/initializers/nn/layers.py new file mode 100644 index 000000000..4f6b25382 --- /dev/null +++ b/ott/initializers/nn/layers.py @@ -0,0 +1,39 @@ +from flax import linen as nn +from jax import numpy as jnp + +__all__ = ["MetaMLP"] + + +class MetaMLP(nn.Module): + r"""A Meta MLP potential for :class:`~ott.core.initializers.MetaInitializer`. + + This provides an MLP :math:`\hat f_\theta(a, b)` that maps from the + probabilities of the measures to the optimal dual potentials :math:`f`. + + Args: + potential_size: The dimensionality of :math:`f`. + num_hidden_units: The number of hidden units in each layer. + num_hidden_layers: The number of hidden layers. + """ + + potential_size: int + num_hidden_units: int = 512 + num_hidden_layers: int = 3 + + @nn.compact + def __call__(self, a: jnp.ndarray, b: jnp.ndarray) -> jnp.ndarray: + r"""Make a prediction. + + Args: + a: Probabilities of the :math:`\alpha` measure's atoms. + b: Probabilities of the :math:`\beta` measure's atoms. + + Returns: + The :math:`f` potential. + """ + dtype = a.dtype + z = jnp.concatenate((a, b)) + for _ in range(self.num_hidden_layers): + z = nn.relu(nn.Dense(self.num_hidden_units, dtype=dtype)(z)) + f = nn.Dense(self.potential_size, dtype=dtype)(z) + return f diff --git a/ott/initializers/quadratic/__init__.py b/ott/initializers/quadratic/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ott/core/quad_initializers.py b/ott/initializers/quadratic/initializers.py similarity index 82% rename from ott/core/quad_initializers.py rename to ott/initializers/quadratic/initializers.py index 6bd66698d..bab5a8f54 100644 --- a/ott/core/quad_initializers.py +++ b/ott/initializers/quadratic/initializers.py @@ -1,19 +1,21 @@ -from abc import ABC, abstractmethod +import abc from typing import TYPE_CHECKING, Any, Dict, Sequence, Tuple import jax -from ott.core import linear_problems, sinkhorn_lr from ott.geometry import geometry +from ott.problems.linear import linear_problem +from ott.solvers.linear import sinkhorn_lr if TYPE_CHECKING: - from ott.core import initializers_lr, quad_problems + from ott.initializers.linear import initializers_lr + from ott.problems.quadratic import quadratic_problem __all__ = ["QuadraticInitializer", "LRQuadraticInitializer"] @jax.tree_util.register_pytree_node_class -class BaseQuadraticInitializer(ABC): +class BaseQuadraticInitializer(abc.ABC): """Base class for quadratic initializers. Args: @@ -24,8 +26,8 @@ def __init__(self, **kwargs: Any): self._kwargs = kwargs def __call__( - self, quad_prob: 'quad_problems.QuadraticProblem', **kwargs: Any - ) -> linear_problems.LinearProblem: + self, quad_prob: 'quadratic_problem.QuadraticProblem', **kwargs: Any + ) -> linear_problem.LinearProblem: """Compute the initial linearization of a quadratic problem. Args: @@ -39,7 +41,7 @@ def __call__( geom = self._create_geometry(quad_prob, **kwargs) assert geom.shape == (n, m), f"Expected geometry of shape `{n, m}`, " \ f"found `{geom.shape}`." - return linear_problems.LinearProblem( + return linear_problem.LinearProblem( geom, a=quad_prob.a, b=quad_prob.b, @@ -47,9 +49,9 @@ def __call__( tau_b=quad_prob.tau_b ) - @abstractmethod + @abc.abstractmethod def _create_geometry( - self, quad_prob: 'quad_problems.QuadraticProblem', **kwargs: Any + self, quad_prob: 'quadratic_problem.QuadraticProblem', **kwargs: Any ) -> geometry.Geometry: """Compute initial geometry for linearization. @@ -58,7 +60,7 @@ def _create_geometry( kwargs: Additional keyword arguments. Returns: - The initial geometry used to initialize a linear problem. + Geometry used to initialize the linearized problem. """ def tree_flatten(self) -> Tuple[Sequence[Any], Dict[str, Any]]: @@ -104,9 +106,9 @@ class QuadraticInitializer(BaseQuadraticInitializer): """ def _create_geometry( - self, quad_prob: 'quad_problems.QuadraticProblem', *, epsilon: float, + self, quad_prob: 'quadratic_problem.QuadraticProblem', *, epsilon: float, **kwargs: Any - ) -> linear_problems.LinearProblem: + ) -> geometry.Geometry: """Compute initial geometry for linearization. Args: @@ -115,9 +117,9 @@ def _create_geometry( kwargs: Additional keyword arguments, unused. Returns: - The initial geometry used to initialize a linear problem. + The initial geometry used to initialize the linearized problem. """ - from ott.core.quad_problems import apply_cost, update_epsilon_unbalanced + from ott.problems.quadratic import quadratic_problem del kwargs unbalanced_correction = 0.0 @@ -131,14 +133,18 @@ def _create_geometry( if not quad_prob.is_balanced: transport_mass = marginal_1.sum() # Initialises epsilon for Unbalanced GW according to Sejourne et al (2021) - epsilon = update_epsilon_unbalanced(epsilon, transport_mass) + epsilon = quadratic_problem.update_epsilon_unbalanced( + epsilon, transport_mass + ) unbalanced_correction = quad_prob.cost_unbalanced_correction( tmp, marginal_1, marginal_2, epsilon ) h1, h2 = quad_prob.quad_loss - tmp = apply_cost(quad_prob.geom_xx, tmp, axis=1, fn=h1) - tmp = apply_cost(quad_prob.geom_yy, tmp.T, axis=1, fn=h2).T + tmp = quadratic_problem.apply_cost(quad_prob.geom_xx, tmp, axis=1, fn=h1) + tmp = quadratic_problem.apply_cost( + quad_prob.geom_yy, tmp.T, axis=1, fn=h2 + ).T cost_matrix = (marginal_cost.cost_matrix - tmp + unbalanced_correction) cost_matrix += quad_prob.fused_penalty * quad_prob._fused_cost_matrix @@ -158,7 +164,7 @@ def __init__(self, lr_linear_initializer: 'initializers_lr.LRInitializer'): self._linear_lr_initializer = lr_linear_initializer def _create_geometry( - self, quad_prob: 'quad_problems.QuadraticProblem', **kwargs: Any + self, quad_prob: 'quadratic_problem.QuadraticProblem', **kwargs: Any ) -> geometry.Geometry: """Compute initial geometry for linearization. diff --git a/ott/math/__init__.py b/ott/math/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ott/core/decomposition.py b/ott/math/decomposition.py similarity index 100% rename from ott/core/decomposition.py rename to ott/math/decomposition.py diff --git a/ott/core/fixed_point_loop.py b/ott/math/fixed_point_loop.py similarity index 99% rename from ott/core/fixed_point_loop.py rename to ott/math/fixed_point_loop.py index 3482bb031..fbfb04789 100644 --- a/ott/core/fixed_point_loop.py +++ b/ott/math/fixed_point_loop.py @@ -17,9 +17,9 @@ from typing import Any, Callable import jax +import jax.numpy as jnp import numpy as np from jax import dtypes -from jax import numpy as jnp def fixpoint_iter( diff --git a/ott/core/implicit_differentiation.py b/ott/math/implicit_differentiation.py similarity index 98% rename from ott/core/implicit_differentiation.py rename to ott/math/implicit_differentiation.py index 8b68f10ef..44fc946e5 100644 --- a/ott/core/implicit_differentiation.py +++ b/ott/math/implicit_differentiation.py @@ -18,7 +18,9 @@ import jax import jax.numpy as jnp -from ott.core import dataclasses, linear_problems, unbalanced_functions +from ott.math import unbalanced_functions +from ott.problems import linear as linear_problems +from ott.utils import dataclasses @dataclasses.register_pytree_node diff --git a/ott/geometry/matrix_square_root.py b/ott/math/matrix_square_root.py similarity index 99% rename from ott/geometry/matrix_square_root.py rename to ott/math/matrix_square_root.py index 22eac70b9..9cca55695 100644 --- a/ott/geometry/matrix_square_root.py +++ b/ott/math/matrix_square_root.py @@ -22,7 +22,7 @@ import jax.numpy as jnp import numpy as np -from ott.core import fixed_point_loop +from ott.math import fixed_point_loop @functools.partial(jax.custom_vjp, nondiff_argnums=(1, 2, 3, 4, 5)) diff --git a/ott/core/potentials.py b/ott/math/potentials.py similarity index 100% rename from ott/core/potentials.py rename to ott/math/potentials.py diff --git a/ott/core/unbalanced_functions.py b/ott/math/unbalanced_functions.py similarity index 100% rename from ott/core/unbalanced_functions.py rename to ott/math/unbalanced_functions.py diff --git a/ott/geometry/ops.py b/ott/math/utils.py similarity index 62% rename from ott/geometry/ops.py rename to ott/math/utils.py index 1bf3248a3..6b1398aad 100644 --- a/ott/geometry/ops.py +++ b/ott/math/utils.py @@ -1,23 +1,42 @@ -# Copyright 2022 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Low level functions used within the scope of Geometric processing.""" - import functools +from typing import Optional, Union import jax +import jax.experimental.sparse as jesp import jax.numpy as jnp +__all__ = [ + "safe_log", "kl", "js", "sparse_scale", "logsumexp", + "barycentric_projection" +] + +Sparse_t = Union[jesp.CSR, jesp.CSC, jesp.COO, jesp.BCOO] + + +def safe_log(x: jnp.ndarray, *, eps: Optional[float] = None) -> jnp.ndarray: + if eps is None: + eps = jnp.finfo(x.dtype).tiny + return jnp.where(x > 0., jnp.log(x), jnp.log(eps)) + + +def kl(p: jnp.ndarray, q: jnp.ndarray) -> float: + """Kullback-Leilbler divergence.""" + return jnp.vdot(p, (safe_log(p) - safe_log(q))) + + +def js(p: jnp.ndarray, q: jnp.ndarray, *, c: float = 0.5) -> float: + """Jensen-Shannon divergence.""" + return c * (kl(p, q) + kl(q, p)) + + +def sparse_scale(c: float, mat: Sparse_t) -> Sparse_t: + """Scale a sparse matrix by a constant.""" + if isinstance(mat, jesp.BCOO): + # most feature complete, defer to original impl. + return c * mat + (data, *children), aux_data = mat.tree_flatten() + return type(mat).tree_unflatten(aux_data, [c * data] + children) + @functools.partial(jax.custom_jvp, nondiff_argnums=(1, 2, 4)) def logsumexp(mat, axis=None, keepdims=False, b=None, return_sign=False): @@ -69,3 +88,10 @@ def logsumexp_jvp(axis, keepdims, return_sign, primals, tangents): return (lse, sign), (sign * res, jnp.zeros_like(sign)) else: return lse, res + + +@functools.partial(jax.vmap, in_axes=[0, 0, None]) +def barycentric_projection( + matrix: jnp.ndarray, y: jnp.ndarray, cost_fn +) -> jnp.ndarray: + return jax.vmap(cost_fn.barycenter, in_axes=[0, None])(matrix, y) diff --git a/ott/problems/__init__.py b/ott/problems/__init__.py new file mode 100644 index 000000000..87714fd6b --- /dev/null +++ b/ott/problems/__init__.py @@ -0,0 +1 @@ +from . import linear, quadratic diff --git a/ott/problems/linear/__init__.py b/ott/problems/linear/__init__.py new file mode 100644 index 000000000..1ab3d4ccb --- /dev/null +++ b/ott/problems/linear/__init__.py @@ -0,0 +1,2 @@ +from .barycenter_problem import BarycenterProblem +from .linear_problem import LinearProblem diff --git a/ott/problems/linear/barycenter_problem.py b/ott/problems/linear/barycenter_problem.py new file mode 100644 index 000000000..a34ee728f --- /dev/null +++ b/ott/problems/linear/barycenter_problem.py @@ -0,0 +1,182 @@ +# Copyright 2022 Apple Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Classes defining OT problem(s) (objective function + utilities).""" +from typing import Any, Dict, Optional, Sequence, Tuple + +import jax +import jax.numpy as jnp + +from ott.geometry import costs +from ott.utils import segment + +__all__ = ["BarycenterProblem"] + + +@jax.tree_util.register_pytree_node_class +class BarycenterProblem: + """Wasserstein barycenter problem :cite:`cuturi:14`. + + Args: + y: Array of shape ``[num_total_points, ndim]`` merging the points of all + measures. Alternatively, already segmented array of shape + ``[num_measures, max_measure_size, ndim]`` can be passed. + See also :func:`~ott.core.segment.segment_point_cloud`. + b: Array of shape ``[num_total_points,]`` containing the weights of all + the points within the measures that define the barycenter problem. + Similarly as ``y``, segmented array of weights of shape + ``[num_measures, max_measure_size]`` can be passed. + If ``y`` is already pre-segmented, this array must be always specified. + weights: Array of shape ``[num_measures,]`` containing the weights of the + measures. + cost_fn: Cost function used. If `None`, + use :class:`~ott.geometry.costs.SqEuclidean` cost. + epsilon: Epsilon regularization used to solve reg-OT problems. + debiased: **Currently not implemented.** + Whether the problem is debiased, in the sense that + the regularized transportation cost of barycenter to itself will + be considered when computing gradient. Note that if the debiased option + is used, the barycenter size in + :meth:`~ott.core.continuous_barycenter.WassersteinBarycenter.init_state` + needs to be smaller than the maximum measure size for parallelization to + operate efficiently. + kwargs: Keyword arguments :func:`~ott.core.segment.segment_point_cloud`. + Only used when ``y`` is not already segmented. When passing + ``segment_ids``, 2 arguments must be specified for jitting to work: + + - ``num_segments`` - the total number of measures. + - ``max_measure_size`` - maximum of support sizes of these measures. + """ + + def __init__( + self, + y: jnp.ndarray, + b: Optional[jnp.ndarray] = None, + weights: Optional[jnp.ndarray] = None, + cost_fn: Optional[costs.CostFn] = None, + epsilon: Optional[float] = None, + debiased: bool = False, + **kwargs: Any, + ): + self._y = y + if y.ndim == 3 and b is None: + raise ValueError("Specify weights if `y` is already segmented.") + self._b = b + self._weights = weights + self.cost_fn = costs.SqEuclidean() if cost_fn is None else cost_fn + self.epsilon = epsilon + self.debiased = debiased + self._kwargs = kwargs + + if self._is_segmented: + # (num_measures, max_measure_size, ndim) + # (num_measures, max_measure_size) + assert self._y.shape[:2] == self._b.shape + else: + # (num_total_points, ndim) + # (num_total_points,) + assert self._b is None or self._y.shape[0] == self._b.shape[0] + + @property + def segmented_y_b(self) -> Tuple[jnp.ndarray, jnp.ndarray]: + """Tuple of arrays containing the segmented measures and weights. + + Additional segment may be added when the problem is debiased. + + - Segmented measures of shape ``[num_measures, max_measure_size, ndim]``. + - Segmented weights of shape ``[num_measures, max_measure_size]``. + """ + if self._is_segmented: + y, b = self._y, self._b + else: + y, b = segment.segment_point_cloud( + x=self._y, + a=self._b, + padding_vector=self.cost_fn.padder(self.ndim), + **self._kwargs + ) + + if self.debiased: + return self._add_slice_for_debiased(y, b) + return y, b + + def _add_slice_for_debiased( + self, y: jnp.ndarray, b: jnp.ndarray + ) -> Tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]: + y, b = self._y, self._b + _, n, ndim = y.shape # (num_measures, max_measure_size, ndim) + # yapf: disable + y = jnp.concatenate((y, jnp.zeros((1, n, ndim))), axis=0) + b = jnp.concatenate((b, jnp.zeros((1, n))), axis=0) + # yapf: enable + return y, b + + @property + def flattened_y(self) -> jnp.ndarray: + """Array of shape ``[num_measures * (N_1 + N_2 + ...), ndim]``.""" + if self._is_segmented: + return self._y.reshape((-1, self._y.shape[-1])) + return self._y + + @property + def flattened_b(self) -> Optional[jnp.ndarray]: + """Array of shape ``[num_measures * (N_1 + N_2 + ...),]``.""" + return None if self._b is None else self._b.ravel() + + @property + def num_measures(self) -> int: + """Number of measures.""" + return self.segmented_y_b[0].shape[0] + + @property + def max_measure_size(self) -> int: + """Maximum number of points across all measures.""" + return self.segmented_y_b[0].shape[1] + + @property + def ndim(self) -> int: + """Number of dimensions of ``y``.""" + return self._y.shape[-1] + + @property + def weights(self) -> jnp.ndarray: + """Barycenter weights of shape ``[num_measures,]`` that sum to 1.""" + if self._weights is None: + weights = jnp.ones((self.num_measures,)) / self.num_measures + else: + # Check that the number of measures coincides with the weights' size. + assert self._weights.shape[0] == self.num_measures + # By default, we assume that weights sum to 1, and enforce this if needed. + weights = self._weights / jnp.sum(self._weights) + if self.debiased: + weights = jnp.concatenate((weights, jnp.array([-0.5]))) + return weights + + @property + def _is_segmented(self) -> bool: + return self._y.ndim == 3 + + def tree_flatten(self) -> Tuple[Sequence[Any], Dict[str, Any]]: + return ([self._y, self._b, self._weights], { + 'cost_fn': self.cost_fn, + 'epsilon': self.epsilon, + 'debiased': self.debiased, + **self._kwargs, + }) + + @classmethod + def tree_unflatten( + cls, aux_data: Dict[str, Any], children: Sequence[Any] + ) -> "BarycenterProblem": + y, b, weights = children + return cls(y=y, b=b, weights=weights, **aux_data) diff --git a/ott/core/linear_problems.py b/ott/problems/linear/linear_problem.py similarity index 99% rename from ott/core/linear_problems.py rename to ott/problems/linear/linear_problem.py index 4357267ff..97c54d4e8 100644 --- a/ott/core/linear_problems.py +++ b/ott/problems/linear/linear_problem.py @@ -20,6 +20,8 @@ from ott.geometry import geometry +__all__ = ["LinearProblem"] + MarginalFunc = Callable[[jnp.ndarray, jnp.ndarray], jnp.ndarray] TransportAppFunc = Callable[[jnp.ndarray, jnp.ndarray, jnp.ndarray, int], jnp.ndarray] diff --git a/ott/problems/quadratic/__init__.py b/ott/problems/quadratic/__init__.py new file mode 100644 index 000000000..48c54ef97 --- /dev/null +++ b/ott/problems/quadratic/__init__.py @@ -0,0 +1,3 @@ +from . import quadratic_costs +from .barycenter_problem import GWBarycenterProblem +from .quadratic_problem import QuadraticProblem diff --git a/ott/core/bar_problems.py b/ott/problems/quadratic/barycenter_problem.py similarity index 57% rename from ott/core/bar_problems.py rename to ott/problems/quadratic/barycenter_problem.py index 68c29f325..089e645de 100644 --- a/ott/core/bar_problems.py +++ b/ott/problems/quadratic/barycenter_problem.py @@ -1,191 +1,21 @@ -# Copyright 2022 Apple Inc -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Classes defining OT problem(s) (objective function + utilities).""" import functools from typing import Any, Dict, Optional, Sequence, Tuple, Union import jax -import jax.numpy as jnp +from jax import numpy as jnp from typing_extensions import Literal -from ott.core import quad_problems, segment from ott.geometry import costs, geometry, pointcloud +from ott.math import utils as mu +from ott.problems.linear import barycenter_problem +from ott.problems.quadratic import quadratic_costs, quadratic_problem +from ott.utils import segment -__all__ = ["BarycenterProblem", "GWBarycenterProblem", "barycentric_projection"] +__all__ = ["GWBarycenterProblem"] @jax.tree_util.register_pytree_node_class -class BarycenterProblem: - """Wasserstein barycenter problem :cite:`cuturi:14`. - - Args: - y: Array of shape ``[num_total_points, ndim]`` merging the points of all - measures. Alternatively, already segmented array of shape - ``[num_measures, max_measure_size, ndim]`` can be passed. - See also :func:`~ott.core.segment.segment_point_cloud`. - b: Array of shape ``[num_total_points,]`` containing the weights of all - the points within the measures that define the barycenter problem. - Similarly as ``y``, segmented array of weights of shape - ``[num_measures, max_measure_size]`` can be passed. - If ``y`` is already pre-segmented, this array must be always specified. - weights: Array of shape ``[num_measures,]`` containing the weights of the - measures. - cost_fn: Cost function used. If `None`, - use :class:`~ott.geometry.costs.SqEuclidean` cost. - epsilon: Epsilon regularization used to solve reg-OT problems. - debiased: **Currently not implemented.** - Whether the problem is debiased, in the sense that - the regularized transportation cost of barycenter to itself will - be considered when computing gradient. Note that if the debiased option - is used, the barycenter size in - :meth:`~ott.core.continuous_barycenter.WassersteinBarycenter.init_state` - needs to be smaller than the maximum measure size for parallelization to - operate efficiently. - kwargs: Keyword arguments :func:`~ott.core.segment.segment_point_cloud`. - Only used when ``y`` is not already segmented. When passing - ``segment_ids``, 2 arguments must be specified for jitting to work: - - - ``num_segments`` - the total number of measures. - - ``max_measure_size`` - maximum of support sizes of these measures. - """ - - def __init__( - self, - y: jnp.ndarray, - b: Optional[jnp.ndarray] = None, - weights: Optional[jnp.ndarray] = None, - cost_fn: Optional[costs.CostFn] = None, - epsilon: Optional[float] = None, - debiased: bool = False, - **kwargs: Any, - ): - self._y = y - if y.ndim == 3 and b is None: - raise ValueError("Specify weights if `y` is already segmented.") - self._b = b - self._weights = weights - self.cost_fn = costs.SqEuclidean() if cost_fn is None else cost_fn - self.epsilon = epsilon - self.debiased = debiased - self._kwargs = kwargs - - if self._is_segmented: - # (num_measures, max_measure_size, ndim) - # (num_measures, max_measure_size) - assert self._y.shape[:2] == self._b.shape - else: - # (num_total_points, ndim) - # (num_total_points,) - assert self._b is None or self._y.shape[0] == self._b.shape[0] - - @property - def segmented_y_b(self) -> Tuple[jnp.ndarray, jnp.ndarray]: - """Tuple of arrays containing the segmented measures and weights. - - Additional segment may be added when the problem is debiased. - - - Segmented measures of shape ``[num_measures, max_measure_size, ndim]``. - - Segmented weights of shape ``[num_measures, max_measure_size]``. - """ - if self._is_segmented: - y, b = self._y, self._b - else: - y, b = segment.segment_point_cloud( - x=self._y, - a=self._b, - padding_vector=self.cost_fn.padder(self.ndim), - **self._kwargs - ) - - if self.debiased: - return self._add_slice_for_debiased(y, b) - return y, b - - def _add_slice_for_debiased( - self, y: jnp.ndarray, b: jnp.ndarray - ) -> Tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]: - y, b = self._y, self._b - _, n, ndim = y.shape # (num_measures, max_measure_size, ndim) - # yapf: disable - y = jnp.concatenate((y, jnp.zeros((1, n, ndim))), axis=0) - b = jnp.concatenate((b, jnp.zeros((1, n))), axis=0) - # yapf: enable - return y, b - - @property - def flattened_y(self) -> jnp.ndarray: - """Array of shape ``[num_measures * (N_1 + N_2 + ...), ndim]``.""" - if self._is_segmented: - return self._y.reshape((-1, self._y.shape[-1])) - return self._y - - @property - def flattened_b(self) -> Optional[jnp.ndarray]: - """Array of shape ``[num_measures * (N_1 + N_2 + ...),]``.""" - return None if self._b is None else self._b.ravel() - - @property - def num_measures(self) -> int: - """Number of measures.""" - return self.segmented_y_b[0].shape[0] - - @property - def max_measure_size(self) -> int: - """Maximum number of points across all measures.""" - return self.segmented_y_b[0].shape[1] - - @property - def ndim(self) -> int: - """Number of dimensions of ``y``.""" - return self._y.shape[-1] - - @property - def weights(self) -> jnp.ndarray: - """Barycenter weights of shape ``[num_measures,]`` that sum to 1.""" - if self._weights is None: - weights = jnp.ones((self.num_measures,)) / self.num_measures - else: - # Check that the number of measures coincides with the weights' size. - assert self._weights.shape[0] == self.num_measures - # By default, we assume that weights sum to 1, and enforce this if needed. - weights = self._weights / jnp.sum(self._weights) - if self.debiased: - weights = jnp.concatenate((weights, jnp.array([-0.5]))) - return weights - - @property - def _is_segmented(self) -> bool: - return self._y.ndim == 3 - - def tree_flatten(self) -> Tuple[Sequence[Any], Dict[str, Any]]: - return ([self._y, self._b, self._weights], { - 'cost_fn': self.cost_fn, - 'epsilon': self.epsilon, - 'debiased': self.debiased, - **self._kwargs, - }) - - @classmethod - def tree_unflatten( - cls, aux_data: Dict[str, Any], children: Sequence[Any] - ) -> "BarycenterProblem": - y, b, weights = children - return cls(y=y, b=b, weights=weights, **aux_data) - - -@jax.tree_util.register_pytree_node_class -class GWBarycenterProblem(BarycenterProblem): +class GWBarycenterProblem(barycenter_problem.BarycenterProblem): """(Fused) Gromov-Wasserstein barycenter problem :cite:`peyre:16,vayer:19`. Args: @@ -195,7 +25,7 @@ class GWBarycenterProblem(BarycenterProblem): See also :func:`~ott.core.segment.segment_point_cloud`. b: Array of shape ``[num_total_points,]`` containing the weights of all the points within the measures that define the barycenter problem. - Similarly as ``y``, segmented array of weights of shape + Similarly, as ``y``, segmented array of weights of shape ``[num_measures, max_measure_size]`` can be passed. If ``y`` is already pre-segmented, this array must be passed. weights: Array of shape ``[num_measures,]`` containing the weights of the @@ -277,7 +107,7 @@ def project( y: jnp.ndarray, b: jnp.ndarray, transport: jnp.ndarray, - fn: Optional[quad_problems.Loss], + fn: Optional[quadratic_costs.Loss], ) -> jnp.ndarray: geom = self._create_y_geometry(y, mask=b > 0.) fn, lin = (None, True) if fn is None else (fn.func, fn.is_linear) @@ -332,7 +162,8 @@ def update_features(self, transports: jnp.ndarray, if self._loss_name == "sqeucl": cost = costs.SqEuclidean() return jnp.sum( - weights * barycentric_projection(transports, y_fused, cost), axis=0 + weights * mu.barycentric_projection(transports, y_fused, cost), + axis=0 ) raise NotImplementedError(self._loss_name) @@ -395,7 +226,7 @@ def _create_problem( y: jnp.ndarray, b: jnp.ndarray, f: Optional[jnp.ndarray] = None - ) -> quad_problems.QuadraticProblem: + ) -> quadratic_problem.QuadraticProblem: # TODO(michalk8): in the future, mask in the problem for convenience? bary_mask = state.a > 0. y_mask = b > 0. @@ -411,7 +242,7 @@ def _create_problem( else: geom_xy = None - return quad_problems.QuadraticProblem( + return quadratic_problem.QuadraticProblem( geom_xx=geom_xx, geom_yy=geom_yy, geom_xy=geom_xy, @@ -448,16 +279,16 @@ def ndim_fused(self) -> Optional[int]: return self._y_fused.shape[-1] if self.is_fused else None @property - def gw_loss(self) -> quad_problems.GWLoss: + def gw_loss(self) -> quadratic_costs.GWLoss: """Gromov-Wasserstein loss.""" # TODO(michalk8): custom losses would require inverting some fns; # `https://jax.readthedocs.io/en/latest/notebooks/ some fns; # Writing_custom_interpreters_in_Jax.html#your-first-interpreter-invert` # might be useful if self._loss_name == 'sqeucl': - return quad_problems.make_square_loss() + return quadratic_costs.make_square_loss() if self._loss_name == 'kl': - return quad_problems.make_kl_loss() + return quadratic_costs.make_kl_loss() raise NotImplementedError( f"Loss `{self._loss_name}` is not yet implemented." ) @@ -481,10 +312,3 @@ def tree_unflatten( return cls( y=y, b=b, weights=weights, costs=costs, y_fused=y_fused, **aux_data ) - - -@functools.partial(jax.vmap, in_axes=[0, 0, None]) -def barycentric_projection( - matrix: jnp.ndarray, y: jnp.ndarray, cost_fn -) -> jnp.ndarray: - return jax.vmap(cost_fn.barycenter, in_axes=[0, None])(matrix, y) diff --git a/ott/problems/quadratic/quadratic_costs.py b/ott/problems/quadratic/quadratic_costs.py new file mode 100644 index 000000000..8de1b398d --- /dev/null +++ b/ott/problems/quadratic/quadratic_costs.py @@ -0,0 +1,34 @@ +from typing import Callable, NamedTuple + +import jax +import jax.numpy as jnp + +__all__ = ["make_square_loss", "make_kl_loss"] + + +class Loss(NamedTuple): + func: Callable[[jnp.ndarray], jnp.ndarray] + is_linear: bool + + +class GWLoss(NamedTuple): + f1: Loss + f2: Loss + h1: Loss + h2: Loss + + +def make_square_loss() -> GWLoss: + f1 = Loss(lambda x: x ** 2, is_linear=False) + f2 = Loss(lambda y: y ** 2, is_linear=False) + h1 = Loss(lambda x: x, is_linear=True) + h2 = Loss(lambda y: 2.0 * y, is_linear=True) + return GWLoss(f1, f2, h1, h2) + + +def make_kl_loss(clipping_value: float = 1e-8) -> GWLoss: + f1 = Loss(lambda x: -jax.scipy.special.entr(x) - x, is_linear=False) + f2 = Loss(lambda y: y, is_linear=True) + h1 = Loss(lambda x: x, is_linear=True) + h2 = Loss(lambda y: jnp.log(jnp.clip(y, clipping_value)), is_linear=False) + return GWLoss(f1, f2, h1, h2) diff --git a/ott/core/quad_problems.py b/ott/problems/quadratic/quadratic_problem.py similarity index 90% rename from ott/core/quad_problems.py rename to ott/problems/quadratic/quadratic_problem.py index e4a649ecf..03d0bdbb7 100644 --- a/ott/core/quad_problems.py +++ b/ott/problems/quadratic/quadratic_problem.py @@ -13,62 +13,19 @@ # limitations under the License. """Classes defining OT problem(s) (objective function + utilities).""" -from typing import Callable, NamedTuple, Optional, Tuple, Union +from typing import Optional, Tuple, Union import jax import jax.numpy as jnp +from typing_extensions import Literal -# Because Protocol is not available in Python < 3.8 -from typing_extensions import Literal, Protocol - -from ott.core import linear_problems, sinkhorn_lr from ott.geometry import epsilon_scheduler, geometry, low_rank, pointcloud +from ott.problems.linear import linear_problem +from ott.problems.quadratic import quadratic_costs +from ott.solvers.linear import sinkhorn_lr +from ott.typing import Transport - -class Transport(Protocol): - """Interface for the solution of a transport problem. - - Classes implementing those function do not have to inherit from it, the - class can however be used in type hints to support duck typing. - """ - - @property - def matrix(self) -> jnp.ndarray: - ... - - def apply(self, inputs: jnp.ndarray, axis: int) -> jnp.ndarray: - ... - - def marginal(self, axis: int = 0) -> jnp.ndarray: - ... - - -class Loss(NamedTuple): - func: Callable[[jnp.ndarray], jnp.ndarray] - is_linear: bool - - -class GWLoss(NamedTuple): - f1: Loss - f2: Loss - h1: Loss - h2: Loss - - -def make_square_loss() -> GWLoss: - f1 = Loss(lambda x: x ** 2, is_linear=False) - f2 = Loss(lambda y: y ** 2, is_linear=False) - h1 = Loss(lambda x: x, is_linear=True) - h2 = Loss(lambda y: 2.0 * y, is_linear=True) - return GWLoss(f1, f2, h1, h2) - - -def make_kl_loss(clipping_value: float = 1e-8) -> GWLoss: - f1 = Loss(lambda x: -jax.scipy.special.entr(x) - x, is_linear=False) - f2 = Loss(lambda y: y, is_linear=True) - h1 = Loss(lambda x: x, is_linear=True) - h2 = Loss(lambda y: jnp.log(jnp.clip(y, clipping_value)), is_linear=False) - return GWLoss(f1, f2, h1, h2) +__all__ = ["QuadraticProblem"] @jax.tree_util.register_pytree_node_class @@ -142,7 +99,7 @@ def __init__( scale_cost: Optional[Union[bool, float, str]] = False, a: Optional[jnp.ndarray] = None, b: Optional[jnp.ndarray] = None, - loss: Union[Literal['sqeucl', 'kl'], GWLoss] = 'sqeucl', + loss: Union[Literal['sqeucl', 'kl'], quadratic_costs.GWLoss] = 'sqeucl', tau_a: Optional[float] = 1.0, tau_b: Optional[float] = 1.0, gw_unbalanced_correction: bool = True, @@ -167,9 +124,9 @@ def __init__( self._loss_name = loss if self._loss_name == 'sqeucl': - self.loss = make_square_loss() + self.loss = quadratic_costs.make_square_loss() elif loss == 'kl': - self.loss = make_kl_loss() + self.loss = quadratic_costs.make_kl_loss() else: self.loss = loss @@ -312,7 +269,7 @@ def update_linearization( transport: Transport, epsilon: Optional[Union[epsilon_scheduler.Epsilon, float]] = None, old_transport_mass: float = 1.0 - ) -> linear_problems.LinearProblem: + ) -> linear_problem.LinearProblem: """Update linearization of GW problem by updating cost matrix. If the problem is balanced (``tau_a = 1.0 and tau_b = 1.0``), the equation @@ -363,15 +320,15 @@ def update_linearization( cost_matrix += self.fused_penalty * self._fused_cost_matrix * rescale_factor geom = geometry.Geometry(cost_matrix=cost_matrix, epsilon=epsilon) - return linear_problems.LinearProblem( + return linear_problem.LinearProblem( geom, self.a, self.b, tau_a=self.tau_a, tau_b=self.tau_b ) def update_lr_linearization( self, lr_sink: sinkhorn_lr.LRSinkhornOutput - ) -> linear_problems.LinearProblem: + ) -> linear_problem.LinearProblem: """Update a Quad problem linearization using a LR Sinkhorn.""" - return linear_problems.LinearProblem( + return linear_problem.LinearProblem( self.update_lr_geom(lr_sink), self.a, self.b, @@ -491,12 +448,12 @@ def is_low_rank(self) -> bool: ) @property - def linear_loss(self) -> Tuple[Loss, Loss]: + def linear_loss(self) -> Tuple[quadratic_costs.Loss, quadratic_costs.Loss]: """Linear part of the Gromov-Wasserstein loss.""" return self.loss.f1, self.loss.f2 @property - def quad_loss(self) -> Tuple[Loss, Loss]: + def quad_loss(self) -> Tuple[quadratic_costs.Loss, quadratic_costs.Loss]: """Quadratic part of the Gromov-Wasserstein loss.""" return self.loss.h1, self.loss.h2 @@ -524,7 +481,9 @@ def tree_unflatten(cls, aux_data, children): return cls(*geoms, a=a, b=b, **aux_data) -def update_epsilon_unbalanced(epsilon, transport_mass): +def update_epsilon_unbalanced( + epsilon: Union[float, epsilon_scheduler.Epsilon], transport_mass: float +) -> epsilon_scheduler.Epsilon: updated_epsilon = epsilon_scheduler.Epsilon.make(epsilon) updated_epsilon._scale_epsilon = ( updated_epsilon._scale_epsilon * transport_mass @@ -533,6 +492,7 @@ def update_epsilon_unbalanced(epsilon, transport_mass): def apply_cost( - geom: geometry.Geometry, arr: jnp.ndarray, *, axis: int, fn: Loss + geom: geometry.Geometry, arr: jnp.ndarray, *, axis: int, + fn: quadratic_costs.Loss ) -> jnp.ndarray: return geom.apply_cost(arr, axis=axis, fn=fn.func, is_linear=fn.is_linear) diff --git a/ott/solvers/__init__.py b/ott/solvers/__init__.py new file mode 100644 index 000000000..15cfac006 --- /dev/null +++ b/ott/solvers/__init__.py @@ -0,0 +1 @@ +from . import linear, nn, quadratic diff --git a/ott/solvers/linear/__init__.py b/ott/solvers/linear/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ott/core/anderson.py b/ott/solvers/linear/acceleration.py similarity index 62% rename from ott/core/anderson.py rename to ott/solvers/linear/acceleration.py index e20b6d0ec..8b638abc9 100644 --- a/ott/core/anderson.py +++ b/ott/solvers/linear/acceleration.py @@ -1,25 +1,14 @@ -# Copyright 2022 Google LLC. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""Tools for Anderson acceleration.""" -from typing import Any +from typing import TYPE_CHECKING import jax -import jax.numpy as jnp +from jax import numpy as jnp -from ott.core import dataclasses +if TYPE_CHECKING: + from ott.solvers.linear import sinkhorn -SinkhornState = Any +from ott.utils import dataclasses + +__all__ = ["AndersonAcceleration", "Momentum"] @dataclasses.register_pytree_node @@ -30,7 +19,7 @@ class AndersonAcceleration: refresh_every: int = 1 # Recompute interpolation periodically. ridge_identity: float = 1e-2 # Ridge used in the linear system. - def extrapolation(self, xs, fxs): + def extrapolation(self, xs: jnp.ndarray, fxs: jnp.ndarray) -> jnp.ndarray: """Compute Anderson extrapolation from past observations.""" # Remove -inf values to instantiate quadratic problem. All others # remain since they might be caused by a valid issue. @@ -53,7 +42,9 @@ def extrapolation(self, xs, fxs): combination = jnp.sum(fxs * weights[None, :], axis=1) return jnp.where(jnp.isfinite(combination), combination, -jnp.inf) - def update(self, state: SinkhornState, iteration: int, pb, lse_mode: bool): + def update( + self, state: 'sinkhorn.SinkhornState', iteration: int, pb, lse_mode: bool + ) -> 'sinkhorn.SinkhornState': """Anderson acceleration update. When using Anderson acceleration, first update the dual variable f_u with @@ -101,12 +92,63 @@ def update(self, state: SinkhornState, iteration: int, pb, lse_mode: bool): ) return state.set(fu=fu, old_fus=old_fus) - def init_maps(self, pb, state): + def init_maps( + self, pb, state: 'sinkhorn.SinkhornState' + ) -> 'sinkhorn.SinkhornState': """Initialize log matrix used in Anderson acceleration with nan values.""" fus = jnp.ones((pb.geom.shape[0], self.memory)) * jnp.nan return state.set(old_fus=fus, old_mapped_fus=fus) - def update_history(self, state, pb, lse_mode: bool): + def update_history( + self, state: 'sinkhorn.SinkhornState', pb, lse_mode: bool + ) -> 'sinkhorn.SinkhornState': f = state.fu if lse_mode else pb.geom.potential_from_scaling(state.fu) mapped = jnp.concatenate((state.old_mapped_fus[:, 1:], f[:, None]), axis=1) return state.set(old_mapped_fus=mapped) + + +@dataclasses.register_pytree_node +class Momentum: + """Momentum for Sinkhorn updates, either constant or adaptive.""" + + start: int = 0 + error_threshold: float = jnp.inf + value: float = 1.0 + inner_iterations: int = 1 + + def weight(self, state: 'sinkhorn.SinkhornState', iteration: int) -> float: + """Compute momentum term if needed, using previously seen errors.""" + if self.start == 0: + return self.value + idx = self.start // self.inner_iterations + + weight = jax.lax.cond( + jnp.logical_and( + iteration >= self.start, + state.errors[idx - 1, -1] < self.error_threshold + ), lambda state: self.lehmann(state), lambda state: self.value, state + ) + return weight + + def lehmann(self, state: 'sinkhorn.SinkhornState') -> float: + """Momentum formula :cite:`lehmann:21`, eq. 5.""" + idx = self.start // self.inner_iterations + error_ratio = jnp.minimum( + state.errors[idx - 1, -1] / state.errors[idx - 2, -1], 0.99 + ) + power = 1.0 / self.inner_iterations + return 2.0 / (1.0 + jnp.sqrt(1.0 - error_ratio ** power)) + + def __call__( + self, + weight: float, + value: jnp.ndarray, + new_value: jnp.ndarray, + lse_mode: bool = True + ) -> jnp.ndarray: + if lse_mode: + value = jnp.where(jnp.isfinite(value), value, 0.0) + return (1.0 - weight) * value + weight * new_value + else: + value = jnp.where(value > 0.0, value, 1.0) + return value ** (1.0 - weight) * new_value ** weight diff --git a/ott/core/continuous_barycenter.py b/ott/solvers/linear/continuous_barycenter.py similarity index 89% rename from ott/core/continuous_barycenter.py rename to ott/solvers/linear/continuous_barycenter.py index 8a328264c..5bc105321 100644 --- a/ott/core/continuous_barycenter.py +++ b/ott/solvers/linear/continuous_barycenter.py @@ -20,8 +20,11 @@ import jax import jax.numpy as jnp -from ott.core import bar_problems, fixed_point_loop, linear_problems, was_solver from ott.geometry import pointcloud +from ott.math import fixed_point_loop +from ott.math import utils as mu +from ott.problems.linear import barycenter_problem, linear_problem +from ott.utils import was_solver __all__ = ["BarycenterState", "WassersteinBarycenter"] @@ -53,7 +56,7 @@ def set(self, **kwargs: Any) -> 'BarycenterState': return self._replace(**kwargs) def update( - self, iteration: int, bar_prob: bar_problems.BarycenterProblem, + self, iteration: int, bar_prob: barycenter_problem.BarycenterProblem, linear_ot_solver: Any, store_errors: bool ) -> 'BarycenterState': seg_y, seg_b = bar_prob.segmented_y_b @@ -63,7 +66,7 @@ def solve_linear_ot( a: Optional[jnp.ndarray], x: jnp.ndarray, b: jnp.ndarray, y: jnp.ndarray ): out = linear_ot_solver( - linear_problems.LinearProblem( + linear_problem.LinearProblem( pointcloud.PointCloud( x, y, @@ -101,7 +104,7 @@ def solve_linear_ot( # Approximation of barycenter as barycenter of barycenters per measure. - barycenters_per_measure = bar_problems.barycentric_projection( + barycenters_per_measure = mu.barycentric_projection( matrices, seg_y, bar_prob.cost_fn ) @@ -123,7 +126,7 @@ class WassersteinBarycenter(was_solver.WassersteinSolver): def __call__( self, - bar_prob: bar_problems.BarycenterProblem, + bar_prob: barycenter_problem.BarycenterProblem, bar_size: int = 100, x_init: Optional[jnp.ndarray] = None, rng: int = 0 @@ -134,7 +137,7 @@ def __call__( def init_state( self, - bar_prob: bar_problems.BarycenterProblem, + bar_prob: barycenter_problem.BarycenterProblem, bar_size: int, x_init: Optional[jnp.ndarray] = None, # TODO(michalk8): change the API to pass the PRNG key directly @@ -183,18 +186,20 @@ def init_state( ) def output_from_state(self, state: BarycenterState) -> BarycenterState: + # TODO(michalk8): create an output variable to match rest of the framework return state def iterations( solver: WassersteinBarycenter, bar_size: int, - bar_prob: bar_problems.BarycenterProblem, x_init: jnp.ndarray, rng: int + bar_prob: barycenter_problem.BarycenterProblem, x_init: jnp.ndarray, + rng: int ) -> BarycenterState: """Jittable Wasserstein barycenter outer loop.""" def cond_fn( iteration: int, constants: Tuple[WassersteinBarycenter, - bar_problems.BarycenterProblem], + barycenter_problem.BarycenterProblem], state: BarycenterState ) -> bool: solver, _ = constants @@ -202,7 +207,7 @@ def cond_fn( def body_fn( iteration, constants: Tuple[WassersteinBarycenter, - bar_problems.BarycenterProblem], + barycenter_problem.BarycenterProblem], state: BarycenterState, compute_error: bool ) -> BarycenterState: del compute_error # Always assumed True diff --git a/ott/core/discrete_barycenter.py b/ott/solvers/linear/discrete_barycenter.py similarity index 94% rename from ott/core/discrete_barycenter.py rename to ott/solvers/linear/discrete_barycenter.py index dd53806df..6373c9657 100644 --- a/ott/core/discrete_barycenter.py +++ b/ott/solvers/linear/discrete_barycenter.py @@ -13,23 +13,29 @@ # limitations under the License. # Lint as: python3 -"""Implementation of Janati+(2020) Wasserstein barycenter algorithm.""" +"""Implementation of :cite:`janati:20` Wasserstein barycenter algorithm.""" -import collections import functools -from typing import Optional, Sequence +from typing import NamedTuple, Optional, Sequence import jax import jax.numpy as jnp -from ott.core import fixed_point_loop, sinkhorn from ott.geometry import geometry +from ott.math import fixed_point_loop +from ott.solvers.linear import sinkhorn -SinkhornBarycenterOutput = collections.namedtuple( - 'Barycenter', ['f', 'g', 'histogram', 'errors'] -) +__all__ = ["SinkhornBarycenterOutput", "discrete_barycenter"] +class SinkhornBarycenterOutput(NamedTuple): + f: jnp.ndarray + g: jnp.ndarray + histogram: jnp.ndarray + errors: jnp.ndarray + + +# TODO(michalk8): refactor as a solver def discrete_barycenter( geom: geometry.Geometry, a: jnp.ndarray, diff --git a/ott/core/sinkhorn.py b/ott/solvers/linear/sinkhorn.py similarity index 94% rename from ott/core/sinkhorn.py rename to ott/solvers/linear/sinkhorn.py index aee00d797..28f41657d 100644 --- a/ott/core/sinkhorn.py +++ b/ott/solvers/linear/sinkhorn.py @@ -21,14 +21,16 @@ import numpy as np from typing_extensions import Literal -from ott.core import anderson as anderson_lib -from ott.core import fixed_point_loop -from ott.core import implicit_differentiation as implicit_lib -from ott.core import initializers as init_lib -from ott.core import linear_problems -from ott.core import momentum as momentum_lib -from ott.core import potentials, unbalanced_functions from ott.geometry import geometry +from ott.initializers.linear import initializers as init_lib +from ott.math import fixed_point_loop +from ott.math import implicit_differentiation as implicit_lib +from ott.math import potentials, unbalanced_functions +from ott.problems.linear import linear_problem + +__all__ = ["Sinkhorn", "SinkhornOutput"] + +from ott.solvers.linear import acceleration class SinkhornState(NamedTuple): @@ -45,19 +47,19 @@ def set(self, **kwargs: Any) -> 'SinkhornState': return self._replace(**kwargs) def solution_error( - self, ot_prob: linear_problems.LinearProblem, norm_error: Sequence[int], + self, ot_prob: linear_problem.LinearProblem, norm_error: Sequence[int], lse_mode: bool ) -> jnp.ndarray: return solution_error(self.fu, self.gv, ot_prob, norm_error, lse_mode) def ent_reg_cost( - self, ot_prob: linear_problems.LinearProblem, lse_mode: bool + self, ot_prob: linear_problem.LinearProblem, lse_mode: bool ) -> float: return ent_reg_cost(self.fu, self.gv, ot_prob, lse_mode) def solution_error( - f_u: jnp.ndarray, g_v: jnp.ndarray, ot_prob: linear_problems.LinearProblem, + f_u: jnp.ndarray, g_v: jnp.ndarray, ot_prob: linear_problem.LinearProblem, norm_error: Sequence[int], lse_mode: bool ) -> jnp.ndarray: """Given two potential/scaling solutions, computes deviation to optimality. @@ -142,7 +144,7 @@ def marginal_error( def ent_reg_cost( - f: jnp.ndarray, g: jnp.ndarray, ot_prob: linear_problems.LinearProblem, + f: jnp.ndarray, g: jnp.ndarray, ot_prob: linear_problem.LinearProblem, lse_mode: bool ) -> float: r"""Compute objective of Sinkhorn for OT problem given dual solutions. @@ -209,14 +211,14 @@ class SinkhornOutput(NamedTuple): g: Optional[jnp.ndarray] = None errors: Optional[jnp.ndarray] = None reg_ot_cost: Optional[float] = None - ot_prob: Optional[linear_problems.LinearProblem] = None + ot_prob: Optional[linear_problem.LinearProblem] = None def set(self, **kwargs: Any) -> 'SinkhornOutput': """Return a copy of self, with potential overwrites.""" return self._replace(**kwargs) def set_cost( - self, ot_prob: linear_problems.LinearProblem, lse_mode: bool, + self, ot_prob: linear_problem.LinearProblem, lse_mode: bool, use_danskin: bool ) -> 'SinkhornOutput': f = jax.lax.stop_gradient(self.f) if use_danskin else self.f @@ -225,7 +227,7 @@ def set_cost( @property def linear(self) -> bool: - return isinstance(self.ot_prob, linear_problems.LinearProblem) + return isinstance(self.ot_prob, linear_problem.LinearProblem) @property def geom(self) -> geometry.Geometry: @@ -352,8 +354,8 @@ def __init__( inner_iterations: int = 10, min_iterations: int = 0, max_iterations: int = 2000, - momentum: Optional[momentum_lib.Momentum] = None, - anderson: Optional[anderson_lib.AndersonAcceleration] = None, + momentum: Optional[acceleration.Momentum] = None, + anderson: Optional[acceleration.AndersonAcceleration] = None, parallel_dual_updates: bool = False, use_danskin: Optional[bool] = None, implicit_diff: Optional[implicit_lib.ImplicitDiff @@ -373,20 +375,20 @@ def __init__( self.implicit_diff = implicit_diff if momentum is not None: - self.momentum = momentum_lib.Momentum( + self.momentum = acceleration.Momentum( momentum.start, momentum.error_threshold, momentum.value, self.inner_iterations ) else: # Use no momentum if using Anderson or unrolling. if self.anderson is not None or self.implicit_diff is None: - self.momentum = momentum_lib.Momentum( + self.momentum = acceleration.Momentum( inner_iterations=self.inner_iterations ) # Use adaptive momentum from 300th iteration. Only do so # if error is already below threshold below. else: - self.momentum = momentum_lib.Momentum( + self.momentum = acceleration.Momentum( start=300, error_threshold=1e-2, inner_iterations=self.inner_iterations @@ -404,7 +406,7 @@ def __init__( implicit_lib.ImplicitDiff() if self.implicit_diff is None else self.implicit_diff ) - self.momentum = momentum_lib.Momentum( + self.momentum = acceleration.Momentum( inner_iterations=self.inner_iterations ) @@ -415,7 +417,7 @@ def __init__( def __call__( self, - ot_prob: linear_problems.LinearProblem, + ot_prob: linear_problem.LinearProblem, init: Tuple[Optional[jnp.ndarray], Optional[jnp.ndarray]] = (None, None), ) -> SinkhornOutput: """Run Sinkhorn algorithm. @@ -436,7 +438,7 @@ def __call__( return run_fn(ot_prob, self, (init_dual_a, init_dual_b)) def lse_step( - self, ot_prob: linear_problems.LinearProblem, state: SinkhornState, + self, ot_prob: linear_problem.LinearProblem, state: SinkhornState, iteration: int ) -> SinkhornState: """Sinkhorn LSE update.""" @@ -458,7 +460,7 @@ def lse_step( return state.set(fu=fu, gv=gv) def kernel_step( - self, ot_prob: linear_problems.LinearProblem, state: SinkhornState, + self, ot_prob: linear_problem.LinearProblem, state: SinkhornState, iteration: int ) -> SinkhornState: """Sinkhorn multiplicative update.""" @@ -478,7 +480,7 @@ def kernel_step( return state.set(fu=fu, gv=gv) def one_iteration( - self, ot_prob: linear_problems.LinearProblem, state: SinkhornState, + self, ot_prob: linear_problem.LinearProblem, state: SinkhornState, iteration: int, compute_error: bool ) -> SinkhornState: """Carries out sinkhorn iteration. @@ -544,8 +546,8 @@ def outer_iterations(self) -> int: return np.ceil(self.max_iterations / self.inner_iterations).astype(int) def init_state( - self, ot_prob: linear_problems.LinearProblem, init: Tuple[jnp.ndarray, - jnp.ndarray] + self, ot_prob: linear_problem.LinearProblem, init: Tuple[jnp.ndarray, + jnp.ndarray] ) -> SinkhornState: """Return the initial state of the loop.""" fu, gv = init @@ -555,7 +557,7 @@ def init_state( return self.anderson.init_maps(ot_prob, state) if self.anderson else state def output_from_state( - self, ot_prob: linear_problems.LinearProblem, state: SinkhornState + self, ot_prob: linear_problem.LinearProblem, state: SinkhornState ) -> SinkhornOutput: """Create an output from a loop state. @@ -625,7 +627,7 @@ def tree_unflatten(cls, aux_data, children): def run( - ot_prob: linear_problems.LinearProblem, solver: Sinkhorn, + ot_prob: linear_problem.LinearProblem, solver: Sinkhorn, init: Tuple[jnp.ndarray, ...] ) -> SinkhornOutput: """Run loop of the solver, outputting a state upgraded to an output.""" @@ -638,20 +640,20 @@ def run( def iterations( - ot_prob: linear_problems.LinearProblem, solver: Sinkhorn, + ot_prob: linear_problem.LinearProblem, solver: Sinkhorn, init: Tuple[jnp.ndarray, ...] ) -> SinkhornOutput: """Jittable Sinkhorn loop. args contain initialization variables.""" def cond_fn( - iteration: int, const: Tuple[linear_problems.LinearProblem, Sinkhorn], + iteration: int, const: Tuple[linear_problem.LinearProblem, Sinkhorn], state: SinkhornState ) -> bool: _, solver = const return solver._continue(state, iteration) def body_fn( - iteration: int, const: Tuple[linear_problems.LinearProblem, Sinkhorn], + iteration: int, const: Tuple[linear_problem.LinearProblem, Sinkhorn], state: SinkhornState, compute_error: bool ) -> SinkhornState: ot_prob, solver = const @@ -675,10 +677,10 @@ def body_fn( def _iterations_taped( - ot_prob: linear_problems.LinearProblem, solver: Sinkhorn, + ot_prob: linear_problem.LinearProblem, solver: Sinkhorn, init: Tuple[jnp.ndarray, ...] ) -> Tuple[SinkhornOutput, Tuple[jnp.ndarray, jnp.ndarray, - linear_problems.LinearProblem, Sinkhorn]]: + linear_problem.LinearProblem, Sinkhorn]]: """Run forward pass of the Sinkhorn algorithm storing side information.""" state = iterations(ot_prob, solver, init) return state, (state.f, state.g, ot_prob, solver) @@ -750,16 +752,16 @@ def make( ) # If no params are passed, align default with that provide in Sinkhorn solver. if momentum is None and chg_momentum_from is None: - mom = momentum_lib.Momentum(start=300, error_threshold=1e-2) + mom = acceleration.Momentum(start=300, error_threshold=1e-2) elif momentum is None: - mom = momentum_lib.Momentum(start=chg_momentum_from) + mom = acceleration.Momentum(start=chg_momentum_from) elif chg_momentum_from is None: - mom = momentum_lib.Momentum(value=momentum) + mom = acceleration.Momentum(value=momentum) else: - mom = momentum_lib.Momentum(start=chg_momentum_from, value=momentum) + mom = acceleration.Momentum(start=chg_momentum_from, value=momentum) if anderson_acceleration > 0: - anderson = anderson_lib.AndersonAcceleration( + anderson = acceleration.AndersonAcceleration( memory=anderson_acceleration, refresh_every=refresh_anderson_frequency ) else: @@ -1100,5 +1102,5 @@ def sinkhorn( by the user. """ sink = make(**kwargs) - ot_prob = linear_problems.LinearProblem(geom, a, b, tau_a, tau_b) + ot_prob = linear_problem.LinearProblem(geom, a, b, tau_a, tau_b) return sink(ot_prob, (init_dual_a, init_dual_b)) diff --git a/ott/core/sinkhorn_lr.py b/ott/solvers/linear/sinkhorn_lr.py similarity index 94% rename from ott/core/sinkhorn_lr.py rename to ott/solvers/linear/sinkhorn_lr.py index e27989f44..62364ef14 100644 --- a/ott/core/sinkhorn_lr.py +++ b/ott/solvers/linear/sinkhorn_lr.py @@ -21,11 +21,14 @@ import jax.scipy as jsp from typing_extensions import Literal -from ott.core import _math_utils as mu -from ott.core import fixed_point_loop -from ott.core import initializers_lr as init_lib -from ott.core import linear_problems, sinkhorn from ott.geometry import geometry, low_rank, pointcloud +from ott.initializers.linear import initializers_lr as init_lib +from ott.math import fixed_point_loop +from ott.math import utils as mu +from ott.problems.linear import linear_problem +from ott.solvers.linear import sinkhorn + +__all__ = ["LRSinkhorn", "LRSinkhornOutput"] class LRSinkhornState(NamedTuple): @@ -48,13 +51,13 @@ def compute_error(self, previous_state: "LRSinkhornState") -> float: def reg_ot_cost( self, - ot_prob: linear_problems.LinearProblem, + ot_prob: linear_problem.LinearProblem, use_danskin: bool = False ) -> float: return compute_reg_ot_cost(self.q, self.r, self.g, ot_prob, use_danskin) def solution_error( - self, ot_prob: linear_problems.LinearProblem, norm_error: Tuple[int, ...], + self, ot_prob: linear_problem.LinearProblem, norm_error: Tuple[int, ...], lse_mode: bool ) -> jnp.ndarray: return solution_error(self.q, self.r, ot_prob, norm_error, lse_mode) @@ -68,7 +71,7 @@ def compute_reg_ot_cost( q: jnp.ndarray, r: jnp.ndarray, g: jnp.ndarray, - ot_prob: linear_problems.LinearProblem, + ot_prob: linear_problem.LinearProblem, use_danskin: bool = False ) -> float: q = jax.lax.stop_gradient(q) if use_danskin else q @@ -78,7 +81,7 @@ def compute_reg_ot_cost( def solution_error( - q: jnp.ndarray, r: jnp.ndarray, ot_prob: linear_problems.LinearProblem, + q: jnp.ndarray, r: jnp.ndarray, ot_prob: linear_problem.LinearProblem, norm_error: Tuple[int, ...], lse_mode: bool ) -> jnp.ndarray: """Compute solution error. @@ -122,7 +125,7 @@ class LRSinkhornOutput(NamedTuple): # TODO(michalk8): must be called `errors`, because of `store_inner_errors` # in future, enforce via class hierarchy errors: jnp.ndarray - ot_prob: linear_problems.LinearProblem + ot_prob: linear_problem.LinearProblem # TODO(michalk8): Optional is an artifact of the current impl., refactor reg_ot_cost: Optional[float] = None @@ -132,7 +135,7 @@ def set(self, **kwargs: Any) -> 'LRSinkhornOutput': def set_cost( self, - ot_prob: linear_problems.LinearProblem, + ot_prob: linear_problem.LinearProblem, lse_mode: bool, use_danskin: bool = False ) -> 'LRSinkhornOutput': @@ -141,14 +144,14 @@ def set_cost( def compute_reg_ot_cost( self, - ot_prob: linear_problems.LinearProblem, + ot_prob: linear_problem.LinearProblem, use_danskin: bool = False, ) -> float: return compute_reg_ot_cost(self.q, self.r, self.g, ot_prob, use_danskin) @property def linear(self) -> bool: - return isinstance(self.ot_prob, linear_problems.LinearProblem) + return isinstance(self.ot_prob, linear_problem.LinearProblem) @property def geom(self) -> geometry.Geometry: @@ -285,7 +288,7 @@ def __init__( def __call__( self, - ot_prob: linear_problems.LinearProblem, + ot_prob: linear_problem.LinearProblem, init: Tuple[Optional[jnp.ndarray], Optional[jnp.ndarray], Optional[jnp.ndarray]] = (None, None, None), key: Optional[jnp.ndarray] = None, @@ -316,7 +319,7 @@ def __call__( def _lr_costs( self, - ot_prob: linear_problems.LinearProblem, + ot_prob: linear_problem.LinearProblem, state: LRSinkhornState, ) -> Tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray, float]: log_q, log_r, log_g = ( @@ -353,7 +356,7 @@ def dykstra_update( c_r: jnp.ndarray, h: jnp.ndarray, gamma: float, - ot_prob: linear_problems.LinearProblem, + ot_prob: linear_problem.LinearProblem, min_entry_value: float = 1e-6, tolerance: float = 1e-3, min_iter: int = 0, @@ -465,7 +468,7 @@ def recompute_couplings( return recompute_couplings(f1, g1_old, c_q, f2, g2_old, c_r, h_old, gamma) def lse_step( - self, ot_prob: linear_problems.LinearProblem, state: LRSinkhornState, + self, ot_prob: linear_problem.LinearProblem, state: LRSinkhornState, iteration: int ) -> LRSinkhornState: """LR Sinkhorn LSE update.""" @@ -476,7 +479,7 @@ def lse_step( return state.set(q=q, g=g, r=r, gamma=gamma) def kernel_step( - self, ot_prob: linear_problems.LinearProblem, state: LRSinkhornState, + self, ot_prob: linear_problem.LinearProblem, state: LRSinkhornState, iteration: int ) -> NoReturn: """Not implemented.""" @@ -484,7 +487,7 @@ def kernel_step( raise NotImplementedError("Not implemented.") def one_iteration( - self, ot_prob: linear_problems.LinearProblem, state: LRSinkhornState, + self, ot_prob: linear_problem.LinearProblem, state: LRSinkhornState, iteration: int, compute_error: bool ) -> LRSinkhornState: """Carries out one LR sinkhorn iteration. @@ -539,7 +542,7 @@ def is_entropic(self) -> bool: return self.epsilon > 0. def create_initializer( - self, prob: linear_problems.LinearProblem + self, prob: linear_problem.LinearProblem ) -> init_lib.LRInitializer: """Create a low-rank Sinkhorn initializer. @@ -569,7 +572,7 @@ def create_initializer( return initializer def init_state( - self, ot_prob: linear_problems.LinearProblem, + self, ot_prob: linear_problem.LinearProblem, init: Tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray] ) -> LRSinkhornState: """Return the initial state of the loop.""" @@ -585,7 +588,7 @@ def init_state( ) def output_from_state( - self, ot_prob: linear_problems.LinearProblem, state: LRSinkhornState + self, ot_prob: linear_problem.LinearProblem, state: LRSinkhornState ) -> LRSinkhornOutput: """Create an output from a loop state. @@ -641,7 +644,7 @@ def _diverged(self, state: LRSinkhornState, iteration: int) -> bool: def run( - ot_prob: linear_problems.LinearProblem, + ot_prob: linear_problem.LinearProblem, solver: LRSinkhorn, init: Tuple[Optional[jnp.ndarray], Optional[jnp.ndarray], Optional[jnp.ndarray]], diff --git a/ott/solvers/nn/__init__.py b/ott/solvers/nn/__init__.py new file mode 100644 index 000000000..e6c465331 --- /dev/null +++ b/ott/solvers/nn/__init__.py @@ -0,0 +1 @@ +# TODO(michalk8): imports diff --git a/ott/core/icnn.py b/ott/solvers/nn/icnn.py similarity index 97% rename from ott/core/icnn.py rename to ott/solvers/nn/icnn.py index 8be945f4e..4e93bc91c 100644 --- a/ott/core/icnn.py +++ b/ott/solvers/nn/icnn.py @@ -13,7 +13,7 @@ # limitations under the License. # Lint as: python3 -"""Implementation of Amos+(2017) input convex neural networks (ICNN).""" +"""Implementation of :cite:`amos:17` input convex neural networks (ICNN).""" from typing import Any, Callable, Sequence, Tuple, Union @@ -24,8 +24,10 @@ from flax.training import train_state from jax.nn import initializers -from ott.core.layers import PosDefPotentials, PositiveDense -from ott.geometry import matrix_square_root +from ott.math import matrix_square_root +from ott.solvers.nn.layers import PosDefPotentials, PositiveDense + +__all__ = ["ICNN"] PRNGKey = Any Shape = Tuple[int] diff --git a/ott/core/layers.py b/ott/solvers/nn/layers.py similarity index 86% rename from ott/core/layers.py rename to ott/solvers/nn/layers.py index 5a4b0a396..a0add5a9d 100644 --- a/ott/core/layers.py +++ b/ott/solvers/nn/layers.py @@ -11,7 +11,7 @@ # limitations under the License. # Lint as: python3 -"""Layers used in input convex neural networks (Amos+(2017), Bunne+(2022)).""" +"""Layers used in input convex neural networks :cite:`amos:17,bunne:22`.""" from typing import Any, Callable, Tuple @@ -19,6 +19,8 @@ import jax.numpy as jnp from flax import linen as nn +__all__ = ["PositiveDense", "PosDefPotentials"] + PRNGKey = Any Shape = Tuple[int] Dtype = Any @@ -30,8 +32,8 @@ class PositiveDense(nn.Module): Args: dim_hidden: the number of output dim_hidden. - rectifier_fn: choice of rectiver function (default: softplus function). - inv_rectifier_fn: choice of inverse rectiver function + rectifier_fn: choice of rectifier function (default: softplus function). + inv_rectifier_fn: choice of inverse rectifier function (default: inverse softplus function). dtype: the dtype of the computation (default: float32). precision: numerical precision of computation see `jax.lax.Precision` @@ -50,11 +52,11 @@ class PositiveDense(nn.Module): bias_init: Callable[[PRNGKey, Shape, Dtype], Array] = nn.initializers.zeros @nn.compact - def __call__(self, inputs): + def __call__(self, inputs: jnp.ndarray) -> jnp.ndarray: """Applies a linear transformation to inputs along the last dimension. Args: - inputs: The nd-array to be transformed. + inputs: Array to be transformed. Returns: The transformed input. """ @@ -79,8 +81,8 @@ class PosDefPotentials(nn.Module): """A layer to output (0.5 [A_i A_i^T] (x - b_i)_i potentials. Args: - use_bias: whether to add a bias to the output (default: True). - dtype: the dtype of the computation (default: float32). + use_bias: whether to add a bias to the output. + dtype: the dtype of the computation. precision: numerical precision of computation see `jax.lax.Precision` for details. kernel_init: initializer function for the weight matrix. @@ -96,11 +98,12 @@ class PosDefPotentials(nn.Module): bias_init: Callable[[PRNGKey, Shape, Dtype], Array] = nn.initializers.zeros @nn.compact - def __call__(self, inputs): - """Applies a few quadratic forms. + def __call__(self, inputs: jnp.ndarray) -> jnp.ndarray: + """Apply a few quadratic forms. Args: - inputs: The nd-array to be transformed (possibly batched) + inputs: Array to be transformed (possibly batched). + Returns: The transformed input. """ diff --git a/ott/core/neuraldual.py b/ott/solvers/nn/neuraldual.py similarity index 99% rename from ott/core/neuraldual.py rename to ott/solvers/nn/neuraldual.py index b4716a06d..b7b773164 100644 --- a/ott/core/neuraldual.py +++ b/ott/solvers/nn/neuraldual.py @@ -23,7 +23,10 @@ from flax import core from typing_extensions import Literal -from ott.core import icnn, potentials +from ott.math import potentials +from ott.solvers.nn import icnn + +__all__ = ["NeuralDualSolver"] Train_t = Dict[Literal["training_logs", "validation_logs"], List[float]] Potentials_t = potentials.DualPotentials diff --git a/ott/solvers/quadratic/__init__.py b/ott/solvers/quadratic/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ott/core/gromov_wasserstein.py b/ott/solvers/quadratic/gromov_wasserstein.py similarity index 95% rename from ott/core/gromov_wasserstein.py rename to ott/solvers/quadratic/gromov_wasserstein.py index 0f9f61305..98c3a0af6 100644 --- a/ott/core/gromov_wasserstein.py +++ b/ott/solvers/quadratic/gromov_wasserstein.py @@ -20,17 +20,14 @@ import jax.numpy as jnp from typing_extensions import Literal -from ott.core import ( - fixed_point_loop, - initializers_lr, - linear_problems, - quad_initializers, - quad_problems, - sinkhorn, - sinkhorn_lr, - was_solver, -) from ott.geometry import epsilon_scheduler, geometry, low_rank, pointcloud +from ott.initializers.linear import initializers_lr +from ott.initializers.quadratic import initializers as quad_initializers +from ott.math import fixed_point_loop +from ott.problems.linear import linear_problem +from ott.problems.quadratic import quadratic_costs, quadratic_problem +from ott.solvers.linear import sinkhorn, sinkhorn_lr +from ott.utils import was_solver LinearOutput = Union[sinkhorn.SinkhornOutput, sinkhorn_lr.LRSinkhornOutput] @@ -107,7 +104,7 @@ class GWState(NamedTuple): costs: jnp.ndarray linear_convergence: jnp.ndarray linear_state: LinearOutput - linear_pb: linear_problems.LinearProblem + linear_pb: linear_problem.LinearProblem old_transport_mass: float keys: Optional[jnp.ndarray] = None errors: Optional[jnp.ndarray] = None @@ -118,7 +115,7 @@ def set(self, **kwargs: Any) -> 'GWState': def update( self, iteration: int, linear_sol: LinearOutput, - linear_pb: linear_problems.LinearProblem, store_errors: bool, + linear_pb: linear_problem.LinearProblem, store_errors: bool, old_transport_mass: float ) -> 'GWState': costs = self.costs.at[iteration].set(linear_sol.reg_ot_cost) @@ -191,8 +188,8 @@ def __init__( def __call__( self, - prob: quad_problems.QuadraticProblem, - init: Optional[linear_problems.LinearProblem] = None, + prob: quadratic_problem.QuadraticProblem, + init: Optional[linear_problem.LinearProblem] = None, key: Optional[jnp.ndarray] = None, **kwargs: Any, ) -> GWOutput: @@ -240,8 +237,8 @@ def __call__( def init_state( self, - prob: quad_problems.QuadraticProblem, - init: linear_problems.LinearProblem, + prob: quadratic_problem.QuadraticProblem, + init: linear_problem.LinearProblem, key: jnp.ndarray, ) -> GWState: """Initialize the state of the Gromov-Wasserstein iterations. @@ -292,7 +289,7 @@ def output_from_state(self, state: GWState) -> GWOutput: ) def create_initializer( - self, prob: quad_problems.QuadraticProblem + self, prob: quadratic_problem.QuadraticProblem ) -> quad_initializers.BaseQuadraticInitializer: """Create quadratic, possibly low-rank initializer. @@ -309,7 +306,7 @@ def create_initializer( assert isinstance( self.quad_initializer, quad_initializers.LRQuadraticInitializer ), f"Expected quadratic initializer to be low rank, " \ - f"found `{type(self.quad_initializer).__name___}`." + f"found `{type(self.quad_initializer).__name__}`." assert self.quad_initializer.rank == self.rank, \ f"Expected quadratic initializer of rank `{self.rank}`, " \ f"found `{self.quad_initializer.rank}`." @@ -345,8 +342,8 @@ def tree_flatten(self) -> Tuple[Sequence[Any], Dict[str, Any]]: def iterations( solver: GromovWasserstein, - prob: quad_problems.QuadraticProblem, - init: linear_problems.LinearProblem, + prob: quadratic_problem.QuadraticProblem, + init: linear_problem.LinearProblem, key: jnp.ndarray, ) -> GWOutput: """Jittable Gromov-Wasserstein outer loop.""" @@ -469,7 +466,7 @@ def gromov_wasserstein( scale_cost: Optional[Union[bool, float, str]] = False, a: Optional[jnp.ndarray] = None, b: Optional[jnp.ndarray] = None, - loss: Union[Literal['sqeucl', 'kl'], quad_problems.GWLoss] = 'sqeucl', + loss: Union[Literal['sqeucl', 'kl'], quadratic_costs.GWLoss] = 'sqeucl', tau_a: Optional[float] = 1.0, tau_b: Optional[float] = 1.0, gw_unbalanced_correction: bool = True, @@ -535,7 +532,7 @@ def gromov_wasserstein( Returns: A GromovWassersteinState named tuple. """ - prob = quad_problems.QuadraticProblem( + prob = quadratic_problem.QuadraticProblem( geom_xx, geom_yy, geom_xy=geom_xy, diff --git a/ott/core/gw_barycenter.py b/ott/solvers/quadratic/gw_barycenter.py similarity index 93% rename from ott/core/gw_barycenter.py rename to ott/solvers/quadratic/gw_barycenter.py index 18891afd1..8020f99e3 100644 --- a/ott/core/gw_barycenter.py +++ b/ott/solvers/quadratic/gw_barycenter.py @@ -4,14 +4,12 @@ import jax import jax.numpy as jnp -from ott.core import ( - bar_problems, - fixed_point_loop, - gromov_wasserstein, - linear_problems, - was_solver, -) from ott.geometry import pointcloud +from ott.math import fixed_point_loop +from ott.problems.linear import linear_problem +from ott.problems.quadratic import barycenter_problem +from ott.solvers.quadratic import gromov_wasserstein +from ott.utils import was_solver __all__ = ["GWBarycenterState", "GromovWassersteinBarycenter"] @@ -93,7 +91,7 @@ def __init__( self._quad_solver = gromov_wasserstein.GromovWasserstein(**kwargs) def __call__( - self, problem: bar_problems.GWBarycenterProblem, bar_size: int, + self, problem: barycenter_problem.GWBarycenterProblem, bar_size: int, **kwargs: Any ) -> GWBarycenterState: """Solver the (fused) GW barycenter problem. @@ -113,7 +111,7 @@ def __call__( def init_state( self, - problem: bar_problems.GWBarycenterProblem, + problem: barycenter_problem.GWBarycenterProblem, bar_size: int, bar_init: Optional[Union[jnp.ndarray, Tuple[jnp.ndarray, jnp.ndarray]]] = None, @@ -188,7 +186,7 @@ def update_state( self, state: GWBarycenterState, iteration: int, - problem: bar_problems.GWBarycenterProblem, + problem: barycenter_problem.GWBarycenterProblem, store_errors: bool = True, ) -> Tuple[float, bool, jnp.ndarray, Optional[jnp.ndarray]]: @@ -236,7 +234,7 @@ def solve_gw( def output_from_state(self, state: GWBarycenterState) -> GWBarycenterState: """No-op.""" # TODO(michalk8): just for consistency with continuous barycenter - # will be refactored in the future + # will be refactored in the future to create an output return state def tree_flatten(self) -> Tuple[Sequence[Any], Dict[str, Any]]: @@ -279,13 +277,14 @@ def init_transports( geom = pointcloud.PointCloud( x, y, epsilon=epsilon, src_mask=a > 0, tgt_mask=b > 0 ) - problem = linear_problems.LinearProblem(geom, a=a, b=b) + problem = linear_problem.LinearProblem(geom, a=a, b=b) return solver(problem).matrix def iterations( solver: GromovWassersteinBarycenter, - problem: bar_problems.GWBarycenterProblem, init_state: GWBarycenterState + problem: barycenter_problem.GWBarycenterProblem, + init_state: GWBarycenterState ) -> GWBarycenterState: def cond_fn( @@ -297,7 +296,7 @@ def cond_fn( def body_fn( iteration, constants: Tuple[GromovWassersteinBarycenter, - bar_problems.GWBarycenterProblem], + barycenter_problem.GWBarycenterProblem], state: GWBarycenterState, compute_error: bool ) -> GWBarycenterState: del compute_error # always assumed true diff --git a/ott/tools/gaussian_mixture/gaussian_mixture_pair.py b/ott/tools/gaussian_mixture/gaussian_mixture_pair.py index c5b8968c1..eccd3da68 100644 --- a/ott/tools/gaussian_mixture/gaussian_mixture_pair.py +++ b/ott/tools/gaussian_mixture/gaussian_mixture_pair.py @@ -16,8 +16,8 @@ import jax import jax.numpy as jnp -from ott.core import sinkhorn from ott.geometry import costs, geometry, pointcloud +from ott.solvers.linear import sinkhorn from ott.tools.gaussian_mixture import gaussian_mixture diff --git a/ott/tools/gaussian_mixture/scale_tril.py b/ott/tools/gaussian_mixture/scale_tril.py index c4f7ea077..53a8df1fc 100644 --- a/ott/tools/gaussian_mixture/scale_tril.py +++ b/ott/tools/gaussian_mixture/scale_tril.py @@ -18,7 +18,8 @@ import jax import jax.numpy as jnp -from ott.geometry import costs, matrix_square_root +from ott.geometry import costs +from ott.math import matrix_square_root from ott.tools.gaussian_mixture import linalg diff --git a/ott/tools/k_means.py b/ott/tools/k_means.py index 335d16dc8..4ad02723d 100644 --- a/ott/tools/k_means.py +++ b/ott/tools/k_means.py @@ -19,8 +19,8 @@ import jax.numpy as jnp from typing_extensions import Literal -from ott.core import fixed_point_loop from ott.geometry import costs, pointcloud +from ott.math import fixed_point_loop __all__ = ["k_means", "KMeansOutput"] diff --git a/ott/tools/segment_sinkhorn.py b/ott/tools/segment_sinkhorn.py index b770e06be..4a5427c6e 100644 --- a/ott/tools/segment_sinkhorn.py +++ b/ott/tools/segment_sinkhorn.py @@ -17,8 +17,9 @@ from jax import numpy as jnp -from ott.core import segment, sinkhorn from ott.geometry import costs, pointcloud +from ott.solvers.linear import sinkhorn +from ott.utils import segment def segment_sinkhorn( diff --git a/ott/tools/sinkhorn_divergence.py b/ott/tools/sinkhorn_divergence.py index faa3e1ba0..46b196339 100644 --- a/ott/tools/sinkhorn_divergence.py +++ b/ott/tools/sinkhorn_divergence.py @@ -17,8 +17,10 @@ import jax.numpy as jnp -from ott.core import potentials, segment, sinkhorn from ott.geometry import costs, geometry, pointcloud +from ott.math import potentials +from ott.solvers.linear import sinkhorn +from ott.utils import segment __all__ = [ "sinkhorn_divergence", "segment_sinkhorn_divergence", diff --git a/ott/tools/transport.py b/ott/tools/transport.py index 73674ed78..c4e98fa68 100644 --- a/ott/tools/transport.py +++ b/ott/tools/transport.py @@ -27,13 +27,19 @@ ott.core.gromov_wasserstein) for better control over the parameters. """ -from typing import Any, NamedTuple, Optional +from typing import Any, NamedTuple, Optional, Union import jax.numpy as jnp +import numpy as np from typing_extensions import Literal -from ott.core import gromov_wasserstein, linear_problems, problems, sinkhorn -from ott.geometry import geometry +from ott.geometry import geometry, pointcloud +from ott.problems.linear import linear_problem +from ott.problems.quadratic import quadratic_problem +from ott.solvers.linear import sinkhorn +from ott.solvers.quadratic import gromov_wasserstein + +__all__ = ["Transport"] class Transport(NamedTuple): @@ -44,7 +50,7 @@ class Transport(NamedTuple): @property def linear(self) -> bool: - return isinstance(self.problem, linear_problems.LinearProblem) + return isinstance(self.problem, linear_problem.LinearProblem) @property def geom(self) -> geometry.Geometry: @@ -109,7 +115,7 @@ def solve( fused_penalty = kwargs.pop('fused_penalty', None) eps_keys = ['epsilon', 'init', 'target', 'decay'] pb_kwargs = {k: v for k, v in kwargs.items() if k in eps_keys} - pb = problems.make( + pb = make( *args, objective=objective, a=a, @@ -120,7 +126,7 @@ def solve( fused_penalty=fused_penalty, **pb_kwargs ) - linear = isinstance(pb, linear_problems.LinearProblem) + linear = isinstance(pb, linear_problem.LinearProblem) solver_fn = sinkhorn.make if linear else gromov_wasserstein.make geom_keys = ['cost_fn', 'power', 'online'] @@ -130,3 +136,76 @@ def solve( solver = solver_fn(**kwargs) output = solver(pb, (init_dual_a, init_dual_b)) return Transport(pb, output) + + +def make( + *args: Union[jnp.ndarray, geometry.Geometry, linear_problem.LinearProblem, + quadratic_problem.QuadraticProblem], + a: Optional[jnp.ndarray] = None, + b: Optional[jnp.ndarray] = None, + tau_a: float = 1.0, + tau_b: float = 1.0, + objective: Optional[str] = None, + gw_unbalanced_correction: Optional[bool] = True, + fused_penalty: Optional[float] = None, + scale_cost: Optional[Union[bool, float, str]] = False, + **kwargs: Any, +): + """Make a problem from arrays, assuming PointCloud geometries.""" + if isinstance(args[0], (jnp.ndarray, np.ndarray)): + x = args[0] + y = args[1] if len(args) > 1 else args[0] + if ((objective == 'linear') or + (objective is None and x.shape[1] == y.shape[1])): # noqa: E129 + geom_xy = pointcloud.PointCloud(x, y, **kwargs) + return linear_problem.LinearProblem( + geom_xy, a=a, b=b, tau_a=tau_a, tau_b=tau_b + ) + elif ((objective == 'quadratic') or + (objective is None and x.shape[1] != y.shape[1])): + geom_xx = pointcloud.PointCloud(x, x, **kwargs) + geom_yy = pointcloud.PointCloud(y, y, **kwargs) + return quadratic_problem.QuadraticProblem( + geom_xx=geom_xx, + geom_yy=geom_yy, + geom_xy=None, + scale_cost=scale_cost, + a=a, + b=b, + tau_a=tau_a, + tau_b=tau_b, + gw_unbalanced_correction=gw_unbalanced_correction + ) + elif objective == 'fused': + geom_xx = pointcloud.PointCloud(x, x, **kwargs) + geom_yy = pointcloud.PointCloud(y, y, **kwargs) + geom_xy = pointcloud.PointCloud(x, y, **kwargs) + return quadratic_problem.QuadraticProblem( + geom_xx=geom_xx, + geom_yy=geom_yy, + geom_xy=geom_xy, + fused_penalty=fused_penalty, + scale_cost=scale_cost, + a=a, + b=b, + tau_a=tau_a, + tau_b=tau_b, + gw_unbalanced_correction=gw_unbalanced_correction + ) + else: + raise ValueError(f'Unknown transport problem `{objective}`') + elif isinstance(args[0], geometry.Geometry): + if len(args) == 1: + return linear_problem.LinearProblem( + *args, a=a, b=b, tau_a=tau_a, tau_b=tau_b + ) + return quadratic_problem.QuadraticProblem( + *args, a=a, b=b, tau_a=tau_a, tau_b=tau_b, scale_cost=scale_cost + ) + elif isinstance( + args[0], + (linear_problem.LinearProblem, quadratic_problem.QuadraticProblem) + ): + return args[0] + else: + raise ValueError('Cannot instantiate a transport problem.') diff --git a/ott/typing.py b/ott/typing.py new file mode 100644 index 000000000..1b27fa3aa --- /dev/null +++ b/ott/typing.py @@ -0,0 +1,22 @@ +from jax import numpy as jnp +from typing_extensions import Protocol + +# TODO(michalk8): introduce additional types here + + +class Transport(Protocol): + """Interface for the solution of a transport problem. + + Classes implementing those function do not have to inherit from it, the + class can however be used in type hints to support duck typing. + """ + + @property + def matrix(self) -> jnp.ndarray: + ... + + def apply(self, inputs: jnp.ndarray, axis: int) -> jnp.ndarray: + ... + + def marginal(self, axis: int = 0) -> jnp.ndarray: + ... diff --git a/ott/utils/__init__.py b/ott/utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/ott/core/dataclasses.py b/ott/utils/dataclasses.py similarity index 100% rename from ott/core/dataclasses.py rename to ott/utils/dataclasses.py diff --git a/ott/core/segment.py b/ott/utils/segment.py similarity index 100% rename from ott/core/segment.py rename to ott/utils/segment.py diff --git a/ott/core/was_solver.py b/ott/utils/was_solver.py similarity index 98% rename from ott/core/was_solver.py rename to ott/utils/was_solver.py index eab88c19a..7e81c4377 100644 --- a/ott/core/was_solver.py +++ b/ott/utils/was_solver.py @@ -19,7 +19,7 @@ import jax import jax.numpy as jnp -from ott.core import sinkhorn, sinkhorn_lr +from ott.solvers.linear import sinkhorn, sinkhorn_lr State = Union[sinkhorn.SinkhornState, sinkhorn_lr.LRSinkhornState, "continuous_barycenter.BarycenterState"] # noqa: F821 From 7f2fa029c88fae726e2b3e822f06a2b2acb0afdc Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Tue, 15 Nov 2022 14:19:41 +0100 Subject: [PATCH 02/34] Update PC docs --- ott/geometry/pointcloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ott/geometry/pointcloud.py b/ott/geometry/pointcloud.py index 9e153f267..dcd576a48 100644 --- a/ott/geometry/pointcloud.py +++ b/ott/geometry/pointcloud.py @@ -600,7 +600,7 @@ def to_LRCGeometry( Useful when this geometry is used in the linear term of fused GW. kwargs: Keyword arguments, such as ``rank``, to :meth:`~ott.geometry.geometry.Geometry.to_LRCGeometry` used when - the point cloud does not squared Euclidean cost. + the point cloud does not have squared Euclidean cost. Returns: Returns the unmodified point cloud if :math:`n m \ge (n + m) d`, where From a41ade869ed36645634144b2c0335f39a9b88bc3 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Wed, 16 Nov 2022 12:03:39 +0100 Subject: [PATCH 03/34] Update imports, fix some types --- ott/geometry/costs.py | 2 + ott/geometry/epsilon_scheduler.py | 2 + ott/geometry/geometry.py | 2 + ott/geometry/grid.py | 2 + ott/geometry/low_rank.py | 2 + ott/geometry/pointcloud.py | 2 + ott/initializers/linear/__init__.py | 1 + ott/initializers/linear/initializers.py | 25 +++++---- ott/initializers/linear/initializers_lr.py | 17 +++--- ott/initializers/nn/__init__.py | 1 + ott/initializers/nn/initializers.py | 60 +++++++++++++++++---- ott/initializers/nn/layers.py | 39 -------------- ott/initializers/quadratic/__init__.py | 1 + ott/initializers/quadratic/initializers.py | 13 +++-- ott/math/__init__.py | 9 ++++ ott/math/fixed_point_loop.py | 5 +- ott/math/implicit_differentiation.py | 20 ++++--- ott/math/matrix_square_root.py | 17 +++--- ott/math/potentials.py | 10 ++-- ott/math/utils.py | 1 + ott/problems/linear/__init__.py | 3 +- ott/problems/linear/linear_problem.py | 1 + ott/problems/quadratic/__init__.py | 4 +- ott/problems/quadratic/quadratic_problem.py | 10 ++-- ott/solvers/linear/__init__.py | 7 +++ ott/solvers/linear/acceleration.py | 2 +- ott/solvers/linear/continuous_barycenter.py | 2 - ott/solvers/linear/discrete_barycenter.py | 2 +- ott/solvers/linear/sinkhorn.py | 3 +- ott/solvers/nn/__init__.py | 2 +- ott/solvers/nn/neuraldual.py | 14 ++--- ott/solvers/quadratic/__init__.py | 1 + ott/solvers/quadratic/gromov_wasserstein.py | 2 + ott/utils/__init__.py | 1 + ott/utils/dataclasses.py | 2 + ott/utils/segment.py | 2 + ott/utils/was_solver.py | 16 +++--- 37 files changed, 176 insertions(+), 129 deletions(-) delete mode 100644 ott/initializers/nn/layers.py diff --git a/ott/geometry/costs.py b/ott/geometry/costs.py index 1af441d73..22225a16a 100644 --- a/ott/geometry/costs.py +++ b/ott/geometry/costs.py @@ -24,6 +24,8 @@ from ott.math import fixed_point_loop, matrix_square_root +__all__ = ["Euclidean", "SqEuclidean", "Cosine", "Bures", "UnbalancedBures"] + @jax.tree_util.register_pytree_node_class class CostFn(abc.ABC): diff --git a/ott/geometry/epsilon_scheduler.py b/ott/geometry/epsilon_scheduler.py index 1e0fc30e7..8f5666385 100644 --- a/ott/geometry/epsilon_scheduler.py +++ b/ott/geometry/epsilon_scheduler.py @@ -19,6 +19,8 @@ import jax import jax.numpy as jnp +__all__ = ["Epsilon"] + @jax.tree_util.register_pytree_node_class class Epsilon: diff --git a/ott/geometry/geometry.py b/ott/geometry/geometry.py index ff3d5000c..3550c881b 100644 --- a/ott/geometry/geometry.py +++ b/ott/geometry/geometry.py @@ -28,6 +28,8 @@ from ott.geometry import epsilon_scheduler from ott.math import utils +__all__ = ["Geometry"] + @jax.tree_util.register_pytree_node_class class Geometry: diff --git a/ott/geometry/grid.py b/ott/geometry/grid.py index 88bd67f0a..3cc94c77b 100644 --- a/ott/geometry/grid.py +++ b/ott/geometry/grid.py @@ -24,6 +24,8 @@ from ott.geometry import costs, geometry, pointcloud from ott.math import utils +__all__ = ["Grid"] + @jax.tree_util.register_pytree_node_class class Grid(geometry.Geometry): diff --git a/ott/geometry/low_rank.py b/ott/geometry/low_rank.py index bf8bfa2a3..1fc748059 100644 --- a/ott/geometry/low_rank.py +++ b/ott/geometry/low_rank.py @@ -22,6 +22,8 @@ from ott.geometry import geometry +__all__ = ["LRCGeometry"] + @jax.tree_util.register_pytree_node_class class LRCGeometry(geometry.Geometry): diff --git a/ott/geometry/pointcloud.py b/ott/geometry/pointcloud.py index dcd576a48..c435a8ded 100644 --- a/ott/geometry/pointcloud.py +++ b/ott/geometry/pointcloud.py @@ -24,6 +24,8 @@ from ott.geometry import costs, geometry, low_rank from ott.math import utils +__all__ = ["PointCloud"] + @jax.tree_util.register_pytree_node_class class PointCloud(geometry.Geometry): diff --git a/ott/initializers/linear/__init__.py b/ott/initializers/linear/__init__.py index e69de29bb..1ce1a00cd 100644 --- a/ott/initializers/linear/__init__.py +++ b/ott/initializers/linear/__init__.py @@ -0,0 +1 @@ +from . import initializers, initializers_lr diff --git a/ott/initializers/linear/initializers.py b/ott/initializers/linear/initializers.py index 2b10ed6e2..65b91cc52 100644 --- a/ott/initializers/linear/initializers.py +++ b/ott/initializers/linear/initializers.py @@ -13,18 +13,17 @@ # limitations under the License. """Sinkhorn initializers.""" import abc -from typing import Any, Dict, Optional, Sequence, Tuple +from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Tuple import jax import jax.numpy as jnp from ott.geometry import pointcloud -from ott.problems.linear import linear_problem -__all__ = [ - "SinkhornInitializer", "DefaultInitializer", "GaussianInitializer", - "SortingInitializer" -] +if TYPE_CHECKING: + from ott.problems.linear import linear_problem + +__all__ = ["DefaultInitializer", "GaussianInitializer", "SortingInitializer"] @jax.tree_util.register_pytree_node_class @@ -33,19 +32,19 @@ class SinkhornInitializer(abc.ABC): @abc.abstractmethod def init_dual_a( - self, ot_prob: linear_problem.LinearProblem, lse_mode: bool + self, ot_prob: 'linear_problem.LinearProblem', lse_mode: bool ) -> jnp.ndarray: """Initialization for Sinkhorn potential/scaling f_u.""" @abc.abstractmethod def init_dual_b( - self, ot_prob: linear_problem.LinearProblem, lse_mode: bool + self, ot_prob: 'linear_problem.LinearProblem', lse_mode: bool ) -> jnp.ndarray: """Initialization for Sinkhorn potential/scaling g_v.""" def __call__( self, - ot_prob: linear_problem.LinearProblem, + ot_prob: 'linear_problem.LinearProblem', a: Optional[jnp.ndarray], b: Optional[jnp.ndarray], lse_mode: bool, @@ -97,7 +96,7 @@ class DefaultInitializer(SinkhornInitializer): """Default initialization of Sinkhorn dual potentials/primal scalings.""" def init_dual_a( - self, ot_prob: linear_problem.LinearProblem, lse_mode: bool + self, ot_prob: 'linear_problem.LinearProblem', lse_mode: bool ) -> jnp.ndarray: """Initialize Sinkhorn potential/scaling f_u. @@ -113,7 +112,7 @@ def init_dual_a( return init_dual_a def init_dual_b( - self, ot_prob: linear_problem.LinearProblem, lse_mode: bool + self, ot_prob: 'linear_problem.LinearProblem', lse_mode: bool ) -> jnp.ndarray: """Initialize Sinkhorn potential/scaling g_v. @@ -141,7 +140,7 @@ class GaussianInitializer(DefaultInitializer): def init_dual_a( self, - ot_prob: linear_problem.LinearProblem, + ot_prob: 'linear_problem.LinearProblem', lse_mode: bool, ) -> jnp.ndarray: """Gaussian initialization function. @@ -243,7 +242,7 @@ def cond_fn(state: Tuple[jnp.ndarray, float, int]) -> bool: def init_dual_a( self, - ot_prob: linear_problem.LinearProblem, + ot_prob: 'linear_problem.LinearProblem', lse_mode: bool, init_f: Optional[jnp.ndarray] = None, ) -> jnp.ndarray: diff --git a/ott/initializers/linear/initializers_lr.py b/ott/initializers/linear/initializers_lr.py index 2999bf850..a8132816f 100644 --- a/ott/initializers/linear/initializers_lr.py +++ b/ott/initializers/linear/initializers_lr.py @@ -21,22 +21,19 @@ from ott.math import fixed_point_loop from ott.math import utils as mu -__all__ = [ - "RandomInitializer", "Rank2Initializer", "KMeansInitializer", - "GeneralizedKMeansInitializer" -] - if TYPE_CHECKING: from ott.problems.linear import linear_problem from ott.problems.quadratic import quadratic_problem from ott.solvers.linear import sinkhorn, sinkhorn_lr from ott.solvers.quadratic import gromov_wasserstein - Problem_t = Union[linear_problem.LinearProblem, - quadratic_problem.QuadraticProblem] -else: - Problem_t = "Union[linear_problems.LinearProblem, " \ - "quad_problems.QuadraticProblem]" +Problem_t = Union["linear_problem.LinearProblem", + "quadratic_problem.QuadraticProblem"] + +__all__ = [ + "RandomInitializer", "Rank2Initializer", "KMeansInitializer", + "GeneralizedKMeansInitializer" +] @jax.tree_util.register_pytree_node_class diff --git a/ott/initializers/nn/__init__.py b/ott/initializers/nn/__init__.py index e69de29bb..7ccb321da 100644 --- a/ott/initializers/nn/__init__.py +++ b/ott/initializers/nn/__init__.py @@ -0,0 +1 @@ +from . import initializers diff --git a/ott/initializers/nn/initializers.py b/ott/initializers/nn/initializers.py index 05f173b42..09fe1b26b 100644 --- a/ott/initializers/nn/initializers.py +++ b/ott/initializers/nn/initializers.py @@ -1,30 +1,31 @@ import functools -from typing import Any, Dict, Optional, Sequence, Tuple +from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Tuple import jax -import jax.numpy as jnp import optax from flax import linen as nn from flax.training import train_state +from jax import numpy as jnp from ott.geometry import geometry -from ott.initializers.linear import DefaultInitializer -from ott.initializers.nn import layers -from ott.problems.linear import linear_problem -from ott.solvers.linear import sinkhorn +from ott.initializers.linear import initializers -# TODO(michalk8): add Charlotte's initializer? -__all__ = ["MetaInitializer"] +if TYPE_CHECKING: + from ott.problems.linear import linear_problem + +# TODO(michalk8): add initializer for NeuralDual? +__all__ = ["MetaInitializer", "MetaMLP"] @jax.tree_util.register_pytree_node_class -class MetaInitializer(DefaultInitializer): +class MetaInitializer(initializers.DefaultInitializer): """Meta OT Initializer with a fixed geometry :cite:`amos:22`. This initializer consists of a predictive model that outputs the :math:`f` duals to solve the entropy-regularized OT problem given input probability weights ``a`` and ``b``, and a given (assumed to be fixed) geometry ``geom``. + The model's parameters are learned using a training set of OT instances (multiple pairs of probability weights), that assume the **same** geometry ``geom`` is used throughout, both for training and @@ -67,7 +68,7 @@ def __init__( self.rng = rng na, nb = geom.shape - self.meta_model = layers.MetaMLP( + self.meta_model = MetaMLP( potential_size=na ) if meta_model is None else meta_model @@ -120,7 +121,7 @@ def update( return self.update_impl(state, a, b) def init_dual_a( - self, ot_prob: linear_problem.LinearProblem, lse_mode: bool + self, ot_prob: 'linear_problem.LinearProblem', lse_mode: bool ) -> jnp.ndarray: # Detect if the problem is batched. assert ot_prob.a.ndim in (1, 2) and ot_prob.b.ndim in (1, 2) @@ -140,6 +141,8 @@ def init_dual_a( def _get_update_fn(self): """Return the implementation (and jitted) update function.""" + from ott.problems.linear import linear_problem + from ott.solvers.linear import sinkhorn def dual_obj_loss_single(params, a, b): f_pred = self._compute_f(a, b, params) @@ -186,3 +189,38 @@ def tree_flatten(self) -> Tuple[Sequence[Any], Dict[str, Any]]: 'rng': self.rng, 'state': self.state } + + +class MetaMLP(nn.Module): + r"""A Meta MLP potential for :class:`~ott.core.initializers.MetaInitializer`. + + This provides an MLP :math:`\hat f_\theta(a, b)` that maps from the + probabilities of the measures to the optimal dual potentials :math:`f`. + + Args: + potential_size: The dimensionality of :math:`f`. + num_hidden_units: The number of hidden units in each layer. + num_hidden_layers: The number of hidden layers. + """ + + potential_size: int + num_hidden_units: int = 512 + num_hidden_layers: int = 3 + + @nn.compact + def __call__(self, a: jnp.ndarray, b: jnp.ndarray) -> jnp.ndarray: + r"""Make a prediction. + + Args: + a: Probabilities of the :math:`\alpha` measure's atoms. + b: Probabilities of the :math:`\beta` measure's atoms. + + Returns: + The :math:`f` potential. + """ + dtype = a.dtype + z = jnp.concatenate((a, b)) + for _ in range(self.num_hidden_layers): + z = nn.relu(nn.Dense(self.num_hidden_units, dtype=dtype)(z)) + f = nn.Dense(self.potential_size, dtype=dtype)(z) + return f diff --git a/ott/initializers/nn/layers.py b/ott/initializers/nn/layers.py deleted file mode 100644 index 4f6b25382..000000000 --- a/ott/initializers/nn/layers.py +++ /dev/null @@ -1,39 +0,0 @@ -from flax import linen as nn -from jax import numpy as jnp - -__all__ = ["MetaMLP"] - - -class MetaMLP(nn.Module): - r"""A Meta MLP potential for :class:`~ott.core.initializers.MetaInitializer`. - - This provides an MLP :math:`\hat f_\theta(a, b)` that maps from the - probabilities of the measures to the optimal dual potentials :math:`f`. - - Args: - potential_size: The dimensionality of :math:`f`. - num_hidden_units: The number of hidden units in each layer. - num_hidden_layers: The number of hidden layers. - """ - - potential_size: int - num_hidden_units: int = 512 - num_hidden_layers: int = 3 - - @nn.compact - def __call__(self, a: jnp.ndarray, b: jnp.ndarray) -> jnp.ndarray: - r"""Make a prediction. - - Args: - a: Probabilities of the :math:`\alpha` measure's atoms. - b: Probabilities of the :math:`\beta` measure's atoms. - - Returns: - The :math:`f` potential. - """ - dtype = a.dtype - z = jnp.concatenate((a, b)) - for _ in range(self.num_hidden_layers): - z = nn.relu(nn.Dense(self.num_hidden_units, dtype=dtype)(z)) - f = nn.Dense(self.potential_size, dtype=dtype)(z) - return f diff --git a/ott/initializers/quadratic/__init__.py b/ott/initializers/quadratic/__init__.py index e69de29bb..7ccb321da 100644 --- a/ott/initializers/quadratic/__init__.py +++ b/ott/initializers/quadratic/__init__.py @@ -0,0 +1 @@ +from . import initializers diff --git a/ott/initializers/quadratic/initializers.py b/ott/initializers/quadratic/initializers.py index bab5a8f54..0c97f83a5 100644 --- a/ott/initializers/quadratic/initializers.py +++ b/ott/initializers/quadratic/initializers.py @@ -4,11 +4,10 @@ import jax from ott.geometry import geometry -from ott.problems.linear import linear_problem -from ott.solvers.linear import sinkhorn_lr if TYPE_CHECKING: from ott.initializers.linear import initializers_lr + from ott.problems.linear import linear_problem from ott.problems.quadratic import quadratic_problem __all__ = ["QuadraticInitializer", "LRQuadraticInitializer"] @@ -27,7 +26,7 @@ def __init__(self, **kwargs: Any): def __call__( self, quad_prob: 'quadratic_problem.QuadraticProblem', **kwargs: Any - ) -> linear_problem.LinearProblem: + ) -> 'linear_problem.LinearProblem': """Compute the initial linearization of a quadratic problem. Args: @@ -37,6 +36,8 @@ def __call__( Returns: Linear problem. """ + from ott.problems.linear import linear_problem + n, m = quad_prob.geom_xx.shape[0], quad_prob.geom_yy.shape[0] geom = self._create_geometry(quad_prob, **kwargs) assert geom.shape == (n, m), f"Expected geometry of shape `{n, m}`, " \ @@ -134,10 +135,10 @@ def _create_geometry( transport_mass = marginal_1.sum() # Initialises epsilon for Unbalanced GW according to Sejourne et al (2021) epsilon = quadratic_problem.update_epsilon_unbalanced( - epsilon, transport_mass + epsilon=epsilon, transport_mass=transport_mass ) unbalanced_correction = quad_prob.cost_unbalanced_correction( - tmp, marginal_1, marginal_2, epsilon + tmp, marginal_1, marginal_2, epsilon=epsilon ) h1, h2 = quad_prob.quad_loss @@ -176,6 +177,8 @@ def _create_geometry( Returns: The initial geometry used to initialize a linear problem. """ + from ott.solvers.linear import sinkhorn_lr + q, r, g = self._linear_lr_initializer(quad_prob, **kwargs) tmp_out = sinkhorn_lr.LRSinkhornOutput( q=q, r=r, g=g, costs=None, errors=None, ot_prob=None diff --git a/ott/math/__init__.py b/ott/math/__init__.py index e69de29bb..faac35ec8 100644 --- a/ott/math/__init__.py +++ b/ott/math/__init__.py @@ -0,0 +1,9 @@ +from . import ( + decomposition, + fixed_point_loop, + implicit_differentiation, + matrix_square_root, + potentials, + unbalanced_functions, + utils, +) diff --git a/ott/math/fixed_point_loop.py b/ott/math/fixed_point_loop.py index fbfb04789..3b22ad20a 100644 --- a/ott/math/fixed_point_loop.py +++ b/ott/math/fixed_point_loop.py @@ -19,7 +19,8 @@ import jax import jax.numpy as jnp import numpy as np -from jax import dtypes + +__all__ = ["fixpoint_iter", "fixpoint_iter_backprop"] def fixpoint_iter( @@ -123,7 +124,7 @@ def fixpoint_iter_fwd( states = jax.tree_util.tree_map( lambda x: jnp.zeros( (max_iterations // inner_iterations + 1,) + jnp.shape(x), - dtype=dtypes.result_type(x) + dtype=jax.dtypes.result_type(x) ), state ) diff --git a/ott/math/implicit_differentiation.py b/ott/math/implicit_differentiation.py index 44fc946e5..1f42888c0 100644 --- a/ott/math/implicit_differentiation.py +++ b/ott/math/implicit_differentiation.py @@ -13,22 +13,26 @@ # limitations under the License. """Functions entering the implicit differentiation of Sinkhorn.""" -from typing import Callable, Optional, Tuple +from typing import TYPE_CHECKING, Callable, Optional, Tuple import jax import jax.numpy as jnp from ott.math import unbalanced_functions -from ott.problems import linear as linear_problems from ott.utils import dataclasses +if TYPE_CHECKING: + from ott.problems.linear import linear_problem + +__all__ = ["ImplicitDiff"] + @dataclasses.register_pytree_node class ImplicitDiff: """Implicit differentiation of Sinkhorn algorithm. Attributes: - implicit_solver_fun: Callable, should return (solution, ...) + solver_fun: Callable, should return (solution, ...) ridge_kernel: promotes zero-sum solutions. only used if tau_a = tau_b = 1.0 ridge_identity: handles rank deficient transport matrices (this happens typically when rows/cols in cost/kernel matrices are colinear, or, @@ -48,9 +52,9 @@ class ImplicitDiff: def solve( self, gr: Tuple[jnp.ndarray, - jnp.ndarray], ot_prob: linear_problems.LinearProblem, + jnp.ndarray], ot_prob: "linear_problem.LinearProblem", f: jnp.ndarray, g: jnp.ndarray, lse_mode: bool - ): + ) -> jnp.ndarray: r"""Apply minus inverse of [hessian ``reg_ot_cost`` w.r.t. ``f``, ``g``]. This function is used to carry out implicit differentiation of ``sinkhorn`` @@ -271,9 +275,9 @@ def first_order_conditions( return jnp.concatenate((result_a, result_b)) def gradient( - self, prob: linear_problems.LinearProblem, f: jnp.ndarray, g: jnp.ndarray, - lse_mode: bool, gr: Tuple[jnp.ndarray, jnp.ndarray] - ) -> linear_problems.LinearProblem: + self, prob: "linear_problem.LinearProblem", f: jnp.ndarray, + g: jnp.ndarray, lse_mode: bool, gr: Tuple[jnp.ndarray, jnp.ndarray] + ) -> "linear_problem.LinearProblem": """Apply vjp to recover gradient in reverse mode differentiation.""" # Applies first part of vjp to gr: inverse part of implicit function theorem vjp_gr = self.solve(gr, prob, f, g, lse_mode) diff --git a/ott/math/matrix_square_root.py b/ott/math/matrix_square_root.py index 9cca55695..74a7651ad 100644 --- a/ott/math/matrix_square_root.py +++ b/ott/math/matrix_square_root.py @@ -24,6 +24,8 @@ from ott.math import fixed_point_loop +__all__ = ["sqrtm"] + @functools.partial(jax.custom_vjp, nondiff_argnums=(1, 2, 3, 4, 5)) def sqrtm( @@ -33,7 +35,7 @@ def sqrtm( inner_iterations: int = 10, max_iterations: int = 1000, regularization: float = 1e-3 -) -> jnp.ndarray: +) -> Tuple[jnp.ndarray, jnp.ndarray, jnp.ndarray]: """Higham algorithm to compute matrix square root of p.d. matrix. See :cite:`higham:97`, eq. 2.6b @@ -171,7 +173,7 @@ def sqrtm_fwd( max_iterations=max_iterations, regularization=regularization, ) - return ((sqrt_x, inv_sqrt_x, errors), (sqrt_x, inv_sqrt_x)) + return (sqrt_x, inv_sqrt_x, errors), (sqrt_x, inv_sqrt_x) def sqrtm_bwd( @@ -226,7 +228,7 @@ def sqrtm_bwd( axis1=-1, axis2=-2 ) - return (vjp_cot_sqrt + vjp_cot_inv_sqrt,) + return vjp_cot_sqrt + vjp_cot_inv_sqrt, sqrtm.defvjp(sqrtm_fwd, sqrtm_bwd) @@ -254,7 +256,7 @@ def sqrtm_only_bwd(sqrt_x: jnp.ndarray, axis1=-2, axis2=-1 ) - return (vjp,) + return vjp, sqrtm_only.defvjp(sqrtm_only_fwd, sqrtm_only_bwd) @@ -270,9 +272,8 @@ def inv_sqrtm_only_fwd(x: jnp.ndarray) -> Tuple[jnp.ndarray, jnp.ndarray]: return inv_sqrt_x, inv_sqrt_x -def inv_sqrtm_only_bwd( - residual: jnp.ndarray, cotangent: jnp.ndarray -) -> jnp.ndarray: +def inv_sqrtm_only_bwd(residual: jnp.ndarray, + cotangent: jnp.ndarray) -> Tuple[jnp.ndarray]: inv_sqrt_x = residual inv_x = jnp.matmul(inv_sqrt_x, inv_sqrt_x) vjp = jnp.swapaxes( @@ -287,7 +288,7 @@ def inv_sqrtm_only_bwd( axis1=-1, axis2=-2 ) - return (vjp,) + return vjp, inv_sqrtm_only.defvjp(inv_sqrtm_only_fwd, inv_sqrtm_only_bwd) diff --git a/ott/math/potentials.py b/ott/math/potentials.py index 0170825cc..6c84ae48e 100644 --- a/ott/math/potentials.py +++ b/ott/math/potentials.py @@ -1,4 +1,4 @@ -from typing import Any, Callable, Dict, Sequence, Tuple +from typing import TYPE_CHECKING, Any, Callable, Dict, Sequence, Tuple import jax import jax.numpy as jnp @@ -6,7 +6,8 @@ import jax.tree_util as jtu from typing_extensions import Literal -from ott.geometry import pointcloud +if TYPE_CHECKING: + from ott.geometry import pointcloud __all__ = ["DualPotentials", "EntropicPotentials"] Potential_t = Callable[[jnp.ndarray], float] @@ -130,7 +131,7 @@ class EntropicPotentials(DualPotentials): """ def __init__( - self, f: jnp.ndarray, g: jnp.ndarray, geom: pointcloud.PointCloud, + self, f: jnp.ndarray, g: jnp.ndarray, geom: "pointcloud.PointCloud", a: jnp.ndarray, b: jnp.ndarray ): n, m = geom.shape @@ -157,6 +158,7 @@ def g(self) -> Potential_t: def _create_potential_function( self, *, kind: Literal["f", "g"] ) -> Potential_t: + from ott.geometry import pointcloud def callback(x: jnp.ndarray) -> float: cost = pointcloud.PointCloud( @@ -164,7 +166,7 @@ def callback(x: jnp.ndarray) -> float: y, cost_fn=self._geom.cost_fn, power=self._geom.power, - epsilon=1.0 # epsilon is not used + epsilon=1.0 # epsilon is not used ).cost_matrix return -eps * jsp.special.logsumexp((potential - cost) / eps, b=prob_weights) diff --git a/ott/math/utils.py b/ott/math/utils.py index 6b1398aad..3c83f8e82 100644 --- a/ott/math/utils.py +++ b/ott/math/utils.py @@ -10,6 +10,7 @@ "barycentric_projection" ] +# TODO(michalk8): move to typing.py when refactoring types Sparse_t = Union[jesp.CSR, jesp.CSC, jesp.COO, jesp.BCOO] diff --git a/ott/problems/linear/__init__.py b/ott/problems/linear/__init__.py index 1ab3d4ccb..1681cefbb 100644 --- a/ott/problems/linear/__init__.py +++ b/ott/problems/linear/__init__.py @@ -1,2 +1 @@ -from .barycenter_problem import BarycenterProblem -from .linear_problem import LinearProblem +from . import barycenter_problem, linear_problem diff --git a/ott/problems/linear/linear_problem.py b/ott/problems/linear/linear_problem.py index 97c54d4e8..6f7aad57d 100644 --- a/ott/problems/linear/linear_problem.py +++ b/ott/problems/linear/linear_problem.py @@ -22,6 +22,7 @@ __all__ = ["LinearProblem"] +# TODO(michalk8): move to typing.py when refactoring the types MarginalFunc = Callable[[jnp.ndarray, jnp.ndarray], jnp.ndarray] TransportAppFunc = Callable[[jnp.ndarray, jnp.ndarray, jnp.ndarray, int], jnp.ndarray] diff --git a/ott/problems/quadratic/__init__.py b/ott/problems/quadratic/__init__.py index 48c54ef97..44f37cc34 100644 --- a/ott/problems/quadratic/__init__.py +++ b/ott/problems/quadratic/__init__.py @@ -1,3 +1 @@ -from . import quadratic_costs -from .barycenter_problem import GWBarycenterProblem -from .quadratic_problem import QuadraticProblem +from . import barycenter_problem, quadratic_costs, quadratic_problem diff --git a/ott/problems/quadratic/quadratic_problem.py b/ott/problems/quadratic/quadratic_problem.py index 03d0bdbb7..e2060015d 100644 --- a/ott/problems/quadratic/quadratic_problem.py +++ b/ott/problems/quadratic/quadratic_problem.py @@ -13,7 +13,7 @@ # limitations under the License. """Classes defining OT problem(s) (objective function + utilities).""" -from typing import Optional, Tuple, Union +from typing import TYPE_CHECKING, Optional, Tuple, Union import jax import jax.numpy as jnp @@ -22,9 +22,11 @@ from ott.geometry import epsilon_scheduler, geometry, low_rank, pointcloud from ott.problems.linear import linear_problem from ott.problems.quadratic import quadratic_costs -from ott.solvers.linear import sinkhorn_lr from ott.typing import Transport +if TYPE_CHECKING: + from ott.solvers.linear import sinkhorn_lr + __all__ = ["QuadraticProblem"] @@ -238,7 +240,7 @@ def init_transport_mass(self) -> float: return a.sum() * b.sum() def update_lr_geom( - self, lr_sink: sinkhorn_lr.LRSinkhornOutput + self, lr_sink: 'sinkhorn_lr.LRSinkhornOutput' ) -> geometry.Geometry: """Recompute (possibly LRC) linearization using LR Sinkhorn output.""" marginal_1 = lr_sink.marginal(1) @@ -325,7 +327,7 @@ def update_linearization( ) def update_lr_linearization( - self, lr_sink: sinkhorn_lr.LRSinkhornOutput + self, lr_sink: 'sinkhorn_lr.LRSinkhornOutput' ) -> linear_problem.LinearProblem: """Update a Quad problem linearization using a LR Sinkhorn.""" return linear_problem.LinearProblem( diff --git a/ott/solvers/linear/__init__.py b/ott/solvers/linear/__init__.py index e69de29bb..7f9d3a1bb 100644 --- a/ott/solvers/linear/__init__.py +++ b/ott/solvers/linear/__init__.py @@ -0,0 +1,7 @@ +from . import ( + acceleration, + continuous_barycenter, + discrete_barycenter, + sinkhorn, + sinkhorn_lr, +) diff --git a/ott/solvers/linear/acceleration.py b/ott/solvers/linear/acceleration.py index 8b638abc9..9684f2956 100644 --- a/ott/solvers/linear/acceleration.py +++ b/ott/solvers/linear/acceleration.py @@ -38,7 +38,7 @@ def extrapolation(self, xs: jnp.ndarray, fxs: jnp.ndarray) -> jnp.ndarray: # Recover linear combination and return it with NaN (caused # by 0 weights leading to -jnp.inf potentials, mixed with weights - # coefficiences of different signs), disambiguated to -inf. + # coefficients of different signs), disambiguated to -inf. combination = jnp.sum(fxs * weights[None, :], axis=1) return jnp.where(jnp.isfinite(combination), combination, -jnp.inf) diff --git a/ott/solvers/linear/continuous_barycenter.py b/ott/solvers/linear/continuous_barycenter.py index 5bc105321..e1d4a96e2 100644 --- a/ott/solvers/linear/continuous_barycenter.py +++ b/ott/solvers/linear/continuous_barycenter.py @@ -39,8 +39,6 @@ class BarycenterState(NamedTuple): inner Sinkhorn iterations. errors: Holds sequence of vectors of errors of the Sinkhorn algorithm at each iteration. - linear_states: State used to solve and store solutions to the OT problems - from the barycenter to the measures. x: barycenter points. a: barycenter weights. """ diff --git a/ott/solvers/linear/discrete_barycenter.py b/ott/solvers/linear/discrete_barycenter.py index 6373c9657..044295129 100644 --- a/ott/solvers/linear/discrete_barycenter.py +++ b/ott/solvers/linear/discrete_barycenter.py @@ -35,7 +35,7 @@ class SinkhornBarycenterOutput(NamedTuple): errors: jnp.ndarray -# TODO(michalk8): refactor as a solver +# TODO(michalk8): refactor as a solver? def discrete_barycenter( geom: geometry.Geometry, a: jnp.ndarray, diff --git a/ott/solvers/linear/sinkhorn.py b/ott/solvers/linear/sinkhorn.py index 28f41657d..b8201d6e5 100644 --- a/ott/solvers/linear/sinkhorn.py +++ b/ott/solvers/linear/sinkhorn.py @@ -27,11 +27,10 @@ from ott.math import implicit_differentiation as implicit_lib from ott.math import potentials, unbalanced_functions from ott.problems.linear import linear_problem +from ott.solvers.linear import acceleration __all__ = ["Sinkhorn", "SinkhornOutput"] -from ott.solvers.linear import acceleration - class SinkhornState(NamedTuple): """Holds the state variables used to solve OT with Sinkhorn.""" diff --git a/ott/solvers/nn/__init__.py b/ott/solvers/nn/__init__.py index e6c465331..a695e215c 100644 --- a/ott/solvers/nn/__init__.py +++ b/ott/solvers/nn/__init__.py @@ -1 +1 @@ -# TODO(michalk8): imports +from . import icnn, layers, neuraldual diff --git a/ott/solvers/nn/neuraldual.py b/ott/solvers/nn/neuraldual.py index b7b773164..dcde36866 100644 --- a/ott/solvers/nn/neuraldual.py +++ b/ott/solvers/nn/neuraldual.py @@ -28,8 +28,7 @@ __all__ = ["NeuralDualSolver"] -Train_t = Dict[Literal["training_logs", "validation_logs"], List[float]] -Potentials_t = potentials.DualPotentials +Train_t = Dict[Literal["train_logs", "valid_logs"], Dict[str, List[float]]] class NeuralDualSolver: @@ -139,7 +138,8 @@ def __call__( trainloader_target: Iterator[jnp.ndarray], validloader_source: Iterator[jnp.ndarray], validloader_target: Iterator[jnp.ndarray], - ) -> Union[Potentials_t, Tuple[Potentials_t, Train_t]]: + ) -> Union[potentials.DualPotentials, Tuple[potentials.DualPotentials, + Train_t]]: logs = self.train_neuraldual( trainloader_source, trainloader_target, @@ -152,10 +152,10 @@ def __call__( def train_neuraldual( self, - trainloader_source, - trainloader_target, - validloader_source, - validloader_target, + trainloader_source: Iterator[jnp.ndarray], + trainloader_target: Iterator[jnp.ndarray], + validloader_source: Iterator[jnp.ndarray], + validloader_target: Iterator[jnp.ndarray], ) -> Train_t: """Implementation of the training and validation script.""" # noqa: D401 try: diff --git a/ott/solvers/quadratic/__init__.py b/ott/solvers/quadratic/__init__.py index e69de29bb..af9e5d01e 100644 --- a/ott/solvers/quadratic/__init__.py +++ b/ott/solvers/quadratic/__init__.py @@ -0,0 +1 @@ +from . import gromov_wasserstein, gw_barycenter diff --git a/ott/solvers/quadratic/gromov_wasserstein.py b/ott/solvers/quadratic/gromov_wasserstein.py index 98c3a0af6..805354fa1 100644 --- a/ott/solvers/quadratic/gromov_wasserstein.py +++ b/ott/solvers/quadratic/gromov_wasserstein.py @@ -29,6 +29,8 @@ from ott.solvers.linear import sinkhorn, sinkhorn_lr from ott.utils import was_solver +__all__ = ["GWOutput", "GromovWasserstein", "gromov_wasserstein"] + LinearOutput = Union[sinkhorn.SinkhornOutput, sinkhorn_lr.LRSinkhornOutput] diff --git a/ott/utils/__init__.py b/ott/utils/__init__.py index e69de29bb..3f5072c03 100644 --- a/ott/utils/__init__.py +++ b/ott/utils/__init__.py @@ -0,0 +1 @@ +from . import dataclasses, segment, was_solver diff --git a/ott/utils/dataclasses.py b/ott/utils/dataclasses.py index 78aebac51..5f3de2ef6 100644 --- a/ott/utils/dataclasses.py +++ b/ott/utils/dataclasses.py @@ -17,6 +17,8 @@ import jax +__all__ = ["register_pytree_node"] + def register_pytree_node(cls: type) -> type: """Register dataclasses as pytree_nodes.""" diff --git a/ott/utils/segment.py b/ott/utils/segment.py index 82c4c98d3..2e15db821 100644 --- a/ott/utils/segment.py +++ b/ott/utils/segment.py @@ -16,6 +16,8 @@ import jax from jax import numpy as jnp +__all__ = ["segment_point_cloud"] + def segment_point_cloud( x: jnp.ndarray, diff --git a/ott/utils/was_solver.py b/ott/utils/was_solver.py index 7e81c4377..2ff7789d9 100644 --- a/ott/utils/was_solver.py +++ b/ott/utils/was_solver.py @@ -14,15 +14,18 @@ # Lint as: python3 """A Jax version of the regularised GW Solver (Peyre et al. 2016).""" -from typing import Any, Dict, Optional, Sequence, Tuple, Union +from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Tuple, Union import jax import jax.numpy as jnp -from ott.solvers.linear import sinkhorn, sinkhorn_lr +if TYPE_CHECKING: + from ott.solvers.linear import continuous_barycenter, sinkhorn, sinkhorn_lr -State = Union[sinkhorn.SinkhornState, sinkhorn_lr.LRSinkhornState, - "continuous_barycenter.BarycenterState"] # noqa: F821 +__all__ = ["WassersteinSolver"] + +State = Union["sinkhorn.SinkhornState", "sinkhorn_lr.LRSinkhornState", + "continuous_barycenter.BarycenterState"] @jax.tree_util.register_pytree_node_class @@ -33,8 +36,8 @@ def __init__( self, epsilon: Optional[float] = None, rank: int = -1, - linear_ot_solver: Optional[Union[sinkhorn.Sinkhorn, - sinkhorn_lr.LRSinkhorn]] = None, + linear_ot_solver: Optional[Union["sinkhorn.Sinkhorn", + "sinkhorn_lr.LRSinkhorn"]] = None, min_iterations: int = 5, max_iterations: int = 50, threshold: float = 1e-3, @@ -42,6 +45,7 @@ def __init__( store_inner_errors: bool = False, **kwargs: Any, ): + from ott.solvers.linear import sinkhorn, sinkhorn_lr default_epsilon = 1.0 # Set epsilon value to default if needed, but keep track of whether None was # passed to handle the case where a linear_ot_solver is passed or not. From 0d30656cc2001cf5a3569799c1cf6d0d32f857eb Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:28:29 +0100 Subject: [PATCH 04/34] Fix more types, pet-peeves --- ott/problems/quadratic/__init__.py | 2 +- ...{barycenter_problem.py => gw_barycenter.py} | 2 +- ott/solvers/nn/neuraldual.py | 18 +++++++++--------- ott/solvers/quadratic/gw_barycenter.py | 13 ++++++------- ott/tools/gaussian_mixture/fit_gmm.py | 15 +++++++-------- ott/tools/segment_sinkhorn.py | 4 ++-- ott/tools/sinkhorn_divergence.py | 4 ++-- 7 files changed, 28 insertions(+), 30 deletions(-) rename ott/problems/quadratic/{barycenter_problem.py => gw_barycenter.py} (99%) diff --git a/ott/problems/quadratic/__init__.py b/ott/problems/quadratic/__init__.py index 44f37cc34..18ff1c517 100644 --- a/ott/problems/quadratic/__init__.py +++ b/ott/problems/quadratic/__init__.py @@ -1 +1 @@ -from . import barycenter_problem, quadratic_costs, quadratic_problem +from . import gw_barycenter, quadratic_costs, quadratic_problem diff --git a/ott/problems/quadratic/barycenter_problem.py b/ott/problems/quadratic/gw_barycenter.py similarity index 99% rename from ott/problems/quadratic/barycenter_problem.py rename to ott/problems/quadratic/gw_barycenter.py index 089e645de..1bfd6a28d 100644 --- a/ott/problems/quadratic/barycenter_problem.py +++ b/ott/problems/quadratic/gw_barycenter.py @@ -36,7 +36,7 @@ class GWBarycenterProblem(barycenter_problem.BarycenterProblem): Only one of ``y`` and ``cost`` can be specified. y_fused: Array of shape ``[num_total_points, ndim_fused]`` containing the data of the points of all measures used to define the linear term - in the fused case. Similarly as ``y``, can be specified as a pre-segmented + in the fused case. Same as ``y``, it can be specified as a pre-segmented array of shape ``[num_measures, max_measure_size, ndim_fused]``. gw_loss: Gromov-Wasserstein loss. fused_penalty: Multiplier of the linear term. Only used when diff --git a/ott/solvers/nn/neuraldual.py b/ott/solvers/nn/neuraldual.py index dcde36866..537a7e4e3 100644 --- a/ott/solvers/nn/neuraldual.py +++ b/ott/solvers/nn/neuraldual.py @@ -14,7 +14,7 @@ """A Jax implementation of the ICNN based Kantorovich dual.""" import warnings -from typing import Dict, Iterator, List, Optional, Tuple, Union +from typing import Dict, Iterable, List, Optional, Tuple, Union import flax.linen as nn import jax @@ -134,10 +134,10 @@ def setup( def __call__( self, - trainloader_source: Iterator[jnp.ndarray], - trainloader_target: Iterator[jnp.ndarray], - validloader_source: Iterator[jnp.ndarray], - validloader_target: Iterator[jnp.ndarray], + trainloader_source: Iterable[jnp.ndarray], + trainloader_target: Iterable[jnp.ndarray], + validloader_source: Iterable[jnp.ndarray], + validloader_target: Iterable[jnp.ndarray], ) -> Union[potentials.DualPotentials, Tuple[potentials.DualPotentials, Train_t]]: logs = self.train_neuraldual( @@ -152,10 +152,10 @@ def __call__( def train_neuraldual( self, - trainloader_source: Iterator[jnp.ndarray], - trainloader_target: Iterator[jnp.ndarray], - validloader_source: Iterator[jnp.ndarray], - validloader_target: Iterator[jnp.ndarray], + trainloader_source: Iterable[jnp.ndarray], + trainloader_target: Iterable[jnp.ndarray], + validloader_source: Iterable[jnp.ndarray], + validloader_target: Iterable[jnp.ndarray], ) -> Train_t: """Implementation of the training and validation script.""" # noqa: D401 try: diff --git a/ott/solvers/quadratic/gw_barycenter.py b/ott/solvers/quadratic/gw_barycenter.py index 8020f99e3..528f676c4 100644 --- a/ott/solvers/quadratic/gw_barycenter.py +++ b/ott/solvers/quadratic/gw_barycenter.py @@ -7,7 +7,7 @@ from ott.geometry import pointcloud from ott.math import fixed_point_loop from ott.problems.linear import linear_problem -from ott.problems.quadratic import barycenter_problem +from ott.problems.quadratic import gw_barycenter from ott.solvers.quadratic import gromov_wasserstein from ott.utils import was_solver @@ -91,7 +91,7 @@ def __init__( self._quad_solver = gromov_wasserstein.GromovWasserstein(**kwargs) def __call__( - self, problem: barycenter_problem.GWBarycenterProblem, bar_size: int, + self, problem: gw_barycenter.GWBarycenterProblem, bar_size: int, **kwargs: Any ) -> GWBarycenterState: """Solver the (fused) GW barycenter problem. @@ -111,7 +111,7 @@ def __call__( def init_state( self, - problem: barycenter_problem.GWBarycenterProblem, + problem: gw_barycenter.GWBarycenterProblem, bar_size: int, bar_init: Optional[Union[jnp.ndarray, Tuple[jnp.ndarray, jnp.ndarray]]] = None, @@ -186,7 +186,7 @@ def update_state( self, state: GWBarycenterState, iteration: int, - problem: barycenter_problem.GWBarycenterProblem, + problem: gw_barycenter.GWBarycenterProblem, store_errors: bool = True, ) -> Tuple[float, bool, jnp.ndarray, Optional[jnp.ndarray]]: @@ -283,8 +283,7 @@ def init_transports( def iterations( solver: GromovWassersteinBarycenter, - problem: barycenter_problem.GWBarycenterProblem, - init_state: GWBarycenterState + problem: gw_barycenter.GWBarycenterProblem, init_state: GWBarycenterState ) -> GWBarycenterState: def cond_fn( @@ -296,7 +295,7 @@ def cond_fn( def body_fn( iteration, constants: Tuple[GromovWassersteinBarycenter, - barycenter_problem.GWBarycenterProblem], + gw_barycenter.GWBarycenterProblem], state: GWBarycenterState, compute_error: bool ) -> GWBarycenterState: del compute_error # always assumed true diff --git a/ott/tools/gaussian_mixture/fit_gmm.py b/ott/tools/gaussian_mixture/fit_gmm.py index d09cf6288..112ac44d7 100644 --- a/ott/tools/gaussian_mixture/fit_gmm.py +++ b/ott/tools/gaussian_mixture/fit_gmm.py @@ -185,7 +185,7 @@ def fit_model_em( def _get_dist_sq(points: jnp.ndarray, loc: jnp.ndarray) -> jnp.ndarray: """Get the squared distance from each point to each loc.""" - def _dist_sq_one_loc(points: jnp.ndarray, loc: jnp.ndarray): + def _dist_sq_one_loc(points: jnp.ndarray, loc: jnp.ndarray) -> jnp.ndarray: return jnp.sum((points - loc[None]) ** 2., axis=-1) dist_sq_fn = jax.vmap(_dist_sq_one_loc, in_axes=(None, 0), out_axes=1) @@ -266,9 +266,9 @@ def initialize( key: jnp.ndarray, points: jnp.ndarray, point_weights: Optional[jnp.ndarray], - n_components: jnp.ndarray, - n_attempts=50, - verbose=False + n_components: int, + n_attempts: int = 50, + verbose: bool = False ) -> gaussian_mixture.GaussianMixture: """Initialize a GMM via K-means++ with retries on failure. @@ -289,7 +289,7 @@ def initialize( for attempt in range(n_attempts): key, subkey = jax.random.split(key) try: - gmm = from_kmeans_plusplus( + return from_kmeans_plusplus( key=subkey, points=points, point_weights=point_weights, @@ -297,6 +297,5 @@ def initialize( ) except ValueError: if verbose: - print(f'Failed to initialize, attempt {attempt}', flush=True) - return gmm - raise ValueError('Failed to initialize') + print(f'Failed to initialize, attempt {attempt}.', flush=True) + raise ValueError('Failed to initialize.') diff --git a/ott/tools/segment_sinkhorn.py b/ott/tools/segment_sinkhorn.py index 4a5427c6e..7f4afeb60 100644 --- a/ott/tools/segment_sinkhorn.py +++ b/ott/tools/segment_sinkhorn.py @@ -31,8 +31,8 @@ def segment_sinkhorn( segment_ids_x: Optional[jnp.ndarray] = None, segment_ids_y: Optional[jnp.ndarray] = None, indices_are_sorted: Optional[bool] = None, - num_per_segment_x: Tuple[int] = None, - num_per_segment_y: Tuple[int] = None, + num_per_segment_x: Optional[Tuple[int, ...]] = None, + num_per_segment_y: Optional[Tuple[int, ...]] = None, weights_x: Optional[jnp.ndarray] = None, weights_y: Optional[jnp.ndarray] = None, sinkhorn_kwargs: Mapping[str, Any] = MappingProxyType({}), diff --git a/ott/tools/sinkhorn_divergence.py b/ott/tools/sinkhorn_divergence.py index 46b196339..119f7be5d 100644 --- a/ott/tools/sinkhorn_divergence.py +++ b/ott/tools/sinkhorn_divergence.py @@ -192,8 +192,8 @@ def segment_sinkhorn_divergence( segment_ids_x: Optional[jnp.ndarray] = None, segment_ids_y: Optional[jnp.ndarray] = None, indices_are_sorted: Optional[bool] = None, - num_per_segment_x: Optional[jnp.ndarray] = None, - num_per_segment_y: Optional[jnp.ndarray] = None, + num_per_segment_x: Optional[Tuple[int, ...]] = None, + num_per_segment_y: Optional[Tuple[int, ...]] = None, weights_x: Optional[jnp.ndarray] = None, weights_y: Optional[jnp.ndarray] = None, sinkhorn_kwargs: Mapping[str, Any] = MappingProxyType({}), From da2c97abe592984a8d05271a727fe39bce7f32d0 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Wed, 16 Nov 2022 14:30:09 +0100 Subject: [PATCH 05/34] Fix tests --- tests/core/continuous_barycenter_test.py | 56 +++++++------ tests/core/discrete_barycenter_test.py | 2 +- tests/core/fused_gromov_wasserstein_test.py | 39 ++++----- tests/core/gromov_wasserstein_test.py | 13 +-- tests/core/icnn_test.py | 18 ++-- tests/core/initializers_test.py | 84 +++++++++---------- tests/core/neuraldual_test.py | 6 +- tests/core/potentials_test.py | 19 +++-- tests/core/sinkhorn_diff_test.py | 7 +- tests/core/sinkhorn_extra_test.py | 6 +- tests/core/sinkhorn_grid_test.py | 2 +- tests/core/sinkhorn_lr_test.py | 7 +- tests/core/sinkhorn_test.py | 5 +- tests/geometry/geometry_lse_test.py | 4 +- tests/geometry/graph_test.py | 11 +-- tests/geometry/matrix_square_root_test.py | 4 +- tests/geometry/scaling_cost_test.py | 5 +- .../tools/gaussian_mixture/scale_tril_test.py | 2 +- tests/tools/segment_sinkhorn_test.py | 2 +- tests/tools/sinkhorn_divergence_test.py | 2 +- tests/tools/transport_test.py | 4 +- 21 files changed, 154 insertions(+), 144 deletions(-) diff --git a/tests/core/continuous_barycenter_test.py b/tests/core/continuous_barycenter_test.py index 5c862a47f..6ff16784e 100644 --- a/tests/core/continuous_barycenter_test.py +++ b/tests/core/continuous_barycenter_test.py @@ -12,7 +12,7 @@ # limitations under the License. # Lint as: python3 -"""Tests for Continuous barycenters.""" +"""Tests for continuous barycenter.""" import functools from typing import Any, Optional, Sequence, Tuple @@ -21,9 +21,13 @@ import numpy as np import pytest -from ott.core import bar_problems, continuous_barycenter, gw_barycenter, segment from ott.geometry import costs, pointcloud +from ott.problems.linear import barycenter_problem +from ott.problems.quadratic import gw_barycenter as gwb +from ott.solvers.linear import continuous_barycenter as cb +from ott.solvers.quadratic import gw_barycenter as gwb_solver from ott.tools.gaussian_mixture import gaussian_mixture +from ott.utils import segment means_and_covs_to_x = jax.vmap(costs.mean_and_cov_to_x, in_axes=[0, 0, None]) @@ -70,7 +74,7 @@ def test_euclidean_barycenter( b.append(c / jnp.sum(c)) b = jnp.concatenate(b, axis=0) # Set a barycenter problem with 8 measures, of irregular sizes. - bar_prob = bar_problems.BarycenterProblem( + bar_prob = barycenter_problem.BarycenterProblem( y, b, epsilon=epsilon, @@ -84,9 +88,7 @@ def test_euclidean_barycenter( # Define solver threshold = 1e-3 - solver = continuous_barycenter.WassersteinBarycenter( - rank=rank, threshold=threshold, jit=jit - ) + solver = cb.WassersteinBarycenter(rank=rank, threshold=threshold, jit=jit) # Set barycenter size to 31. bar_size = 31 @@ -119,19 +121,21 @@ def test_barycenter_jit(self, rng: jnp.ndarray, segment_before: bool): @functools.partial(jax.jit, static_argnums=(2, 3)) def barycenter( - y: jnp.ndarray, b: jnp.ndarray, segment_before: bool, - num_per_segment: int - ) -> continuous_barycenter.BarycenterState: + y: jnp.ndarray, + b: jnp.ndarray, + segment_before: bool, + num_per_segment: Tuple[int, ...], + ) -> cb.BarycenterState: if segment_before: y, b = segment.segment_point_cloud( x=y, a=b, num_per_segment=num_per_segment ) - bar_prob = bar_problems.BarycenterProblem(y, b, epsilon=1e-1) + bar_prob = barycenter_problem.BarycenterProblem(y, b, epsilon=1e-1) else: - bar_prob = bar_problems.BarycenterProblem( + bar_prob = barycenter_problem.BarycenterProblem( y, b, epsilon=1e-1, num_per_segment=num_per_segment ) - solver = continuous_barycenter.WassersteinBarycenter(threshold=threshold) + solver = cb.WassersteinBarycenter(threshold=threshold) return solver(bar_prob) rngs = jax.random.split(rng, 20) @@ -220,7 +224,7 @@ def test_bures_barycenter( num_per_segment=(num_components, num_components), padding_vector=bures_cost.padder(y.shape[1]), ) - bar_p = bar_problems.BarycenterProblem( + bar_p = barycenter_problem.BarycenterProblem( seg_y, seg_b, weights=barycentric_weights, @@ -231,9 +235,7 @@ def test_bures_barycenter( assert bar_p.max_measure_size == seg_y.shape[1] assert bar_p.ndim == seg_y.shape[2] - solver = continuous_barycenter.WassersteinBarycenter( - lse_mode=lse_mode, jit=jit - ) + solver = cb.WassersteinBarycenter(lse_mode=lse_mode, jit=jit) out = solver(bar_p, bar_size=bar_size, x_init=x_init) barycenter = out.x @@ -318,17 +320,17 @@ def test_bures_barycenter_different_number_of_components( for i in range(num_measures)] # positions of mass of the measures - ys = jnp.vstack( + ys = jnp.vstack([ means_and_covs_to_x(means_covs[i][0], means_covs[i][1], dim) for i in range(num_measures) - ) + ]) # mass distribution of the measures weights = [ gmm_generators[i].component_weight_ob.probs() for i in range(num_measures) ] - bs = jnp.hstack(jnp.array(weights[i]) for i in range(num_measures)) + bs = jnp.hstack([jnp.array(weights[i]) for i in range(num_measures)]) # random initialization of the barycenter gmm_generator = gaussian_mixture.GaussianMixture.from_random( @@ -340,7 +342,7 @@ def test_bures_barycenter_different_number_of_components( # test second interface for segmentation seg_ids = jnp.repeat(jnp.arange(num_measures), n_components) - bar_p = bar_problems.BarycenterProblem( + bar_p = barycenter_problem.BarycenterProblem( y=ys, b=bs, weights=barycentric_weights, @@ -354,7 +356,7 @@ def test_bures_barycenter_different_number_of_components( assert bar_p.num_measures == num_measures assert bar_p.ndim == ys.shape[-1] - solver = continuous_barycenter.WassersteinBarycenter(lse_mode=True, jit=jit) + solver = cb.WassersteinBarycenter(lse_mode=True, jit=jit) # Compute the barycenter. out = solver(bar_p, bar_size=bar_size, x_init=x_init) @@ -439,8 +441,8 @@ def test_gw_barycenter( "epsilon": epsilon } - problem_pc = bar_problems.GWBarycenterProblem(y=ys, b=bs, **kwargs) - problem_cost = bar_problems.GWBarycenterProblem( + problem_pc = gwb.GWBarycenterProblem(y=ys, b=bs, **kwargs) + problem_cost = gwb.GWBarycenterProblem( costs=costs, b=cbs, **kwargs, @@ -454,7 +456,7 @@ def test_gw_barycenter( assert problem_pc.ndim == self.NDIM assert problem_cost.ndim is None - solver = gw_barycenter.GromovWassersteinBarycenter(jit=True) + solver = gwb_solver.GromovWassersteinBarycenter(jit=True) out_pc = solver(problem_pc, bar_size=bar_size) out_cost = solver(problem_cost, bar_size=bar_size) @@ -479,8 +481,8 @@ def test_fgw_barycenter( def barycenter( y: jnp.ndim, y_fused: jnp.ndarray, num_per_segment: Tuple[int, ...] - ) -> gw_barycenter.GWBarycenterState: - prob = bar_problems.GWBarycenterProblem( + ) -> gwb_solver.GWBarycenterState: + prob = gwb.GWBarycenterProblem( y=y, y_fused=y_fused, num_per_segment=num_per_segment, @@ -495,7 +497,7 @@ def barycenter( assert prob.ndim == self.NDIM assert prob.ndim_fused == self.NDIM_F - solver = gw_barycenter.GromovWassersteinBarycenter( + solver = gwb_solver.GromovWassersteinBarycenter( jit=False, store_inner_errors=True ) diff --git a/tests/core/discrete_barycenter_test.py b/tests/core/discrete_barycenter_test.py index c17d031d5..2b09ac991 100644 --- a/tests/core/discrete_barycenter_test.py +++ b/tests/core/discrete_barycenter_test.py @@ -18,8 +18,8 @@ import jax.numpy as jnp import pytest -from ott.core import discrete_barycenter as db from ott.geometry import grid, pointcloud +from ott.solvers.linear import discrete_barycenter as db class TestDiscreteBarycenter: diff --git a/tests/core/fused_gromov_wasserstein_test.py b/tests/core/fused_gromov_wasserstein_test.py index a2333c96e..ebd15e7de 100644 --- a/tests/core/fused_gromov_wasserstein_test.py +++ b/tests/core/fused_gromov_wasserstein_test.py @@ -21,8 +21,9 @@ import numpy as np import pytest -from ott.core import gromov_wasserstein, quad_problems from ott.geometry import geometry, low_rank, pointcloud +from ott.problems.quadratic import quadratic_problem +from ott.solvers.quadratic import gromov_wasserstein as gwb_solver class TestFusedGromovWasserstein: @@ -55,7 +56,7 @@ def test_flag_store_errors_fused(self): geom_x = pointcloud.PointCloud(self.x) geom_y = pointcloud.PointCloud(self.y) geom_xy = pointcloud.PointCloud(self.x_2, self.y_2) - out = gromov_wasserstein.gromov_wasserstein( + out = gwb_solver.gromov_wasserstein( geom_xx=geom_x, geom_yy=geom_y, geom_xy=geom_xy, @@ -66,7 +67,7 @@ def test_flag_store_errors_fused(self): ).errors assert out is None - out = gromov_wasserstein.gromov_wasserstein( + out = gwb_solver.gromov_wasserstein( geom_xx=geom_x, geom_yy=geom_y, geom_xy=geom_xy, @@ -86,7 +87,7 @@ def test_flag_store_errors_fused(self): assert out.ndim == 2 @pytest.mark.fast.with_args(jit=[False, True], only_fast=1) - def test_gradient_marginals_fused_gromov_wasserstein(self, jit: bool): + def test_gradient_marginals_fused_gwb_solver(self, jit: bool): """Test gradient w.r.t. probability weights.""" geom_x = pointcloud.PointCloud(self.x) geom_y = pointcloud.PointCloud(self.y) @@ -98,7 +99,7 @@ def reg_gw(a, b, implicit): 'implicit_differentiation': implicit, 'max_iterations': 1001 } - out = gromov_wasserstein.gromov_wasserstein( + out = gwb_solver.gromov_wasserstein( geom_x, geom_y, geom_xy=geom_xy, @@ -136,14 +137,14 @@ def reg_gw(a, b, implicit): ) @pytest.mark.fast.with_args(lse_mode=[False, True], only_fast=1) - def test_fused_gromov_wasserstein_pointcloud(self, lse_mode: bool): + def test_fused_gwb_solver_pointcloud(self, lse_mode: bool): """Test basic computations pointclouds.""" def reg_gw(x, y, x_2, y_2, fused_penalty, a, b): geom_x = pointcloud.PointCloud(x) geom_y = pointcloud.PointCloud(y) geom_xy = pointcloud.PointCloud(x_2, y_2) - return gromov_wasserstein.gromov_wasserstein( + return gwb_solver.gromov_wasserstein( geom_x, geom_y, geom_xy=geom_xy, @@ -163,7 +164,7 @@ def reg_gw(x, y, x_2, y_2, fused_penalty, a, b): assert cost is not None @pytest.mark.parametrize("lse_mode", [False, True]) - def test_gradient_fused_gromov_wasserstein_pointcloud(self, lse_mode: bool): + def test_gradient_fused_gwb_solver_pointcloud(self, lse_mode: bool): """Test gradient w.r.t. pointclouds.""" def reg_gw(x, y, x_2, y_2, fused_penalty, a, b, implicit): @@ -175,7 +176,7 @@ def reg_gw(x, y, x_2, y_2, fused_penalty, a, b, implicit): 'max_iterations': 1001, 'lse_mode': lse_mode } - return gromov_wasserstein.gromov_wasserstein( + return gwb_solver.gromov_wasserstein( geom_x, geom_y, geom_xy=geom_xy, @@ -206,7 +207,7 @@ def reg_gw(x, y, x_2, y_2, fused_penalty, a, b, implicit): ) @pytest.mark.parametrize("lse_mode", [False, True]) - def test_gradient_fused_gromov_wasserstein_geometry(self, lse_mode: bool): + def test_gradient_fused_gwb_solver_geometry(self, lse_mode: bool): """Test gradient w.r.t. cost matrices.""" def reg_gw(cx, cy, cxy, fused_penalty, a, b, implicit): @@ -218,7 +219,7 @@ def reg_gw(cx, cy, cxy, fused_penalty, a, b, implicit): 'max_iterations': 1001, 'lse_mode': lse_mode } - return gromov_wasserstein.gromov_wasserstein( + return gwb_solver.gromov_wasserstein( geom_x, geom_y, geom_xy=geom_xy, @@ -259,7 +260,7 @@ def test_adaptive_threshold_fused(self): # without warm start for calls to sinkhorn def loss_thre(threshold: float) -> float: - return gromov_wasserstein.gromov_wasserstein( + return gwb_solver.gromov_wasserstein( geom_xx=geom_x, geom_yy=geom_y, geom_xy=geom_xy, @@ -274,7 +275,7 @@ def loss_thre(threshold: float) -> float: assert loss_thre(1e-3) > loss_thre(1e-5) @pytest.mark.parametrize("lse_mode", [False, True]) - def test_gradient_fused_gromov_wasserstein_penalty(self, lse_mode: bool): + def test_gradient_fused_gwb_solver_penalty(self, lse_mode: bool): """Test gradient w.r.t. penalty.""" def reg_gw(cx, cy, cxy, fused_penalty, a, b, implicit): @@ -286,7 +287,7 @@ def reg_gw(cx, cy, cxy, fused_penalty, a, b, implicit): 'max_iterations': 1001, 'lse_mode': lse_mode } - return gromov_wasserstein.gromov_wasserstein( + return gwb_solver.gromov_wasserstein( geom_x, geom_y, geom_xy=geom_xy, @@ -318,7 +319,7 @@ def reg_fgw(x, y, x_2, y_2, fused_penalty, a, b): geom_y = pointcloud.PointCloud(y) geom_xy = pointcloud.PointCloud(x_2, y_2) sinkhorn_kwargs = {'max_iterations': 1001} - return gromov_wasserstein.gromov_wasserstein( + return gwb_solver.gromov_wasserstein( geom_x, geom_y, geom_xy=geom_xy, @@ -333,7 +334,7 @@ def reg_gw(x, y, a, b): geom_x = pointcloud.PointCloud(x) geom_y = pointcloud.PointCloud(y) sinkhorn_kwargs = {'max_iterations': 1001} - return gromov_wasserstein.gromov_wasserstein( + return gwb_solver.gromov_wasserstein( geom_x, geom_y, a=a, @@ -366,7 +367,7 @@ def test_fgw_lr_memory(self, rng: jnp.ndarray, jit: bool): geom_y = pointcloud.PointCloud(y) geom_xy = pointcloud.PointCloud(xx, yy) - ot_gwlr = gromov_wasserstein.gromov_wasserstein( + ot_gwlr = gwb_solver.gromov_wasserstein( geom_x, geom_y, geom_xy, rank=5, jit=jit ) res0 = ot_gwlr.apply(x.T, axis=0) @@ -391,14 +392,14 @@ def test_fgw_lr_generic_cost_matrix( geom_y = geometry.Geometry(cost_matrix=y @ y.T) geom_xy = geometry.Geometry(cost_matrix=xx @ yy.T) - problem = quad_problems.QuadraticProblem( + problem = quadratic_problem.QuadraticProblem( geom_x, geom_y, geom_xy, ranks=cost_rank, tolerances=5e-1 ) assert problem._is_low_rank_convertible lr_prob = problem.to_low_rank() assert lr_prob.is_low_rank - solver = gromov_wasserstein.GromovWasserstein(rank=5, epsilon=1) + solver = gwb_solver.GromovWasserstein(rank=5, epsilon=1) out = solver(problem) assert solver.rank == 5 diff --git a/tests/core/gromov_wasserstein_test.py b/tests/core/gromov_wasserstein_test.py index 0ded70d48..27e7f58b8 100644 --- a/tests/core/gromov_wasserstein_test.py +++ b/tests/core/gromov_wasserstein_test.py @@ -21,8 +21,9 @@ import numpy as np import pytest -from ott.core import gromov_wasserstein, quad_problems from ott.geometry import geometry, low_rank, pointcloud +from ott.problems.quadratic import quadratic_problem +from ott.solvers.quadratic import gromov_wasserstein @pytest.mark.fast @@ -48,7 +49,9 @@ def test_quad_to_low_rank( geom_yy = geometry.Geometry(geom_yy.cost_matrix) geom_xy = geometry.Geometry(geom_xy.cost_matrix) - prob = quad_problems.QuadraticProblem(geom_xx, geom_yy, geom_xy, ranks=rank) + prob = quadratic_problem.QuadraticProblem( + geom_xx, geom_yy, geom_xy, ranks=rank + ) assert not prob.is_low_rank # point clouds are always converted, if possible @@ -94,7 +97,7 @@ def test_implicit_conversion_mixed_input(self, rng: jnp.ndarray): geom_xx = pointcloud.PointCloud(x) geom_yy = pointcloud.PointCloud(y).to_LRCGeometry() - prob = quad_problems.QuadraticProblem(geom_xx, geom_yy, ranks=-1) + prob = quadratic_problem.QuadraticProblem(geom_xx, geom_yy, ranks=-1) lr_prob = prob.to_low_rank() assert prob._is_low_rank_convertible @@ -325,7 +328,7 @@ def test_gw_lr(self, rng: jnp.ndarray): geom_xx = pointcloud.PointCloud(x) geom_yy = pointcloud.PointCloud(y) - prob = quad_problems.QuadraticProblem(geom_xx, geom_yy, a=a, b=b) + prob = quadratic_problem.QuadraticProblem(geom_xx, geom_yy, a=a, b=b) solver = gromov_wasserstein.GromovWasserstein(rank=5, epsilon=0.2) ot_gwlr = solver(prob) solver = gromov_wasserstein.GromovWasserstein(epsilon=0.2) @@ -347,7 +350,7 @@ def test_gw_lr_matches_fused(self, rng: jnp.ndarray): geom_xx = pointcloud.PointCloud(x) geom_yy = pointcloud.PointCloud(y) geom_xy = pointcloud.PointCloud(x, z) # only used to compute n x m matrix - prob = quad_problems.QuadraticProblem( + prob = quadratic_problem.QuadraticProblem( geom_xx, geom_yy, geom_xy=geom_xy, fused_penalty=1.3, a=a, b=b ) solver = gromov_wasserstein.GromovWasserstein(rank=6) diff --git a/tests/core/icnn_test.py b/tests/core/icnn_test.py index 43b5be23c..b45d2c521 100644 --- a/tests/core/icnn_test.py +++ b/tests/core/icnn_test.py @@ -20,7 +20,7 @@ import numpy as np import pytest -from ott.core.icnn import ICNN +from ott.solvers.nn import icnn @pytest.mark.fast @@ -32,22 +32,22 @@ def test_icnn_convexity(self, rng: jnp.ndarray): dim_hidden = (64, 64) # define icnn model - icnn = ICNN(dim_hidden) + model = icnn.ICNN(dim_hidden) # initialize model key1, key2, key3 = jax.random.split(rng, 3) - params = icnn.init(key1, jnp.ones(n_features))['params'] + params = model.init(key1, jnp.ones(n_features))['params'] # check convexity x = jax.random.normal(key1, (n_samples, n_features)) * 0.1 y = jax.random.normal(key2, (n_samples, n_features)) - out_x = icnn.apply({'params': params}, x) - out_y = icnn.apply({'params': params}, y) + out_x = model.apply({'params': params}, x) + out_y = model.apply({'params': params}, y) out = list() for t in jnp.linspace(0, 1): - out_xy = icnn.apply({'params': params}, t * x + (1 - t) * y) + out_xy = model.apply({'params': params}, t * x + (1 - t) * y) out.append((t * out_x + (1 - t) * out_y) - out_xy) np.testing.assert_array_equal(jnp.asarray(out) >= 0, True) @@ -58,17 +58,17 @@ def test_icnn_hessian(self, rng: jnp.ndarray): # define icnn model n_samples = 2 dim_hidden = (64, 64) - icnn = ICNN(dim_hidden) + model = icnn.ICNN(dim_hidden) # initialize model key1, key2 = jax.random.split(rng) - params = icnn.init(key1, jnp.ones(n_samples))['params'] + params = model.init(key1, jnp.ones(n_samples))['params'] # check if Hessian is positive-semidefinite via eigenvalues data = jax.random.normal(key2, (n_samples,)) # compute Hessian - hessian = jax.jacfwd(jax.jacrev(icnn.apply, argnums=1), argnums=1) + hessian = jax.jacfwd(jax.jacrev(model.apply, argnums=1), argnums=1) icnn_hess = hessian({'params': params}, data) # compute eigenvalues diff --git a/tests/core/initializers_test.py b/tests/core/initializers_test.py index 01df6ad6d..47cdc10e4 100644 --- a/tests/core/initializers_test.py +++ b/tests/core/initializers_test.py @@ -19,17 +19,15 @@ import numpy as np import pytest -from ott.core import gromov_wasserstein -from ott.core import initializers as init_lib -from ott.core import ( - initializers_lr, - linear_problems, - quad_initializers, - quad_problems, - sinkhorn, - sinkhorn_lr, -) +import ott.initializers.nn.initializers from ott.geometry import geometry, low_rank, pointcloud +from ott.initializers.linear import initializers as lin_init +from ott.initializers.linear import initializers_lr +from ott.initializers.quadratic import initializers as quad_init +from ott.problems.linear import linear_problem +from ott.problems.quadratic import quadratic_problem +from ott.solvers.linear import sinkhorn, sinkhorn_lr +from ott.solvers.quadratic import gromov_wasserstein def create_sorting_problem(rng, n, epsilon=0.01, online=False): @@ -56,7 +54,7 @@ def create_sorting_problem(rng, n, epsilon=0.01, online=False): epsilon=epsilon, batch_size=batch_size ) - ot_problem = linear_problems.LinearProblem(geom=geom, a=a, b=b) + ot_problem = linear_problem.LinearProblem(geom=geom, a=a, b=b) return ot_problem @@ -77,7 +75,7 @@ def create_ot_problem(rng, n, m, d, epsilon=0.01, online=False): batch_size = 3 if online else None geom = pointcloud.PointCloud(x, y, epsilon=epsilon, batch_size=batch_size) - ot_problem = linear_problems.LinearProblem(geom=geom, a=a, b=b) + ot_problem = linear_problem.LinearProblem(geom=geom, a=a, b=b) return ot_problem @@ -87,7 +85,7 @@ def run_sinkhorn_sort_init( x, y, a=None, b=None, epsilon=0.01, vector_min=True, lse_mode=True ): geom = pointcloud.PointCloud(x, y, epsilon=epsilon) - sort_init = init_lib.SortingInitializer(vectorized_update=vector_min) + sort_init = lin_init.SortingInitializer(vectorized_update=vector_min) out = sinkhorn.sinkhorn( geom, a=a, b=b, jit=True, initializer=sort_init, lse_mode=lse_mode ) @@ -109,7 +107,7 @@ def run_sinkhorn_gaus_init(x, y, a=None, b=None, epsilon=0.01, lse_mode=True): a=a, b=b, jit=True, - initializer=init_lib.GaussianInitializer(), + initializer=lin_init.GaussianInitializer(), lse_mode=lse_mode ) return out @@ -122,12 +120,12 @@ def test_init_pytree(self): @jax.jit def init_sort(): - init = init_lib.SortingInitializer() + init = lin_init.SortingInitializer() return init @jax.jit def init_gaus(): - init = init_lib.GaussianInitializer() + init = lin_init.GaussianInitializer() return init _ = init_gaus() @@ -136,18 +134,18 @@ def init_gaus(): @pytest.mark.parametrize( "init", [ "default", "gaussian", "sorting", - init_lib.DefaultInitializer(), "non-existent" + lin_init.DefaultInitializer(), "non-existent" ] ) def test_create_initializer(self, init: str): solver = sinkhorn.Sinkhorn(initializer=init) expected_types = { - "default": init_lib.DefaultInitializer, - "gaussian": init_lib.GaussianInitializer, - "sorting": init_lib.SortingInitializer, + "default": lin_init.DefaultInitializer, + "gaussian": lin_init.GaussianInitializer, + "sorting": lin_init.SortingInitializer, } - if isinstance(init, init_lib.SinkhornInitializer): + if isinstance(init, lin_init.SinkhornInitializer): assert solver.create_initializer() is init elif init == "non-existent": with pytest.raises(NotImplementedError, match=r""): @@ -201,7 +199,7 @@ def test_sorting_init_online(self, rng: jnp.ndarray): ot_problem = create_sorting_problem( rng=rng, n=n, epsilon=epsilon, online=True ) - sort_init = init_lib.SortingInitializer(vectorized_update=True) + sort_init = lin_init.SortingInitializer(vectorized_update=True) with pytest.raises(AssertionError, match=r"online"): sort_init.init_dual_a(ot_problem, lse_mode=True) @@ -212,7 +210,7 @@ def test_sorting_init_square_cost(self, rng: jnp.ndarray): epsilon = 0.01 ot_problem = create_ot_problem(rng, n, m, d, epsilon=epsilon, online=False) - sort_init = init_lib.SortingInitializer(vectorized_update=True) + sort_init = lin_init.SortingInitializer(vectorized_update=True) with pytest.raises(AssertionError, match=r"square"): sort_init.init_dual_a(ot_problem, lse_mode=True) @@ -225,10 +223,10 @@ def test_default_initializer(self, rng: jnp.ndarray): ot_problem = create_ot_problem(rng, n, m, d, epsilon=epsilon, online=False) - default_potential_a = init_lib.DefaultInitializer().init_dual_a( + default_potential_a = lin_init.DefaultInitializer().init_dual_a( ot_problem, lse_mode=True ) - default_potential_b = init_lib.DefaultInitializer().init_dual_b( + default_potential_b = lin_init.DefaultInitializer().init_dual_b( ot_problem, lse_mode=True ) @@ -244,11 +242,11 @@ def test_gauss_pointcloud_geom(self, rng: jnp.ndarray): ot_problem = create_ot_problem(rng, n, m, d, epsilon=epsilon, online=False) - gaus_init = init_lib.GaussianInitializer() + gaus_init = lin_init.GaussianInitializer() new_geom = geometry.Geometry( cost_matrix=ot_problem.geom.cost_matrix, epsilon=epsilon ) - ot_problem = linear_problems.LinearProblem( + ot_problem = linear_problem.LinearProblem( geom=new_geom, a=ot_problem.a, b=ot_problem.b ) @@ -316,7 +314,7 @@ def test_meta_initializer(self, lse_mode, rng: jnp.ndarray): base_num_iter = jnp.sum(sink_out.errors > -1) # Overfit the initializer to the problem. - meta_initializer = init_lib.MetaInitializer(geom) + meta_initializer = ott.initializers.nn.initializers.MetaInitializer(geom) for _ in range(100): _, _, meta_initializer.state = meta_initializer.update( meta_initializer.state, a=a, b=b @@ -354,7 +352,7 @@ def test_create_default_initializer(self, rng: jnp.ndarray, kind: str): geom = geometry.Geometry(geom.cost_matrix) else: raise NotImplementedError(geom) - prob = linear_problems.LinearProblem(geom) + prob = linear_problem.LinearProblem(geom) solver = sinkhorn_lr.LRSinkhorn(rank=rank, initializer=None) initializer = solver.create_initializer(prob) @@ -389,7 +387,7 @@ def test_partial_initialization( key1, key2, key3, key4 = jax.random.split(rng, 4) x = jax.random.normal(key1, (n, d)) pc = pointcloud.PointCloud(x, epsilon=5e-1) - prob = linear_problems.LinearProblem(pc) + prob = linear_problem.LinearProblem(pc) q_init = jax.random.normal(key2, (n, rank)) r_init = jax.random.normal(key2, (n, rank)) g_init = jax.random.normal(key2, (rank,)) @@ -416,7 +414,7 @@ def test_generalized_k_means_has_correct_rank( n, d = 100, 10 x = jax.random.normal(rng, (n, d)) pc = pointcloud.PointCloud(x, epsilon=5e-1) - prob = linear_problems.LinearProblem(pc) + prob = linear_problem.LinearProblem(pc) solver = sinkhorn_lr.LRSinkhorn( rank=rank, initializer="generalized-k-means" @@ -437,8 +435,8 @@ def test_generalized_k_means_matches_k_means(self, rng: jnp.ndarray): pc = pointcloud.PointCloud(x, y, epsilon=eps) geom = geometry.Geometry(cost_matrix=pc.cost_matrix, epsilon=eps) - pc_problem = linear_problems.LinearProblem(pc) - geom_problem = linear_problems.LinearProblem(geom) + pc_problem = linear_problem.LinearProblem(pc) + geom_problem = linear_problem.LinearProblem(geom) solver = sinkhorn_lr.LRSinkhorn( rank=rank, initializer="k-means", max_iterations=5000 @@ -464,7 +462,7 @@ def test_better_initialization_helps(self, rng: jnp.ndarray, epsilon: float): x = jax.random.normal(key1, (n, d)) y = jax.random.normal(key2, (n, d)) pc = pointcloud.PointCloud(x, y, epsilon=5e-1) - prob = linear_problems.LinearProblem(pc) + prob = linear_problem.LinearProblem(pc) solver_random = sinkhorn_lr.LRSinkhorn( rank=rank, epsilon=epsilon, initializer="random", max_iterations=10000 @@ -507,14 +505,14 @@ def test_create_default_lr_initializer(self, rng: jnp.ndarray, kind: str): geom_y = geometry.Geometry(geom_y.cost_matrix, epsilon=eps) else: raise NotImplementedError(kind) - prob = quad_problems.QuadraticProblem(geom_x, geom_y) + prob = quadratic_problem.QuadraticProblem(geom_x, geom_y) solver = gromov_wasserstein.GromovWasserstein( rank=rank, quad_initializer=None, kwargs_init=kwargs_init ) initializer = solver.create_initializer(prob) - assert isinstance(initializer, quad_initializers.LRQuadraticInitializer) + assert isinstance(initializer, quad_init.LRQuadraticInitializer) assert initializer.rank == rank linear_init = initializer._linear_lr_initializer if kind in ("pc", "lrc"): @@ -528,24 +526,24 @@ def test_non_lr_initializer(self): rank=-1, quad_initializer="not used" ) initializer = solver.create_initializer(prob="not used") - assert isinstance(initializer, quad_initializers.QuadraticInitializer) + assert isinstance(initializer, quad_init.QuadraticInitializer) @pytest.mark.parametrize("rank", [-1, 2]) def test_explicitly_passing_initializer(self, rank: int): if rank == -1: - linear_init = init_lib.SortingInitializer() - quad_init = quad_initializers.QuadraticInitializer() + linear_init = lin_init.SortingInitializer() + q_init = quad_init.QuadraticInitializer() else: linear_init = initializers_lr.Rank2Initializer(rank) - quad_init = quad_initializers.LRQuadraticInitializer(linear_init) + q_init = quad_init.LRQuadraticInitializer(linear_init) solver = gromov_wasserstein.GromovWasserstein( initializer=linear_init, - quad_initializer=quad_init, + quad_initializer=q_init, ) assert solver.linear_ot_solver.initializer is linear_init - assert solver.quad_initializer is quad_init + assert solver.quad_initializer is q_init if solver.is_low_rank: assert solver.quad_initializer.rank == rank @@ -564,7 +562,7 @@ def test_gw_better_initialization_helps(self, rng: jnp.ndarray, eps: float): jax.random.normal(key4, (m, d2)), epsilon=eps, ) - problem = quad_problems.QuadraticProblem(geom_x, geom_y) + problem = quadratic_problem.QuadraticProblem(geom_x, geom_y) solver_random = gromov_wasserstein.GromovWasserstein( rank=rank, initializer="random", diff --git a/tests/core/neuraldual_test.py b/tests/core/neuraldual_test.py index 289fc59b5..4fa9c009b 100644 --- a/tests/core/neuraldual_test.py +++ b/tests/core/neuraldual_test.py @@ -21,7 +21,7 @@ import pytest from typing_extensions import Literal -from ott.core.neuraldual import NeuralDualSolver +from ott.solvers.nn import neuraldual class ToyDataset: @@ -94,7 +94,7 @@ def decreasing(losses: Sequence[float]) -> bool: dataloader_source, dataloader_target = toy_dataset # initialize neural dual - neural_dual_solver = NeuralDualSolver( + neural_dual_solver = neuraldual.NeuralDualSolver( input_dim=2, num_train_iters=num_train_iters, logging=True, @@ -113,7 +113,7 @@ def test_neural_dual_jit(self, toy_dataset: Tuple[ToyDataset, ToyDataset]): num_train_iters = 10 dataloader_source, dataloader_target = toy_dataset # initialize neural dual - neural_dual_solver = NeuralDualSolver( + neural_dual_solver = neuraldual.NeuralDualSolver( input_dim=2, num_train_iters=num_train_iters ) neural_dual = neural_dual_solver( diff --git a/tests/core/potentials_test.py b/tests/core/potentials_test.py index a381bf5d8..00476a72a 100644 --- a/tests/core/potentials_test.py +++ b/tests/core/potentials_test.py @@ -3,8 +3,9 @@ import numpy as np import pytest -from ott.core import Sinkhorn, linear_problems from ott.geometry import pointcloud +from ott.problems.linear import linear_problem +from ott.solvers.linear import sinkhorn from ott.tools import sinkhorn_divergence from ott.tools.gaussian_mixture import gaussian @@ -24,8 +25,8 @@ def test_entropic_potentials_dist(self, rng: jnp.ndarray, eps: float): y = g2.sample(key2, n2) geom = pointcloud.PointCloud(x, y, epsilon=eps) - prob = linear_problems.LinearProblem(geom) - out = Sinkhorn()(prob) + prob = linear_problem.LinearProblem(geom) + out = sinkhorn.Sinkhorn()(prob) assert out.converged potentials = out.to_dual_potentials() @@ -50,8 +51,8 @@ def test_entropic_potentials_displacement( y = g2.sample(key2, n2) geom = pointcloud.PointCloud(x, y, epsilon=eps) - prob = linear_problems.LinearProblem(geom) - out = Sinkhorn()(prob) + prob = linear_problem.LinearProblem(geom) + out = sinkhorn.Sinkhorn()(prob) assert out.converged potentials = out.to_dual_potentials() @@ -75,11 +76,11 @@ def test_distance_differentiability(self, rng: jnp.ndarray, jit: bool): x = jax.random.normal(key1, (n, d)) y = jax.random.normal(key2, (m, d)) - prob = linear_problems.LinearProblem(pointcloud.PointCloud(x, y)) + prob = linear_problem.LinearProblem(pointcloud.PointCloud(x, y)) v_x = jax.random.normal(key3, shape=x.shape) v_x = (v_x / jnp.linalg.norm(v_x, axis=-1, keepdims=True)) * 1e-3 - pots = Sinkhorn()(prob).to_dual_potentials() + pots = sinkhorn.Sinkhorn()(prob).to_dual_potentials() grad_dist = jax.grad(pots.distance) if jit: @@ -103,9 +104,9 @@ def test_potentials_sinkhorn_divergence( y = jax.random.normal(key2, (m, d)) + mu1 x_test = jax.random.normal(key3, (n, d)) + mu0 geom = pointcloud.PointCloud(x, y, epsilon=eps) - prob = linear_problems.LinearProblem(geom) + prob = linear_problem.LinearProblem(geom) - sink_pots = Sinkhorn()(prob).to_dual_potentials() + sink_pots = sinkhorn.Sinkhorn()(prob).to_dual_potentials() div_pots = sinkhorn_divergence.sinkhorn_divergence( type(geom), x, y, epsilon=eps ).to_dual_potentials() diff --git a/tests/core/sinkhorn_diff_test.py b/tests/core/sinkhorn_diff_test.py index 7e60a8309..c55711dc7 100644 --- a/tests/core/sinkhorn_diff_test.py +++ b/tests/core/sinkhorn_diff_test.py @@ -22,9 +22,10 @@ import numpy as np import pytest -from ott.core import implicit_differentiation as implicit_lib -from ott.core import linear_problems, sinkhorn from ott.geometry import costs, geometry, grid, pointcloud +from ott.math import implicit_differentiation as implicit_lib +from ott.problems.linear import linear_problem +from ott.solvers.linear import sinkhorn from ott.tools import transport @@ -765,7 +766,7 @@ def test_hessian_sinkhorn( def loss(a: jnp.ndarray, x: jnp.ndarray, implicit: bool = True): geom = pointcloud.PointCloud(x, y, epsilon=epsilon) - prob = linear_problems.LinearProblem(geom, a, b, tau_a, tau_b) + prob = linear_problem.LinearProblem(geom, a, b, tau_a, tau_b) implicit_diff = ( None if not implicit else implicit_lib.ImplicitDiff(ridge_kernel=ridge, ridge_identity=ridge) diff --git a/tests/core/sinkhorn_extra_test.py b/tests/core/sinkhorn_extra_test.py index 6bdc0b258..2f64a4c4a 100644 --- a/tests/core/sinkhorn_extra_test.py +++ b/tests/core/sinkhorn_extra_test.py @@ -15,7 +15,7 @@ # Lint as: python3 """Tests Anderson acceleration for sinkhorn.""" import functools -from typing import Any, Callable, Tuple +from typing import Callable, Tuple import chex import jax @@ -23,8 +23,8 @@ import numpy as np import pytest -from ott.core import sinkhorn from ott.geometry import costs, geometry, pointcloud +from ott.solvers.linear import sinkhorn non_jitted_sinkhorn = functools.partial(sinkhorn.sinkhorn, jit=False) @@ -330,7 +330,7 @@ def f( def test_jit_vs_non_jit_bwd(self, implicit: bool): def loss( - a: jnp.ndarray, x: jnp.ndarray, fun: Callable[[Any], + a: jnp.ndarray, x: jnp.ndarray, fun: Callable[..., sinkhorn.SinkhornOutput] ): out = fun( diff --git a/tests/core/sinkhorn_grid_test.py b/tests/core/sinkhorn_grid_test.py index 0c4180561..7937ce717 100644 --- a/tests/core/sinkhorn_grid_test.py +++ b/tests/core/sinkhorn_grid_test.py @@ -20,8 +20,8 @@ import numpy as np import pytest -from ott.core import sinkhorn from ott.geometry import grid, pointcloud +from ott.solvers.linear import sinkhorn class TestSinkhornGrid: diff --git a/tests/core/sinkhorn_lr_test.py b/tests/core/sinkhorn_lr_test.py index 3782b6e0b..afb6961e4 100644 --- a/tests/core/sinkhorn_lr_test.py +++ b/tests/core/sinkhorn_lr_test.py @@ -19,8 +19,9 @@ import numpy as np import pytest -from ott.core import linear_problems, sinkhorn_lr from ott.geometry import low_rank, pointcloud +from ott.problems.linear import linear_problem +from ott.solvers.linear import sinkhorn_lr class TestLRSinkhorn: @@ -58,7 +59,7 @@ def test_euclidean_point_cloud_lr( if use_lrcgeom: geom = geom.to_LRCGeometry() assert isinstance(geom, low_rank.LRCGeometry) - ot_prob = linear_problems.LinearProblem(geom, self.a, self.b) + ot_prob = linear_problem.LinearProblem(geom, self.a, self.b) # Start with a low rank parameter solver = sinkhorn_lr.LRSinkhorn( @@ -131,7 +132,7 @@ def test_output_apply_batch_size(self, axis: int): data = self.a if axis == 0 else self.b geom = pointcloud.PointCloud(self.x, self.y) - ot_prob = linear_problems.LinearProblem(geom, self.a, self.b) + ot_prob = linear_problem.LinearProblem(geom, self.a, self.b) solver = sinkhorn_lr.LRSinkhorn( threshold=threshold, rank=10, diff --git a/tests/core/sinkhorn_test.py b/tests/core/sinkhorn_test.py index ae3d04255..fba7a243e 100644 --- a/tests/core/sinkhorn_test.py +++ b/tests/core/sinkhorn_test.py @@ -20,8 +20,9 @@ import numpy as np import pytest -from ott.core import linear_problems, sinkhorn from ott.geometry import costs, geometry, pointcloud +from ott.problems.linear import linear_problem +from ott.solvers.linear import sinkhorn class TestSinkhorn: @@ -455,7 +456,7 @@ def test_sinkhorn_online_memory(self, batch_size: int): x = jax.random.uniform(rngs[0], (n, 2)) y = jax.random.uniform(rngs[1], (m, 2)) geom = pointcloud.PointCloud(x, y, batch_size=batch_size, epsilon=1) - problem = linear_problems.LinearProblem(geom) + problem = linear_problem.LinearProblem(geom) solver = sinkhorn.Sinkhorn() out = solver(problem) diff --git a/tests/geometry/geometry_lse_test.py b/tests/geometry/geometry_lse_test.py index d7a030107..f0b076147 100644 --- a/tests/geometry/geometry_lse_test.py +++ b/tests/geometry/geometry_lse_test.py @@ -20,7 +20,7 @@ import numpy as np import pytest -from ott.geometry import ops +from ott.math import utils as mu @pytest.mark.fast @@ -36,7 +36,7 @@ def test_lse(self, rng: jnp.ndarray): b_1 = jax.random.normal(keys[2], (n, 1)) def lse_(x, axis, b, return_sign): - out = ops.logsumexp(x, axis, False, b, return_sign) + out = mu.logsumexp(x, axis, False, b, return_sign) return jnp.sum(out[0] if return_sign else out) lse = jax.value_and_grad(lse_, argnums=(0, 2)) diff --git a/tests/geometry/graph_test.py b/tests/geometry/graph_test.py index 3bf976e9f..97b245a59 100644 --- a/tests/geometry/graph_test.py +++ b/tests/geometry/graph_test.py @@ -11,10 +11,11 @@ from networkx.generators import balanced_tree, random_graphs from typing_extensions import Literal -from ott.core import decomposition -from ott.core import implicit_differentiation as implicit_lib -from ott.core import linear_problems, sinkhorn from ott.geometry import geometry, graph +from ott.math import decomposition +from ott.math import implicit_differentiation as implicit_lib +from ott.problems.linear import linear_problem +from ott.solvers.linear import sinkhorn # we mix both dense/sparse tests sksparse = pytest.importorskip("sksparse") @@ -373,7 +374,7 @@ def test_graph_sinkhorn( def callback(geom: geometry.Geometry) -> sinkhorn.SinkhornOutput: solver = sinkhorn.Sinkhorn(lse_mode=False) - problem = linear_problems.LinearProblem(geom) + problem = linear_problem.LinearProblem(geom) return solver(problem) n, eps, tol = 11, 1e-5, 1e-3 @@ -422,7 +423,7 @@ def callback( geom = graph.Graph(G, t=1.) solver = sinkhorn.Sinkhorn(lse_mode=False, **kwargs) - problem = linear_problems.LinearProblem(geom) + problem = linear_problem.LinearProblem(geom) return solver(problem).reg_ot_cost diff --git a/tests/geometry/matrix_square_root_test.py b/tests/geometry/matrix_square_root_test.py index 0b48fb706..b5d3c08d8 100644 --- a/tests/geometry/matrix_square_root_test.py +++ b/tests/geometry/matrix_square_root_test.py @@ -21,7 +21,7 @@ import numpy as np import pytest -from ott.geometry import matrix_square_root +from ott.math import matrix_square_root def _get_random_spd_matrix(dim: int, key: jnp.ndarray): @@ -56,7 +56,7 @@ def _get_test_fn( unit = jax.random.normal(key=subkey3, shape=(dim, dim)) unit /= jnp.sqrt(jnp.sum(unit ** 2.)) - def _test_fn(x: float) -> float: + def _test_fn(x: jnp.ndarray) -> jnp.ndarray: # m is the product of 2 symmetric, positive definite matrices # so it will be positive definite but not necessarily symmetric m = jnp.matmul(m0, m1 + x * dx) diff --git a/tests/geometry/scaling_cost_test.py b/tests/geometry/scaling_cost_test.py index 2767c1ea5..8a6752608 100644 --- a/tests/geometry/scaling_cost_test.py +++ b/tests/geometry/scaling_cost_test.py @@ -19,8 +19,9 @@ import numpy as np import pytest -from ott.core import linear_problems, sinkhorn, sinkhorn_lr from ott.geometry import geometry, low_rank, pointcloud +from ott.problems.linear import linear_problem +from ott.solvers.linear import sinkhorn, sinkhorn_lr class TestScaleCost: @@ -154,7 +155,7 @@ def test_scale_cost_low_rank(self, scale: Union[str, float]): def apply_sinkhorn(cost1, cost2, scale_cost): geom = low_rank.LRCGeometry(cost1, cost2, scale_cost=scale_cost) - ot_prob = linear_problems.LinearProblem(geom, self.a, self.b) + ot_prob = linear_problem.LinearProblem(geom, self.a, self.b) solver = sinkhorn_lr.LRSinkhorn(rank=5, threshold=1e-3) out = solver(ot_prob) return geom, out diff --git a/tests/tools/gaussian_mixture/scale_tril_test.py b/tests/tools/gaussian_mixture/scale_tril_test.py index ef37cff8f..32f2f93f3 100644 --- a/tests/tools/gaussian_mixture/scale_tril_test.py +++ b/tests/tools/gaussian_mixture/scale_tril_test.py @@ -18,7 +18,7 @@ import numpy as np import pytest -from ott.geometry import matrix_square_root +from ott.math import matrix_square_root from ott.tools.gaussian_mixture import scale_tril diff --git a/tests/tools/segment_sinkhorn_test.py b/tests/tools/segment_sinkhorn_test.py index e7269d2da..5a1e81c1e 100644 --- a/tests/tools/segment_sinkhorn_test.py +++ b/tests/tools/segment_sinkhorn_test.py @@ -20,8 +20,8 @@ import numpy as np import pytest -from ott.core import sinkhorn from ott.geometry import costs, pointcloud +from ott.solvers.linear import sinkhorn from ott.tools import segment_sinkhorn from ott.tools.gaussian_mixture import gaussian_mixture diff --git a/tests/tools/sinkhorn_divergence_test.py b/tests/tools/sinkhorn_divergence_test.py index 152002638..2d5d63d5d 100644 --- a/tests/tools/sinkhorn_divergence_test.py +++ b/tests/tools/sinkhorn_divergence_test.py @@ -21,8 +21,8 @@ import numpy as np import pytest -from ott.core import sinkhorn from ott.geometry import costs, geometry, pointcloud +from ott.solvers.linear import sinkhorn from ott.tools import sinkhorn_divergence from ott.tools.gaussian_mixture import gaussian_mixture diff --git a/tests/tools/transport_test.py b/tests/tools/transport_test.py index 44c157261..4954c85bd 100644 --- a/tests/tools/transport_test.py +++ b/tests/tools/transport_test.py @@ -18,8 +18,8 @@ import numpy as np import pytest -from ott.core import linear_problems from ott.geometry import pointcloud +from ott.problems.linear import linear_problem from ott.tools import transport @@ -60,7 +60,7 @@ def test_transport_from_problem(self, rng: jnp.ndarray): geom = pointcloud.PointCloud(x, y, batch_size=9) b = jax.random.uniform(rngs[2], (num_b,)) b /= jnp.sum(b) - pb = linear_problems.LinearProblem(geom, b=b) + pb = linear_problem.LinearProblem(geom, b=b) ot = transport.solve(pb) np.testing.assert_array_equal(ot.matrix.shape, (num_a, num_b)) From eb384eb7e831c8c40b0c8acccbadcf02374d017a Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Wed, 16 Nov 2022 15:34:43 +0100 Subject: [PATCH 06/34] Update cost funcs and potentials --- ott/geometry/costs.py | 18 ++++++++++-------- ott/math/potentials.py | 32 +++++++++++++++++++++----------- ott/solvers/linear/sinkhorn.py | 2 +- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/ott/geometry/costs.py b/ott/geometry/costs.py index 065426661..084673300 100644 --- a/ott/geometry/costs.py +++ b/ott/geometry/costs.py @@ -52,7 +52,6 @@ class CostFn(abc.ABC): def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> float: pass - @abc.abstractmethod def barycenter(self, weights: jnp.ndarray, xs: jnp.ndarray) -> float: raise NotImplementedError("Barycenter not yet implemented for this cost.") @@ -115,14 +114,14 @@ class TICost(CostFn): @abc.abstractmethod def h(self, z: jnp.ndarray) -> float: - """RBF function acting on difference of `x-y` to ouput cost.""" + """TI function acting on difference of :math:`x-y` to output cost.""" def h_legendre(self, z: jnp.ndarray) -> float: - """Legendre transform of RBF function `h` (when latter is convex).""" + """Legendre transform of TI function :func:`h` (when latter is convex).""" raise NotImplementedError("`h_legendre` not implemented.") def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> float: - """Compute cost as evaluation of :func:`h` on `x-y`.""" + """Compute cost as evaluation of :func:`h` on :math:`x-y`.""" return self.h(x - y) @@ -135,9 +134,10 @@ class SqPNorm(TICost): """ def __init__(self, p: float): + super().__init__() assert p >= 1.0, "p parameter in sq. p-norm should be >= 1.0" self.p = p - self.q = 1. / (1 - 1 / self.p) if p > 1.0 else 'inf' + self.q = 1. / (1. - 1. / self.p) if p > 1.0 else "inf" def h(self, z: jnp.ndarray) -> float: return 0.5 * jnp.linalg.norm(z, self.p) ** 2 @@ -159,9 +159,10 @@ class PNorm(TICost): """p-norm (to the power p) of the difference of two vectors.""" def __init__(self, p: float): + super().__init__() assert p >= 1.0, "p parameter in p-norm should be >= 1.0" self.p = p - self.q = 1. / (1 - 1 / self.p) + self.q = 1. / (1. - 1. / self.p) if p > 1. else "inf" def h(self, z: jnp.ndarray) -> float: return jnp.linalg.norm(z, self.p) ** self.p / self.p @@ -182,8 +183,9 @@ def tree_unflatten(cls, aux_data, children): class Euclidean(CostFn): """Euclidean distance. - Note that the Euclidean distance is not cast as a `TICost`, because this - would correspond to `h = jnp.linalg.norm`, whose gradient is not invertible, + Note that the Euclidean distance is not cast as a + :class:`~ott.geometry.costs.TICost`, since this would correspond to :math:`h` + being :func:`jax.numpy.linalg.norm`, whose gradient is not invertible, because the function is not strictly convex (it is linear on rays). """ diff --git a/ott/math/potentials.py b/ott/math/potentials.py index 50d62b3a6..cf2914c67 100644 --- a/ott/math/potentials.py +++ b/ott/math/potentials.py @@ -1,4 +1,4 @@ -from typing import TYPE_CHECKING, Any, Callable, Dict, Sequence, Tuple +from typing import TYPE_CHECKING, Any, Callable, Dict, Optional, Sequence, Tuple import jax import jax.numpy as jnp @@ -6,10 +6,8 @@ import jax.tree_util as jtu from typing_extensions import Literal -from ott.geometry import costs - if TYPE_CHECKING: - from ott.geometry import pointcloud + from ott.geometry import costs, pointcloud __all__ = ["DualPotentials", "EntropicPotentials"] Potential_t = Callable[[jnp.ndarray], float] @@ -27,15 +25,16 @@ class DualPotentials: g: The second dual potential function. cost_fn: The cost function used to solve the OT problem. corr: Whether the duals solve the problem in distance form, or correlation - form (as used for instance for ICNNs, see e.g. top right of p.3 in :cite:`makkuva:20`) + form (as used for instance for ICNNs, see, e.g., top right of p.3 in + :cite:`makkuva:20`) """ def __init__( self, f: Potential_t, g: Potential_t, - cost_fn: costs.CostFn, *, + cost_fn: 'costs.CostFn', corr: bool = False ): self._f = f @@ -44,7 +43,7 @@ def __init__( self._corr = corr def transport(self, vec: jnp.ndarray, forward: bool = True) -> jnp.ndarray: - r"""Transport ``vec`` according to Brenier formula. + r"""Transport ``vec`` according to Brenier formula :cite:`brenier:91`. Uses Theorem 1.17 from :cite:`santambrogio:15` to compute an OT map when given the Legendre transform of the dual potentials. @@ -66,6 +65,8 @@ def transport(self, vec: jnp.ndarray, forward: bool = True) -> jnp.ndarray: Returns: The transported points. """ + from ott.geometry import costs + vec = jnp.atleast_2d(vec) if self._corr and isinstance(self.cost_fn, costs.SqEuclidean): return self._grad_g(vec) if forward else self._grad_f(vec) @@ -127,6 +128,8 @@ def _grad_g(self) -> Callable[[jnp.ndarray], jnp.ndarray]: @property def _grad_h_inv(self) -> Callable[[jnp.ndarray], jnp.ndarray]: + from ott.geometry import costs + assert isinstance(self.cost_fn, costs.TICost), ( "Cost must be a `TICost` and " "provide access to Legendre transform of `h`." @@ -152,15 +155,22 @@ class EntropicPotentials(DualPotentials): g: The second dual potential vector of shape ``[m,]``. geom: Geometry used to compute the dual potentials using :class:`~ott.core.sinkhorn.Sinkhorn`. - a: probability weights for the first measure. - b: probaility weights for the second measure. + a: Probability weights for the first measure. If `None`, use uniform. + b: Probability weights for the second measure. If `None`, use uniform. """ def __init__( - self, f: jnp.ndarray, g: jnp.ndarray, geom: "pointcloud.PointCloud", - a: jnp.ndarray, b: jnp.ndarray + self, + f: jnp.ndarray, + g: jnp.ndarray, + geom: "pointcloud.PointCloud", + a: Optional[jnp.ndarray] = None, + b: Optional[jnp.ndarray] = None, ): n, m = geom.shape + a = jnp.ones(n) / n if a is None else a + b = jnp.ones(m) / m if b is None else b + assert f.shape == (n,) and a.shape == (n,), \ f"Expected `f` and `a` to be of shape `{n,}`, found `{f.shape}`." assert g.shape == (m,) and b.shape == (m,), \ diff --git a/ott/solvers/linear/sinkhorn.py b/ott/solvers/linear/sinkhorn.py index b8201d6e5..84c6100d4 100644 --- a/ott/solvers/linear/sinkhorn.py +++ b/ott/solvers/linear/sinkhorn.py @@ -295,7 +295,7 @@ def transport_mass(self) -> float: def to_dual_potentials(self) -> potentials.EntropicPotentials: """Return the entropic map estimator.""" return potentials.EntropicPotentials( - self.f, self.g, self.geom, self.a, self.b + self.f, self.g, geom=self.geom, a=self.a, b=self.b ) From 5280021027795ba32faedd08f1f348a948150253 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Wed, 16 Nov 2022 16:20:02 +0100 Subject: [PATCH 07/34] Fix LR initializer --- ott/initializers/linear/initializers_lr.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ott/initializers/linear/initializers_lr.py b/ott/initializers/linear/initializers_lr.py index a8132816f..24b4fcaa8 100644 --- a/ott/initializers/linear/initializers_lr.py +++ b/ott/initializers/linear/initializers_lr.py @@ -380,8 +380,8 @@ def _compute_factor( which: Literal["q", "r"], **kwargs: Any, ) -> jnp.ndarray: - from ott.problems import linear as linear_problems - from ott.problems import quadratic as quad_problems + from ott.problems.linear import linear_problem + from ott.problems.quadratic import quadratic_problem from ott.solvers.linear import sinkhorn from ott.tools import k_means @@ -395,7 +395,7 @@ def _compute_factor( ) fn = jax.jit(fn, static_argnames="k") if jit else fn - if isinstance(ot_prob, quad_problems.QuadraticProblem): + if isinstance(ot_prob, quadratic_problem.QuadraticProblem): geom = ot_prob.geom_xx if which == "q" else ot_prob.geom_yy else: geom = ot_prob.geom @@ -407,7 +407,7 @@ def _compute_factor( arr, centroids, epsilon=0.1, scale_cost="max_cost" ) - prob = linear_problems.LinearProblem(geom, marginals, init_g) + prob = linear_problem.LinearProblem(geom, marginals, init_g) solver = sinkhorn.Sinkhorn(**self._sinkhorn_kwargs) return solver(prob).matrix From a5999b50eb048496ad778e821795fbc913cce998 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Wed, 16 Nov 2022 16:23:49 +0100 Subject: [PATCH 08/34] Fix k-means initializer --- ott/initializers/linear/initializers_lr.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ott/initializers/linear/initializers_lr.py b/ott/initializers/linear/initializers_lr.py index 24b4fcaa8..c3d83f735 100644 --- a/ott/initializers/linear/initializers_lr.py +++ b/ott/initializers/linear/initializers_lr.py @@ -512,8 +512,8 @@ def _compute_factor( which: Literal["q", "r"], **kwargs: Any, ) -> jnp.ndarray: - from ott.problems import linear as linear_problems - from ott.problems import quadratic as quad_problems + from ott.problems.linear import linear_problem + from ott.problems.quadratic import quadratic_problem from ott.solvers.linear import sinkhorn def init_fn() -> GeneralizedKMeansInitializer.State: @@ -588,7 +588,7 @@ def body_fn( cost_matrix=cost, epsilon=eps, ) - problem = linear_problems.LinearProblem( + problem = linear_problem.LinearProblem( cost, a=consts.marginal, b=consts.g ) @@ -614,7 +614,7 @@ def body_fn( del kwargs - if isinstance(ot_prob, quad_problems.QuadraticProblem): + if isinstance(ot_prob, quadratic_problem.QuadraticProblem): geom = ot_prob.geom_xx if which == "q" else ot_prob.geom_yy else: geom = ot_prob.geom From 35ae68a02e9e4a58b43b3a8dad702f6216329136 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Wed, 16 Nov 2022 16:42:29 +0100 Subject: [PATCH 09/34] Move `utils` --- ott/geometry/__init__.py | 2 +- ott/{utils => geometry}/segment.py | 2 +- ott/initializers/linear/initializers_lr.py | 2 +- ott/initializers/nn/initializers.py | 2 +- ott/math/__init__.py | 1 - ott/math/implicit_differentiation.py | 4 ++-- ott/problems/linear/__init__.py | 2 +- ott/problems/linear/barycenter_problem.py | 3 +-- ott/{math => problems/linear}/potentials.py | 0 ott/problems/quadratic/gw_barycenter.py | 5 ++--- ott/solvers/linear/acceleration.py | 10 +++++----- ott/solvers/linear/continuous_barycenter.py | 2 +- ott/solvers/linear/sinkhorn.py | 4 ++-- ott/solvers/nn/neuraldual.py | 2 +- ott/solvers/quadratic/gromov_wasserstein.py | 2 +- ott/solvers/quadratic/gw_barycenter.py | 2 +- ott/{utils => solvers}/was_solver.py | 1 + ott/tools/segment_sinkhorn.py | 5 ++--- ott/tools/sinkhorn_divergence.py | 5 ++--- ott/typing.py | 2 +- ott/{utils/dataclasses.py => utils.py} | 0 ott/utils/__init__.py | 1 - tests/core/continuous_barycenter_test.py | 3 +-- 23 files changed, 28 insertions(+), 34 deletions(-) rename ott/{utils => geometry}/segment.py (99%) rename ott/{math => problems/linear}/potentials.py (100%) rename ott/{utils => solvers}/was_solver.py (98%) rename ott/{utils/dataclasses.py => utils.py} (100%) delete mode 100644 ott/utils/__init__.py diff --git a/ott/geometry/__init__.py b/ott/geometry/__init__.py index 7309ab155..e57c7b3ec 100644 --- a/ott/geometry/__init__.py +++ b/ott/geometry/__init__.py @@ -12,4 +12,4 @@ # See the License for the specific language governing permissions and # limitations under the License. """OTT ground geometries: Classes and cost functions to instantiate them.""" -from . import costs, epsilon_scheduler, geometry, graph, grid, pointcloud +from . import costs, epsilon_scheduler, geometry, graph, grid, pointcloud, segment diff --git a/ott/utils/segment.py b/ott/geometry/segment.py similarity index 99% rename from ott/utils/segment.py rename to ott/geometry/segment.py index 2e15db821..1051f348a 100644 --- a/ott/utils/segment.py +++ b/ott/geometry/segment.py @@ -14,7 +14,7 @@ from typing import Callable, Optional, Tuple import jax -from jax import numpy as jnp +import jax.numpy as jnp __all__ = ["segment_point_cloud"] diff --git a/ott/initializers/linear/initializers_lr.py b/ott/initializers/linear/initializers_lr.py index c3d83f735..6059a17bb 100644 --- a/ott/initializers/linear/initializers_lr.py +++ b/ott/initializers/linear/initializers_lr.py @@ -13,8 +13,8 @@ ) import jax +import jax.numpy as jnp import numpy as np -from jax import numpy as jnp from typing_extensions import Literal from ott.geometry import geometry, low_rank, pointcloud diff --git a/ott/initializers/nn/initializers.py b/ott/initializers/nn/initializers.py index 09fe1b26b..b10a58dac 100644 --- a/ott/initializers/nn/initializers.py +++ b/ott/initializers/nn/initializers.py @@ -2,10 +2,10 @@ from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, Tuple import jax +import jax.numpy as jnp import optax from flax import linen as nn from flax.training import train_state -from jax import numpy as jnp from ott.geometry import geometry from ott.initializers.linear import initializers diff --git a/ott/math/__init__.py b/ott/math/__init__.py index faac35ec8..60c303c2e 100644 --- a/ott/math/__init__.py +++ b/ott/math/__init__.py @@ -3,7 +3,6 @@ fixed_point_loop, implicit_differentiation, matrix_square_root, - potentials, unbalanced_functions, utils, ) diff --git a/ott/math/implicit_differentiation.py b/ott/math/implicit_differentiation.py index 1f42888c0..58ed326bf 100644 --- a/ott/math/implicit_differentiation.py +++ b/ott/math/implicit_differentiation.py @@ -18,8 +18,8 @@ import jax import jax.numpy as jnp +from ott import utils from ott.math import unbalanced_functions -from ott.utils import dataclasses if TYPE_CHECKING: from ott.problems.linear import linear_problem @@ -27,7 +27,7 @@ __all__ = ["ImplicitDiff"] -@dataclasses.register_pytree_node +@utils.register_pytree_node class ImplicitDiff: """Implicit differentiation of Sinkhorn algorithm. diff --git a/ott/problems/linear/__init__.py b/ott/problems/linear/__init__.py index 1681cefbb..2088e5a3c 100644 --- a/ott/problems/linear/__init__.py +++ b/ott/problems/linear/__init__.py @@ -1 +1 @@ -from . import barycenter_problem, linear_problem +from . import barycenter_problem, linear_problem, potentials diff --git a/ott/problems/linear/barycenter_problem.py b/ott/problems/linear/barycenter_problem.py index a34ee728f..7a6e63129 100644 --- a/ott/problems/linear/barycenter_problem.py +++ b/ott/problems/linear/barycenter_problem.py @@ -17,8 +17,7 @@ import jax import jax.numpy as jnp -from ott.geometry import costs -from ott.utils import segment +from ott.geometry import costs, segment __all__ = ["BarycenterProblem"] diff --git a/ott/math/potentials.py b/ott/problems/linear/potentials.py similarity index 100% rename from ott/math/potentials.py rename to ott/problems/linear/potentials.py diff --git a/ott/problems/quadratic/gw_barycenter.py b/ott/problems/quadratic/gw_barycenter.py index 8d95a2773..a052e6224 100644 --- a/ott/problems/quadratic/gw_barycenter.py +++ b/ott/problems/quadratic/gw_barycenter.py @@ -2,14 +2,13 @@ from typing import Any, Dict, Optional, Sequence, Tuple, Union import jax -from jax import numpy as jnp +import jax.numpy as jnp from typing_extensions import Literal -from ott.geometry import costs, geometry, pointcloud +from ott.geometry import costs, geometry, pointcloud, segment from ott.math import utils as mu from ott.problems.linear import barycenter_problem from ott.problems.quadratic import quadratic_costs, quadratic_problem -from ott.utils import segment __all__ = ["GWBarycenterProblem"] diff --git a/ott/solvers/linear/acceleration.py b/ott/solvers/linear/acceleration.py index 9684f2956..a22ac9ab6 100644 --- a/ott/solvers/linear/acceleration.py +++ b/ott/solvers/linear/acceleration.py @@ -1,17 +1,17 @@ from typing import TYPE_CHECKING import jax -from jax import numpy as jnp +import jax.numpy as jnp + +from ott import utils if TYPE_CHECKING: from ott.solvers.linear import sinkhorn -from ott.utils import dataclasses - __all__ = ["AndersonAcceleration", "Momentum"] -@dataclasses.register_pytree_node +@utils.register_pytree_node class AndersonAcceleration: """Implements Anderson acceleration for Sinkhorn.""" @@ -107,7 +107,7 @@ def update_history( return state.set(old_mapped_fus=mapped) -@dataclasses.register_pytree_node +@utils.register_pytree_node class Momentum: """Momentum for Sinkhorn updates, either constant or adaptive.""" diff --git a/ott/solvers/linear/continuous_barycenter.py b/ott/solvers/linear/continuous_barycenter.py index e1d4a96e2..a1534647c 100644 --- a/ott/solvers/linear/continuous_barycenter.py +++ b/ott/solvers/linear/continuous_barycenter.py @@ -24,7 +24,7 @@ from ott.math import fixed_point_loop from ott.math import utils as mu from ott.problems.linear import barycenter_problem, linear_problem -from ott.utils import was_solver +from ott.solvers import was_solver __all__ = ["BarycenterState", "WassersteinBarycenter"] diff --git a/ott/solvers/linear/sinkhorn.py b/ott/solvers/linear/sinkhorn.py index 84c6100d4..9f824e142 100644 --- a/ott/solvers/linear/sinkhorn.py +++ b/ott/solvers/linear/sinkhorn.py @@ -25,8 +25,8 @@ from ott.initializers.linear import initializers as init_lib from ott.math import fixed_point_loop from ott.math import implicit_differentiation as implicit_lib -from ott.math import potentials, unbalanced_functions -from ott.problems.linear import linear_problem +from ott.math import unbalanced_functions +from ott.problems.linear import linear_problem, potentials from ott.solvers.linear import acceleration __all__ = ["Sinkhorn", "SinkhornOutput"] diff --git a/ott/solvers/nn/neuraldual.py b/ott/solvers/nn/neuraldual.py index 5d107e78c..306afa5c2 100644 --- a/ott/solvers/nn/neuraldual.py +++ b/ott/solvers/nn/neuraldual.py @@ -24,7 +24,7 @@ from typing_extensions import Literal from ott.geometry import costs -from ott.math import potentials +from ott.problems.linear import potentials from ott.solvers.nn import icnn __all__ = ["NeuralDualSolver"] diff --git a/ott/solvers/quadratic/gromov_wasserstein.py b/ott/solvers/quadratic/gromov_wasserstein.py index 805354fa1..05a427a74 100644 --- a/ott/solvers/quadratic/gromov_wasserstein.py +++ b/ott/solvers/quadratic/gromov_wasserstein.py @@ -26,8 +26,8 @@ from ott.math import fixed_point_loop from ott.problems.linear import linear_problem from ott.problems.quadratic import quadratic_costs, quadratic_problem +from ott.solvers import was_solver from ott.solvers.linear import sinkhorn, sinkhorn_lr -from ott.utils import was_solver __all__ = ["GWOutput", "GromovWasserstein", "gromov_wasserstein"] diff --git a/ott/solvers/quadratic/gw_barycenter.py b/ott/solvers/quadratic/gw_barycenter.py index 528f676c4..46ee6618f 100644 --- a/ott/solvers/quadratic/gw_barycenter.py +++ b/ott/solvers/quadratic/gw_barycenter.py @@ -8,8 +8,8 @@ from ott.math import fixed_point_loop from ott.problems.linear import linear_problem from ott.problems.quadratic import gw_barycenter +from ott.solvers import was_solver from ott.solvers.quadratic import gromov_wasserstein -from ott.utils import was_solver __all__ = ["GWBarycenterState", "GromovWassersteinBarycenter"] diff --git a/ott/utils/was_solver.py b/ott/solvers/was_solver.py similarity index 98% rename from ott/utils/was_solver.py rename to ott/solvers/was_solver.py index 2ff7789d9..c823ef1d4 100644 --- a/ott/utils/was_solver.py +++ b/ott/solvers/was_solver.py @@ -28,6 +28,7 @@ "continuous_barycenter.BarycenterState"] +# TODO(michalk8): refactor to have generic nested solver API @jax.tree_util.register_pytree_node_class class WassersteinSolver: """A generic solver for problems that use a linear reg-OT pb in inner loop.""" diff --git a/ott/tools/segment_sinkhorn.py b/ott/tools/segment_sinkhorn.py index 7f4afeb60..fca0a4226 100644 --- a/ott/tools/segment_sinkhorn.py +++ b/ott/tools/segment_sinkhorn.py @@ -15,11 +15,10 @@ from types import MappingProxyType from typing import Any, Mapping, Optional, Tuple -from jax import numpy as jnp +import jax.numpy as jnp -from ott.geometry import costs, pointcloud +from ott.geometry import costs, pointcloud, segment from ott.solvers.linear import sinkhorn -from ott.utils import segment def segment_sinkhorn( diff --git a/ott/tools/sinkhorn_divergence.py b/ott/tools/sinkhorn_divergence.py index 119f7be5d..2b8404439 100644 --- a/ott/tools/sinkhorn_divergence.py +++ b/ott/tools/sinkhorn_divergence.py @@ -17,10 +17,9 @@ import jax.numpy as jnp -from ott.geometry import costs, geometry, pointcloud -from ott.math import potentials +from ott.geometry import costs, geometry, pointcloud, segment +from ott.problems.linear import potentials from ott.solvers.linear import sinkhorn -from ott.utils import segment __all__ = [ "sinkhorn_divergence", "segment_sinkhorn_divergence", diff --git a/ott/typing.py b/ott/typing.py index 1b27fa3aa..28e746713 100644 --- a/ott/typing.py +++ b/ott/typing.py @@ -1,4 +1,4 @@ -from jax import numpy as jnp +import jax.numpy as jnp from typing_extensions import Protocol # TODO(michalk8): introduce additional types here diff --git a/ott/utils/dataclasses.py b/ott/utils.py similarity index 100% rename from ott/utils/dataclasses.py rename to ott/utils.py diff --git a/ott/utils/__init__.py b/ott/utils/__init__.py deleted file mode 100644 index 3f5072c03..000000000 --- a/ott/utils/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from . import dataclasses, segment, was_solver diff --git a/tests/core/continuous_barycenter_test.py b/tests/core/continuous_barycenter_test.py index 2f3430d27..2a0efa9d1 100644 --- a/tests/core/continuous_barycenter_test.py +++ b/tests/core/continuous_barycenter_test.py @@ -21,13 +21,12 @@ import numpy as np import pytest -from ott.geometry import costs, pointcloud +from ott.geometry import costs, pointcloud, segment from ott.problems.linear import barycenter_problem from ott.problems.quadratic import gw_barycenter as gwb from ott.solvers.linear import continuous_barycenter as cb from ott.solvers.quadratic import gw_barycenter as gwb_solver from ott.tools.gaussian_mixture import gaussian_mixture -from ott.utils import segment means_and_covs_to_x = jax.vmap(costs.mean_and_cov_to_x, in_axes=[0, 0, None]) From c1dfdb469d3852955ad274522e65bd778236febb Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Wed, 16 Nov 2022 19:31:17 +0100 Subject: [PATCH 10/34] Update imports in notebooks --- docs/notebooks/GWLRSinkhorn.ipynb | 49 +++++++--------- docs/notebooks/Hessians.ipynb | 27 +++------ docs/notebooks/LRSinkhorn.ipynb | 56 ++++++++----------- docs/notebooks/MetaOT.ipynb | 32 +++++++---- docs/notebooks/OTT_&_POT.ipynb | 20 +++++-- docs/notebooks/One_Sinkhorn.ipynb | 6 +- docs/notebooks/Sinkhorn_Barycenters.ipynb | 31 ++++++---- docs/notebooks/application_biology.ipynb | 23 ++++++-- docs/notebooks/gromov_wasserstein.ipynb | 8 +-- .../gromov_wasserstein_multiomics.ipynb | 9 ++- docs/notebooks/icnn_inits.ipynb | 11 ++-- docs/notebooks/introduction_grid.ipynb | 20 +++++-- docs/notebooks/neural_dual.ipynb | 17 +++--- docs/notebooks/point_clouds.ipynb | 30 ++++++---- docs/notebooks/soft_sort.ipynb | 4 +- .../wasserstein_barycenters_gmms.ipynb | 13 +++-- 16 files changed, 195 insertions(+), 161 deletions(-) diff --git a/docs/notebooks/GWLRSinkhorn.ipynb b/docs/notebooks/GWLRSinkhorn.ipynb index 376212527..4b1cd42e1 100644 --- a/docs/notebooks/GWLRSinkhorn.ipynb +++ b/docs/notebooks/GWLRSinkhorn.ipynb @@ -42,9 +42,11 @@ }, "outputs": [], "source": [ - "import jax.numpy as jnp\n", "import jax\n", - "import ott\n", + "import jax.numpy as jnp\n", + "from ott.geometry import pointcloud\n", + "from ott.solvers.quadratic import gromov_wasserstein\n", + "from ott.problems.quadratic import quadratic_problem\n", "import matplotlib.pyplot as plt" ] }, @@ -65,15 +67,7 @@ }, "id": "PfiRNdhVW8hT" }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "WARNING:absl:No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)\n" - ] - } - ], + "outputs": [], "source": [ "def create_points(rng, n, m, d1, d2):\n", " rngs = jax.random.split(rng, 5)\n", @@ -120,11 +114,10 @@ }, "outputs": [], "source": [ - "geom_xx = ott.geometry.pointcloud.PointCloud(x)\n", - "geom_yy = ott.geometry.pointcloud.PointCloud(y)\n", - "# below `z` is there only to create n x m geometry\n", - "geom_xy = ott.geometry.pointcloud.PointCloud(x, z)\n", - "prob = ott.core.quad_problems.QuadraticProblem(\n", + "geom_xx = pointcloud.PointCloud(x)\n", + "geom_yy = pointcloud.PointCloud(y)\n", + "geom_xy = pointcloud.PointCloud(x, z)\n", + "prob = quadratic_problem.QuadraticProblem(\n", " geom_xx,\n", " geom_yy,\n", " geom_xy=geom_xy,\n", @@ -161,7 +154,7 @@ }, "outputs": [], "source": [ - "solver = ott.core.gromov_wasserstein.GromovWasserstein(rank=6)\n", + "solver = gromov_wasserstein.GromovWasserstein(rank=6)\n", "ot_gwlr = solver(prob)" ] }, @@ -193,7 +186,7 @@ }, "outputs": [], "source": [ - "solver = ott.core.gromov_wasserstein.GromovWasserstein(epsilon=0.05)\n", + "solver = gromov_wasserstein.GromovWasserstein(epsilon=0.05)\n", "ot_gw = solver(prob)" ] }, @@ -230,26 +223,22 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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U6dOWdQ3GAwYMmQItFotvvjiC4sv0Htt3rwZ06ZNw0svvYQlS5bUWe7q1au19n3zzTf417/+JZ0LuDv9eNu2bRbbwoULAQCzZs3Ctm3bpJlEHTt2xLFjx/C///3P4rgbN26EVqtF9+7dAQBNmzbF008/jYMHD1q81++//x4HDx7Eb37zG2mftceUy8rKAgC8+OKLdb5/anwemTup16xZI6Vp5KZPn4533nkHu3btQt++ffHaa6/B3d0df/vb31BRUVFrimHNl//p06fx7rvvSvv79++Pzz//HF5eXrXukrWFn58f+vfvj/feew9VVVVo2bIlvvzySxQUFNh9zHv17t0bO3bswNNPP40XXngB27dvh4eHh2LZ9u3b409/+hMWLlyIfv364fnnn4eXlxf+85//ICwsDOnp6WjRogVmz56N+fPnIyEhAc8++yxOnz6NDz/8EL169cLLL78MAPj6668xdepUjBw5Eh07dsSdO3fw8ccfw83NDSNGjJDO2aNHD3z11VdYsmSJlNaLiYkBcHea64ABA7Bnz577vseYmBiMHDkSs2fPxpUrV9C+fXusX78eFy5cwOrVqy3Kjh07Fnv37rUIbgkJCTh//jxmzZqFAwcO4MCBA9JrISEh0hfq4cOHMXbsWAQGBmLw4MH45JNPLI7dp08fqfeXlJQEb29v9OnTB8HBwfjuu++watUq+Pj44C9/+YtUR+ku72bNmgEAevXqZXGH+cyZM/H555+jX79+mDp1KgIDA/HZZ5/h888/x29/+1uLVNq7776LnJwcPPXUU5g2bRoAYNmyZQgICMCcOXPsOiYAVFdXY/PmzejduzfatWtX918KNT7OHAB5GGoGbOvaagbi8vPzRXx8vGjSpInw8fERgwYNEgcPHlQ8Zs3gcXFxsbTvwIEDAkCtqY11qRlgvnr1aq3XLl68KIYPHy6aNWsm/P39xciRI8Xly5cFAJGWlvbAY9S854KCAmkfFO6k3rFjh3B3dxdJSUkPHFRfs2aN+NWvfiW8vLxE8+bNxYABA8SuXbssyixfvlxEREQIDw8PERISIiZPnix+/vln6fXz58+LV199VbRr107odDoREBAgBg0aJL766iuL45w6dUr0799fmmYpH7CGldNchbg7A+zNN98Uer1eeHl5iV69eons7Oxa5QYMGCDu/adwv8+M/PwP+nytXbtW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\n", "text/plain": [ - "
" + "
" ] }, - "metadata": { - "needs_background": "light" - }, + "metadata": {}, "output_type": "display_data" }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "
" + "
" ] }, - "metadata": { - "needs_background": "light" - }, + "metadata": {}, "output_type": "display_data" } ], @@ -282,7 +271,7 @@ ] }, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -296,7 +285,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.12" + "version": "3.10.6" } }, "nbformat": 4, diff --git a/docs/notebooks/Hessians.ipynb b/docs/notebooks/Hessians.ipynb index 59a6e6a55..51383ca35 100644 --- a/docs/notebooks/Hessians.ipynb +++ b/docs/notebooks/Hessians.ipynb @@ -22,7 +22,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -34,7 +34,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 2, "metadata": { "id": "F0ESAZHMV_vL" }, @@ -43,10 +43,9 @@ "import jax\n", "import jax.numpy as jnp\n", "\n", - "import ott\n", "from ott.tools import sinkhorn_divergence\n", "from ott.geometry import pointcloud\n", - "from ott.core import implicit_differentiation as implicit_lib\n", + "from ott.math import implicit_differentiation as implicit_lib\n", "import matplotlib.pyplot as plt" ] }, @@ -61,7 +60,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 3, "metadata": { "id": "0jfa6mSiWAw6" }, @@ -80,19 +79,11 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 4, "metadata": { "id": "79peUzQOVqcJ" }, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "WARNING:absl:No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)\n" - ] - } - ], + "outputs": [], "source": [ "a, x, b, y = sample(15, 17, 3)" ] @@ -257,9 +248,9 @@ "provenance": [] }, "kernelspec": { - "display_name": "ott", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "ott" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -271,7 +262,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.12" + "version": "3.10.6" } }, "nbformat": 4, diff --git a/docs/notebooks/LRSinkhorn.ipynb b/docs/notebooks/LRSinkhorn.ipynb index dc304a367..5328cfb91 100644 --- a/docs/notebooks/LRSinkhorn.ipynb +++ b/docs/notebooks/LRSinkhorn.ipynb @@ -43,6 +43,9 @@ "import jax.numpy as jnp\n", "import jax\n", "import ott\n", + "from ott.geometry import pointcloud\n", + "from ott.problems.linear import linear_problem\n", + "from ott.solvers.linear import sinkhorn, sinkhorn_lr\n", "import matplotlib.pyplot as plt\n", "\n", "plt.rcParams.update({\"font.size\": 18})" @@ -56,9 +59,6 @@ }, "outputs": [], "source": [ - "import ott\n", - "\n", - "\n", "def create_points(rng, n, m, d):\n", " rngs = jax.random.split(rng, 4)\n", " x = jax.random.normal(rngs[0], (n, d)) + 1\n", @@ -91,8 +91,8 @@ "n, m, d = 19, 35, 2\n", "x, y, a, b = create_points(rng, n=n, m=m, d=d)\n", "\n", - "geom = ott.geometry.pointcloud.PointCloud(x, y, epsilon=0.1)\n", - "ot_prob = ott.core.linear_problems.LinearProblem(geom, a, b)" + "geom = pointcloud.PointCloud(x, y, epsilon=0.1)\n", + "ot_prob = linear_problem.LinearProblem(geom, a, b)" ] }, { @@ -128,31 +128,27 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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koIPPvgAnTt3Ro0aNeDm5obatWujV69e+PLLL5GVlVXiNW7fvo3Zs2ejc+fO8PPzg6urK3x9fdGkSROEhobi7bffLvblUvTv99ChQxYH5h48eFDyY3VEM2fORHx8POrWrYuPPvrIrrI+//xznDlzBhqNBkuXLpWV9z//+Y94e9KkSVZbCkaNGgUfHx8AwM6dOxXvKvL19ZWU7oUXXhBv//HHHxbTmI6batKkic3ygoODxdtSHqNer8frr78OAHjjjTfQsWPHEvMorWBWmfRB8JaPin4Uj44q15VmOjAwKSmpXAe6ZmVlYejQodi6davZ/fHx8Vi7di02bNiALVu2oG/fvqUq32AwYOLEiVixYgUAoEuXLtixY4fND6w9e/Zg6NChePDggdn9x48fx/Hjx7F161Zs374d7u7uVsuYM2cOPvroo2KDLuPj4xEfH4+oqCj06NEDmzZtgp+fX4mPIzY2FgMGDMClS5dKTAsUDIYdOHAgjh49anb/9evXcf36dfzwww/Yt2+fzV+yZWHPnj1icFujRg288cYbZVb2f//7X4waNarY65SYmIjExERER0dj4cKF2Lp1K9q1a2exjJ07dyI8PBzp6elm9z948AAPHjzA9evXceTIEXzzzTfFrlMakZGR4oDtiIgIhx47dezYMbEl8Msvv4S3t3epy4qJiRFbeT744APUq1dP1ow608HCYWFhVtOp1WqEhoZi586dePjwIQ4dOoR+/fqVut5lxfS5e/jwocU0tWrVEm//9ddfNsszPd+8efMSrz9jxgzcunULdevWlTT2qjyonAoOu8rgGKNyU+UCo0aNGuHixYsACj7gymvGBgCMHj0aW7duRYcOHRAeHo769evj/v37WL9+PY4dO4bs7GwMHz4cV69eRfXq1WWVnZ2djWHDhokz7fr164eoqCibXYC//fYbFixYAEEQMH78eHTu3Bnu7u44c+YMVqxYgYyMDOzbtw8fffSR1ab4mTNn4pNPPgFQMBYgPDwcTz31FDw9PfHHH3/gm2++wd27d3Hw4EH07NkTJ0+etDlzJDs7G4MGDcKlS5cQEhKCwYMHIzAwEElJSbh7926x9Hl5eRg8eDCOHj2Knj17YuDAgahduzZu3bqF1atX49KlS0hNTUV4eDguXbpks0vLXvv27RNvv/jii2V2rV27dmHw4MFi4NmtWzcMGTIE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LVuv1yM9PV1S2XKub+vapuelPi6jShUYATDrB42JibGazt3dHT4+PmYHERFRZVOWLUb16tUz6y2ZN2+exWumpaWJt9VqtdW6mZ4zzWOLkmXLub6ta5uel3tt7rtBRETkIBISEswaAtzdlVvE9FFV6QKjEydOiLctjXQnIiJyJGWxCawxv9QeEuPAY6BgILY1pudM88gp21p9SlO2nOvbelym5+Veu1wDI0EQbP5xZGdn49133wUAeHl54emnn1asLjGx0gaZGZ08Fic57dBh7SWnDaztmF2Act7kp87clJy2dQvLA+mscXWt2quw+/jI+zWo5DYfx07ES05bvbrtJm5TwY2ry6pHzVoaWemVkpcvfUuQ8GHtJKeV+xoqtc2H3HrkZOdKTitnyxi52yA5OUkbISI1XZlQFR72liFDYGCgePvWrVto06aNxXS3bt2ymEdO2dYCI2PZPj4+0GjK7n1rvL5p3W1dX+rjMir1X0Zqairu378vHsbR75mZmWb3mw64Onz4MHr16oXvvvsON2/+78syNzcX0dHRCA0NxcmTJwEAs2fPRrVq1UpbPSIiokdW8+bNxeDPdBZZUcZzAQEB8PPzk1S26Uw0KWW3aNFCUrlSGa9/7949JCUlWUyTn5+PP//8EwDQsmVLWeWXOjBq164datSoIR7GWWELFiwwu/+NN94Q8wiCgOjoaIwcORL16tWDWq1GjRo14OXlhV69euH06dNwcnLCO++8gxkzZpS2akRERJVGRSzwqFar0bVrVwDAnj17LKYRBAF79+4FAPTu3Vty2cHBwahfv77NsjMyMvDrr7/KLluKZ555Rrxt7fpHjx4VB13LvX65zkpr3bo1Fi5ciMGDByM4OBienp548OABPD090bZtW7zxxhv47bff8NFHH5VntYiIiBRTEesYAUBERASAgt3qjb0xpqKiosTZ3yNHjpTxeFRi+g0bNogb1JpaunQp0tPT4ezsjGHDhsmvvA2PPfYYQkJCAACLFi1Cbm7xbtxPPvkEANCgQQN069ZNVvmlDozi4uIgCEKJx7fffivm8ff3x9SpU/HTTz/h6tWrSE5ORm5uLnQ6HX777Td8+eWXaN26dWmrRERERIUiIiLQunVrCIKAwYMHi2sDGgwGREVFYezYsQAKVq8uOqY3MjJSbKmyFPhMmzYNAQEByMzMxLPPPouzZ88C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y5YJbm5uAgBhwoQJFusWExMjeHl5CQCE0NBQ4dq1a4IgCEJ6erowd+5cQaVSCQCE+fPnW8x/9OhRwdnZWQAgDBo0SLh586YgCIKQnJwsTJgwQaz7f/7zH9nPGwMjIiKiKio2NlYICgoSAwW1Wi14eHiI/27Xrp3FTVpLCowEQRDOnDkj+Pv7i+m8vb0FV1dX8d+9e/cWsrKyrNZt586dglqtFtNrtVox2AEgvPLKK4LBYLCaf9WqVYKLi4uYvlq1amJABUCYM2eO3KdLEARuIktERFRlBQUF4cKFC5g9ezZatWoFlUoFV1dXdOjQAQsXLsSJEyfg6+tbqrI7dOiAS5cuYcqUKWjSpAlyc3Ph5eWFkJAQrFq1Crt377a6nAAA9OvXDxcuXMDYsWMRFBSErKws+Pr64plnnsFPP/2Eb775xuZWKK+++ipOnjyJl19+GXXq1EFmZiZq1qyJgQMHIjo6utTrO6kEQRBKlZOIiIioimGLEREREVEhBkZEREREhRgYERERERViYERERERUiIERERERUSEGRkRERESFGBgRERERFWJgRERERFSIgRERERFRIQZGRERERIUYGBEREREVYmBEREREVIiBEREREVEhBkZEREREhf4fTpl+sa49KEQAAAAASUVORK5CYII=\n", "text/plain": [ - "
" + "
" ] }, - "metadata": { - "needs_background": "light" - }, + "metadata": {}, "output_type": "display_data" }, { "data": { - "image/png": "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\n", + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAh0AAAGnCAYAAAAT2eVpAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjYuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8o6BhiAAAACXBIWXMAAA9hAAAPYQGoP6dpAAEAAElEQVR4nOyddZhV5fqG77VzOoGhu0QUFMHCwMTuOHa3x0R/NuoR45jn2EfswhY7UBQLUFq6BwYYmM6d6/fHs5Z7QFBiAobvvq65GPbsvfa383vW+z7v+1q2bdsYDAaDwWAwNDCepl6AwWAwGAyG7QMjOgwGg8FgMDQKRnQYDAaDwWBoFIzoMBgMBoPB0CgY0WEwGAwGg6FRMKLDYDAYDAZDo2BEh8FgMBgMhkbBiA6DwWAwGAyNgq+pF1CXeDxOQUEB6enpWJbV1MsxGAwGg8GwEdi2TUVFBW3btsXj2XA8Y6sSHQUFBXTo0KGpl2EwGAwGg2EzyM/Pp3379hv8+1YlOtLT0wEtOiMjo4lXYzAYDAaDYWMoLy+nQ4cOf+zjG2KrEh1uSiUjI8OIDoPBYDAYtjH+zhphjKQGg8FgMBgaBSM6DAaDwWAwNApGdBgMBoPBYGgUjOgwGAwGg8HQKBjRYTAYDAaDoVEwosNgMBgMBkOjsFWVzG4ukUiEWCzW1MvYpvF4PPj9ftMJdjti6dKlfP/991RWVpKVlcVBBx1EixYtmnpZBoOhGbNNi47y8nLWrFlDKBRq6qU0C7xeLykpKbRq1YpAINDUyzE0EL/88gv3jBjBp598Qjwex7I82HYcfyDIKSefzK233kKvXr2aepkGg6EZYtm2bTf1IlzKy8vJzMykrKzsb5uDlZeXs3z5ctLS0sjMzDRn6VuAbdvEYjFqamooKysjHo/Tvn17UlJSmnpphnrmzTff5Iwzz8Sf24GU/keQusO+WIFk4tVlVM74hpqpn+CLVvPZJ5+wzz77NPVyDQbDNsLG7t/brOhYuHAhfr+f9u3bG7FRj8RiMZYsWYLP56Njx45NvRxDPfLDDz+w/5AhJPXah9zDrsLy/jnQGQ9VU/T+v/CULGbq5Ml07dq1CVZqMBi2NTZ2/94mjaSRSIRQKERmZqYRHPWM1+slJyeHqqoqotFoUy/HUI/ceddd+Ft03qDgAPAEU8g97hbCto9HHnmkkVdoMBiaO9uk6HBNo36/v4lX0jwJBoMARnQ0I+bNm8fXX31F6oCjNig4XDzBVJL7HszzL75IZWVlI63QYDBsD2yTosPFRDkaBvO8Nj/GjBmD5fGS0mvwRl0/tc/+VFdWMn78+AZemcFg2J7YpkWHwWDYOCoqKvAFk/H4gxt1fU9Kxh+3MxgMhvrCiA6DYTsgMzOTaG018XDtRl0/VlUK8LeGboPBYNgUjOgwGLYDDj74YMCmatb3G3X9qt+/JT0jkz322KNhF2YwGLYrjOjYSMLhMMXFxYTD4aZeisGwyXTp0oWhhx1G9eSPsKORv7xurKac2hlfccH555leLQaDoV7ZLNExadIk7rzzTo4++mh69+5Nbm4ufr+f3Nxc9t57b+655x6Ki4vre62NTn5+Prfddhut27QlGAySm5tLMBikdZu23HbbbeTn5zf1Eg2GjWb4HXcQKymg6ON/Y0fXL55jNRUUvXsXqUEfV199deMu0GAwNHs2qznYFVdcwRNPPPHH/5OSkvD7/WuZzlq0aMHo0aPZc889N/q4G9tcpLa2lkWLFtGlSxeSkpI2dfl/y6pVq7jk0ksZ/eGHeAJJJO0whGCbHliBZOxwDaEV86id9S3xcC1HH3MMTz/1FHl5efW+jqaioZ9fQ9MxevRoTjr5FKyUHFL6H0bKDvviSUojVlVC1fQx1Ez/nBSfxZdffM7AgQOberkGg2EboUE7kr788ssUFhYyePBgevfuTVZWFgCVlZW89957XH/99axevZpWrVoxd+5cMjMz63XRDbkpzp07l4MOPoRVpZWk7XEKqX32xxP8c4g5HqqmauZYKn8ZRV5WGl9/9SU9e/as17U0FUZ0NG8mTpzCxdf8m8nj34Y6qZaU1FTOPusshg0bRpcuXZpwhQaDYVujSdugf/nllxx66KEAvPrqq5x++ukbdbumFh2rVq1i4KDdWV0TJ/fEO/FltPrb20TLCyl65w5aJnuYOGF8k0U8TjnlFN566y2GDRvGAw88sNbf5s6dy6677grA5MmT6dGjx18ey4iO5s3KZXDxEhjdbRX8+BPeikrez81iv/32M9UqBoNhs2jSNuh1He/Lli1riLtoEC659FJWlVZutOAA8GW0IvfEO1lVWsmll13WwCvcME8//TTt27fnoYce4ttvv/3j8kgkwumnn05VVRWPPvro3woOQ/NnRQGM7wrk5cFxx5FyxpkcddRRRnAYDIYGp0FEx7hx4/74vVu3bg1xF/VOfn4+oz/8kLQ9TtloweHiy2hF2h6n8OEHHzSZyMrOzubll18G4KyzzqKkpASA4cOH8+uvv3LsscdywQUXNMnaDFsXn1dBUTbgxDjTTQ2bwWBoJOrt6yYUCrF48WIef/xxzjzzTAC6d+/OUUcdVV930aA8++yzeAJJpPbZf7Nun9pnfzz+JJ599tn6XdgmMGTIEK677jqWLVvGJZdcwrhx47jvvvto06YN//vf/5psXYath3AIfkiBaACsuC5LM6LDYDA0Elv8dZOUlIRlWSQlJdGlSxeuvPJKSkpK2HvvvRkzZswfw8O2dv733EiSdhiyXtPoxuAJppDUZ3+efW5kPa9s0/jXv/5F//79eeuttzjyyCOxbZsXXniBFi1aNOm6DFsH81ZBQRpggVdzE0lr0hUZDIbtiS0WHa1btyYvL4/U1NQ/LhsyZAiPPvooHTt2/MvbhkIhysvL1/ppCsLhMKtWriDYZsv8DsHWPVi1ooBI5K+bLzUkgUCAl156CZCx55JLLvnD1GswvF4BBU720OOmV5puOQaDYTtji0XH4sWLWblyJZWVlaxatYoHH3yQKVOmMGjQIG6//fa/vO29995LZmbmHz8dOnTY0uVsFu74biuQvEXHcW/f1EOyRo0a9cfvU6ZMIRaLNeFqDFsTv1iwJhOIg8dJr5ieowaDobGo12xuq1atuO666/j888+xLIu7776bjz/+eIPXv+mmmygrK/vjp6k6fKalKcBsh2u26Dju7dPTm+7c8YcffuD++++ndevWHHTQQfz888/cc889TbYew9bDb9VgVUHcB54YWF7AY0SHwWBoPBrEQjZo0CAGDx4M8JfGymAwSEZGxlo/TUEgECCvdRtCK+Zt0XFCK+eR16Ytfr+/nla2aZSXl3PmmWcSi8V44YUXePXVV2nZsiV3330348ePb5I1GbYeni+HUBCwIBAF2/n0G9FhMBgaiwbzrbdr1w6A+fPnN9Rd1CsXXnC+WpuHqjfr9vFQNbUzx3LRBefX88o2niuuuILFixdzxRVXMHToUPLy8njuueeIRqOcccYZVFVVNdnaDE1L3IYpEZjRGbDBZydER+pf3dBgMBjqkQYTHQsXLgSaNtWwKVx00UXEw7VUzRy7WbevmjmWeDTERRddVL8L20jefvttXnnlFfr06bNWR9Kjjz6aCy+8kPnz53PVVVc1ydoMTc/nQLuVUJqiUlm/DTELLEz1isFgaDw2WXTEYjH+rnP6mDFjmDBhAgD777//Zi2ssenQoQNHH3MMlb+MIlpeuEm3jZYXUvnzmxxzzDG0b9++gVa4YZYvX87FF19MIBDgtddeIzl5bUPsI488Qo8ePRg5ciTvv/9+o6/P0PS8HII2RYAFvqhSKrZHXwAm0mEwGBqLTRYd+fn57LLLLjzzzDMsXLhwLQGSn5/PfffdxzHHHINt2+Tk5HDNNdfU64Ibkqefeoq8rDSK3rljo4WHO3slLyeDp558soFXuH7atWtHcXExoVCI/v37/+nvqampzJ07F9u2Oe644xp/gYYmpdqGJVXwQ0/AAn8cAh6Ioy8A4+kwGAyNhW9zbjR16lQuueQSQCbMjIwMampq1vIMdOnShXfffZfWrVvXz0obgby8PL7+6ktNmX1t2MZNmf35TfKy0xnz1ZfNary9ofnwqg39V8JLzkSCYBRSg0qteIEtKxQ3GAyGjWeTRUfbtm15++23GTt2LOPHj6egoIA1a9bg9Xrp2LEj/fr145hjjuG00077U5h/W6Bnz55MnDCeSy+7jA8/eJry714kqc/+BFv3wAokY4drCK2cR+3MscQjtRxz7LE89eSTRnAYtlreBY5eDDW9wYpBIA5ugZUPE+kwGAyNxyaLjkAgwIknnsiJJ57YEOvZKsjLy+O9d98lPz+f//3vfzz73EhWTf408fc2bbnmhuu46KKLmsTDYTBsLMtsqAnDFKca3R+FFA94nMSqMZIaDIbGZLPSK9sLHTp04K677uKuu+4iHA5TWVlJWloagUCgqZdmMGwUz9owpBCe76P/+2Pg80Gt83djJDUYDI2JmS+5kQQCAXJycozgMGxTfA3stQRWZoBlQ1IM0vxQiT78FkZ0GAyGxsOIDoOhmTLRhmwbZtVA1Kv+HCkxSPVCyLmOSa8YDIbGxIgOg6GZ8pwNx1fBB50AW6mVdAvSPBIdFvoCaJrhAwaDYXvEiA6DoRkSs2ESsHcBTGmr1EowCmk+yAJiJNIrJtJhMBgaCyM6DIZmyPvAjsD8IqhMdvwcEQgE9aG3UY8OCzAuJYPB0FgY0WEwNENet+Es4AuPZqx4bEgCLD8EkejwOD+mhM1gMDQWRnQYDM2MMhuWAwNK4Ysuimb4o5DhgYAFUdQC3Yf+ZkSHwWBoLIzoMBiaGS/YsD+QvwqWZ4InDklhyLPAD5Q61/Ov86/BYDA0NEZ0rIfffvuN4cOHEw6H//J64XCY4cOH89tvvzXSygyGv2c0cJ4FP1ZByJ8wkWYmQToJ0RHARDoMBkPjYkTHOkyYMIEDhuzHnXfeySknn7RB4REOhznl5JO48847OWDIfkyYMKGRV2ow/Jl5tvwaPYH30yFuSVgEPRANqBFYpXNdV3R4m2itBoNh+8OIjjpMmDCBgw86gL7ZEd48IZlPP/l4vcLDFRyffvIxb56QTN/sCAcfdIARHoYm5zkbjgFKi2FyK6VW/FH15rCBTBIt0IPOv1aTrNRgMGyPGNHhUFdwfH5akFP6+nnvpOCfhEddwfHeSbre56cFjfAwNDk28D1wlgW/FkFpKvhikBaCXKc+NhuJDguJDiM4DAZDY2JEB38WHOlBfRUf0XNt4VFVVbWW4Diipyx46UHLCA9DkzPWhjZAjgVfRSDmAduC5BC0DyiNkkKiMVgy5gvAYDA0Ltv9d85vv/22XsHhUld4dOnS+U+Cw2Vd4dGY5tJff/0Vy7LYa6+9NnidESNGYFkWd9xxR6Oty9C4vGjDaUA8Dl9kA46/w+uDVgFVqcRI9OhIxkQ6DAZD47Ldi46PPvqI8ooq/jnQ+yfB4eIKj9Ro6XoFh0t60OKfA72UV1Tx0UcfNeSy12K33XZj11135eeff+b333//099t22bkyJF4PB7OP//8RluXofEI2zATONqCxcWwJAO8cQjEIRiACpRaqXGu73YiNaLDYDA0Jtu96Lj55ps59pijOevDEJ/MjWzwekf09LPonykbFBwAn8yNcNaHIY495mhuvvnmhljuBrnkkksA+N///venv40ZM4aFCxdy6KGH0rFjx0Zdl6FxeBPY1Vbzr7HlUB0AXxTSaiDLKx9HAImPGEq1BDHlsgaDoXHZ7kVHIBBg1Ftvc/gRR3L8238tPP6KT+ZGOP7tEIcfcSSj3nqbQKBxJ1qcdtppZGRk8MorrxAKhdb623PPPQfAhRde2KhrMjQeb9twjvNp/sTxcsQ9kFMF7fwQdkpnC5HocEtlTbmswWBoTLZ70QFbLjyaWnAApKamcsYZZ1BcXMy77777x+Vr1qzh/fffp3Xr1hx11FGNvi5Dw7PahiJgDyAcg0lpaggW94A3AG2d4FwmUIA8Hb46PwaDwdBYGNHhsLnCY2sQHC7rS7G8/PLLhMNhzj33XHw+s8U0R56z4SDAsuDnUlidCoEI+OJgJ0MOSqWkocZg7oRZM+zNYDA0NkZ01CEQCPDqa6+TmZ3DFZ9vnOi44vMImdk5vPra600qOAB22mkn9tprL8aOHcu8efMAGDlyJJZlccEFFzTp2gwNx2fA+Y4j9JsqCPlkIs2shlS/hrsFUTltBP3fS0J8GAwGQ2NhREcdwuEwZ5x+GmUlxTw+dOPGYD0+1E9ZSTFnnH7a385qaQzcaMdzzz3Hjz/+yMyZMznwwAPp2rVrE6/M0BBMc0bWd3JEx9fOrJWoF1pVQI4PSlDlSgskOGzk6TBzVwyG7Yfly5fzwAMPcMsttzBu3Dhs226SdRjR4bBup9G/qlKpy7oNxJpaeJx00knk5uby4osv8uSTTwLGQNqcec6GE5zfl0VhYYrSKlEvJPmgrQ8qLUU6oqydVjFzVwyG7YOxY8fSvUdPbr7tDh7879Psu+++XHvttU2yFiM62HzB4bI1CY+kpCTOPvtsCgsLef3112nZsiXHHntsk63H0HDEbRgP/MOJcnxbBqVJEAyDz4ZoGrQFwijCUcHafg43zWIwGJovtm1z4cWXYLXsStvLXqL1pS+Stf95PProo0yaNKnR17Pdi44tFRwuW5PwuPjii7Es7URnn312k3tNDA3D50BXIMMRHV9GVLHiicnP4QkqrWKhD/oq518LpWRiqHeHwWBovhQUFDB/7hzSBhyNJ5iKZXnIGHgMvmAyY8aMafT1bPeiY8SIEXzw4WhePuavBccncyN0+U/13zYQe/mYIB98OJoRI0Y0xHI3ip49e9K+fXsAYyBtxrxiw5nO77XAZB9YcQj7oEMJZPkT81YCwDISkY4klG4xosNgaN5kZGTg8QeIFBf8cVmsoohouJZWrVo1+nq2e9Fx1FFHkZGeyn8mxqgIrd9Y45bFVvmy/rKctiJk85+JMTLSU5u0J8bPP/9Mfn4+++23H7169WqydRgajiobFgCHOFGOX6OwIgkCUQgHZBrN8Sq1koLmrCxHKRU/EhtuVYvBYGieTAAyU9OJn30WpT+9Qem416iY8jlF7w6nZctWHH/88Y2+pu1edAwYMICvvv6GGSV+hr4e+pPwqNuHY9GixRvs41ERshn6eogZJX6++vobBgwY0JgPYy3uueceAK644oomW4OhYXnVhj1t8DmiY0wFVPs1UdZrQ3U25AIrLPXpaIu6kdpIdLhiw0Q6DIbmxwpgJ2B3W92Jeey/7HnRRYQmf0jxF48zcIcujP32G9LT0xt9bdu96AAYNGjQeoXHuo2/UlNT19tAbF3BMWjQoEZ/DD/99BPnn38+u+++O5988gm77rprk6hYQ+PwHom25zbwU8wphY1BRg2EkiU0KpDAaItSMCH0oTeRDoOh+VEJnA60tWGGe/4chyeDSfz0+H+oKC+jurqa778bS58+fZpkjUZ0OKwrPEbNWH+n0XU7l46aEWlywQEwd+5cnn/+eWbNmsURRxzBe++9h8djXt7myBIbqoH+zv8XAQt8inDUBqHrGkgKQEvn7xZqDBZDosMPpCLRkdS4SzcYDA1AGLgbnVy8XjdYH4dnLLjUKVPz+XwkJyc3/gLrYHalOtQVHqe+W7PB1uZ1hcep79Y0ueAAOOecc7Btm/Lycj7++GM6derUZGsxNCwjbTgctT0H+DkKhQFICkMoAF2jkOuRoLBQF9Ik9GGPOpelY0SHwbCtEwNeA3og0VFRV3BE4UkLLtrKdnnTkHAdBg0axDfffsdHH33EzTffvMFyU1d4jBgxgqOOOqpJPRyG7QcbGAO8YyUu+74KokmQWQE1AajJgSwS1SlRYCUylMadnwxUzWJEh8Gw7WED3wPXAXOBKtS3x2037I3Aw3641PqLgzQRRnSshwEDBmyUiAgEAgwfPrzhF2QwOIy3JSjaOF8mZcAMt7e5DWkhKMuAjkChpcmyAPORwIg7/zfpFYNh22Q6cBNqDBhlnegG4IvACD/8cysUHGBEh8GwTfGCDafW+f8kYIkP/HGoDkKPNRDvIj/HaqA90iNLUHrFHfTmCpCmze4aDIaNZTEwAjUF9KATjkid6Aa2SuZvDsCwJlvl37OVZXs2jaYaWNPcMc/r1knEhinA8XXOYH6OQKlPfo6aJBhUBakWdEJm02SgHUqleJCJ1C2bjaGUi8Fg2HopBG5HPq6vUKRgCXUEB2oKmByFawK67tbMNhnp8HplxY1EIk3uxG2OhEIhQE5nw9bD+0BvG1KdU4UoMKUG7ACkVUFFEti5inKkIWHhQ472FSQGveH8bkSHwbD1Ug68DLyOPr9dgZ+AWldo2E6QIw4pNpwfgHtR0GNrZpuMdPj9foLBIGVlZeasvJ6JxWIUFxeTmppqRMdWxps2nF3nG2U2sCAO3rjzxROGFdnya7jEkegoR2V1PpRisZBoSWusxRsMho2iFgmNU4GHUSo0FRiLBMcfQxptffYzgDP88BBbv+CAbTTSAdCiRQuWL1/OsmXLyMzMxO/3/zHkzLBp2LZNLBajpqaGsrIy4vE4bdq0aeplGepQYkMBsF+dt/hEYKkf/DGoSIYuxVDeEboARZa+nEqQ6HD75wb4I/1LjLUFisFgaDqiwBdIcExBUciDgVdQfx1sSLah1nJONGzItOAoLzzGtrOZbyvr/BMZGRkArFmzhuXLlzfxapoHXq+XlJQUWrVqZSbTbmW8aMN+gLeO6JgWgRoPZNVASQrsvxxmWqpuyUdnQCH0IY85P0kkBIgRHQZD02MD44A3gcmoq+jhwGfA86gU1gP4baejsBPdzPTCYR54nG2rs/A2KzpAwiMjI4NIJEIsFmvq5WzTeDweEy3aivkE+G+dl6YQmF8Llk9+juI0yM1U069uwG/ITFpMoh9HEYkSWhulXoynw2BoGmwU0XgdmIU6Cw9A3YOfIBHdSEUnChEkOHxxTZA+0FKEY1s7cdimRYeL3+/H79/wWHqDYVtmjq2oRO86l00E5gG+mPwcSRFY3gLygNbo+jlIWMxH4qKARNWKK9FNPMtgaHzmoU6iC5DwaAvcgrqKLkKCxGtDhg2llj7j2JDkCI59Lfk9sppk9VtGsxAdBkNz5jkbjiHR9hz0RbXKB4EQlKVB+zJY0VYOdx8SE+6gtwXIMBpDYVq3R4dbOmswGBqH5SiysRhFI33AlUiEXILM3tgqdU+1YY2lHjwRG9LjkOGHvS24H2jVJI9gyzGiw2DYirGBH4EP6wiOELA0JONZdggK0+H0FTCtvc6Oiq1E59G2KE/sCg23esXFfAEYDA1PMfJsLEIdRYuBfYGhwKUoCmkDHqfjcMzWdZJj6reTE4f0AAwC7kRN/7ZVtsmSWYNhe+FbW+mSlnUNpMCCCHhjkFYJcQ/0TIU0J/+7GFWoVCDRsRxFPdxhb66Pw8aIDoOhIalC1ScPoB4bY1Ha8zEkJk5EPTgAArZG0lcCVRakOIIjLw5ZAdgD+D+geyM/hvrGfOcYDFsxL9pw2jqXTQTmWRCMAnG1Pq5uCbkovTIbCYtVyJRWiL6oPKxtHnUjHwaDoX4JI/P3RBSZ/AGlQ24AWgCnA2uc61rIu5Frw0Kn1D0lAmUWtLch2YlwXALs1NgPpAEw3zkGw1ZKrQ1zgCPrRDlsIN+GMg+kxqEkHdpUwMw8RUS6oUhIW6AURThqnR8/+jJMq3Ms8wVgMNQfceBbNAk6HUU3/MCxwNnAjUiMxHGa9Nk6WUixYb4FSbYmxJZ5oTMQ8CnCcRqwe2M/mAbCfOcYDFspbwL9bUiqkwRdAqwKA3FIr4aCTDg+Hxa3gTxbgsKHcr6lJEpjq9GXX4SE490YSQ2G+sEGJgAfIfH/O+oCvCNwObAQ2Mu5zB26mGxDSxsqLVjsgbQ4RCNQ4YEeFvi9MBg4BBjSBI+poTCiw2DYSnnPhv9bx3X1K/B7VF1IM6ogPxf29KssNoYmT/pRP45clB92W567TcKykQgxkQ6DYcuZCbyFxEYh8B2KUlyEhMZ5qPmXRUJwtLChja1mfpYFWXEoj0DYAz28EPDAPiitclTjP6QGxXznGAxbIStsNfPaY53LZwJLPBAIQzwu8ZHcErJtVagsdSpXPCjFshhFP9yJs1EkSEIY0WEwbAlLgDdQ518L9d1oD5yMUikfA/1QajOAU7Juy9vR3oZxFqRZkB2D/Ch4vNDTCwFL3Yd7oPkrzQ3znWMwbIW8YMNBgKeOn6MCqLL1JZYRgzXp0LIKZrfU+PquSGTE0HXaoghIOjKVJqPIRxLyehjRYTBsOoVIbMSQKfQNlLLcG7gQfd5ORN4qC0Ueo0CmrZODdEuCI92CtlGYFdPk6PZeCFpwAJoUfQHbxgC3TcWUzBoMWxk28CVw1jrfOJOApWGVymZVQ2kG7FcGv1tKpXRHEY0AKsNrh/oCpCGxkYa+xHwkvBxGdBgMG0c58BwwEqVPxgEfoE7BlwMj0MC23ZCnI0gindLWhq6Of2OKpehI1zDMjEMLD7TzQYqlAW/pwBU0T8EB5jvHYNjqmGrrC6vbOt86vwFTnZbnaRUQaQlDvfAiMoh2QF9w6cBKlGPORxGOWiQ6CtGH3uP8GCOpwfDX1AKjUWpzH2QY/QZVig0E/oEiifsjw6gHCf8oqkZpBXS34QtLwiLZho5h+M2Crh5I8amM/QDn/q5h7QZ+zQ0jOgyGJqKkpIQPPviACRMm8NvkKZSUlODz+ajp3JmddtuNcQcdyODBg7EsizjqUFjoUXmdbWvwU7tcCYca1IgoGYmPmejDXYBG3bs9OTzOvzaJqIfBYPgzURRx/BEJAg+KZnRHVSXnIQ/HvcAz6POUhNIuEVRNlmLDTha8bkF7C8I25IRgsgcGeCDs08nAEFTOfhPNfx6S+c4xGBqZ5cuXc8cdd/Dqa68TDoVIatURT4sueDLyIB4jMnsFK75/lI/vupNevXfgxhuGsfs55xDGIhYFfxQKsyC3BlbmQitbX1RLLQmQ9qhBGKhs1h3yZqMzKDfK4f5uMBgS2KiZ1+costELzTppB/RHfo09kLDfHUUP3aihO36gqw3ptiIbr1s6EUiywa6FmT44yIJin0zd+6G06I3opKG5Y0SHwdCIvPzyy1x+5ZWEbR8pA0+k5c6H4E3L/tP1bNumduk0lk36mPPOO48dXn2NvKefJ9iqI9mVsDAXDimFX1pAFzvh34iiUG0L5zgR9IXolsz6SQiN5hzCNRg2hynAu8AuyMj9H/S52QmNnT8BfX7+DxiFPk9B57ZRNIogF+hmK31SYCm90iEOc0KwwgcneWCBV4JjbyQ4rkE+j+0BIzoMhkbAtm1uvfVWRowYQVrfA2h1wIV4k9M3eH3Lskju1I/kTv1IXTSJ+V8+ztzBu9PqvTGkp/Uh3EZdDt9HZ1HdgK/RmVYEiRB3fH218687fdZT5/8Gg0FVXm+iz9IpwCMogrEjqkw5F8gDxqN25JVIcCShz1sY6GSrJHY/4GkLsi2dAOwZgzFhRTbO88AUr3rl7Ik+m5eROEnYHjDRVYOhEXjwwQcZMWIE2UPOJ/eIa/9ScKxLcpddyTvjYTxWCoXHHUj1quV4beibrS+/GpRndkfXr0TlskXoS9FtgQ4JoeGmWgyG7ZkC4EFUdXIasAC4DpWs7ogEyP+hqMSlyDQaRp8rd3KzB+jtpFMOsOBhD/Sy5LE6KgKfh6HMD1d5YbpXAmMP1CvnfHSCsD1hRIfB0MBMmTKFm26+mYzdTyRj0HGbdQxvahZ5J90JtRFm3HshmTU2tekKyZahCEYSOrMqINGjIwOJkQD6svQjwWFEh2F7pgR4CjX0OgF5KS53Lt8FeTX+5fz+NapSGYdSKUnOMUKo70YXG9rZmgT9b0tdSAuAE8PwWhRCfrjeC+M9EjMD0OfvVNRbZ3vDiA6DoQGxbZtzzzsff24HsvY5fYuO5U3NJueQy6ge+xmtRo1iigUdHRPpEieU2xGV77VCZ21ZKPzrzl1JRl+WZsKsYXvEHTX/X1SRsiuKbIxFYqEbmgR7HIognoEiHF70GUpBgiEM7GDLw7GfDdMs+NRSB9JC4IAQvBDTjW7wwg+WIhw7IeFyONCnkR7z1oYRHQZDAzJu3DimTJ5Exv7nY3m3vCtGSvfdSeq6K2ueeZhfUZOizqgTqQ/lpN0JlotRpKMKfdGFUHlebZ3rGAzbAxHgQ+A+lDY5CVWkPIm8FR1RVcrVQA7ySrkVKkEk1m3nOF6gnxMuPNOC/3ig1lJfnHQbutfCqBgkBeA6H3zvNO/bwTn2Hijasb1iRIfB0IA8/fQzJLXoQFKnfvV2zPT+R7Bk2kTmTp6MH52dLUFfiG1JiIllSGSUO/+GSTQKM5EOw/ZAHI2ZH47Mm1cA7wDD0GyTXkBf4C4kClajKMdN6DPjQcI9jkR7OzQ3Jc2GoRZcYakDaQ0wyIZQCL6woUUQLvPCzyS6BXd0/t23UR751osRHQZDAzLm228JdN8dy6q/psbJXQfg8QUo/O47KtEX2Ro0m8WDXPagNEvQuTwNnam5EQ8jOgzNGRuYCNyOBMH/oajF+cjjtDdKPd4EHIYae72AKk/cz03QudxCQn03G/xxGGhDkgU3WzKWTgfOj8OUEEy0oXsAzvCqg3AW0AmlVTKc+9reMd87BkMDsXLlSgpXrqDFHj3q9biW10cwrws1v/3GGpQr9qAvWrdyBZRWiaCyvCCKeCShL+EY5sNvaJ7MQqPmeyFRMRZNfc1FXopCVJXimjjz0aC25c51IigqUouig+mojXmFDRdYStEssjRN9ivgthg8HIbFHtjTDwO9EjjpQBvkFSlEZbcG871jMDQYy5cvB8Cf1brej21l5GHnL8MHrLKUK65FrvlOznXcLqRREqWz7lh7E+kwNDeWoomv2cibsRhVpNQgsbEQpVAuRdGLOGr+9QwSBwHnx/1sVKLUC07/jSssOMf5rO2OIikjonBLBFZ64Cg/tPVoHSlIwAwFZjjraK4D3DYV871jMDQQsZjTnqseUysuluXBF4v9YSJNReV4BcgYV0viwx1FkY4YOvsqxEQ6DM2HNcDrSCycg97v9wBz0NRWN+14KxIDoDEBl6By87ZImLRA0UAPig7ub8NKW8KjhwUnOlNglzp/vyYCN0TU9OtCH0Q8KrkNIHF/IvA9qo4xgiOB+d4xGBqInJwcAGJVZfV+7FhNGek5HemORIeFKlfmI/GxkESPDrdSxYPECSjaYT78hm2ZCuBtlFL8BxINzwPfotLVI5E/4xw0jwgkSP6F0i+dUAqy7syUaufytjYU2nCapf4cV1jwT2RCHQjsGlaEoywAt/lgvqXPmA+Ztc8CPkLzVIxxcm3M82EwNBBdu3YlJS2N8KoF9Xpc27aJrFpA3i67rFW50pHE9Nj5KLwLiWFUXhKio5bmP83S0DwJIbHxIDAI9dWYgjwTs4HTkbjYCbiFhOD4FVWOfIYEegVKq1QiobAGCYpsG2pt+D8LHrHgTUuG1DeQF6RdGB4IQ3kQ/uuD350eOXEUUbwUeA+4nkQnYEMCIzoMhgbC4/EwcLeBhJdOrdfjRgoXEasuJ2PQIFqj0LBrKHW/5BYi57wb5UhGYsQNL9eQGFRlMGwLRNHk17uQkLgDRSrORyLkVFTSGnX+tgd6z9cA16KIh+uu8qOKrpBz/ShwkA3VtqIcV1lwvKV0y8Woe+ldNiwJw4shqE2GN73whSXh4kYOrwFeRoLD7VxqWBsjOgyGBuTcc86metFkIsXL6+2YFVM+JT2vNT0OOoiwlfBrFJH4Ul1KwjQaRMJj3UjH9jBG27Dt446avwNtWHeh9/n1wL/R+Pn90ZTli1CqxY3ifYvKYCeiapU1SLBUkPA3dUXdRfNtOMKG3SyNnt8D2Bn4AHjehtfD8FkIYinwsReet1QhU+Hc103A/5DA2fjJStsfRnQYDA3IySefTHZOLuU/vlEvx4uUFFA181sOv+QSuvj9LCXhvHdnroDK/zJIiA439FvX02FEh2FrZxoSG6ucfweiCbDDUJXKGcjTNMi5rJVzuwpUBnsV6mMTRaI7B0UvUpxjDgFSbCi34ToLJnngQkspm5WonPYlG24Nw4QIeFPga6+6mQ5AQj/mrO0J4ErnPgwbxogOg6EBSU5O5tFHHqZy5liq5/68Rcey4zFKP/8PyW3bcvT11/9hIk1BZ291RUcxCiG7RrkIEiZueiWECf8atl4WIsPnJCQAjkImzkuRh+lSEiXhw4H+dW77IYnIR0/0ueiMhEgSUIoE94E2FNmQacNdFlxrqQrmGeTfaAU8GocLQjArChkp8J1XEY39kCgJOev8L4qy1H9xfPPDGNgNhgbmzDPP5O133uHzzx7Fm96CYJtNbxZm23GKv3yKmmUzOXDMGEpSU9kf+BRFNDqis8L9nOvXoMZGpSRy1+4kWh8mvWLYOlmBNvwgKmnNRWWnLyCBcRYS2jNQNUndqMJq57I5qHplnnN5axTVaONc1h+ZRRfaSs3sa+knDXgMmUaPAI6PwVlhlc12SIbRHkVPjgbmIkH/b+c2Z5Doj2P4a4zoMBgaGMuyeP211zjo4EOY9NYtZB36T1J7D97o28dqKij+8glq5vzImc8/T5v992eFrS/R5SQGvY1FIWd3dH0ZEh2tkejwo9BmEAmS1HXvyGBoIkqBUeg9exqK3M1BUYRVqMlWNpplchpq8uVio8mxj6DPRE8kTLqgiF8qSi+WoCZhq2xYbcPFFhQ6o+iHAIOR4LgGaBeFC8Kw2oK+SfCqR9UxpyHBUwE8hAbGHY28HYaNw6RXDIZGID09nTFff8VRhw9lzYf3sfqDewkXLvzL28QjISqnfUXB/y6met4vDNxzT5ZEIvSpqMSLeo7F0NlhO+c2FvoCT0JfxpUopeKKDreSxYgOw9ZANfAa6gy6P0qlBFFzrztQiuNKVAIeAO5kbcGRjwa0PY7EQ6VzeUf0ueiA0iwdkLBYbkPQhn9Z8L6lypcbUUTleeTL8Ebh2loo9MI+QQmOs5DomInEyyPO9fdh7dSO4e8xkQ6DoZFIS0vj3XfeYb8332LSP69lxQv/JNCuN0nt+xLI64YnJQNiUSIlBYRXzqdm/njitZX487qRnNeNGfnLqL74EsZfex37XHghS4ffQVZGBpUkjKKgnLcbdg45/9YgR33MuV4E4+kwNB0R1C9jInAsiiDUAi+ieSYtUcThZ5Q2vIG1K0LiKMowEkU0eqOZK91QFCKMIiOzUQQjxYZ5tnp3nGXBcZb+9hrwKBIrbwJvReDVEJQE4Hg/3GHBmWg67XhU/fIo8n7siAbHGTYNIzoMhkakCov4cadwXvB4Zo37iO/GPkD5rx9CLJK4ksdHoFVn0nY+hLT+Q/Fnt/3jT9HyQiqmfM43Tz7FwLff5qIXXyD5wANZQcJEugCFmT1IZFjoSz6ZhIHUxjQuMjQ+ceA74GvgIFT+aiOh8Tp6z56H0iyfow2/6zrHmIOqUiqAA5DZNAUJjqWoWmUSinYcjkphV9nwDws6Wao6SUKlsFegKOHzwN1h+KoGylLgEp+qWE5Hpbk/ovLa/wAfI1F0cP0+NdsNRnQYDI3I20DPfIjgp7hwAZEp40nuuQdZg8/C8vmxPF68qdlYvvVLAl9GK7L3PYu0nQ+h5Iv/cs8hh3Dec89RcO65f4iOxShcnIy+5JPQmV8AnU26osN8+A2NhY1GvX+AhqUNR6J3GvAcSgkeDPRA7cMPRv6KujNLIsADwLvIQ9EOdSLtjt7XK53b/4qMpH2BWTb4bLjVgimOf2MvJDYuQJGKu224LAKTaqAiFW73wuGWUiq3oV4fK5BhdCwqvz2uXp+d7QvzvWMwNCLv29B7Cfz8+ZNMev0GMvc8hcx9zsDaxKFw/qzWtDzpLoq/eoqR559PemoqF518MqA8944kuiTWFR11Ix3mw29oDOYgk2h31JY8GRmgn0NejT6o6+fHKDJ3K3+urPoN9eGIo+jFz8hs2hNYhqJ8q1Gk4yAkNObYqii5woI7LUUzrkVRin+iKMq5NpwehgU1UJUO//WqIdi5yMT6pXP8x1D0ZBmqqjFsPuZ7x2BoJPJtqCyD/BnT+fH5q0kfcNRmCQ4Xy+Ml55DLsMM1PHn++Zy/xx7QsSMFqJtiBTqbdKtV3EhHEJNeMTQ8+Shlko02+Sz0nnwGiYZslLr4HUVAziFhiHapRVGRr1HkIgj8giIdUeRf2sX5+44odTLfhhobjrBkHj3G0n28gVI2b6G0Tn8bTgxBQQhC6fCaV8bVi1Ep7KfOY3gECafJwNWYibFbiqleMRgaiZds6DU/xlf/ORt/dluy9z9vswWHi2V5JDy8yVxzwQXYtv3HKO8aFNVIQl+UfhL9OUykw9BQrEHNst4BzkZNs9KA0WjT/hVFGU5EAqAbcDN/FhzfA4egKMfRKMoQQoKjEAnpPCRg9kBpmzk2RGy4xoIdLOhvKaryPapw+QWJns5xOC0Ey0MQT4fRXomiy5FR9HNU9fKQc79jkHAyG+aWY55Dg6ERsIFvYlDw0ScULZpM9qFXbNC3sal4gqlkHXAhX3/1FRMmTCCEShGr0ZmhO3slhbUjHUZ0GOqTCtTE6xnUXOsaJAomOL9/gMpib0RCIR+Vxe7O2tGDcuS5GIbKY3uh8fI7ogqWxagKZSHOoDagpQ1z49DChnstmG3Jr9EbiZ2zUOrmNaAoBhfWwqooBNJhjEdi5hpUMvsxMmM/hMpj3wOuw3xe6gvzPBoMjcBvNqSthEmjHyfYtjdJ7Xf4+xttAsk99iCY04b/PP4Ent13pxiVAQZZe6y9MZIa6psQMn9OB05AfgiQOBiJIg090RTYb4D3UfSj1TrHsdGGfx8aVngCijC0Q+KhGJlJd0WRiF3QzJXpNoRsdeM9wYKbLflFLgMOA05CvTQeBj6IwWO1UGxBixT42KPuosPRbd5CPpMH0WflZeD/MBOZ6xPzvWMwNAIv2NBmegkfTf2KnKFX1vvxLctD8o4H8e6773LIC89T6fNRjFIpXhTSXFd0GE+HYUuIIS/F9yiycQKJ5nQvo8qULOTbWIkmsJ6MKkvWpQhFE+YhX4YXldHuhN67c3GqUVBH0H1RFGSaDWEbLrKgm6XOpdOBl5x1XAQciSIqD0VhVA0U+6FLED601CfkPhSheQN5Nx5w1vQ0iraYJnr1i0mvGAwNTBiYUQOFv04CIKl9nwa5n2D7PoRqqvHPno2F2kdnOX/zkehMmoSqAMwZh2FzsIGfUEmrjUyZe6EoxNuokdcsNGL+EhQ9qEDdRNcVHDZKeRyJenOcgoRHOUqnVKI0yhBUrtoGRTQyneqUFGdYW6qlyEc+8ANK3zyIupneCdwQhTer1PSrXxA+tySWHkACaRRqFnYfEuqPoV4gWfXzlBnqYL53DIYG5jMb2i2BRQsm4wkk48tZ1zJXPwTyuumXyZOx+/alkrWjG2ko1B3ApFcMm8c01CdjJyQ6XH/QOCQuQqgD6GFIgPyONu/s9RwrH0U3VqF24qA0zQDn93moLLY9SrsMQuJmog0xWymT0y342JK46YeqZS5B7/P7ka/j3AhMqYTSNNjPBy9YmkT7P+BVJDp+R4Ijy/n3Ev6c/jHUD+Z7x2BoYN6IQ14+zK4swZuSiWU1TIDRE0zBEwjiLy4mCZ15eklEN1wjqReFwbelD//ixYuZOHEikydPpqioCNu2yc3NZZdddmHgwIF06dKlqZfYrFmI0g9tUbrEbUk+B7UuL0Hj489B/Sz+g7p59l7PsWLAsygF0gFFRD5yjt0XRQYXogZh7znH3RsZSifaUGvDGRYMsjRmfiTqYnoVaqmeisyseTacGoGFlVCaAcd64TFLj+NVJFBeQOmYEah/x33IdNphS54sw1+yLX3vGAzbHMU2FBZBbrWmzTZ0jb9tQ5rHQw5y9sdR6WyQRNQjhoTH1v7hD4fDjBo1iv8+/gQTJ4wHIJjZAm+aJsvEKksIla0GYLeBg7jyiss55ZRTCAaN7a++WIk2Zz/qX9HCuXw1EhuL0PvqCpSrfwJFOu5k/bn7OSj9UoWqTuJIWOyO3q9LUXpjb5R22RN5N1bb8nV4nO6iaZaiKdNwSmCBo5Aw+TcSJv8IQ0EVlGfC+V6t6TlkZH0TiZVpwN1I8DyAOo322KJnzPB3bNb3TlFREaNHj2bMmDFMmjSJJUuWEI1GadmyJbvtthtnn302xx1nGsUaDG8AnRdD3IakjJZEq0qw4zEsj7fe7ytWU4EdCdGqRYs/zG8xVDobQJtDDJ1Jeti6jaS//vorZ519DrNm/k5Kl11occz/kdRhR7ypawfqY1Wl1ObPYOb0Lzn77LMZce99vPLySwwcOLCJVt48KEM+hxI0jM09869FaZOf0XvoOOSleBWJhXUHs7mEUZOtD5FAOBaladqgDqA2ieFsvzrH3xulXX60wWtrlsp5FuRbKr21UYvyGSjSsR/qIjo7DteEobgGKrLgRo9E0RPAF+gzORKYioRIJ9Sb40BnLYaGZbNER+vWrYlGo3/8PykpCb/fz/Lly1m+fDkffvghhx12GO+88w4pKSn1tliDYVvj0zB0XaWzuE69duWnSIjImqUEWtV/OiC8cj4APQYM+MOzEUCmvHSUXgFFPrbmSMejjz7KdddfT7BVF9qc8x8CeeuO/ErgTc0itfdgUnsPJly4iKVf/Ic99tiD+++/n+uvv74RV908qEGRgHnI1OmmR+KofPUj5/dBqFrlS7SZn4Wmva6PKajs1ELRiSiKngxGIma1c7/HoqjFnsgD0hP42SmHPc6C/Sz1+rgMtU5/FzX8eg+laK4DvozD3bVQHoaqTPi3R4/jQSRkXkfRjsnIk9IVVan0R9EWQ8OzWcnlaDTKoEGDePLJJ1mwYAE1NTVUVlayaNEizj//fAA+++wzLr744npdrMGwLTHXBrsAos4A2QP32QXL46V26fQGub/a/Ol4MzLI6N6dWiQ4giiU7ZbMgr7gPWydouO+++7jmmuuIW3AMbQ8/cG/FBzrEmjVhZan/Zu0gcczbNgw7rnnngZcafMiggTFPSi9cDsJwTENeSdc38WtKCJwH0q33M76BUeNc90rgNbAGSiKsRyZRd1S2PYo2vAiKoXdDb135zvdRa+zYB9LXUsvRv0+PnKO+xlqNjYMeCUGd9ZAWRRqMmCkIzj+5dzvq8jDMclZVw/nsnZoWq2hcdis751vvvmGIUOG/Onyzp0789xzz+Hz+XjmmWd49dVXGTFiBB06GFuOYfvjJRs6LtbvHguO6JtG1sHHUDHpC9IHHLXFLdDrYsci1M74mrZnnkGJx0MN+uIOoNB2jESko5qtM9IxevRobrrpJjL3+gdZ+5y+WcewvD6y9z8Hyx/k1ltvZYcdduD444+v55U2H2w0av4rEqPm3TPRAmT2XIXE60XIbPkiEhG3IYPy+vjeOVYK8lpEnGPtj8pnq5FZ9GQkBDqgCMcQ4Bsbkm1VW/3LgqilPiBTUYrmUFRiG0Q+jD2A+6Pwfo2OTRq85ZGwuQ01KXsJmVcnIvHSC0V0vKjFuqHx2KxIx/oER13caAcoN2swbG/EgAnl4C3T/9tmgD8NvOdeTnT1EmoW1u/nour3sYQritnh0kspQimVVBLVKz4SIqOarS/SUVxczAUXXkRq90FkDj5ti4+XudeppPbck4suvoQ1a9bUwwqbF+6o+duRf2M42vA9qDfGc2hDX4k2/OFo0x+JIhZns37BUY6qSG5F0Y8z0LyTxUgceJ3fPUjkPIbSGr2RSBjrNPsaaGl+yjJLqZyFyI+xM2oAluvcdjfg+ii8WwUVXgimwQcedS29AUVVRjqP51cUFdkBiazVKC1jaFwapHYvKSnxdozFYg1xFwbDVs2PNmQugXhc/9+zE/xcA7EDhxDY+wCKv3qKeKi6Xu4rWllM+XfPs9+p/6BX376EUMvoJOTlCLN2G+daEgPgthaGDx9OaUUVWYdcXi8RIMuyyD74MsqrQ9x+++31sMLmwxxUsTEbpU2OQe+FKEpb3IrMmf1RKWkqSlH0cK6/vi4zbgvzo9FGfxwa5DYSRTBaICH+KxINZWiK6xAkHGLAImc67KUWHGbBaEvm0NaoGdkSNChuF+QjaQ1cHIXvKqAiCFnJ6tnRHQ2WK0PRjZFo/sv1qCT3J9SX40LMxNimoEFEx9ixY//4faeddmqIuzAYtmpeikHLAv0e9MJeHeH7cm2GaY8+TzxcRdHn/8W241t0P3YsQsmnD5ORksThj//3jyqDQhTaTkObiQ9tDCDRsTVFOioqKhj5wguk9D8CX3puvR3Xm5ZNyi5H8OJLL1FeXl5vx91WWYaiF9+iiamno/eIjdION6FUSxsUETgQpTPcwWyDWP8mvRpt4I+gKMIZwI/IjDrYuc4q9J48HUUdWiMBMBSZRe24DKN3WdC1jn/jWBTheBmla45EEY64DedEYEoZlKdAxyB8ZOm4lyOz65POfU0ArkVRkiko9XPFBh6LoeGpd9FRWlrKvffeC8A+++xDr169NnjdUChEeXn5Wj8Gw7ZOFbC0EO3uQK88CPrhuwj4I0DnTnQb/jLVc3+k6NPHsOObFw2MR0Ks+fA+Istm8taoNynOzaUz+jJdg6IcQeTrsEiIj63NSDpq1ChqqqpJ6z+03o+d1u9QamtDvPHGG/V+7G2FIhQZeAtFCi4h0d57MYp6vI1SH2ehDfkTVDJ7MTJjBtZzXBuVn56IUnbHo+jGkyi60RK9x35FUZMuSJgMRR6O3YCvbAmIns6gNtuSB+QlZFR9DImI94HzUYlrga0uowvLoCID+gbhXQsykO8kxbndSJTaudK5/zlo4uy1bD3v/e2RehUd8XicM888kxUrVpCUlMTjjz/+l9e/9957yczM/OPHGE4NzYHRNuQt4o/QwgFdYakNqwLgjULMCx0HHs/uD75G1azvWPnaMMKrl2zSfYQK5rD6lWsI509j9OgPOeCAA1iOqgvSUV4+nUTZrEWiHXoNW1dH0nHjxpHcpju+jJb1fmxfeguS2/Zk3Lhx9X7srZ1KtHk/jTb6a1EUAzQM7QnnpxRVb9yDohb3oqjG9Ug4rI98FNF4CbUfPxPNRpmJvBpRlN6YhcTC16iR2N7I21EJLLGhwoYzLZXEzrfU5nw+SvMchwTIclRyezHwqw0Xh2FFBZRnwV5+CZ8gcAGQh8pjR6IS2cuRV2QxqlS5nvULKEPjUa+i46qrruLjjz8G4IknnmDnnf+61cpNN91EWVnZHz/5+fn1uRyDoUl4txbSi/R7Tgr0bAkTQ1Dh15mcBfgikHfCqbT86AfiyRYrXr6aos//S3jVwg0e17ZtQstnsebjB1n16jA6t2/B/RMnMHSoIgSlSFTkIB+Hr85PgEQ7dNdYurWEl8dP+BVPq40vjd1UvK26Mn7C9mNoD6MeFg+gM/xbUATC/ds7KLqxCKVD7kY+iLuRGBjOhptkxZCIOQNFRk50jv0ISqXkIQEwBc0uOQylRfZBXpBjgTE2BGx1673Dgh2d/hsHoPfuOJQWHIqE893A4cAncbihFooqJTgO90lc2MC5KJJyD/A8EhwXI/HkTri9nkQFl6HpqLeTneuvv/6PyMYjjzzCeeed97e3CQaDpmWxoVmxwoaqfGgR1aY+oD1YFvxQwR9DTwJhCAWh2AvBXXcnZ+wUog8/TOlrT1A59Qu8OW0JtuyKP7cDls+PHQkRKVpKrHABodJCsjt34bZHHqbf5ZeT4Ut8hKtRLtttge6KCg/6EncjHaVsPVEOgCVLFpO8514NdnxfTnuWzvi6wY6/tRADvkERh8NQKsJ9D9ho+upo9Nq3RKmUNFQCG0Pmy6y/OP5c5PsIo+qQI9Cmn+T8Xo1KYycgUfIpikIcgbwWWcCnNvhsGUuvdNIpNzlrOBI1+/reuZ9+yEvSAXghDi/WQGUYyrPhDI/+VoPmvQxA1SovIKPo+SjFUwz8F7gGyNzI59HQsNTLd88NN9zAQw89BMCDDz7I1VdfXR+HNRi2OV6xoc1Sfdl7LRjSRSWAv8QgEIeYBzIrgFQot8CywedJIueCm/FddQNrJn4CY7+jdvKv1Mz/FjsShmAS+/buRbtjzqbvgQcSPvhgbvR4eIlEAyfXFVKKzjQtJEAiKMqRgr6gk5D42JpERzQawfI23Iosr49IJNxgx29qbORd+BhFFO5k7dd3Lppj4np6TkUmzs+B8cjcuWHnnUTGY0hEdEEVJZUoknIkMokmoRLcHJTmuBNFPjKRIfV7lEoptzUZdoClnhqnoWZdd6DIxFNIgOyHemykA/fG4PNqqIorwnG1Jd9JOSrd3RcJpheRgfUc574rgIdRB1N3Zoyh6dniT/qwYcN48MEHAXjggQe47rrrtnhRBsO2iA18VwIdKvT/TjmQnQrT47DaC74Q1CRBVhkkJ0so2HGlWpJrIdTGh+eoYwgcfAyxAMS9EPUrQPKmpbPYAcgQCLCCRI6+GM2+KEZf9D4kOmqR4Ejlz+mVrYVgUhLxcG2DHd8O15KU3DwD6zOQCXRH/tysazXyMaxBwuFANLl1NkqhuALlr9JsU5zjBpHx8whUhupHnos1SNR+j9Ihxaj3xylIFBwOvOXMTVlqw00WtLRk6jwZCeG3kc/jKtT99EQkIuLADTEYXwkhD1Smq7rlH879nI0iOpcib8lPyFuyL3rfP4hmsqyvxNfQdGzRd8/111//R4TjgQceYNiwYfWyKINhW2SaDf7Fil4ADHZ6Q0+KQLkPkmplIs0og2gOeOL6Yg1GlHIJO3WtHhShwKMNwUIVCEvQGaBr7qvbf2MZCl8Xow91UIeiAm1E7oTZIFtfpGOH3jswa/XiBjt+ePVievVe35D1bZfFKHWRx9qj5kGv7ztoZDtohsmpzuWPoZTKjc6/G6IaGUp/RH6PIcgYei8SEkVIbCxBovhKNN01A4mPtmiw20c2pNp6v91lgc/xb1yFhMhnzm2ORe/XC1DkpRwYFoXpFRDzQ1Uq/MdSN9LVSHCcgETFK846T3HWGUGC42Q0W8WwdbHZ3z11BceDDz5oIhyG7Z4XI9BypX5P8cNA5xTrx0qlVWyPBEkgAiuSwRsC2wsZFRpJbzsiwxMDLHlBQCKkAG0ahSjHbbP2Gep8dEZXjISFH20KbpOwFBKejhBbV2Ow3QcN5PdRHzbY8eOF89n9uPovx20KCtHQMg/aoOtWl8RRNOxLFPXKQr6NVmi660zn/53/5j7GIUNmNuoWeiRKe3iReFmComnfIm9HTzRs7SQkCI501pBlQ5kNx1ow2FJk7xbkuzjEOeYqJFLaoVLeISiCd30U5pWBnQKRJHjRSphCz0GekdNRJOdHJEAOdp6DR5E42fFvn01DU7BZoqOuh+Phhx/mmmuuqddFGQzbGmFg1kroFNL/d2wDPq/y2DOikBSXac4fBdsHlX7wVgJeaFEGC9tIkFg2WE5NWV3RMRttJPkor16E8ucui9HQrDUkGoP5kOjIQJGOCucYbmXL1sIhhxzCE088QWjlfIKtu9frscOrFlJTuIRDDjmkXo/b2JShtFoxSi90XOfv01FfjQASB8ehypXfUN+MQ9HG/FepFLcd+u8ounEgGm1/N0qrlCDh4EbdLkFi5iskAmqctb0B9LBhpq1W5u0sHftMNN31BhTpGIeiNDuigW19gDk23ByFpWUQTwd/AF6y9Ld8ZBC9CKVgXkPm2GOQcLGdx7o7MHAjnlND07DJJbNLly7l3//+t27s8XD//ffTunXrDf64fg+DoTnzjQ1Zi/WBsoCDnBrF6XFY41V0I+6BtCqN3PbWQswGrw2pFVCWpRt6HHFiO7uDjYbFTcfJi6NIRwEKYbssRaFkC50NZjiXl6GNyE2vbI2RjsMPP5w27dpTOenjej92xeRPyGvdhqOP3jbHetUCb6Kz98EoLVJXcBQA96PmWRHku/gXqha5HwmI25DPYUOCw0Ym0ZOQsNgdRRNGow6lZ6MIRhvk3chE/S/uQO+tPZBI6QV8YENuXIbRux3BMQelBWejyMTVqKx1mHNfI5CoGG/DdWHIL4VIBmQE5WXqg2avnIvSOCeiaM84FFU53HkML6DPwL4b+dwamoZNPuGJu8MknN9XrVr1l9evrKzc9FUZDNsYr1VCeol+b5UOHbL0++QolPiU1456oUWJDHH+Snk4kiKQVA1RnyMw4vzh68Dp6eFBX7pnAwtQ2Hw8axvkVqJIx28o2pGG0iwhHYZUEtUrYdb2ADQ1Pp+P6665mmHDbiBtl8MJtulZL8cNrZxP1fSvuXXEPfh8W1Ns5++JouqSX9A8k1NYWzRUIjGyBL1vOqPUhx9FGvLR+6WuMF0fq9GslXyUJjkECY870GZe6fzUoqjCSSglcjWKOixw7nc0kOtUp+xuwcHOYt9H5artneu0R5GOiSj6cgN6r35kw1O1sKYKarOhg9ODozWqvrkUCa5DnMf3g3P7o5zH8Y5znMP/5vEamp5N/iR27twZ27b//ooGw3ZCKVCyFNo7dau7d1RqxLbhlypFLWIeRTqyy2BBC0iqhHgQsqugKAOwIG6Bz9H0HicH7oqOVSRy8RY6w+1bZw2VJPL4xSiPn4EERhylW9zqlTBbV6QD1Fjw1ddeZ/bnj9HyjIfw+Dc0NH3jiEdClHz2KD179dym0r82OoP/AjXLqjtqHiRGPkM+hjT0up6JBOiPzt+ORemOv7uft1DTrG4oWnEU8B/0/rgAlbL2Acag6MYNKIriQ36KsPPvy8BONky04QoLulha5x0kxtk/jdI+JzmP4QSUnvECz8Xh3RooroWqbOjjVYVMNorwXYWiNUNQCukH57k5znksn6HU4bl/85gNWwcNMvDNYNieeDsG2fnO5FYP7OtUraywYUkckh2fh2Xr97gTzYh7wBOGZe3BioPHTpzN+qzEgDYPiSoU18exEp0FuoTQJpCLBEgaEh0RJDrqVq9sbZ4OULTjspdfIlyxmjUf3Icd3fy+GnY0wuoP7iW0eglzZs1ix5125rHHHqOioqIeV1y/2GiTv51EpOFAEl/QNooO3I5ai3vQWf1N6PX9FxKid/D3foZlKAryOvJTHIfSEregTqS7IV9QEvJr7IN8ExehNuadUeOuDsB7NrSPq6X5XY7gKEMpkFdQOuRlJMyHovffOSg9A3BfHN6pgrIQVGTD7l7128hG/o+rkKdkCBJJPzjrOdG5/ffIRH0uW0+HXcNfY0SHwbCFfLoGkmv0e/eWkOoMd5hmQ6EHAjVOE7AYRCwI1jgRDEt9OqrTFA3xxpwIiQVJjujwOD9htFm404ncRk+gtIkPbVY5JBqCZSAxEmPt6pUwW9f8CRtNA/1sx76ccssH1C6bwaq3bidaVrjJx4qWr2bV27dTu2QquUdcR8vjb2OFtxXXXncdvXfow5dfflnv699S5qGN9Xc0Y+QY1n59FqNqkm/Qc9UHRUB6oeftbZR+OJm/fl2jKIJwDnpv7I4iGu8gT8clKDXXAfXnKEDzWhY693+l8/fzUVRljQ3VNrS34AYLUi0JogPQv/9DKZGJJNqlX4VETi1wUwy+qYDKKJRkwcEeRURSUVrpOtSAbG9njT84az7VeTy/ojTjZRjBsS2xtZ3wGAzbFAttsBcrSgGwX5fE36bEoMQLaY6fI7UGStPVlyPqVyVLbQCIK+oRiELc54gO5xhuBUsczcro7Pxe92xhNfqiLkIllDEUtnY7k7pzVuo2B9ta0is1qL/DXBs6L4DJ+x3MLvd/xay7TmXF81eQue+ZpO188N+mW+KRWqqmf03Jdy/jCSSTd8rdJHVQAiqlx+5Ey1ZR8sXjHHroofzrX//illtuafgH9zcsR9GGNNRhM2edv5eiCo0i9JrnoQ02DaU8xiKh8dcTrsRc5N2wUVruKJSyuxENaEtG4qYVEiA7IJPm1agK5kgkWk5DYmI3G8bZcJEFvev4N6531vkWEhmvoTbkA5CA6YbSf7fEYHa5SsaLsuBkS1EcL4pe3InmufRDc2TGofLc053HMxN5Xm50bmPYdjCiw2DYAl4MQXahNvWMIOzktAiN2TC5RpGMqFdNwVqUQUkGpEWh1g/EoTgHUish4lPHUssJb/isxNlbHImEOSi0vJq1+zMsQm2ei5Bx0L2dn7XFhRvpiJJoKtaUFKJmU5U2tKyE6WsgbEGbboPZ49rfmfD2dfw65lnKfniN1D77E+zQl0Dr7nhTswCLWFUJ4ZXzCS37naoZ3xAPVZO604HkHHABnqS1W1/5MvNocdJdlP34BrfeeitJSUlN1luoGJkha9Emuq7ZM4xMl1NQRMKPPBodUFTkVZQCuZO/33DDaNP/AqVS2iAB8ShK2V2BohYDUSSlFkVClqMoyBWo6+n+zmXv2NDTltdiuAUZjn9jOEqj7AU846z7diQW9kN+kFwUrbstCvPLIRiAFalwiQX/RO/br9BI+/+iiM77zjF2RikhC0VeRqHU0tYing0bjxEdBsNmEgOmLIe8iP7fr71SJgDzbFgTheSIIhdxD2SugZXdIKsCdRuNQ20SpJcnGoPF0d/ctAokIhvL0Rf3FNbeqBaiDamYtaMYFmuH290IiE3Tf1nPRGPVfTa0CMHEpVCUDHkroEURZFmZXLDHc6RfcQur33+GZeNeY80GSmq96S1I6z+UtP6H4c9qvd7rAFiWRdbg07CjIW648UaGDBnCrrvu2jAPcD1UobP2pShFsG5HEhsJgE9RxCGOfB0DkE/iCeeya9m44WWTURomFdgJpTVWIAGwPxKqM511fIwiFNchAVGNUiELkP/iNaCtDVFbwvU2p3ldKeoK+isSKTej99npSMAciqIlSSjlcncEFpdBWgqsTFZb9NPRe/UTJIaeQpU076OoRx/nPlwD9UiUhtoyq7GhqTCiw2DYTMbHIWmJPkQe4KA6PZenxWGlR16PWkvpl2A1RIMQD0FGNZQEVLES84I/rAFxEdQcLOZEOmz0Je5HG4H7xVtXdCxBDcOWkhhv7w6Ac1MsdWlq0fE18BGQ6Rhrx5RAiQdarwR/BFqugZ1mwmcHQ1Z6F4b2vY/kXe+j79CfOWi/fQh23Y2U3oPxJmcQyOuKNzV7k+4/a58ziSyZwtnnnMvUKZPxeBrW2hZGG+okVLVx1nquMxelWlwx0Q1FGSwkCNzJrRtTTFyFUlY/owiBG914GL0//omiB91R74w5yJTawbmPk1DaK+j8/gSwtw1f23CWBf2dN9Rs5/oVKDJxDDI4n+Yc6yAUNfEAP9rwWERNvzIyYHUA7rXUdAwkxp5BqZvOqOnYOOfxXug8D2uctVzH1lXybdg0jOgwGDaTl8sgrVy/t8tSfw6XqXGNrs+KAn7wRRXZSAYqfJBdAeE0mUdrkyHNncJmg9cjw6kHiQen2OWPUHoBym+7LENf8EtQNCSAQt628/v6REdTfPDjqDJhPtDGVhv40WGgGHJXQVIYctdA++WKDFWkK+ffohjmngHz3noDX0o6LY+5Ecu3+VZYy+sjY8gFzHjjJr755hsOOuig+nmA61B31PxQFHVY97VYg6o83IqiVBQxSEdpjVGo2dWd67nt+hiHmoK1RtGNE9B74lqUmuuMohL9UEQlBfki3kZj5W9EYuVs9Dq9ZUu4/GTDLRbkOot4D0VMWiKB0BuJqsvr3O+hzpo+sOGVECyvgORMKA9IpAx2/v668xw8T6KfxzhUUXOJ87jLkcdjfd4Xw7aFER0Gw2ZQDRQsgdZOX429Otf5mw2LnNLYkF+eDn8YqtIgzwtFHnk+wn7wxqE6GTJCuh6WIh5uJ9Koc8wYCR/GKhR+dyki0aVyBYpiWOgMNICETl2aQnRUoWFjMaC9DZEwvB+DdithYRW0KoewFzosh/4z4JWT1dMktwgiadByQIg7jn6RpL5Dt0hwuAQ79CU5rzPPPvtsvYsOG0UmRqPKizv58/Ndi87u56HoRgRFQDoiIfI/JDz+DwmRv6MMlc3ORJt+OxRFeMg53tUodeNYifgKpW0OQ+bUnqgqZa5z3ZfQ6+Sx9V4fbsln5Po3XkGzUJ5BIuBdNGRtV1Ra2895Hv5nwxc1UFANviyw/aqgcc2vLzi3fR5FZD5CgqMDEjAWuv9/o4iHO1XZsO1iRIfBsBl8HIX0AkUfknywV53e1L/HoTwGqbVqChbzQHY5lORCpyhUhDWqO+YBb1Tj7u2IYyK1wONZuzrFg77AXQESZ20DYRXauAJIdDi9xliDNqx1h7o3tuhYgc5sU5AJsSgMH0ahbwV8aUGflari6VAAO/0ORdlQlKsNatBEmDYU9poznaqKClp3371e1mRZFv4uA/lu3A/1cjyX31HUYAf+PGoe9NyPQRGQ9ijCMRgZQ6OoNHRjB7O5x/scPb+dkVn0ZNRh9GrU9OsQEmbRj9D76RIUGfsH8m7MRtGR3ZGvYogNn9mqKtndiW6UIGEyCaVVhiMRMwKJmL2dY3VAIurfcZhYBYVhiGdDplfr7OGs+ylkcH0eiehPkOBoi8pzLef5eRD5Pjbm+TBs/RjRYTBsBh+sTDT96p0HgTqfpKlx+SvSqqDcC/GAzJFLekBOCFKroTBTfTviXpXQ+uJQ6wxucctcXeHhRUIjhDamuoLDFSM16IxzKjKbQkJ0rHum3JiejmmoXXcSsIcNC8LwflSb20vAgLm6XmY59JoLnVbAiydBp3woz1K0Z+kQ6DtqMpbHi79Vlw3d1SYTaN2dwl/eZtWqVeTl5W3RsRajipSWKJWRsZ7rTEelpG3Ra9sORQV8KOXxPkrD/N1gNpdC1AysANgFbfaHoejGSiQAJqCoShYSHB2d+7zVuc5wZNa8yFnf28AgG76yYZgFrZ2FzEL+jFLUO+MkFLU6B6U+9kHlshlIBN8Vh1kVUBaD2izo6FX6ph16/z2KBMYL6H37KZrzkues200tPoLKe3tvxPNh2DYwosNg2EQKgZrFkOH05jig29p/n2bDGg+0ijoD4GyNr6/JhZKVkOykWvwRpV9yIop41PpUERC3Er013KhEFJ31FaIvZpdSlD4pRl/eZWhj8aAzU3fCrF3nNo0V6fgczYLxAYfbMM0RHHv6YGQN7Pg7pFdDVRAyQ4pyFGZDfnvIqYQdp8G8QZCXAaVr1uBPTsPjr79iX2+q3AFr1qzZbNFRiMQGKArQaj3XWYHKSQMkok43oNdmBRJf7Vh/ZGR92Mjr8SKq7NgBzWZZgkyiu6GNeizq5DkavXdOQO+d49E8l11RufW1qCKkgw0ptjwgd1sQqOPf+D/0/noHpW+KkGG0PfKcXI6E7BpgeBwWlkHI0iDDHTyKcLRw1n4fqqx5AUXoPkXCpyWKzriRvcdRFGiXjXhODNsORnQYDJvIq1WQXixRkJsC3XMTf1ttQ2VMno1qxxjqi6oRWIoHYnGwovJvBGshFITMCIRsIO4IDcfT4YoOPzrrq0QbQt3KlVVIVBSjCEc18gKkIYNpa+fvERLRjYYWHTG0icWd9Zxtw0+O4NjFCx9Eof08aLsG1qRDhyLoNxlalMOLJ0CfObCglzwdPx6oqohCjwfbjv/l/W4ytmp8Nqd6pRxFLVajFEXn9VzHHcq2EkUaStGclE4o+vAKeo3OYeO9CktRdKMSiYvOKH3yMHpvXIVKqlegCph3ScxNeQd1Pr0NRRX2Rhv9g8AhNnxiw2EWDHHERgT5UV5DG//TSFTNAC5GTcP2ITGMbglwTwwWloLfB2syYJCltaWj992dqFpmpHPZ5yjikY0Gw7kl3c8hMbX3Rj4vhm0HIzoMhk3ABn7IhxzHYDGgY6JrKKhUtjQCGTWaIhuPgy8CoTTIqIX0Ul3u3iTmgZBX/SpAfg4fyNvhXMeDNvAalKuvU5nLAiQsilDZbAQZTjPQxuZl7WFv7mNoqA9+JQqd90Zns5fbMCYCH8SgpxfmeqC6AHZaDCWp0HoNRJNh5+mwqgUs6AY7zYVu82FuX4i1kRdhWocORKoriNVU4E2un4LJaOlKLMuiXbt2f39lh1pUrTELeSf6rO+4JCbEdkav2yEkZqL8QGIw25kbu1YU2XiHxJn/6ahHy1XImHk88kgcgvwRVUgUHIA8HHmoj8YPyDPxK/AtsL+tKa//tKCj88YsRsbNSaiF+b/Q++pj1BJ9EIqc7OusZRrwaBQWlUFmABamwQGWohpuCfdtSGQ9h4TwF0j8ZKAyWPc9OQoJ6EM28rkxbFsY0WEwbAK/x8G7VB8crwUHdl3771PjCllnVsEarzwbqVVQ0RLskKbLrmmhEfZRLyRHIRJzKk4cT0fduSsWiaZecSQyBte5P7cxWBGqGIijtbkTZi3WHvbmlt82xAd/OfCks76fgOtt+CwCH0SgvQdSAvB9Gew3XSmkYAjiObD/Z5ARhvf3hj6zYd4OcNCn8PklkOzTjBH/gAEAhFfMJbnrgHpZb3jlPLp260FGxvocGGsTBb5EhsyjkeBYXynyb8ib0R29fi1QJMOHIgEvoTP44Wy8r2Yuim54kHDpgUqkH0KRjyuREXQ2in68hV7rq5DX5DiUdlmJ0h/XogqSTja0sOUD+pfzvgMZWc9Dqbo7kTCyUMRiNLAn8oC4PovvgZFhWFAGbVNhTjIcb8k34qYG/w8JmWdQOvArlP5JQ14Q9/34ERLI7nwVQ/OjYbviGAzNjOeLJCIsoHMuZNRJwts2zIxDoaUGYJ4YxHyQUwwlLSESUdqlKkWiozYZWkfUq8O2dUzLkwhFWySiHF705b2ctVugL0MRjiLnOh7n30wSY+1d0ZHsXOah/o2kU9CGsg+KcNxswxcR+DACuR7YK6jmULtPUDv4yiToXqRUU9/psKI9zO4FrYshtwSWdoVgF6WS/ECPHj1o37ETVbO+r5f12rEIobk/cdjQvz6fttGmegfyZNyNTLDrCo4lKAIwET33tejs/Vi0iT6HUh2XIRPmxjz/IbTR/xMJry6oqqUVEhrJaMMeg3wWK1HkYgdnnc+gUfUPoUjEEFTNcj+wpw2TbWhnwXV1BMe7KGJiIx/KWeg9dBGKkOyJBERv5zrv2TCyFuaVQcd0mJcCZ1tqge5DkbfrUPn2kyQEx7coAjesznPxLYrkra95mqH5YESHwbCRRIBFS8DvpEL2WaeQYpFjxIvFoconL4fXVtvzyky1Qa9MUctzjw01yTpr9ceAmDN11pPoSum2P3cjHWESXUldlqPcfQ3yDLjNwNxIh01CdCQ5t/dQf5EOG4Xcv0HGxLnIP/BxVGWxqR44MwCX2TBgMnRcCctyodtyWNQbDvsIkgPwbT/otgDm7AS9focpg8GXpE0S5Lu44rJLqZk9jmhl8Ravu2rW94QrS7j00ks3eJ0paPMsQqLjIP78hVmGNtO3kBhcg6Igl6LX4Cs0X2Z3FGFosZHrm4LSJzNQdKM3EhrPIj/EpSjNNh61NH/Lue/TUVriKJSiuBBt5sOQQH0LONSWWDjXgsMtpQcjKDIxzLmv9537LUeRHdDreyuJFu3P2PBBDcyrhI6ZMC9J0ZVrnOcphARTFBlJk0gMqgui94nbcWU8EqtuMzBD88WIDsN2S3l5OQUFBRQWFhKLxf72+mNCkLRSG3ZKAHZbxwowNa55KylVqkoBVaWkV0OND9otg4o0dSZ1oyBxr34n7rQ/R6ZDx1eKB20INhIRkXUfAzIhWugsMcm5rptHj5IYax9EeX4f9SM6oqjXQhXa4CrQxviBE+EIWHBtQL0eui2BLotUKpxZDrSEnNXQaw6s6Agz+kC/OVCeAZXpUNsV8Ctl5HL2hReSnJVJ8ZdPYtv2n9azscSqSikf+zzHHX88ffr82ZUxD3kYpqGz+uP488j4MPJXuCbJUmSsvBX5OOaiFEotSlHstJFrq0JRiluQd6MtSnXkoCiJD/kyvkWiI4b8G62d+5mGqmhuJ1FafSWKtFTb0CGu6bB3WtDD2d3dSpQ3kMB4w7nfuag9ei8kOG5CAjYM3BeHH6tgQTXkZcHSgB77Oei9WIu6hwaRxyeABMe3KLJxI4lmd9Odv/0TsyFtDxhPh2G7obq6mlGjRvHhh6OZMPFXVhQs++NvSckp9OvXj/323YcLLriAHj16/On2owrUqhtgp7ZqV16XqXGYY0NymSIaUb8ERsAP0TiklULYGYZiA37HdGo5ZSoWinQkW4nhb24FSxwJjnU3v2q0IdkkWqC7kRFXsNSNdLjm0i394FegHgpD0Blqd7RBvR+BjyKK2tzoCI6UCug9WWmkiAf2XAWT+8MZz0IwD8Z1USfSZT2h51yYsDd0TtbjcF+FmcB/cnLo+swzTD/heComvk/GoOM3ed12NELxJw+SGvDy1JNPrvW3AtSSOwVt8Otrt20jX8dnqBEXyJcwHG2mZagqBTZ+MJvLD6iSpA3a5HuhCa0PIi/PxcgX8RXqNvo2el8ciqo8Lkbpi4eBD4Bz0fvnXuBoW82+drIkWlzz8+8odVKMRMX5zm2+RsbPvZ01HEmiy+09NiyphOURyMyGUq9SS0Ocx1GFBEdL1DjMh8TGN87vN5IwNc9DKZ2bMJvR9oJ5nQ3NnlAoxH333cfDjzxKeXkZKR374uuwOy0GnIoVTIV4jGhJATNWzmfSE8/wwAMPcMihh/LYo4/Su7fscqU2lC9ReNwCDlqnN0fIhqU2LLOhdQi8AUUygiEI5YC3BqoDzsRZS3/LikNtDPDIlIqlzdoNL7v/uv6OKGt/YF2jqCssVqKNL0LCtxFGZ5Ru9UoNWy468lH55Bmoh8MBKNf/oRPhsIFhAbjBgtUxGPyLvBpz2sLAuTBjZ+iyENqvgRVdYWpfOOYL+OIoGDARVvaE3kmJ9T8LTHT6R7Q87jhSrv8/Sh68j3gkROaeJ2N5vBtY6drEqsso/uQhIstn8dFnn/7Rm6MYVUxUojP+DdWyzEPlo51QiWcJCWERQybIX53n5c+SdcOUIGEwG5lwq9D8lblI/HRBoubtOn9/B72uVyDj8uEo5eJFougWlMaYChxjw5s2nGdBX+dNZTvHuANFap4jUZ7qposGo1SRW3VTCNwTh2XlUBIHb7Zm5DyIWqqDRMmlKNpzp7Oeb1Ekw4sEh9uWPx8Za28iEfUwNH+M6DA0a6ZOnco/TjudOXNmk7rLkbQdcNRfjj+3o2GqZo/j+1/eYud+/Rlxz7+47rrreKvCIrlMm3leBrRf5xR2pq1cdyQGkQAEomp6lV0KxbnQYjUsbe+kUnyau9LNqeCodMpkbVuRCjfC4fo4nJEsf/zuUoTOyt0eHYvQF3rIOUaQhGCpda5b7Rxjc42kvyIPx0Voo/oHKhv9OAKjIxJVVwbgdUub3+Cp0HkpLGit8li7ldZ00OeQ3A5+bgW5xWqT3bIEfhkMWX4o92uzvRkosyWocoHvopB38QiSFycx8507qV34G9kHXUSwzYbnr9qxKFWzx1ExdiTJPouPPvuUAw44gCokmhajaokNCYU1wKvote+ANssz0fpA6YFRyFsxnI33JNiotPZJNPukH/JT7I2iFXNR5CGMIhfHOPcTQamXU9AQuQnIM/Gxc9ujnGN2sRUt+djWKPpsZ2Fh53ZvotfucdRQLo78GEtRSeyldR7jQuDBGOSXaQJyKAtaeFQSu4NznVLnNr1ResdCJbFjnL/fSKI5WiESrsPYuNkyhuaDER2GZst3333H4UccgZ2eR95ZjxBo1fVvb2P5AqT1PZCUXntTOu5Vhg0bxty5cym94mnSoh6wYFCnP99uWhymVKmypTYJ0mqgytbMlcVdoMskmN9FVSug6aleD6SEoCJFw7Q8HvBb+vJ3UyQ2ichE3V4boHRAJhIfOWhzbOncpoq1UzG1aNMuYfMiHTbqT7EEeQyeQaa/DsCnEaVV4hac74cFlrweexVA+1mwOkO9Sobkw9jdYeBv0MoPy5Pgt34w9BuYPFjRj+8OhEFJ8iYARGxtUL2BUTGIlsIOcyyObnEH844+kCd/O4+VL19LUuueBLvtRqB1d7xpORCPESkpILxiHqF5PxIuL+KYY4/l6aeeIqd1az5A5a3Ho8jE+oSCO5RtIRIF01Ca4TwSs21ecl4D1++wsRSijX8FEivrRjfaOX9/B3lF+qC0jQcJkVRnLbsjD8j7zutRi6Imx9nwpS0xcYflRNKcNV+G+m8ciipZ0lCU53SU2umLxIebXpoMPBODRaWQ6oPCDOhkqR16Z+c6RUhw7IrEhYWqfuoKDvf5KUPD/65C/iXD9oURHYZmydSpUzn8iCOgZQ9aHHcrnsDGNJhO4PEnkXPABQRaduF/zz1Kj6J0Djj9Ifxe2K/zeu4vBtPjEhmVaTrj98QgqxrWJMGg1TCrB6TUyusRiEGlBS1CEE0Fn1dCxO2lESMxEdSLBIQ7e8VlPjL8uS3Qy0k0DitHx3I30y3xdETQWWk7NBvkWZRWyEVlse85Ho6T/JDu0aY2MAStJmg+zbJsOGwy/DhQXVh3WgDBDJiYDSnV0CIKpenwe3/d34IkCNsSOmvQ5nhvHCLlipoc9Skk1cLq4wYz+KJZ9Hr0M75Z9gzLf/+M1T+WrLX2Dh07c+TpJ3PppZey4047/eEtOJT1j5oH3e83JCpy3LTWcBLpq/dRZ82zULplY4kjIfEyauiViQTFHii6MRtFUVJQJOJ4FI2pdu7nQhT1eBKZXRegFMqtaIOfCpxkwys2/MOC3eo8wOnotSlCG/7lSMQsdh7HXug1/SeJdMcY4J0IzCmFtskwO1UpmgdItOMvRIJjMGpjbqEuo187j/dGlMIBiasHkUBaX8t4Q/PHiA5DsyMcDnPa6WcQT8uj5WYIjrqk7XQg8doK5r33ML12OpxDDj2QlHVyE6U2LAhBlR/ahaA6RZNjfTHIiUEspqmp/piESDgArWNQEQdP1JmL4gXLC0FLgiNCohLFHf7mQWekblXLYrQRFSNTo9uLw3Yuq3vm7Vav1LJpJbNlqPrgcLSZjCJxVv9VBN51qlQO8sEuHoX9+9jQcjx0WAFTO6k8NtpaC9vnJ2iXDgVVMLEf7PsTzN0BeiyGF09RmWm1T2ssR2bIS22Il0NqJZzxKqSGYHU2lOZAl2Ve9mx9JDs/dyQXdLRZunQpRUVFeDweOnbsSE5Ozh+j5m9H3pM7/+Lxz3Ae4w7Iv7MSnfVnOX+fiDb9w1C/jU0p71yKhE4lcCASEhcioXEZqkC5BwmazqjnyYvObU9AAuhy9BqORP6SI1Fk4r8ondIfGGXDjRa0Wse/MRxt/o8jHw5IHNzo/L8HEh+uefktG74Lw4xy2CEVJiZr4ux9dZ6PAmfthyDhYSFD7FdIrNUVHCEkOM5EETLD9okRHYZmx3333cfs2bPIO/ORLRIcLum7HU3N/Al8+8R53H72bBJWODE1BoWl4EmHaACSIlDl+DqsJJ3dL+6gSpakWpXJ9rLgt7jC3hYQtiDJk4huuL4Oi7W9GXG0GWchb8GRKOWRi4RKOjKLFjnXcQtsNifSsRj4H/JvzENn0TehNX4d1RlwigUDfHC4V4IjB2g7H7rMhDltNFH3iMUweh/IWw2t2oJnFkxOU6qpQzm80kNt4z1AapIEUzsSjbQoB18IjvwUWpVDPAo/7qUnZbffYMp+cFUnsLDo1KkTnTolYg+zkIjojaIBG3o3uEPZMtHmOx+lXbrW+fuLaMDZbX9xnPURRWmY91BEI4SiHANQBdBMZGBt7azhZNQifDJ6DS9HKZ5DgLNRpdCbKFpRhipETrLhW1upkrstpetA/o07kTG0F0pruB7o553HtL/zc5BzeQx42obfa2FqJeySDj8lSZjcRULMLkVG1mNQygfk4/kKvRdvJFG9E0WRnONQqsqw/WJEh6FZUVNTw0MPP0LqLkcSyPt7D8fGYFkecg69nIL/XcyMsaMY2OWctf4+ugrW2JBeCZXJkF7rTE4thyU5kF0CZZkade8Pa+psulcj7S3A71H1S6pX4iLC2iWzbhmsz/m3GG1GhcjoNxltBHHnugEkOtJJiI661StuZctfMR6ZHIehDbACpVQs4JsovBWGLA909cDpXm2IVcDeFdD6FyhOg5APzvkZPttHfo8d86GHB5bYML4/DPoNlrWF3DL45gAIRsGbpNTCFWjmh78CPDWQVwD7jYeID+Z1gfQaCBRL2C064c8VJ0tQv4lcFKnYUOlqFdrAVyNhMhGVo7qtv2vRhl3Apg1mc5mNohcWeo5qkYj4HYkqt6z0UyQmj0ICBTTX5EjkuxiDUlxjnLXcitqyTwXOsOF5G462YJ86oZc1zn1NRoLhAed5iKP+I7NRSuUfJPqI1AIP2ZBfDdNrYEAm/BTQOm4l4RNaiPp//AOJM1Dr+y/Q+3cYiWhIHHVGPQCJLcP2jREdhmbFqFGjKC8vo+2uR9brcf057UjuOoD/Pv445557zh+X2zaMqVFfjo6rYXWqWnsD5JTDotYyUQZDgA2lGeCLa0NOrnFEhyVRkO3Vv9Xoy90tfQ06l7lzLFzXQhXatKLO725VSjI6U687rL1upOOvmoPZyDy5EkU1RqIz8Aucv4+NwpthyPNAjgUX+eEqW5vf0Dikfq80yPxOMGAuVLTX491hJrTZDyKvw/QkqA2qmmXsoeD3QnUapFVDS69SRhdYkFIJnkooT4YrXoG4H2JhmN5PUZKDf4TxQ+HgOopiNRIbcWT43JBvIEZiKNseqFtnlES7cxulHj5HvopNbc1di8y0Y1AUoQRFgvqhaMMMFNHohqINJyOh9z56DS9Br/vRKNXztHO9U1H05VHn3z0dwXG1pZbmLtOQcCtC/TuuQq95NYqWZKO0zOUkBFsZcJ8NqytgThj6ZcIEv+7zGhKVU3ORd+NcnEgU8DMJwXEdCROqjUzH/ZDp1WAwDeAMzYoPPxxNcocd8Wdv6jnp35Pa90AmT/qNlStX/nHZx2VQWwuhgPwbALU+dSLNroSKZAglKcLhsWFNSwjGYZVHJktwem1YahkeQAIiSCK64ZpIPShcXoy+zGvQ5mGjs98gibkrlc7/3VB4XdGxIU9HGG2IAeQ1eASZHI9z/v59FN4IQ0evGphdGYCRthpqHQn4pkD7BTCzA+SWw9FL4LduTiVOH+gxBRbbML6fKlh8yVCcAT/tBYFq6JEEaRa8a2lgnq8cStLg8vckSKwY/LYLdFkO2cUQSoX5h8LBHkViRqIy3qPRprg+wWGjst/bnec0l8RZ+4nOY1+MKkJWIR/Epo6Xm4yiIr8DB6PX9wb02lyGIlQj0Ov4C4oWvO6soztKYUxy1vNP1JzrHRIlpyPQKPrCOEy1NazNFRw2isycgV7PB0lMcF2Goji9UJroJhKCYwVwZxyWl8P8CHTPhul+pU2uIyE4ZiABcwkJweFGxcL8udX7q859HLiJz6Gh+WIiHYZmxYSJv+JvN/Dvr7gZBNv2AuC3337jiCOOAGBkESTFNWOlzKdwf21QZ/dpYW045WmQuwa8cahKg7Y1Eh25tfoyj1lOC3TH01GNBEMZ2gR96AvdFR/FJEQFSEQsQ6LC4s8TZiFhPnXFy7of/BJ09nw0yrnfi8RGP+fvP0bh1TD08ioNcHMAfrK1cR0BxFdBlwkwr43u7Pyf4L399XwMyIc+R8Psj2C2X6mmDitg9q6QEYHlraBjEfRPgn9bkBsCqwyKU+CAubDzJFUDlflgZVs9rv2/gfFHQm66TKKz0Cbd9y9evyVoE2yLxNR0VCba3fl7FTJnVqIIQO5fHGt9VDrP4UTkj1iJGmvthMTcNDQArj/wAvJCLEI+DttZyw5oU1/lrMXtpXEj2tinAufZ8D8bDrR0P253Ude/8Y7zmB4iMQn2FyQIjkS+D7elOihy8UQcVpbqfZCXDfkeCR5XWIDE1E0o6nGoc9l4lBoKO5fXHUb4PnqvHb2xT6Bhu8BEOgzNhoqKCgqW5+Nv1aVBju/NaIU/OY0ZM2YAMKVcJYsEIbcaKvzyc8Sc08JYWAIkENJG6Ytqg0gFam2VzQbj6uoY9Cu870URDNfH4Xcuizj/ViHRUYiOU4ZExnIS1S6u6PDw594RIZyUTp3LFqIz4gtQVcF9KJ3gCo6fo/BSBHb2QaENNwSgwFbYfRDgCUO7b6HSDyWpcOBUKGmntuctymH2iTDtG/XcmNIX+k+BNC9M7gZLdoQ2IfB64RGfRIi/DMqSoEc5DPlFFT6xMPy0J+w8H9LKYHlnWDQASi2lY+5gw4KjDJWYvos2/PnoNrejzdlG/oj7kMfBLQfeFMah6MZK1A7c9U140Aa/AkUo4kg8nIOaef2OIgNuM62Dkci4E5l4T0JRgodRlOoAG5604SILDrYSgqPQec3eQaWrr5AQHK8jj8VhSFBeSUJwTAQej8GSEqjxgD8LKjwSOXUFx3jUqO1GEoJjAgnB8U/Wjix9hVJdp23Cc2jYPjCRDkOzoaqqCgBPMOVvrrl5WJaFN5hCZWUlAC/nQ00WeJLV0js/CGmVMoqmVmvSbDgIOUW6fW1Q81ZSLW3IwZiEQsiCPG9ilL1bqQKJD2iMRDvzUlS5kkOiG+k8EkIl07meTaIDpMu6kY6fkO/g/5Dx8Gl0pu1uIOOj8HwE9vDCjDgMD8rHciiKGLQFMn+C5EKY2Q3aF8KhK+C/R2qDrcqFLgHY9Tf4wQMr2qhMtrA9BHwQ7gLxIpiXDJlxqAlpjekROCQfOk4H4rA0W2ue1R0G/gSfHqsH/pClTXp9hFFr8uloI/4JiTbXtwE6y38V+Q2Gs3bH142hGPg3MmUORdGUwSiF8V8UmTgCdQp9EZlJWyKfg4WEwP7IaPkBKmed4bwmt6K02QjgHzb8Zqul+F2W3kMuU9FrtgYJj2EkfCl3oCZoQ5CRc+/EzfgC+DwKc0sh26+mXxmWUkF71rneOGcNt9a5/UQSguMK1jbY/oTE1FWYibGGP2NEh6HZ4PM5b+d4rMHuw45H8fl8zKyAaSHIjcCyLPkz4kka7GZbahJWnQRRLwScKpXiHEiNOy3OY/rJ9qlctrVPm4abBoG1W6GDREfE+VmA8vJFSHT8SKJxVSqJybTrRjpcT4cXhe5LUMh8Jirp/D8SfRUmRuG5COznhZ/jcFdAoXn3TH4PC+LzoOV0mNIBAmG47Cd4e39IL9X6s06DXd6BOTGY0Rd2nqbS4O/6wi7tVDZcHYfWflhVqwPHAvCPldBnjDNkLCzfx+DJMLUbzOgPrRzRsgN/xiYxlG1v5/mZiTZH1+BYhtIaHuRZ2FB1y4awUcTiWRQt2RdFqm5BG+4VSPD9C4met5Cx9U30eqaSSHGcgMTb6yi6sQ/yeXyGBMVFNjxnw0DL6aOxjn/jXiRe70bpG8u5j3ORqbgfqsZx27zbzn1NDcPUMnlpZqTJF3Ira1eYfI3SNMNJGEF/RZNtQ6xtRAWYglqfD8MIDsP6MaLD0GzIyckhJTWVSElBgxw/HqomVF5M586deXmZKla6eGBZzJmfEoPqoP5NrYBVLdQMzIrLRFqbpM2lzJIp0h+HFL8EQnufvBJuJ9E4+nC6KRWv81ONvsyXoMqHui3Q2zi3d2e1wPrTK6Cz8B1RiuR7VH1Qd/DWbzF4NgKHeuGrmCIcOZb8BPOQUbGsDDr/AEuzoNYPp38PK9tAgQ+iyZC7B8Srwf5RaZCFneCUXyE9GSpbw9u9YVU15HqhtlrN0WqCcO0cSCmGlALpx3ldoXO+mon1mgHjjoR2eWqWte7G5g5l64XSLROQV8LdcKPoDH3SOpdvCivRRl+ASlwXoChCNxSpmIwiQQehXhh7IaH2JHpNd0H+ky+QKLkRVRo9hQyaWWij7wocbsNjNlxowQ51HmwIGU7fQ43E7ichFlY5j20v9D66ioTXIgo8YcOKEPxSAYNSYFyKxtzfSaKHB87z9LjzWPs7l/2G0kIh1AysfZ3rzwFGI+FqNhbDhjCeDkOzwePx0L//LoRXLmiQ44dX6bgtdhjA3FKIpEBaNmRWa4JssuPh8EdV7rm8g5NuQX0q4j6deVZ7JESCtjN1Fkjz68zY7Rzqeji8rF1x4kYw8tEG4bZAr0QCI0zCRBomkV5x96tydDZ6MOoy+gE6M7+etQXH02E4ygefx+DmILTxwMO2NpzzgaoYdBoHNTWwPAd2WAw9SuDlQZDsgx2zYUkeDP0OisIwowfsMEtDx+b2Bk8LmGZBi2qNu4/EoTQF/jEfajtCz48hEIfSGEzfGXoXQLAapu4NOWngSflzT4pH0Ua+J0pRtEfpBVdYTENn7KnrXL6xxFFk4RIUGdkTbeK3Oa/LlcgfcycSgM+j8tQ5KCriRdGO41F05UnUuGwlimjcjl7rEUhsxOLwga3ZKXUFxyrkCXkXRR9eIiE4JjvHH4rea7eQEBw1wL02LKuGHypgvzT4LlXj7u9nbcHxHvAESh31dy6bhNJVYdQwrmOd67sm3etZe+aPwbAuRnQYmhX77bsPkaVTiEdCf3/lTaRm/gQys7L5MaU3UQ8EAzDXDy3L1QI9tQqwJCQyyqA8Xd4OXwRqUiBgyw8RsYCYNr9KFAWxPUpd1JAQHT4S4sGHxIbt/F6ENlXX01FL4su+gkRvj7Q6t5uHNt5+zs9I53aXkPAyTI7BUyE40QejozKNdvLAJ3EZLU8HSixoNQ28C2Bme0itgUMmw2e7w8CVQDJ8ug/cHILF38qDMqcH7DIL2vjgyx1hdA/IjoIdgZQwrMiE3sXQrQt0/QxiVVAUgyn9YfAU+HkQZJZBaSvo2Eqt193W768hL8oA5/koRaJiH+f5W42MshNQNOdANj30vxid2X+A0iExFM04HUWNHiLRYv1dZ10nOetagyIZw5FwOYREZMPtYXEhMl++BVxuw7u27uM2CzLrLHYyik7NcP59hoT/5l0kfI5DouhGEr1zi4G7bCiqhJ9r4KAM+CxZ1TX3s3aK5E1UXfMQeo7d+x2N3pcXkJg+CxJNzyLB0TBuKkNzwogOQ7Pi/PPPJ1JdQfXscfV63HgkRM3vX3PyOeczu8KHJxX2Cqq7ZlqtztbjUTWt8kXUpyPqU+VKSo16dbSwIW7rDN6yJTrWOGWyYa/+H0LiwS2RtdAXfQrOjBbnsjJU9VCKNpgYiVRKJYl+HynO7Zehs+qeSIg8gs5UTyWxAU+LwRMh+Icf3o7CPwPQ0wu/x7Up7o/Ke5MLIPU3mN1a93vM95CcC2XZMKE9eAZqM/72J/BVwcwu0H0BZNowKQ+WtIYsp1OVDRSkQ8cy6JUGc0ogc5y8LzV+KGwHbcohFoLJ+6k/SFW2KjPGIh9DW5SS+A15DE6p8xy+hTb+U9BmuW666e+IIHF2NUpj7EKiiVgt2uTnoyhFbxQd+AcSfm6TsiOQYHnZWcMDqOrneZT62Alt8LVoOuyDNhxlwakeDdIDPU9voihTLYpg3EEiOjUC9Sg5DBlrLyTx5b4MRTjKymFSGA7NhE+Dej3vZ+1KnReddT9CovplChJbURRhqdvntxiJrmvYdF+MYfvEiA5Ds6Jbt24MPewwKn95i3iktt6OWzHxA2K1VSQdcQmgOSu7pEE0qo3XY0PYpyhHZhks7CqjqD8K/ojKaHfzyTSZGtExMz1Q5oE0D1Q4pbRuJCNOYrR9DJ2xupUnERQRcVufh53rZeIYL9GmG3WO9RISJzc5152EogAH13l8M2LwnxCc4YdRUbgwADt7oSguc2IXoIsFVEObn2GFBcWpsMcM2K8KXt5Hd3xAK1jYEspj0G2M2sPP2gEGzNDz81U/yOmi9ExKBVSkQKta6O6B6SmwxxvQ3lZa5ec9YOgP8N1eKiv2eKBPCz1n0yylinZBxsVDkDBwN9CJKOrQDpkjO2zGaz4bbd7fo46hVai3xsnI6/AwsBtqc/4pSnmdh0RKvvP63IKE3imoV8YHaCDaKmdd5c7tD7fBb2s67M0W9K8T3ahFKZy7UYTkvyTEoisE5qGoxVCUNnOZBTwShzWlMDsG+2bCZ35d718kTMM2Emcfop4ibu+SqajfRpy1zaig99nDyBBbtyGYwfBXGNFhaHY89uij2FXFlH73cr0cL7x6MeU/vcFl197A8rRu5HjBkwULPJqnUhtUD47agEyk6eUwv7su88ZlILUt6OaHahuSwvrgZfqh3IJcp6OmB0U9PCQqV9zy2WQSKZS6ZlELVaD4nOukkjCjRlBaIQMZGcvQRroT2qBcfo/BoyE4OyDBcZofdvdqvPwRznoOs+S76PobFK+ABXnQqhROmQev9oPOBWD3gOd3UfohMhn8RbCoA7RfpsjPqiTI7wsLLfCEIGqpEqNFDNbkwAlToOsCKI5piqwfdSit9MDveyqC9HtLhfNPQVUUrdAZf0/nsRQg4+NcFH3Ym01PpdSijff/kKjZgUT5aTWKTsxGIm4Qigoc5TzPzyKRuCtKb0xGIuBYJEAeRgbTU5HPYxTwTxs+tiXQ7rSgRZ0Fr0TRjXdR+mgkEjqgFNsRSFC1da5Xt3vqT8DIGBSU6Dj9MmGcT+mX20kMrbNRye7XSNB0di6fhrwdoH4bvdZ5jh5EFTJ1UzMGw99hRIeh2dGzZ0/uu3cEFb+NpnLal1t0rGhFEcUf3EPPnj1pcc5w4jbkpkDbJBgTVfOrSj+kVKknhy+uSEdxtqbLplWo9bnXAturM9OkiFIsAZ96dLT0KiUSQmIhytqpFR9OPw/n8hL0wXWbia1wbudHG1+Jc/n3KNy+F9osH0IGx7ozWWbF4JEQnBeAd6JwtB/298l7cqatpmNXWlBpQfp8WDULFrTV4zzrO/g9HZZ1hsLWsKwPHGqpn8SBX0GxnWgGFvbC8t5Q1UrRHkIa3nbiMshtBdkx6D8KUuJQGYVf9oTDfoBv94KyLLVJJwvSA5pVU4w2zn1JDGZ7CTXFOheld4JsOpPQ5j0dCZs1KLJxPDJ+/huZNu9HEZYpqDz2LWTI9Tn/PwRFMu5F5ampwNtIiHQlkU451YYHbJliz/UkpsO6azkD+TeOR5GIts7fZqCoy+HovTAMNTxz+Qj4OArzS6DWC20zYaZXYud6EtUltvOYfkKCw61GmY6EDsibUrc02RWzJ7G2+dRg2BiM6DA0S66++mouuugiij77D2Xj38W245t8jPDqxax58//ISfLw8gefMK0iSI4H8lrDLl5YEoecaqj0qbLCQp4NT0y/J4VUvVKTIh9FsWPK8IUhLQZRjzwSeT4JinISrc6T0THcpmDu5WnorDUJiYtsFMpPRh/mDNST4nd0Ft4XbVA/oI3J7XAK6p3xUAguCMD7UdjHC4c5u9EdtkTL9cB8C+athtAUKE9SeexBv0ByFH7ZF7ougvLeYOUoBXHdbKjKVzQkq0j9O1r54KvBUGVpoq4vDG2isHtvTdlNnQkdSiE/Dou6QPdl4E2HZZkwcwf5Ylq3kgi4Em2eQbRpfo9SD33Rpt56k19pRZpGoJTDfmgz9aLoRgXquDndeQ4PRJvuPsj38CjaiDs4148gk6kPRTJed17LG9H670ERikxbI+Svt2DPOmLD7aNxMRKL1zjrck2anyDz7+kkeoNk1bntC8DkCEwqhcwAeDNhtUeVNJeQiPzEneNOQYLDbfA1A3U2tVDUqm6n17jzeA/lr1vOGwwbwpRTG5ollmXx1FNPkZWVxQMPPEB44USyDrkcf+7fZ/fjkVrKJ35AxU9v0rNnTz7/7FPejHUiHocOyRBKVwg8HIMkp1olbstEasWh1HHUBUP6iXk10n2p7UQoYpAWhyInvdDWq14PJSRmriSj6EcMbTauMTIblWC2IdEYrIBElcIiVKVxIomBYp+is/Ys97kB5sbg347g+DgKfT1wgvNt8EZcaYILgAkeWByCIydDdTEUtIf2BbD/SpjXHawSmLwnJPdSXv9OYM1XkG/DhH5wwBhI98D8FpDfRptk2Auda+DQVvBhAKKlsO9Y+TiiNkzbGc55D947Gla1htYVEEqBVql63Ac4j2MxMmfu6Nzv5nyZ2UiQPYFKS09GPojzUermSef5HIC8E+87r8vVqGqmBr2mp6JowKsoInE/EoD/do7VhUSzr2tteN15L9xtqRLHpRYJgQ9R1KFuF1BQKucz9PpmsLaIiCDxUBOCseWwUzLMTVX30ovQ3BWXKOrzsci5jeuFmYEiMl40M6VuozAbVdwMYu30nMGwKRjRYWi2eDwe7r//foYOHcrZ555H/nOXktxlV1L7HkCgTS98Wa2xnPaO8VAV4VULqJ4/gaoZY7BDVfzfjTdyxx13UOEJMnGSRs/vnacqApCfI+QH4hAO6Es5rQIWd5bxMbUSSrJVqbKjH750enNEPPIxrEaiI8+r29aiDSSEBEI12kjSSUyYzUZioyeJctnfkSgZ6/y9DxIAv6KZGachIeNSHIN/h+GiAHwRg3YeOMuvtUyIK7oxGPjZ0nrPnQFFi2BmG3kzLv5Zd/hOP+hbDEk7qIz2NSC6FPJnw9wcSK6CnCqoCcCHB0HbMEzJgvZF0CoJqlL1GHOnwYAlsMKGyf1gwFSoaqG0zcqW0G05HNkJVlrq8xFFoqiKzRvM9sfzgCIWc1GqYgkSfXegzfcevbRc47weD6KNOAV5MzJQ2uQq53hno7V9jKIRq5FosEk0+zrLhvttGGrBAeuYTVag534K6o0xgkTKJIYMm9UoEtMP+XRcqlDVi7cWvqyEA1JhbDK0sVQ5s2+d60acda1CXo5s5/LfkeDwobRN/3Wer5eQ32O/Pz+VBsNGY0SHodkzZMgQ5s6exUuvvcU9DzxB/kcPAmAFU/AkpUIsSqyyBAB/Wg7tTr+Erv93Mbd360YQeGKRohPtkiGvpaorvoxDbimUBsEfgpokVaukV0JhK0U/couhoIPMpD39OrvNiEHcCzkeWBUBnxdSfAnzaJxEN1IP2myy0WYWR43AKtAm6PbqWI5SKgchgfEIytFHUFrgBxL+htoY/ByG//hhnNMr5GJHcCyL6/a5qIPojhYMWASF82BGhlq6X/ClzoJHDobDZ8Drp0L3DLjR0npXfgUzbJjcHwb/qBRS1ANLesGKLMiqhQ4RqMmEPhbMXKEBbsVlarBW0A4OmwCjTpTg6FoFST7o5qRuLBRFOJVED4lNxUbRghfRpn4CEh4XOs/v0yhCtLNz2WcohXU18koUOMfYF0UhxqNmX+ci8fKY87fTUbOwkcibUWLDI7Y8Mp3WERy/ooFqhWjDv51EZUkZel36o+f4ONaeN7MGNW7zVcFXNXBcOrybpAqUq1jbXBpGBthSJDgynMtnIl9KAFU11b0NKN2SjNJCBsOWYESHYbsgKSmJvQ4/i39mn0XtmEK+TZvEtNh0qqgkr8RHenYnWrcfQErH3nx1lJeD/DLSHRGBn1cryrFPK5hjqYPji1HYrVpn9BlFUJSlnhzBsEpkPXEJkNIsyAipKVY4DikxjbHP9cKSkKbLxixtYjk40Q8Sw9/iyMexxrnM7buRhkRHWzRnZCDyIaSiqoND0dmwa7JMAxbGoSAMh/pgYlypgRsD6gVRGdeGUgEcbkFLSw3Ols6ClZVQ2QoGzoaWFTCzrTarD/eFc5wIRGegaDV8OwmK0vX4c8pU5vrzPrAyS1N1+0TA74eMJPg2Brt+D2VxqInD+N3hgB+gpBUs2UmzbNJWQNeWMMajSpCr0Mbu+lI2lRWoT0YBSlHMQpv77ehM/27n+b0cCbqHUERhd+RlcKMqw5AY/BcawvaS8xo9hlIebVFaaypwvQ3v2Yqs3G2p14iLjSJE/3Ue06UoreU+vrkoPXOKs/bLWHu42hI0ddaugO/D8I9MeDkgQXYDiV4boPfBjSSqc9Kcy2ch70kS6t0xaJ3n7HMkfM5b/1NqMGwSRnQYthsWFUG4GtIyWtG6/1AW7ToUKwN6TlfPCH8cVrdRyqQ7Eh2rCiAShTZJsGcreCAO+3shFFVfjLAHgjVgZQI2xDwQ9Ut8AIQCkBlymofFIDmiMe4pzq6S7tfmH0WegqkkTKFu9COFxEh6G0U/WqAw/Ey0wbVHIuR5tK5BKDIA2mSicXg+BC19Ks1cGYdbgqoEidjySSwF7rcg34KaCFRMB2s5rGwF6TWwx++Q4YWv94SeC+HgfjDVo1HwK4ERX8OOcZjWHwb+6jw+G0bvrzLh7jEIeqHIA339MHc2+Eogp9xJQwE9/p+98w6Po76+/mdm+6pXy7033BtgG2NsgzEdDKGbEiAOvYWWhEAgQEhoIZTQeyf0jhs2zRjj3nuX1ev2nXn/OLNIlmUI5fcmwJ7n0SNbOzs7Myvt98y955xbAR+eCvU5UNSo404WSfOyvyEXx/dBEtk/X0Tai6PQgp6KNH8AzZ/pixb2j1BC6AWoUjTLeT96IHvsDqSR6I/aKU85j/8JkZZUO+VsW22PUYYWbaMZ4Qgj0vKW8x5eya4tk2mo1XM2Cvi6hiaiACKXz9gQqoHFFpySA495ZNf9PbvGlDciogSqhKVEqStRuzCIqjYjW1y3Oc51upD0ALc0fhyk3Stp/CKQtGBTFcRDYHrUKkh6pLfwhR0RKFDWTa2Ip5F986U45LpgdBG43bDdhvkGZIZk+bQQyTCT0nHEPBAOQHYNVOertVJowsakXouY7v5jhmNxdUhHFBGJCE3OFRvd8fqdn3vQ3bSNKiJfIQEl6DjeoUlg6KIpfXObBY9H4dceldcrLLjaJxHsZmCMLUHhHcA6A8psqFkJmVtga57O64jpumt+ZW8Ysgx2TIYOmcqxmAnsVw97fQaJoHJJist1R/9ZXygvgmH10DUbKiLg9sOSWsjbDKU50HEzfDYSDv8YfEUwdxhEk+CrhbpcGOhVmNV+3/O934AWzVdRm8N0rvUfEFG7DLU3zkFVhbuc63cOarVsRaTwbKTpeAXZRS9DlZebUOvjbNSGSblTuthKAj3dgMOMXQnHdmf/ryPicg+7Eo77kN32TKTjaEk4ZgMvWlBZDcttOD4XnvDAvkgg2pxw1CNdihu9xynCsQqlj2YistHy+s5HROx80oQjjR8P6UpHGr8IbK+V24QQGD5VJCwD/BHladgmxIKQKJL1cT2wXxVszoHRdRKQLregr6k5JMXVUO7TtNiwzyEvUWV1RH3QcROUtwFvAtq59QHutsCMgx1wZq4A+W4tKhHklgiju14DLXQeRDpSUehbaEoh7ec8FkaLwzCUGGk7X0FkQf13VLqNKhvqk3CET46JZ4GXLRGP84D1pv6dtx26boYNcWjIhDFfwMgYzMtXRSK/GxzTQdZMLyI7k2ZCTgw+HAZDv9Rxx2148Rg4Zj3Eu8E6C5I2NPjh+FdhfQK2ZUNlEfTYBiVReOfXUOeFeJ3mspxVrPcigBbU74IYIo/voECtUTSNuM9CYtTPnGt2PnqP7kVkYBVqeQTRonwuqpZMRUTlDVRpegQt6Pk0tVOusOEd2xn+ZkBWixX7C1QRKUX6ietpihC3kHakDFVSCmgaV4/zvr6GEmTX1kCdCYfmwLOmLLx/ZNc48lqkN8lBJMbr/HwVev+zUWWkudAU1HJ5B7Vjvm8rK400WkO60pHGLwIbqrS4Z4Qg4W+aY1JY7lQggJr2kGvoQzZhwydhLepmB8j1wiJb1tK1CegagnKv2jIRv6yyGSHNW7FMyG7QYpqZgM5uWGmAJ6mhb3GXCIAB5HtU+jZpssWm8idSwWCpIDA/uiN3o3ZIFGVHbEeVgFx0V5taoEIW3BSFYR797O0EtPVCyNBCV2EpLnw8qnrMBzo2Qr9VULsTtudA20o4YosW0Fn7w5FrYfN+cKdTqZkDFEbh8I8kqq3OgU7bdU3Xt4NQGxjXHta5oSKkk+leCuFKyCqHvDpYNAjGfAlWO3hxMDRGoF8tBIKwX4YqNnHju832WIGIwmzU1mhEyZlXoyrR5Wjx/zUiHA8gy/KlaDFehEjG4c42y1Hg10Bk032KJndKgKawr3NtjaL3GfCHFoTDRiToIkQ6z0KW2tR5NSCRqB+1eUY6/0/twkLzVdYl4MtqSLphRA68ZSo75M/seo2qUCWmEA3rSxGO1UhHksfuLhhQZeg50hNj0/i/QZp0pPGLwPpKqLelTYg4n6Q20Kbc+VA3oaobZNpOMmgSNhTAwFrYnK+fLba0iMSSUGgpFKygWm0Wd9KpdHgdEagFoSDkWtJvVBraJtOCgEepm0lDgtJqtNAYaKFLlR9TyaQpu2yl81g2arOUoQWwG01Jkg3OfkoteDoKJzpC1Tfj8DufQqLWAb+y5F7oCfQ3YIYBA5IwaAlU7oQVeeA24eSP9DqbBsCQDfCP08Ad0OL2iXM8N3wMVY2woD8MWghuWyFin06UvfKBoKox2RGo9cEVT4uQJA1ViUYvVEDY3adpKF5mEhJR2KsYvjIU9jXkP3yfw+i8/oQW1AOQQPQypFl4BAlJC9GC70FW2ONRlsZfaZro+wcUGX8HWrzvRwLdm2iKMt9IUzulj61JrpMNOLZFOyXk7O82VBW5FrV8Uu/1RmcfBznHdCq7VnZiiNiEYzCtBoq80DZbOSqp9NPmE17LUMWkI9KNeJyfr0HEpxARmwNbXL/tiNhcQVP2Sxpp/JhIk440fvaoi0BlI9TZkJ9QbDlAdr3aKyaaXOrK0OLvt5WxEfHBkKAsmy/aTvUDVTSiblUiPHHNWLGReyUS0IyQaFA/y0YLRtQlfUeOBQE3WDE97neLTOTT5FoB3dXG0WJRjtJKe6G76TwkdNyJhJWZzpcHVTpsC16MwlEe5Vp8GoPDffCQqbbCPpa0AhnAQAM+NVRiH78GllXAagOSXjhspoaQ5WXAnO6wrQtMbK/X+MR5vT8noGoaNLpgRwn02ABht8Syi4bD6CxFqBNXtPqgCsjfCrUGbOkAcQ90W62TK+sNViN0qwPbDcfnqYVRye4Cx9YwHwlDl6AWSTkSfl6BFuHLkb31VHQX/yS6678a+ACYgapcQ1DFI4JaGxuA99E02Zed7fuh9sMLwJU2rLLheRv+aMjd1BzbnON6Ey30tyPyksIcZK+d6rynl7FrvHg9cJMN3jC8WgsD/Qqo22oo5v5Kdq1I7HCOvyeqaKWIzVrnnNs416X5MYCu873O62eRRhr/N0iTjjR+9thY5bg+opDpggavhKSFFU2R5TWdtMBmArGoFm7DgOVFupN8wYJ+JrxhQYcqKPNqm7BfVY2EGzwxVVGCYek5gjFngiwiESQh19Zre5LgcQGmqhO5iFh4aKp42KgtsAlVFvJpasVsRAtYKbrD9SCCs8mC1VG5QzobcGcMvF6oMrU4Zdqyd9ainIw6Q4vswWXw9haor1Jse48NsFeV7nbvHwvHL4PGQ+V2+Te6TjcBq7+EeBUs6wuDFqt6AbBuLAzNgMedVoM3rNbSqe/AtBLotA1W9YBJs6CTF+aeAX5LtmKzEbIKINuUvqXO+OYppnUoSOuvSNewL6puXEHTCPlbEVm7BekkbkGL7ijnuY7ch4tQdeQd5HA5C1UK7nCue0rjkGqnXGjLslpvw3UG5LcgHHMRAVqK2hj3sOsck0dQVeZCpNf5A7uGnZUhMWpWCP7dAJMyYVWmhuUd6TyvueZiKyINA5x9pR5bh973dkhkemgr1/AOpHf5vmFraaTxnyBNOtL42WN9JTTYkBcCl18j1pMm5Fc7G7gg2qFpsa+PQsIHWUmY49FCVm1JtLc+CT1DsMMLvjjUBaXnMJPQkCERabBReo6CmCKotwDYYCaU8BkypdvI9qoK0ohIRwVNd6wJREYs5BrxoOeE0J1qJrqDTUWgm2jjh6PQ1Q1BE25yCMdAl2KwkzassfWc4YiUDAK6h+Gl5ZBTATuydS4T52lhW9Md+tXAmlOgvU9WzBK0wH9kQ+b7EDVhUxfotlZEqWMSph+pc3HZUGBrDk2bGshbASs7gZ3QQtqrHAKjYGY7aKyDrvVazA8rkh6jHXue8WGjis95qJowFVUl+qM7/Z2oovEpyrn4PXKefOb8exGyiwaQiPdaREwuRq6VV1HL5W/O849w9p9qpwyyNaPmQAOmmBrq1/zYnkQEoB5VMm6jaUG3EIF5x3kshEiSv2kXrEehX/4GeC0Mp2TBh05r61colr05x9mIqjkjnH2bzfbzGGrBlTjn0Rxh59jOYdcMkDTS+L9AmnSk8bNGwoLN1bqTa2upyhBzKz3UndQ2rlxVHdzAmihYCd3RH2Sr5PyZLefJPMPJ7EhAlRfyq5TDYaNI9LpczVnJCMs22yGhhXerSxoRb1KOmTB6rWKPFpvUILcKmqbMbkCLYY7zuAtlKtjoTrzMeTw1/K3OgveiMMitLI1norCvB+5zNVUJbrLVctgLZV70ATZa8OVy6FIP692q1Bz+IXQyoIMXPh0M+2bB3C7Kd8hDd8TTAfdX4N0Ga3pBz5Vg2pCbgDUT5QTKRSFj4YRaUvmbYVsMqgtgwVA45CMoDMJbJ4I3DiEbMurBkwuTvDrfUlSNaIkKRBLuQ3ftg5B49PeoVfQEIkbZqJLRBQV/DUctk7+hak8YpX0ehxwdE5Hg9FWU0zHTeZ2u7NpO2WTDw7aSWEe0qG40Io3FXegaXIHIR4pQhhCJSaLQsQ5IrNr8w3gB8Iitg/wwCudnw9N+VSlOY/e8kjXO64xBhCt1SCnC0QURnmNaPC+GtC0n0TTSPo00/i+RJh1p/KyxrQbiSd1tdjc1Sj5mSouRcq1kFKq1Ege2J6TpyDfk9DCA+20YbMhOGghDvVt394UVcqqYNmQ0yk2AoZhzG+hmOtNjHdIRsMH2aDhcwoB2XolIgzgZIegPcg1aLHOc/zei2Oq2znY9nP8HneckLXg+CqPdsMMFH8dgbw/8yd3Uz7/VUik/HwVVAbxsQ/5G6FoJaxok/hy1ACaENNDupVFw7DK4+VjpJaLAQ0jfYEUh8L4qRit7wl7LRapKMqDqMAWd5diwzYDGELgroPNySGRIK+MGhq2DjKNgehZEQ9C2QW2KfsWqDvVFaaftmr2fNlr8L0SEYSpqpYxwfrYdVTc+RvHm1znH+y7SYpQhQWiWcy2vQ/qJfzn7uh0RgJvQ616EqhKpdsolNjzqkI4bDChuQTi2ItfMm0Bv1MY5kiYSsBVVScYgTc1E5DxqjunAmxbU18DnSbgoF+726nr8ht3FnymydTC7ZmpsQISjGyK1x7V4XhIRycPZteWTRhr/l0jndKTxs8aGSn24JoB2FqwxtVC6LBGGcAAyMlRhKE2oBZGbhI5uJT6WAEssONKEV20orIJyj2NpdekOHVvOFRvNXKnPgUBSTpdGU3oPQpBhQSCgCksU6OSRFTM1in0bIhMD0KLrRtstSybJWrmSsjVrsBIJtublUTd4MLkFBSyzoDwKY9ywyg3zozDRpeMHLZQfWCIyfVFg1zpkF51aA1vWw9JqqApASQiOWSP77Oq2qgq9dyws9Ir83IIiseMxWLgMpmyC5d2g63q1mjp6oPsIOC0X/mbD9bYz0K0C/GHovA7Wd4OGAHQshy7Z8PrBIis1CehSCw1BmJyhtkmqrZTCNlRl2YEqBdtRdeKPznZPoecVI4IRRUFZk1BL4R5UeUggfcUw53pPQSTvfdRy+QciDu3YdXZKlg1/suEIQ5WilvgMkZUdiFRc5xxLCnNRxeMi9Lv1W+QuScFGA9c2J2FjrUjuBblwm0u6m/Oc343mWIysskehCkgKG5GWpRdNU3Cbw0ai0dGoMpRGGv+/kCYdafyssaFKd8+5LghEVaGwLL6O/KwsVCvFsKE2rgWwqwEet+5KJwP3W7DNDaEElDTC2ky1ZxoDgKVgMdvW/jCgNh8KYyqnxxG5STp22XgmZMa0IPbwaoEagchHORpAFkBlb2vuXLbecy/xV17BCjWy3jmnK53v/+jdl+RZv6HHGWewqTCXjCh0dcFojxasNcA9lu6EuwLnGXIzBIHL4/DlMqgLQ6MHTB+c/LYqFAkTXhoJQ6vh6p4q/++NFuRNcVhVD+d+AnELluwFR72h8fXtffD6JLVgXrKhyoC2Eei4HRrDkB+Bp/aGkYvA9EPWKTDNDclGJbzWJyG7EPoY0lr4DeVPJJFr5DV0134WcpqciBbhdWgBrUfk4nBn29VokV+NWggdUJvtKlRFmokIy0Wo7fA4TVHmblRRWehsv9CpcFxsQIcWhCOl33jY+bU6DrU4musznkFhZFehJNkr2TVTI4myQkjAl7VyA52VDbebagldyq6OFlD16S/OdTih2c83IaLUR7vj1BbPs53He/P9U17TSOP7Ik060vjZoiYEVSEtND3yIb4UGg25TZIukYWyYuVKVMdFDPIs6JglApAJdLZFSGYAJCAvAau80nDUO75C21Byp4HISIMfutfLYltjqLoQc9o2dX7FrieAzh5pEzaiisBeOO2S6mq2XnwJ4aeexJ3flqxhx+DvsBeegk4YLjfJxhpiO9fRuHYuoWt+x6K/3Uzyngc47ZhjWOFW62ApsvnORwvpbUbTXJBCG5athFAIrAjEM+Cw2dAnDhjw5nDothFu/I3u9vdGpXozAVV1MKEa/IthTWdovw1yo1DsBbMP/LWDiMBiG4pN6LBFmST9VkFdAKoKJCDtlwmvDZVWpj4KvWqhxg2T8qRnGIzu4pNI2FmLKhJrnGO5Fn14PY2qG/mIUJlIuzHaed6jNGWf9ECtCQvFii9A5MZwnnMkIoAhlPXRBQ1re9RWK+dGQ0SoORqRZuR9VLE6HZGO1GY2qngsQq2P9chV0ryCE0H6j6IYvFGnHJcjsuFeQ06cK9i1xQSagvs355o013dsRi2w/s55nMmuYlOQLiWP3S2zaaTx/wNp0pHGzxYbqvS9HhiRB9FaTTtNoS5LeRCeuGaCmG7NSTEczUEhMMuWdXMVUByGHFMZHR3LoSJfTo9AGEKZcq6kxKl7u2BjDGo8jh02AV4LKrzgqRNRsVxaRC9EA8ViwJa1a3l/woHEyispOPQSMvqPxzB2lV6Z/kw8BR3I2GssifoKqj64n/knTmbsFVdSd+tfeR0Drw3LbJGYkYYsow1oAR65A6p3QmYdbPEpIXTsDlV8duTCl0VQvhfYPi1ojwLHJ2FWHWS5oNM06WKWDoAj3lH4WbYHrj1I+29rSwA6rAE2lem8+q+Dt8fDqK+gtAtMGa0KgysKdgwiMfC1gYNcuuMfhXQYf0QL6DGI+E1Bd/AbaKpuTELi0A9Qwup5SAB8EyIaG1Eroy1a9H+D2h9vo2rHpyhIq4Bd2ykFjjvlAEPaC6PF6r3FOb5FzjH9DlUlUoihRT8P5WlYiAg1300tahl1icLzddDVDz0y4XlDxOlKdrewznGecza7jprfgqopg1Db6Gx2JxxvoSpby3ZLGmn8/0JaSJrGzxYbqnRnbwI9nVyNnYGm2SQVhapilMUgkdCAtw4BaQW8qBy/zIJxhu5GC6sh5BFhyKqRE8WVhGBIYtJQBiS84E+qzdFgQdgFOUlZR32WRKgBC6yAJroGUEugFqC0lK/GjccKWbQ7424yBxy4G+FoCXdWIUWT/0je+LO54+9/Y/qNN3IUsMOWhmB/dEcfQhHv/RuhdBV0iMA2l9pIx85RiJgN3Lm/Ys2/2EvPfQo419IdeIkBF5aryrGtBIoqIT8Khgs+6wjxvtJJGLYsu5E1Shb1JXXdqgtgn0Xg6w0ftFfFqS4GPashbEC/IlUANqCQrRpk4wwgPcd1iEQ8h5wpJlr0D3D+H0MVjPfRbJQspNX5EyIcTyDdww2oenA3qjT9kabZKc+jFkiDDbfacI4BBxu7E45PUeViKQouu51dCcdO5KoZgaoUe6GFvvludqDXaB+GJ+tgRBByM5UOO9o5rpaE40NEOM5ld8LxL6T9qKR1wjELtV5OI400/ntIk440fpZIJGFLtVJI22SCJ6ycjYhbH8ZxD9TlOEFbjWqxZJvKzihDtknLhrAFh5lORaMeNnglQk14VdUwkwoFC4Qg7oVIhhbiQlMLfdIDsZheJ8vUELRyryoKlyMrbBCotm3emjqVZE0DxcffiDu7eE+nthsMwyB7xNHk7HcKO6+/ntc/+YxFaNGdZ6jsPwEoSEDmEmiXgNUxiAfgmA+gmy3C8Vg/yKuCZ45VJaAG6GHD43VaBK8G3pyrIXcrBsCIBZqJ4nVD4CBYZcDeNsyzYWA5bG9Q5aP3OpizD+TXyiLbrbcGogXjqnCYYcjIhQlekYHlqNL0W1TdOBItlNsRqfgIzYu5GRGUu5zHhyJCYaGW2tHIDhtBFZIPUfWkCFVBJiIiEEELeRjZYV+zYJrjTunWYuW2kCPkD4goHokIR/tm2yxAFaKpiNRMZveBaquBe2woboSnG+DoTCjLgLWGqjx/YPdU0LeRRfgS59hT2IoIx96I7Exld8LxBWq1ndvKY2mk8f8T6fZKGj9LbKlRRkcdMDIfojWwNiDnCkBtDuCSADQSVWpmiU933JYNvWyYhqy0n6JpsYkEVHrBF4P6TIWC2abu7P1hzV2Je6BLBIJeDVwz3VAVFqkJ+qDCALxwRATqs3UnngQWvfUW5W+8QdHRv8ed9U35m3tGzsjjCa/5nGemnIpvzVqGmQZBdOe7FihaC5FGKGuAkA+Gr4DhNWAaMDsI83pCdQ8Y47Mp+2IeC2bMxJ47H3vdOqYl4jzvzaBvoj9l7YdhbDuM3IaO5JoQLoTAUOlUdgB9bfAukDXWj+Lh25dDXR40HAdlToprTQy6VovcGIVaUJegRfVV57ivR4vki6gVkoEqEYXIZdIOVTLeQwLNYtRySQ1i+xS1KM5GeovXUavsarSoN2+ntLE1O2WoIZLSsrrRgIjOdBTQdjwK9mq+2b8RCboWtUEucrZtjnnAW7YySf4dg6nZ8JpP5HOU85yWH8z/RoPormDX3JJtyAK8L2ojXcDupGIJIlzNA8PSSOO/hTTpSONniQ2V+t4AjCqA0FbFR9vOJ3J1LmCIdHhRO6C9VwtgrgEZNtRY0M5U5kJhWNkajV5oVwF12YClYW8xjwSRSZf2OdwNtUkRnjw3NCTVfvkyX8/pVQ2di7VIeNFC1njXP/B16EOwd2tRWP8ZDNNF3riz2Pn875k4+yM6jjuAbEcEe1c5vL0VQmGoNyE7CZMWKqb9M+C1/aFd1KJq/tO8csqdhBYtxPAF8bXphju3HWUBD1Y0xMKyGXy+4jGMD89na4fDOX7oNRx36r4c4dIivMGGwDYt7L6YdCvrOsO+i6B7BqzuJ4FoYQJWJaBjA+wIQixTug0beNVQW2USagfci67lfqgysQA5Rc5AbZFbEfmIoxbHSF1mbkCppk+iNsVf0ZTYlPunuTtlkw1/seFsQ/HwLbEJEaCFSL9xIbuHlt2MZtL8AVUVfo9IUnO8D3xlQawOZiTgihy436NW3gh03i1f/jlEOq5C1YwUtiGiNgqRygtbee5a57lXk/6wT+N/A+nfwzR+drBt6TkiaKBa5xz4tArqHctA0qWYcwvAgkwDcjyyMJajBawWqLEV8PUlML4adrj13LalsKaT2iqmBTFnX3GvWgaDPLDZsefuZcJ8W3f9/Qww43K1dPFq8awGhpWWUj9jOgWHXvqDz93XaQDunBKW3Hcv4w44gMeBqRFYv0KVn/qEAromvwbZhloVG7tCcudGXv73r9k+eybBHiMoPu56/F2HYJiu3V7DioZoXPERi758m3mvj2JG50uI7vMXwoEgnggYS6EuAwJx2ZNHLYTNXeHi/eByJyOlNA7BapGThkK4xRA56I7IwwSUWTEDVSx+h1oYD6DqyXVIs/KE85ztyB6bjxbjs5Gg8n0UHvYIstp2Q22v+5E75Wpb0ehLbc1OyWmFcHyCLLfbUILoNcjanELCeT0vqpBsRIt88ytno0pFtQXba/R6f86FG9zKTxmLbK9Gi+c8jlpCf2DXSbvbEeEYjZJbL2H3KsZW5/lXs6t9N400/ptIk440fnaoDkNNWHqOznlgmvBFvcKuQLkZBhpO5kbViEa3FrcE0kBsAGotyHWDnYSCetjihIIF69VacSfAG5OA1HDrrj4nDL0y4PNGhYJl2YpM71INhVmwIyaxZIZH1sXewJovvwTA36ll9NN3h2EY+LsMZstHs3kN3b0vWg52HCpDEPXD2M8hOwSLDPB64J3gXD774ySSLi/Fx99IoOuQb3wN0xcka/AhZA6cSN2813j3vnvJ/vRjer37LvO2F1BfD56AhLbuBPRfBWsPh4buWrj7J+HjOHSuEynsmgdzDMiz1QJ5ArVH6lHb4GRETlKZFN3QgutH7akimuaQvITaGzcggelzQBVqd/jZtZ3SwYabbe3vT4baTM1hoUX7Geffk1CVpLnWotI5vgNxKmbsTh4SzvFmJ+GrGthiwPW58Hsn9OtQdh/AZiOdxizUPmo+f2YHqv7sj1onl7I74ShHxOp3yPqdRhr/K0i3+NL42SHVWqkDhhVAOA5rbf3MNkQ63En927She6aIQCozrAh9mOcC623pOcJxaTZMC2oyZLM1HSFpQxZU54h0dEpCTw9sS8hau9hSayYjCZk+CVPrvPCqR4SjEFiwbBmmPwNXdtGPcv7e4i7EKsq5xbap2QQN1bA1AhETcmugz3rlh/iApzou5vPrJmLktKPt6Xd/K+FoDsN0kbPPsbQ55W80rljNNRMOZvuGBqpywZOQw+XY9yHUHgb2lQAzZqutkFEH+RbECmGQC35jK53zPkPXPo7spSehCsgsdLdvozZGG0RgzkBhYFHUmngJCS4HouyNDkgj4WNXd4rLVmLqYQacbO5OOFK6kAdRi+QU1F5pTjiW0TSJNoTaHEeyK+EIo/ZPURzerYZKF/w+F652yelzPK0Tjn+i1tCf2Z1w3IMI1SJU4WhZi6pFxOsiZNdNI43/JaRJRxo/O2yo1Ad3CBhZAIu3SyCKofZI3KugKxv9AbQPQtCWswScO+IkDDTlpMiL6IM84tO01IoCVTowJCLNroWaHC02A90KkFprSVTav05iVL8BHW3NYdmZA5cYIjqZwKZIBMPrx2ipXPyeMDx+sG1KKhOsWqdx8RsTEPHDpOlqOyWAdYURZj1wAnZWIUXHXY/p/373xL6SHhT96gbKVixj6f1XkfRB1AM5DdBpJ1Qer5ksTwKRhBbgvtVQbkCXAmkRXkf205nAQUh/4UKL7gCUrfEcEnEWIHvsdYioLEBujiFIcLoC2WGnosU5RJM75WobZtvwuA3XGDCklUu+ES3Y76EqyGWofdJ8cX/L2f+f0eJ/GtJkNEc1Iki9YvBcLbi88Jsc+IMpbcYZ7O5qsZAbZi4iTX2aPZYiHOORZuQydi9VN6KJsVPZXcCaRhr/C0i3V9L4WSGWhK21mlialwlZPnh7naobALhEBswkYIDb+coydHftA3YY0GjDfqYWyoHViqVOuKDdToWK+SMSkGbXQFUhhAKQlYCBHvhrEsI2tDch7Og+vG7YYEGdB8ZWQ1mB2jlrgO2ZmdjRELZtfWsux38CK9KIYbqYttJNDbAtAvEgTJyhCbqmAR4Tntl4A1U711Fy2l2YvuAPek1vm+7kjD2dte/fR95BvyJ+4AGc+hpklMB7w6RBwNbY+/KQRtv3yoODvHKqPIxEoAeggWsvo+yJq1AL4wZEPtaj6kd/7Y7bUGXjXyjD41+IzF2HPtyat1O62BoVn4fssO5WCMccNARti3M8Vzr7bY47ESG5CZGgy5GWpDlSIs/+YXioAXoGYEyGtCt7I+tq7xbPSaKqyHJn312aPVaKCMcElEZ6Bbt/eMec6zEFTaNNI43/RaQrHWn8rLClWlNX64DeBVBWD5trnF90Z5GJuyRmBPA6FZAg2sYFVNsa/JZnSHxZWAtht4hLl61N+7FNtREMt1o17iTM8UM7ZwhcFzcYMf17e1CzRdo1QolHi9tcJI4cOXAgVjRMonrHj3INYmXryG3TgQVRg7Ko2j5dN0FBqSLZvcDaPtUsfOMusvc+Fm9R52/d53+CrKGH4WvbizXP3IAnCf3XwNunwWqXKitdLNiZgOEVuqOPFMInhkhHH+AQJ6PizyjM6zJkW34WCTe3I0dIf5SlcjSK/f4AtT1uQEThLPQ+Nm+nBG241taE3bPM3QmHhYjPDahCcSCKGW9OOJKogrAYtTW+QC2floRjBZpM3KMRHmyAURnQLwMeNKRRuYzdCUcCVTZWosF6XZo9VobaLRORBfh37Bqjnnr+7c416UUaafzvIk060vhZobmeY2QhfLYNEnFnLoolDUfCow9t22gqmfudfA4/SvNsayiK2pMAfxzCHsBQQJg7of34Ino8ENW+wj642NQU1qSpKbKRpKLQ3W4YHpazZmOWXBkjUfl+r+HDMVwuwhu++sHnb1tJwhu+ot3QSWRZEE3qWIZ/qsoKQEEuvLz2CZLJOFlDD/um3X0nGIZJ1vAjqVkwk7bTVrC+P+wcpLbHdhs8SegQhawoRDNgdabEvD2RfuJl1Kq4GDk6/oK0GlG0SF+K9BVvInvtOSir4yOkF7kMkY7m7ZRrgC9tuM+Gyw2RjpZI6TceReTzOEQAcpttU4v0Gp2Q06QMLf6+Fvv6DHjZhuIGeCwEx2eBGYQ3nQrHlexehUi1ijahtlLHZo+VOed4sHOeVyDS2Bw2aicdgBw7aaTxv4w06UjjZ4OUVdZC+Rl9M2DGFukuDEQwbCdbw2Mr/MlEjwWd9koAqLSgr6kP+YKINBBhvwjGziJZZV0Jh3wAOwsBG4ZHodSEdQkdg2nB4lxNru3lUjrolqDIRl/neGJAj9w8Co88moZF72Lb1g+6BuG1c7Eaaxh1+FRWRyEchIPehxJbC3wb4KkxsPXLl/F3H4Er48eVGgZ7jcb0ZRD+4FX6nK7XnAd0tKBLEvLLJcCsKISxBtxkK7XzKUQEr0WVhHvRHXtqNs3+6P25EFUkXkPahjtR+yUVZb4etSYOBY6w4V5LM2huNEQkW2IDqlp8gAjgeSjevHnrYjUSq57qHGMH5LJpubu3gE9tMOvgpShckA3L/TqfoYgAtcyZjTjnvBMRjuaD3VKE4xCkdbmS3UmOjWzEg9DvVRpp/K8jTTrS+NmgKgR1Ec3NaJsPGyuhIqTgrpyENBmGLStrpssRkjri0ZRdNhOFgvU39KHfs1Z3uQkPZNVBeaHmrYD2t6oPVORp3koHj4jKhrjmkcyyoSii7Sv8sNqETnUw3lnRLJQF8k4UjEsvI16+mYYF737v87fiEapnPEJBv1E0dBqC5YF+i6BrjeyrOcDHe0FtfpJtKxfga9fn23b5nWG4PXhLetBYO58ZbUUCBtgQsTUAr65RWphOOaomPIPyJAYZ8CvUItiMKk5+tCAXIZ3DRNRmeQ2FuP0F2VhTFtXm7ZRcp53S14ALTaeN1gKzUeVgCSIFf2D3yavT0NC2G51jOAS1XpojladRakF5DbyfgOtz4HWfqigDnH3ntHheyPl5NSIczYWf5YhwHOYcw5W0nrXxDGpFTWjlsTTS+F9EmnSk8bNB89bKgAKYvkXCSZcN7Rs1GRVb81AyTafSYWjB8huKKs+0JQI1DIhb0KkGaj2ACworZI+1TYlRdxZruqztgpwYeFww1JYLI+mGQfVQ54Z6HxxuquRuJBVEVoTI0StJeNMNof1GEZg6lerZjxMrW/+dz922bao+fAArVMPIyx4jaoErAiMWq6IyACgLwJxh0LNoA8lQCG9xtx/nwreAp6gLKyoWcQpaWD0WVNngqtDjwwpgkgv+bkjXcifSuMxEGoo1iEgcg96b+9AcljuQaPM1FOh1DRqk1rKdssyG22w414AJrZCNJBr/fjOqkhyAdBR9W2x3HyIDd6AY8d+i69gccWRP9SdhQQ3MteH2HLjTmS68l3NMLWW6DUif0ui8RvPg+wpnn4ejcLMraWqNNcdrzvU5qpXH0kjjfxVp0pHGzwapUfZ1wPBMWLhTv+AeC3IatdgYiBCktBwup/USRCX1qAUZhnIhXEnIi2lInAkUlAO27mzjXk1N9Tn6jeIkHG8q4rragA4uWOeCBjfkJmB/WwuMz4QtJqy04UULSg3Yy6XFyXPrbdC3DztfvJbotpX/8XnbyThVH9xH45IPOejif+Eq6kW9H454X4LJLoaI0LQx0LkbvF7TAPC9LbLfBtOfidlYz0vIhjzHkqB2YK3I3bYCBZNNtrXYvm6outEZLbjXoVZHNap+LESLbzdEDnw0hXQ1b6ccZcNjFsxx2ildWiEcdah68gSqHByFKibNp7laSFfyCRK1vo9SPduzK1KEoXsC3q3RsLa7cuBqtzQofRFJaqnBqHX2l0BuleZC1ApEwo5ESaRXsjthAVU/dqJgsjTS+CkhTTrS+FkgmoCtNfogd2VBeQU0xLRAtQ/LBkvqyy3iAM3aK4YISLkNuU4+R1YUtlvKt3BZYCTAF3VEpFGFhVXm6vk9bSgw4Q1b2o9yN4yogryoElH9MVVaGgJwng3PJAELOpkq16+xIZCZiefDaRh9+lL67FVUf/Q4yUjDHs/Ztm0iW5ez46nLCC35kJGXPkTW+DMI+WD8p1AQVluhHNjWGSp7wGf5EPam+juJH/ldcGAlMVxu/oUzkdeAfarV2grnge2VdXQRmg3SgNpao5E41IusqIejiseDSFfxN2SXTYVpNW+nFNlwnQ1FBlxhSKPTEusRCfgQuUN+jchFc1LQgKbCZiPCk5qh0nLiawUiQENj8HQN1Lrg7zlwgUutmoFIG9IyuKuKpoj0W9i15VKJCMdRSB9yJbvPbgGJVZeg7JJWTjONNP6nkc7pSONngU3Vcp/U29A5H97fJNGox4be1fC5t4lzmEbTtNmUlTZgiEzssMHtkWaga61IR8wHgTBUFir23JNQFcQTh5pMLayFPg0q8zuD3jJM8DRAMgABtwKpvsiXXddIQgcLMj26W37VUvk8Dnh9+Vjvzyb451up+NeN1M9/k2Dv0fja98VT0BHD5SEZqiFWuo7Q+i+I71hLdo+BnHP9PFZ3G0yDBW3Lofs6yDPVerDdMG8srOimSsNlXTswFYhX78DXvmVT4YcjXr0Do7M8Gr2SCmbLrRBx6FcIpxtyqjyFCMQ7SAzZAVWjrkGVmRdRJP1T6P1IRZk3n51yDbDIhidtmGpA7z2swrNQu2QjMBgJUge22GYjEoj+2nmNBK0PUduEiNDICPyrAXK8cGWW2jn7IVvsMa0cQwXScGQgjUhzQlGF2jhHA28gwtFaHWqRcy5XtHJcaaTxU0CadKTxs8DGZnqOQR54u15Og3w/ROog5tgCDMBl6u4bY1chadgClwG1jp6jYw1s9oBhgi8MNXlyrgBkNEBmFBoz5FJZ5NMC2rdRi0uxDWuD0Ggq9vx6H8STkBkHwwvDPVpYGywotAA3WPVKTM2Oesif+kdCF55N8U0Ps+mLF2ic9hEkk00nnJuHuf8YBp90N72KDqbGMkk4mSFjZ0oI60WZEQtGQXknEay8AJwSzOXCzt2Jla6F/uN/9PciUb6O4IHHMMpSK8EKKaOkcwY0BqXH6Ou0m7o5VtIOqBLxG+RUeQtdxxuRS2WKs+/mYV+9bXjBlgbkz4YC3loiidwubyDNx/5owW6Z1jkbEZi/oLbKOFR5aYkliDANCcE/GqFvAH6VARcZMAaJXVsTdZYi0pSHWjbNNRpVSEB7DNJpXMHulRXQ/JnXnONMf3Cn8VNF+nc3jZ88UlZZgAYPJGsgGtedZDcv1ESl4zAMtVVchkM6aNJ0BAyotKGjC2baqkb4QhAJKIMjtwp2toFMZ2aLL66Y750FsoV2M+BSGw51qhY+C6a3lWujHbBXGKZ7weeGYicqvTypELKAGxIxzSXxRSAYgso8yE+U4Lvij7h6/BF3PExkwwaFjuTnQ6cO+CyDzNVQWwWNdRBzwyHToWcCfIZ6/nVt4JOBEMiBxhyJXSfuBHvMGMJvv49tn/2jpKCmEK/YQqxiK0VjxpCVgI8NGFEqAugq1MyXQwx4zRbByEczSB5H5ODvyPr5KUr9PBe5M2ykcViE2im2DTfa0sL80dB72xK1SHPxCdKOjEXx5i1dII+iasrdiBCdTusBW7OBj23o2gj3hmFCBgwMigSMQW2ZfVp53jY0tK0YzW9pbntNEY7Jzmtfwe4uF1B15WnUmmmpEUkjjZ8S0qQjjZ88yhuhIaoQqWAOzN0O2OBzQagKMGSTTZEOrwsSTkBH83CwSqCX05LIico6G/GpnVJSCmXFcq6EA9JmlDqhCy6gjwnLEiIuGPBhUO2XrDB09MEyQwLFfqYIyqykFsUSl8LH4lFFs+fUyhkT80PnVbC8L1husN0BzL32wkotrrbEmQ22ckey3NBzPYwu1euXAvkGXD8RqttD50ww3RAqhVWZEDxlKrVPP05k/VcEug//0d6L+oXv4C8sInHEkayxISsJXRtgoQf8ObC3ofepG3AEannMQ0TtPbSg3o8W5j+hD6hdRtEjEe5DNpxhwMA99BjWIpHmIiTqPBKFfjXf3EYEZj1a+F9BmR0tx+7ZqMKw1YbMeng0BlOywOWXBmM0qsT0Z3dsQkSjI8oSaU4Yqp3XPRb4N9Kb5Layj52onbMnjUcaafyUkBaSpvGTR8oqW29DOwPKo46AtBCqd4IrNeTNyegIJlWdMGgKDvPbsnU2mArsKqiTCyUScOa0WNJ1uBP6/9yRTQmnqfkt1wM7PJrmuncjdK+DkEsEIGrLVruXCfOSsMMSgeliQbxCMesFFeCNQ30W5FZDaYnsuCktSktYlsKlYl4IxDWy3kZx4QngiaFQ2RH2yYa1fvBUwHqvtjmteB8GDh1O/cdPYSd/HEFpvHIrjYs/IPvc31Lo8THEAHeDor07F+paHG/AJFuVj68MhYAdgSoN1SiGfG+kq0jNTkm5UybbEuo+a6u6sSfCMQMt8ItRaNZFSBTafPMwmvAKcAFyqPyB3QmHhey1jRbU1sHLMbgsG6r9mmg7AkWjt0Y41iEnTldEoJoTjho0J+U4RDguZfc4dVAl5G5EhlqrgKSRxk8NadKRxk8eG5tZZRN1kEzItdGQA746tHo5i3fcpzHzyebtFcfhEbQldoza4KlSG8VygysOFU4SqdsRkWZEIenVvBUTZT7McUEwrvRRMw5rc0RKwgaML9P+PgB2JhUeVgAU1cKCIGQ0QlE5RL0iSB03Q3kRxJs1/+0Wt+m2KQIR88KkD3QuVYZitjdnwUf7QbgtLAxAXiPE4tJUnL0Wru9rcMH9/yJcvpHaz178we+BbSWpeu9uPB07MO6qqwkm4dME5NZB3ICaAjlWeqEAtTXozv0aNKH1Q5qizIfp9HZxp3Sy4VYbam243lAVpyUSaODbnag6MAoRwZbTX7ciEjMJRbSnjqVlFkYMVSLaWLC4Fj5MwM258LFX+SKDERloLe1kFSJQvRGZaV5SrkVtpF8hfcgl7JrTkUIDEpeex+5kKI00fqpIk440ftKIxGF7rf7dGIBt1U7uhg9WRyEnDG6vFmiAhFekIyUkTbVX1iPx3gpbY+uDIREUA4lGq/MlGDVtkQ/LpfwOy5AV1gYGRcBrwVaXxrbHDPBbuiNfmwWNXtiWgHw3dDQUsf424I1AfqV0Jw1Z4A/Btg7gtTVkzmNoQW3+x2rYuguPeqBjBbSv0fl40DTbFw4FfxfwZogoNdRBbQ4M2gIXd4GbXHDNoGHkXPJHaj99joZlM7/3e2BbSSrfvZvojlUc9MTjVPqDVBkQaARvCNrlQQe35qJsQW2EQiQGnYhIQjVNUeapsK8QIiUVNvzJhvEGnGZKk9MStaia8AKqKByERKEtszXmIhvuHxFJzUY23ZbW1npEJAcn4L0azW+5NxcecasN1wfpL9qxO5ai6swARJia7ztFOI5HlZKLaZ1QRFAl5MxWziGNNH7KSGs60vhJI2WVDQFZNlTGHaFeLpiNqjyEC/g6mCPpBV9SLhGDJvfKSgs2Oj8riajaEfOqIpJbDZs7qsJhxKS5qA/qTjg7CaPdsMCCFS4IWiqD17ogK6JFbacB64K64y9ww3ADvojDupiqF4VlEIxAKKjXLCqF0nZg+XQuMZz4dqPpPGzny21DbqXuioNAnQ1L+kBNPzgxRwFkVMHmIGQ3wqAsOD4ImyzIroPEeX8isn0zlc/dQbKunOx9jsUwWy7Be0YyVEvl+/cQWTuXg556Ctfo/ViRhJ5JKI9DtxjsW6jKxAsG3G4ravxWQ22PUkQ+Usbd5u6UPrYqINNtuNKANntop6xBGR5LUWXhQOAUdm9JPYO0EfehybZHoKpKS6RmnhwUh0froNyER3LgclMEoAANe2vN0roAkajh7G63bU44XkBtnZazWEDW6VTrpXvrp5xGGj9ZpElHGj9pfB19bkGy3snmMGCbW2PkM9xQEdCiDVrI/QlIOhYCEygzFcGdmZRNs6ROlYVwQPNVSrbDtnaqbjRmwfa2qnC4gX4W9DPkbIiailF329C+Hsr80OjRnXedKVLgM3WM9RUQyYWiMuizSjNcavLkXqkpkE0XR19iOfbXeGoWXIp8mFBcph95DKV/ht0w+0h4pQPMt6HUcbV4E5AfhRdKID8OWY3gqoVIwKTDnx6m54q2vDfnr0TWzCVn/K/xtd8LozVLiAM7Eadx5WxqPnoctxvOfuVV5h91JP0SOs/8asCCzAxYFRTxiqIo8cFoVHsVcpdk0uROWYiqAz4b7rZFum40Wp+dAgoRexQJRwcj58molseK2iwLUR7IU8g507mV/aVIz4ExuKcOLA88mA1nGWrF5KIWUMvBa6BR9/9EwtKp7Eo46tiVcJyHXDktYdE0VbY1nUgaafzUkSYdafxkYdtNeo56G8JRp5SdAUkL2oShwAdLPU0LQDKgzI2UkLTOC//OhKMMeNWWxdVTLTFo1KPWRGNALRbTlptlr23wRZ7srrkuWGqr9WJYaqe4k9ArBEsKoSAK25PgdtoxhwINZbAjQ22VklLtN+pV8ml2jdNacTfNhXHbOlcMkSc79XOnYpNhyG4biMMHx8E1PbWQXxOCmFNNyUnCyhLIs8AMga9Maar1WTD5TZNe428ictzhrLztbEqfuQpfYVf8fUbhLemBJ78DhsuDFQsRL9tAdPsqwitmkwjV0OWQY9jroftY2q6E3ramrPYAsmoVAV9SBM8a8DsDfmurtbIdkbub0XGm3CmdUTtlu2OHPdzQJNrWkEACz/fRgj4SaSO6tNguBpyBWhjXAc+htkhuK/tcALwOjI3AbfVQ6IfrM+FUQ/NZ8lB1orUPzY8RoRmHWiLND7ueXQnHubTeMrGd6zCc3XUoaaTxc0GadKTxk0VZAzTG9GEdtiCWkBiwxg8FLuiQhKAbGp3fchuw/eBvEOmI+GBHMkny3N/w0qaV2C+8RdSXR7RBbZiECYGQWh3emBZ520qy+rbfkNy5EuvVt9iak8cwINMCO6rFMOmB2SVa7JMWhH2yzxYlobIO3vKAPyy9xl5LYUsnR8sRhppcOVa8toKtkmjhDMTAaHZ7bTSreriTastUdQBGwggv/LoO6mM6d38cwjnKp4gkILccKvJFOPpshsFL4ePhcNDqkZx65BJWb53O21vuZ8Xit7E+rtntugeKOtNv8hR6//Y8Gvv2wZUngpSRVAukRy1s9kMoDC/mwG2Ggq/eRE6Wew1VJzqzazulL5qb8oatsK2OeyAc1cgOOw9pQPZHZKZloNZO1GaZjBwkM5Gos7Wci+moMjQwDH9vhAFBOCcIUwzpTkqQ9qO1Q5qBzucQNAulJeH4G9KRvIAqIB1aPy2edK7JAXt4PI00fg5Ik440doNlWaxdu5YtW7ZgWRYFBQX069cPn6+1ovJ/D+ud1kptEnkgHUdH3A1FLuhtOmJLo5nzwy9tRmkuJKwk9edMYet7z5PpM4kcNo6ct2bitvKIebWfklqoy4b8KjBjSTb9ZQpV058nw2cSnjgO48OZ/C4vjzkxLegxE9b7NAgOS+6XTlFojKvF8kFEhCarUQFjoQwIp7QcO6G8GNxOW8WL+vuupMLKbKOpTeRKot6QKVdKTS58fDoMLYDnamCOCSE3FO8EqyO0s2GbpeOqzpYVePgG6L0UQh5oyIY2O8FjmezV4SAWXXkQQ0ttAss3Mr/Deg5/O8abh2TQPbMfnTIK2HgM7KiCXn61Ffa1lWdiu8G3A5bmQy8vTDI0Q+UplMPxV0TMujv/X4DaKQEbHrSlTbnRUG5Ka1iFRKZLkH5jP1TJaKlCWYCqEtehYXKVqC3Scrc2EnRW21DUCP8Mw8GZMD4AZyPC0QuRhtYO6T0U2nUkTRbcFFoSjrOQs6g1vIxaNofv4fE00vi5IO1eSQMQ0Zg2bRrHTJ5Mdk4uvXv35sADD2TixIkMGzaMzKwsRo4azeOPP044HP5vHy7QpOeojkjv4AFCAYk1OwYV8BUzNO/EMmSNTXrgk/bgCyeJOoTjucl+PjrNT2DtMhomjaMuVE2jX4vMoCWavRJ1JVn5VxGO5yf7mXOan8zVy9h60Dje31lNJKnBbgkDSCrvw2do/gkhKA0AdWrVZNdCVgiy6mTrLStSJaQyn6/1Gl0MZyptVEQjlZxq4LhoDC2YSUPtmWmHQd9usK0BbjIlWuy+AWo6ac7LqiSQ0HUwbehZBhXtYNjnsGov6L9Y+8UQ8SlrA/t/bLBkcleObJhA8ZBDsCbsT3awgMp9wIopdnyxT62N/S2oMZWsOj+u3JOzC2CQATcZ0NHWgjsAzQ5Zic7vGtTS+pMNnQy49BsIx4fIkbLY2c9paCFvSTheRiLVB4CvEME5ld1JQxK1MwwbQvXweASmZMOQgHIzDkRtjhNbeS4oWv0ZJPj8JsLxImq5dGn9tHgP5XacsIfH00jj54TvRTpCoRDvvvsuf/nLX5g8eTKdO3fGMAwMw+D666//kQ8xjf9rrFq1ilGj9+Oggw7ivU8W4Bl6DMXH30i7qQ/T/rePUnLqbWQfcBaLd0Y588wz6dqtO++8885/9ZjDMYkkbRsakhJnYkPUD0VuGFsCsVqIGFpoLRMiQdiUBQN3JKm4cAqh10Q4ftXPw6ASF7NO9eHduIy1548jHqrGtCCjHlzRJOtvnELFTBGO5ttbq5dx8yHjqA9XY1gaJJe0IOxVlcUDZNuQXQ8hvxItvQnouUrVhrAPwn7ZZEPZ4DFV0VhvQG4C3FGdr+2QETPZFGiGrbCzcD6sGAHBGLwWgRoX9N4E4a4iPktN8NQ5GSNxMH3QpxOM/VTXbUcxtNvWtN+tneGQd5UXUpULI9bDtANgwHLY1A0yO0NtBLp4IcuUPmJpEj42wSyFEj/0dsPrblhpqKWQD/RD5/UEIgzHoZbGrbaEmpOM1uPMEyhE7H6kB9kbuUfGtdjORjqRx1Cs+gvIodJyO5Al9e9ADwuW18KrMfhdNvh9mv46HtluD23luSBi8xIiJEe3eKwBEY4TnW2m0HqWB8AclA1zFukBbmn8MvC92itffPEFhx66pz/HNH5KePHFF5ly2umYmQUUn/AX/J0H7eZacOcU42vfh6yhhxOv2kbN9Ac57LDDuOiii7jzzjsxzf//BbON1Y6WIwExZw5azK0Bb+0D0CcH5tdIEGoZWjy3doautUmmXTeFmmYEIoUUkTjgqWVUnDyONk/PZHtRNpWXT6Fm2jds//QyGg8ZR/CFmRjZeVQFlVY60JYLoz4EoULwRCEQlVg0vwqiPthZ7LQ8cp0gMkQwsi2IhhzBaAISzsu6nGAzG0fY6oeFI8D0wv3lUJMD3bZCp1z40BlG124b+OskHI1lwGM2/D0Bv5oN63tD7+XShbgs5ZiUtoWJC+C9Q2HYIljdVTNt2oSgby9YkIRuCWWAFAGFNtxri8BMrIJphdArE0IG3Oy4fF5GVuIHUFWgtw1P2crt+LMBmXtYcavQAv6V8/xRtB4XHkeaCx9qvzyK2iutOURqnW0OSsKrdTDfgltzYZ5buSmjkQ5kSCvPtVF14wNEnA5q8Xijc7wnoQrHyUDP1k+N+WjGzO9IE440fjn43qtFXl4eEyZM4IorruC5556jpKTkxzyuNP4/4KWXXuKkk07C231fik7/B4Eug7/RJgngyW9P4XHXk3fgVO7+5z+54IILsG37/9MRNyHVWqkIy0XisjWvpI0HxrSBZASSMajxwdZ2mpPSf1WS5RdMYVkrhCOFQSUuZk3x4d+4jMoTxzH7kVOoboVw7LL9qT6yVi+jbvI4wo3V2AZ0b4BNNhRHYZNX1tvMRnDHoetGqM2T8yQSUGslGpQeJYYWYF+FWiggcpHEaavQVOVwJ6GiGGpzoTwEdUElgA6w4YMSkYjua6F4h8SquOAUA17xQclchXet7ATd1kuPEYzBgqEw/DMRhSX9YMJn8FF/kaVVw6E4Fw6IQNCAuT5lWjyYgAPc0L9BTpQdGdA2AL93CEetLVdI0LGdDrPhz7bO85pvIBwrUftlNppdMg5pNHJbbFeJKhoDkL7jRed5rRGOHYgUHJaAJ2tgkQ0P5sI7brV9hiEysSfC8Shq85xF64TjVtQmedn53rv1U2MFSly9lN3bQ2mk8XPG96p0jBkzhqqqql1+dvXVV/8oB5TG/x+sX7+e004/g0Dv/cg//LLvNGnUMAyyhx2B4fJw//33MGbMGE466aT/w6PdFZZjlbVsqIsBpohHdha08UH/fAjvkL7izWEK5eqzGpZ8cCc7X3uOh45onUCk0LziUb1m8R4JR/Ptp5/iY8RDi7B/91synn2BzTnQowFmR6EmS+4Xf1R6juw62NkO6jOhsEJOkqRLFYx2JkQaIacOGvz6uTuuKojlvEWG1ZSOWp2jmTC2qfZI1wp4ryf4k9B3GVRkwM7ukBOBgTmwyQNbInDqLNjQE3qt0rG5LOlhQgEoqIIVvSGvBpZ1h7YVOtYOvUQQ6iLg8sIaEwbbMN6WzdW/E5aZMDgoncYgQ+6UG2gauPZbW6LRsw3lm+wJ7wHPouCvIc7zJ7Wy3VKU/XElqnZ8xZ5Hv69GDpEj43B3HVS54MlsuMGUDrk3srN2bOW5NopYn4vaRS2zQFIVjhPQLJXJNAWetcQGZN29hvTE2DR+efhelQ6XK83Nf8qwbZszf30Wtj+b/EkXfu/R5lmDJ5HZd3/OPe98ysvLf+Sj3BWVlZV89tlnAOysg3AcQlFpNhq2rqS6cg0lftivRDHZFXXw5GhNbe2+XpUCDj8Ed14u98y3qAp/c3VmUImLL8/y8/EZgW8kHAAJy+bvn8WxTZOCI04kZkjAWheB6gxZYV1Jp8qxCerylMuRdGkxC2WKULQFEknwhJSumjS0XZaTEWIAOGTDtKHRJ92ImYSIF3psgMXdZV0d/zGsbSdbcNcyOKBIrZO1FhQshnYVsLw79FmpfdkmLBgE+3wuIeeMsTByrkLR6jIgZwj8ygPbE7AmAS/71TY4IqlJuesiipxPZsFQH4w24D1Ds1O6oXbDwzZstuG6byAcceAeVFHYhoScF9I64XgLEY57kEPFRKFbrRGOeehYDonC32qVmfJ0DlxqSnfTAVUdWiMcFhq69oWz/5aEI0TTLJVXkcZjT8FeO4CHUVZIy1kvaaTxS0DavfILxNy5c5n90Syyx5+D6f1hH325B06lIRTiwQcf/JGObnfs3LmTMaNHMmrUKB588EHWO0W2qjCUrfyEaVcNZdblQwmv+oLB+dIJ3JEJ45eCrxK2toGP9ofq0f0Y9dws1jYEGf90dBfi8chXMW79OLpLq6h7vsnIjk1L2I56i9+8GWZlRfLrnyUsmymvRXlheYL2975AzkHHkBNR9aHRkNU1GFZloqAe8kIaulZWJCJSm++MpjcVctWxVLbTak+T06TY4XMp14qZlH4l4ZbwMuRT62ZjNyhIQvFWWNZeM2La1sLQPhDww2obdsbhsBmwoyt0WweuhDQkWVHNm/EkoDYIdblQng+jvoS6jhDopsX3y7AqBp18WoA/seAdA/IaFDzmLxJZWOG4by62Jdp80IZlNtxgQO436DeuRWTCBvZF81RaqxjcgcjG08hFMhxlgbS26/eRYHNUGG6ug1yfUkZPNeRsyUOVktYiyZPOay1Cjpi9WzweQhWO45zjOBwY2PrpUekc82XsnimSRhq/FKRJxy8Q9913P/78tgS6//DcQ1cwh0Dfsdxz3/1YlvXtT/iO2LlzJ+PGjqFmx0ZOG+Rh6tSpPPrQg8STsGbpJ3x800HsXZJkcEGMx347gWe+/IKHgH1WwCovvD0eVvSBYQtgvyro2nUQ/Z+ZxfJQE/G447MoZ78Z4erpUX73QbRVjcqOeovxT4Z46Ks4454IsbIiuQvhyHr8BQYcehwHl4MnDAlLpCEQlmYDoMtGqM+VhTfmUaWjPtMZ6GZrNkpBWI6c8iw9zxf9etyK0kid73GvSIiFtkn6tZDV2pBZrlCzjDj06A3eAEwDohb0WwP9tsDnvaH3CrVVgjFY2R16rFPFZcZY6TxMoLoY+nYFvwlTDaWodvfKZfN5AhpcMCQEJSGoMMCbCasNONZoGtu+2pYzJceEwXsgHCtQcNfHyFo7Cuk3Wk5fTaL2xmI0R+UhFAC2byv7TIk+t9vQuRFubYC9gnBzJhxnSDAaQG2OnFaen0AajRWoCtJS5xFGhONXKPjsYBTF3hrqEXm5AM1uSSONXyr+q+Fg0WiUaDT69f/r6ur+i0fzy8EH06bh7Tnqe7dVWiLYewyliz5gzZo19O69J+ncd0dzwjHrNC89802yvAb33jKVfVasZsG797F3SZL3TvaRtOHgZ6Oce9AEJn44nXd67E28KwxaDm3LNUelMQjLvVDSeRBFz89i2YkHMOBf9WyvS3LNfl7aZZlc+G4EgNsm+r4W1aYIR33U5pNfB/nNmxHGPRFiaFs3765P4n/2BXIPPo5INXyaCXZcc1rMpAiBOyEhZtsYrMiBsmLpKMJ+CLqg0QVt4hpt3+AFKwGJDMiugoagWiQA2NJzRP36t5VawF1ge6AmCe22yhWTFQd/R6j3wTqgwIbSBEyeCYs6aDt/VGJUtwWeiJwfIWDxAOi/CsbNgRcvgMJizS0ZlYBoQuFibmC9Bf09sLEOikKwsxDWGvAvA/raEko+bMOThjI9ctj9A8dG+o0XUfDXUCTQPJLdqxY1KG9jpPP4Q6hq0NoinkCD3TrbUNEA/4zAYZlwXACORS0QG015bU1XEUPW2S3IXdKy2hJGhOQ45HiZQOvD41Lb/h2FjbUmbk0jjV8S/quVjltuuYWcnJyvvzp2bK2jmsaPiZ07d7Jzx3a8JXsy8n13eEt6ADB//vwfbZ8AZ/3616xdu5bpp3rpVeDCMAz+eYiP80d4mfva7V8TjgyvQbbP4P2TfbRxh3n70EM4apnFXS9ASYVSQsMe+DwPSqol8PQNHITnlLO+Jhw3jfdxwd5e7jnEzx2fx76ueDQnHLPOyGBURzczTg+S6zd4f12crL/eQfC447AsKHdDg6FIczOh9ok3Lr1Fh0qNlg87bZH6DPB4NTPFa4G3EXIaYbMXEk5uhy+s5zcEdT0M1IpJpCwstv6AwwFwh8GMy1rrtaHDAKjw6Q77VDRVtsd2KFwJ8/eCgYtFOAwDtrSBgmrZizf1lPaky2YoL4EtXeArU0mivSOwCVjhh0OSsL+pykepAduikF0IvWwYamua7BobDjYUdPY5IgvNEUPthifR4r43sr0exe6EYw0iGmc6+3kHVUZaIxwpQjDQhjV18GQUzsyGiQFdi2NQheNyWiccUeBGYCtKS21JOCI0tVTeA8awe9ul+TnehjQtXfewTRpp/JLwX610XHPNNVx22WVf/7+uri5NPP6PsW3bNgDcOW1+tH26Alm4A5nM2baNdjSNXU+1BVJNF7vFv7/tq++ZZ/Le++9x08dxnjjKxGUaXxOPw3q62L+zm4xm40cfXxhjY1WS0Veeww7D5NFBsKFAlYLSdlBoaEFf3ga2PXEHsXtv/5pwpKoa5++tZeiCdyOE4jazNiW/Jhw98sXRizNMPjojyP5PRtjwt5vwHXYwhUV9iMVgZ47yKny2WheWS0THZ8B2r9I+vTFZXWOm2hbZtZAbgQZb4+1dSVldt5WoUhL18PUqHPOpgmK59DPTADMGUZem58Z8UNIXNmQokOt44B1LFYrRM1RlKSpXlcM2pOHIroVMN9TZMHOcJsQOnwu3XQFjM9VWWGjAJxHFtQ81YX0M2nkgsR1qXVCSCdl+BYBdCxxtSKOR77w3XyHdRAoV6O5/CZCBNBmXoBknLTENtVr+CSxDORtX0PodUzVqYxxlwet1MDsJV+dAwCMNyjEoW2QKres/wohwVKK2S8tQrxThOBZldezL7mQqhSQac38Ye3aypJHGLw3/VdLh8/n+5+Z5/NzxtV7hW/I4vissw2CWZVENZCN9QRa6o/S38pX6uQ/dbaaOJrWQGEDP446jzTPPcvUpJ2MAjx/l+5p4HNJzV0fJ3XOjXPxelGFXXMGpf7yFvMegNKg48hU9YO+VYAyBzGqYtnYWsWsuZ+owzy6EI4XmxKN9lrEL4UihOMNk9ml+Bj5QRd2hk7Bnb6DObZBwq5ViI3LQ6Ichi2FNT3C7lVjakAWJTPDYIgreKLjqNSsl4IFQI3RaD6VFgCUHi40W/BSTM2xdLF9U1RMXaqvs5Vfk+vnIJVGKsijaV8PABfD+ONhvlvbjScoB47JFXkIB2NQOznwUVvWEQC/oa2hq7OMJiMahXTb0SUqfsTABlRHobMCBxSIF42y4yoT2Blxri4DUOe9z6i99GUoYXQLshcScFzm/Dy1xP9KFPIdaMP1p3ckCErDeB5yQhEdqpfv4ew5sdatScwQ6l6P38PxG4M/O8f6B3eekpAjHMYgIDUezX1qD7ZzjaNQySiONNIT0wLdfGNq0UYUjWV8BTlvkh8KKhrDCDRzepg1DgHJ0p1iDwqMSyAppInuiGy2SqWXc63w1pxEGWoSCJ5zA2cDDp5xM0AMPHL770vTYghgXvxflkiuu4I5bb6Vhq8GyOpjfFraXaIpqdgCWmLCtE7gzBuDv0YtXVq3l0n0tehfubgE/f28vvQtNeheYdMxpvQv57JI4O+uTZP36OBriBtEMcDvTaLFVjei8TVHnlgUVhSIYkTbQwQVbLPDXyq6aiEJtNzCqwPKpEgESk5oWJJvldPijGhRnWmAltb1hQa5HF/EalHSZCTxoQZsE7P+R2jv+RshoVBXGRvNf/Dl6v6YdBNnV0HcNPHkNjPYql+JTYFBEi3iWX7bZZR7oVAY1QaWdrszUsf3ddKo6tlofXgM+QhUBG7VFXgWW07Ro/4rdqw4WEm+WI4fK/WixH9zqOyGx57PAiQm4uxY2mvBQNsxwKYp8ImqBjN/D8+sQ4QgjotRy9HyUJsIxCxiEptu2BhtNzu3FnklJGmn8UpEmHb8wtG/fnryCQmKlawn2bE3z/90RK1sPts24YcM4hNbL1jYSKdY2+6pp8e9Ii+emlvq8jh3x+Hxsa2jdHbO9Xrf/X/bowe2GQZ9aWN4ZFnSGEYugKgO+7AKbAnDwJtjizqGix15Ur1/NAU+EmHV6sFXicWC3Pf953PV5lEvfj+LzGFgffkj019XYwTyCUTAczYXlhh6rYF13PSfhgpo8cGdDdws2WxBsAI8FySIIN9qUvDmPshWvMmNVBRVvhPEGcrD69CD621OhuAhXQi2i1LTZqFM+yDWhwAPtEDlIAp/aMMKCdo3Q+1OYORr2+UzPdVvgj0AsAG6XgtQ+2Qcuvhu2jYTybrDJkJ20nQGfRSDTC3WGrMBdbGhbCl8WA3kiRBcaIhygFNGxzrX6EoVu3Y3yMnYgAnAKyuFoiQakvRiIWi7/RHkce2q8fobGy0+OwS11UOeGp7PhYVPZGqOROHVPuotqRDjiyKLbssWTIhxHI+ttX1qf55LCiyg1dU8VmTTS+CUjTTp+YTAMg3EHjOXdj+di73fKt8ae/ycIrf4Mb34BC/r2ZS5qqwxBd6UpoZ+BevcZaGH8NlhIBDn900+5auKB7N0myQuTW2/F/X6Mlx0NNvdOncoOoOiQ35DRA7JLYWsezBsGpge6xMBXmmDhvacR+eB1/nWon7vmxhj3RIiZeyAerSFFOK4a7eXEfm7GPbOcipPHkfvcTHz+PJJ+VR38jpMk7oGGDFUVYgHIdcHHIVU44h5wVYXY+O7z1D98LztWfIUrK58dWUUYbi+xWIj4vx+Gm6/GOO54rHPPwxyxL5iG3CvO29fXo4CsGJrp4QOKbVVdrM80o8VMQla9xKlmUhWTWLZI3+fDITsEbSph+TmwxQP7oGrDXQmoj0NONqxNwpluyKiFeS5ojMH1beFWQymcIMvvIlTBaHS+bkHVDT+qcFzE7tUEgI3A6cBvgD5oONyVtG5pBWV6rAfGReGGeolzn8uCaw3pRgYiweeewroqUGKqDVyP9B7NEUPakyMRuemOps/uCW+jaskZ37BNGmn8kpHO6fgF4rdTpxLeuYHolqU/eF9WLEzDsumYZ53Nk6abz5078HpUFv8T0hbMR5WMb8Ldd9/N6JH7snXrVkxg2aefcvrEAxlenOSdE31fi0afXxqn/30NLC1LAOzialk3dSrxRx5gyFpY2QNmHeDMFYnDGhK88s/TCL2hWSrnDPMy47QgeQGDcU+EWNUs9GtPaE44bpngY3BbN7NO1ayW2hPHYVRU4wvobrlgm/QbqWpEWRtIZMPIBi3MeY1gr17NsuMGUPG7szFjLoqPu472v32MtlNup+SkW2h3+j/ocN7j5I4+FfP96dj7jyJx3jnYCYV/ZFnKzNiOqhsr0HTTMlstnXVxGD8L5g2EIQt0LQwgowHCGRB2SVD63iFw0svw8YHwSTvobygjY40BGyOqBhge2N9WpWLpNlgb1DRZrweKDAl1QRbdrigZ9iVUeZmLUj+HOL8TrRGOOaj6cSsiGXOA39M64bDRJNlaoE8Y/lIHhX54MgvOM1S16ITcLnsiHDtRhcNwvrdGOP6GtCBfoErLwXvYF6jtshGRpjTSSKN1fG/SUV1dTUVFxddfqWCoUCi0y88bGhp+tINN48fBhAkTGDhoMHUzHsJOxn/Qvqo/ehywyD7vPCqAxRY8ZcFttsrqQ1FfeytwO7qrfBndnTaP4Lrjjju4+OKLWbJgHuPGjmHr1q1cf911RCMRnjjS+zXheG5JnFNeCbOp1mKfh0Ms2dlEPO482Ef3fJOVf7uCT/pbhDPgyHdgn/myrHa68y7q3niOBw7zfR1t3ibT5NnJAXY02Pz5oyjfhIaYzeUfRBnezuSWCU0C1NTsleiKRey8YSpVmbC+VpoOG2kpfBFwe8E04eOYIs7Dy5aw/qSRuBrjtDvrfoqPu45A9xEY5q4VF1cgm5x9JtP+7AfIn3Qh9pOPw7GTcccTFHhENupRG2Ac8A8bQhaELZjwJTRaEqHmV6rC4Y7rD78mS8RieW9VXfKqoXy02i35SIfxIrApAt28UG1CDxPWhMCOQjQAp+bDG/aurYtZqLXyJhJ2liGyMQq4GlW7WuIxREaeRSSlgT1naMSBu4A2tqzGf2uAQRlwZwacYIhs5KL49JbukxS2od9Fj/M9v8XjqQrHYYgwl7DnMfegttE81Eb6cSXaaaTx88L3Jh1DhgyhqKjo668tW7YA8Pe//32Xn19wwQU/2sGm8ePANE2eePwxYpWbqZ71+PfeT2jtXBq+ehvfDX8l2akT7Q3FeZejGRurk3CfrTyEj9CC+DsksJuDytm3A+fecQeXX3451+znZdHUILHqbYwbO4arr7mGvPx8DnshRkXI4rklcU59NcypA9ysvTCTzjkm+z4i4pGwbE5+NcqGOoOce55k5DaTA+ZAICYxYyc/FBx4CL6cXWev7Ki3OOHlMO2zDG4Y15p/ogmZXoO7J/n5cru1S3Lp17NXDJOsX51ELKF5K1GfnCdxj0SkjdlQvE2aA/eG7VScegguXy5tTv4bnoIO33q9DcMka9DBFB9zLbz3LvZF57EdtaIsRCDeReX9oUAkCYOnwVcDYPACCU8NJCRtzJStN+mCtw6B0Z/AJwfD0UUiMTWGKgGfJCA7DiN9yhR5yYAxpZBvQNIDAwPSeOzrrLSWrZj0t4EXUPtiFGpxTGH3DxwbZWG8g8joM6itciKtL96NyImyrw2bGuC+EByWBVcGZdMd6TzvcvYcxLUZ+AtyUP2Z3SspqWyNQxEBykPVjj1hKYpav6SV80sjjTR2Rfpv5BeKwYMHc+cdd1D/5etUz3oc2/r21kJzhFZ/RvnrtxKYeDSHdj+fWEIBT9jQ24ACA8oM2GBBZRK2W7pzPB0FKo1ApCNyxx38yyEcN4330TXPZNYUH7HqbZx9zllMeP5FypJZDHkg/DXhePSoAG0yTWafGfyaeBz2bIR/r0qS++LLnJh/NL0Wg9tWZcFtQr0HYr370fG5Wax0Zq8sL08y7okQDTG7VVtsazi/RYBYPGkpCn1ZguK7XsA66RjySxUAZiY0wt6w5T4JW1DnkWgz8rc/Q0OI4mOvwxX4bpM4At2Hk3/guSQfegh77lzc6I59C1BhQ9yCF5Kw7xLw1ENlNhSX6Ti8UfBZkPRCkQmzBkhvMrIcNvaFsKG7/p3A54YCwQLAmx5VKI5JQKgW1gdgWACm25BjaKw8SEC6AvjIUMtnMNJvjG7lPMIoR8RGIWF3Il3InlwhFUgbcrQNs+vg+SickwO/8itQ7EjUwrua3SsXKax39pGNCEfLK58iHJOQBiUDOVb2hHWILP2OXd1XaaSRRusw7NYGTfyXUFdXR05ODrW1tWRnZ/+3D+cXgTvvvJPLL7+cQPu+5B58IZ7Cbw5nS0YaqJn5KA2LPyB48GQ63fws8RwfHatgiTNGPs9WibvYWXiqkUMjCPQwtEhVGLDhjjuY14xwNBe1bqi2GPNUlGReew486iSeufMWpgwU4XCZTdtVhCz2fyzE6iqLYXfeTcbpF9JnDpguSJZpmmg0C5KDoTELSsvBvWARX07dH1+ojjx/6zkc34Z7v4hxwbsRehearKq0aXfni4ROO468CGSVKZCswxaoKIKNXfScolKoy4dYYy21/dqRM+wYckef9J1eNwXbSrLt4akYRx4Mjz+OD1UZcm1VAeoScNjtsLYIirZC2x2QEVILxfBBWR4kM+HJyQoUmxyEF3pDZzcsNeAoQ4FgZoUqH+tz4BQX9N4Bq0vh/TZwT1vpdrJNDXJbDFxgq/0xxNAC/wDQuZXj34oqH6ei9tuTiJzsKbJuE5q1cpoFT9TCpxb8IRvae5RieipyQF1GUx5IS6xCxCYf2WJbjjqMI8IxESWggioue8JWNO7+amRPTiONXzL+0/U7Xen4hePSSy/lo48+otgTYfuj51H+8vU0LJtJvHILtpXEti0S9ZWE1s6l8r1/sv3eM4is/ITf9/oXL/lfpqHIR3YtlGZqtHobZKvcYMAqW06VfYAiF4RMRWN/mYStr77OvMsv57J9dyccAF3zTOZM8eGp3sbTd97ClEGe3QgHQGFQFY9u+W5W33QDN5VG6L9QOoKlvdQ+yHFDgw9KbWgXAn9Re4zsHLK830w43l0TZ2NN6zbdVMVjVYVF4MAjcR19HLZXg+XiLrUy6rM16TWVHmo54WCx55+EWJTMQd8kS/xmGKaLrIGTSDz3PFRWEkVWtCJgexJ+tQ7ySmFHMbTbrvkv/oiqHNVZgAs+G66BcyU1ML8L1LhgLWo3DAAaErA1BoUe2NtUO2ZTuaoBRg7UGgoPa2fA68A/bdiAxtoPRjqP1gjHXOAEVOnqjSoFv2fPhGMJGnV/RhLurYHPbbgjFzIdwnEG0rNcwZ4JxzJEONog7cg3EY4NQE1lJT3nz2fu3LmsWLGCRCKxy/blKDvkMtKEI400vgvSltk0GDNmDMuXLeX555/nnnvv46u3bm91u7btOzBp1O/x/eocRn5UQoc6uOMauORW6LxZA9Wyd0JRiVT8ZSh1MxMtLn0M2OCCchtiPXvhy81h5pYwtVHIbUVO0TXPZM5pPt5b6+LsoZ7dCEcKi0otNtda9JgwjE/DProCHUo1F2RxP+hVJiul34asRli7aRmJLVs4cqiH7nmt7zPlUinK9jBnirdVO+2xe7m5cY5J1ZrFhDJs+mw3cDUos8K0pemoypPeoqRMM1SiPog+/TDBniNxZ+6pCfCfIXPgQdR88gyJZ5/FuOBCsNWC6AckP4Cv+kDvZRpfH4joqyFT81uShRDxQH0O7JsLr2Xo+nQDVgJ/BJJhzU1Z6ZWbpWM1rEtqPsz+QbVW+puaqzIXkYNhSAuRw+5uEJBQ9AFUIZmHQsmuRmFxrWE2Cic7Oa4Mjq0mPJIDy025XFJx5hezZwHnQlSRaI/0Iy3FqQmkLer41Vdcfd99zP9gGtVbNu2yjdfnZ/DgwUw59RSOnDKFB3JyuIg9t3HSSCON1pGudKQBQCAQ4Mwzz2T+l/OorKzkww8/5JFHHuGhhx7i1VdfZdOmTWzbspnTX7mW8RtLmDYBsKB/BdxwI2zqrJkd7i0QqYYBNuQZUGZCpaEo7loLetgwzoAO/fvSd/pMljcGGP90lJpI612+zrkmU4d790g4pq9PcNjzETzjD+TcV19jVMSQgNAL+Tuh/RZY1wNyPBIh5kXAGjMW9x1389BXca78cPdR9inCkX3+hVS368r+T8V2s9OWNljs/0SEmkAh7R55n8ZMg06rRDLcliosnpiTJOrRMLa4VyPt7Q3r8LX94dN4XcEc3PntYN163LYWwAYLktvAWgPrO0HX9Yo598bV8qnPAMMFn42AmAt8CShvD8lkk7XUiyoBw6LQyaVQsAZg9g6JRJcWK15+kwF3G9I11CP9w+9R6udn7DqTxEbzU14EXgPeQJkdv6V1wmGj5NJlwKQYXFergXrP5MAcUyThOHTOZ7FnwjEPkZxOiNy0Rjj+sGED/z7wIKYMG8ZH/36TRMkQCo+4gpLT76Ltr++hzUm3kDF6CsuqDS66+BJ6tG+P6847KUx+Nx1UGmmkka50pNEK8vPzOfDA1iOQJhXA1ZMhvgbWFMPAOpiwAWr+AXdcBAOWQXQ5VAyAflkiHFsNLVr5wAZbPfxewIChQyieMZOPxo9j/NNhZpzqI9f/nxsOU4Rj1PgDOf6112nj9zPbB2v2h6WZGt8eiMHQUvi8vca3m057wfvbC+m0CW77x0UA/O0gtXhShKPkqquI3nALnopy6seOYcxTG76ueJQ2WIx5MsoWI592//4Yq3NPOm6C9R01fM0yRDrKihU3nhGWniQUgOyKJJXhRgxfa8bR7w7TG8RVW4PXUHUl24De02FLT+i7XK8fDMkmG/eD7YK1XcG/HT48HHDDMh+sTKr18LGh750tCMShLiBtzvGNsDyqykRvRxy70yEcBo5oF1hpiNytpikYLoryMgpR2Nc/gENoPY0UVBl6BFVLBkbgzw3g88ILWXC/ofTRQ4Cezvc94VOkFemG2iAtP+wSwJRnn+XVc36D7c2g8OhrCPbcdzfLMoC/0wAYcRSJ+grq5v6bGy+/nGkv/5vXXn2F4uLibziKNNJIoznSlY40vhPcQP/BUBiEDydCfRjCfjhxAfz6SVjWV/M72iySpbGdrf5+hqGgKcuABTastdV6GTlkCENnfHvFoyWmr09w+PMRBow/kOtee52j/X42AQctgbGzoftG3Ulv6AqftZMYcrtPZCQShMxGaHfshfS44G5u+yzGlR9GvyYc3X93Fb6bbyFsGGRkFpPz4hxqSroy5qkYszcl2P/JGFvII/DOx4T79yRhwvAvoXgrbGkvm6wrIQdLOEsj6sMBaTv6bjBxmV7sxDdngvynsJMx7ECQuA01FkSjMHQhrO4BPdZooq074WSGOPbdTf0hux6iQfhTIVxkiTSUomrFdhNWRvQ+LQ6odfJ6qYShmzOhxgOzbQVzDUMtjj+i1sUY1FqLo2CtK1EFxQdMQALOPcWfg/QitwNdbcgKwU31UOKHp7PgRkOVi9GoivJNhOMjRDh6IWdJa4TjpEcf5flTTsHddW/anHEPGb1Ht0o4msOdVUj+gVNpc9ItzF+ygv3G7E9ZWdk3PieNNNJoQpp0pPGdMTkINUMgWgKrO4M/qcFfv5kJR78NC/tDoBK6LICVDZBha5Fpg9wtGwzINeAdG2ZY0H/wEC6aMZOV9V6Oein2ra+/qiLJYc9H8I8dR/K117na7+cupBd4p0YLnicChaXQay103AHbkpBZBUt6afKrmdAgtsITL6TgTyIel74fJfeKq+h70y2YGNgWZNZApHMxnnfnUNO2K2MfD7GZXLo88TEd+/Qk7HWixWP6Y8pogMpCVTW8MfA1QmNAVYZOW6HtDoNMfyGJ2p0/+H2wrSTJugrMwkJiqE3RoRRW94ZOa1X5yGhUCmnMq9CvJf2h1yJ4dbLaX4e6laWSBRxhapFuCxwX1lC6ER64JA45dbquK9rCPFvv4UbHoTIL2U+XIz3IDkRADkPVhruQ7fQlFCP+LArkugNNkJ2H9D91yM46wYayRri9EYZlwj2Z8FtDLqg+SDPyTYPUPgSeR2TnUnb/kEsAF338Mf8+5xyyhhxKwWGXYfqC3+na+zv2p/Ckv7JpRzlHHzOZZLrVkkYa/xHSpCON74xsoGNHKCiA134FVWEosCEZhIv/Dft/DvMHgqsc+n0FixskUuyPRovnGjDHUHk+34D1NlSvWEmkoZG++d9e6cgLGBRmukisX4e3ooJqFC5VYUmzYJlK3ky6lLRZFwQM6FIOvTZoIisW1OTCxm5gXXwh2f94nJE3307nm2+hxjSotyGrBmozILF1E7FTJ2OccxE5J59H8ZV3U5MD+RlQ1KBKT102xOqq2HH2ROzb/k5dHtTkqKIQ80Fyw3q6PjaHActhn07HElr+0Q9Ogw2vmYvVWEvimGPw2NA2DoEKmN8P+qwUGTKQpiPmhx3ZsLUDtKuBsraQnw13GrDVgmMMWGKoIpSRhA0x6OlXXkePMrU6qr0QyNR7fYahCsdc4ApbrQ7T0PvwJHA4cIAuM7chgrE3+h34Nap4nIMExuUo0vxwlDNySwRusWB4NlwYUJZHkfN1MnueNAsKJXvV2eZ8dtd6JIBbQyFeO/0M/O36kHfg1O89f8iT1468I67ks08/4Z///Of32kcaafzSkCYdaXwvTPZBoAASHWHe3lCT0Ad6QQZc/QgMXi7iYVXAPl/B540QtKAY2THrgDYGrDYg+/nneGTKKUwZ4ObeQ/ZkemxCcYbstLnV21g/dgzVzqyW3mFY1x5eGw8buil+3HZBSQZ0NlSury2AjAjk1ajlkF8JwUbIO+F0kldeRp1psAmINDrajK2biB4xBveXnxK74kLcFmy96mQqDhvBlq8W4C2HojJY2raKedeOx5o7nbqbryRx81+w3bC9PbBsKYn9R/DhX8eyZcbT7N/7XJKN1YRWffqD3oO6Re9g7DuS3EGDMQH3JlVvsmvAE5eWw2WrpZLwwOd7w6SP4fHTRSIsF1wAlBswwtCCHzJgeFj24oIgjLDhkwppNEqLwDCgowF/MkQ+81AWy1Skm7jAeX87oyrImWjC64UouOsVRAYmoECvy1D+xmvAoRaEG2BjGE4JwGCf4tTrgE9Qzst6FD7W2Mr1eBWRjuFoYFxLKpFEepId993Hzs2byZt00be2U74N/o79yRp6GH/44x+pr6//QftKI41fAtKkI43vhW6A2QH6heHFM6A2qVCw7UDXDLj2Hui2Bb7sDw1VcNAXMCukVM5laAJpDuB/7jlecwjHo0f69+hSaYmueSazp/jwVm8jPHYMf966lb9WQGFM6aNJG9Z1gKV9YX4eVBtK46zMVsshvxJyaqG0rTQPe4dgkAmnW1AUUZWkoWoToSPHUNJQysrzMjh7iJvK5+9jRFuDgZlhNh84DnP+Anw7qig9czyULmPurwPccICP0N+uJfHXvxBau5T4pLH08TdwygA3j3x8Gpsq51Pc9wDq57+JbbeeA/JtiJVtILphIe4LzieMKgrFmzRPpXinQsCSHsCpclQUgmGCLxMWd4OkD24GqmwNn1vjfBJUI6tsoQc+d0GgGtZbUGlCr0K1bP7qiEVTGRxfooU+DpyN2jwvIjFoV+RoORbpO+5FBGOG8/VnNNfklCS8E4F33ODOhbc8craAqi0jkPV6G7Lo3u889y/O6/wB5X0MR0RnT4RjhGXx8r33Eei9338UPf+fIHufXxEOR3j66ad/lP2lkcbPGWnSkcb3xsEeyG+jsveL5ypIqr2tpMY+PrjhTmhbDl/1htIQTPgUPgzDOEvWRddzz7Hw1O9OOFJIBYjlVm/j12PHcH/FVnpUQ+GHy2m/I07SC9mWwrqSwOIe0OCXTgMDfDFVOarL19DLaGS9AVYSdjSAvWkT9sQxtG0o5ePTfHTONXngcD9nD/Uwb1uScwaaDMgIs+qCsSy6aCTurcv4aIqPgW1cXDvWJ+Lx12uxRu9NH18DM6Z4eeJoP6cN8vDIJ6eR32NfoqWrqZ7xyHe+7slQLWWv3wy9+5A5+TjigBl1AsBCEq36I7KdNgYAL3y2N4z6GO47UjbehAs+MuE8FBX/IhKAepIwPwYZfiV4LiwVEYnlQYFLQ9ZK0Oyc7siNVAjUG9Jw+BGxuBVFiX/TGPgZwMfA7xOwvFY6nHe98JTzqXQHija/Cw2Mq0YakZdQG+cj5/kPogpKObAAtW5uAZ5COR/raWrvrPriC3Zu3PCDgtlawp1dSLD7cJ58Kk060kjj25C2zKbxvbEP8GY7GLYQXh8Ge+8NBQuV8VBvQB8TbrgDrrkSFnWGHtvh3Nnwxv7Q4dMZvHvqKZql8g2EY+aGBE8sinPXJH+rdtqueSYfTfEx5sltvHHU/vSZcDEzn7yEtnsfytiLX8Ho5mNre93N+wEMCGXC1oAEltF3XqP+guO4e+BAgtNnsNLOpa5sE8kjxlDSKMLRKUeroGkYPHC4HwM4/50Ifz3Qx42zG3CXr2HWGUEGtmkq1V87Vm2iP80Kc8QwLwVB7eORI/XzJ966FU6ZQv3TTwKQN/4sDOPb7wEStWXs/Pf1WK4k5rvvEHH78NvKBCktkk04EoCoX/bg9g2woL9i0Nf3hYoeMMaCTFuOkw8sRZYHUQWhXUSulIUByIpAIiqC2L4YNtoKC5vhJJCOQlNkQYv6EFQBmYwW+NfQdv5mXz7naz5yqgxPwLmNUOOGGzIV/nU9qphsQtkee5J42qh6MQcFf+WgYW5rUCsn5Q8qR22g94EN8+ZhuD342v3wnJTm8Hbox8LPnyeRSOB2pz9W00hjT0j/daTxveEChrkg1g66VcMbx0NBBYzaquCoTgb0TMLV/4S/XAzrSyCjEk6cCU9lZ+H2eCgLaUCZq5X1NpXDEY1bLKkwmL6HHI+aiE1D3CZpJZj55CUct5ebN756j/f+NZn2T7xCIOEjYIHt0UJsWnKZbJ/3GrUXHMfB3Uzmrl5C9YTx8PqH2EcdQFFdKR+f0UQ4UjANg38drvjUq6ZFKQgYzDh9V8KRwrVjfSQsmxtmx+hbaDJlkBfTMJqIx7NP4T/2NOpffZro9hVkDT1Ctk337gPdE7Vl1C96j/pF72EW5dPx/dls7twVKwnuiNoJvoj0Ko1BsA2I+xRzPnswjH8Xpp+tKojXgvGG2iJxG3a4NEV1rQFlIQh4ocIN+21xgsIy4c4gHGnBzYbGxm9GAtITUMsjJeDciaoMqUFqSbT4p0hAIxKatkGtsIfDmo9zXhBmGqq4jEGVjH0RmWlNWmwDXwBVSEOSsuX2p4nceJCLJQ/pQhYAicWL8RR1xnD9uOPZPG26Ux0OsW7dOnr3/nEJTRpp/JyQJh1p/CAcDNzVBobvgBeL4cvjoPhR6FGvGRZeAzo1wB8ehJt/A8vyAQOm1o7giZffYfpxh3LUi1FeP96H391EKKavT3Do8xGKxx/IFdf9mSsOndRqgNiCHUnGPRMllF9CfMsWLtvXy20TfXywLsmRL7zH9rMnk/HSKzQEfFhVG2lbmktxKJdN81+j9orjmNzHxR0HeVhbaTP5lSXUH3kgRu8+lE/fzMoKazfSAU3EY1AbF+O6utirqHUxYiRh88X2JAE3u8x32YV4vPoUvt9dRWzePCrfup3qmQ+T0Wd/3NnFGB4fVrSR6PZVhNd+ARkZGGeejvGna6G4GMOCgAG+BqjLhM6bRDRqixxiFYX3xkDfRTBvDGzpBgUJKHXDIQY8a0GNrSrBQsAXh8o4hLOhXRIyqjU7x1+sRNkS5DZaR5NWYxkwy1Db4xOUNNqcMrlQpSKIiMcTOC2XCNxeD+388HAQ3jDUKrkWpZv+lj33fpPA3UhTci6yz0aafUWdfTzjbGOjVtFk4Mn6ely+H39aissvmlVXV/ej7zuNNH5OSJOONH4QsoA8E/p2gEVl8GV7yDkd2t7jDAYD+hngK4eLnoN/ngQr8rUYTayfQO3L7zDjuEM5+sUorznEI1Xh6DT+QNq99jp/8/s5c/pMnpiwa3Lpgh1Jxj8TxZdfQu2WrV8TDsMwOLiHmzdO8HPEC+9hnziZrhdeyuITj6SmuC11Z/2Bndf/hsm9XVwzys2QB0IE3PDE4X5Of2sp9bZNcvAwDn92Hm+dHGRi993/TEzD4Py9d69IpBBJ2BzzQoiPNiZ586QgIzvuuo8U8UjaNk/fdisZ783iuH/cy4v3/ov6t1+BFZUQCUN2DnTvjnHf/RinnIxtGARWraS4sJgtgLcCvHUhrFXL8cSHE3VaK/WZUJ4FFR2h0xaY1g9WZMHECJQZuvvfYkOtSxNl37YlIDUMCPqgR400G+VeOD0X7rbhOIfrLURtky2VlRz+9NNMnDOHTV/Op6GijFzDoG1JO/bZezgHHHAAJ510EplZWVSg0K9DbFgfhsfC0CEDrgzAnw1lfewHLEWVjteBMEqybWz21YAqHHVAB5TvcZ/zWL3zeAhNnAUJbFMZJu8CCbcbr/XjZ2qk7M8eT3rAfRppfBPSo+3T+MHYiIK+zIXwTBHsvxOS6+GKp6HKkNA0gFJIZwyF5w6GUAb0KYejGuENczovH3soEzrBhcNdHPtylD7jD6Tja6+zxu9nHMqDyFqwgK/Gj6NnRpg7DnQz+eUYgfwStrcgHM3x/toEhz0fBgz27ehiQ5VNWUOCid093HCAh4lPh+maa9AYh/qozb2H+jn9rTiNHXpgrV+L20rw5kmtE489oSXhmNCt9edWh23GPxNlaWOAnrPn4evZk7VxDYYzbfAkbWIenY8NGPV1+A8+iPDcL2hz972Un38eAz+uZ8XvDiY67zOGn3I3XY+4kM2dYMqTsHxf6L4UPj8Y1gyHbVnQLwkNbrVTNlvwigGdTNhqQ7AcAiZ0zof2KzUgbmd7OLEN/MGGawxoNODP1dX4r7qKbU88CZaFr31fvMXdcWUVgG2TqCsjXraOyNaVuIMBep53Hu7rr6dPIEhpHNYmodgDA90iMNVIjOpH0elRRDiaVy5SLZoGVOnw0DSzxYXunnw0VVRK0O9dyvkSRm2gBddeS9U/7qPDuU9873yO1tCwdAaVb99BdXU1ubm5P9p+00jjp4L/dP1OVzrS+MHoAlQYcHRnmLcVpufAMXvDxnUw4HMJ+3ogEeL+X8H2bJg1EtYWw/QymByaQO4r7/DQ5EN5f02Y7gdPZPhrr1Pu93MxKpP/FnhmyBDcM2aycvw4xj1RS6BjB6q/gXAABDzgNWz2bm/yzsl+dtTb7P94I6srE0x8OkG3PIMPTs0gkrAZ/2SI89+JqOLx5loau3QjsXE9RzwX+o+Jx3clHEsaA9jTZhHp15ONMU2AxVBEvG00EQ7q6nBNOgjX4q84ZYCHZy46H8Ih1j/zCu7V8/jVQA9PP3MRtgvM8y5kXXdo8EJpCdS3hxXZ2tEqF+wPvGIrlj5hwHYD4nFwJ8EVhLqIbMO1JmQWwhu2WiZfGPDStGnUnDoFu66BnJEnkjlwIq5gTqvnmKgrp37he6y86258r7xKxdPPUTt0OEEf1JhynCQRaWhApGEzIhR+RCCKEXlog36PTGS/PQhpS0xk096GyO8zzj4bUNKpB3jeFmmxbEgOGYZVX0WyvgJ3dmtzcL8fYqVr6Ny1e5pwpJHGtyBtmU3jR8FBwKd5Ks97fFBfAW9NhUg7aQY2oxyHpAEnz4b9loArAouL4cMATGQCV7/xAW0uvoyRr75OzO9nCrLf/hWJD22g/ZAhdJn1Ee2nTiVeWsqAYpO/74FwAFzwTgTLhmcmBwh6DLrnm8w+I4NoArrlinDkBQzaZpk8fISfbfU2765NcNsEN4k1q8k7/2ISLjdHPBdi8c5vL8uf+XqYmRv+c8JhvD8Lc/BgKmwtlpZDOmKmCEGqDGmedDyehfOZcYqPp47xc8k+XrjqCpKr5zHtFC9PHq2fzX/yImpnvc5XI2CfufD5aKhuq31l2HoPNgOlBkQNvV+2DZ6QFvk+PsioUWUqkgf93bDUhiwDnn3lFSoPOQRPsC1tz7yHnH1/tUfCAeDOLiJv/ym0PeNurIiLHRMOoPenczjb1PC/8WgOy+NIc7GRJvKwEvgK2WofQR9UWcA41Eq5G0Wcn4II6R+Aa1B7ZpENi22IWNCYVAJtGwuG2TBm5BgMr4/GFbO/9b38T2EnE0TXfMohBx/0o+0zjTR+rkiTjjR+FHiBZwy4tBMMqYD5+dCnGh6+CjJ9utPcgNwFhTac8y4MWwGJOCwphg+90MMay0F/vZ3lpp9jLCfvA+Uw9HD+nQGcMGgQPf71L3IeeJCl5TbnvR3FaqVLOH19glWVFkkbDns2RHVY23TPN1l5QSafniXCAbCuyuLEf4fpVWByeC83F38YJzByFIMuvgizW3eCHoMs77eX49tmmsSSsLWu9dCv6rDNBKelYnw4C2PoYGwDwkkJPzE0GbalZcPqP5BwNMmC0iSGYXDHwT7+dZifOaf52LeDm1Ac5u0EnzeA3bUzgTAsGwLtC6BNDmRa4DFE+lYiTUSKQtVbSjAt98BHCaiq0TZLi+FBW5NjF3w1n/oTTyLYcxTFx16HO6vwW69FCp789rQ54SYCRT1YfeRhzN24kV8hi+2laER9EWqLLEXW1kdReNnvUJz6NOeYZyKx6peoNbMCWGnDElsuKCMJSUtTckcAp5hwKjAyBkY9rMsowDP5eOoXvYedTPzH5/BNCK3+lFhdJeeee+6Psr800vg5I63pSOMHYxbSXHRFrZb1y+HhDBhaCd32AmseHHq7BH5dUBl9E2D54a9Hw7z+EEhC9zI4wYa2B8AlNpzkgS9Mld9HIMJhAn9CtsxHgBWPPcb2s87iN0M93HeYD9OpeExfn+Dw50Ic0MXFlaO8HPJMmD6FJjNPbyIaKayrstjvsUZMAx443M+Jr8awB+yD78XnqDvmSLLWLGPWqT4GlXx7ZLZl2/z2rQgPfxXnsaP8nD64SWyaIhxLGgMwfRbB3oNp9OmkbBuGlsNXRRLfGoAr6QR5AWBjXHQh9r338uDhfs4Z1rTfxpjNwc9FmVvm4rTjp/H+DaMYtBB2dIRYT1gXhITVpH2wgJhDQIqAWBRyqyCSBdkx6L0RajNhUm94w4LtsSiBYcPYVB2lzSl//952UysaYsfjF9BhSH/2mz6NEYZBHFUu4s5xmag1UuF8X4L4VzvnezkQtnUeAVval3JUmWljaP7LyShrZGcC6iOwJA5lHtmIQwkIr1xMwhyEfgAAiJBJREFUePRQckadSO7ok77XuXx9TpEGyh6/gANGDue9d9/9QftKI42fMv7T9Ttd6Ujje8NGffkVwBVo1PgHwAGdYFAFfFkAS7bAnNHQ5ggRjo3O8zoBiQhc+jb0Xw61fthWCNOByjlwtQl3xqEwqbkeFUhsuAlNGX0C5TMMPfNMej/yCA9+Ff+64tGccLx6QpBxXT3cfYifRTstHpi/+xTbG2ZHKW2w2VFvc+zLEaz++8DTz1H/HQkHNNlpzx7q4czXIzyxUK/XvMLhnTELY+BgsMFlaLG1LNjscua92PpZwvkyAAwD46ZbMIuLufGTXe/QP96c5JONMTyn/hbXvqMorISlA9QeaZehAW3ZthbkTgZ0MKSVGJKEYyzYO6bWSokBmY0ihUYxPGkrEKzDQw+xZuVK8idd/IPyLUxfkPyJF7Jx5gwSr79OGOkxNqHZLmtQe6XB+focve9xIGQrX6RDEvIsyLFEmPJMONuEa12QZcIoG8oisLgGZjXADBOiXkiYisInAVM/G8gJPa6h7tPniWxd/r3Px7Ytqj64D3cywoMPPPC995NGGr8kpIWkaXwv2KjSkI166gbquecC8UwYmAWzkxBrgDPD8PRp8Ot1ULBco8/bIRKxvB4umQb3mvD5EOVL1FdAz49h2hi4KAnPARe5VHI/BAVGnYtSLbcCvz3zTB7fAQ/88SwA1lcnSVpw36EB/G6DhaVJrpkeZXg7k6nDdre5/ml/HzM3JChtsEm4fBhPPod9wpFkfkfCkULzALEzX48AsLPRZsG2GJmPP0xo0GBMC3LdzqwTwLChMUPP95kiISDNRcIG6usxDj0Yf30VL5686zlM7O7ikn283PXgnXzxmx749z6PumwYFFSmhmVBxJSGI2RB3IQGG/ax4TOgYwxKPBAMQ34jNHphZS50sMHEZv699xLoNRJvcdfvdB1aQ6DrEHwd+jLrn/dQfPTRpKScZYh8NCIRaaMNuTYMshUAZjnXooPTLhlkKPirxtn25TiMiMC8GGxzQ8wLnW0NsNvg0kU+9B04/TmwLdg09lq+CH3E5pdvoPC4P+HvsNd3Og/bSlL1/r2EVs7hhRdeoFOnTj/42qSRxi8B6UpHGt8ZcbTwd0Fjx5s3Kw5HuQmrO8LYKlhdCLVboNwNm38HhXmyL65HC+5gIKMMzpkDI5bApnzYkA9VUdjwKTzhgookXJeAKZVVPL9mDScDj6Fch78BfweOHXAmky56hAe+ilMQNGmTaTDpmRAfrosz4cnQ1y6VVGslFLeJJ5s0HjNPz6AwaGDm5ZE84TCC30I4IgmbS9+LMGND67qAlhWPTjkmg9t7CV9yIf7PFuBLwHavFlnLhkAIGv26mKlKh23oywDMk07AO/8Lpp3sZd8ObhpjNqe+GuHxhbGvNR4X7u1l0YPns3XRm0QDkO2D7bbTvjDAZ6nFVYKIzGeGhJZL4xD2wOqEokGWFsGjBgy1Ibx4EbFVK8kcNOl7/a60hsyBB1M6YzpvlZXxFBKMdrf/H3vnHR5Vnf3h996p6ZWEhN47CEgndGmK0lRUir277lrWumtZy+qq67p2RaUICoKA0pQeUDpI7y2EkF6nz9z7++PMkEICwXWL/u7neeZJyNy5c+fOZb7vnPM550jU5fIAFAXAq8lxJqgwXoXXTDBHhQcUKFKkPToatHXBxmIoLpWoW04YdDFDpApbzXBGhZaHYMadcNvnAmCZKeCNs/K767+lSXIXcmY/QdH6z8/12riYvHmnyP38UZx7VzJt2jSuvfbaX+zcGDL0W5cBHYYuSQ7ET9Gf84d56Uir6Z3AI2EwOA7iPLDCC1eVwsexUPoIeE3yDbUR4tNIBsJPwsAt0Gu/pFn2xMN3Dti9ET4ygTkjkx7dL2dqh/as+O47zMBZJCz/pgcWNwfL5FtoPPIevtjj45XBNpw+ndFfunB4dRZcH17JNNr67TJ6f+KoZC6de20Y/qws9OxsNF1goDqFymLf3ORl5OdOvjtasyHRr8k39GgbrJ5oo2OEC/9VA0ndvYM6GpgViNmxC0t25jnzqC34WBPyH1QHtJatcXsD7MvVcHh1hs5yM2u3n1sWuvlwmxeXH37KBastDG9qPbw2WKrIaHuvAmZdolKpCpToEB4QY2ZTv/hJFDf4AnBMgdJEuFUXY3De1i2gqNjqta7dBVIL2Rq0B2DYlm301+R5t2iSTtmtQj0VnjHBVBU+U+AKBXYB/wRO6pDpg9Qy2F0E77gh3wI2O1xlEdhYrsrU3Ogy+NM/4B9/BrtDWr6fTQV3LESYIcIWzdN3fs8jv3+S0o1zyJ56N8Ub5+IvzqGq1U33e3Gf3EXeolfJ/uwB6kcqbFi/nkmTJv1i58WQof8PMoykhmqtfODvwGSgZZX7vMi48UYIRGQDgz3w+i5YkQJ3FEFuW9iqwD8Wg+1jydc3QHwePmRg15qe8F032NwUmmVDlwK43J/Jow+l4co/TesE2HBa4YpF39B56FCGAd+UQcoHsMC1gDXPjmdcKxOzx9k4WaTTa2oZhW5Ia2ji2xvDySrVGTDNQZhZocCl0yBGYdVk6dOR9pmLE9ZEwr9YgucPd2A/uIs1E210TimPdlTswzHvujDe2eJjxTE/i6r08dB0ndsXuZn2k49po+1M7CgpkSK3zqCZYia1fr+asDNnKBw/BjU+Dm1NOlqrlqBLmsmmg24Gjw56QIMH7oMP3qdlookjhQpawI89qR7unExa1TFxxGGl2QfLKRmVRl50MB0REC+DJ9hrI1WVTqQBTSIoscUyWTfMCTYPqHZwNIJiDewKFD34O/xfLqTeLe/yS0nXdU6/NYG2z/yJXo89RhwQEwSLJOCPyLWxPvizI9BGg90emOqFsgB4zXDaLD1Huprhex32alCiQoQPBmyFSV9CTJZ0KtV1ONNQzLKhLvoRSTByMsTGwe7du3njjTf4fNZsfF4P1sgYzHH1QDWDpxRX7in0QICmzVvwu/vv46677sJut/9i58SQoV+7art+G9BhqFbKAN4H7kP8GBVVgPRNGAV0RVIDfwL+Aqw5Ce+5weuDLqlQHCuh8ai/w8F0iZy0RIyMJ5Fv47OGw4/tYWcq1N+TyU93p2F3nmbtFBv1ohRGz/Gw+hRcu+gbmg8dyvBj8PAXC9j45/GMCQKHWVU4nB9gwDQnFhXynDpdU00cK9QItyismRJOdplGz6lOWieqOH1wvEiD3v0xfbscm8eFOmgw2rGfWDPJTucUU7WNvzx+nXFzXJXAoybgCCkEHruKzOgeL8ObKhwqUjiuxhJYnY6tRUt8wXMSUKXfhK5ASoaG7c77OLnmE3Svl3svt/DJLg0lpi5eRxH64qWkNE8jN0V8G2YFwrSgXwSJnMSo8gZ5/eKPcORJJIQANMyBQ62AsGD5qQru225F/34jKTf97Re9ns58cAvjHribN55/HjcCs8VAWyQF1AHxnJz1wWovbPPJ9VFkBqsJYhUYb4EVulSnFKhiUOtyEoZshO4rQS8R2PKb4GwD8EVI2TAKRDaAkRMgpkqbkYKCAjZs2MC2bds4fvw4Pp+P6OhoOnXqRLdu3ejatesv2snUkKHfioyOpIZ+Me0HZiEVKrFV7juMNHe6B5mFAZISuBzpCNk2FcJ+kgmzI07D6FiYqkDcfdD5JISdkq6RChIlOQxM+Q5K4qC4KJO1D6QRFwSOpnGSDVxwnY3Rczx8cfUopi76hucznGz803jGtD4fOKJtCqunhHM4X2PE507qRausmRJOSpRKSpTKisnhDJnuRNPhiT5W/vpjOr4bx2KfNZ+6f/wnR27rx4BpDpbdFM7z6zzndRq1mRXmXRfGuDkurp7tZMGEcObs9dUIHACxdoXHepm4aZ6Tq1qYmXudnQKXTtqMIo4PTCNuVToFLVqiqZIW0YNrXLv9CnuSY9C9Xv4+zMbve9q4vr2fYbPOEtO2L8Wdu5IdJt/kdQUaaNC5FAoj4ZAukKEhfTnyVJnyWqzLQL6ccNAjoHc4RGvyhuQrUGKzsS1wfsXPvyrF76OdzcZ2ZCaLCxlnPxzpjLrGC3/1QIEOLgV+skK0AneosN4q3oz3vZCtQMAEHRzQcxN0PA6N14LbI2kpTxjk1wV/RPA6M0FkMxh2zfnAARAfH8+oUaMYNWrUL/6aDRkyZEQ6DF1Em4DvgYeRXggVtS54+z1QdW5nGdIhMhrofhrmlApY3FYHXk6Am4EbzoDjUchwQgvkfh9ScaH6MknbmYbNcZr0CsARksevM3qOhxXHNXRNY0xrc7XAMfdaO1aTQssEEyeLNKKsEGVTsJjKv60eyA0w8WsXRws1nh9g4+GVPqyXpeHav42W4W7CVI0dZzXCzNTYadTj17l6tpPvjgVQFWoEDoDFh3yMneNiZHMzX14bhjV4LGfLNNJmeDmpxsLKdCJbtaRECbYK9+o0uekJjsx/5RxwnHsfTvoZNsuDr08agUWL6RQezj4VGgcgyQf7LLKPRFXA8CePtIcPFEGsF6JKwKmCkgDJcbBHk1LdUgW0f7yJ/tjjNPz9HBT10qp4alLAUcjptydxxdy5mMaPpxHwvA7HfAIbu31yrZ0xSSVKuALDTdDIAm9ooHgkfecyQ/MA9DgAzY9D48OQsEkiQ4oJSsLEn+KNEBBWbRDRAoYOh/i4X+SlGDJkKCijT4ehf1nLkJLKx6kMHDowAzFxPsH5wKEDi5HS2CnAiBTo4oS90TD9NLyrSavqQCpEPCgTT88GH2sBzK5MRq5LI7yseuAAiTDc2klFC/gZ3Uo9BxwA9y1xk+vQef9KO1fOcnHZ+w5WHfcTY1cYOctFx/cdZJWWdwxtXcfEd5Mi0HX4cLuPRdfacG1di1ZWRpk7wJKbwrjlMguLb6y5tbnTB9luOc56MWZGNK9+u3PA0aIycADUjVRJn2SlkVaEPjgN/eAhNABdR3myeuAA6NfIzPIbbVg2pKNefSWnXE7MyEyVIot4GhTEk+PTwK1IyWyMVwykuRFg8sCRWFivBctQFXnPE7pcDj4P3uxj1b6enyPPmYNynrp2pUUAurvg2RL40AEZfjhjhm02aGWBCVZIC4OjJnjODfluOGGCeBNMKIaxq6HLUWi5BWJ+BI8GJjsURYIrUVIqKqBGQkRruGKYARyGDP03ZUCHofOkI4OzzgIPUjkH50LKVOsi4fCq332LgReR2RqvI1ESpwly6kGjInCEQWku9FdkfLnSA+qMKx9drus61268ApeewZqbqwcOkMjCpK/dNI5RuK+b5RxwADyZZkNVpPW5zQSd6qqMmOmk+0dl7MoOUOrRGTDNeQ48/JrO/UtclHnhz/2srDgeQNPhts4Wskp1enzs5N0rbQxscuFZKvuc4UTNmkWhKZr+M73kOyu3Qq8EHOMrA0dIIfBorBXhHJSG/vnn8Pjj+P9ePXCEFAIP64Z0ykZdieJw4kHMoxD0Muiw2y9D5VSP9OywlgQbZyVASx2a6tBal4ZijXWI794dNSkZx54V1T7vz5Fjzwpi2nUgM6ExuSWwzytpn30W2GmD0Rb4zg6+MFinwnYXLHXJxGKrGSYBY/ZBp3SoVwL1VoN1v0Q3lCgosIM/HtwRAlvmeIhsAUMGQ0L8L/YyDBky9DNkQIehSgoAbyNpkZup3IPjLAIUVyMD3qpqDwIkE4P3N0LaWL8E3JkEQzxwJgK+y4LBAdimQ44O6k3QsJOYVTVFoXtcb3KdATZn1jxgzWZWGNPazPEinStmuFh4oLzHQsMYlTi7QlKEwpvD7BzI0/BqcKRAx+WHtEYmnD4Bj4xijclfu5iz18/scXY2ZWq8/qOXf46w8/HVYcy7LozMEp1Wbztw+8+fp3IOOBxhXLNqDd0n3EDyqnUc8kZWAo8Vx/wXBY6QQuDR0J+PMnkivPbqBYEjpBB4mDako426kkAgwDEAHQp1KZX16FLVEuGGOD9YLJDgAuKlWqYAiYyUKTIvp4PVStTtd1K2dzWBssILPn9t5MvLwHl4Mwm33UcPFMwmWGWBLCs8bYVldqhrh0kKbHPDUYek+MwWuNEMY0uh1RpodhyiyqDuMlAzITIMfFFQaAIlDhzhyLTeVAhrBIMGQGLCv3z4hgwZ+hdleDoMnVOoiiANabxVUbuBOcDv4FwXyZB0YC7S0vpupKOkFzGYHge6ANcCe3PhrSzIDoPHwiExFeYBjyugF0PeQ1CSDw30ALfvuIXPT8/k87F2JrSX1tvFbp0omzTeWnDAx7VzXYxuZUYHFhzwM++6MDokmxg4zYHNpPDmMBs3fe2idaKJPg1MvP6jF1Ow+daE9mbWnwqQ79TxBjgPOO7vXu7HWHjAx7g5LupFKxy8PwK7WVhdWpu72e0IJ2zlGnp1vozjOuTpkHpgLwf796OVtYy1E628tcnL8+u8zBobxg0datdK/GyZxoSvXIxva6l0PBdSmVen33Q3PxVb0U5kQFwcZh1Mqvg73EhFTHwOxBZBfjA3VpoqHWUjFWinSq8Qux++Borz8/G3a4M1oRl1xjz1s6s3dC1A9qw/YrLrpG3YydmoMHqq8IAZGpvhG2C7Dps8kOGTCJnTBANUuEyBmP0Qc0TmrdhyIGE1mHwQFy3zV8oUMMdCXrhEPSwNwBYPg/pB0i83xd6QIUPVyKheMXRJKka6jI5HyhUragmwDxkfXrUzQTHStKkHAhYK0nfjI2AskoL5EzAOaJsIl2fCTDssz4bHkiHGBFt06BYDiY9B6ZNQ4jfxcedPAbhp/kwAmsapDJ3hoGd9E7d2tnDTfDdjWpuZNU7cJhO+cjFujou4MIizq5WAY9lN4UTbwGaCl9Z7UYAv9vi5ob2Z0yU6v+tuYWMNwAFwTWsL866DcXNctHrbwcH7I3D5FAZPd7ArR8M87R18nS9jH1CiQWsTnGnXjrhV6zg0sB8DZpax4kYLhws0Jn3twmqCcW0vDh51I1XW3Bxx0e1CKvPqDJ/tYXexhdRvvycvLo4AMtgtSpPuo0VWiHOB7gPVDVosRMTC5SY4rkEPFXJ1OO6CQyq0yINdsQkk/OV9ztw3jpKNc4npdV2tjykkXdcpXPkhnqzDtFm5nv4xYdxtBYsqabaZOhR74TufRFoCVundcacZKIFmWyFQIvuyH4X4HyHCBmFxkBOQdufWOOlIqljB2hgsETAwzQAOQ4b+l2REOgyRjfTZuANpbR6SH4GHOOB6KqdaQNIpsys8TkfGju8D7kWafQEsQDwgPYEjhfDmSTgWDY+aoHtDeFaHFxSwKeBbDofeg6aAWw9w845b+CZjBuEWaJGgsi9Hw6/DuDYCHCEvh1/TmfCVi68P+Pljbyvvb/OeA46YYDcoXdd5epWHl9aXRzzu6mImwqrwxkZftcBRUaGIR2qUQlIE7MuVdI268BvMo66iSQD6qrBGlUiP2wWRm/dyelw/WlrL6Jio8cUeP4qiMGecvVbgUVuFgGNjnpk2y1ZQ0LUXZ6xAcKS9WZPffWYYWgyefCjzQl48hKXA7bpAR9syeNoOsR5oeBZO2qAgBhpkQtGrz3Jy+nNE9xhHbNrEWg9/07wuClZ8gGP3Cm595wPev+dO3IpcF4d06eGyzgcZGpRaoZkCdc3iKel9GPwHgrNodLDvkPk9ydHgMEFxADwqmGIlgqZGgLURmG0CHHWTf7FTbMiQoQvIaA5mqFY6hgxu+z2V0yYlwJtIq/PeVR5TXTqlAJm50gUZylYRUByIqfTPSGfIGXthWhRcng9Pt4etFhncNkkJdo78J5xaJb6SHQU/cvsP/bisrsZ7V4XR/SMHg5tId9GK5lEQ8Lhxnou5+/z0rF8ZOM4du67zu6Vu3t7iw6xKm3LgosARUgg8FEUeG/7u+7jvvotYXTwQbVX4CRmiVuySBb/s1F5cA/oRKCzE8sk0fMuWosz54hcDj4rAEVi2AluPXuiAVwW7BqoKVj8kmCBbkyZgdfKhJBqaxEHbaHC5Id0rw9OKzNA4T0ymW5pBdDHYXdI7pezd13E/+ziWxIbEDbgVe6NONaZbdC2A68gmCtd8iu4u5qP332Pc5Ml8jVx3Fh9874V8P5y2QbQK95pgpQp9y6D9VigO2kg0H0SmQ51cqB8NWQExwgbMQDScDQNTHFjqgckswJFS918+tYYMGaqlDOgwdFHtRL5tPozk80M6CXyIpEaaVHlMxXTKEAQuNgHfAncibc2r03sIjDQGMkrg70dgdzw8rMGwJvCcDjcr8JMCm7zQ+zEo3L6ZB34cTLtkL0sn2jCrMGiakwN5AVZOjqBr6vl9I/yazhd7fFzdykK07fzF8FSxxsBpDgIaNIxRSD+l1Ro4QnrjRw8Pf+dBnTQJy2fTiUL6ktQBchSJciQUS+SmSSScUuHssWPcfOYMP/Xqy5aAH+vkyXi/+tfBoypwKL16gQ5RiqQpmiIG3TANXCr0d4I9B5oVw5JUuDsOZjhlIF99FxTawOoBxQmnY8Bpg0674HRjiHJBmQXMW3dS+OitFB/egSWxAWFNu2FNboYpOhF0HX9xDt6zh3Ed3YS/KIem/QcwZ+rH7GrWjFNArB+WeCHHJwZSkwm6mGCMCtOAO45CYC8Egj5irwMS10ADP8TZ4aRXBtapFvBGwdlwsNQFU6Lsq39fqJfys0+pIUOGfoYM6DB0Qa1BYOEPlJdVEvzbMiTyUbVhY9V0ihsZN29HyhgvtHRmIKmX+4L/nrMfPrFD62J4phVsssOLOgxQpH13x6WbmTJ6MO2TvCybaDsHEMVunWEzLwweNSkEHJoOa6ZEUDdS4WC+Rsfk2u9jT06Afp86KE1IRduxC0tCAjqyuHsUMTQGPNDEA3XCwWoRM22hLuB1W9B/EJvnp/DByTD354NHReCwLVtBap9eHAlGbtrpsFcR+LD64CGztJ9/sEAMvQ398G0qpCjg90BpFPgsgB8sZeAPQGwhZCcCVqhfBLZcONIIup2AI4k6/t3phM/+kKwf0sk7farSsSU2bU69gf3pd889dOjalSwEGhb4IMsDWRYwWaCBCj1MEhHKdMAV28GXW+E15kCTjdDCKum+016pvrFaoDQSciLFMGqKlohOv95Qv94ln0pDhgz9izKMpIaqlY5EN84ibc3VCn+fiyyaT1H5wgjdlwk8jTSNOooAx7XIePqLqQEyyr4UiaoMaAjb98OGJHg2AyJbSjqlLpCweTM3TRjMZXW9LLvRViliEWNXWD4xnGEznQye7qg1eFQFjkax8sp/DnCUJaRi27kLb0ICAzXYo8hk1B+AKB1MAdADgAnSNZltYtfhOQ28QcRP9JhxvfcJPhWu+/ILltqUSgPjLiZd17nySw+b8sxoy1bg69OLQiTypCsy1C1Jl/cuBjgIhAVglQJJHkiPAt0EMW74MVGiNCOPwZJ4MZi2PQnbW4MzEjpngloAe5pBchEUREILRSHQpx+xg/phNkPvwnzG5efQzaqyIqUux2NiyEH2G6vJpOFlLsgxgzsMOpqgoyoNzFKBnJPQ/SfwBQf2+gDnSbhsFzS1Q54fcvxgVcBuhdxwyIsR/4YaJiCS1ssADkOG/tdl9On4fyQd8W/4EC9G6M33IKWy4YgBtOLSV4z02YhDoh92pMz1K+AxagccIQ0Dvgv+Hh0B9hjweOGgC5o55BimbtrMbVcMpnPs+cARUgg8WieaGDzdwfas6vt5uHw63x7ycao4UC1whFTk1ll+xH/eOPOK2pMToP9nTkp9ClHTZ+BOSMCswy5Fzlu6Lu23/T5Qy+BslHRz9SvSsVVVJMJhD55L99rluOsnEHb6NJqmk++8tICjpkOOE0wRkZCQQADoT9BLo8v9tyDenESTTGyt44AcD2TZID9WxrvvSISGAbhtJ2w2S7fS1scgMwUK46BpjsDiqSSI8MLww4BZupceCIe6VvjaBgvrJTCpYxu+bd2KpTExnABu0OCoB/5ZAns8cCIc6tvhJTN0MsF+FR7wQMFGqLsNwoPAUaKD/xAM2AvNbDLKPscPESpE2yA7HPITwdasHDj69oIG9aueJUOGDP2vyYCO/yfyISWxjYDrKDd65gEvAIOBq6hsAA01+7oJafaVj0yOjUTGj19qAuxyYBuS2nkaKG0ArQqhOAEOnIK5Xg9HRgyloc3N0hqAI6QYu8JLg20Ue+DTHecPJHP6dEbNdjJqtosJX7k5Vqjzx96284Aj3ykRkOGfO3l2jada8NiTE2DQNCepUQrtk8yUjR1DYNt2XEizrSNBH4cKhDnAEQktzWBVZUhZJ0VmnjgAXQXzsuVk3HYVMbqLkvR0/j7MVuveHSGZVIXVN1lpqBehDkwj+tAhrtZBe+891E7tsR06dG6QXh5ivPSXgunzLzh4TRvcB7bhjIBuTuhwGBbGQHYktD4FB1rAyWQI90CYB+LzIKEEEtwwpzOcSYE4FeZa4Q07NDJBDlLhtAS4TIdkD7xWBrtdcCQMosKlH8drZlhskiFur2TC1ysgMUuuJT14nAkHYNgJiDXDMa9UqMSZBThOhUFRKlibghI8ZX16QqOazESGDBn6n5IBHf8P5ABeBvpRuZPoQSS6cC/QqcLfdaQR2PcIHDRGUgf/AG5DIhY/pz3UCaRq4WPkG/+tduiYACYnZARgktvGoGtGcyg/wJLD/gvua+uZAOPmOOlZ38QLgyp3D3H6ZPjaxtMBHutjZePpAE1iFR5Y6q7UuTTfqTFkhpPMEp3f97Dy/DrveeBRETjWTAln9WQ7HaLcmIYMhB3bKVBBC5b7RgE2HwxX4WQwwmEBtmhg1SFZhzgNcv/4EKaAn0KXXqtOozWpbqTKuklWmmpFOPun8Ydn/gz330v00f2UDurL7AOH8CKekugS0OfMJuPxGwk7fQjHlQOZsngbx0thf5gYRFXgaD0ojBDASCmFOpmQmQzHG0GZDVq64GEdeqvQxSyw8TYSVQnXIdoLW8pglwMOW0AJh3FmeN8s1TPvKdDXC1dvgemboI5XgMMLnFChx17of1oiNUc84NIgxQIRFjhig5Im4uFQgp9cfXpA44Y/6/QZMmTovyDDSPobVz4CFpOBlhX+vhLYjHQYrdh+qhhZRLohgOJBenVEIxGPn2MCKgVmImPrvUilx11IxUtfL2zdKabGywqhW5sAc8aPY8WihcwaG3auG2lFbT0T4IoZjvP6cEBl4FhyUzj9GpmZ8ZOXKQvcNI5VOFWsM++6MPo2NJ0DjlVTwmmfZOKV9R4eX+nhz/2sPDvAxt5c7RxwrJwcTkK4rHRFbp3Bn3vYVWqnzqrVZHXpQpwuTbfyzeANHnJH4KQu74FZAdUPTbPOsq9DKwIlJf8ScFTU2TKNftPdHMvzM7mThZcH2+g308sxYmDVetSWLYl/fzbZD97EpA5m3hxmZ+hsDz8V2Yj8ajUxrbsSFoDMJOlcOmwvrG4Kdjc4rJBUCqluaFAK9RqCPxxaW6HMLumbLGCXD8o8EOWBE2EQaRLfxp0maa2/UoEkwJINcdthjwsSgFhkwFxhBFy3EmJLJbIRMow2tIj3ZE8EOFtIx9GQenWHZlXLqwwZMvRfkVG9YogM4H2kYiQ1+DcNaU+uIiBSMdRVtTrlEFLCOIHzu5TWRhpSCbMciYx0BTojk2mHIiW2RcCbp2CrExI1eCZZZ1DXxhSdycDj1/m8CnhcKnCEVBU8GsUqlHo4BxwhhcDjib5WPt7uJSFcZf0t5cARUpFbZ8jnHnaW2knY8AMFLdthCsjCetomg/CSkfSLR4HhOszLOoupY1t8BYW/GHCEdLZMY8BnTkq9OqunhBNjU+g/08tRYlD/8Ai+Jx9nUgczn1xtx6QqFLt1Bs8S8Lhs+moyBnfFYYXuh2FnfdDM0PAEpDhgWCakN4WYWOhXB95V4Co7jFLhz3444oVYpwxaUyzQzgTXq9BFgdkKtAd0PxzfDXHHJdoVjxhcs8IhygrXLACzT8poQ4bRRlZp2b49HjzNwRxe/np7doPmTX+x02fIkKF/UQZ0/D/XfmAW0oMjNvg3B5Ii6QUMrLCtjhhDM4B7kLkbXwGnEMNp1dH1tdEeYAYSKUkBRiEmUgtieFwSPK5CYJwf5u+AL1OhXR44Zz/BvFf+SvdUla1Z2jnwyCzRaP9eGS0TVL6bGFEJOHwBnRGfVw8cIYXAw2KScP26WyIqAUdIIfBoFqeQWaqz+MZwBlUzYfbJlW5eXu+lzuwvcV51HX6LRDkiFOiuwHpdoK6jAqbMs2zq2Bbt3wAcIZ0tk8hMsaccPPrN9HIo28vkTpZzwBFSsVun92duDrnDsR3OJEaRsax1nBBeAJFuGJQF2bFwtBF4EyHGCo390MQOb3tBLQWXHfw26GCCwQqMV2G5IhGeW4CV+XB4G6SUSaonFjHfHmsIV5yBtkvlGjwd9G9EqNDQCj4FfqwPgUYymC6kHpdDi2a/+OkzZMjQv6Dart+Gp+M3qE1IhcmTlANHJlKFMp7KwBGqTolGenaUImbRBARYLhU48oDXkOZiIBNnU5HBYaMRT8geJPrRGTGk5prhdKr0gnDaoedDL9H7kUfYfEbj8hSVm+a7+GKPjzCLQqxdIc+pU+KpzMq5Tp31pwJ0TjHRo171ZbCTOlmZPsZOqwS1RuAAmNDeQnyYQrRNoW9DE1fOcrLqeGWPyXNrPLy83ov5hRe5bMx11C+FFibxGrgVGVxmQiI8+dnZbO3cEa2gkDeG1h44yrw6tyx0sqCCD+VCqhupsmpKOFFWhYHTnIRZFNInWfnsGvt5wAHw4+kAh/MD6H36EmENp2U+/O5HaHQGCqOghQNifbCsHWTHwW1mKPDCRgXeLwCHB3JjoEUYXGeG10wyw+VdRaqaHgrAwj2wf50AxwkkumG2w6EucNsuAQ6/Dsc8QcOoCRrbZF7M2nYQaFoZOLp1MYDDkKFfs4xIx29My4ADwP2U+y92IIv+gwhMhLQXiYaEOo+uQ7wedyPRiUuRN/gc6xCSHY4suD8AYxCj6nKkjPTq4GNWI5DTAeijwavbYVVduCYXnumoc/Pjf2T2a69Vinj0rC9TZFVFyl8bxAg3nyrW6PFxGTkOGNHcxLzrwrGZL93uerJIY0Bw/6unRJAUoTBqtpP1pwLnIh7PrfHw7FoPzV58kZLHn8TvBQLgiJBUQh6QqUt6qWlONmcG9MN88iilrgD3d7Pw1gj7RSe1lnl1Rn7uJP1UAJMCX4wPY3wtGojtyxXja1KEwpY7Imo8B8uO+Ll6jhuGjGDAP+ZxNM7G0ythUyrsjAEtDJqXyOKf0UTSbTl2yCyR1EteBDQ1QRcVJqly/2cKtEAG/ZUUwYxtUFQMDZEIRyTgqwe2xnDDNNCPi1E01GE0xQIJZmkCtrgfhAWn3YbU9TJo0+qip8CQIUP/BRmRjv9n0oHPkaZfDyLAERrAtgapQkmosO1cBAKeRnwIbyLplT9xacChAxuRRmNrkejFBAQuVOBZJK3zDFKx8nzw5zcIkNwJuIBPVBjYAHoVwpZIeDRHYfyrr3L3wxLx6BqMeBwt0Fg9JQJNhwHTHGQUa+caf9nNCkObmlh6JMD4OU48/kvj6arA0TBGxW5W+OaG8HMRjylfu3h2rYeUF1+k95NP4vNDqgPKwuT1+oG6ehD4crI5PaAfUWeOs/U2Ox9cZeftLT5+t9R9wZ4gIeDYeTZA+i3hXNfOzISvXHy178IRj3250kskPkxh1ZSaoSsEHKahI7jj1Xl4VBv1i6HUAptS4HgDqOOBfkdBrwOZFpnkml0MWdHgioKeFnjcBK+pcEKBGYp4ga7VYN8B+HhNOXCcQBp6ne0K3VPgpn8IcJQEJMKhIf6NBLMAzddjZCJwReDo0skADkOGfgsyoOM3oADwDpIiuRmJMPiQKhQv0tQr1Oq8GCmfDaVTTiDplaFcenVKBgIV04BEYAoysfYYktqpCzyHDIN7BoGZvyC9Ou5EeoY8j/TveBwwJ0HAD4V2iD8LV2kK7/7tVa558PdsPaNhViHCCo1j1UrgEWr8dUVTM8uOBrirq4XvjwW4dm7twaM64AipInjM3O3DcuuttH3ySTppMvTNapIW3NFAqQ6ndenAGvXCC3gOHOKL0RZaJpi4s6v1ouBRETiWTwynb0Mz08eEXRQ89uUG6DfDQ5FPxaeaSAirHjgO5we4+ks3lv6DGP/PeeS7baSUwsCj8Pc08W/UL4Y+J2B+Z0iPg7giaex1Ig7qWmCCGWaq0sL8r8HoxhOAvRQWr4Nl+2QYW0Nkjk8gGfL6w31F0O4NCBSKYfSUV9qfN7NClAmUKJgzCSKKIKzCqbmsA7RtXau30ZAhQ//jMqDjVy430sDrMsSsCVIR8iIySn485T019gW3vQEZ1jYLScc8CbS9hOd0IGW0LyAQcx1SjfAjAhM9kUZkuxGYaBd83vVIxYyOgMpkBEwOIHASqcDDDaB7AWyJhi1Z4PV68R8+hMWksGhCOD3rCxZVBI8QcHy0XcbTv3tlGAsmhPPd0QDXzXVdMKoAwcqPGoAjpBB4DGpqQZ/1OSmrVjHTCz4VzkQJsF2NRHGKFShUoPlDDxFZP5Vbl/jJLJGhKBcCj6rA0auBvFazqlwQPAQ4vJTEJOP3BxhQX6kxfZMUodI43oxl109kbTlBl0zYkwzHYsDigTA/OOywvCl4rOA0QWEi+MwQq4hv42EVPlCkz8ufge467DsKC1bBvgJwIkB50gxnOkFSN3h6I9g/kLk0GV6pUAlXpeOoTQVzPZh1C0SehIgKb1fH9tD+Ui5OQ4YM/U/LgI5fsUqQqMVVQN/g344BfwNuRXptQHl1yjLKZ6f8BaiHRDsq9um4kHSkYdjDwFYkOtID8XEMReBjWnCbe4PH9BZStTIBudg+R6pnfocsWjsRKHoBMbi2TYCuCjgtsCjTw5gxY1nx/TIWTQhjWPPKcZjGsSq77o7khnaWc8ARmhbbLVWlcazKxswAnuq7pJ/TiSKNk0U6veqbqR9ds9fCblb4ZoKdAfV1vrpyJM3WbEVTpY9ET8Cly7nsqwiMZTZpQmBNOtn2RPrO8F4QPGoCjpBC4HFlCzPXVwCPEHAUxSThy8xkckcL719Zs1E1xi7m0rp6MT/cm8b3ykG6Z0B4QGaZOK2AW1qg76wL8TokmmVK7VOAQxHgnBS8+ZywbD1s/AnOBgRIGwHHEuGn/nBjA5j0BTjng0+rbBhtYgOTAvYOMGsihO+BKK38WDu0hY7tLvzeGTJk6NclY+Dbr1TZyIIe6qkBsAFYhUQXQqPqi5HUSxckzbIGgYR7ES9HbXUQWWyKkKhKYySycSUCHHOQBecGJEc/FSmPHR08ri8Rg2ESYjg9g0QG3gfmUx6NURQZBrfjgIdPnxxLcfoyFl1vPw84QoqyKRS6dUwKNAgCQ6jTaIFLGn/ZL2Io7VnfzLTRdqYscBNmhg9G2VFriBRYTVA/EtweL1tLClGCJsqFOuzUobEiRtIkxN9hbtyU5FXpnByYRt/peayfbKVetMqdXQWO7vrWTUCHPTlajcAR0ukSna05oIeHc/08F68Uafx1o0ZpTBKBIHBUV6VSVcmRKmsnW+k/vZgfb02jdE46p9q1IqFAzLCn60IXTd6jBhZ5fWf8cDQCrkHgEh0On4JNu8Drk0GBDsRcur0NnG4Cb7vB+jY4DstcmpNe8OmSokkMvsSoK2BmLzCtl8FwIbVrI1EOQ4YM/bZkVK/8CnUMWdR/j0zx1JFUiQOJcISWrH1IZOH24HYfINGNa5FyztqoAPgUqXRJQVqpb0AafQ1EjKrHkUhGJAIXfqRV+o/IAjwWKZWcj0RnxgAVU/QvIQ3MYoL/3u7ycNXwseT9sIxvLgAcIfkCOjfNd/H1AT+fXG3njY3eSp1Ga6tQH4/bOluqBQ9N17n9Gw+f7fQR+dF0XFMmopnEy5GILLwzFYkmHQEa6ZAXgFIFovYco3hkGvW8eaybJOAB8OE2L3d96ybKygWB40SRRt/pHs7GphJYsQbzU0/g/+ILzE0a4z9xghvbW5g++nzg+O6on7c2+3j/Shv1oysHNrPLNPpM83BaiSdq2zHMEeGgQi8dtodJn4xrrHDYByk6fGCTKJnLAxu2w6ks2U8e0mW2XRx8dRnUiYbXsqH4PfDniWE0wytg2SDk3zBB3I3wRTMoSofkCtGoNq3EOHqRAh9Dhgz9D8loDvYb1U5kNP3DSDTDDfwTKTsdHtxGR/p0nEKafR1FOo1OBmpbAOAHFgKLEZ/CCAQ8EhGISEdSLGOQYWZzkIVnGLAFaQw1FqlAmB88prFIaW5VbUNapKs6vO7ysG/8WLzfXzjCUVW+gM74OU6WHAkQa1dYfYnAEVJN4FEROC77eDoZ4ybiipKeHDoCeu0Rw+5WRZpf3azBZk3GzPvdkHjgGFlj04h257G+AngsPeyjXrRKx+TqjzcEHLmxqfjWrkdpUB/F7ycwfQa07YD5yqG0sTtYM9FGfAUDaahKxadLqil9krUSePyY4WfILA+By3oSsWQtXbNMlNSBKZHwrAJ2C1ylQE4pPB8BbcxwIhN+2Aluj+wjDyhToVNL+KgZpFng4f2Q/zFobsj1QbZfDKONreLfMEVAwp3wdSycXAf1KgBH65ZSGmsAhyFDvy4Z0PEb1Bqk8dcfkMUtB0mxTEAWPJBIwttIOmUw8AXS9fMO5FtqbbQNiaS4kGiGAwGKG5E0y0oELtohXpE8xJi6E4mMjEMW4q+R6MdYytuwV1UJ8LIOH+mQ7NXxjb+ak98tYdF1tQcOkIX7gaVuityw5uaageNUscacvT4e6G6tsaQ0BB73dbPwz5FhlYCj42fTKbpyIlFhkBIu/Tj2IdChItGgM0h6JSEAdRU46ocWZyGuDpzcls4P/fpxd1cL711Vu3dk8tcuZuzyYdu0FUu3rjgUiRpoGkSWgWXvHkqu6kfbCuARAg5t6AgSXnsd55BB1HHnsW6ygEcIOHyde9By6nLCwiLpnAOXd4B3dThmhWEmGOGFfR540Q4bd8HRU+XHlQ+URUFqF/g+BoaocNNaKJ4r5t5Qh9FwVUpiTQpY6kLivbBEgd1roaG/PLXWqjlc3sUADkOGfo0y+nT8hhRawPcj/TCsSNThLcSQGQKOfcArCIR0RMyiTYLb1GZ5y0KqEd5E8viDkTRBGjAA8V84gYeCf38HibAkII2+BgIjEdBJR9I691M9cOzRYZwGXX2wzgtX69ALLzlbN9MgWqVzSu0vzXn7fFw128WJIp3GsUq11ScgZbF9P3Hw6Pcerp3rqrGctlNdE9E22J2jnQOOT3f6SP5sOo2umojXDHqYpKp8yH+ieAXsipyfAJCrw2FFjJh6AFpocN+Z0xy7eTKpcRb+2Kf2bdCfSrNSJ9pCYMqNWHKyUYL7VDXw2UBp157AinXsc0cwYIaH2bt95cDx1Xz8LVphXrKes5ZE+k33Mm+fjyGzPHi79CBmznLizZHc/SO4m8N7VshV4UpdwHMq0K4YFqysAhwKlDYHZz84GAtX6XDdl1A0p7JhNLaiYbQdJD0Ka1TYWQU4WjQzgMOQof8PMqDjf1w68sHvQzqFqkg1yGLKG3tVrE55EkmnfIzARt/zd3me3MAnSEvyMqQaphhJm9yIpEcOB/dXiMxvaYr4RNIROBmITJLdhTQnux1JxVSUBszWoUcAxvrA6of7VBhohd4miLPbePOvr3Cy0M+Az5zkODQupnn7fFw/z42uQ+NYhZ1nNYbOcJzXJv1kkUbapw5MKnxytZ3vjvoZV00DsV3ZAfp/5kBRFGaODTsHHPbp04meMJFtboiySKlvjiadNO2KgFl9ZNE0KZKe0oBFKiga7Dt7mpHD0lALM1k/yUaTuNr/12uVaCJ9kpW4M8cpHdAP7Ww2dh/o6Chz5lGSewq1Y3u01evY54ngxvku/ENHoHw1H7fFiqkYvM2bkPLVejJtiYyf68LXpQfhc5fTUIvEbYGtnWB1HKgeSHPBJDOc9UDqflhWAA5X+fGURcDpPpDfRqbAdnLDyHfAsU4Mo0eDI+nrWqC+VcAiaiAk3gM/umH9WmhSATiaNYHuXQ3gMGTo/4OM9Mr/sHxINKMjMmY+gABIBAIDCpXTKb0Rs2gTJKVxsWVNR1I204P7GoREMNoF9/cVEIf0/1iLzEzpj0yfLUP8HFkIBHVGfB/Vld8WAy9rsCAAFg2GqZBgAq8qEZTeSKXLw6tW8e6VI+mapHEk30+dCBkpnxRR/SsJAYem6TzWx8JfBtq45ksXy48EuDxV5btJEUTblErAsfbmCCKtCpd/7OJkoZ8RLa3MG2/DZlYEOGZ4cKc0xJtxmjoWP9kOnY7TpmOfOJEzZeBSoX44XKMLfPkBmyKDzY4qEo06iqQ+TEiawXrkNNrwNBKLM0m/ROCoqIN5AdJmeClIbULEt2spmfYe/OV5zPVSYHU6/qbNsO7fh3n+fJx//COxASvxJZAZL292HSfEHTgBCz/Fc8Oj5DePpOsp2NwYmlnArEKrXIiNh/vKYO5GcDoguzEkOKFVPtAYlrWF9mZoYwJXLlzxLvizazCMqhA7ASLTYHsBLFgDzX3l12bTxjKi3gAOQ4Z+3TI8Hb9yOYDXkYW8GzKj5B/IIh2KXuxHogu3Be+fgyx+zWux/6MIrOQQ7DGB+C+GAUsR4BmD+Du2A32QqhknUjZ5HIly9EKAqLpkwW4d/qTB7oCkIq5QwWOCeoq8rsYVtl21ahVXXTmStPo6C6+3caJIRrXXBB5VgeOlwTLPJKDpXP2F8xx4fDQqjCtnOSsBx+CZHva6I1BeeJHA7x9keBOFZ/tZuGKWB3eLdsSvXEX0zp0cmTwJ9aVXaDl5IkoA3NkQkwATbPCeLv6VRsH3IQwx9mYBZl2m67YBsk+dpmxgGgnFlx7hqE4h8CiyROPLz+fJvlZmH4AMWwL+1emEN2tGHTectENMCWgWgUvdBM0LISIBwo6Bwwd2G2xsDF1ccDIa2pVCVwfkqdBjA+z2QUOTwMOy9pAWDQsT4HcK2FXIPwQDPwTNWdkw2sgq96thYhi1t4a9hfDFGmjmLa+uatIIevWAi1T4GjJk6FcgAzp+xSpAOnpOBloi7cbfR8phmyGLyHykxfRtwd9dwfvtF9l3MZJ62RLcdx0kajEaMalmIkbQQ0jJay+kCsaFRDwOBB87CIl6VLV6asBMHd4OQGFABoJ1MEmVR29FoCm8ymN27txJ7149zwFHqK/GgbxAteAxb5+P679yoelUAo6QKoKHpkOTOIXVUwQ4Bs1wk+GJxL1qLdaOHfAvW4Zz9NXoPh+mTp0IW7mKPvHxhOmwUhePynEFDrohUoeUMGnvvTP4XHmKnD8zcr8LaeHt1KFl5mn2DUgjqah2EY5Sj46qQIT1wqvwgVw/fT91YjEp7LgznCIPjPzSR4YtgcRvfyCyRWOOhEFEmVSLWP3QsBT8dcGaB1nBKNMVR+DHFuCKh2YBOOGCK46C3QWNMuGMBu3M0LQ+rOkAn1lhsQJZKuSnQ98vQQtApheKqhpGkyDhXrAkw5Ei+Gy1AEdoZF3jhtC7h7SPN2TI0K9fhpH0V6oMJMJxDwIFW5CUyqMIcJQAf0XSGOORMfKtkWZfFwKOAFJqezfizxiAAEKP4PPMRtI4XZGuol6kEuMg4tdogPT8SEQMqoOpDBzFwKMatPHB37wSPbnOCt0tMEaFvyiygFcEDgfiQ3k/eHxV1TrRxJqbw8l16AyeLh6Peft8TJjnomuKyt+usJ4HHAAmVVqmD2tuQgf+0NNKpFVhwGcO9jtsDF29Fl/HDtgAy/DhKN8sxnTDTQRWrmJgfDwxwOYgit+lwEk32HygWATGFuli5u0QhKiGBBfToJk0XAe/AidefQXf8RPMGGW5KHCcKNJo+4GbDh+5z3UurU4BTeeFdB9lXjhbpjN4uosuHzgY2UTHn5lF3tghnBzWH6ZMQnvnH0Rt30+cB7rEQ7NS2BULiWVyrDsaQLdweEeDHQFoehC+awTxTijQIdEGvbrBvC6w0wLvq3INlM6BPrPA5xfDaFFVw2grSPqjAMepYvh0DTSpABwN6xvAYcjQ/1cZkY7/Ie1Hmnw9BMQiEYwzCChYKE+n3IosftsQ2Ii/yH5/Qnp5OBH/RBaSolGR7qTDEMhYiVSj5CBehSuAHchQuFGIz6Pqd/CdOvxZg73BFEpfEwRUaWU+NPg6KkoL7nM1AlDhCHwUr1zJR1ddSf/6OgsqRDugPOIRYYVTxTrj25qZMSYM80Xi8gFN5+aFLmbt9tMwRuFMIJzmG36kqH0HnDo0V6Tix65BqSrA8EekjXxXTc4bCthyoTgSWoRJZCgfmKPARMRYqwTPmR2BL0tA0gvhxzM4MzCNOqVnSJ9so3Fs9atsqA9HQWwqUT4f4e5c1k+ynevjUVF3f+vi4+0+OtVV2Z6lYVLgsroqO85q6PYowpp0BXR8xWfxZR8Hv5cmvfrj+sszmDoMZMQ+WNZRDLBKBHygwf5tMKsOlMWJDyXWDW1OwMSu8KFNWpa/o8AGF3y+Fa5dC41PV99hNLIvxF4PihnOlsDbq6CRp7x6qn49SOsNJgM4DBn6TclIr/zKtInyuSYm4F3EEHp18P75yOJ/IxKJaI14Ky607OYgsLEfMXp6kYmgDYLP1QfxYnwf3F8+kroZGDyeAiTtUnX8hQZMD6ZQioIplCYmiFZgiCIelKprSgYCNYcQ0PAi0Zp+yJTZYuCdlSt5KQgei6oBj8HTnQxsbOaz0faLAgeAX9OZMM/NvP1+TNFR9F63nuYdO7AZZB6LAqeRkt9sVSqBAkC+Lr6M4cCqADQpgEOJ8DdVIku3AOMVqdAxIf6YPCAZGX62UgeHClFe6PBTBluvSyOu+AzrqwGPEHDkxKZiX7ueW71e3huQRmoN4DFqloPlRwPMuy6M7476GdTEzOw9Pr7a76fOhJcIa9jx3La634fz0A+UbF+EN/MgMTfdTb/7X+dYajg5sdDIBE1OQK4X6ueCuy5sagitA3A0Crqq0MIEzymwKRfK3oWmefC3ETB8EUS4KxhGFYi5FiIHyO+5pfDPVVDPXW4urpcC/foawGHI0G9RBnT8irQM8Urcj3z7/wcCFF2C/34HmXdSB+nXcRtSslqTPEjE5Fsk9J+ImES7IAt/G2SB/D64nyIEXvogPg4fUv3SrMp+i4FnNVgcAKsGfc0QqUI7RRbolCrblyJm000IqCjBWw+kxFRDWqpvReAnBti0ciWzagAPv6bXCjZC294438O8gwFMX8wl8upR3GQ2sxsBo6kByFDAqoPfJLARg8DSOA1mIJEQWy5kxECYRc7LJEXmq6xEIO0Asqg6g+cYTUBGVyDghxgnhBdm4BiYhrUKeISAIzs2lcS160moXx9PCRzJOYZlYBr1PJXB41B+gH6fOsh3QWqUwsnfR7HggI8xX7qI6jaa+EG3VzoHAUch/tJ8rMlNKduxlMI1nxLR6XLCv1rK0OJI9seB1QNnwmDoEbCkQmwDeNcsfUeSVVipwK7DUPYhtCiDPD/siIMfu8EfV0CYCqodEm6XPhwARWXw5ipIckmLeIDUutC/L5guvUmsIUOGfgUyoONXoNDMFB8wBSlX/QxJmdSjPJ0yCVm8NeBmqq8UCe1vAxIlMSNw4UMMnz8g3+SbI7BRD4ECMxKZ+AFJD4xFIiEVtUOHpzXYH0yhdDVDlAIDFUnTWCtsG0CqXdYgkRJL8Bg6IBGU6OBzbUIAJBqBHjPQHfGCbFy5kiuD4LGwCnjURiHg+PpggLZfzuXI6DF0QRpaZQItFNgXEHNrJNBJlWiFL3gOIjQBrIAfuhRDbpyYL93AD8EUzFnEG5OIvGc3IyC32y9D4Yp0UAPQ2g1qOGScyqB0UBpJxZJqAeg73UNebCra2vWo9eqjaGD2g9MM4buP4R6VRn1XNuunhOHw6Qz4zInbr1Pigc/HhrE/L0Cv+mbe2OhhxTGNOhNexB6MdPiLs8mb/RjeknwSrn6MiNZ98WTuJ2fuM0T0GEDql9/y4EaFqa3hZBJMCsDBBIj2wUYgzAqXK2DPgAmviNH0TAXD6P7uEO6HK7Klw6glSJwlDvjHKoh1Srk1QN1kGJAGZgM4DBn6zcqAjv9xBYD3kJLLUB+M9cgQt3DK0ykjEfPeNZSPqq9OJxADaibQCVkg+yHfxE2ISXQVski6EBjoiCwwoXkqFafOasCnOrwTkM6SHUzQWIVmCoxUzo+CnES+/R8I7tuPRFkGIaWxWxDY8CCgEQKeHsFbJEjOZTewHeasWMmkr69kYEOdhRNsNbYsr6qApnPDfA/zDwZ4bs5c3hgzhlINGvvhRHBi6m3AX3U5hnpAgioQEQGUBCtPbECbPDgbC9eZYZoihzdQkddSEjxvaUjpcT3AoUNMAFQzRPogJyBRlGQVzuoQdSqD4iFpJBSdwQRkxaYSt3Y9rtT6JOVCdoT4YVx2UAI6+kO/w/ze2zSMUXD5INau0DpRZclhP30amFh1IoDNBL0bmFhzIoAloR7Jk95Ac5eRN/sx6qpFXF5XYf4B/znwcB7dQu5Xz9Hh+Y+oP+Z24nLgaG9wWQW6mvmk7LWjGTZmgucs9DsKg74DpyaG0XpBynz3JrirMzQJ5k/KHPDWagh3lDeGS06CgWlgNuZZGzL0m5YBHf/DcgN/Rxas3kgoP4B8W3Ygi1jH4N/2IJGP2Br2VQp8iBhCmyOA0RWJHhQgfokfkEXdgxj6WgGbKfeMxFXYX6EOz+iwNJhC6WaGRBX6KwIQFZt/lSCwtBFZsHTEH9EPAZ9dCEiVIj0snAiQnAMNHaGkUDOQPcjKHlT3srvYsuZD0m8Jp2/D2q1a+3IDtHvXgXXcOHrMmct2RZHojAcW2wQ6AsBxHXRdvs0fVAXEngVe0wRArnVCrhNi4mCXWYDsjCLejaaA0+ejpcVCErL9KmRwWTxgVSHDBaW6nOuTqo98q5nWikL7zAwW9E9DAbyr16PWr0/zE1DPC50zA8zqopIVp0DGaWjUgD4NVDJKdCIsCqumhPPFbh8Pf+9B1+Hvw2wsP+pn+dEAui7nP7rXBDz7VlBXLSJ9ip2UKIVJX7v5cm85eOQteRPfsS1M+j6T9MsiuMwCx1QBzbN+eEqDtE/hhWbgi4AcG/TdBNfthDrBtyGiF3ADvGGR1vl+J7yzGtQyifgAJNWBgf3AYgCHIUO/edV2/TY+Dv7DKkEiEuORaMGrSARjMOXplPFIg66OwBNUbxbVkFbonyDQ0AaBCHtwPz2QUevbEBBRgttsRyDgEYLRhaC26bLYHAhAqgIDTdDMDCMUSY2EjsEf3OdqIBvxQIQjpaO9kAZi6cASBFDcSNSgc/CYopxIWcj24C2n+vP0+tHX2bL/Q57sa6VPg9rH5dvWMfHaFTYemTcPnn4a5YUXSFEU9pkkbXJCAbcungs7Un2SHyyB/VKXf0cCh9zgioIys5zfHEVeTwGQs2MHRUOHkH3NGOp++CGtVVUiSDoMVsVk6wb8Khw9cZTSYQOwduqCY95c0hs0wLx7L9ei8JUtHMUJZ5Mg7qezvH/PAPS6SbBwEcqjD6NGR7Iho4yn+lp5qp+Nz3b6eHSFh+QIha+uDaN3QzP3dbdyplTjmdUepv3kx/XTElJtTtKn2GkQnEEzY4wdcPPloldQFJXYPjeSuWcVKzZ/jv3yO7GZoIkCp/1g98Px9eJX+dNX8NgEiPbAj2nQyQtJhyFmDEQOEcPoaOADLwRWg1ZW7uupk2gAhyFDhs6XEen4DyobaWt+B7IQv4OUXbZCDKLHkIV5eXCbRjXsZy8CK2VINUos4sPYj0Q59iPf5nVkAU1FshbdkfLYUD+PAPCxDu8FpIV1W5NUNKQFy10rluIeR9InexDQUBGQ6If4H9YiBspQZUoYAlM9dYg6Tnk0I3RwF9DrR1/nkf2P8GRfKy8Msp3Xh6M2ev0HD4987yHsySf58IUXeAOFtl742gweVdJJ6Rp0VGCFIpGYFF0CL1eVwWIzRFigxCznLTN4DrQdO1AGD6SRycGxwgDRk6eQ/PFUnIpKXgDCFHAooHrBfPAo/pF9qePOJ7ssQP1hIwibN59IixWLE360QXIWOLx5uIb3IjbnJA6vhtMeiam0lHoxKqe9VgJlTq5ta2buPj8ALw2y8URaZWePpuvcs9jDulMa391UDhwhHcwL0OE9B5bGXalz7XPkfPUcSrKdnunruFmFl3QYdAQSfoTFl0PfPRCZA/23wTt3QYwLTifDSybo2ap8vy433H4IorKhT4HAaWICDOoPVguGDBn6fyIj0vE/pmNIk6/fI30y5gZ/tyHNvtoi36QPIuFqazX7yEeiJDuRhl71kejFfoJDtZAeGCAgkoCUqLZDUgehNaBAhz/psCyYQulkhhZWiWp0p/yiCMHEeuSbuxJ83gFI9GQ9Ak42JL0SSdAMWgJROykHjaLan6dfAjgAHu4ti/IjL73Ec7pOvRdeZK1JIaBLlUo+wj67kBc2Dvhch55uD+5iE7ZEM0VmGX6Xpchr13fswDR4IO0jXay8KYxvD/mYPH2aQNYHUzGpKnYVnD7Qjh/FP6IvqYEC0m+xsStb45o5S4kYOxZt5nz8ZitKQEpz7UcOUHzkCKM6W7its41bv3XwyrU2dp7VeGaNE1u/fsxdtw57s26YIxN4atUykiMVbu1cfpWoisIHV9nRdf28c3akQGPADA+m2BTiRjwIgK1BO5yb5tJMD/BXTDy1B5YfgvqFcNMy+Gw49NwDB/rAW9Pg3vugVQP4S5SUYTcF3G6YsRqal8LOdlBWBo0jYVA/AzgMGTJUvQzo+A9oJ9IN9DEk9XAAmRB7DEmnXIFUlIxDylqryouYSecg4es2CEgcD94Xh0QglODv0Ujr8suRSEooObFZh6cCcEiDFAX6m6CnGa5UxAgJkj7ZiEQ1MoP7TEW8H0nB+2YgkY4AAjo9AtD7EERsR0DjCBJmuUS9dvR1Hv0ZwKHrOt4A55lNz4HHyy8T7fNT8tIr6CYFKwIeRYqkCMzAeh2sGRls7p+GOS6BtOUr2GCPY6kiL8W7YwfKoBBw2Ii1K0zsKIv+5OnTiNPB8/FUUlWV0iNH8VwRBI5JVlKjVFKjVBZeZ+fqL5ei3TgWy+z5hPuttPfC3iv6EvbGm0x96Pd0TFbZf084Cw74eGaNB/sfHsLSqBGeDT9QZ/QTKCYzKAq3L1oKcA48AprO/jyN9kmVU1FHCjTSprkpMtch4YZXMEXE4c09gaVOY7wuB0UnTtJXacrSDOi+AzKbAj64dwFMGwex+2HxlfBVQxgfJebgh4C3PLByDZwpES9RwlH4oQPc3BCs1RGzIUOGDGFAx79da5Hy0D8ipZWJwB8QCDmK+CU2Bu+PqfJYHemb8Xrw9+YIcOQhhtNYJDKiIqmQMMTH0Q9JzygIGLyrwfsBKNGgpQnGWAQ0+iFRCh0BoO8Ru4UW3PdIxHfyE+IfCVS4r28+dN8O4duR8Irj552f7AjY1Bj+efR1VvwM4PD4da6d62LrmQCrpoTTOrF80dV1nTOlOlYTbH/tbyQBtpdfIVxV2Jqbiy8rC4vTSSA2miwNPKNGkFSSRWleJt+PGEzj71eSER+HtmMH6qCBtKsAHCGFwGPKjGkkKVD05JO4r+hHI18B6yYLcIQ0vLmZRdfbuebLpXD9WNxz5rO+qRW/z4fth/X4TCptEk2cKdVoEqsSbjfh27wRoqNR7eGoZnmu+KH3AHD7oqWYFJjY0cKti9xM/8nHM/2tPDtAEmjHC88HjuL0mRT/+CVhzaQWqmSvg3gTDFoKawZCh0NgjYSsFvDnr+CFWyGiDswxS3TuGqCeD64rgP4OgV8FaGaDpg1hsVU8SYYMGTJUnQzo+DdJR8DiLAIAryE+iXbAK4jp040s4I9xvlk0A3gRiWY0obzqogxJw5xAIhgJyJvoRPwaIdNnvg5PavBdAKwKtFMlqjFaER+IgmQ9liJg5EBSOr0RA+sRpOplVfC1JPpg4D7ovB3CtgcP4Geo1AqbGsHWJpCRCLEOMK1czoqvHuH+bpZaAYdf07nrWw9FbnD5Aqw6HqBhjMqAz5ysuVnAQ9d1Hv7Ow983eumQpHIgTyPntb9R1+3l5MH9BL7/DpC0EMjI+rpRJtJvDqPIrTNo5m4KrxiM6Z130UYMrxY4QjoHHtOnoc+eRZNozgOOkIY3N7PwejtXz1mKf+K1xM5fQPuVK1n11Vc80svKvtwAQ2c6eainlb8OsPC7ZT8QSK6H7nWj6xqKoqIoKtE9xuHcs5KPd/hYdSLAzF0+rm9n5rm1Uv7z7AA7y4/6OVviJ27wVZWAY0J7M1/u3QJAfsCKMwnsbeD6b2H7ALmeuhyDDdfCe3Xh94qcp1nALC8MKAXFDT90hjbbID4KBg8Am1UA+RCShjNkyJChqjKMpP8G6Yh/IxoxU36MgIcbSU1chngJ7kJ8GRXlRHwSy5G0RgPEK+FHIhmZSGQjIbi9CakgCHn7ftDhzwE4GIBkk/TXGAVcETRL+pA+E98hKRgT0B5JxRQgpbQlwX01zIIB26HdNrDvQmpuL1EeFbbXh81N4EgK2Pxw2UnoeRya5Qn8nEk6Q5+FPVD1bNZMtp1ngqwov6Zz03wP8w74UXQdVdH55oZwOtdVGTTdSa5DZ/WUcD7a7uPvG728M9LObZ0tjP7CyXfHZOqspW5zoi4bibVOIzSPk6Ilr5NkKiX95rBz3UJ3ng0wYKYHkhtSfOwYf+hp5fWhFwaiL/b4mLrTz7RrbNUCR0hnyzR6furhjD2R94+eZK2q8PXkiTjmzEHTder0vYLc9d9jUhXU3mmE3f4QJTePJvXOD7HEpZ5r/JWsFNItReHrA34+HxvGhPYWXk738OQqD8/0t/Ln/jZuW+Tms50+wlr0xHV4I68MsfHHPjY+3+Vj0tcurNdPZuT4T/FGqzR3wV2rYWdzODwWRqdKSu86pNqpvR9KD0PEAVjSV8ylKRp8URfCg+7kMuBl4CnOnyZsyJCh366MPh3/JfmQCpWOCCx8D/wOKTE9gKQz4hGvRcUwk460LX8b+bBuANRFICQaiXyEIhuhlt1jkQqXAPC2Bh8FpLlVMxP0VmU+yGXB/R9BQGN7cPvGSFTDh4BGHmD2QNvd0HcbtNgG1qxLf/0asDcZNjWBPQ0BBdpmQvfj0D4LzBpCP52RUpvOQBycOHGCgf3TUB3ZrJlUPXhUBI4ul13Grp92sGhCGEObyZnMdWgMmu7kSIGG2w/vjLRzbzeJQnj8OmO+dLL8aABLnSYkTfwbmquUvNl/pA6FpE+xnzcXJQQeZbHJBDIzebyPlZcG/3xzKwhw9Jvh5bgay5CF6QyKbUluIXyd7Ofss78nPLourr88hfrBVEp3rMH+5gc0PO7iQK86JIz8PfaGHWoEjpAqgsef+lnp8qGTXdnaOeAI6fNdPiYtcBM+fgpjbpuKalMxW+D3jcFcT/xG1wNfAqN8cF8xKAXQqhR6nIZ3+0GTKOhtFo9S6KzsQ6613//ss2TIkKFfmwzo+C/IgYSXhyNpkQJgAjK6PTH4twkIkFTUPuB5pKKiIQIEZQhYZCBwEocs6CkIbKQAOTo8ocGKYAqlrUnSJ6MUeb4CBHZWBPeXgLQttyM2jEwdIrOh8ybouQUa7gWL/9JeswaciIONTeCnRuC0Q7Ns6HYcumRAmB9ZjVohkNEFMadUEwi4EHhUbG3+1uezePqB+0jQi/nhZhuJ4eXb5To0Jn3tYlwbC3d0rexofHeLl/uWuLGoCuZ6bdBKc2sEjpBC4FEam4z2L4JHCDhOEUtgZTr9ClviDgd3GGTGQUk02ALQ5AzUKYANHaXD6ZBtsPbeQZwtPg2uwgsCR0gh8OjXyMS6k4HzgCOkz3f5mLzATfT4KXR4aSqd6qt47XAl0nflLWCoH947BabjsLeV9O1o5obfNYW7woQhxyORu5BmIdfowEs+S4YMGfo1yoCO/7AKgDcQqFiGlMA2BaYjaRIXMqI+qsJjioCXECNpveD2biSyEQoyhCIbLZA0SgKQHkyhHApIh8ieCtykQC9FIGAzYvw8iQBGn+Dj9gPHvZB4CLqnQ7dNkJRfXkpbW+VEwMbGsLUp5EdBaiFcfhx6nIRYV3CjeMoho1OVF34BVQceFYe3NZv7FW1Gj8aydy/f9+9HY2sZKydaK4FHdfp4u5c7vnFz7+UWrm9nZvgsDwkR6gWBI6QVx/xcMcMJaWmQnv6zwCMEHCeUWCLnp+Po0pKrNkOiGwrjYEN9SNVBt0KeFRQTlNkgohSuWA3538/j6/fFonlHFwsfbfdxRxcLH44Kq/E5Q+BRE3CEdNtCF5/s9NF+w0aU3j0YhAB0U+B2Pzx5HIrPghYJDUthW1NISYZuFmlqdycSnfsDYjQFuWb/goBI1UGAhgwZ+u3JgI7/oDKQaMYEJAc+HqkG+QnxYvQHhlC5q+e04C0W+XBXkA/uUIPOeOSDuzNSrhoOvKnBJwEo1qGpCa5W4TpFUjGHkS6gW5FUTQfEgHpSh2MFkLQX+q6ATj9BvHZpoFGd+fPy49DzBKSEDCBmhLRCoNGI892xF1AAaS62D1h+4gSf9k8jzpHN6ptsPLbSy/yDAZ6f+xVFo0fzavAxe/fuZWD/fqRay1hxAfCoCBxvj7SjKArHCjWibVwUVnIdGv1nejgWiCZybTrasmUUPvTQJYFHCDjO+GOpPy8dR7OWlMXA8xmwJgU8CmyOkn4pmgZup3RAzTJBfCFMWQuN9mq8+ukAjuZuQ/M66dvAxIaMAFOvtnNLZ2uNz53n1C74Gqdu93L7N26a3HMP/d95hwxFIQsJTMVokH8GGu4EVzz4bRClQoe2sM0moN0Naef/IJI6fAWBXJBKqn8ifWcMx7ohQ79tGdDxH9J+JJR8JfANMj9lHpI9KAXuQSIdIW0AXkA8mU2QShQ74qnQEQhRkDTIcMSj8bgGKwMy06OTCrcoYgx1IKmTZYj3oxGy7uc54Wg21PsJ+i2F1mckPVNb0PCqsK0+bGkCh1OkNXbHoPmzeV4FlqiLAEZXJGdkr2mPlRUCjN1IVOYQ0n7cjnwr7gyknjjB/f3TOJuZiaaozJ37FaNHj+YTZFFrBZQFYN2OPYzp05XBjWDJjecfwObMAD0+djCxo4Xpo+2XFJ3IdWj0/8zJEaeV+lu2UadtWzICUhbrevVl5l8Xxpg2Fz+rw2e5WHkCbp63i1ODWrPWDOHBHinXA5/6JdK1QYeUHLAEoLkPZteH5BJ44l0IKwVH8VHun9ERxRZBoCS31uBRk0LAce899/DIO+/wiqKQjXiHjmjS7CsqR6bmDjgpw94cPeAyq0TRMhDY7Y00hXscMTt/iFyHINf7EWSKsiFDhn67MjqS/ge0CTGK9kJGuY8HPkJKT1shTZRCJzgT+BPyTb4B0v/CRPlgttjg44YEbxt0GB6AwxokmuAGM9ysyJTXjchMllMEsxgB8ObA0Vw4ux36roJJhXJfbUAjZP7c3AR2B82fbTIh7Qjcmx40fxI8wG6Ug0Yt4uYBZHHaFTxfhxHACEcW2suRpmjNqDwLhsaN6bhuPbffdiu33fc72g67hnUl0u3zPivUcUK2orP/k0/wer2MblU98bSIV2mfpPL9UT8H87VKfTwupFyHRv/pHo6UmAmYVLTklhx0gev4UbQZn9K0jpXetZwJc3NHM98fdfPN/FdwXzkVv64yAJkkvNgDWS5QfFAWDh4zvGiD3dvh62RwqHAsGbp7YeWIZtwU9S2z/3klpog41mcUYjXBbYvcAJcEHiHgqH/3PVzx9js0QOEdBJhnahCbC/kKFCdB0wL4sRkMqwOTrJIy7I74hDoh174fMZO+gID2LOT97YM0x/spuK0hQ4b+f8uIdPxMLUMAIhSpiEIaeYF8q2sf/D00UfZrxNwZXNMpQRbkWGQBvgrorcM/dEmhlADNTXCbIubQs8F9bENAokMpWE5DRgHU3w7ddkDb3NqBRlXzp6OC+fPyDIlsnFNDylMmbam+P3tQIcDYgUQwDiNQFYmUBl8evDXl/HJKXYfSAGT54KwXTvjggAZHdcgzgTO4vps1yLNBD6fOiWceZt37f+ftEXbu617zgeU4NAYHy2lDfTwupBBwHHWEEdFzAoUrPyT5aA5qQQnFo/pSt0Kn0dpq5i4vUxZ6YPIU9I+mMlZTaVUCqxxw1pdL/h0TsI26mpd+9yD1v4e9hfCXKyE1G7odgqxEeOB9SL8GMg9v5us3b8B99iSW8EjMqW1wH95Y64hHCDji7rqH1u++g0lRaIakSDrq8PVmeD0eiiMEmvOjISUMmpvl+noAaRTWBgGKMKRapR8C228g1/pc5Pp2I/6O6hrgGTJk6LchI73yb5KOfIsrQ3LWXZFFtggZ6X03AiI6sAgZzKYiC60JSYOE2ofHI5UoDYJVKKuCKZQ+KjygQFNF4GYp4AlAk1yIPwV5eZB8GLrshY5ZwXHqFznuWpk/CR78ZQhkdEFWj2oUQCIt2xDAOIKAUiRSfdOVcsCoaGHUdRkuF4KL436ZbHu8ClxYNIj2QyMFmqvQ2gSpVqhrgTMWeOqRh1n2xhvVAseWzAAtE1RiKjTyqggea28Op1UN4FEROBIm/JVAWQE5Xz7NuO9W8d3NN5LkK2DdJQJHSDN3eZm80IM+aQpx707FrKo4C3LxX9Ef9chBXF6N9o/+jXGtHqFAg6k3iqcjuRASHFAUDv4wOBsLPdY6yfzweXamv4fXXYJitqIGvJQ8EUW4peYUUplXJ+avpUR16cZn6ZuIDFN4HzilQ5gOqZnQbDtEuSG3LqxpKQPcsq2SLuwYfD/vRFKLZqRPXAywEAGPhsAHSPrwCwTKjyF+p+oa4RkyZOjXLwM6/g0KAO8hC+th5AN2CQIYY5BBaAryYfwYklJpgnxIu4OPj0a+9V8L5OrwbDCFkmyCiQrcqMq+5+hwxg11sqHBMSgpgPhs6LpXSlEvBhqVzJ91ILasGvNnSC0oT5m0pHxYS1B+BDC2IoBxFAGM6ODrC0UwGlc4Jl2HooCAxVkfHAtGLo7rkG8CVwW4iAnIY1uo0qY91Qp1rZBglvkoZyi/nQWcLhd/jY+jXYyfLXeEYzWVL2Mh02iHuhZWTapcTjt3r4/rvnLx535WnhtYfTpm0tcuZu7yUXfKm9jqNsdxYD15C/9K3eQ6RGvFrP6ZwBFSCDz63jCFyFf+yuqhA4k4dZj1U2zM3OXjxXQvA8f8jcE9HuGl+yGhEOY/BQ4dHn0YspIh7UcXLY4Gq1asZcybfiN7t31z0SqVkEJVLd3//AzTnnuWZsB8Dd4okmvW7oE+mTAkC/r2h3ejBay9SCSrAVIN1R95//ci13ckkp7pA9RB5gV1AT5B4GQhcmld9bPPniFDhv5XZUDHL6xQmiQJMdE1R0xyCUjzr7rIVNY/Ib0xkhHzphdpwBWNrOfjdfgmmEIpQ9qTP6xCfQW+8sHOMrDnQaMj4HdAdAF03QPdTkKiVjNoXMj82SyvSluMGMojGZ2pFPP2B1/fZqRz6VHkOKMR30V3BDAaIguJrkNhEC6ygnBxUINjOhRWgAurBrEBeVxrBZpboJ4FkqygmiG7AlxkBc+ZEjy/KcFDNAWPLxeYvmwZ319zNcObKMy71obVpJwDjqgbbsS7+Bua2xysuTmcxHCVNSf8jPzcSd+GJhZOCCeshmjAhlN+rpjpRklpS/yYP5E97ffoJVk0jFVJvzn8gsCR69D4ISPA1a3MFzSsztzlZcoCD6aYaMzOUgJ+jVnjwhjbxsyfVnt4Md1L8sN/I/evj9DxCDw/Az7vDaVJOgemPcHJD//OhClf0a3dVaxb9Wfmf/NCrYEjpBB4NH/mGUY98yyX/QRnj8C6JnCkAbgt0DICHrJI+/71CvwNMS+fobyMuzEC0N8jaRYz4lXpgVwzC5CBhm+Gnhe4Mfg4Q4YM/XZkQMcvqBJkdkoC0m/DiXzwDkKqD0Banb+DfPAmIR/KPiSn3QkYoMM/A7BGB7sCI1W4BdiZD8s84CmFeifB4oKYfOiyD3oeg6RA9aBR1fypqdDutHT+7JBVwfwJQhxtKI9mBGt0/UhofCMSxTiGAEYsAkjdgrf6gKJDgb88LXLEL3BxIggX7iBc2IJw0RhoqUILM6RYIdwCLjOcVcrBItRVPR75ZhwWPFQf0igtB/DpAnwqYNWD0211Wfy+Wb6Mo2MEPEY2U7l3iZuke+5h9GOPM6N3D/xnz9K2jsqf+tmY9LXrosAR0vpTfobOdOO3ROJ3lqBDpUFq1elsmcagaU7252kXLactdut0+8jBsUKNgA6dklX25mp8UQU8TC//jZRbHyHKBXH5OmdmPs6Jv79Kx2QTu/Og8+Xj2L5pziUDR0gh8Ej54zNE/+FZYorh6uOQ5Icjg2GVXaC5PfAkkup6FTGOliHvTxPkvbsdWI/AoQOJAHZGonwrEJ/TEwiYv4qU0V76ERsyZOh/VQZ0/ELKRiIcNsTesBv5xv07ZB1PR0oFS5Bv5Drl4+Z7A3U1gY1jQKoK93kg+RgsUOGMX8Ln0SUCGp33Q+/DkOo7HzRC5s9NTWBHY3DaLmD+JHiwFZpz+SPkGDYiUYwTCDzFAa2RCEZXpEFVvl9SIllBuDhUAS48FeAizg9NFIGL5maItoJuAYcZshQBi5BdJCZ4syIRDB9StVMchAoP5VCh63IO/cFzHYuAXB1FIkihn18D/uXLeOCaq/F4fXS/5x7yH3+csv5pWEuymDrSzE3z3eQ6da5oWjvgCGn9KWkI1iJO4YrmFt740cubw2w82PP8pfJsmUa/Tx0Uu+GWzhZe2eCtETyK3TpXzHCy42wAvwavDLHxUC8rE+e7mLfffx54NH7ob7zQ/GEePfI4WW+8ypvDbNxzuYUO7zs5lH9+a/Nd2QHe+NHLy4NtpESp+AI6FlPlY/hsp5fD+RrPD7Tx6gYvT67yUPeBZ4h78lkwQbco+J1dLqFXgPW6RDCGAI8CJxV4Dok4ZSGejQZI2sSJvId5CJy0Q/5PhCqubkFSNRuRKhdDhgz9NmRAxy+gY8gsFBPyAbwT+fZ2P1L2+SBSCpiEfCh7g9sN0uGMBnM0cCrQqwSu2wc7/PBTDFj9kFAA8XkS0Ug7CPU954NGVfNnStD82bOq+ROkZKU90AX8XeFwffhREaPncWRhj0MKUHoCnXUw+SHHC2d8cNgHB3XxbhSZwK0KHNgDEB8oh4t6FjBbwGeBkiBcOIOHEB68hapnvEBhMCpRBqh60C4ShAodec1xVIaJ0M9o4EJtNXKQAXpdVq9mx44dHB4/ns/6p1GnJIv0STYaxarsyw3wxR4fT/S11Ro4Qlp/ys/wmU66pap0STVXCx5nyzQGTnNyOF9jZAszi24I5+8/enjoO8954FEdcISAwa/pNYJH674DOLB+zbnn3njaT6+pTm69zMLUa8o7ku7KDjBompN8l07LBJU7u1h4ao2X90bYzlW1vLPZy/1LpcT25sssfDzKzm2L3Ez7ycflX22mxbBuZEWAV5Fo1z0IVLwA7NbFt3E7wfb+ioBfIQLdjZGoXgfEe1OCTCpuikDlHqSt+kjE59EaAXNDhgz9+mVAx7+oncgHI8jimIt8APdEyv9mI4Ue4cg38jpAPx1+CMAWr0DBVQcgNgPWNwe3NViJEDSD9t8PjVyVQaPUCpuD5s9TiRDngC5B82dqVfMnQD0IdIHjl8Oa9rDNKhEMD5IKagt016GpHzxBQ+chHxzWxbdRZJIpsCG4SAjCRRMVYi2ABTxWiXA4FDkPJmQRMiPRF48uIXMH4A1+I0aXc6IHz08C1UcqLgYVF5MHWQxHAtkZGUzs15e4kizWB4HjQtJ1vVaNwi4EHqGUytFCjXpRChtujSAl6PmoCh4lHrhihoMdZ7XzgCOkquAxprWJZv90cKJIrwQ73oDO2C9drDjmZ9EN4QxtZj4HHI1iFd6/MozRXzjJduiozVvgP3KYqaPsOH1w/1I3f+hp5fJUE5O+djG4iYm1JzU6dr6SLxfO41CSlW8CUKzCaRO4FGiuyLXvBV7WBWLrI3056gJPKvK3XCSa1QwxVh9AIl1bgtuXIdvNQgJwzyHgXkOBlCFDhn5FMqDjX9BaBCrMSHolBRnVvRZ4BvnwjUQW1RSgjR+25ECmCk2zoMchOFwPMhMgqgzqnYFuu2HgXmhWVg4aHhPsqCe+jIuaPwHNDpld4YfesL4DHIqTNEUi0E6Htj5I9oHDK6PtDwcELorN4AvuLCwIFw0VSDFBhBkCVnBY5Hj8ivhRbAhgBHT5xuoBXMErRdFlGxDwSkagIkmp/PPnQIUfKT8urPCzsMK/vRW2Vd1uvpw0kUBEBJbjxznwQzpDGptJjlT4cJQds1r9k+c6NIbO8tA8VuHzsbZK1S/VaclhH1fOcvFQTwsWk8IrG7w829/Gl3t91QJHSCHweKinlR8y/PyUreHyVw8c515/BfBoW0dlV7ZWbVrH49cZN0fA483hdp5e5aFRrML3kyKID1M4UqDR9zMnhcmNoWdvvDNnAvCHnlZeHyrRl1m7fUyc7yIhoT5btx+iUUOJmmg6bNPgm4CAQ7YZyoIl3HcjqZPXdfnZBYHwlYpAek7wPWyItO/PQ8BjJxLRKgn+bTFy7XyMwMvPrwcyZMjQ/4L+I9BRWlrK66+/zrx58zh+/Dgmk4mWLVsyYcIEHnjgAazWS2vN/EtDh8/nY8OGDZw9exaPx0NMTAwdO3akadOmNT5mPtJfQ0VCxBOQlMrdSBQhAokMNC6Duhlw0AdlVmiVBTYdjqaC2Seg0XsHDN4LrYoENELmz01NYU8D0JXyse9VzZ8akGOHnV1hYw/4qTWUJEGiKi2ym3ohygeZAfFcnNIl3RGCi/BgWiRZkY6mNrNELfwW8JnApwSNfMH0hwcxbXqDr13T5XVGIJ0l61IOE6FIRRS1gwoNiYZUBYnQ7xUzRRbEwxFXw8/QFeV2uxk7ZjSrV36PX9Pp2LUbO7dsxqIKKI1pbWbWuLDzwCPXodF/mpPDxVY0v5erWpiYe629RvAo8egMme7gQJ7G6ikRdElReWKlh1c2eAm3SIfUP/S08Maw6gevhcAj2gYlHrihvZlZ46q2Rqssv6Zz0zwXc/b5a/SRgIDH0BlOfjgdoEOSyorJAhwhHSnQ6DvdQ649Di07pxJwhDRrt49JX7uZPHkyn3z66XnRnyMafOMXk2ieCYpUMZTeDRwEPtbl2rkGmAw8pwhgFCIw3BuJdJUgpbV25H33IFVeexAIuR5Dhgz9mvVvh46TJ08yYMAATpw4AUB4eDiBQACPR2oSOnfuzMqVK4mLi/vFD/piOnPmDB999BHvvf8B2Wezzrv/iqFDuf+++7jyyisxmcQZqSMNjVYhC68VMc29EfybVYOUM5CaD7ZcOJgkkZC6ZZAbJ4t93RxI2wZDf4IOecHGSbUwf2pAVjjsT4G9jWH3ZVDQACyJUM8EKT7w+8XMmQGUmMBfIS0SExAYiVHFb+GyyLRSvyrH4EHAQg+mPQj+rlDeLbQB5TCRWAuo0JFweU0RidIK26pI2D2OyhARutVyZEslPfjgg7z11lssuTEMbwDGf+VGVWDJDWHkOnVumHd+P45ch8aAGW6OFILXK2dCVeCqFmbmXhd2HniEgGN3jsaaKeH0qG8OnjudL/b4sZt1Ji/04vJqPJVWc++PhQd8NIlVeH2jj5m7fMwaG8b11Yyjr6iApnO0UKNlwoW7pz6z2s3z67zMGhvGDR3O3+eRAo1hsz2Ma6nwypDzja0ev07fT13szlfJycmt8f9dng5L/fCNJpGPXBUaKOLv2AzM0+X/zMPI+/13RaA9VDjVHYGLg5RDqB1Yh4wOGIl4PAwZMvTr1L8VOvx+P126dGH37t2kpKQwffp0hgwZgqZpzJ07lzvuuIPS0lJGjhzJ4sWLf/GDrkm6rvPqq6/y5FNPoZqt2NsMILLTMCxxqShmK5q7DNexbbh+WoIz8yAtWrZiyeJvadS8OS8hs0GcyNRMiw7vBkB1QsfdEFkCxZFwMhmivdJbwmGD+AIYsBmu2gaXZUvr6B+bwLYm5ebPbseCnT/d8oF7OhL2pcChunAsBQpSQImDqFgwRclifgYoNUu6A8R8Gq3Lgh1hAq8VAhapFtHVYKfTKu+kKWj8S0VKG5OreCoiqQwVOhJ1qCkiUYIcPwiwRFF9RCKO8ohQbeWn3HAa+lnxVvFvfuDQ0qXMvvpqRrdUmT3WxubMAHazQrsklVGznaw9EeC7SWEMaCwLca5DY/BMLzmBKFatWUdUVBRZWVmsXLmSZ/78Z65srvLl+PJUS03AUVWbTvsZMtN9UfAAAYlbF7lrDR4Xk67r/G6pm3e2+DCbYNGEcIY3P/84a/KvePw6Y+d6+O6Ezt0LFvHAiOG04MLvm0uXzrlzA3DEBLkmSTFOVmCFLhBRFymxnQVsUOQ9S0H6fRQhze/cyPWUCnyLlKQ/iVw3hgwZ+vXp3wodU6dO5fbbbwfghx9+oFevXpXunz17NjfeeCMAK1asYPDgwb/oQVcnXdd56KGHePPNN4nuMZ6YXteh2moOY3uyDlG05A3smotha9dwun17zF4YcRrejweHBh12yTfhnDgojZb0id8sPo2+22D8BmiTC7saSlOujCrmz+QSOBUtEYx9KXCkvkQ7FDMEEsAdDyXRAi8hL4XVD5EahCmgWgCrDAHTKwAISATDXAEqmioSsajoqQhBhZeaIxJFlEc/QIyfNUUkorhw7t1L7aDBQTm8yHsnP22alM1adXltJh1MmlS9KMGbpgd7d+iwcd4cPr/tep5Os/KXQbLYP7DExdtbfCgKTOlk5eNRNgpceiXgaNu2LRW1ePFixo4ZzcjmKl+Os+H2w5DpDnZla6y9uRw4jhRojJ7j4aZ2Kk+klac81p/yk/ap1PC8OMjGk2nVp0PglwMPXdf5w3IP/9jkpf1bb1G6bClZ3y1n4fX2asGjqjx+nfFzPXx/QudPCxZRMHw4R5DrqgMSebhMqbkZnabDZg3mBGCTAmdNArPXKvCtLl6QzsH9vKUIRIch6ZYwxFBajIBHZwQ6lgJ/wGiTbsjQr1H/Vujo168f6enpDBw4kFWrVp13v67rNGvWjOPHjzN58mSmTZv2ix50dXrjjTd4+OGHib/ibqK61K7RcsBVQs6XT2MKeBj1wRYOdUzheB1okCGLf248BMygmCDKAZfvgRtWQYQfdjWGg6kQ5hPzZ/fjoAZgVz3Y1QhOJIHTLhEJnw3K4sAfBb4wARdzQBZXxQQmM/isoJlANQkoqMiHc7QejFQoEq0IeSviERNpsVIZKEI/PRVep42aIxIxlE/C1YOPqwkWKv7NETST+nXwa/JTqQAMZh3UIDCouuxcD0JDQAePJuDg0qUC5kIXoVWRhmp2RWDMroDZ4+bTiddwZN0Klt9kp18jeRVLDvu45ks3CV27kb1lMze2t7AzV+FYIIpuq9dRv21bUpBz2hAp86yjwJbFi5k4ZjQjmqnkOGHbGT8Wk8KKiTZ61jdzpEBjwHQPHnMUefkFPD/Axp/62/AGdK6b5+GbQwG0gNhrXx5s4/G+/z7wqAgcb7/7Lv3vuYcVXi9vjBtL9vKlLLzuwuBRETgWLFzE8OHDAYG5vcBKZHihC2gEXAn0VWoe1nZQg6/9sDAIHzEKDFNgqS7G0muQ1MpKRa7ZpshguJPBv5cGn2Mk4v8Ycklnw5AhQ/8L+rdBh9PpJCoqCk3TePXVV3n00Uer3e7ee+/lvffeo27dumRlne+r+FcOuqoKCgpISa2Hrf1Q4ofcWevHAfhL88j67HeYbr6FhKffJMwFhTGgqBDhho4HYcRGWTgP1RfzZ+tMSCqC/EjYX19mmxRFgdcGHptAhaKC3w6aBTQbKFbQTRKxMKsCF+EKJFSAisYITNgUqRxxI7NHihCQcFY4bjPVQ0Qs8u00FHWoCA6lwfLWYh2KNQEHf/AWCP4MRRdMFaILSggYNIEGfzDacKELx1IBEmwVYMGuQJh6PkScu18t/3fosdV5PEOejqU3hTOgsYlJC7xEWOCjq6zM3+9nwjwXY8aMYeHCBdRJiOeb1euIa9uWk0j/lVPInJHs4Hl1AYWLF3N07GhMYXYuW/ANJ55+EueOLXw4wsLDK/0Ekhpy59p0tk6dyrI//Yln+9vYmaPzzRENJTEFsz0Jf/ZhGoU5OXL/hc2iPxc8KgLHqHffZdA992AleK14vXzQrRvF+3dT+GhkjX1J/rrewxMrPUz8dBp3T5lMK+X8slVdl+hEOrAc+T0e6cI7PBhVq7r3HB0W+WGGDifMcn13V2Bd0Et0FdKp9KwiUbOuSJlt6D24E0mv3IaYlw0ZMvTr0b8NOrZt28bll18OwJIlSxgxYkS127377rvcd999AOTn5xMfH/+LHXRVvfHGGzz6x8dIvedTTBG1N66GVLjmM0r3fYf98Bms9gianYKeuyTa4bTJT0UXv8bpJCiNDFaCBD1+uiLf6hUVAnbQIiAQDnYTJKhQT4G6inyLi0NSNgrSOKxi+w0VKTONQUx2NqSaQwlCgUMvB4YSXSDCXREcNNlO0ctTEuiga7KIBILmUTMCNSqVFw4T50NBRQCoCgXn7lPPBwtzcMehdvBeyufQ+Kr5PdSBtDbb+YC9y5Yxc9QormmpUuaF1RmgBTRGt1Qp8cLa0wrjvvseXVWJqleP2MaNz71mU/B1K5SnizRd9n1061b8UVGorVqRX1rK9hHDKP7hR8JaNCN5TTrmlBS5Zl54gfw//wnVYib1Ly9w+vHHscXUIcpfwLopdtrUubABFAQ8Or4v7dBLHo86r3Nodfoxw0/vT5w0Gns7rV//CE8Y+OxiIM796B32/f5+Hupp5fVhNXtLjgarWsriGtBuwXoiGqcQaYWGKnQ1weUmaKFUblPu0KXR3LdIV14zMoPnGqCDUnlGoEOH7wPwvgb7zHItNNNhlyLXd0PgB0WgtTVyjWQj4Pc4AlB/przJnCFDhv73Vdv1++LJ3yo6c+bMud/r1av5+0jF+86cOVMtdHg8nnPVLqGDvlRpmsY/33mXsFZ9fhZwAEReNpySzfOo//fZNB9yOwET7GoGjjC5eWwCFhBMi3ghulRSKzYzWOIgrCHEWMVHYVLEiOpHUgcEUxA5GpytkGLwBVMLFVMUAb0cHELAoCAf6uYafoZXiCBUjCxYVYk4mBWwqHJcZkWgRw1GEBQ1CEEKaErlhd5T4ZYX/OmmHBACiDcjELz5q/wM3a8jryFEt3qFW8jboVW4hbavaQnWAYYPp9O8eSwYNw7VbKLD4sVoZWUsGD8exWzi8sVLcKSloQaPtwgBjOpuJuQ8WYCG3S4naKXBEh3FoGXL2fjWW6TdeisJdVPwBs+B6+mn+bFBA2yNGnF882ayVC4JOHRd58mVHvblSh+O2gAHQLd6Jq5vb2Xuwk9p1H44LfqNw6rA/oXvsO+t+/lDTyuvDb3wVJNm8Srpk230n57B0dF96Td7PVpSCgcV2G6B961gsUO8GVqr0N0E3c2QpshkZV2HQ4gH43kEfpsDo4F+CkQoMNoMV+mwUYN3/LDeJNdeuA7bFWiuC2jsVcqjdD7gJaRd+gzg1lqdEUOGDP2adMnQUVpaXgwZHl5zCLnifRUfU1Evv/wyzz333KUeQiXt3buXE8eOknT9LT97H5bYutgbdeLklnkUPSQGWasXIssgMR8anIZ6WZCSC1oUZLWAo52gLFG8CS4dSp2Q6wCCUKHrspiFvl1XvCmIT8GqSKrFGowU6KrAjR4EAE2RQW4BRW4+RTpE6hVuAWS70CJfEQSqLvJw4cW84jf/EOiEbhVBp2KkJHSzVLiFWqFbK/ys+Lutys/Q7zYkwmOp8jzmKrdzf7v6arauW0eY1Ur3rl1RgB/WrsVut9OlS5eLve21U1QUPPVU9ffdPIXc3Fw63zSBWJtyScDx+AoPr/5Q8zyXmmRWFWaOsaHrHua9eD0R6peU5J9lwz/vr7YPR01qHq+ydrKNftMzSL+pLw9+s57kpBS0AJzwQEaZeC22mGCNRTxHNiskm6CLCj1McIsFfq9AgS5zVr4AXtGlO+9QpDlYXxP0UeGADu/7YF4QPgp18ChQX5dUV7Ei5udCBDzuArYjzcd0XWfr1q2cOnUKs9lM27ZtadGiRa3PmSFDhv53dMnQ8UvqiSee4KGHHjr375KSEho0aHBJ+8jNzQXAHJP8Lx2LOToJ08k8rvoG4goh3C0QkJMMGY1hXyfITRZfBsHIQOinAudW8tC/lVBEgfJ0iikYXVCRD96Ki3rFRTuUVrFTviiHBf9d8W+hbUO3qgu4rcL2ocXcVOFn1UX819gVckCVyqnevf9z0zxyc3MZNKA/vtI80m8J/7cDR0hmVTqp8rWHuc9fi67pNQKHx6/z1CoPw5ubGdK08n/35vEq64Lg8bcRafRZsAdvrB2LFSLCITEATTSw+sDpgQJNOpN+b4EFFjFJR5olLdNdhdtM0MkK+5CZLDcgEb9eSFXLm1Z4QoOpfvhQlf8jBbpE6vy6eGyiFImUfQCU+Hy0+fBDPvzn2xw+eKDSsffrP4BHHn6IUaNGXfL5M2TI0H9PlwwdUVFR5353Op01blfxvoqPqSibzYbN9q8NuPb5fAAopot/4F9QJjNRxR5GHAFnC/C0Br01tIyBOEUc+dFKudei4oJupXqPhKHfrkLAkZtxhDWT//0Rjqoyqwqfj7GRURzgx4wAWjXOrFAfjiWHvPxzq7/aqhZdF0ewqitEZSmY8oN/B8psUGADjx3cdindNivSCTfFLRE9jw6FZphrhWlWaakfrUJLBcaYoJkJDljhiWB0o10w9XIvMC8AfwWyTPJ8FsSrpCqAy8Vno0ejrVhJRMueJF3/AtbkpqAFcJ/cxdadi7n66qt54oknePHFF2sV3TFkyNB/X5cMHampqed+z8zMpGPHjtVul5mZWe1jfmnFxsYCoLlKITrpZ+9Hc5XSpEk8176EQQ6GLiiv18vggQIcqydZaw0co2Y7WXw4UGvgKHLrxNprvhjNqsK6m8Pp+bGTf2ySqTR/HybRjlBZ7MoTOgsXLuTDjz5k9JylLKgAHofzA/Sf4cWX3IhRi9ehJNpweiHgBs0FeED1QngZRBaB6hODss8ELht4w8BtA7cJcEGEQyIbug4HbLDDKn4ozBCvQGsFLBb4yArPm2T2zx+BGA1e1OGgKdiCXwOmTIE160i69jnCGl9W6XVHtO1PRNv+lGyez8svv0xKSgoPPPDARc+nIUOG/vu6ZOho06YNqqqiaRp79uypsXplz549ANStW7dWlSs/V23btiUsPALn4Y1Yk5v9rH1oPje+kzvo9cC9BnAYuqgCgQAOh4MwC4TXUJZaVU+s9LD4cIAbO5gvChyhsti3N3uZOTaMCRcopzWrCj/eFk7qG2XnwOOVIbbz+nAMHz6c8ePGngOPZnEKA2d4iUluxKo160gJVuVUHLiX44cMP2T5ICsA2QEo9su8GZcfFJ/MGYp2CJyYAuIn8poBn7TitzgFQpw6bAxCSMAmkcFCFQ6bwG+FGBNMQnwcP+3eiTZ3LglX/uE84Kio6O5j8RWc4c/PPMsdd9yB3f5zGuobMmToP6lLTuOHh4fTp08fAJYtW1btNrqus3z5cgCGDh36LxzexRUVFcWUyZNw7f4OPeC/+AOqkXP/OvxuB3fdddcvfHSGfosKCwtj9dp0lKi6DJzh4WSRdsHt397s5ZUNXiIjI5m928+Mn7w1bluxD4fVrHDTfBdf7vHVuL2m6zy4zEO+CyZOnMg/Nnlp9Y7jvMZfVquVr+bNZ+iwEYye46bfdA9RVYAD5FtIItAC6GOGCXb4QxS8GgvTEmBBMnxXH9Y2gi8bwcsN4J4GcFVD6NAA6qSCtQ4QDSY7mCxgNkl1UFgQUGLyIawA/AVQXAyFxXCoFL4uhZMOsP7zPUzRiUS0HXDR9yK6+xiKCguYM2fORbc1ZMjQf18/yzs4ZcoUAFavXs2mTZvOu3/u3LkcO3YMgMmTJ/8Lh1c73XPPPXhL8nEe3HDJj9W1AM6dSxg2fPgFp88aMlRRDRs2ZM269XAR8Hh7s5cHlrq55ZZbKCoq4rbbbmPKAne14FEROJIjTcTGJ3L11ddw49fuasFD03XuX+Lh/W0+Pv74Y2bMmMF7771HqRJdCThCCoHHVaOuoU6DFucBx6VIVSDFCj2iYGw8PJAMr9SDGQ1hWSOBkmV14as4eCcKngqHSRZpLtZeh0YaJOkQ54dYJ8QXyxyj6ALwLV9OeKu+KOrF01aW+HqE1WvFd99997NehyFDhv6z+pcHvtWrV49p06YxePBgNE1j3rx53H777ZSUlDBixAiWLFlS6/3+K23Qr7zyKr5fvZY6N/4NS2LtKmB0Xadw1cc4tn/DqlWr6N+//yU9pyFDp06dYkC/vlB6ltWTbDSKLef4EHA89NBDvPbaayiKgqZp3HXnnUydOpVpo+1M6iTTTaoChykygTXr1tO0aVNuveUWZn4+k1lj7Oc6l1YFjltvLe9qUdOAt4qqzTb/TnkDUOyGInfwpwvOuOCEH17sH094l6uJ6XltrfaV89VzDGlbl0WLFv2bj9qQIUM16d/WHAzAbDazaNEiBg4cyIkTJxgyZAjh4eFomobb7QZktP3nn3/+847+Z2jWrM/p1bsPR+c8RfyYP2FLuXAdv64FKFz1MaXbvuHtt982gMPQz1Io4jGgX18GzigHj+qAA0BVVT748EMApkydCsDEjpZqgSPUi+KTTz8F4MbPZwJwbTtzjcAB1Aom/tvVHlYT1ImQW1V9FBdDcVlh7XfmKiYmptUvd3CGDBn6t+lnt2b4v/buPSaqK48D+Hd4zQAFAhRDqcpoTaRUVGpK29AmUpuuWBVTk64PlJTGksZtBIXGtIlgWlNLRzFblKCmPkqRhLS2mxq0LYVtta3VVZeQ1nSrjAwzhQa1MMwDw8zdP9iZheoAwnDPveP3k0xyvedO8uXkgr8558y5er0eLS0t2LZtG+bMmQONRoPQ0FAsWLAABoMBP/zwA2Jjx7dD6HjExMTgn81NmJsyC79/VIzuf5TDaWrFnwdyXM4+9J77DL8f2gjbxROoqqrybtdONB5Dp1oWftiPsmbnHQsOD0/h4Zlqyf7I7rPgAIDg4GB8cOgQctfmYs1xJ7I/cvgsOALB8qVL0P/LaUgu32tZPG51t8Nh+QVLliyRIRkRTdS4plcmy0SmVzwcDgf279+P9/fuw5X//ILwhGlAdCI0waHALRv6LZehcbuxcuVKFBUV4vHHH/fzT0H3Ks9US9s1k8+CYyjPVMvB/414JCVOua3gGMrlciH/pZfwYU1NwBYcwOA339LS0hD3l78hav7iEa/tPlEBXWcLzB2mCe/5Q0TjN6mPtp8s/ig6PCRJQlNTE+rq6tDV1QW7w4G42FjMmzcP+fn5SExM9FNqov8zm804ffo0XnzxxTFNYUiShI6ODpw7dw6PPvoo9Hr9iNe73W6Yzea73rlXbdavz0PtsTrE5WxFxKyM29olSULPmWPoOVOL6upqvPLK3T1dmoj8654vOohIvfr7+/HXVavw2aefImLmAkTOz0bYlJmQJDecxktw/LsBjs4r2LFjB9544w3RcYnueSw6iEjVXC4Xampq8Pf3K3HhX+e95zUaDZY8/zw2FxXhmWeeEZiQiDxYdBBRwPjpp59gMpkQEhKC2bNnY+rUqaIjEdEQk/qVWSIiOaWmpiI1NVV0DCKaIDU+zZyIiIhUiEUHERERyYJFBxEREcmCRQcRERHJgkUHERERyYJFBxEREcmCRQcRERHJQlH7dHj2Kevt7RWchIiIiMbK8//2aPuNKqrosFqtABDwD7MiIiIKRFarFTExMT7bFbUNutvthsViQVRU1Jie0KlUvb29mDZtGkwmE7dznwD2o/+wL/2Hfek/7Ev/Ed2XkiTBarUiKSkJQUG+V24oaqQjKCgooJ6pEB0dzV8kP2A/+g/70n/Yl/7DvvQfkX050giHBxeSEhERkSxYdBAREZEsWHRMAq1Wi9LSUmi1WtFRVI396D/sS/9hX/oP+9J/1NKXilpISkRERIGLIx1EREQkCxYdREREJAsWHURERCQLFh1EREQkCxYdMrHb7WhoaMDbb7+NF154AcnJydBoNNBoNCgrKxMdT1GsVivKysqQlpaG++67DzExMXjsscewa9cu3Lp1S3Q8VeD95j/Xr1/HoUOHkJubi9TUVERGRkKr1WLq1KlYsWIFjh8/Ljqialy4cAHbt2/H8uXLkZKSgvj4eISGhiI+Ph6ZmZnYsWMHbty4ITqmau3cudP7e67YXb0lkkVTU5ME4I6v0tJS0fEUw2g0Snq93ts3ERERklar9f47PT1dunHjhuiYisf7zX9CQkKG9Z9Op5MiIyOHncvOzpZsNpvoqIq3cePG2/oyKipq2Ln7779f+u6770RHVZ3Lly9LOp1uWF8qEUc6ZBQbG4tFixahpKQEx44dQ2JiouhIijIwMIBly5bBaDTigQcewJdffgmbzQa73Y66ujpERUXh4sWLyM3NFR1VFXi/+cfAwAAyMjKwb98+XLlyBQ6HA319fWhra8PLL78MAGhoaEBBQYHgpMqXkZGB9957D99//z1u3rwJh8OB3t5eWK1WHDlyBAkJCeju7saKFSvQ09MjOq5quN1u5Ofnw+l04sknnxQdZ2Siq557xcDAwG3nkpOT+clziIMHD3or9Dt90qmtrfW2f/XVVwISqgfvN//5+uuvR2wvKCjw3pft7e0ypQpMp06d8vZlTU2N6DiqsWfPHgmAtHbtWqm0tJQjHQQEBweLjqB4R44cAQBkZWXdsVpftWoVZsyYAQA4evSorNnUhveb/2RlZY3Y7hntAIDz589PdpyA9sQTT3iPOzo6BCZRj7a2Nrz55puIj49HRUWF6DijYtFBimC323HmzBkAQHZ29h2v0Wg0WLx4MQDgiy++kC0b0Uh0Op332OVyCUyift9++633+KGHHhKYRD02bNgAm82G3bt3IyEhQXScUbHoIEX4+eef4Xa7AQBz5szxeZ2nrbOzk6vcSRGam5u9x2lpaeKCqFR/fz+MRiMqKyuxbt06AMCsWbOwbNkywcmU78CBA2hsbMSzzz6L9evXi44zJiGiAxABgMVi8R4/+OCDPq8b2maxWBAXFzepuYhG8scff+Cdd94BADz99NOYPXu24ETqodPp0N/ff9v5zMxM1NbWKv7BZaKZzWaUlJQgPDwc1dXVouOMGYsOUgSr1eo9joiI8Hnd0Lah7yGSm9vtxrp16/Dbb79Bp9OhsrJSdCRVSUxMhNPpRF9fH2w2G4DB9TPl5eWYPn264HTKV1BQgJ6eHrz77ruYOXOm6DhjxukVHw4fPjxsk5W7fZ08eVL0j0BEk2jTpk34/PPPAQB79+7F3LlzBSdSF6PRiM7OTvT19aGrqwsGgwGXLl1CRkYGtm3bJjqeotXU1ODEiROYP38+Nm/eLDrOXWHRQYoQFRXlPbbb7T6vG9o29D1EciouLvaObFRUVCA/P19wInWbMmUKtmzZgpMnT0Kj0eCtt97yFnQ0XFdXFwoLCxEcHIwDBw4gJERdExbqSiuj1atXY+nSpeN+f0xMjB/TBL6kpCTvsdls9vmp0Ww23/E9RHJ5/fXXsWvXLgCAwWBAYWGh2EABJCMjA0899RS++eYb7N+/f0J/gwPV1q1bcf36dbz66qtISUlBX1/fsPahj4rwtIWFhSEsLEzWnL6w6PBBq9VyIZOMHn74YQQFBcHtdqO1tdXn12ZbW1sBDM4HcxEpya2kpAQGgwEAUF5eji1btghOFHg8i8V//fVXwUmUqa2tDQBQVVWFqqqqEa/1jAZv2rQJe/bsmexoY8LpFVKEiIgIZGZmAoDP9TCSJOHUqVMAgOeee062bETA4JTK0IKjpKREcKLAdPXqVQCcPg1ULDpIMfLy8gAATU1NOHv27G3t9fX13j9IavlOOgWG4uLiYVMqLDjunsvlgiRJI17T2NiIH3/8EQCwcOFCGVKpT3NzMyRJ8vkqLS31Xus5p5RRDoBFh6xu3ryJ7u5u78uzGZbdbh92/s9zdPeKvLw8pKWlQZIkrFy5Eo2NjQAGv5pYX1+PDRs2ABjcsXTRokUio6oC7zf/GLqGY/fu3ZxSGSeTyYT09HRUV1fj6tWrwwoQk8mEnTt3IicnB5IkIS4uDkVFRQLT0mTRSKOVnuQ3er0e165dG/W6vLw8HD58ePIDKZDRaERWVhaMRiOAwWkXt9sNp9MJAEhPT0djYyNiY2MFplQH3m8T197ejuTkZABAUFDQqNtMFxcXo7i4WI5oqmM0Gr3PTgIGFzdGR0fD4XB49+kAgBkzZuDjjz9Genq6iJiqV1ZWhu3btwPAqCNLInAhKSmKXq9HS0sLDAYDPvnkE7S1tSE0NBSPPPIIVq9ejddee00xq7Ap8HlGhzzHXV1dI17PUSPfkpKSUF9fj+bmZpw9exYWiwXd3d0IDg7G9OnTMW/ePOTk5GDNmjUIDw8XHZcmCUc6iIiISBZc00FERESyYNFBREREsmDRQURERLJg0UFERESyYNFBREREsmDRQURERLJg0UFERESyYNFBREREsmDRQURERLJg0UFERESyYNFBREREsmDRQURERLJg0UFERESyYNFBREREsvgvK8hmAeAkOTAAAAAASUVORK5CYII=\n", "text/plain": [ - "
" + "
" ] }, - "metadata": { - "needs_background": "light" - }, + "metadata": {}, "output_type": "display_data" } ], "source": [ - "solver = ott.core.sinkhorn.Sinkhorn()\n", + "solver = sinkhorn.Sinkhorn()\n", "ot_sink = solver(ot_prob)\n", "\n", "transp_cost = jnp.sum(ot_sink.matrix * geom.cost_matrix)\n", @@ -198,31 +194,27 @@ "outputs": [ { "data": { - "image/png": "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\n", + "image/png": "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HD9+HAMGDMDPP/+MqKgou/XcuHEDQ4cOxa+//goA6Ny5M5566im0atUKZ8+exdq1a3Hp0iXs27cPjzzyCPbs2QM/P8e/Wlu2bIm77roL3bt3R2xsLEJCQmAymfDrr78iLS0NZ86cwfXr1zFq1Cjs2rUL9913n8O6UlJS8PHHHwMAIiMjMXXqVHTr1g35+fn44IMP8P333+PcuXO4//77cfToUcTExNitZ9u2bXjyySfFAPSOO+7AmDFj0Lp1axiNRmRnZ+P999+HXq/Hzz//jHvuuQfHjx+HVqt12Day4orEmeyGkEegegVVb2Ghri/N/v37xesHDBjgsNx3330naDQasewHH3xQxxbL98knn9g8z2eeeUa4fv26w/JlZWXCu+++K0RHRwuxsbFua6dcAwYMcOq1c9bMmTOFBQsWCF988YVw5coVQRAEYeHChWKbNmzYoHgbrl69KjRp0kQAICxfvlzy+3nTpk1iuZiYGCEnJ8duubVr14rlmjZtKpSXl1crk5+fL1y9erXGxysoKBDuuusum/egIzNmzBDL3X///cLNmzer1ZWQkCCWWblypd16Kisrhezs7BrbVVFRITz77LNiXZ06dXJY9vPPPxfLtW7dWrh48WK1x3vyySfFMo8++qjDutq2bSuWW7BggWA2m6uVyc/PF7p37y6WW7FiRY3PhQRBp9MJAISHO70pjOn6d6eOhzu9KQAQdDqdp59Wg8A5N43U3XffbfNX7/bt293yuDk5OXjqqafEfy9duhRr1qxB06ZNHV7j7++Pp59+GsePH8f999/vjmY2SG+//TYWLVqEBx98ENHR0R5pw7Rp03Djxg0kJCTgb3/7m+Tr0tPTxdsvvvgiYmNj7ZabMmUKevXqBaBqGOvUqVPVykRGRqJFixY1Pl5ERATWrFkj/vurr76yW+6PP/7A6tWrAQDBwcHYuHEjgoJshxciIiKwadMmcUh58eLFduel+Pj4oEuXLjW2y9fXF3//+98RGRkJAPj1119x/vx5u2Wt53atWbMGrVu3rvZ4q1atEu//9NNPceLEiWr1nD17VnyM5s2bY+HChXbnSEVGRuK1114T/33w4MEanwuRJzG4acSSkpLE27/99ptbHnPJkiXimP6IESMwe/ZsyddGRkbi3XffdXi+oqIC69evxwMPPIDo6GgEBgYiMjISd955J+bNm4ff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NTWpa91ardbm+pqutT4vtV3WGNwQEREpICAgAFFRUYhrE+uS+rRaLWJiYmzuW7hwIVJTU11SvzdhcENERKSAoKAgXLhwweldgC0EQag298Zerw0AhISEiLctm+TZY33O+pqa3Fq3o00FHdVtuV1Tu6zPS22XNQY3RERECgkKCkJQUJDbHzc6Olq8feXKFXTv3t1uuStXrti9Rk7djoIbS92hoaHikJT19UVFRbh586bDeTeW66W2yxonFBMREXmZzp07w8en6iveenXSrSznoqKiEBERIalu6xVOUuru0qWLU9d37dpVUrusMbghIiLyMhqNBomJiQCA3bt32y0jCALS09MBAEOGDJFcd4cOHdC6desa6zYajTh48KDduvv37y/21ji6/uLFizh16pTstlkwuCEiIvJCKSkpAID9+/fj6NGj1c6npaXh/PnzAICJEydKrlelUonlt2zZIibltLZq1SoUFxfD19cX48aNszkXHByMUaNGAQDWrFkDnU5X7fo333wTQNV8m5EjR0pum0h2wgYiIiKq98rLy4Vu3boJAISWLVuK+aMqKyuFTz75RAgNDRUACEOHDq127cKFC8W8UxcuXKh2/saNG0JUVJQAQOjSpYuQmZkpCIIglJaWCqtXrxYCAgIEAMKzzz5rt23nz58XgoODBQBCUlKS8NtvvwmCIAjFxcXCokWLBJVKJQAQ3nzzzTo9d5UgCIL8kIiIiIjqu5ycHAwaNEjsXdFoNDCbzSgpKQEAJCQkICMjA+Hh4TbXpaamYtGiRQCqMnjHxcVVqzsrKwvJyckoKCgAUNXLUlJSgvLycgBVw0nbt293uKJr586dGD16tLgqKiwsDMXFxaisrAQAPPnkk3jvvfdk784McFiKiIjIa8XFxeH48eNYsGAB4uPjoVKp4O/vj169emHZsmU4cuRItcBGql69eiE7OxszZ85E+/btUV5ejuDgYPTv3x/r1q3Drl27HAY2APDAAw/g+PHjmDJlCuLi4lBSUoLw8HDcd999+PTTT/Hvf/+7ToENALDnhoiIiLwKe26IiIjIqzC4ISIiIq/C4IaIiIi8CoMbIiIi8ioMboiIiMirMLghIiIir8LghoiIiLwKgxsiIiLyKgxuiIiIyKswuCEiIiKvwuCGiIiIvAqDGyIiIvIqDG6IiIjIqzC4ISIiIq/y/wHEdguCwAcbQAAAAABJRU5ErkJggg==\n", "text/plain": [ - "
" + "
" ] }, - "metadata": { - "needs_background": "light" - }, + "metadata": {}, "output_type": "display_data" }, { "data": { - "image/png": "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\n", + "image/png": "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\n", "text/plain": [ - "
" + "
" ] }, - "metadata": { - "needs_background": "light" - }, + "metadata": {}, "output_type": "display_data" } ], "source": [ - "solver = ott.core.sinkhorn_lr.LRSinkhorn(rank=int(min(n, m) / 2))\n", + "solver = sinkhorn_lr.LRSinkhorn(rank=int(min(n, m) / 2))\n", "ot_lr = solver(ot_prob)\n", "\n", "transp_cost = ot_lr.compute_reg_ot_cost(ot_prob)\n", @@ -273,12 +265,12 @@ }, "outputs": [], "source": [ - "geom = ott.geometry.pointcloud.PointCloud(x, y, epsilon=0.1)\n", - "ot_prob = ott.core.linear_problems.LinearProblem(geom, a, b)\n", + "geom = pointcloud.PointCloud(x, y, epsilon=0.1)\n", + "ot_prob = linear_problem.LinearProblem(geom, a, b)\n", "costs = []\n", "ranks = [15, 20, 35, 50, 100]\n", "for rank in ranks:\n", - " solver = ott.core.sinkhorn_lr.LRSinkhorn(rank=rank, initializer=\"k-means\")\n", + " solver = sinkhorn_lr.LRSinkhorn(rank=rank, initializer=\"k-means\")\n", " ot_lr = solver(ot_prob)\n", " costs.append(ot_lr.reg_ot_cost)" ] @@ -359,7 +351,7 @@ ] }, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -373,7 +365,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.12" + "version": "3.10.6" } }, "nbformat": 4, diff --git a/docs/notebooks/MetaOT.ipynb b/docs/notebooks/MetaOT.ipynb index a574c9ba1..dc4c3acd2 100644 --- a/docs/notebooks/MetaOT.ipynb +++ b/docs/notebooks/MetaOT.ipynb @@ -42,10 +42,19 @@ "execution_count": 1, "id": "9fde1353", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Note: you may need to restart the kernel to use updated packages.\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], "source": [ - "%pip install ott-jax\n", - "%pip install torchvision" + "%pip install -q ott-jax\n", + "%pip install -q torchvision" ] }, { @@ -56,8 +65,9 @@ "outputs": [], "source": [ "from ott.geometry import pointcloud\n", - "from ott.core import initializers as init_lib\n", - "from ott.core import linear_problems, sinkhorn\n", + "from ott.initializers.linear import initializers as init_lib\n", + "from ott.problems.linear import linear_problem\n", + "from ott.solvers.linear import sinkhorn\n", "\n", "import jax\n", "import jax.numpy as jnp\n", @@ -505,7 +515,7 @@ ], "source": [ "a, b = demo_batch.a[0], demo_batch.b[0]\n", - "ot_problem = linear_problems.LinearProblem(geom, a, b)\n", + "ot_problem = linear_problem.LinearProblem(geom, a, b)\n", "\n", "# Predict the optimal f duals.\n", "f = meta_initializer.init_dual_a(ot_problem, lse_mode=True)\n", @@ -606,7 +616,7 @@ "\n", "\n", "def get_meta_ot_potentials(a, b):\n", - " ot_problem = linear_problems.LinearProblem(geom, a, b)\n", + " ot_problem = linear_problem.LinearProblem(geom, a, b)\n", " f = meta_initializer.init_dual_a(ot_problem, lse_mode=True)\n", " g = geom.update_potential(f, jnp.zeros_like(b), jnp.log(b), 0, axis=0)\n", " return f, g\n", @@ -618,7 +628,7 @@ "\n", "\n", "def get_gaussian_potentials(a, b):\n", - " ot_problem = linear_problems.LinearProblem(geom, a=a, b=b)\n", + " ot_problem = linear_problem.LinearProblem(geom, a=a, b=b)\n", " f = init_lib.GaussianInitializer().init_dual_a(ot_problem, lse_mode=True)\n", " g = geom.update_potential(f, jnp.zeros_like(b), jnp.log(b), 0, axis=0)\n", " return f, g\n", @@ -677,7 +687,7 @@ " \"max_iterations\": 26,\n", " }\n", "\n", - " ot_problem = linear_problems.LinearProblem(geom, a=a, b=b)\n", + " ot_problem = linear_problem.LinearProblem(geom, a=a, b=b)\n", " base_sink_out = sinkhorn.sinkhorn(\n", " geom, a=a, b=b, init_dual_a=None, **sink_kwargs\n", " )\n", @@ -750,7 +760,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -764,7 +774,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.4" + "version": "3.10.6" } }, "nbformat": 4, diff --git a/docs/notebooks/OTT_&_POT.ipynb b/docs/notebooks/OTT_&_POT.ipynb index 15181472b..5d4ca6815 100644 --- a/docs/notebooks/OTT_&_POT.ipynb +++ b/docs/notebooks/OTT_&_POT.ipynb @@ -28,7 +28,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": { "id": "IO2KLVZ1KWvq" }, @@ -52,7 +52,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": { "id": "ysURew0UKhHE" }, @@ -63,7 +63,7 @@ "import jax.numpy as jnp\n", "import ott\n", "from ott.geometry import pointcloud\n", - "from ott.core import sinkhorn\n", + "from ott.solvers.linear import sinkhorn\n", "\n", "# import OT, from POT\n", "import numpy as np\n", @@ -510,9 +510,21 @@ "provenance": [] }, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.6" } }, "nbformat": 4, diff --git a/docs/notebooks/One_Sinkhorn.ipynb b/docs/notebooks/One_Sinkhorn.ipynb index 3735ccf20..e7c1f580e 100644 --- a/docs/notebooks/One_Sinkhorn.ipynb +++ b/docs/notebooks/One_Sinkhorn.ipynb @@ -40,7 +40,7 @@ "source": [ "import ott\n", "from ott.tools.sinkhorn_divergence import sinkhorn_divergence\n", - "from ott.core.sinkhorn import sinkhorn\n", + "from ott.solvers.linear import sinkhorn\n", "from ott.geometry.pointcloud import PointCloud\n", "from ott.geometry.geometry import Geometry" ] @@ -1129,7 +1129,7 @@ "toc_visible": true }, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -1143,7 +1143,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.12" + "version": "3.10.6" } }, "nbformat": 4, diff --git a/docs/notebooks/Sinkhorn_Barycenters.ipynb b/docs/notebooks/Sinkhorn_Barycenters.ipynb index db3116b76..02e388fba 100644 --- a/docs/notebooks/Sinkhorn_Barycenters.ipynb +++ b/docs/notebooks/Sinkhorn_Barycenters.ipynb @@ -27,7 +27,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -57,7 +57,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "metadata": { "id": "LraeyMnPxVyC" }, @@ -65,14 +65,13 @@ "source": [ "import jax\n", "import jax.numpy as jnp\n", - "import ott\n", - "from ott.geometry import grid\n", - "from ott.core import discrete_barycenter" + "from ott.geometry import grid, costs, epsilon_scheduler\n", + "from ott.solvers.linear import discrete_barycenter" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "metadata": { "id": "Vm4s3i53yZXa" }, @@ -246,7 +245,7 @@ "outputs": [], "source": [ "@jax.tree_util.register_pytree_node_class\n", - "class Custom(ott.geometry.costs.CostFn):\n", + "class Custom(costs.CostFn):\n", " \"\"\"Custom function.\"\"\"\n", "\n", " def pairwise(self, x, y):\n", @@ -257,9 +256,7 @@ "g_grid = grid.Grid(\n", " x=[jnp.arange(0, n) / 100 for n in grid_size],\n", " cost_fns=[Custom()],\n", - " epsilon=ott.geometry.epsilon_scheduler.Epsilon(\n", - " target=1e-4, init=1e-1, decay=0.95\n", - " ),\n", + " epsilon=epsilon_scheduler.Epsilon(target=1e-4, init=1e-1, decay=0.95),\n", ")" ] }, @@ -478,9 +475,21 @@ "provenance": [] }, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.6" } }, "nbformat": 4, diff --git a/docs/notebooks/application_biology.ipynb b/docs/notebooks/application_biology.ipynb index d8a4f869a..ffbcd0306 100644 --- a/docs/notebooks/application_biology.ipynb +++ b/docs/notebooks/application_biology.ipynb @@ -27,7 +27,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -39,16 +39,17 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": { "id": "n8JBHUyPHSJE" }, "outputs": [], "source": [ + "import numpy as np\n", "import matplotlib as mpl\n", "from matplotlib import pyplot as plt\n", - "import numpy as np\n", - "from ott.core import sinkhorn\n", + "\n", + "from ott.solvers.linear import sinkhorn\n", "from ott.geometry import pointcloud" ] }, @@ -388,9 +389,21 @@ "provenance": [] }, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.6" } }, "nbformat": 4, diff --git a/docs/notebooks/gromov_wasserstein.ipynb b/docs/notebooks/gromov_wasserstein.ipynb index d6c09bf08..23c2db96b 100644 --- a/docs/notebooks/gromov_wasserstein.ipynb +++ b/docs/notebooks/gromov_wasserstein.ipynb @@ -41,7 +41,7 @@ "source": [ "from IPython import display\n", "import jax\n", - "from jax import numpy as jnp\n", + "import jax.numpy as jnp\n", "from jax import random\n", "import numpy as np\n", "from matplotlib import animation\n", @@ -50,7 +50,7 @@ "import mpl_toolkits.mplot3d.axes3d as p3\n", "\n", "import ott\n", - "from ott.core import gromov_wasserstein as gw\n", + "from ott.solvers.quadratic import gromov_wasserstein as gw\n", "from ott.geometry import pointcloud" ] }, @@ -8203,7 +8203,7 @@ "provenance": [] }, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -8217,7 +8217,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.12" + "version": "3.10.6" } }, "nbformat": 4, diff --git a/docs/notebooks/gromov_wasserstein_multiomics.ipynb b/docs/notebooks/gromov_wasserstein_multiomics.ipynb index 5b3db54b7..574d35292 100644 --- a/docs/notebooks/gromov_wasserstein_multiomics.ipynb +++ b/docs/notebooks/gromov_wasserstein_multiomics.ipynb @@ -58,9 +58,8 @@ "source": [ "import sys\n", "\n", - "!git clone -q https://github.com/rsinghlab/SCOT\n", - "\n", "if \"google.colab\" in sys.modules:\n", + " !git clone -q https://github.com/rsinghlab/SCOT\n", " !pip install -r SCOT/src/requirements.txt\n", " !pip install -q git+https://github.com/ott-jax/ott@main\n", " !pip install -q seaborn" @@ -68,7 +67,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -82,7 +81,7 @@ "from IPython import display\n", "\n", "import ott\n", - "from ott.core import gromov_wasserstein as gw\n", + "from ott.solvers.quadratic import gromov_wasserstein as gw\n", "\n", "from ot.gromov import init_matrix, gwloss\n", "from SCOT.src.scot import SCOT" @@ -16943,7 +16942,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.12" + "version": "3.10.6" } }, "nbformat": 4, diff --git a/docs/notebooks/icnn_inits.ipynb b/docs/notebooks/icnn_inits.ipynb index e75704b73..6fb90cc9b 100644 --- a/docs/notebooks/icnn_inits.ipynb +++ b/docs/notebooks/icnn_inits.ipynb @@ -17,7 +17,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -29,7 +29,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -42,8 +42,7 @@ "from torch.utils.data import DataLoader\n", "from ott.tools.sinkhorn_divergence import sinkhorn_divergence\n", "from ott.geometry import pointcloud\n", - "from ott.core.neuraldual import NeuralDualSolver\n", - "from ott.core import icnn" + "from ott.solvers.nn import icnn, neuraldual" ] }, { @@ -551,7 +550,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -565,7 +564,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.5" + "version": "3.10.6" }, "vscode": { "interpreter": { diff --git a/docs/notebooks/introduction_grid.ipynb b/docs/notebooks/introduction_grid.ipynb index 402041b86..cadf21432 100644 --- a/docs/notebooks/introduction_grid.ipynb +++ b/docs/notebooks/introduction_grid.ipynb @@ -25,7 +25,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -37,7 +37,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": { "executionInfo": { "elapsed": 320, @@ -58,7 +58,7 @@ "import jax.numpy as jnp\n", "import numpy as np\n", "\n", - "from ott.core import sinkhorn\n", + "from ott.solvers.linear import sinkhorn\n", "from ott.geometry import costs\n", "from ott.geometry import grid\n", "from ott.geometry import pointcloud" @@ -460,9 +460,21 @@ "provenance": [] }, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.6" } }, "nbformat": 4, diff --git a/docs/notebooks/neural_dual.ipynb b/docs/notebooks/neural_dual.ipynb index 30f5f621c..1ba8b126e 100644 --- a/docs/notebooks/neural_dual.ipynb +++ b/docs/notebooks/neural_dual.ipynb @@ -17,7 +17,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -29,7 +29,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -40,10 +40,9 @@ "import matplotlib.pyplot as plt\n", "from torch.utils.data import IterableDataset\n", "from torch.utils.data import DataLoader\n", - "from ott.tools.sinkhorn_divergence import sinkhorn_divergence\n", + "from ott.tools import sinkhorn_divergence\n", "from ott.geometry import pointcloud\n", - "from ott.core.neuraldual import NeuralDualSolver\n", - "from ott.core import icnn" + "from ott.solvers.nn import neuraldual, icnn" ] }, { @@ -163,7 +162,7 @@ " a = jnp.ones(len(x)) / len(x)\n", " b = jnp.ones(len(y)) / len(y)\n", "\n", - " sdiv = sinkhorn_divergence(\n", + " sdiv = sinkhorn_divergence.sinkhorn_divergence(\n", " pointcloud.PointCloud, x, y, epsilon=epsilon, a=a, b=b\n", " )\n", " return sdiv.divergence" @@ -331,7 +330,7 @@ } ], "source": [ - "neural_dual_solver = NeuralDualSolver(\n", + "neural_dual_solver = neuraldual.NeuralDualSolver(\n", " input_dim,\n", " neural_f,\n", " neural_g,\n", @@ -535,7 +534,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3.9.15 64-bit", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -549,7 +548,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.15" + "version": "3.10.6" }, "vscode": { "interpreter": { diff --git a/docs/notebooks/point_clouds.ipynb b/docs/notebooks/point_clouds.ipynb index 7447d8cd6..763022977 100644 --- a/docs/notebooks/point_clouds.ipynb +++ b/docs/notebooks/point_clouds.ipynb @@ -21,7 +21,15 @@ "id": "O2Qs8m9SN1ag", "outputId": "ed53b82f-b649-4836-994a-453b16377772" }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], "source": [ "%pip install -q git+https://github.com/ott-jax/ott@main" ] @@ -41,8 +49,8 @@ "\n", "import ott\n", "from ott.geometry import costs, pointcloud\n", - "from ott.core import sinkhorn\n", - "from ott.core import linear_problems" + "from ott.problems.linear import linear_problem\n", + "from ott.solvers.linear import sinkhorn" ] }, { @@ -89,7 +97,7 @@ "\n", "This geometry object defines a `LinearProblem` object, which contains all the data needed to instantiate a linear OT problem (see Gromov-Wasserstein tutorials for *quadratic* OT problems).\n", "\n", - "We can then call a `Sinkhorn` solver to solve that problem, and compute the OT between these points clouds. Note that all weights are assumed to be uniform in this notebook, but non-uniform weights can be passed as `a= .. ,b= ..` arguments when defining the `LinearProblem` below." + "We can then call a `Sinkhorn` solver to solve that problem, and compute the OT between these points clouds. Note that all weights are assumed to be uniform in this notebook, but non-uniform weights can be passed as `a= ..., b= ...` arguments when defining the `LinearProblem` below." ] }, { @@ -117,7 +125,7 @@ ], "source": [ "# Define a linear problem with that cost structure.\n", - "ot_prob = linear_problems.LinearProblem(geom)\n", + "ot_prob = linear_problem.LinearProblem(geom)\n", "# Create a Sinkhorn solver\n", "solver = sinkhorn.Sinkhorn()\n", "# Solve OT problem\n", @@ -252,7 +260,7 @@ "):\n", " # Wrapper function that returns OT cost and OT output given a geometry.\n", " def reg_ot_cost(geom):\n", - " out = sinkhorn.Sinkhorn()(linear_problems.LinearProblem(geom))\n", + " out = sinkhorn.Sinkhorn()(linear_problem.LinearProblem(geom))\n", " return out.reg_ot_cost, out\n", "\n", " # The jax.value_and_grad operator. Note that we make explicit that\n", @@ -217025,7 +217033,7 @@ "outputs": [], "source": [ "geom = pointcloud.PointCloud(x, y)\n", - "out = sinkhorn.Sinkhorn()(linear_problems.LinearProblem(geom))\n", + "out = sinkhorn.Sinkhorn()(linear_problem.LinearProblem(geom))\n", "dual_potentials = out.to_dual_potentials()" ] }, @@ -217087,7 +217095,7 @@ "outputs": [], "source": [ "geom = pointcloud.PointCloud(x, y, cost_fn=costs.SqPNorm(p=1.1))\n", - "out = sinkhorn.Sinkhorn()(linear_problems.LinearProblem(geom))\n", + "out = sinkhorn.Sinkhorn()(linear_problem.LinearProblem(geom))\n", "dual_potentials = out.to_dual_potentials()" ] }, @@ -217144,7 +217152,7 @@ "provenance": [] }, "kernelspec": { - "display_name": "Python 3.9.15 64-bit", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -217158,7 +217166,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.15" + "version": "3.10.6" }, "vscode": { "interpreter": { @@ -217167,5 +217175,5 @@ } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } diff --git a/docs/notebooks/soft_sort.ipynb b/docs/notebooks/soft_sort.ipynb index 69a801f3d..244cf2976 100644 --- a/docs/notebooks/soft_sort.ipynb +++ b/docs/notebooks/soft_sort.ipynb @@ -760,7 +760,7 @@ "toc_visible": true }, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -774,7 +774,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.12" + "version": "3.10.6" } }, "nbformat": 4, diff --git a/docs/notebooks/wasserstein_barycenters_gmms.ipynb b/docs/notebooks/wasserstein_barycenters_gmms.ipynb index 547f7a720..1703efe78 100644 --- a/docs/notebooks/wasserstein_barycenters_gmms.ipynb +++ b/docs/notebooks/wasserstein_barycenters_gmms.ipynb @@ -52,9 +52,10 @@ "metadata": {}, "outputs": [], "source": [ + "from ott.geometry import costs\n", "from ott.tools.gaussian_mixture import gaussian_mixture\n", - "from ott.core import bar_problems, continuous_barycenter\n", - "from ott.geometry import costs" + "from ott.problems.linear import barycenter_problem\n", + "from ott.solvers.linear import continuous_barycenter" ] }, { @@ -406,7 +407,7 @@ "outputs": [], "source": [ "# create a barycenter problem.\n", - "bar_p = bar_problems.BarycenterProblem(\n", + "bar_p = barycenter_problem.BarycenterProblem(\n", " y=ys,\n", " b=bs,\n", " weights=barycentric_weights,\n", @@ -606,9 +607,9 @@ ], "metadata": { "kernelspec": { - "display_name": "ott", + "display_name": "Python 3 (ipykernel)", "language": "python", - "name": "ott" + "name": "python3" }, "language_info": { "codemirror_mode": { @@ -620,7 +621,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.12" + "version": "3.10.6" } }, "nbformat": 4, From f213453032c356d851f705ac323d1f459b3ed336 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Fri, 18 Nov 2022 10:48:00 +0100 Subject: [PATCH 11/34] Update geometry docs --- ott/geometry/costs.py | 114 +++++++++++++++------- ott/geometry/epsilon_scheduler.py | 1 + ott/geometry/geometry.py | 6 +- ott/problems/linear/barycenter_problem.py | 2 +- ott/problems/quadratic/gw_barycenter.py | 2 +- ott/tools/segment_sinkhorn.py | 4 +- ott/tools/sinkhorn_divergence.py | 4 +- tests/core/continuous_barycenter_test.py | 2 +- 8 files changed, 89 insertions(+), 46 deletions(-) diff --git a/ott/geometry/costs.py b/ott/geometry/costs.py index 084673300..5b11005d3 100644 --- a/ott/geometry/costs.py +++ b/ott/geometry/costs.py @@ -52,11 +52,29 @@ class CostFn(abc.ABC): def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> float: pass + # TODO(michalk8): make weights optional? def barycenter(self, weights: jnp.ndarray, xs: jnp.ndarray) -> float: - raise NotImplementedError("Barycenter not yet implemented for this cost.") + """Barycentric projection. + + Args: + weights: TODO. + xs: TODO. + + Returns: + TODO. + """ + raise NotImplementedError("Barycenter is not yet implemented.") @classmethod - def padder(cls, dim: int) -> jnp.ndarray: + def _padder(cls, dim: int) -> jnp.ndarray: + """TODO. + + Args: + dim: TODO. + + Returns: + TODO. + """ return jnp.zeros((1, dim)) def __call__(self, x: jnp.ndarray, y: jnp.ndarray) -> float: @@ -104,6 +122,7 @@ class TICost(CostFn): real-values, to be used as: .. math:: + c(x,y) = h(z), z := x-y. If that cost function is used to form an Entropic map using the @@ -117,7 +136,7 @@ def h(self, z: jnp.ndarray) -> float: """TI function acting on difference of :math:`x-y` to output cost.""" def h_legendre(self, z: jnp.ndarray) -> float: - """Legendre transform of TI function :func:`h` (when latter is convex).""" + """Legendre transform of :func:`h` when it is convex.""" raise NotImplementedError("`h_legendre` not implemented.") def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> float: @@ -131,6 +150,9 @@ class SqPNorm(TICost): For details on the derivation of the Legendre transform of the norm, see e.g. the reference :cite:`boyd:04`, p.93/94. + + Args: + p: TODO. """ def __init__(self, p: float): @@ -156,7 +178,11 @@ def tree_unflatten(cls, aux_data, children): @jax.tree_util.register_pytree_node_class class PNorm(TICost): - """p-norm (to the power p) of the difference of two vectors.""" + """p-norm (to the power p) of the difference of two vectors. + + Args: + p: TODO. + """ def __init__(self, p: float): super().__init__() @@ -219,7 +245,11 @@ def barycenter(self, weights: jnp.ndarray, xs: jnp.ndarray) -> jnp.ndarray: @jax.tree_util.register_pytree_node_class class Cosine(CostFn): - """Cosine distance CostFn.""" + """Cosine distance cost function. + + Args: + ridge: TODO. + """ def __init__(self, ridge: float = 1e-8): super().__init__() @@ -236,27 +266,32 @@ def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> float: return jnp.clip(cosine_distance, 0., 2.) @classmethod - def padder(cls, dim: int) -> jnp.ndarray: + def _padder(cls, dim: int) -> jnp.ndarray: return jnp.ones((1, dim)) @jax.tree_util.register_pytree_node_class class Bures(CostFn): - """Bures distance between a pair of (mean, cov matrix) raveled as vectors.""" + """Bures distance between a pair of (mean, cov matrix) raveled as vectors. + + Args: + dimension: Dimensionality of the data. + kwargs: Keyword arguments for :func:`ott.math.matrix_square_root.sqrtm`. + """ def __init__(self, dimension: int, **kwargs: Any): super().__init__() self._dimension = dimension self._sqrtm_kw = kwargs - def norm(self, x: jnp.ndarray): + def norm(self, x: jnp.ndarray) -> jnp.ndarray: """Compute norm of Gaussian, sq. 2-norm of mean + trace of covariance.""" mean, cov = x_to_means_and_covs(x, self._dimension) norm = jnp.sum(mean ** 2, axis=-1) norm += jnp.trace(cov, axis1=-2, axis2=-1) return norm - def pairwise(self, x: jnp.ndarray, y: jnp.ndarray): + def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray: """Compute - 2 x Bures dot-product.""" mean_x, cov_x = x_to_means_and_covs(x, self._dimension) mean_y, cov_y = x_to_means_and_covs(y, self._dimension) @@ -268,17 +303,6 @@ def pairwise(self, x: jnp.ndarray, y: jnp.ndarray): )[0] return -2 * (mean_dot_prod + jnp.trace(sq__sq_x_y_sq_x, axis1=-2, axis2=-1)) - @functools.partial(jax.vmap, in_axes=[None, None, 0, 0]) - def scale_covariances(self, cov_sqrt, cov_i, lambda_i): - """Iterate update needed to compute barycenter of covariances.""" - return lambda_i * matrix_square_root.sqrtm_only( - jnp.matmul(jnp.matmul(cov_sqrt, cov_i), cov_sqrt) - ) - - def relative_diff(self, x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray: - """Monitor change in two successive estimates of matrices.""" - return jnp.sum(jnp.square(x - y)) / jnp.prod(jnp.array(x.shape)) - def covariance_fixpoint_iter( self, covs: jnp.ndarray, @@ -287,27 +311,40 @@ def covariance_fixpoint_iter( ) -> jnp.ndarray: """Iterate fix-point updates to compute barycenter of Gaussians.""" - def cond_fn(iteration, constants, state): + @functools.partial(jax.vmap, in_axes=[None, 0, 0]) + def scale_covariances( + cov_sqrt: jnp.ndarray, cov_i: jnp.ndarray, lambda_i: jnp.ndarray + ) -> jnp.ndarray: + """Iterate update needed to compute barycenter of covariances.""" + return lambda_i * matrix_square_root.sqrtm_only( + (cov_sqrt @ cov_i) @ cov_sqrt + ) + + def cond_fn(iteration: int, constants: Tuple[...], state) -> bool: + del iteration, constants _, diff = state - return diff > jnp.array(rtol) + return diff > rtol - def body_fn(iteration, constants, state, compute_error): - del compute_error + def body_fn( + iteration: int, constants: Tuple[...], state: Tuple[jnp.ndarray, float], + compute_error: bool + ) -> Tuple[jnp.ndarray, float]: + del iteration, constants, compute_error cov, _ = state cov_sqrt, cov_inv_sqrt, _ = matrix_square_root.sqrtm(cov) scaled_cov = jnp.linalg.matrix_power( jnp.sum(self.scale_covariances(cov_sqrt, covs, lambdas), axis=0), 2 ) - next_cov = jnp.matmul(jnp.matmul(cov_inv_sqrt, scaled_cov), cov_inv_sqrt) - diff = self.relative_diff(next_cov, cov) + next_cov = (cov_inv_sqrt @ scaled_cov) @ cov_inv_sqrt + diff = jnp.sum((next_cov - cov) ** 2) / jnp.prod(jnp.array(cov.shape)) return next_cov, diff - def init_state(): + def init_state() -> Tuple[jnp.ndarray, float]: cov_init = jnp.eye(self._dimension) diff = jnp.inf return cov_init, diff - state = fixed_point_loop.fixpoint_iter( + cov, _ = fixed_point_loop.fixpoint_iter( cond_fn=cond_fn, body_fn=body_fn, min_iterations=10, @@ -316,8 +353,6 @@ def init_state(): constants=(), state=init_state() ) - - cov, _ = state return cov def barycenter(self, weights: jnp.ndarray, xs: jnp.ndarray) -> jnp.ndarray: @@ -345,7 +380,7 @@ def barycenter(self, weights: jnp.ndarray, xs: jnp.ndarray) -> jnp.ndarray: return barycenter @classmethod - def padder(cls, dim: int) -> jnp.ndarray: + def _padder(cls, dim: int) -> jnp.ndarray: """Pad with concatenated zero means and \ raveled identity covariance matrix.""" dimension = int((-1 + math.sqrt(1 + 4 * dim)) / 2) @@ -370,6 +405,12 @@ class UnbalancedBures(CostFn): This cost implements the value defined in :cite:`janati:20`, eq. 37, 39, 40. We follow their notations. It is assumed inputs are given as triplets (mass, mean, covariance) raveled as vectors, in that order. + + Args: + dimension: TODO. + gamma: TODO. + sigma: TODO. + kwargs: TODO. """ def __init__( @@ -382,10 +423,10 @@ def __init__( super().__init__() self._dimension = dimension self._gamma = gamma - self._sigma2 = sigma ** 2 + self._sigma = sigma self._sqrtm_kw = kwargs - def norm(self, x: jnp.ndarray) -> Union[float, jnp.ndarray]: + def norm(self, x: jnp.ndarray) -> jnp.ndarray: """Compute norm of Gaussian for unbalanced Bures.""" return self._gamma * x[0] @@ -393,7 +434,7 @@ def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> float: """Compute dot-product for unbalanced Bures.""" # Sets a few constants gam = self._gamma - sig2 = self._sigma2 + sig2 = self._sigma ** 2 lam = sig2 + gam / 2 tau = gam / (2 * lam) @@ -446,12 +487,13 @@ def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> float: ) def tree_flatten(self): - return (), (self._dimension, self._gamma, self._sigma2, self._sqrtm_kw) + return (), (self._dimension, self._gamma, self._sigma, self._sqrtm_kw) @classmethod def tree_unflatten(cls, aux_data, children): del children - return cls(aux_data[0], aux_data[1], aux_data[2], **aux_data[3]) + dim, gamma, sigma, kwargs = aux_data + return cls(dim, gamma=gamma, sigma=sigma, **kwargs) def x_to_means_and_covs(x: jnp.ndarray, diff --git a/ott/geometry/epsilon_scheduler.py b/ott/geometry/epsilon_scheduler.py index 8f5666385..284fa29f7 100644 --- a/ott/geometry/epsilon_scheduler.py +++ b/ott/geometry/epsilon_scheduler.py @@ -44,6 +44,7 @@ class Epsilon: decay: geometric decay factor, smaller than 1. """ + # TODO(michalk8): directly use the defaults instead of `None` def __init__( self, target: Optional[float] = None, diff --git a/ott/geometry/geometry.py b/ott/geometry/geometry.py index 3550c881b..f33a8225c 100644 --- a/ott/geometry/geometry.py +++ b/ott/geometry/geometry.py @@ -28,7 +28,7 @@ from ott.geometry import epsilon_scheduler from ott.math import utils -__all__ = ["Geometry"] +__all__ = ["Geometry", "is_linear", "is_affine"] @jax.tree_util.register_pytree_node_class @@ -37,7 +37,7 @@ class Geometry: Optimal transport problems are intrinsically geometric: they compute an optimal way to transport mass from one configuration onto another. To define - what is meant by optimality of a transport requires defining a cost, of moving + what is meant by optimality of transport requires defining a cost, of moving mass from one among several sources, towards one out of multiple targets. These sources and targets can be provided as points in vectors spaces, grids, or more generally exclusively described through a (dissimilarity) cost matrix, @@ -900,4 +900,4 @@ def is_affine(fn) -> bool: def is_linear(fn) -> bool: """Test heuristically if a function is linear.""" - return fn(0.0) == 0.0 and is_affine(fn) + return jnp.logical_and(fn(0.0) == 0.0, is_affine(fn)) diff --git a/ott/problems/linear/barycenter_problem.py b/ott/problems/linear/barycenter_problem.py index 7a6e63129..943292bc2 100644 --- a/ott/problems/linear/barycenter_problem.py +++ b/ott/problems/linear/barycenter_problem.py @@ -101,7 +101,7 @@ def segmented_y_b(self) -> Tuple[jnp.ndarray, jnp.ndarray]: y, b = segment.segment_point_cloud( x=self._y, a=self._b, - padding_vector=self.cost_fn.padder(self.ndim), + padding_vector=self.cost_fn._padder(self.ndim), **self._kwargs ) diff --git a/ott/problems/quadratic/gw_barycenter.py b/ott/problems/quadratic/gw_barycenter.py index a052e6224..be87ec098 100644 --- a/ott/problems/quadratic/gw_barycenter.py +++ b/ott/problems/quadratic/gw_barycenter.py @@ -263,7 +263,7 @@ def segmented_y_fused(self) -> Optional[jnp.ndarray]: return self._y_fused y_fused, _ = segment.segment_point_cloud( x=self._y_fused, - padding_vector=self.cost_fn.padder(self.ndim_fused), + padding_vector=self.cost_fn._padder(self.ndim_fused), **self._kwargs ) return y_fused diff --git a/ott/tools/segment_sinkhorn.py b/ott/tools/segment_sinkhorn.py index fca0a4226..3b149c0f5 100644 --- a/ott/tools/segment_sinkhorn.py +++ b/ott/tools/segment_sinkhorn.py @@ -98,9 +98,9 @@ def segment_sinkhorn( dim = x.shape[1] if cost_fn is None: # default padder - padding_vector = costs.CostFn.padder(dim=dim) + padding_vector = costs.CostFn._padder(dim=dim) else: - padding_vector = cost_fn.padder(dim=dim) + padding_vector = cost_fn._padder(dim=dim) def eval_fn( padded_x: jnp.ndarray, diff --git a/ott/tools/sinkhorn_divergence.py b/ott/tools/sinkhorn_divergence.py index 2b8404439..baf5688c3 100644 --- a/ott/tools/sinkhorn_divergence.py +++ b/ott/tools/sinkhorn_divergence.py @@ -270,9 +270,9 @@ def segment_sinkhorn_divergence( dim = x.shape[1] if cost_fn is None: # default padder - padding_vector = costs.CostFn.padder(dim=dim) + padding_vector = costs.CostFn._padder(dim=dim) else: - padding_vector = cost_fn.padder(dim=dim) + padding_vector = cost_fn._padder(dim=dim) def eval_fn( padded_x: jnp.ndarray, diff --git a/tests/core/continuous_barycenter_test.py b/tests/core/continuous_barycenter_test.py index 2a0efa9d1..2578a459c 100644 --- a/tests/core/continuous_barycenter_test.py +++ b/tests/core/continuous_barycenter_test.py @@ -221,7 +221,7 @@ def test_bures_barycenter( num_segments=num_measures, max_measure_size=num_components, num_per_segment=(num_components, num_components), - padding_vector=bures_cost.padder(y.shape[1]), + padding_vector=bures_cost._padder(y.shape[1]), ) bar_p = barycenter_problem.BarycenterProblem( seg_y, From 4b560d242f8f111a5c8fac7fb840f2ea9447abad Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Fri, 18 Nov 2022 10:55:29 +0100 Subject: [PATCH 12/34] Update initializers --- ott/initializers/linear/initializers_lr.py | 6 ++++-- ott/initializers/nn/initializers.py | 4 ++-- ott/initializers/quadratic/initializers.py | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/ott/initializers/linear/initializers_lr.py b/ott/initializers/linear/initializers_lr.py index 6059a17bb..3eb4670ee 100644 --- a/ott/initializers/linear/initializers_lr.py +++ b/ott/initializers/linear/initializers_lr.py @@ -342,7 +342,8 @@ class KMeansInitializer(LRInitializer): rank: Rank of the factorization. min_iterations: Minimum number of k-means iterations. max_iterations: Maximum number of k-means iterations. - sinkhorn_kwargs: Keyword arguments for :class:`~ott.core.sinkhorn.Sinkhorn`. + sinkhorn_kwargs: Keyword arguments for + :class:`~ott.solvers.linear.sinkhorn.Sinkhorn`. kwargs: Keyword arguments for :func:`~ott.tools.k_means.k_means`. """ @@ -466,7 +467,8 @@ class GeneralizedKMeansInitializer(KMeansInitializer): inner_iterations: Number of iterations used by the algorithm before re-evaluating progress. threshold: Convergence threshold. - sinkhorn_kwargs: Keyword arguments for :class:`~ott.core.sinkhorn.Sinkhorn`. + sinkhorn_kwargs: Keyword arguments for + :class:`~ott.solvers.linear.sinkhorn.Sinkhorn`. """ def __init__( diff --git a/ott/initializers/nn/initializers.py b/ott/initializers/nn/initializers.py index b10a58dac..0f6ce2e07 100644 --- a/ott/initializers/nn/initializers.py +++ b/ott/initializers/nn/initializers.py @@ -30,7 +30,7 @@ class MetaInitializer(initializers.DefaultInitializer): instances (multiple pairs of probability weights), that assume the **same** geometry ``geom`` is used throughout, both for training and evaluation. The meta model defaults to the MLP in - :class:`~ott.core.initializers.MetaMLP` and, with batched problem + :class:`~ott.initializers.nn.initializers.MetaMLP` and, with batched problem instances passed into :meth:`update`. **Sample training usage.** The following code shows a simple @@ -192,7 +192,7 @@ def tree_flatten(self) -> Tuple[Sequence[Any], Dict[str, Any]]: class MetaMLP(nn.Module): - r"""A Meta MLP potential for :class:`~ott.core.initializers.MetaInitializer`. + r"""Potential for :class:`~ott.initializers.nn.initializers.MetaInitializer`. This provides an MLP :math:`\hat f_\theta(a, b)` that maps from the probabilities of the measures to the optimal dual potentials :math:`f`. diff --git a/ott/initializers/quadratic/initializers.py b/ott/initializers/quadratic/initializers.py index 0c97f83a5..2b09306d9 100644 --- a/ott/initializers/quadratic/initializers.py +++ b/ott/initializers/quadratic/initializers.py @@ -172,7 +172,7 @@ def _create_geometry( Args: quad_prob: Quadratic OT problem. kwargs: Keyword arguments for - :meth:`ott.core.initializers_lr.LRInitializer.__call__`. + :meth:`~ott.initializers.linear.initializers_lr.LRInitializer.__call__`. Returns: The initial geometry used to initialize a linear problem. From 75602d290f1283a17ae09cebbc8954341b13f892 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Fri, 18 Nov 2022 10:57:28 +0100 Subject: [PATCH 13/34] Update math docs --- ott/math/decomposition.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ott/math/decomposition.py b/ott/math/decomposition.py index cbdf61a8b..a05ee1b23 100644 --- a/ott/math/decomposition.py +++ b/ott/math/decomposition.py @@ -65,12 +65,12 @@ def _solve(self, L: Optional[T], b: jnp.ndarray) -> jnp.ndarray: def create(cls, A: Union[T, sp.spmatrix], **kwargs: Any) -> "CholeskySolver": """Instantiate sparse or dense Cholesky solver. - Optionally converts :class:`scipy.sparse.spmatrix` to its + And optionally convert :class:`scipy.sparse.spmatrix` to its :mod:`jax` equivalent. Args: A: Symmetric positive definite matrix of shape ``[n, n]``. - kwargs: Keyword arguments for the initialization. + kwargs: Keyword arguments for the solver initialization. Returns: Sparse or dense Cholesky solver. From 7dcbea69367abcfd3c3c9e5795abf3b22b296106 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Fri, 18 Nov 2022 11:11:50 +0100 Subject: [PATCH 14/34] Update problem docstrings --- ott/problems/linear/barycenter_problem.py | 8 ++++---- ott/problems/linear/potentials.py | 2 +- ott/problems/quadratic/gw_barycenter.py | 9 +++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/ott/problems/linear/barycenter_problem.py b/ott/problems/linear/barycenter_problem.py index 943292bc2..ac30a2b01 100644 --- a/ott/problems/linear/barycenter_problem.py +++ b/ott/problems/linear/barycenter_problem.py @@ -30,16 +30,16 @@ class BarycenterProblem: y: Array of shape ``[num_total_points, ndim]`` merging the points of all measures. Alternatively, already segmented array of shape ``[num_measures, max_measure_size, ndim]`` can be passed. - See also :func:`~ott.core.segment.segment_point_cloud`. + See also :func:`~ott.geometry.segment.segment_point_cloud`. b: Array of shape ``[num_total_points,]`` containing the weights of all the points within the measures that define the barycenter problem. - Similarly as ``y``, segmented array of weights of shape + Same as ``y``, pre-segmented array of weights of shape ``[num_measures, max_measure_size]`` can be passed. If ``y`` is already pre-segmented, this array must be always specified. weights: Array of shape ``[num_measures,]`` containing the weights of the measures. cost_fn: Cost function used. If `None`, - use :class:`~ott.geometry.costs.SqEuclidean` cost. + use the :class:`~ott.geometry.costs.SqEuclidean` cost. epsilon: Epsilon regularization used to solve reg-OT problems. debiased: **Currently not implemented.** Whether the problem is debiased, in the sense that @@ -49,7 +49,7 @@ class BarycenterProblem: :meth:`~ott.core.continuous_barycenter.WassersteinBarycenter.init_state` needs to be smaller than the maximum measure size for parallelization to operate efficiently. - kwargs: Keyword arguments :func:`~ott.core.segment.segment_point_cloud`. + kwargs: Keyword arguments :func:`~ott.geometry.segment.segment_point_cloud`. Only used when ``y`` is not already segmented. When passing ``segment_ids``, 2 arguments must be specified for jitting to work: diff --git a/ott/problems/linear/potentials.py b/ott/problems/linear/potentials.py index cf2914c67..dde7366d6 100644 --- a/ott/problems/linear/potentials.py +++ b/ott/problems/linear/potentials.py @@ -154,7 +154,7 @@ class EntropicPotentials(DualPotentials): f: The first dual potential vector of shape ``[n,]``. g: The second dual potential vector of shape ``[m,]``. geom: Geometry used to compute the dual potentials using - :class:`~ott.core.sinkhorn.Sinkhorn`. + :class:`~ott.solvers.linear.sinkhorn.Sinkhorn`. a: Probability weights for the first measure. If `None`, use uniform. b: Probability weights for the second measure. If `None`, use uniform. """ diff --git a/ott/problems/quadratic/gw_barycenter.py b/ott/problems/quadratic/gw_barycenter.py index be87ec098..9d582ce1c 100644 --- a/ott/problems/quadratic/gw_barycenter.py +++ b/ott/problems/quadratic/gw_barycenter.py @@ -13,6 +13,7 @@ __all__ = ["GWBarycenterProblem"] +# TODO(michalk8): better abstraction (common superclass for Wasserstein bary) @jax.tree_util.register_pytree_node_class class GWBarycenterProblem(barycenter_problem.BarycenterProblem): """(Fused) Gromov-Wasserstein barycenter problem :cite:`peyre:16,vayer:19`. @@ -21,10 +22,10 @@ class GWBarycenterProblem(barycenter_problem.BarycenterProblem): y: Array of shape ``[num_total_points, ndim]`` merging the points of all measures. Alternatively, already segmented array of shape ``[num_measures, max_measure_size, ndim]`` can be passed. - See also :func:`~ott.core.segment.segment_point_cloud`. + See also :func:`~ott.geometry.segment.segment_point_cloud`. b: Array of shape ``[num_total_points,]`` containing the weights of all the points within the measures that define the barycenter problem. - Similarly, as ``y``, segmented array of weights of shape + Same as ``y``, pre-segmented array of weights of shape ``[num_measures, max_measure_size]`` can be passed. If ``y`` is already pre-segmented, this array must be passed. weights: Array of shape ``[num_measures,]`` containing the weights of the @@ -42,7 +43,7 @@ class GWBarycenterProblem(barycenter_problem.BarycenterProblem): ``y_fused != None``. scale_cost: Scaling of cost matrices passed to geometries. kwargs: Keyword arguments for - :class:`~ott.core.bar_problems.BarycenterProblem`. + :class:`~ott.problems.linear.barycenter_problem.BarycenterProblem`. """ def __init__( @@ -138,7 +139,7 @@ def update_features(self, transports: jnp.ndarray, """Update the barycenter features in the fused case :cite:`vayer:19`. Uses :cite:`cuturi:14` eq. 8, and is implemented only - for the squared :class:`~ott.geometry.costs.SqEuclidean` cost. + for the :class:`~ott.geometry.costs.SqEuclidean` cost. Args: transports: Transport maps of shape From 3365f247f135bd6d219efab2d3b7ac3ce89860bc Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Fri, 18 Nov 2022 12:27:19 +0100 Subject: [PATCH 15/34] Update `solvers` docstrings --- ott/geometry/geometry.py | 4 +-- ott/geometry/low_rank.py | 13 +++---- ott/solvers/linear/sinkhorn.py | 3 +- ott/solvers/linear/sinkhorn_lr.py | 38 ++++++++++---------- ott/solvers/quadratic/gromov_wasserstein.py | 40 ++++++++++----------- ott/solvers/quadratic/gw_barycenter.py | 13 +++---- 6 files changed, 56 insertions(+), 55 deletions(-) diff --git a/ott/geometry/geometry.py b/ott/geometry/geometry.py index f33a8225c..d76282e51 100644 --- a/ott/geometry/geometry.py +++ b/ott/geometry/geometry.py @@ -65,11 +65,11 @@ class Geometry: 'median', 'mean' and 'max_cost'. Alternatively, a float factor can be given to rescale the cost such that ``cost_matrix /= scale_cost``. If `True`, use 'mean'. - tgt_mask: Mask specifying valid rows when computing some statistics of + src_mask: Mask specifying valid rows when computing some statistics of :attr:`cost_matrix`, see :attr:`src_mask`. tgt_mask: Mask specifying valid columns when computing some statistics of :attr:`cost_matrix`, see :attr:`tgt_mask`. - kwargs: additional kwargs to epsilon scheduler. + kwargs: additional kwargs for epsilon scheduler. Note: When defining a ``Geometry`` through a ``cost_matrix``, it is important to diff --git a/ott/geometry/low_rank.py b/ott/geometry/low_rank.py index 1fc748059..eeffa0733 100644 --- a/ott/geometry/low_rank.py +++ b/ott/geometry/low_rank.py @@ -47,10 +47,11 @@ class LRCGeometry(geometry.Geometry): ``cost_matrix /= scale_cost``. If `True`, use 'mean'. batch_size: optional size of the batch to compute online (without instantiating the matrix) the scale factor ``scale_cost`` of the - ``cost_matrix`` when ``scale_cost='max_cost'``. If set to ``None``, the - batch size is set to 1024 or to the largest number of samples between - ``cost_1`` and ``cost_2`` if smaller than `1024`. - kwargs: Additional kwargs to :class:`~ott.geometry.geometry.Geometry`. + :attr:`cost_matrix` when ``scale_cost = 'max_cost'``. If `None`, the batch + size is set to `1024` or to the largest number of samples between + :attr:`cost_1` and :attr:`cost_2` if smaller than `1024`. + kwargs: Additional keyword arguments for + :class:`~ott.geometry.geometry.Geometry`. """ def __init__( @@ -189,10 +190,10 @@ def compute_max_cost(self) -> float: Three cases are taken into account: - If the number of samples of ``cost_1`` and ``cost_2`` are both smaller - than 1024 and if ``batch_size`` is ``None``, the ``cost_matrix`` is + than 1024 and if ``batch_size`` is `None`, the ``cost_matrix`` is computed to obtain its maximum entry. - If one of the number of samples of ``cost_1`` or ``cost_2`` is larger - than 1024 and if ``batch_size`` is ``None``, then the maximum of the + than 1024 and if ``batch_size`` is `None`, then the maximum of the cost matrix is calculated by batch. The batches are created on the longest axis of the cost matrix and their size is fixed to 1024. - If ``batch_size`` is provided as a float, then the maximum of the cost diff --git a/ott/solvers/linear/sinkhorn.py b/ott/solvers/linear/sinkhorn.py index 9f824e142..624a2a115 100644 --- a/ott/solvers/linear/sinkhorn.py +++ b/ott/solvers/linear/sinkhorn.py @@ -305,7 +305,8 @@ class Sinkhorn: A Sinkhorn solver takes a linear OT problem object as an input and returns a SinkhornOutput object that contains all the information required to compute - transports. See :func:`~ott.core.sinkhorn.sinkhorn` for a functional wrapper. + transports. See :func:`~ott.solvers.linear.sinkhorn.sinkhorn` + for a functional wrapper. Args: lse_mode: ``True`` for log-sum-exp computations, ``False`` for kernel diff --git a/ott/solvers/linear/sinkhorn_lr.py b/ott/solvers/linear/sinkhorn_lr.py index 62364ef14..5f4dbd7ec 100644 --- a/ott/solvers/linear/sinkhorn_lr.py +++ b/ott/solvers/linear/sinkhorn_lr.py @@ -217,38 +217,38 @@ class LRSinkhorn(sinkhorn.Sinkhorn): case. Args: - rank: the rank constraint on the coupling to minimize the linear OT problem - gamma: the (inverse of) gradient step size used by mirror descent. + rank: The rank constraint on the coupling to minimize the linear OT problem + gamma: The (inverse of) gradient step size used by mirror descent. gamma_rescale: Whether to rescale :math:`\gamma` every iteration as described in :cite:`scetbon:22b`. - epsilon: entropic regularization added on top of low-rank problem. + epsilon: Entropic regularization added on top of low-rank problem. initializer: How to initialize the :math:`Q`, :math:`R` and :math:`g` factors. Valid options are: - - `'random'` - :class:`~ott.core.initializers_lr.RandomInitializer`. - - `'rank2'` - :class:`~ott.core.initializers_lr.Rank2Initializer`. - - `'k-means'` - :class:`~ott.core.initializers_lr.KMeansInitializer`. - - `'generalized-k-means'` - - :class:`~ott.core.initializers_lr.GeneralizedKMeansInitializer`. + - `'random'` - :class:`~ott.initializers.linear.initializers_lr.RandomInitializer`. + - `'rank2'` - :class:`~ott.initializers.linear.initializers_lr.Rank2Initializer`. + - `'k-means'` - :class:`~ott.initializers.linear.initializers_lr.KMeansInitializer`. + - `'generalized-k-means'` - :class:`~ott.initializers.linear.initializers_lr.GeneralizedKMeansInitializer`. - If `None`, :class:`~ott.core.initializers_lr.KMeansInitializer` + If `None`, :class:`~ott.initializers.linear.initializers_lr.KMeansInitializer` is used when the linear problem's geometry is :class:`~ott.geometry.pointcloud.PointCloud` or :class:`~ott.geometry.low_rank.LRCGeometry`. - Otherwise, use :class:`~ott.core.initializers_lr.RandomInitializer`. + Otherwise, use :class:`~ott.initializers.linear.initializers_lr.RandomInitializer`. - lse_mode: whether to run computations in lse or kernel mode. At the moment, + lse_mode: Whether to run computations in lse or kernel mode. At the moment, only ``lse_mode = True`` is implemented. - inner_iterations: number of inner iterations used by the algorithm before + inner_iterations: Number of inner iterations used by the algorithm before re-evaluating progress. - use_danskin: use Danskin theorem to evaluate gradient of objective w.r.t. + use_danskin: Use Danskin theorem to evaluate gradient of objective w.r.t. input parameters. Only `True` handled at this moment. implicit_diff: Whether to use implicit differentiation. Currently, only ``implicit_diff = False`` is implemented. - kwargs_dys: keyword arguments passed to :meth:`dykstra_update`. - kwargs_init: keyword arguments for - :class:`~ott.core.initializers_lr.LRInitializer`. - kwargs: Keyword arguments for :class:`~ott.core.sinkhorn.Sinkhorn`. + kwargs_dys: Keyword arguments passed to :meth:`dykstra_update`. + kwargs_init: Keyword arguments for + :class:`~ott.initializers.linear.initializers_lr.LRInitializer`. + kwargs: Keyword arguments for + :class:`~ott.solvers.linear.sinkhorn.Sinkhorn`. """ def __init__( @@ -268,8 +268,8 @@ def __init__( kwargs_init: Optional[Mapping[str, Any]] = None, **kwargs: Any, ): - assert lse_mode, "Kernel mode not yet implemented for LRSinkhorn." - assert not implicit_diff, "Implicit diff. not yet implemented for LRSink." + assert lse_mode, "Kernel mode not yet implemented." + assert not implicit_diff, "Implicit diff. not yet implemented." super().__init__( lse_mode=lse_mode, inner_iterations=inner_iterations, diff --git a/ott/solvers/quadratic/gromov_wasserstein.py b/ott/solvers/quadratic/gromov_wasserstein.py index 05a427a74..3468147e3 100644 --- a/ott/solvers/quadratic/gromov_wasserstein.py +++ b/ott/solvers/quadratic/gromov_wasserstein.py @@ -144,33 +144,33 @@ class GromovWasserstein(was_solver.WassersteinSolver): Args: args: Positional_arguments for - :class:`~ott.core.was_solver.WassersteinSolver`. + :class:`~ott.solvers.was_solver.WassersteinSolver`. warm_start: Whether to initialize (low-rank) Sinkhorn calls using values from the previous iteration. If `None`, warm starts are not used for standard Sinkhorn, but used for low-rank Sinkhorn. quad_initializer: Quadratic initializer. If the solver is entropic, - :class:`~ott.core.quad_initializers.QuadraticInitializer` is always used. - Otherwise, the quadratic initializer wraps low-rank Sinkhorn initializers: + :class:`~ott.initializers.quadratic.initializers.QuadraticInitializer` + is always used. Otherwise, the quadratic initializer wraps the low-rank + Sinkhorn initializers: - - `'random'` - :class:`~ott.core.initializers_lr.RandomInitializer`. - - `'rank2'` - :class:`~ott.core.initializers_lr.Rank2Initializer`. - - `'k-means'` - :class:`~ott.core.initializers_lr.KMeansInitializer`. - - `'generalized-k-means'` - - :class:`~ott.core.initializers_lr.GeneralizedKMeansInitializer`. + - `'random'` - :class:`~ott.initializers.linear.initializers_lr.RandomInitializer`. + - `'rank2'` - :class:`~ott.initializers.linear.initializers_lr.Rank2Initializer`. + - `'k-means'` - :class:`~ott.initializers.linear.initializers_lr.KMeansInitializer`. + - `'generalized-k-means'` - :class:`~ott.initializers.linear.initializers_lr.GeneralizedKMeansInitializer`. If `None`, the low-rank initializer will be selected in a problem-specific manner: - - if both :attr:`~ott.core.quad_problems.QuadraticProblem.geom_xx` and - :attr:`~ott.core.quad_problems.QuadraticProblem.geom_yy` are - :class:`~ott.geometry.pointcloud.PointCloud` or - :class:`~ott.geometry.low_rank.LRCGeometry`, - :class:`~ott.core.initializers_lr.KMeansInitializer` is used. - - otherwise, use :class:`~ott.core.initializers_lr.RandomInitializer`. + - if both :attr:`~ott.problems.quadratic.quadratic_problem.QuadraticProblem.geom_xx` + and :attr:`~ott.problems.quadratic.quadratic_problem.QuadraticProblem.geom_yy` + are :class:`~ott.geometry.pointcloud.PointCloud` or :class:`~ott.geometry.low_rank.LRCGeometry`, + :class:`~ott.initializers.linear.initializers_lr.KMeansInitializer` + is used. + - otherwise, use :class:`~ott.initializers.linear.initializers_lr.RandomInitializer`. kwargs_init: Keyword arguments when creating the initializer. kwargs: Keyword arguments for - :class:`~ott.core.was_solver.WassersteinSolver`. + :class:`~ott.solvers.was_solver.WassersteinSolver`. """ def __init__( @@ -493,16 +493,14 @@ def gromov_wasserstein( - if `True`, use the default for each geometry. - if `False`, keep the original scaling in geometries. - if :class:`str`, use a specific method available in - :class:`ott.geometry.geometry.Geometry` or - :class:`ott.geometry.pointcloud.PointCloud`. + :class:`~ott.geometry.geometry.Geometry` or + :class:`~ott.geometry.pointcloud.PointCloud`. - if `None`, do not scale the cost matrices. a: jnp.ndarray[num_a,] or jnp.ndarray[batch,num_a] weights. b: jnp.ndarray[num_b,] or jnp.ndarray[batch,num_b] weights. loss: defaults to the square Euclidean distance. Can also pass 'kl' to define the GW loss as KL loss. - See :class:`~ott.core.gromov_wasserstein.GromovWasserstein` on how to pass - custom loss. tau_a: float between 0 and 1.0, parameter that controls the strength of the KL divergence constraint between the weights and marginals of the transport for the first view. If set to 1.0, then it is equivalent to a @@ -528,8 +526,8 @@ def gromov_wasserstein( geometries are *not* :class:`~ott.geometry.pointcloud.PointCloud` with `'sqeucl'` cost. If :class:`float`, that tolerance is shared across all 3 geometries. - kwargs: Keyword arguments to - :class:`~ott.core.gromov_wasserstein.GromovWasserstein`. + kwargs: Keyword arguments for + :class:`~ott.solvers.quadratic.gromov_wasserstein.GromovWasserstein`. Returns: A GromovWassersteinState named tuple. diff --git a/ott/solvers/quadratic/gw_barycenter.py b/ott/solvers/quadratic/gw_barycenter.py index 46ee6618f..f88e175cd 100644 --- a/ott/solvers/quadratic/gw_barycenter.py +++ b/ott/solvers/quadratic/gw_barycenter.py @@ -15,7 +15,8 @@ class GWBarycenterState(NamedTuple): - """Holds the state of the :class:`~ott.core.bar_problems.GWBarycenterProblem`. + """Holds the state of the \ + :class:`~ott.problems.quadratic.gw_barycenter.GWBarycenterProblem`. Args: c: Barycenter cost matrix of shape ``[bar_size, bar_size]``. @@ -44,7 +45,7 @@ def set(self, **kwargs: Any) -> 'GWBarycenterState': @jax.tree_util.register_pytree_node_class class GromovWassersteinBarycenter(was_solver.WassersteinSolver): """Gromov-Wasserstein barycenter solver of the \ - :class:`~ott.core.bar_problems.GWBarycenterProblem`. + :class:`~ott.problems.quadratic.gw_barycenter.GWBarycenterProblem`. Args: epsilon: Entropy regulariser. @@ -56,7 +57,7 @@ class GromovWassersteinBarycenter(was_solver.WassersteinSolver): as its linear solver, at each iteration for each measure. quad_solver: The GW solver. kwargs: Keyword argument for - :class:`~ott.core.gromov_wasserstein.GromovWasserstein`. + :class:`~ott.solvers.quadratic.gromov_wasserstein.GromovWasserstein`. Only used when ``quad_solver = None``. """ @@ -129,9 +130,9 @@ def init_state( - :class:`jax.numpy.ndarray` - barycenter cost matrix of shape ``[bar_size, bar_size]``. Only used in the non-fused case. - - 2- :class:`tuple` of :class:`jax.numpy.ndarray` - the 1st array - corresponds to ``[bar_size, bar_size]`` cost matrix, - the 2nd array is ``[bar_size, ndim_fused]`` a feature matrix used in + - :class:`tuple` of :class:`jax.numpy.ndarray` - the 1st array + corresponds to a cost matrix of shape ``[bar_size, bar_size]``, + the 2nd array is a ``[bar_size, ndim_fused]`` feature matrix used in the fused case. a: An array of shape ``[bar_size,]`` containing the barycenter weights. From 861bdfa5bf3b53935b7c8f616b46fd96e6255100 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Fri, 18 Nov 2022 12:36:29 +0100 Subject: [PATCH 16/34] Update `tools` docstrings --- ott/tools/segment_sinkhorn.py | 29 ++++++++++++++------------- ott/tools/sinkhorn_divergence.py | 34 +++++++++++++++++--------------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/ott/tools/segment_sinkhorn.py b/ott/tools/segment_sinkhorn.py index 3b149c0f5..886430d07 100644 --- a/ott/tools/segment_sinkhorn.py +++ b/ott/tools/segment_sinkhorn.py @@ -29,7 +29,7 @@ def segment_sinkhorn( cost_fn: Optional[costs.CostFn] = None, segment_ids_x: Optional[jnp.ndarray] = None, segment_ids_y: Optional[jnp.ndarray] = None, - indices_are_sorted: Optional[bool] = None, + indices_are_sorted: bool = False, num_per_segment_x: Optional[Tuple[int, ...]] = None, num_per_segment_y: Optional[Tuple[int, ...]] = None, weights_x: Optional[jnp.ndarray] = None, @@ -56,22 +56,23 @@ def segment_sinkhorn( parallel. Args: - x: Array of input points, of shape [num_x, feature]. Multiple segments are - held in this single array. - y: Array of target points, of shape [num_y, feature]. - num_segments: Number of segments contained in x and y. Providing this number - is required for JIT compilation to work, see also - :func:`~ott.core.segment.segment_point_cloud`. + x: Array of input points, of shape `[num_x, feature]`. + Multiple segments are held in this single array. + y: Array of target points, of shape `[num_y, feature]`. + num_segments: Number of segments contained in `x` and `y`. + Providing this is required for JIT compilation to work, + see also :func:`~ott.geometry.segment.segment_point_cloud`. max_measure_size: Total size of measures after padding. Should ideally be set to an upper bound on points clouds processed with the segment - interface. Providing this number is required for JIT compilation to work. - cost_fn: Cost function, defaults to :class:`~ott.core.costs.SqEuclidean`. - segment_ids_x: **1st interface** The segment ID for which each row of x + interface. Providing this is required for JIT compilation to work. + cost_fn: Cost function, defaults to + :class:`~ott.geometry.costs.SqEuclidean`. + segment_ids_x: **1st interface** The segment ID for which each row of `x` belongs. This is a similar interface to `jax.ops.segment_sum`. - segment_ids_y: **1st interface** The segment ID for which each row of y + segment_ids_y: **1st interface** The segment ID for which each row of `y` belongs. indices_are_sorted: **1st interface** Whether `segment_ids_x` and - `segment_ids_y` are sorted. Default false. + `segment_ids_y` are sorted. num_per_segment_x: **2nd interface** Number of points in each segment in `x`. For example, [100, 20, 30] would imply that `x` is segmented into three arrays of length `[100]`, `[20]`, and `[30]` respectively. @@ -87,9 +88,9 @@ def segment_sinkhorn( `y`/`y` (except when `static_b` is `True`, in which case `y`/`y` is not evaluated). kwargs: keywords arguments passed to form - :class:`ott.geometry.pointcloud.PointCloud` geometry objects from the + :class:`~ott.geometry.pointcloud.PointCloud` geometry objects from the subsets of points and masses selected in `x` and `y`, possibly a - :class:`ott.geometry.costs.CostFn` or an entropy regularizer. + :class:`~ott.geometry.costs.CostFn` or an entropy regularizer. Returns: An array of sinkhorn reg_ot_cost for each segment. diff --git a/ott/tools/sinkhorn_divergence.py b/ott/tools/sinkhorn_divergence.py index baf5688c3..28c32fa82 100644 --- a/ott/tools/sinkhorn_divergence.py +++ b/ott/tools/sinkhorn_divergence.py @@ -70,8 +70,9 @@ def sinkhorn_divergence( match that of `b` to converge. b: the weight of each target point. The sum of all elements of `b` must match that of `a` to converge. - sinkhorn_kwargs: keywords arguments for :func:`~ott.core.sinkhorn.sinkhorn` - that is called twice if ``static_b = True`` else 3 times. + sinkhorn_kwargs: keywords arguments for + :func:`~ott.solvers.linear.sinkhorn.sinkhorn` that is called twice + if ``static_b = True`` else 3 times. static_b: if True, divergence of measure `b` against itself is **not** computed. share_epsilon: if True, enforces that the same epsilon regularizer is shared @@ -138,7 +139,7 @@ def _sinkhorn_divergence( all elements of b must match that of a to converge. symmetric_sinkhorn: Use Sinkhorn updates in Eq. 25 of :cite:`feydy:19` for symmetric terms comparing x/x and y/y. - kwargs: Keyword arguments to :func:`ott.core.sinkhorn.sinkhorn`. + kwargs: Keyword arguments to :func:`~ott.solvers.linear.sinkhorn.sinkhorn`. Returns: SinkhornDivergenceOutput named tuple. @@ -190,7 +191,7 @@ def segment_sinkhorn_divergence( cost_fn: Optional[costs.CostFn] = None, segment_ids_x: Optional[jnp.ndarray] = None, segment_ids_y: Optional[jnp.ndarray] = None, - indices_are_sorted: Optional[bool] = None, + indices_are_sorted: bool = False, num_per_segment_x: Optional[Tuple[int, ...]] = None, num_per_segment_y: Optional[Tuple[int, ...]] = None, weights_x: Optional[jnp.ndarray] = None, @@ -219,23 +220,24 @@ def segment_sinkhorn_divergence( a tensor, and `vmap` used to evaluate sinkhorn divergences in parallel. Args: - x: Array of input points, of shape [num_x, feature]. Multiple segments are - held in this single array. - y: Array of target points, of shape [num_y, feature]. - num_segments: Number of segments contained in x and y. Providing this number - is required for JIT compilation to work, see also - :func:`~ott.core.segment.segment_point_cloud`. + x: Array of input points, of shape `[num_x, feature]`. + Multiple segments are held in this single array. + y: Array of target points, of shape `[num_y, feature]`. + num_segments: Number of segments contained in `x` and `y`. + Providing this is required for JIT compilation to work, + see also :func:`~ott.geometry.segment.segment_point_cloud`. max_measure_size: Total size of measures after padding. Should ideally be set to an upper bound on points clouds processed with the segment interface. Should also be smaller than total length of `x` or `y`. - Providing this number is required for JIT compilation to work. - cost_fn: Cost function, defaults to :class:`~ott.core.costs.SqEuclidean`. - segment_ids_x: **1st interface** The segment ID for which each row of x + Providing this is required for JIT compilation to work. + cost_fn: Cost function, + defaults to :class:`~ott.geometry.costs.SqEuclidean`. + segment_ids_x: **1st interface** The segment ID for which each row of `x` belongs. This is a similar interface to :func:`jax.ops.segment_sum`. - segment_ids_y: **1st interface** The segment ID for which each row of y + segment_ids_y: **1st interface** The segment ID for which each row of `y` belongs. indices_are_sorted: **1st interface** Whether `segment_ids_x` and - `segment_ids_y` are sorted. Default false. + `segment_ids_y` are sorted. num_per_segment_x: **2nd interface** Number of points in each segment in `x`. For example, [100, 20, 30] would imply that `x` is segmented into three arrays of length `[100]`, `[20]`, and `[30]` respectively. @@ -260,7 +262,7 @@ def segment_sinkhorn_divergence( symmetric_sinkhorn: Use Sinkhorn updates in Eq. 25 of :cite:`feydy:19` for symmetric terms comparing x/x and y/y. kwargs: keywords arguments passed to form - :class:`ott.geometry.pointcloud.PointCloud` geometry objects from the + :class:`~ott.geometry.pointcloud.PointCloud` geometry objects from the subsets of points and masses selected in `x` and `y`, this could be for instance entropy regularization float, scheduler or normalization. Returns: From 4faeca54f585d71193a056a8203ffa7e4cb2f582 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Fri, 18 Nov 2022 12:47:25 +0100 Subject: [PATCH 17/34] Remove remaining `core` mentions from docstrings --- ott/geometry/graph.py | 2 +- ott/initializers/linear/initializers_lr.py | 2 +- ott/problems/linear/barycenter_problem.py | 2 +- ott/solvers/linear/continuous_barycenter.py | 2 +- ott/solvers/linear/sinkhorn.py | 4 ++-- ott/solvers/linear/sinkhorn_lr.py | 6 +++--- ott/tools/transport.py | 9 ++++----- 7 files changed, 13 insertions(+), 14 deletions(-) diff --git a/ott/geometry/graph.py b/ott/geometry/graph.py index 8cd6c05a7..dd361f67a 100644 --- a/ott/geometry/graph.py +++ b/ott/geometry/graph.py @@ -192,7 +192,7 @@ def laplacian(self) -> Union[jnp.ndarray, Sparse_t]: # in the sparse case, we don't sum duplicates here because # we need to know `nnz` a priori for JIT (could be exposed in `__init__`) - # instead, `ott.core.decomposition._jax_sparse_to_scipy` handles it on host + # instead, `ott.math.decomposition._jax_sparse_to_scipy` handles it on host return D - self.graph @property diff --git a/ott/initializers/linear/initializers_lr.py b/ott/initializers/linear/initializers_lr.py index 3eb4670ee..8e60ba51a 100644 --- a/ott/initializers/linear/initializers_lr.py +++ b/ott/initializers/linear/initializers_lr.py @@ -531,7 +531,7 @@ def init_fn() -> GeneralizedKMeansInitializer.State: crossed_threshold=False ) - # see the explanation in `ott.core.sinkhorn_lr` + # see the explanation in `ott.solvers.linear.sinkhorn_lr` def converged( state: GeneralizedKMeansInitializer.State, consts: GeneralizedKMeansInitializer.Constants, iteration: int diff --git a/ott/problems/linear/barycenter_problem.py b/ott/problems/linear/barycenter_problem.py index ac30a2b01..c2f28860f 100644 --- a/ott/problems/linear/barycenter_problem.py +++ b/ott/problems/linear/barycenter_problem.py @@ -46,7 +46,7 @@ class BarycenterProblem: the regularized transportation cost of barycenter to itself will be considered when computing gradient. Note that if the debiased option is used, the barycenter size in - :meth:`~ott.core.continuous_barycenter.WassersteinBarycenter.init_state` + :meth:`~ott.solvers.linear.continuous_barycenter.WassersteinBarycenter.init_state` needs to be smaller than the maximum measure size for parallelization to operate efficiently. kwargs: Keyword arguments :func:`~ott.geometry.segment.segment_point_cloud`. diff --git a/ott/solvers/linear/continuous_barycenter.py b/ott/solvers/linear/continuous_barycenter.py index a1534647c..715a72384 100644 --- a/ott/solvers/linear/continuous_barycenter.py +++ b/ott/solvers/linear/continuous_barycenter.py @@ -149,7 +149,7 @@ def init_state( x_init: Initial barycenter estimate of shape ``[bar_size, ndim]``. If `None`, ``bar_size`` points will be sampled from the input measures according to their weights - :attr:`~ott.core.bar_problems.BarycenterProblem.flattened_y`. + :attr:`~ott.problems.linear.barycenter_problem.BarycenterProblem.flattened_y`. rng: Seed for :func:`jax.random.PRNGKey`. Returns: diff --git a/ott/solvers/linear/sinkhorn.py b/ott/solvers/linear/sinkhorn.py index 624a2a115..bfc86c4a5 100644 --- a/ott/solvers/linear/sinkhorn.py +++ b/ott/solvers/linear/sinkhorn.py @@ -327,8 +327,8 @@ class Sinkhorn: unroll-able :func:`jax.lax.while_loop` that monitors convergence. In that case the error is not monitored and the ``converged`` flag will return ``False`` as a consequence. - momentum: a Momentum instance. See ott.core.momentum - anderson: an AndersonAcceleration instance. See ott.core.anderson. + momentum: Momentum instance. + anderson: AndersonAcceleration instance. implicit_diff: instance used to solve implicit differentiation. Unrolls iterations if None. parallel_dual_updates: updates potentials or scalings in parallel if True, diff --git a/ott/solvers/linear/sinkhorn_lr.py b/ott/solvers/linear/sinkhorn_lr.py index 5f4dbd7ec..13f014818 100644 --- a/ott/solvers/linear/sinkhorn_lr.py +++ b/ott/solvers/linear/sinkhorn_lr.py @@ -300,9 +300,9 @@ def __call__( ot_prob: Linear OT problem. init: Initial values for the low-rank factors: - - :attr:`~ott.core.sinkhorn_lr.LRSinkhornOutput.q`. - - :attr:`~ott.core.sinkhorn_lr.LRSinkhornOutput.r`. - - :attr:`~ott.core.sinkhorn_lr.LRSinkhornOutput.g`. + - :attr:`~ott.solvers.linear.sinkhorn_lr.LRSinkhornOutput.q`. + - :attr:`~ott.solvers.linear.sinkhorn_lr.LRSinkhornOutput.r`. + - :attr:`~ott.solvers.linear.sinkhorn_lr.LRSinkhornOutput.g`. Any `None` values will be initialized using the initializer. key: Random key for seeding. diff --git a/ott/tools/transport.py b/ott/tools/transport.py index cc736d859..1f25a8d8f 100644 --- a/ott/tools/transport.py +++ b/ott/tools/transport.py @@ -21,10 +21,9 @@ >>> ot = ott.transport.solve(x, y) >>> Tz = ot.apply(z) -Even if the transport.solve sole function can support many complex use cases, we -suggest more advanced users to instantiate directly their problem (see -ott.core.problems) and their solvers (see ott.core.sinkhorn and -ott.core.gromov_wasserstein) for better control over the parameters. +Even if the `transport.solve` sole function can support many complex use cases, +we suggest more advanced users to instantiate directly their :mod:`ott.problems` +and their :mod:`ott.solvers` for better control over the parameters. """ from typing import Any, NamedTuple, Optional, Union @@ -43,7 +42,7 @@ class Transport(NamedTuple): - """Implement a core.problems.Transport interface to transport solutions.""" + """Transport interface to transport solutions.""" problem: Any = None solver_output: Any = None From ba29e10ee7060b6afd5f645455430ce0d6d67be1 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Fri, 18 Nov 2022 14:58:14 +0100 Subject: [PATCH 18/34] Start updating documentation --- .gitignore | 2 +- docs/index.rst | 36 ++---------------------------------- docs/problems/index.rst | 13 +++++++++++++ docs/problems/linear.rst | 20 ++++++++++++++++++++ docs/problems/quadratic.rst | 20 ++++++++++++++++++++ docs/solvers/index.rst | 14 ++++++++++++++ docs/solvers/linear.rst | 32 ++++++++++++++++++++++++++++++++ docs/solvers/nn.rst | 2 ++ docs/solvers/quadratic.rst | 2 ++ 9 files changed, 106 insertions(+), 35 deletions(-) create mode 100644 docs/problems/index.rst create mode 100644 docs/problems/linear.rst create mode 100644 docs/problems/quadratic.rst create mode 100644 docs/solvers/index.rst create mode 100644 docs/solvers/linear.rst create mode 100644 docs/solvers/nn.rst create mode 100644 docs/solvers/quadratic.rst diff --git a/.gitignore b/.gitignore index e289be07d..d8f1864bd 100644 --- a/.gitignore +++ b/.gitignore @@ -161,7 +161,7 @@ cython_debug/ # generated documentation docs/html -docs/_autosummary +**/_autosummary # macos **/.DS_Store diff --git a/docs/index.rst b/docs/index.rst index 52af7189f..35771b557 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -57,46 +57,14 @@ There are currently three packages, ``geometry``, ``core`` and ``tools``, playin between GMMs, or computing differentiable sort and quantile operations :cite:`cuturi:19`. -.. toctree:: - :maxdepth: 1 - :caption: Tutorials: - - notebooks/point_clouds.ipynb - notebooks/introduction_grid.ipynb - -.. toctree:: - :maxdepth: 1 - :caption: Benchmarks: - - notebooks/OTT_&_POT.ipynb - notebooks/One_Sinkhorn.ipynb - notebooks/LRSinkhorn.ipynb - -.. toctree:: - :maxdepth: 1 - :caption: Advanced Applications: - - notebooks/Sinkhorn_Barycenters.ipynb - notebooks/gromov_wasserstein.ipynb - notebooks/GWLRSinkhorn.ipynb - notebooks/Hessians.ipynb - notebooks/soft_sort.ipynb - notebooks/application_biology.ipynb - notebooks/gromov_wasserstein_multiomics.ipynb - notebooks/fairness.ipynb - notebooks/neural_dual.ipynb - notebooks/icnn_inits.ipynb - notebooks/wasserstein_barycenters_gmms.ipynb - notebooks/gmm_pair_demo.ipynb - notebooks/MetaOT.ipynb .. toctree:: :maxdepth: 1 :caption: Public API: ott packages geometry - core - tools + problems/index + solvers/index .. toctree:: :maxdepth: 1 diff --git a/docs/problems/index.rst b/docs/problems/index.rst new file mode 100644 index 000000000..89a0b44db --- /dev/null +++ b/docs/problems/index.rst @@ -0,0 +1,13 @@ +ott.problems package +==================== + +TODO(cuturi): add some nice text here please. + +.. currentmodule:: ott.problems +.. automodule:: ott.problems + +.. toctree:: + :maxdepth: 2 + + linear + quadratic diff --git a/docs/problems/linear.rst b/docs/problems/linear.rst new file mode 100644 index 000000000..4f4ce5cb2 --- /dev/null +++ b/docs/problems/linear.rst @@ -0,0 +1,20 @@ +ott.problems.linear package +=========================== +.. currentmodule:: ott.problems.linear +.. automodule:: ott.problems.linear + +OT Problems +----------- +.. autosummary:: + :toctree: _autosummary + + linear_problem.LinearProblem + barycenter_problem.BarycenterProblem + +Dual Potentials +--------------- +.. autosummary:: + :toctree: _autosummary + + potentials.DualPotentials + potentials.EntropicPotentials diff --git a/docs/problems/quadratic.rst b/docs/problems/quadratic.rst new file mode 100644 index 000000000..900081871 --- /dev/null +++ b/docs/problems/quadratic.rst @@ -0,0 +1,20 @@ +ott.problems.quadratic package +============================== +.. currentmodule:: ott.problems.quadratic +.. automodule:: ott.problems.quadratic + +OT Problems +----------- +.. autosummary:: + :toctree: _autosummary + + quadratic_problem.QuadraticProblem + gw_barycenter.GWBarycenterProblem + +Costs +----- +.. autosummary:: + :toctree: _autosummary + + quadratic_costs.make_square_loss + quadratic_costs.make_kl_loss diff --git a/docs/solvers/index.rst b/docs/solvers/index.rst new file mode 100644 index 000000000..cf054d858 --- /dev/null +++ b/docs/solvers/index.rst @@ -0,0 +1,14 @@ +ott.solvers package +=================== + +TODO(cuturi): add some nice text here please. + +.. currentmodule:: ott.solvers +.. automodule:: ott.solvers + +.. toctree:: + :maxdepth: 2 + + linear + quadratic + neural diff --git a/docs/solvers/linear.rst b/docs/solvers/linear.rst new file mode 100644 index 000000000..16ddd20fe --- /dev/null +++ b/docs/solvers/linear.rst @@ -0,0 +1,32 @@ +ott.solvers.linear package +========================== +.. currentmodule:: ott.solvers.linear +.. automodule:: ott.solvers.linear + +Sinkhorn Solvers +---------------- +.. autosummary:: + :toctree: _autosummary + + sinkhorn.Sinkhorn + sinkhorn.SinkhornOutput + sinkhorn_lr.LRSinkhorn + sinkhorn_lr.LRSinkhornOutput + +Barycenter Solvers +------------------ +.. autosummary:: + :toctree: _autosummary + + continuous_barycenter.WassersteinBarycenter + continuous_barycenter.BarycenterState + discrete_barycenter.discrete_barycenter + discrete_barycenter.SinkhornBarycenterOutput + +Sinkhorn Acceleration +--------------------- +.. autosummary:: + :toctree: _autosummary + + acceleration.Momentum + acceleration.AndersonAcceleration diff --git a/docs/solvers/nn.rst b/docs/solvers/nn.rst new file mode 100644 index 000000000..887929c9f --- /dev/null +++ b/docs/solvers/nn.rst @@ -0,0 +1,2 @@ +ott.solvers.nn package +====================== diff --git a/docs/solvers/quadratic.rst b/docs/solvers/quadratic.rst new file mode 100644 index 000000000..c1e85ca70 --- /dev/null +++ b/docs/solvers/quadratic.rst @@ -0,0 +1,2 @@ +ott.solvers.quadratic package +============================= From 2bd6d8b41aae15262e29aaecab53e436d4eb5ead Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Mon, 21 Nov 2022 10:28:19 +0100 Subject: [PATCH 19/34] Fix typing --- ott/geometry/costs.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ott/geometry/costs.py b/ott/geometry/costs.py index 5b11005d3..4521b89e7 100644 --- a/ott/geometry/costs.py +++ b/ott/geometry/costs.py @@ -320,14 +320,14 @@ def scale_covariances( (cov_sqrt @ cov_i) @ cov_sqrt ) - def cond_fn(iteration: int, constants: Tuple[...], state) -> bool: + def cond_fn(iteration: int, constants: Tuple[Any, ...], state) -> bool: del iteration, constants _, diff = state return diff > rtol def body_fn( - iteration: int, constants: Tuple[...], state: Tuple[jnp.ndarray, float], - compute_error: bool + iteration: int, constants: Tuple[Any, ...], + state: Tuple[jnp.ndarray, float], compute_error: bool ) -> Tuple[jnp.ndarray, float]: del iteration, constants, compute_error cov, _ = state From d54074aa09c549da36f64970e123432bc6d307af Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Mon, 21 Nov 2022 10:31:31 +0100 Subject: [PATCH 20/34] Update solvers docs --- docs/solvers/index.rst | 2 +- docs/solvers/nn.rst | 17 +++++++++++++++++ docs/solvers/quadratic.rst | 19 +++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/solvers/index.rst b/docs/solvers/index.rst index cf054d858..9954a585c 100644 --- a/docs/solvers/index.rst +++ b/docs/solvers/index.rst @@ -11,4 +11,4 @@ TODO(cuturi): add some nice text here please. linear quadratic - neural + nn diff --git a/docs/solvers/nn.rst b/docs/solvers/nn.rst index 887929c9f..cd28c655f 100644 --- a/docs/solvers/nn.rst +++ b/docs/solvers/nn.rst @@ -1,2 +1,19 @@ ott.solvers.nn package ====================== +.. currentmodule:: ott.solvers.nn +.. automodule:: ott.solvers.nn + +Neural Dual +----------- +.. autosummary:: + :toctree: _autosummary + + neuraldual.NeuralDualSolver + +ICNN +---- +.. autosummary:: + :toctree: _autosummary + + icnn.ICNN + layers.PositiveDense diff --git a/docs/solvers/quadratic.rst b/docs/solvers/quadratic.rst index c1e85ca70..5ac42cabd 100644 --- a/docs/solvers/quadratic.rst +++ b/docs/solvers/quadratic.rst @@ -1,2 +1,21 @@ ott.solvers.quadratic package ============================= +.. currentmodule:: ott.solvers.quadratic +.. automodule:: ott.solvers.quadratic + +Gromov Wasserstein Solvers +-------------------------- +.. autosummary:: + :toctree: _autosummary + + gromov_wasserstein.GromovWasserstein + gromov_wasserstein.GWOutput + gromov_wasserstein.gromov_wasserstein + +Barycenter Solvers +------------------ +.. autosummary:: + :toctree: _autosummary + + gw_barycenter.GWBarycenterState + gw_barycenter.GromovWassersteinBarycenter From 6a1521345823be1985b44939f5da20b335203da4 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Mon, 21 Nov 2022 11:00:31 +0100 Subject: [PATCH 21/34] Add initializers --- docs/Makefile | 1 + docs/index.rst | 1 + docs/initializers/index.rst | 14 ++++++++++++++ docs/initializers/linear.rst | 23 +++++++++++++++++++++++ docs/initializers/nn.rst | 12 ++++++++++++ docs/initializers/quadratic.rst | 12 ++++++++++++ docs/solvers/quadratic.rst | 2 +- ott/geometry/costs.py | 2 +- 8 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 docs/initializers/index.rst create mode 100644 docs/initializers/linear.rst create mode 100644 docs/initializers/nn.rst create mode 100644 docs/initializers/quadratic.rst diff --git a/docs/Makefile b/docs/Makefile index 3db4deda9..2dab86e59 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -22,3 +22,4 @@ help: clean: @rm -rf $(BUILDDIR)/ @rm -rf $(SOURCEDIR)/_autosummary + @rm -rf $(SOURCEDIR)/**/_autosummary diff --git a/docs/index.rst b/docs/index.rst index 35771b557..7a2bebe6b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -65,6 +65,7 @@ There are currently three packages, ``geometry``, ``core`` and ``tools``, playin geometry problems/index solvers/index + initializers/index .. toctree:: :maxdepth: 1 diff --git a/docs/initializers/index.rst b/docs/initializers/index.rst new file mode 100644 index 000000000..a2f4162f3 --- /dev/null +++ b/docs/initializers/index.rst @@ -0,0 +1,14 @@ +ott.initializers package +======================== + +TODO(cuturi): add some nice text here please. + +.. currentmodule:: ott.initializers +.. automodule:: ott.initializers + +.. toctree:: + :maxdepth: 2 + + linear + quadratic + nn diff --git a/docs/initializers/linear.rst b/docs/initializers/linear.rst new file mode 100644 index 000000000..8fb5d89ea --- /dev/null +++ b/docs/initializers/linear.rst @@ -0,0 +1,23 @@ +ott.initializers.linear package +=============================== +.. currentmodule:: ott.initializers.linear +.. automodule:: ott.initializers.linear + +Sinkhorn Initializers +--------------------- +.. autosummary:: + :toctree: _autosummary + + initializers.DefaultInitializer + initializers.GaussianInitializer + initializers.SinkhornInitializer + +Low-rank Sinkhorn Initializers +------------------------------ +.. autosummary:: + :toctree: _autosummary + + initializers_lr.RandomInitializer + initializers_lr.Rank2Initializer + initializers_lr.KMeansInitializer + initializers_lr.GeneralizedKMeansInitializer diff --git a/docs/initializers/nn.rst b/docs/initializers/nn.rst new file mode 100644 index 000000000..6f439f6a8 --- /dev/null +++ b/docs/initializers/nn.rst @@ -0,0 +1,12 @@ +ott.initializers.nn package +=========================== +.. currentmodule:: ott.initializers.nn +.. automodule:: ott.initializers.nn + +Neural Initializers +------------------- +.. autosummary:: + :toctree: _autosummary + + initializers.MetaInitializer + initializers.MetaMLP diff --git a/docs/initializers/quadratic.rst b/docs/initializers/quadratic.rst new file mode 100644 index 000000000..d3ea718a9 --- /dev/null +++ b/docs/initializers/quadratic.rst @@ -0,0 +1,12 @@ +ott.initializers.quadratic package +================================== +.. currentmodule:: ott.initializers.quadratic +.. automodule:: ott.initializers.quadratic + +Gromov-Wasserstein Initializers +------------------------------- +.. autosummary:: + :toctree: _autosummary + + initializers.QuadraticInitializer + initializers.LRQuadraticInitializer diff --git a/docs/solvers/quadratic.rst b/docs/solvers/quadratic.rst index 5ac42cabd..33b0a9014 100644 --- a/docs/solvers/quadratic.rst +++ b/docs/solvers/quadratic.rst @@ -3,7 +3,7 @@ ott.solvers.quadratic package .. currentmodule:: ott.solvers.quadratic .. automodule:: ott.solvers.quadratic -Gromov Wasserstein Solvers +Gromov-Wasserstein Solvers -------------------------- .. autosummary:: :toctree: _autosummary diff --git a/ott/geometry/costs.py b/ott/geometry/costs.py index 4521b89e7..fdf52e7de 100644 --- a/ott/geometry/costs.py +++ b/ott/geometry/costs.py @@ -333,7 +333,7 @@ def body_fn( cov, _ = state cov_sqrt, cov_inv_sqrt, _ = matrix_square_root.sqrtm(cov) scaled_cov = jnp.linalg.matrix_power( - jnp.sum(self.scale_covariances(cov_sqrt, covs, lambdas), axis=0), 2 + jnp.sum(scale_covariances(cov_sqrt, covs, lambdas), axis=0), 2 ) next_cov = (cov_inv_sqrt @ scaled_cov) @ cov_inv_sqrt diff = jnp.sum((next_cov - cov) ** 2) / jnp.prod(jnp.array(cov.shape)) From 987ad2595575d544d8d48d9009b06a448f311014 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Mon, 21 Nov 2022 12:31:02 +0100 Subject: [PATCH 22/34] Update docs --- docs/conf.py | 3 + docs/core.rst | 115 -------------------- docs/geometry.rst | 7 ++ docs/index.rst | 49 +++++++-- docs/initializers/index.rst | 2 + docs/math.rst | 35 ++++++ docs/problems/index.rst | 2 + docs/references.bib | 3 +- docs/solvers/index.rst | 9 ++ docs/solvers/linear.rst | 1 + docs/tools.rst | 1 - ott/_version.py | 6 +- ott/initializers/nn/initializers.py | 28 ++--- ott/math/decomposition.py | 2 +- ott/math/implicit_differentiation.py | 28 ++--- ott/math/matrix_square_root.py | 2 +- ott/math/utils.py | 7 +- ott/problems/quadratic/quadratic_problem.py | 2 +- ott/solvers/quadratic/gromov_wasserstein.py | 2 +- ott/{typing.py => types.py} | 0 20 files changed, 142 insertions(+), 162 deletions(-) delete mode 100644 docs/core.rst create mode 100644 docs/math.rst rename ott/{typing.py => types.py} (100%) diff --git a/docs/conf.py b/docs/conf.py index 6b8dbd0bc..c53993a05 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -73,6 +73,9 @@ source_suffix = ['.rst'] autosummary_generate = True +autosummary_filename_map = { + "ott.solvers.linear.sinkhorn.sinkhorn": "sinkhorn-function" +} autodoc_typehints = 'description' diff --git a/docs/core.rst b/docs/core.rst deleted file mode 100644 index 0c62c88e5..000000000 --- a/docs/core.rst +++ /dev/null @@ -1,115 +0,0 @@ -.. _core: - -ott.core package -================ -.. currentmodule:: ott.core -.. automodule:: ott.core - -The core package contains definitions of various OT problems, starting -from the most simple, the linear OT problem, to more advanced problems -such as quadratic, or involving multiple measures, the barycenter problem. -We follow with the classic :class:`~ott.core.sinkhorn.sinkhorn` routine (essentially a -wrapper for the :class:`~ott.core.sinkhorn.Sinkhorn` solver class) :cite:`cuturi:13,sejourne:19`. -We also provide an analogous low-rank Sinkhorn solver :cite:`scetbon:21` to handle very large instances. -Both are used within our Wasserstein barycenter solvers :cite:`benamou:15,janati:20a`, as well as our -Gromov-Wasserstein solver :cite:`memoli:11,scetbon:22`. We also provide an implementation of -input convex neural networks :cite:`amos:17`, a NN that can be used to estimate OT :cite:`makkuva:20`. - -OT Problems ------------ -.. autosummary:: - :toctree: _autosummary - - linear_problems.LinearProblem - quad_problems.QuadraticProblem - bar_problems.BarycenterProblem - bar_problems.GWBarycenterProblem - -Sinkhorn --------- -.. autosummary:: - :toctree: _autosummary - - sinkhorn.sinkhorn - sinkhorn.Sinkhorn - sinkhorn.SinkhornOutput - -Sinkhorn Dual Initializers --------------------------- -.. autosummary:: - :toctree: _autosummary - - initializers.DefaultInitializer - initializers.GaussianInitializer - initializers.SortingInitializer - initializers.MetaInitializer - initializers.MetaMLP - -Low-Rank Sinkhorn ------------------ -.. autosummary:: - :toctree: _autosummary - - sinkhorn_lr.LRSinkhorn - sinkhorn_lr.LRSinkhornOutput - -Low-Rank Sinkhorn Initializers ------------------------------- -.. autosummary:: - :toctree: _autosummary - - initializers_lr.RandomInitializer - initializers_lr.Rank2Initializer - initializers_lr.KMeansInitializer - initializers_lr.GeneralizedKMeansInitializer - -Quadratic Initializers ----------------------- -.. autosummary:: - :toctree: _autosummary - - quad_initializers.QuadraticInitializer - quad_initializers.LRQuadraticInitializer - -Barycenters (Entropic and LR) ------------------------------ -.. autosummary:: - :toctree: _autosummary - - discrete_barycenter.discrete_barycenter - continuous_barycenter.WassersteinBarycenter - continuous_barycenter.BarycenterState - gw_barycenter.GromovWassersteinBarycenter - gw_barycenter.GWBarycenterState - -Gromov-Wasserstein (Entropic and LR) ------------------------------------- -.. autosummary:: - :toctree: _autosummary - - gromov_wasserstein.gromov_wasserstein - gromov_wasserstein.GromovWasserstein - gromov_wasserstein.GWOutput - -Dual Potentials ---------------- -.. autosummary:: - :toctree: _autosummary - - potentials.DualPotentials - potentials.EntropicPotentials - -Neural Dual Potentials ----------------------- -.. autosummary:: - :toctree: _autosummary - - icnn.ICNN - neuraldual.NeuralDualSolver - -Padding Utilities ------------------ -.. autosummary:: - :toctree: _autosummary - - segment.segment_point_cloud diff --git a/docs/geometry.rst b/docs/geometry.rst index edabf6b3b..c857dd8a7 100644 --- a/docs/geometry.rst +++ b/docs/geometry.rst @@ -58,3 +58,10 @@ Cost Functions costs.Cosine costs.Bures costs.UnbalancedBures + +Utilities +--------- +.. autosummary:: + :toctree: _autosummary + + segment.segment_point_cloud diff --git a/docs/index.rst b/docs/index.rst index 7a2bebe6b..24a376771 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,11 +17,10 @@ The first family consists in *discrete* solvers computing transport between poin using the Sinkhorn :cite:`cuturi:13` and low-rank Sinkhorn :cite:`scetbon:21` algorithms, and moving up towards Gromov-Wasserstein :cite:`memoli:11`, :cite:`memoli:11`; the second family consists in *continuous* solvers, using suitable neural architectures :cite:`amos:17` coupled -with SGD type estimators :cite:`makkuva:20`, :cite:`korotin:21`. +with SGD type estimators :cite:`makkuva:20,korotin:21`. Design Choices -------------- - `OTT` is designed with the following choices: - Take advantage whenever possible of JAX features, such as `Just-in-time (JIT) compilation`_, @@ -44,19 +43,51 @@ Design Choices Packages -------- -There are currently three packages, ``geometry``, ``core`` and ``tools``, playing the following roles: - - :ref:`geometry` contains classes to instantiate objects that describe *two point clouds* paired with a *cost* function. Geometry objects are used to - describe OT problems, handled by solvers in ``core``. -- :ref:`core` classes describe OT problems (linear, quadratic, barycenters), and - solver classes, to instantiate algorithms that will output an OT. + describe OT problems, handled by solvers in the :ref:`solvers`. +- :ref:`problems` TODO(marcocuturi) +- :ref:`solvers` TODO(marcocuturi) +- :ref:`initializers` TODO(marcocuturi) - :ref:`tools` provides an interface to exploit OT solutions, as produced by - solvers in the ``core`` package. Such tasks include computing approximations + solvers in the :ref:`solvers`. Such tasks include computing approximations to Wasserstein distances :cite:`genevay:18,sejourne:19`, approximating OT between GMMs, or computing differentiable sort and quantile operations :cite:`cuturi:19`. +- :ref:`math` TODO(marcocuturi) + +.. toctree:: + :maxdepth: 1 + :caption: Tutorials: + + notebooks/point_clouds.ipynb + notebooks/introduction_grid.ipynb + +.. toctree:: + :maxdepth: 1 + :caption: Benchmarks: + + notebooks/OTT_&_POT.ipynb + notebooks/One_Sinkhorn.ipynb + notebooks/LRSinkhorn.ipynb + +.. toctree:: + :maxdepth: 1 + :caption: Advanced Applications: + notebooks/Sinkhorn_Barycenters.ipynb + notebooks/gromov_wasserstein.ipynb + notebooks/GWLRSinkhorn.ipynb + notebooks/Hessians.ipynb + notebooks/soft_sort.ipynb + notebooks/application_biology.ipynb + notebooks/gromov_wasserstein_multiomics.ipynb + notebooks/fairness.ipynb + notebooks/neural_dual.ipynb + notebooks/icnn_inits.ipynb + notebooks/wasserstein_barycenters_gmms.ipynb + notebooks/gmm_pair_demo.ipynb + notebooks/MetaOT.ipynb .. toctree:: :maxdepth: 1 @@ -66,6 +97,8 @@ There are currently three packages, ``geometry``, ``core`` and ``tools``, playin problems/index solvers/index initializers/index + tools + math .. toctree:: :maxdepth: 1 diff --git a/docs/initializers/index.rst b/docs/initializers/index.rst index a2f4162f3..4baba57f5 100644 --- a/docs/initializers/index.rst +++ b/docs/initializers/index.rst @@ -1,3 +1,5 @@ +.. _initializers: + ott.initializers package ======================== diff --git a/docs/math.rst b/docs/math.rst new file mode 100644 index 000000000..de58a29e2 --- /dev/null +++ b/docs/math.rst @@ -0,0 +1,35 @@ +.. _math: + +ott.math package +================ +.. currentmodule:: ott.math +.. automodule:: ott.math + +Implicit Differentiation +------------------------ +.. autosummary:: + :toctree: _autosummary + + implicit_differentiation.ImplicitDiff + +Fixed-point Iteration +--------------------- +.. autosummary:: + :toctree: _autosummary + + fixed_point_loop.fixpoint_iter + +Cholesky Decomposition +---------------------- +.. autosummary:: + :toctree: _autosummary + + decomposition.DenseCholeskySolver + decomposition.SparseCholeskySolver + +Matrix Square Root +------------------ +.. autosummary:: + :toctree: _autosummary + + matrix_square_root.sqrtm diff --git a/docs/problems/index.rst b/docs/problems/index.rst index 89a0b44db..462411f7d 100644 --- a/docs/problems/index.rst +++ b/docs/problems/index.rst @@ -1,3 +1,5 @@ +.. _problems: + ott.problems package ==================== diff --git a/docs/references.bib b/docs/references.bib index 74abe1adb..882822f55 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -29,7 +29,6 @@ @InProceedings{peyre:16 url = {https://proceedings.mlr.press/v48/peyre16.html}, } - @InProceedings{feydy:19, title = {Interpolating between Optimal Transport and MMD using Sinkhorn Divergences}, author = {Feydy, Jean and S\'{e}journ\'{e}, Thibault and Vialard, Fran\c{c}ois-Xavier and Amari, Shun-ichi and Trouve, Alain and Peyr\'{e}, Gabriel}, @@ -492,7 +491,7 @@ @inproceedings{chizat:20 year = {2020} } -@Article{higham:1997, +@Article{higham:97, author = "Higham, Nicholas J.", title = "Stable iterations for the matrix square root", journal = "Numerical Algorithms", diff --git a/docs/solvers/index.rst b/docs/solvers/index.rst index 9954a585c..b903983a7 100644 --- a/docs/solvers/index.rst +++ b/docs/solvers/index.rst @@ -1,3 +1,5 @@ +.. _solvers: + ott.solvers package =================== @@ -12,3 +14,10 @@ TODO(cuturi): add some nice text here please. linear quadratic nn + +Wasserstein Solver +------------------ +.. autosummary:: + :toctree: _autosummary + + was_solver.WassersteinSolver diff --git a/docs/solvers/linear.rst b/docs/solvers/linear.rst index 16ddd20fe..8e39c984e 100644 --- a/docs/solvers/linear.rst +++ b/docs/solvers/linear.rst @@ -8,6 +8,7 @@ Sinkhorn Solvers .. autosummary:: :toctree: _autosummary + sinkhorn.sinkhorn sinkhorn.Sinkhorn sinkhorn.SinkhornOutput sinkhorn_lr.LRSinkhorn diff --git a/docs/tools.rst b/docs/tools.rst index 2a890d52a..a0847d506 100644 --- a/docs/tools.rst +++ b/docs/tools.rst @@ -23,7 +23,6 @@ Segmented Sinkhorn segment_sinkhorn.segment_sinkhorn - Sinkhorn Divergence ------------------- .. autosummary:: diff --git a/ott/_version.py b/ott/_version.py index 23af74edc..689bed779 100644 --- a/ott/_version.py +++ b/ott/_version.py @@ -1,13 +1,11 @@ -from packaging.version import parse - try: from importlib_metadata import PackageNotFoundError, version # Python < 3.8 except ImportError: from importlib.metadata import PackageNotFoundError, version try: - __version__ = str(parse(version("ott-jax"))) + __version__ = version("ott-jax") except PackageNotFoundError: __version__ = "" -del parse, version, PackageNotFoundError +del version, PackageNotFoundError diff --git a/ott/initializers/nn/initializers.py b/ott/initializers/nn/initializers.py index 0f6ce2e07..f87dce7d0 100644 --- a/ott/initializers/nn/initializers.py +++ b/ott/initializers/nn/initializers.py @@ -33,25 +33,27 @@ class MetaInitializer(initializers.DefaultInitializer): :class:`~ott.initializers.nn.initializers.MetaMLP` and, with batched problem instances passed into :meth:`update`. - **Sample training usage.** The following code shows a simple - example of using ``update`` to train the model, where - ``a`` and ``b`` are the weights of the measures and - ``geom`` is the fixed geometry. - - .. code-block:: python - - meta_initializer = init_lib.MetaInitializer(geom=geom) - while training(): - a, b = sample_batch() - loss, init_f, meta_initializer.state = meta_initializer.update( - meta_initializer.state, a=a, b=b) - Args: geom: The fixed geometry of the problem instances. meta_model: The model to predict the potential :math:`f` from the measures. opt: The optimizer to update the parameters. rng: The PRNG key to use for initializing the model. state: The training state of the model to start from. + + Examples: + The following code shows a simple + example of using ``update`` to train the model, where + ``a`` and ``b`` are the weights of the measures and + ``geom`` is the fixed geometry. + + .. code-block:: python + + meta_initializer = init_lib.MetaInitializer(geom) + while training(): + a, b = sample_batch() + loss, init_f, meta_initializer.state = meta_initializer.update( + meta_initializer.state, a=a, b=b + ) """ def __init__( diff --git a/ott/math/decomposition.py b/ott/math/decomposition.py index a05ee1b23..e05888e81 100644 --- a/ott/math/decomposition.py +++ b/ott/math/decomposition.py @@ -25,7 +25,7 @@ except ImportError: cholmod = None -__all__ = ["CholeskySolver", "DenseCholeskySolver", "SparseCholeskySolver"] +__all__ = ["DenseCholeskySolver", "SparseCholeskySolver"] T = TypeVar("T") diff --git a/ott/math/implicit_differentiation.py b/ott/math/implicit_differentiation.py index 58ed326bf..26bbbea97 100644 --- a/ott/math/implicit_differentiation.py +++ b/ott/math/implicit_differentiation.py @@ -31,24 +31,26 @@ class ImplicitDiff: """Implicit differentiation of Sinkhorn algorithm. - Attributes: + Args: solver_fun: Callable, should return (solution, ...) ridge_kernel: promotes zero-sum solutions. only used if tau_a = tau_b = 1.0 ridge_identity: handles rank deficient transport matrices (this happens - typically when rows/cols in cost/kernel matrices are colinear, or, + typically when rows/cols in cost/kernel matrices are collinear, or, equivalently when two points from either measure are close). symmetric: flag used to figure out whether the linear system solved in the implicit function theorem is symmetric or not. This happens when either ``a == b`` or the precondition_fun is the identity. False by default, and, at the moment, needs to be set manually by the user in the more favorable case where the system is guaranteed to be symmetric. + precondition_fun: TODO(marcocuturi) """ - solver_fun: Callable = jax.scipy.sparse.linalg.cg # pylint: disable=g-bare-generic + solver_fun: Callable[[jnp.ndarray, jnp.ndarray], + Tuple[jnp.ndarray, ...]] = jax.scipy.sparse.linalg.cg ridge_kernel: float = 0.0 ridge_identity: float = 0.0 symmetric: bool = False - precondition_fun: Optional[Callable[[float], float]] = None + precondition_fun: Optional[Callable[[jnp.ndarray], jnp.ndarray]] = None def solve( self, gr: Tuple[jnp.ndarray, @@ -64,9 +66,11 @@ def solve( Given a ``precondition_fun``, written here for short as :math:`h`, the first order conditions for the dual energy - :math:`E(K, \epsilon, a, b, f, g) :=- + - \langle\exp^{f/\epsilon}, K - \exp^{g/\epsilon}>` + + .. math:: + + E(K, \epsilon, a, b, f, g) :=- + - \langle\exp^{f/\epsilon}, K \exp^{g/\epsilon}> form the basis of the Sinkhorn algorithm. To differentiate optimal solutions to that problem, we exploit the fact that :math:`h(\nabla E = 0)` and @@ -97,7 +101,7 @@ def solve( application elementwise of :math:`h'` to the row (respectively column) marginal sum of the transport. - Note that we take great care in not instantiatiating these transport + Note that we take great care in not instantiating these transport matrices, to rely instead on calls to the ``app_transport`` method from the ``Geometry`` object ``geom`` (which will either use potentials or scalings, depending on ``lse_mode``) @@ -112,7 +116,7 @@ def solve( that subspace to enforce solutions have zero sum. The Schur complement can also be rank deficient if two lines or columns of T - are colinear. This will typically happen it two rows or columns of the cost + are collinear. This will typically happen it two rows or columns of the cost or kernel matrix are numerically close. To avoid this, we add a more global ``ridge_identity * z`` regularizer to achieve better conditioning. @@ -120,10 +124,8 @@ def solve( ``implicit_solver_fun``, which is set by default to ``cg``. When the system is symmetric (as detected by the corresponding flag ``symmetric``), ``cg`` is applied directly. When - it - is not, normal equations are used (i.e. the Schur complement is multiplied - by - its transpose before solving the system). + it is not, normal equations are used (i.e. the Schur complement is + multiplied by its transpose before solving the system). Args: gr: 2-tuple, (vector of size ``n``, vector of size ``m``). diff --git a/ott/math/matrix_square_root.py b/ott/math/matrix_square_root.py index 74a7651ad..761e67af2 100644 --- a/ott/math/matrix_square_root.py +++ b/ott/math/matrix_square_root.py @@ -24,7 +24,7 @@ from ott.math import fixed_point_loop -__all__ = ["sqrtm"] +__all__ = ["sqrtm", "sqrtm_only", "inv_sqrtm_only"] @functools.partial(jax.custom_vjp, nondiff_argnums=(1, 2, 3, 4, 5)) diff --git a/ott/math/utils.py b/ott/math/utils.py index 3c83f8e82..fef5ae667 100644 --- a/ott/math/utils.py +++ b/ott/math/utils.py @@ -1,10 +1,13 @@ import functools -from typing import Optional, Union +from typing import TYPE_CHECKING, Optional, Union import jax import jax.experimental.sparse as jesp import jax.numpy as jnp +if TYPE_CHECKING: + from ott.geometry import costs + __all__ = [ "safe_log", "kl", "js", "sparse_scale", "logsumexp", "barycentric_projection" @@ -93,6 +96,6 @@ def logsumexp_jvp(axis, keepdims, return_sign, primals, tangents): @functools.partial(jax.vmap, in_axes=[0, 0, None]) def barycentric_projection( - matrix: jnp.ndarray, y: jnp.ndarray, cost_fn + matrix: jnp.ndarray, y: jnp.ndarray, cost_fn: "costs.CostFn" ) -> jnp.ndarray: return jax.vmap(cost_fn.barycenter, in_axes=[0, None])(matrix, y) diff --git a/ott/problems/quadratic/quadratic_problem.py b/ott/problems/quadratic/quadratic_problem.py index e2060015d..2bf975c5d 100644 --- a/ott/problems/quadratic/quadratic_problem.py +++ b/ott/problems/quadratic/quadratic_problem.py @@ -22,7 +22,7 @@ from ott.geometry import epsilon_scheduler, geometry, low_rank, pointcloud from ott.problems.linear import linear_problem from ott.problems.quadratic import quadratic_costs -from ott.typing import Transport +from ott.types import Transport if TYPE_CHECKING: from ott.solvers.linear import sinkhorn_lr diff --git a/ott/solvers/quadratic/gromov_wasserstein.py b/ott/solvers/quadratic/gromov_wasserstein.py index 3468147e3..0489b9680 100644 --- a/ott/solvers/quadratic/gromov_wasserstein.py +++ b/ott/solvers/quadratic/gromov_wasserstein.py @@ -249,7 +249,7 @@ def init_state( prob: Quadratic OT problem. init: Initial linearization of the quadratic problem. key: Random key for low-rank initializers. Only used when - :attr:`warm_start` is `False`. + :attr:`warm_start` is `False`. Returns: The initial Gromov-Wasserstein state. diff --git a/ott/typing.py b/ott/types.py similarity index 100% rename from ott/typing.py rename to ott/types.py From 8e0fcb7873163ce8a50301df524fcba591e7e619 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Mon, 21 Nov 2022 14:17:35 +0100 Subject: [PATCH 23/34] Fix MetaOT links --- docs/conf.py | 3 ++- docs/notebooks/MetaOT.ipynb | 16 ++++++++-------- docs/notebooks/gmm_pair_demo.ipynb | 19 ++++++++++++++++--- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index c53993a05..fc7b55099 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -74,7 +74,8 @@ autosummary_generate = True autosummary_filename_map = { - "ott.solvers.linear.sinkhorn.sinkhorn": "sinkhorn-function" + "ott.solvers.linear.sinkhorn.sinkhorn": + "ott.solvers.linear.sinkhorn.sinkhorn-function" } autodoc_typehints = 'description' diff --git a/docs/notebooks/MetaOT.ipynb b/docs/notebooks/MetaOT.ipynb index dc4c3acd2..ebe8bc21d 100644 --- a/docs/notebooks/MetaOT.ipynb +++ b/docs/notebooks/MetaOT.ipynb @@ -24,9 +24,9 @@ "\n", "We will cover:\n", "\n", - "+ [ott.core.initializers.MetaInitializer](../_autosummary/ott.core.initializers.MetaInitializer.html): The main class for the Meta OT initializer\n", - "+ [ott.core.initializers.MetaMLP](../_autosummary/ott.core.initializers.MetaMLP.html): A Meta MLP to predict the dual potentials from the weights of the measures\n", - "+ [ott.core.initializers.GaussianInitializer](../_autosummary/ott.core.initializers.GaussianInitializer.html): The main initialization class for the Gasusian initializer" + "+ [MetaInitializer](../initializers/_autosummary/ott.initializers.nn.initializers.MetaInitializer.html): The main class for the Meta OT initializer\n", + "+ [MetaMLP](../initializers/_autosummary/ott.initializers.nn.initializers.MetaMLP.html): A Meta MLP to predict the dual potentials from the weights of the measures\n", + "+ [GaussianInitializer](../initializers/_autosummary/ott.initializers.linear.initializers.GaussianInitializer.html): The main initialization class for the Gaussian initializer" ] }, { @@ -220,7 +220,7 @@ "This tutorial shows how to train a meta OT model to predict\n", "the optimal Sinkhorn potentials from the image pairs.\n", "We will reproduce their results using \n", - "[OTT's Meta OT initializer](../_autosummary/ott.core.initializers.MetaInitializer.html),\n", + "[OTT's Meta OT initializer](../initializers/_autosummary/ott.initializers.nn.initializers.MetaInitializer.html),\n", "which provides an easy-to-use interface\n", "for training and using Meta OT models.\n", "\n", @@ -296,7 +296,7 @@ "We interpret the pair of MNIST digits as discrete measures\n", "$\\alpha = \\sum_{i=1}^{n_a} a_i \\delta_{x_i}$ and $\\beta = \\sum_{j=1}^{n_b} b_j \\delta_{y_j}$.\n", "The default Sinkhorn implementation in \n", - "[ott.core.sinkhorn.sinkhorn](../_autosummary/ott.core.sinkhorn.sinkhorn.html)\n", + "[ott.solvers.linear.sinkhorn.sinkhorn](../solvers/_autosummary/ott.solvers.linear.sinkhorn.sinkhorn-function.html)\n", "can easily compute their optimal coupling and associated\n", "dual potentials $f$ and $g$ from scratch.\n", "The optimal coupling between the measures can be used\n", @@ -380,16 +380,16 @@ "in the meta distribution $\\mathcal{D}$ during training.\n", "\n", "The following instantiates\n", - "[ott.core.initializers.MetaInitializer](../_autosummary/ott.core.initializers.MetaInitializer.html),\n", + "[MetaInitializer](../initializers/_autosummary/ott.initializers.nn.initializers.MetaInitializer.html),\n", "which provides an implementation for training and deploying Meta OT models.\n", "The default meta potential model for $f_\\theta$ is a standard multi-layer MLP\n", - "defined in [ott.core.initializers.MetaMLP](../_autosummary/ott.core.initializers.MetaMLP.html)\n", + "defined in [MetaMLP](../_autosummary/ott.initializers.nn.initializers.MetaMLP.html)\n", "and it is optimized with Adam by default.\n", "\n", "**Custom model and optimizers**.\n", "The model and training procedure use\n", "[flax](https://flax.readthedocs.io/en/latest/) and\n", - "[optax](https://optax.readthedocs.io/en/latest).\n", + "[optax](https://optax.readthedocs.io/en/latest/).\n", "The Meta OT initializer can take a custom-written Flax module\n", "in `init_model` or optimizer in `opt` that may be better-suited\n", "to your setting than an MLP." diff --git a/docs/notebooks/gmm_pair_demo.ipynb b/docs/notebooks/gmm_pair_demo.ipynb index 176410897..ceb701d4d 100644 --- a/docs/notebooks/gmm_pair_demo.ipynb +++ b/docs/notebooks/gmm_pair_demo.ipynb @@ -16,7 +16,7 @@ "\n", "[1] Y. Chen, T. T. Georgiou, and A. Tannenbaum, [Optimal transport for Gaussian mixture models](https://arxiv.org/abs/1710.07876), arXiv, (2017).\n", "\n", - "[2] Y. Chen, T. T. Georgiou, and A. Tannenbaum, [Optimal Transport for Gaussian Mixture Models](), *IEEE Access*, 7 (2019), pp. 6269–6278, https://doi.org/10.1109/ACCESS.2018.2889838.\n", + "[2] Y. Chen, T. T. Georgiou, and A. Tannenbaum, [Optimal Transport for Gaussian Mixture Models](https://doi.org/10.1109/ACCESS.2018.2889838), *IEEE Access*, 7 (2019), pp. 6269–6278.\n", "\n", "[3] Y. Chen, J. Ye, and J. Li, [A distance for HMMS based on aggregated Wasserstein metric and state registration](https://arxiv.org/abs/1608.01747), arXiv, (2016).\n", "\n", @@ -813,10 +813,23 @@ ] }, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", + "language": "python", "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.6" } }, "nbformat": 4, - "nbformat_minor": 0 + "nbformat_minor": 1 } From f7b92957fa1cd8a523bcfc51c50507bc9954a8f7 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Mon, 21 Nov 2022 16:26:23 +0100 Subject: [PATCH 24/34] Fix bibliography links --- docs/notebooks/One_Sinkhorn.ipynb | 4 ++-- docs/notebooks/gromov_wasserstein_multiomics.ipynb | 2 +- docs/references.bib | 11 +---------- examples/fairness/data.py | 6 ++---- ott/geometry/costs.py | 1 + ott/math/implicit_differentiation.py | 5 ++--- 6 files changed, 9 insertions(+), 20 deletions(-) diff --git a/docs/notebooks/One_Sinkhorn.ipynb b/docs/notebooks/One_Sinkhorn.ipynb index e7c1f580e..01aefa7ec 100644 --- a/docs/notebooks/One_Sinkhorn.ipynb +++ b/docs/notebooks/One_Sinkhorn.ipynb @@ -54,7 +54,7 @@ "## From Texts to Word Histograms\n", "\n", "\n", - "We adapt a [keras NLP tutorial](https://keras.io/examples/nlp/pretrained_word_embeddings/) to preprocess raw text (here a subset of texts from the [newsgroup20](https://kdd.ics.uci.edu/databases/20newsgroups/20newsgroups.html) database) and turn them into word embeddings histograms. See [colab](https://colab.research.google.com/drive/1uCK_qBpOb8yY32ABU_GcykSKE-Q-yjfi#scrollTo=zsekRny9wZoI) for detailed pre-processing." + "We adapt a [keras NLP tutorial](https://keras.io/examples/nlp/pretrained_word_embeddings/) to preprocess raw text (here a subset of texts from the [newsgroup20](https://kdd.ics.uci.edu/databases/20newsgroups/20newsgroups.html) database) and turn them into word embeddings histograms. See [colab](https://colab.research.google.com/drive/1uCK_qBpOb8yY32ABU_GcykSKE-Q-yjfi) for detailed pre-processing." ] }, { @@ -497,7 +497,7 @@ "id": "o05rHAyPK3pN" }, "source": [ - "We now take a closer look at the actual convergence curves of the error of the `sinkhorn` algorithm (i.e. marginal error). We introduce a `plot_results` function to visualize this convergence (See [colab](https://colab.research.google.com/drive/1uCK_qBpOb8yY32ABU_GcykSKE-Q-yjfi#scrollTo=zsekRny9wZoI))." + "We now take a closer look at the actual convergence curves of the error of the `sinkhorn` algorithm (i.e. marginal error). We introduce a `plot_results` function to visualize this convergence (See [colab](https://colab.research.google.com/drive/1uCK_qBpOb8yY32ABU_GcykSKE-Q-yjfi))." ] }, { diff --git a/docs/notebooks/gromov_wasserstein_multiomics.ipynb b/docs/notebooks/gromov_wasserstein_multiomics.ipynb index 574d35292..545f9cd9f 100644 --- a/docs/notebooks/gromov_wasserstein_multiomics.ipynb +++ b/docs/notebooks/gromov_wasserstein_multiomics.ipynb @@ -15,7 +15,7 @@ "id": "BB8VjJrVsuuG" }, "source": [ - "A [variety of single-cell measurements](https://en.wikipedia.org/wiki/Single-cell_analysis) can help explore cell characteristics that are helpful to understand biological mechanisms. These measurements can for instance [describe epigenetic changes](https://en.wikipedia.org/wiki/Single_cell_epigenomics) (DNA methylation, chromatin accessibility, histone modifications, chromosome conformation), the genome itself, as well as the proteins present in the cell ([single cell sequencing](https://en.wikipedia.org/wiki/Single_cell_sequencing#Single-cell_genome_(DNA)_sequencing)). However, performing measures of different natures rises a major challenge: that of establishing an alignment across two (possibly several) measurement spaces that are unrelated, in the sense that no biological-based theory allows to construct such correspondences between them. In the absence of supervised information, the alignment can be constructed from first-hand principles, such as that of preserving geometry (i.e. an isomorphism) between the two measurement spaces. Indeed, since the population of cells measured is (statistically) the same across measurements, we expect that cells with similar genomes will be mapped to cells with similar transcriptomes, proteomes and epigenetic changes. \n", + "A [variety of single-cell measurements](https://en.wikipedia.org/wiki/Single-cell_analysis) can help explore cell characteristics that are helpful to understand biological mechanisms. These measurements can for instance [describe epigenetic changes](https://en.wikipedia.org/wiki/Single_cell_epigenomics) (DNA methylation, chromatin accessibility, histone modifications, chromosome conformation), the genome itself, as well as the proteins present in the cell ([single cell sequencing](https://en.wikipedia.org/wiki/Single_cell_sequencing). However, performing measures of different natures rises a major challenge: that of establishing an alignment across two (possibly several) measurement spaces that are unrelated, in the sense that no biological-based theory allows to construct such correspondences between them. In the absence of supervised information, the alignment can be constructed from first-hand principles, such as that of preserving geometry (i.e. an isomorphism) between the two measurement spaces. Indeed, since the population of cells measured is (statistically) the same across measurements, we expect that cells with similar genomes will be mapped to cells with similar transcriptomes, proteomes and epigenetic changes. \n", "\n", "The Gromov Wasserstein optimal transport framework, implemented in OTT, is a relevant candidate to perform such an unsupervised cell alignment. \n", "This approach was proposed by Demetci et al. in *Gromov-Wasserstein optimal transport to align single-cell multi-omics data, ICML 2020 Workshop on Computational Biology, 2020* ([ICML article](https://icml-compbio.github.io/icml-website-2020/2020/papers/WCBICML2020_paper_51.pdf), pre-print ) who called it SCOT ([GitHub repo](https://github.com/rsinghlab/SCOT)), from which this notebook is adapted.\n", diff --git a/docs/references.bib b/docs/references.bib index 882822f55..3bef70d15 100644 --- a/docs/references.bib +++ b/docs/references.bib @@ -160,10 +160,8 @@ @Article{demetci:20 title = {Gromov-Wasserstein optimal transport to align single-cell multi-omics data}, elocation-id = {2020.04.28.066787}, year = {2020}, - doi = {10.1101/2020.04.28.066787}, publisher = {Cold Spring Harbor Laboratory}, URL = {https://www.biorxiv.org/content/early/2020/11/11/2020.04.28.066787}, - eprint = {https://www.biorxiv.org/content/early/2020/11/11/2020.04.28.066787.full.pdf}, journal = {bioRxiv} } @@ -213,8 +211,6 @@ @Article{gelbrich:90 number = {1}, pages = {185-203}, doi = {https://doi.org/10.1002/mana.19901470121}, - url = {https://onlinelibrary.wiley.com/doi/abs/10.1002/mana.19901470121}, - eprint = {https://onlinelibrary.wiley.com/doi/pdf/10.1002/mana.19901470121}, year = {1990} } @@ -293,9 +289,8 @@ @Article{benamou:15 pages = {A1111-A1138}, year = {2015}, doi = {10.1137/141000439}, - URL = {https://doi.org/10.1137/141000439}, - eprint = {https://doi.org/10.1137/141000439} } + @article{brenier:91, title={Polar factorization and monotone rearrangement of vector-valued functions}, author={Brenier, Yann}, @@ -406,8 +401,6 @@ @Article{delon:20 pages = {936-970}, year = {2020}, doi = {10.1137/19M1301047}, - URL = {https://doi.org/10.1137/19M1301047}, - eprint = {https://doi.org/10.1137/19M1301047}, } @InProceedings{janati:20a, @@ -435,8 +428,6 @@ @Article{schmitz:18 pages = {643-678}, year = {2018}, doi = {10.1137/17M1140431}, - URL = {https://doi.org/10.1137/17M1140431}, - eprint = {https://doi.org/10.1137/17M1140431}, } @Article{alvarez-esteban:16, diff --git a/examples/fairness/data.py b/examples/fairness/data.py index 075e46367..91547d421 100644 --- a/examples/fairness/data.py +++ b/examples/fairness/data.py @@ -19,8 +19,6 @@ import numpy as np import pandas as pd -open_fn = open - def load_df( data_path: str, @@ -30,12 +28,12 @@ def load_df( **kwargs ): """Load a pandas dataframe from two filenames.""" - with open_fn(data_path, 'r') as fp: + with open(data_path) as fp: df = pd.read_csv(fp, skipinitialspace=True, header=None, **kwargs) headers = [] targets = [] - with open_fn(info_path, 'r') as fp: + with open(info_path) as fp: for line in fp: if line.startswith('|') or not line.strip(): continue diff --git a/ott/geometry/costs.py b/ott/geometry/costs.py index fdf52e7de..eefcf1613 100644 --- a/ott/geometry/costs.py +++ b/ott/geometry/costs.py @@ -188,6 +188,7 @@ def __init__(self, p: float): super().__init__() assert p >= 1.0, "p parameter in p-norm should be >= 1.0" self.p = p + # TODO(marcocuturi): fid case when `p=1` self.q = 1. / (1. - 1. / self.p) if p > 1. else "inf" def h(self, z: jnp.ndarray) -> float: diff --git a/ott/math/implicit_differentiation.py b/ott/math/implicit_differentiation.py index 26bbbea97..8e5cf30c8 100644 --- a/ott/math/implicit_differentiation.py +++ b/ott/math/implicit_differentiation.py @@ -120,8 +120,7 @@ def solve( or kernel matrix are numerically close. To avoid this, we add a more global ``ridge_identity * z`` regularizer to achieve better conditioning. - These linear systems are solved using the user defined - ``implicit_solver_fun``, + These linear systems are solved using the user defined ``solver_fun``, which is set by default to ``cg``. When the system is symmetric (as detected by the corresponding flag ``symmetric``), ``cg`` is applied directly. When it is not, normal equations are used (i.e. the Schur complement is @@ -129,7 +128,7 @@ def solve( Args: gr: 2-tuple, (vector of size ``n``, vector of size ``m``). - ot_prob: the instantiation of the regularizad transport problem. + ot_prob: the instantiation of the regularized transport problem. f: potential, w.r.t marginal a. g: potential, w.r.t marginal b. lse_mode: bool, log-sum-exp mode if True, kernel else. From 8875b2fd0bba89fdfadabc7ad85a4002d9ed411d Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Mon, 21 Nov 2022 16:55:36 +0100 Subject: [PATCH 25/34] Fix more links in the notebooks --- docs/notebooks/gromov_wasserstein_multiomics.ipynb | 4 ++-- docs/notebooks/soft_sort.ipynb | 4 ++-- ott/solvers/nn/layers.py | 5 +++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/notebooks/gromov_wasserstein_multiomics.ipynb b/docs/notebooks/gromov_wasserstein_multiomics.ipynb index 545f9cd9f..778b97cc1 100644 --- a/docs/notebooks/gromov_wasserstein_multiomics.ipynb +++ b/docs/notebooks/gromov_wasserstein_multiomics.ipynb @@ -20,7 +20,7 @@ "The Gromov Wasserstein optimal transport framework, implemented in OTT, is a relevant candidate to perform such an unsupervised cell alignment. \n", "This approach was proposed by Demetci et al. in *Gromov-Wasserstein optimal transport to align single-cell multi-omics data, ICML 2020 Workshop on Computational Biology, 2020* ([ICML article](https://icml-compbio.github.io/icml-website-2020/2020/papers/WCBICML2020_paper_51.pdf), pre-print ) who called it SCOT ([GitHub repo](https://github.com/rsinghlab/SCOT)), from which this notebook is adapted.\n", "\n", - "The original SCOT code code uses [POT](https://github.com/PythonOT/POT) (Python Optimal Transport). Here, we propose a slight modification of the SCOT code to use the `gromov_wasserstein` [function](https://ott-jax.readthedocs.io/en/latest/notebooks/gromov_wasserstein.html) from `ott.core`, which we have found to be faster than the POT implementation `ot.gromov.entropic_gromov_wasserstein` on GPU (see *Alignment and evaluation*). We then use this OTT version of the SCOT algorithm to perform cell alignment for the SNARE-seq dataset , which contains measures of two natures:\n", + "The original SCOT code code uses [POT](https://github.com/PythonOT/POT) (Python Optimal Transport). Here, we propose a slight modification of the SCOT code to use the [gromov_wasserstein function](../solvers/_autosummary/ott.solvers.quadratic.gromov_wasserstein.gromov_wasserstein.html), which we have found to be faster than the POT implementation `ot.gromov.entropic_gromov_wasserstein` on GPU (see *Alignment and evaluation*). We then use this OTT version of the SCOT algorithm to perform cell alignment for the SNARE-seq dataset , which contains measures of two natures:\n", "\n", " - Chromatin accessibility ([scATAC-seq](https://en.wikipedia.org/wiki/ATAC-seq) data)\n", " - Gene expression ([scRNA-seq](https://en.wikipedia.org/wiki/Single_cell_sequencing#scRNA-Seq) data)" @@ -137,7 +137,7 @@ "id": "G8nC8PpHsuuS" }, "source": [ - "## Using `gromov_wasserstein` from `ott.core` to perform OT" + "## Using `gromov_wasserstein` from OTT" ] }, { diff --git a/docs/notebooks/soft_sort.ipynb b/docs/notebooks/soft_sort.ipynb index 244cf2976..0a2f3d661 100644 --- a/docs/notebooks/soft_sort.ipynb +++ b/docs/notebooks/soft_sort.ipynb @@ -183,7 +183,7 @@ "\n", "This colab shows how _soft_ counterparts to these operators are defined in OTT. By _soft_, we mean **differentiable**, **approximate** proxies to these original _\"hard\"_ operators. For instance `soft_sort.ranks` returned by OTT operators won't be integer valued, but instead floating point approximations; `soft_sort.sort` will not contain exactly the `n` values contained in the input array, reordered, but instead `n` combinaisons of thoses values that look very close to them.\n", "\n", - "**These soft operators trade off accuracy for a more informative Jacobian**. This trade-off is controlled by a non-negative parameter `epsilon`: The *smaller* `epsilon`, the closer to the original ranking and sorting operations; The *bigger*, the more bias yet the more informative gradients. That `epsilon` also correponds to that used in regularized OT (see doc on [sinkhorn](https://ott-jax.readthedocs.io/en/latest/_autosummary/ott.core.sinkhorn.sinkhorn.html#ott.core.sinkhorn.sinkhorn)).\n", + "**These soft operators trade off accuracy for a more informative Jacobian**. This trade-off is controlled by a non-negative parameter `epsilon`: The *smaller* `epsilon`, the closer to the original ranking and sorting operations; The *bigger*, the more bias yet the more informative gradients. That `epsilon` also correponds to that used in regularized OT (see doc on [sinkhorn](../solvers/_autosummary/ott.solvers.linear.sinkhorn.sinkhorn-function.html)).\n", "\n", "The behavior of these operators is illustrated below." ] @@ -599,7 +599,7 @@ "\n", "In this tutorial we show how OTT can be used to implement a loss based on soft ranks. That soft 0-1 loss is used here to train a neural network for image classification, as done by Cuturi et al.\n", "\n", - "This implementation relies on [FLAX](https://github.com/google/flax) a neural network library for JAX." + "This implementation relies on [Flax](https://github.com/google/flax) a neural network library for JAX." ] }, { diff --git a/ott/solvers/nn/layers.py b/ott/solvers/nn/layers.py index a0add5a9d..770d966dd 100644 --- a/ott/solvers/nn/layers.py +++ b/ott/solvers/nn/layers.py @@ -42,8 +42,9 @@ class PositiveDense(nn.Module): bias_init: initializer function for the bias. """ dim_hidden: int - rectifier_fn: Callable = nn.softplus - inv_rectifier_fn: Callable = lambda x: jnp.log(jnp.exp(x) - 1) + rectifier_fn: Callable[[jnp.ndarray], jnp.ndarray] = nn.softplus + inv_rectifier_fn: Callable[[jnp.ndarray], + jnp.ndarray] = lambda x: jnp.log(jnp.exp(x) - 1) use_bias: bool = True dtype: Any = jnp.float32 precision: Any = None From 791bfed58e2330321982d2d641de49dc0c3f544e Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Mon, 21 Nov 2022 17:43:56 +0100 Subject: [PATCH 26/34] Follow line length in README.md --- .editorconfig | 2 +- CONTRIBUTING.md | 2 +- README.md | 48 +++++++++++++++++++++++++++++++++--------------- 3 files changed, 35 insertions(+), 17 deletions(-) diff --git a/.editorconfig b/.editorconfig index a20daede6..e480103dd 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,9 +3,9 @@ root = true [*] end_of_line = lf insert_final_newline = true +charset = utf-8 [*py] -charset = utf-8 indent_size = 2 indent_style = space max_line_length = 80 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6038bcf66..34eb5da00 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,7 +9,7 @@ to the project, participating in discussions or raising issues. 1. fork the repository using the **Fork** button on GitHub or the following [link](https://github.com/ott-jax/ott/fork) 2. ```bash - git clone https://github.com/YOUR_USERNAME/ott + git clone https://github.com//ott cd ott pip install -e .'[dev,test]' pre-commit install diff --git a/README.md b/README.md index 48e8e75f9..6dede0b38 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,43 @@ logo -# Optimal Transport Tools (OTT). - +# Optimal Transport Tools (OTT) [![Tests](https://img.shields.io/github/workflow/status/ott-jax/ott/tests/main)](https://github.com/ott-jax/ott/actions/workflows/tests.yml) [![Docs](https://img.shields.io/readthedocs/ott-jax/latest)](https://ott-jax.readthedocs.io/en/latest/) [![Coverage](https://img.shields.io/codecov/c/github/ott-jax/ott/main)](https://app.codecov.io/gh/ott-jax/ott) -**See [full documentation](https://ott-jax.readthedocs.io/en/latest/).** +**See the [full documentation](https://ott-jax.readthedocs.io/en/latest/).** ## What is OTT-JAX? - -A JAX powered library to compute optimal transport at scale and on accelerators, OTT-JAX includes the fastest implementation of the Sinkhorn algorithm you will find around. We have implemented all tweaks (scheduling, acceleration, initializations) and extensions (low-rank), that can be used directly, or within more advanced problems (Gromov-Wasserstein, barycenters). Some of JAX features, including [JIT](https://jax.readthedocs.io/en/latest/notebooks/quickstart.html#Using-jit-to-speed-up-functions), [auto-vectorization](https://jax.readthedocs.io/en/latest/notebooks/quickstart.html#Auto-vectorization-with-vmap) and [implicit differentiation](https://jax.readthedocs.io/en/latest/notebooks/Custom_derivative_rules_for_Python_code.html) work towards the goal of having end-to-end differentiable outputs. OTT-JAX is developed by a team of researchers from Apple, Google, Meta and many academic contributors, including TU München, Oxford, ENSAE/IP Paris and the Hebrew University. +A JAX powered library to compute optimal transport at scale and on accelerators, OTT-JAX includes the fastest +implementation of the Sinkhorn algorithm you will find around. We have implemented all tweaks (scheduling, +acceleration, initializations) and extensions (low-rank), that can be used directly, or within more advanced problems +(Gromov-Wasserstein, barycenters). Some of JAX features, including +[JIT](https://jax.readthedocs.io/en/latest/notebooks/quickstart.html#Using-jit-to-speed-up-functions), +[auto-vectorization](https://jax.readthedocs.io/en/latest/notebooks/quickstart.html#Auto-vectorization-with-vmap) and +[implicit differentiation](https://jax.readthedocs.io/en/latest/notebooks/Custom_derivative_rules_for_Python_code.html) +work towards the goal of having end-to-end differentiable outputs. OTT-JAX is developed by a team of researchers +from Apple, Google, Meta and many academic contributors, including TU München, Oxford, ENSAE/IP Paris and the +Hebrew University. ## What is optimal transport? +Optimal transport can be loosely described as the branch of mathematics and optimization that studies +*matching problems*: given two families of points, and a cost function on pairs of points, find a `good' (low cost) way +to associate bijectively to every point in the first family another in the second. -Optimal transport can be loosely described as the branch of mathematics and optimization that studies *matching problems*: given two families of points, and a cost function on pairs of points, find a `good' (low cost) way to associate bijectively to every point in the first family another in the second. - -Such problems appear in all areas of science, are easy to describe, yet hard to solve. Indeed, while matching optimally two sets of *n* points using a pairwise cost can be solved with the [Hungarian algorithm](https://en.wikipedia.org/wiki/Hungarian_algorithm), solving it costs an order of $O(n^3)$ operations, and lacks flexibility, since one may want to couple families of different sizes. +Such problems appear in all areas of science, are easy to describe, yet hard to solve. Indeed, while matching optimally +two sets of *n* points using a pairwise cost can be solved with the +[Hungarian algorithm](https://en.wikipedia.org/wiki/Hungarian_algorithm), solving it costs an order of $O(n^3)$ +operations, and lacks flexibility, since one may want to couple families of different sizes. -Optimal transport extends all of this, through faster algorithms (in $n^2$ or even linear in $n$) along with numerous generalizations that can help it handle weighted sets of different size, partial matchings, and even more evolved so-called quadratic matching problems. +Optimal transport extends all of this, through faster algorithms (in $n^2$ or even linear in $n$) along with numerous +generalizations that can help it handle weighted sets of different size, partial matchings, and even more evolved +so-called quadratic matching problems. -In the simple toy example below, we compute the optimal coupling matrix between two point clouds sampled randomly (2D vectors, compared with the squared Euclidean distance): +In the simple toy example below, we compute the optimal coupling matrix between two point clouds sampled randomly +(2D vectors, compared with the squared Euclidean distance): ## Example - -```py +```python import jax import jax.numpy as jnp from ott.tools import transport @@ -41,17 +54,22 @@ ot = transport.solve(x, y, a=a, b=b) P = ot.matrix ``` -The call to `solve` above works out the optimal transport solution. The `ot` object contains a transport matrix (here of size $12\times 14$) that quantifies a `link strength` between each point of the first point cloud, to one or more points from the second, as illustrated in the plot below. In this toy example, most choices were arbitrary, and are reflected in the crude `solve` API. We provide far more flexibility to define custom cost functions, objectives, and solvers, as detailed in the [full documentation](https://ott-jax.readthedocs.io/en/latest/). +The call to `solve` above works out the optimal transport solution. The `ot` object contains a transport matrix +(here of size $12\times 14$) that quantifies a `link strength` between each point of the first point cloud, to one or +more points from the second, as illustrated in the plot below. In this toy example, most choices were arbitrary, and +are reflected in the crude `solve` API. We provide far more flexibility to define custom cost functions, objectives, +and solvers, as detailed in the [full documentation](https://ott-jax.readthedocs.io/en/latest/). ![obtained coupling](https://raw.githubusercontent.com/ott-jax/ott/main/images/couplings.png) -## Citation +## Citation If you have found this work useful, please consider citing this reference: ``` @article{cuturi2022optimal, title={Optimal Transport Tools (OTT): A JAX Toolbox for all things Wasserstein}, - author={Cuturi, Marco and Meng-Papaxanthos, Laetitia and Tian, Yingtao and Bunne, Charlotte and Davis, Geoff and Teboul, Olivier}, + author={Cuturi, Marco and Meng-Papaxanthos, Laetitia and Tian, Yingtao and Bunne, Charlotte and + Davis, Geoff and Teboul, Olivier}, journal={arXiv preprint arXiv:2201.12324}, year={2022} } From 41a7ca7e59269f1780d21516fef1de054aa9a025 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Tue, 22 Nov 2022 09:31:26 +0100 Subject: [PATCH 27/34] Update `tests` structure --- .../{geometry_costs_test.py => costs_test.py} | 0 .../{geometry_lr_test.py => low_rank_test.py} | 0 ...cloud_apply_test.py => pointcloud_test.py} | 0 ...etry_subset_test.py => subsetting_test.py} | 4 +- .../linear/sinkhorn_init_test.py} | 261 +----------------- .../linear/sinkhorn_lr_init_test.py | 171 ++++++++++++ tests/initializers/quadratic/gw_init_test.py | 132 +++++++++ .../geometry_lse_test.py => math/lse_test.py} | 0 .../matrix_square_root_test.py | 0 .../linear}/potentials_test.py | 0 .../linear}/continuous_barycenter_test.py | 158 +---------- .../linear}/discrete_barycenter_test.py | 2 - .../linear}/sinkhorn_diff_test.py | 0 .../linear}/sinkhorn_grid_test.py | 0 .../linear}/sinkhorn_lr_test.py | 0 .../linear/sinkhorn_misc_test.py} | 2 +- .../{core => solvers/linear}/sinkhorn_test.py | 0 tests/{core => solvers/nn}/icnn_test.py | 0 tests/{core => solvers/nn}/neuraldual_test.py | 0 .../solvers/quadratic/fgw_barycenter_test.py | 78 ++++++ .../quadratic/fgw_test.py} | 40 +-- tests/solvers/quadratic/gw_barycenter_test.py | 113 ++++++++ .../quadratic/gw_test.py} | 22 +- 23 files changed, 531 insertions(+), 452 deletions(-) rename tests/geometry/{geometry_costs_test.py => costs_test.py} (100%) rename tests/geometry/{geometry_lr_test.py => low_rank_test.py} (100%) rename tests/geometry/{geometry_pointcloud_apply_test.py => pointcloud_test.py} (100%) rename tests/geometry/{geometry_subset_test.py => subsetting_test.py} (98%) rename tests/{core/initializers_test.py => initializers/linear/sinkhorn_init_test.py} (50%) create mode 100644 tests/initializers/linear/sinkhorn_lr_init_test.py create mode 100644 tests/initializers/quadratic/gw_init_test.py rename tests/{geometry/geometry_lse_test.py => math/lse_test.py} (100%) rename tests/{geometry => math}/matrix_square_root_test.py (100%) rename tests/{core => problems/linear}/potentials_test.py (100%) rename tests/{core => solvers/linear}/continuous_barycenter_test.py (69%) rename tests/{core => solvers/linear}/discrete_barycenter_test.py (99%) rename tests/{core => solvers/linear}/sinkhorn_diff_test.py (100%) rename tests/{core => solvers/linear}/sinkhorn_grid_test.py (100%) rename tests/{core => solvers/linear}/sinkhorn_lr_test.py (100%) rename tests/{core/sinkhorn_extra_test.py => solvers/linear/sinkhorn_misc_test.py} (99%) rename tests/{core => solvers/linear}/sinkhorn_test.py (100%) rename tests/{core => solvers/nn}/icnn_test.py (100%) rename tests/{core => solvers/nn}/neuraldual_test.py (100%) create mode 100644 tests/solvers/quadratic/fgw_barycenter_test.py rename tests/{core/fused_gromov_wasserstein_test.py => solvers/quadratic/fgw_test.py} (92%) create mode 100644 tests/solvers/quadratic/gw_barycenter_test.py rename tests/{core/gromov_wasserstein_test.py => solvers/quadratic/gw_test.py} (96%) diff --git a/tests/geometry/geometry_costs_test.py b/tests/geometry/costs_test.py similarity index 100% rename from tests/geometry/geometry_costs_test.py rename to tests/geometry/costs_test.py diff --git a/tests/geometry/geometry_lr_test.py b/tests/geometry/low_rank_test.py similarity index 100% rename from tests/geometry/geometry_lr_test.py rename to tests/geometry/low_rank_test.py diff --git a/tests/geometry/geometry_pointcloud_apply_test.py b/tests/geometry/pointcloud_test.py similarity index 100% rename from tests/geometry/geometry_pointcloud_apply_test.py rename to tests/geometry/pointcloud_test.py diff --git a/tests/geometry/geometry_subset_test.py b/tests/geometry/subsetting_test.py similarity index 98% rename from tests/geometry/geometry_subset_test.py rename to tests/geometry/subsetting_test.py index 5d57e6eb9..2369e4b28 100644 --- a/tests/geometry/geometry_subset_test.py +++ b/tests/geometry/subsetting_test.py @@ -11,7 +11,9 @@ @pytest.fixture() -def pc_masked(rng: jnp.ndarray) -> Tuple[pointcloud.PointCloud, Tuple]: +def pc_masked( + rng: jnp.ndarray +) -> Tuple[pointcloud.PointCloud, pointcloud.PointCloud]: n, m = 20, 30 key1, key2 = jax.random.split(rng, 2) # x = jnp.full((n,), fill_value=1.) diff --git a/tests/core/initializers_test.py b/tests/initializers/linear/sinkhorn_init_test.py similarity index 50% rename from tests/core/initializers_test.py rename to tests/initializers/linear/sinkhorn_init_test.py index 47cdc10e4..27e3f347d 100644 --- a/tests/core/initializers_test.py +++ b/tests/initializers/linear/sinkhorn_init_test.py @@ -20,14 +20,10 @@ import pytest import ott.initializers.nn.initializers -from ott.geometry import geometry, low_rank, pointcloud +from ott.geometry import geometry, pointcloud from ott.initializers.linear import initializers as lin_init -from ott.initializers.linear import initializers_lr -from ott.initializers.quadratic import initializers as quad_init from ott.problems.linear import linear_problem -from ott.problems.quadratic import quadratic_problem -from ott.solvers.linear import sinkhorn, sinkhorn_lr -from ott.solvers.quadratic import gromov_wasserstein +from ott.solvers.linear import sinkhorn def create_sorting_problem(rng, n, epsilon=0.01, online=False): @@ -333,256 +329,3 @@ def test_meta_initializer(self, lse_mode, rng: jnp.ndarray): # check initializer is better if lse_mode: assert base_num_iter >= meta_num_iter - - -class TestLRInitializers: - - @pytest.mark.fast.with_args("kind", ["pc", "lrc", "geom"], only_fast=0) - def test_create_default_initializer(self, rng: jnp.ndarray, kind: str): - n, d, rank = 110, 2, 3 - x = jax.random.normal(rng, (n, d)) - geom = pointcloud.PointCloud(x) - - if kind == "pc": - pass - elif kind == "lrc": - geom = geom.to_LRCGeometry() - assert isinstance(geom, low_rank.LRCGeometry) - elif kind == "geom": - geom = geometry.Geometry(geom.cost_matrix) - else: - raise NotImplementedError(geom) - prob = linear_problem.LinearProblem(geom) - - solver = sinkhorn_lr.LRSinkhorn(rank=rank, initializer=None) - initializer = solver.create_initializer(prob) - - assert initializer.rank == rank - if kind in ("pc", "lrc"): - assert isinstance(initializer, initializers_lr.KMeansInitializer) - else: - assert isinstance(initializer, initializers_lr.RandomInitializer) - - q, r, g = initializer(prob) - - assert q.shape == (n, rank) - assert r.shape == (n, rank) - assert g.shape == (rank,) - - def test_explicitly_passing_initializer(self): - rank = 2 - initializer = initializers_lr.RandomInitializer(rank=rank) - solver = sinkhorn_lr.LRSinkhorn(rank=rank, initializer=initializer) - - assert solver.create_initializer(prob="not used") is initializer - - @pytest.mark.parametrize( - "initializer", ["random", "rank2", "k-means", "generalized-k-means"] - ) - @pytest.mark.parametrize("partial_init", ["q", "r", "g"]) - def test_partial_initialization( - self, rng: jnp.ndarray, initializer: str, partial_init: str - ): - n, d, rank = 100, 10, 6 - key1, key2, key3, key4 = jax.random.split(rng, 4) - x = jax.random.normal(key1, (n, d)) - pc = pointcloud.PointCloud(x, epsilon=5e-1) - prob = linear_problem.LinearProblem(pc) - q_init = jax.random.normal(key2, (n, rank)) - r_init = jax.random.normal(key2, (n, rank)) - g_init = jax.random.normal(key2, (rank,)) - - solver = sinkhorn_lr.LRSinkhorn(rank=rank, initializer=initializer) - initializer = solver.create_initializer(prob) - - if partial_init == "q": - q, _, _ = initializer(prob, q=q_init) - np.testing.assert_array_equal(q, q_init) - elif partial_init == "r": - _, r, _ = initializer(prob, r=r_init) - np.testing.assert_array_equal(r, r_init) - elif partial_init == "g": - _, _, g = initializer(prob, g=g_init) - np.testing.assert_array_equal(g, g_init) - else: - raise NotImplementedError(partial_init) - - @pytest.mark.fast.with_args("rank", [2, 4, 10, 13], only_fast=True) - def test_generalized_k_means_has_correct_rank( - self, rng: jnp.ndarray, rank: int - ): - n, d = 100, 10 - x = jax.random.normal(rng, (n, d)) - pc = pointcloud.PointCloud(x, epsilon=5e-1) - prob = linear_problem.LinearProblem(pc) - - solver = sinkhorn_lr.LRSinkhorn( - rank=rank, initializer="generalized-k-means" - ) - initializer = solver.create_initializer(prob) - - q, r, g = initializer(prob) - - assert jnp.linalg.matrix_rank(q) == rank - assert jnp.linalg.matrix_rank(r) == rank - - def test_generalized_k_means_matches_k_means(self, rng: jnp.ndarray): - n, d, rank = 120, 15, 5 - eps = 1e-1 - key1, key2 = jax.random.split(rng, 2) - x = jax.random.normal(key1, (n, d)) - y = jax.random.normal(key1, (n, d)) - - pc = pointcloud.PointCloud(x, y, epsilon=eps) - geom = geometry.Geometry(cost_matrix=pc.cost_matrix, epsilon=eps) - pc_problem = linear_problem.LinearProblem(pc) - geom_problem = linear_problem.LinearProblem(geom) - - solver = sinkhorn_lr.LRSinkhorn( - rank=rank, initializer="k-means", max_iterations=5000 - ) - pc_out = solver(pc_problem) - - solver = sinkhorn_lr.LRSinkhorn( - rank=rank, initializer="generalized-k-means", max_iterations=5000 - ) - geom_out = solver(geom_problem) - - with pytest.raises(AssertionError): - np.testing.assert_allclose(pc_out.costs, geom_out.costs) - - np.testing.assert_allclose( - pc_out.reg_ot_cost, geom_out.reg_ot_cost, atol=0.5, rtol=0.02 - ) - - @pytest.mark.parametrize("epsilon", [0., 1e-1]) - def test_better_initialization_helps(self, rng: jnp.ndarray, epsilon: float): - n, d, rank = 81, 13, 3 - key1, key2 = jax.random.split(rng, 2) - x = jax.random.normal(key1, (n, d)) - y = jax.random.normal(key2, (n, d)) - pc = pointcloud.PointCloud(x, y, epsilon=5e-1) - prob = linear_problem.LinearProblem(pc) - - solver_random = sinkhorn_lr.LRSinkhorn( - rank=rank, epsilon=epsilon, initializer="random", max_iterations=10000 - ) - solver_init = sinkhorn_lr.LRSinkhorn( - rank=rank, epsilon=epsilon, initializer="k-means", max_iterations=10000 - ) - - out_random = solver_random(prob) - out_init = solver_init(prob) - - assert out_random.converged - assert out_init.converged - # converged earlier - assert (out_init.errors > -1).sum() < (out_random.errors > -1).sum() - # converged to a better solution - assert out_init.reg_ot_cost < out_random.reg_ot_cost - - -class TestQuadraticInitializers: - - @pytest.mark.parametrize("kind", ["pc", "lrc", "geom"]) - def test_create_default_lr_initializer(self, rng: jnp.ndarray, kind: str): - n, d1, d2, rank = 150, 2, 3, 5 - eps = 1e-1 - key1, key2 = jax.random.split(rng, 2) - x = jax.random.normal(key1, (n, d1)) - y = jax.random.normal(key1, (n, d2)) - kwargs_init = {"foo": "bar"} - - geom_x = pointcloud.PointCloud(x, epsilon=eps) - geom_y = pointcloud.PointCloud(y, epsilon=eps) - if kind == "pc": - pass - elif kind == "lrc": - geom_x = geom_x.to_LRCGeometry() - geom_y = geom_y.to_LRCGeometry() - elif kind == "geom": - geom_x = geometry.Geometry(geom_x.cost_matrix, epsilon=eps) - geom_y = geometry.Geometry(geom_y.cost_matrix, epsilon=eps) - else: - raise NotImplementedError(kind) - prob = quadratic_problem.QuadraticProblem(geom_x, geom_y) - - solver = gromov_wasserstein.GromovWasserstein( - rank=rank, quad_initializer=None, kwargs_init=kwargs_init - ) - initializer = solver.create_initializer(prob) - - assert isinstance(initializer, quad_init.LRQuadraticInitializer) - assert initializer.rank == rank - linear_init = initializer._linear_lr_initializer - if kind in ("pc", "lrc"): - assert isinstance(linear_init, initializers_lr.KMeansInitializer) - else: - assert isinstance(linear_init, initializers_lr.RandomInitializer) - assert linear_init._kwargs == kwargs_init - - def test_non_lr_initializer(self): - solver = gromov_wasserstein.GromovWasserstein( - rank=-1, quad_initializer="not used" - ) - initializer = solver.create_initializer(prob="not used") - assert isinstance(initializer, quad_init.QuadraticInitializer) - - @pytest.mark.parametrize("rank", [-1, 2]) - def test_explicitly_passing_initializer(self, rank: int): - if rank == -1: - linear_init = lin_init.SortingInitializer() - q_init = quad_init.QuadraticInitializer() - else: - linear_init = initializers_lr.Rank2Initializer(rank) - q_init = quad_init.LRQuadraticInitializer(linear_init) - - solver = gromov_wasserstein.GromovWasserstein( - initializer=linear_init, - quad_initializer=q_init, - ) - - assert solver.linear_ot_solver.initializer is linear_init - assert solver.quad_initializer is q_init - if solver.is_low_rank: - assert solver.quad_initializer.rank == rank - - @pytest.mark.parametrize("eps", [0., 1e-2]) - def test_gw_better_initialization_helps(self, rng: jnp.ndarray, eps: float): - n, m, d1, d2, rank = 123, 124, 12, 10, 5 - key1, key2, key3, key4 = jax.random.split(rng, 4) - - geom_x = pointcloud.PointCloud( - jax.random.normal(key1, (n, d1)), - jax.random.normal(key2, (n, d1)), - epsilon=eps, - ) - geom_y = pointcloud.PointCloud( - jax.random.normal(key3, (m, d2)), - jax.random.normal(key4, (m, d2)), - epsilon=eps, - ) - problem = quadratic_problem.QuadraticProblem(geom_x, geom_y) - solver_random = gromov_wasserstein.GromovWasserstein( - rank=rank, - initializer="random", - quad_initializer="random", - epsilon=eps, - store_inner_errors=True, - ) - solver_kmeans = gromov_wasserstein.GromovWasserstein( - rank=rank, - initializer="k-means", - quad_initializer="k-means", - epsilon=eps, - store_inner_errors=True - ) - - out_random = solver_random(problem) - out_kmeans = solver_kmeans(problem) - - assert out_random.reg_gw_cost - out_kmeans.reg_gw_cost >= 1. - random_errors = out_random.errors[out_random.errors > -1] - kmeans_errors = out_kmeans.errors[out_kmeans.errors > -1] - np.testing.assert_array_equal(random_errors >= 0., True) - np.testing.assert_array_equal(kmeans_errors >= 0., True) diff --git a/tests/initializers/linear/sinkhorn_lr_init_test.py b/tests/initializers/linear/sinkhorn_lr_init_test.py new file mode 100644 index 000000000..bff23ec2e --- /dev/null +++ b/tests/initializers/linear/sinkhorn_lr_init_test.py @@ -0,0 +1,171 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Lint as: python3 +"""Tests for Sinkhorn initializers.""" + +import jax +import jax.numpy as jnp +import numpy as np +import pytest + +from ott.geometry import geometry, low_rank, pointcloud +from ott.initializers.linear import initializers_lr +from ott.problems.linear import linear_problem +from ott.solvers.linear import sinkhorn_lr + + +class TestLRInitializers: + + @pytest.mark.fast.with_args("kind", ["pc", "lrc", "geom"], only_fast=0) + def test_create_default_initializer(self, rng: jnp.ndarray, kind: str): + n, d, rank = 110, 2, 3 + x = jax.random.normal(rng, (n, d)) + geom = pointcloud.PointCloud(x) + + if kind == "pc": + pass + elif kind == "lrc": + geom = geom.to_LRCGeometry() + assert isinstance(geom, low_rank.LRCGeometry) + elif kind == "geom": + geom = geometry.Geometry(geom.cost_matrix) + else: + raise NotImplementedError(geom) + prob = linear_problem.LinearProblem(geom) + + solver = sinkhorn_lr.LRSinkhorn(rank=rank, initializer=None) + initializer = solver.create_initializer(prob) + + assert initializer.rank == rank + if kind in ("pc", "lrc"): + assert isinstance(initializer, initializers_lr.KMeansInitializer) + else: + assert isinstance(initializer, initializers_lr.RandomInitializer) + + q, r, g = initializer(prob) + + assert q.shape == (n, rank) + assert r.shape == (n, rank) + assert g.shape == (rank,) + + def test_explicitly_passing_initializer(self): + rank = 2 + initializer = initializers_lr.RandomInitializer(rank=rank) + solver = sinkhorn_lr.LRSinkhorn(rank=rank, initializer=initializer) + + assert solver.create_initializer(prob="not used") is initializer + + @pytest.mark.parametrize( + "initializer", ["random", "rank2", "k-means", "generalized-k-means"] + ) + @pytest.mark.parametrize("partial_init", ["q", "r", "g"]) + def test_partial_initialization( + self, rng: jnp.ndarray, initializer: str, partial_init: str + ): + n, d, rank = 100, 10, 6 + key1, key2, key3, key4 = jax.random.split(rng, 4) + x = jax.random.normal(key1, (n, d)) + pc = pointcloud.PointCloud(x, epsilon=5e-1) + prob = linear_problem.LinearProblem(pc) + q_init = jax.random.normal(key2, (n, rank)) + r_init = jax.random.normal(key2, (n, rank)) + g_init = jax.random.normal(key2, (rank,)) + + solver = sinkhorn_lr.LRSinkhorn(rank=rank, initializer=initializer) + initializer = solver.create_initializer(prob) + + if partial_init == "q": + q, _, _ = initializer(prob, q=q_init) + np.testing.assert_array_equal(q, q_init) + elif partial_init == "r": + _, r, _ = initializer(prob, r=r_init) + np.testing.assert_array_equal(r, r_init) + elif partial_init == "g": + _, _, g = initializer(prob, g=g_init) + np.testing.assert_array_equal(g, g_init) + else: + raise NotImplementedError(partial_init) + + @pytest.mark.fast.with_args("rank", [2, 4, 10, 13], only_fast=True) + def test_generalized_k_means_has_correct_rank( + self, rng: jnp.ndarray, rank: int + ): + n, d = 100, 10 + x = jax.random.normal(rng, (n, d)) + pc = pointcloud.PointCloud(x, epsilon=5e-1) + prob = linear_problem.LinearProblem(pc) + + solver = sinkhorn_lr.LRSinkhorn( + rank=rank, initializer="generalized-k-means" + ) + initializer = solver.create_initializer(prob) + + q, r, g = initializer(prob) + + assert jnp.linalg.matrix_rank(q) == rank + assert jnp.linalg.matrix_rank(r) == rank + + def test_generalized_k_means_matches_k_means(self, rng: jnp.ndarray): + n, d, rank = 120, 15, 5 + eps = 1e-1 + key1, key2 = jax.random.split(rng, 2) + x = jax.random.normal(key1, (n, d)) + y = jax.random.normal(key1, (n, d)) + + pc = pointcloud.PointCloud(x, y, epsilon=eps) + geom = geometry.Geometry(cost_matrix=pc.cost_matrix, epsilon=eps) + pc_problem = linear_problem.LinearProblem(pc) + geom_problem = linear_problem.LinearProblem(geom) + + solver = sinkhorn_lr.LRSinkhorn( + rank=rank, initializer="k-means", max_iterations=5000 + ) + pc_out = solver(pc_problem) + + solver = sinkhorn_lr.LRSinkhorn( + rank=rank, initializer="generalized-k-means", max_iterations=5000 + ) + geom_out = solver(geom_problem) + + with pytest.raises(AssertionError): + np.testing.assert_allclose(pc_out.costs, geom_out.costs) + + np.testing.assert_allclose( + pc_out.reg_ot_cost, geom_out.reg_ot_cost, atol=0.5, rtol=0.02 + ) + + @pytest.mark.parametrize("epsilon", [0., 1e-1]) + def test_better_initialization_helps(self, rng: jnp.ndarray, epsilon: float): + n, d, rank = 81, 13, 3 + key1, key2 = jax.random.split(rng, 2) + x = jax.random.normal(key1, (n, d)) + y = jax.random.normal(key2, (n, d)) + pc = pointcloud.PointCloud(x, y, epsilon=5e-1) + prob = linear_problem.LinearProblem(pc) + + solver_random = sinkhorn_lr.LRSinkhorn( + rank=rank, epsilon=epsilon, initializer="random", max_iterations=10000 + ) + solver_init = sinkhorn_lr.LRSinkhorn( + rank=rank, epsilon=epsilon, initializer="k-means", max_iterations=10000 + ) + + out_random = solver_random(prob) + out_init = solver_init(prob) + + assert out_random.converged + assert out_init.converged + # converged earlier + assert (out_init.errors > -1).sum() < (out_random.errors > -1).sum() + # converged to a better solution + assert out_init.reg_ot_cost < out_random.reg_ot_cost diff --git a/tests/initializers/quadratic/gw_init_test.py b/tests/initializers/quadratic/gw_init_test.py new file mode 100644 index 000000000..b900b60fc --- /dev/null +++ b/tests/initializers/quadratic/gw_init_test.py @@ -0,0 +1,132 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Lint as: python3 +"""Tests for Gromov-Wasserstein initializers.""" + +import jax +import jax.numpy as jnp +import numpy as np +import pytest + +from ott.geometry import geometry, pointcloud +from ott.initializers.linear import initializers as lin_init +from ott.initializers.linear import initializers_lr +from ott.initializers.quadratic import initializers as quad_init +from ott.problems.quadratic import quadratic_problem +from ott.solvers.quadratic import gromov_wasserstein + + +class TestQuadraticInitializers: + + @pytest.mark.parametrize("kind", ["pc", "lrc", "geom"]) + def test_create_default_lr_initializer(self, rng: jnp.ndarray, kind: str): + n, d1, d2, rank = 150, 2, 3, 5 + eps = 1e-1 + key1, key2 = jax.random.split(rng, 2) + x = jax.random.normal(key1, (n, d1)) + y = jax.random.normal(key1, (n, d2)) + kwargs_init = {"foo": "bar"} + + geom_x = pointcloud.PointCloud(x, epsilon=eps) + geom_y = pointcloud.PointCloud(y, epsilon=eps) + if kind == "pc": + pass + elif kind == "lrc": + geom_x = geom_x.to_LRCGeometry() + geom_y = geom_y.to_LRCGeometry() + elif kind == "geom": + geom_x = geometry.Geometry(geom_x.cost_matrix, epsilon=eps) + geom_y = geometry.Geometry(geom_y.cost_matrix, epsilon=eps) + else: + raise NotImplementedError(kind) + prob = quadratic_problem.QuadraticProblem(geom_x, geom_y) + + solver = gromov_wasserstein.GromovWasserstein( + rank=rank, quad_initializer=None, kwargs_init=kwargs_init + ) + initializer = solver.create_initializer(prob) + + assert isinstance(initializer, quad_init.LRQuadraticInitializer) + assert initializer.rank == rank + linear_init = initializer._linear_lr_initializer + if kind in ("pc", "lrc"): + assert isinstance(linear_init, initializers_lr.KMeansInitializer) + else: + assert isinstance(linear_init, initializers_lr.RandomInitializer) + assert linear_init._kwargs == kwargs_init + + def test_non_lr_initializer(self): + solver = gromov_wasserstein.GromovWasserstein( + rank=-1, quad_initializer="not used" + ) + initializer = solver.create_initializer(prob="not used") + assert isinstance(initializer, quad_init.QuadraticInitializer) + + @pytest.mark.parametrize("rank", [-1, 2]) + def test_explicitly_passing_initializer(self, rank: int): + if rank == -1: + linear_init = lin_init.SortingInitializer() + q_init = quad_init.QuadraticInitializer() + else: + linear_init = initializers_lr.Rank2Initializer(rank) + q_init = quad_init.LRQuadraticInitializer(linear_init) + + solver = gromov_wasserstein.GromovWasserstein( + initializer=linear_init, + quad_initializer=q_init, + ) + + assert solver.linear_ot_solver.initializer is linear_init + assert solver.quad_initializer is q_init + if solver.is_low_rank: + assert solver.quad_initializer.rank == rank + + @pytest.mark.parametrize("eps", [0., 1e-2]) + def test_gw_better_initialization_helps(self, rng: jnp.ndarray, eps: float): + n, m, d1, d2, rank = 123, 124, 12, 10, 5 + key1, key2, key3, key4 = jax.random.split(rng, 4) + + geom_x = pointcloud.PointCloud( + jax.random.normal(key1, (n, d1)), + jax.random.normal(key2, (n, d1)), + epsilon=eps, + ) + geom_y = pointcloud.PointCloud( + jax.random.normal(key3, (m, d2)), + jax.random.normal(key4, (m, d2)), + epsilon=eps, + ) + problem = quadratic_problem.QuadraticProblem(geom_x, geom_y) + solver_random = gromov_wasserstein.GromovWasserstein( + rank=rank, + initializer="random", + quad_initializer="random", + epsilon=eps, + store_inner_errors=True, + ) + solver_kmeans = gromov_wasserstein.GromovWasserstein( + rank=rank, + initializer="k-means", + quad_initializer="k-means", + epsilon=eps, + store_inner_errors=True + ) + + out_random = solver_random(problem) + out_kmeans = solver_kmeans(problem) + + assert out_random.reg_gw_cost - out_kmeans.reg_gw_cost >= 1. + random_errors = out_random.errors[out_random.errors > -1] + kmeans_errors = out_kmeans.errors[out_kmeans.errors > -1] + np.testing.assert_array_equal(random_errors >= 0., True) + np.testing.assert_array_equal(kmeans_errors >= 0., True) diff --git a/tests/geometry/geometry_lse_test.py b/tests/math/lse_test.py similarity index 100% rename from tests/geometry/geometry_lse_test.py rename to tests/math/lse_test.py diff --git a/tests/geometry/matrix_square_root_test.py b/tests/math/matrix_square_root_test.py similarity index 100% rename from tests/geometry/matrix_square_root_test.py rename to tests/math/matrix_square_root_test.py diff --git a/tests/core/potentials_test.py b/tests/problems/linear/potentials_test.py similarity index 100% rename from tests/core/potentials_test.py rename to tests/problems/linear/potentials_test.py diff --git a/tests/core/continuous_barycenter_test.py b/tests/solvers/linear/continuous_barycenter_test.py similarity index 69% rename from tests/core/continuous_barycenter_test.py rename to tests/solvers/linear/continuous_barycenter_test.py index 2578a459c..36fdf1d97 100644 --- a/tests/core/continuous_barycenter_test.py +++ b/tests/solvers/linear/continuous_barycenter_test.py @@ -14,18 +14,16 @@ # Lint as: python3 """Tests for continuous barycenter.""" import functools -from typing import Any, Optional, Sequence, Tuple +from typing import Tuple import jax import jax.numpy as jnp import numpy as np import pytest -from ott.geometry import costs, pointcloud, segment +from ott.geometry import costs, segment from ott.problems.linear import barycenter_problem -from ott.problems.quadratic import gw_barycenter as gwb from ott.solvers.linear import continuous_barycenter as cb -from ott.solvers.quadratic import gw_barycenter as gwb_solver from ott.tools.gaussian_mixture import gaussian_mixture means_and_covs_to_x = jax.vmap(costs.mean_and_cov_to_x, in_axes=[0, 0, None]) @@ -378,155 +376,3 @@ def test_bures_barycenter_different_number_of_components( jax.vmap(is_positive_semidefinite, in_axes=0, out_axes=0)(covs_bary), True ) - - -class TestGWBarycenter: - ndim = 3 - ndim_f = 4 - - @staticmethod - def random_pc( - n: int, - d: int, - rng: jnp.ndarray, - m: Optional[int] = None, - **kwargs: Any - ) -> pointcloud.PointCloud: - key1, key2 = jax.random.split(rng, 2) - x = jax.random.normal(key1, (n, d)) - y = x if m is None else jax.random.normal(key2, (m, d)) - return pointcloud.PointCloud(x, y, batch_size=None, **kwargs) - - @staticmethod - def pad_cost_matrices( - costs: Sequence[jnp.ndarray], - shape: Optional[Tuple[int, int]] = None - ) -> Tuple[jnp.ndarray, jnp.ndarray]: - if shape is None: - shape = jnp.asarray([arr.shape for arr in costs]).max() - shape = (shape, shape) - else: - assert shape[0] == shape[1], shape - - cs, weights = [], [] - for cost in costs: - r, c = cost.shape - cs.append(jnp.zeros(shape).at[:r, :c].set(cost)) - w = jnp.ones(r) / r - weights.append(jnp.concatenate([w, jnp.zeros(shape[0] - r)])) - return jnp.stack(cs), jnp.stack(weights) - - # TODO(cuturi) add back KL test when KL cost GW is fixed. - @pytest.mark.parametrize( - "gw_loss,bar_size,epsilon", - [("sqeucl", 17, None)] #, ("kl", 22, 1e-2)] - ) - def test_gw_barycenter( - self, rng: jnp.ndarray, gw_loss: str, bar_size: int, - epsilon: Optional[float] - ): - tol = 1e-3 if gw_loss == "sqeucl" else 1e-1 - num_per_segment = (13, 15, 21) - rngs = jax.random.split(rng, len(num_per_segment)) - pcs = [ - self.random_pc(n, d=self.ndim, rng=rng) - for n, rng in zip(num_per_segment, rngs) - ] - costs = [pc._compute_cost_matrix() for pc, n in zip(pcs, num_per_segment)] - costs, cbs = self.pad_cost_matrices(costs) - ys = jnp.concatenate([pc.x for pc in pcs]) - bs = jnp.concatenate([jnp.ones(n) / n for n in num_per_segment]) - kwargs = { - "gw_loss": gw_loss, - "num_per_segment": num_per_segment, - "epsilon": epsilon - } - - problem_pc = gwb.GWBarycenterProblem(y=ys, b=bs, **kwargs) - problem_cost = gwb.GWBarycenterProblem( - costs=costs, - b=cbs, - **kwargs, - ) - for prob in [problem_pc, problem_cost]: - assert not prob.is_fused - assert prob.ndim_fused is None - assert prob.num_measures == len(num_per_segment) - assert prob.max_measure_size == max(num_per_segment) - assert prob._loss_name == gw_loss - assert problem_pc.ndim == self.ndim - assert problem_cost.ndim is None - - solver = gwb_solver.GromovWassersteinBarycenter(jit=True) - out_pc = solver(problem_pc, bar_size=bar_size) - out_cost = solver(problem_cost, bar_size=bar_size) - - assert out_pc.x is None - assert out_cost.x is None - assert out_pc.cost.shape == (bar_size, bar_size) - np.testing.assert_allclose(out_pc.cost, out_cost.cost, rtol=tol, atol=tol) - np.testing.assert_allclose(out_pc.costs, out_cost.costs, rtol=tol, atol=tol) - - @pytest.mark.fast( - "jit,fused_penalty,scale_cost", [(False, 1.5, "mean"), - (True, 3.1, "max_cost")], - only_fast=0 - ) - def test_fgw_barycenter( - self, - rng: jnp.ndarray, - jit: bool, - fused_penalty: float, - scale_cost: str, - ): - - def barycenter( - y: jnp.ndim, y_fused: jnp.ndarray, num_per_segment: Tuple[int, ...] - ) -> gwb_solver.GWBarycenterState: - prob = gwb.GWBarycenterProblem( - y=y, - y_fused=y_fused, - num_per_segment=num_per_segment, - fused_penalty=fused_penalty, - scale_cost=scale_cost, - ) - assert prob.is_fused - assert prob.fused_penalty == fused_penalty - assert not prob._y_as_costs - assert prob.max_measure_size == max(num_per_segment) - assert prob.num_measures == len(num_per_segment) - assert prob.ndim == self.ndim - assert prob.ndim_fused == self.ndim_f - - solver = gwb_solver.GromovWassersteinBarycenter( - jit=False, store_inner_errors=True, epsilon=epsilon - ) - - x_init = jax.random.normal(rng, (bar_size, self.ndim_f)) - cost_init = pointcloud.PointCloud(x_init).cost_matrix - - return solver(prob, bar_size=bar_size, bar_init=(cost_init, x_init)) - - bar_size, epsilon, = 10, 1e-1 - num_per_segment = (7, 12) - - key1, *rngs = jax.random.split(rng, len(num_per_segment) + 1) - y = jnp.concatenate([ - self.random_pc(n, d=self.ndim, rng=rng).x - for n, rng in zip(num_per_segment, rngs) - ]) - rngs = jax.random.split(key1, len(num_per_segment)) - y_fused = jnp.concatenate([ - self.random_pc(n, d=self.ndim_f, rng=rng).x - for n, rng in zip(num_per_segment, rngs) - ]) - - fn = jax.jit(barycenter, static_argnums=2) if jit else barycenter - out = fn(y, y_fused, num_per_segment) - - assert out.cost.shape == (bar_size, bar_size) - assert out.x.shape == (bar_size, self.ndim_f) - np.testing.assert_array_equal(jnp.isfinite(out.cost), True) - np.testing.assert_array_equal(jnp.isfinite(out.x), True) - np.testing.assert_array_equal(jnp.isfinite(out.costs), True) - np.testing.assert_array_equal(jnp.isfinite(out.errors), True) diff --git a/tests/core/discrete_barycenter_test.py b/tests/solvers/linear/discrete_barycenter_test.py similarity index 99% rename from tests/core/discrete_barycenter_test.py rename to tests/solvers/linear/discrete_barycenter_test.py index 2b09ac991..8f2bcfa0b 100644 --- a/tests/core/discrete_barycenter_test.py +++ b/tests/solvers/linear/discrete_barycenter_test.py @@ -13,8 +13,6 @@ # limitations under the License. # Lint as: python3 -"""Tests for the Policy.""" - import jax.numpy as jnp import pytest diff --git a/tests/core/sinkhorn_diff_test.py b/tests/solvers/linear/sinkhorn_diff_test.py similarity index 100% rename from tests/core/sinkhorn_diff_test.py rename to tests/solvers/linear/sinkhorn_diff_test.py diff --git a/tests/core/sinkhorn_grid_test.py b/tests/solvers/linear/sinkhorn_grid_test.py similarity index 100% rename from tests/core/sinkhorn_grid_test.py rename to tests/solvers/linear/sinkhorn_grid_test.py diff --git a/tests/core/sinkhorn_lr_test.py b/tests/solvers/linear/sinkhorn_lr_test.py similarity index 100% rename from tests/core/sinkhorn_lr_test.py rename to tests/solvers/linear/sinkhorn_lr_test.py diff --git a/tests/core/sinkhorn_extra_test.py b/tests/solvers/linear/sinkhorn_misc_test.py similarity index 99% rename from tests/core/sinkhorn_extra_test.py rename to tests/solvers/linear/sinkhorn_misc_test.py index 2f64a4c4a..fd1664b50 100644 --- a/tests/core/sinkhorn_extra_test.py +++ b/tests/solvers/linear/sinkhorn_misc_test.py @@ -13,7 +13,7 @@ # limitations under the License. # Lint as: python3 -"""Tests Anderson acceleration for sinkhorn.""" +"""Tests Anderson acceleration for Sinkhorn.""" import functools from typing import Callable, Tuple diff --git a/tests/core/sinkhorn_test.py b/tests/solvers/linear/sinkhorn_test.py similarity index 100% rename from tests/core/sinkhorn_test.py rename to tests/solvers/linear/sinkhorn_test.py diff --git a/tests/core/icnn_test.py b/tests/solvers/nn/icnn_test.py similarity index 100% rename from tests/core/icnn_test.py rename to tests/solvers/nn/icnn_test.py diff --git a/tests/core/neuraldual_test.py b/tests/solvers/nn/neuraldual_test.py similarity index 100% rename from tests/core/neuraldual_test.py rename to tests/solvers/nn/neuraldual_test.py diff --git a/tests/solvers/quadratic/fgw_barycenter_test.py b/tests/solvers/quadratic/fgw_barycenter_test.py new file mode 100644 index 000000000..d3dca9ad6 --- /dev/null +++ b/tests/solvers/quadratic/fgw_barycenter_test.py @@ -0,0 +1,78 @@ +"""Tests for Fused Gromov-Wasserstein barycenter.""" +from typing import Tuple + +import jax +import jax.numpy as jnp +import numpy as np +import pytest + +from ott.geometry import pointcloud +from ott.problems.quadratic import gw_barycenter as gwb +from ott.solvers.quadratic import gw_barycenter as gwb_solver + + +class FGWBarycenterTest: + + @pytest.mark.fast( + "jit,fused_penalty,scale_cost", [(False, 1.5, "mean"), + (True, 3.1, "max_cost")], + only_fast=0 + ) + def test_fgw_barycenter( + self, + rng: jnp.ndarray, + jit: bool, + fused_penalty: float, + scale_cost: str, + ): + + def barycenter( + y: jnp.ndim, y_fused: jnp.ndarray, num_per_segment: Tuple[int, ...] + ) -> gwb_solver.GWBarycenterState: + prob = gwb.GWBarycenterProblem( + y=y, + y_fused=y_fused, + num_per_segment=num_per_segment, + fused_penalty=fused_penalty, + scale_cost=scale_cost, + ) + assert prob.is_fused + assert prob.fused_penalty == fused_penalty + assert not prob._y_as_costs + assert prob.max_measure_size == max(num_per_segment) + assert prob.num_measures == len(num_per_segment) + assert prob.ndim == self.ndim + assert prob.ndim_fused == self.ndim_f + + solver = gwb_solver.GromovWassersteinBarycenter( + jit=False, store_inner_errors=True, epsilon=epsilon + ) + + x_init = jax.random.normal(rng, (bar_size, self.ndim_f)) + cost_init = pointcloud.PointCloud(x_init).cost_matrix + + return solver(prob, bar_size=bar_size, bar_init=(cost_init, x_init)) + + bar_size, epsilon, = 10, 1e-1 + num_per_segment = (7, 12) + + key1, *rngs = jax.random.split(rng, len(num_per_segment) + 1) + y = jnp.concatenate([ + self.random_pc(n, d=self.ndim, rng=rng).x + for n, rng in zip(num_per_segment, rngs) + ]) + rngs = jax.random.split(key1, len(num_per_segment)) + y_fused = jnp.concatenate([ + self.random_pc(n, d=self.ndim_f, rng=rng).x + for n, rng in zip(num_per_segment, rngs) + ]) + + fn = jax.jit(barycenter, static_argnums=2) if jit else barycenter + out = fn(y, y_fused, num_per_segment) + + assert out.cost.shape == (bar_size, bar_size) + assert out.x.shape == (bar_size, self.ndim_f) + np.testing.assert_array_equal(jnp.isfinite(out.cost), True) + np.testing.assert_array_equal(jnp.isfinite(out.x), True) + np.testing.assert_array_equal(jnp.isfinite(out.costs), True) + np.testing.assert_array_equal(jnp.isfinite(out.errors), True) diff --git a/tests/core/fused_gromov_wasserstein_test.py b/tests/solvers/quadratic/fgw_test.py similarity index 92% rename from tests/core/fused_gromov_wasserstein_test.py rename to tests/solvers/quadratic/fgw_test.py index ebd15e7de..338ffafb1 100644 --- a/tests/core/fused_gromov_wasserstein_test.py +++ b/tests/solvers/quadratic/fgw_test.py @@ -23,7 +23,7 @@ from ott.geometry import geometry, low_rank, pointcloud from ott.problems.quadratic import quadratic_problem -from ott.solvers.quadratic import gromov_wasserstein as gwb_solver +from ott.solvers.quadratic import gromov_wasserstein as gw_solver class TestFusedGromovWasserstein: @@ -50,13 +50,13 @@ def initialize(self, rng: jnp.ndarray): self.cy = jax.random.uniform(keys[5], (self.m, self.m)) self.cxy = jax.random.uniform(keys[6], (self.n, self.m)) - def test_flag_store_errors_fused(self): + def test_fgw_flag_store_errors_fused(self): """Tests whether errors are properly stored if requested.""" threshold_sinkhorn = 1e-2 geom_x = pointcloud.PointCloud(self.x) geom_y = pointcloud.PointCloud(self.y) geom_xy = pointcloud.PointCloud(self.x_2, self.y_2) - out = gwb_solver.gromov_wasserstein( + out = gw_solver.gromov_wasserstein( geom_xx=geom_x, geom_yy=geom_y, geom_xy=geom_xy, @@ -67,7 +67,7 @@ def test_flag_store_errors_fused(self): ).errors assert out is None - out = gwb_solver.gromov_wasserstein( + out = gw_solver.gromov_wasserstein( geom_xx=geom_x, geom_yy=geom_y, geom_xy=geom_xy, @@ -87,7 +87,7 @@ def test_flag_store_errors_fused(self): assert out.ndim == 2 @pytest.mark.fast.with_args(jit=[False, True], only_fast=1) - def test_gradient_marginals_fused_gwb_solver(self, jit: bool): + def test_gradient_marginals_fgw_solver(self, jit: bool): """Test gradient w.r.t. probability weights.""" geom_x = pointcloud.PointCloud(self.x) geom_y = pointcloud.PointCloud(self.y) @@ -99,7 +99,7 @@ def reg_gw(a, b, implicit): 'implicit_differentiation': implicit, 'max_iterations': 1001 } - out = gwb_solver.gromov_wasserstein( + out = gw_solver.gromov_wasserstein( geom_x, geom_y, geom_xy=geom_xy, @@ -137,14 +137,14 @@ def reg_gw(a, b, implicit): ) @pytest.mark.fast.with_args(lse_mode=[False, True], only_fast=1) - def test_fused_gwb_solver_pointcloud(self, lse_mode: bool): + def test_fgw_solver_pointcloud(self, lse_mode: bool): """Test basic computations pointclouds.""" def reg_gw(x, y, x_2, y_2, fused_penalty, a, b): geom_x = pointcloud.PointCloud(x) geom_y = pointcloud.PointCloud(y) geom_xy = pointcloud.PointCloud(x_2, y_2) - return gwb_solver.gromov_wasserstein( + return gw_solver.gromov_wasserstein( geom_x, geom_y, geom_xy=geom_xy, @@ -164,7 +164,7 @@ def reg_gw(x, y, x_2, y_2, fused_penalty, a, b): assert cost is not None @pytest.mark.parametrize("lse_mode", [False, True]) - def test_gradient_fused_gwb_solver_pointcloud(self, lse_mode: bool): + def test_gradient_fgw_solver_pointcloud(self, lse_mode: bool): """Test gradient w.r.t. pointclouds.""" def reg_gw(x, y, x_2, y_2, fused_penalty, a, b, implicit): @@ -176,7 +176,7 @@ def reg_gw(x, y, x_2, y_2, fused_penalty, a, b, implicit): 'max_iterations': 1001, 'lse_mode': lse_mode } - return gwb_solver.gromov_wasserstein( + return gw_solver.gromov_wasserstein( geom_x, geom_y, geom_xy=geom_xy, @@ -207,7 +207,7 @@ def reg_gw(x, y, x_2, y_2, fused_penalty, a, b, implicit): ) @pytest.mark.parametrize("lse_mode", [False, True]) - def test_gradient_fused_gwb_solver_geometry(self, lse_mode: bool): + def test_gradient_fgw_solver_geometry(self, lse_mode: bool): """Test gradient w.r.t. cost matrices.""" def reg_gw(cx, cy, cxy, fused_penalty, a, b, implicit): @@ -219,7 +219,7 @@ def reg_gw(cx, cy, cxy, fused_penalty, a, b, implicit): 'max_iterations': 1001, 'lse_mode': lse_mode } - return gwb_solver.gromov_wasserstein( + return gw_solver.gromov_wasserstein( geom_x, geom_y, geom_xy=geom_xy, @@ -252,7 +252,7 @@ def reg_gw(cx, cy, cxy, fused_penalty, a, b, implicit): grad_matrices[0][2], grad_matrices[1][2], rtol=1e-02, atol=1e-02 ) - def test_adaptive_threshold_fused(self): + def test_fgw_adaptive_threshold(self): """Checking solution is improved with smaller threshold for convergence.""" geom_x = pointcloud.PointCloud(self.x, self.x) geom_y = pointcloud.PointCloud(self.y, self.y) @@ -260,7 +260,7 @@ def test_adaptive_threshold_fused(self): # without warm start for calls to sinkhorn def loss_thre(threshold: float) -> float: - return gwb_solver.gromov_wasserstein( + return gw_solver.gromov_wasserstein( geom_xx=geom_x, geom_yy=geom_y, geom_xy=geom_xy, @@ -275,7 +275,7 @@ def loss_thre(threshold: float) -> float: assert loss_thre(1e-3) > loss_thre(1e-5) @pytest.mark.parametrize("lse_mode", [False, True]) - def test_gradient_fused_gwb_solver_penalty(self, lse_mode: bool): + def test_gradient_fgw_solver_penalty(self, lse_mode: bool): """Test gradient w.r.t. penalty.""" def reg_gw(cx, cy, cxy, fused_penalty, a, b, implicit): @@ -287,7 +287,7 @@ def reg_gw(cx, cy, cxy, fused_penalty, a, b, implicit): 'max_iterations': 1001, 'lse_mode': lse_mode } - return gwb_solver.gromov_wasserstein( + return gw_solver.gromov_wasserstein( geom_x, geom_y, geom_xy=geom_xy, @@ -319,7 +319,7 @@ def reg_fgw(x, y, x_2, y_2, fused_penalty, a, b): geom_y = pointcloud.PointCloud(y) geom_xy = pointcloud.PointCloud(x_2, y_2) sinkhorn_kwargs = {'max_iterations': 1001} - return gwb_solver.gromov_wasserstein( + return gw_solver.gromov_wasserstein( geom_x, geom_y, geom_xy=geom_xy, @@ -334,7 +334,7 @@ def reg_gw(x, y, a, b): geom_x = pointcloud.PointCloud(x) geom_y = pointcloud.PointCloud(y) sinkhorn_kwargs = {'max_iterations': 1001} - return gwb_solver.gromov_wasserstein( + return gw_solver.gromov_wasserstein( geom_x, geom_y, a=a, @@ -367,7 +367,7 @@ def test_fgw_lr_memory(self, rng: jnp.ndarray, jit: bool): geom_y = pointcloud.PointCloud(y) geom_xy = pointcloud.PointCloud(xx, yy) - ot_gwlr = gwb_solver.gromov_wasserstein( + ot_gwlr = gw_solver.gromov_wasserstein( geom_x, geom_y, geom_xy, rank=5, jit=jit ) res0 = ot_gwlr.apply(x.T, axis=0) @@ -399,7 +399,7 @@ def test_fgw_lr_generic_cost_matrix( lr_prob = problem.to_low_rank() assert lr_prob.is_low_rank - solver = gwb_solver.GromovWasserstein(rank=5, epsilon=1) + solver = gw_solver.GromovWasserstein(rank=5, epsilon=1) out = solver(problem) assert solver.rank == 5 diff --git a/tests/solvers/quadratic/gw_barycenter_test.py b/tests/solvers/quadratic/gw_barycenter_test.py new file mode 100644 index 000000000..94cd5759b --- /dev/null +++ b/tests/solvers/quadratic/gw_barycenter_test.py @@ -0,0 +1,113 @@ +# Copyright 2022 Apple +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Lint as: python3 +"""Tests for Gromov-Wasserstein barycenter.""" +from typing import Any, Optional, Sequence, Tuple + +import jax +import jax.numpy as jnp +import numpy as np +import pytest + +from ott.geometry import pointcloud +from ott.problems.quadratic import gw_barycenter as gwb +from ott.solvers.quadratic import gw_barycenter as gwb_solver + + +class TestGWBarycenter: + ndim = 3 + ndim_f = 4 + + @staticmethod + def random_pc( + n: int, + d: int, + rng: jnp.ndarray, + m: Optional[int] = None, + **kwargs: Any + ) -> pointcloud.PointCloud: + key1, key2 = jax.random.split(rng, 2) + x = jax.random.normal(key1, (n, d)) + y = x if m is None else jax.random.normal(key2, (m, d)) + return pointcloud.PointCloud(x, y, batch_size=None, **kwargs) + + @staticmethod + def pad_cost_matrices( + costs: Sequence[jnp.ndarray], + shape: Optional[Tuple[int, int]] = None + ) -> Tuple[jnp.ndarray, jnp.ndarray]: + if shape is None: + shape = jnp.asarray([arr.shape for arr in costs]).max() + shape = (shape, shape) + else: + assert shape[0] == shape[1], shape + + cs, weights = [], [] + for cost in costs: + r, c = cost.shape + cs.append(jnp.zeros(shape).at[:r, :c].set(cost)) + w = jnp.ones(r) / r + weights.append(jnp.concatenate([w, jnp.zeros(shape[0] - r)])) + return jnp.stack(cs), jnp.stack(weights) + + # TODO(cuturi) add back KL test when KL cost GW is fixed. + @pytest.mark.parametrize( + "gw_loss,bar_size,epsilon", + [("sqeucl", 17, None)] # , ("kl", 22, 1e-2)] + ) + def test_gw_barycenter( + self, rng: jnp.ndarray, gw_loss: str, bar_size: int, + epsilon: Optional[float] + ): + tol = 1e-3 if gw_loss == "sqeucl" else 1e-1 + num_per_segment = (13, 15, 21) + rngs = jax.random.split(rng, len(num_per_segment)) + pcs = [ + self.random_pc(n, d=self.ndim, rng=rng) + for n, rng in zip(num_per_segment, rngs) + ] + costs = [pc._compute_cost_matrix() for pc, n in zip(pcs, num_per_segment)] + costs, cbs = self.pad_cost_matrices(costs) + ys = jnp.concatenate([pc.x for pc in pcs]) + bs = jnp.concatenate([jnp.ones(n) / n for n in num_per_segment]) + kwargs = { + "gw_loss": gw_loss, + "num_per_segment": num_per_segment, + "epsilon": epsilon + } + + problem_pc = gwb.GWBarycenterProblem(y=ys, b=bs, **kwargs) + problem_cost = gwb.GWBarycenterProblem( + costs=costs, + b=cbs, + **kwargs, + ) + for prob in [problem_pc, problem_cost]: + assert not prob.is_fused + assert prob.ndim_fused is None + assert prob.num_measures == len(num_per_segment) + assert prob.max_measure_size == max(num_per_segment) + assert prob._loss_name == gw_loss + assert problem_pc.ndim == self.ndim + assert problem_cost.ndim is None + + solver = gwb_solver.GromovWassersteinBarycenter(jit=True) + out_pc = solver(problem_pc, bar_size=bar_size) + out_cost = solver(problem_cost, bar_size=bar_size) + + assert out_pc.x is None + assert out_cost.x is None + assert out_pc.cost.shape == (bar_size, bar_size) + np.testing.assert_allclose(out_pc.cost, out_cost.cost, rtol=tol, atol=tol) + np.testing.assert_allclose(out_pc.costs, out_cost.costs, rtol=tol, atol=tol) diff --git a/tests/core/gromov_wasserstein_test.py b/tests/solvers/quadratic/gw_test.py similarity index 96% rename from tests/core/gromov_wasserstein_test.py rename to tests/solvers/quadratic/gw_test.py index 27e7f58b8..0e37a9c6d 100644 --- a/tests/core/gromov_wasserstein_test.py +++ b/tests/solvers/quadratic/gw_test.py @@ -88,7 +88,7 @@ def test_quad_to_low_rank( assert lr_prob._is_low_rank_convertible assert lr_prob.to_low_rank() is lr_prob - def test_implicit_conversion_mixed_input(self, rng: jnp.ndarray): + def test_gw_implicit_conversion_mixed_input(self, rng: jnp.ndarray): n, m, d1, d2 = 200, 300, 20, 25 k1, k2 = jax.random.split(rng, 2) x = jax.random.normal(k1, (n, d1)) @@ -152,7 +152,7 @@ def test_flag_store_errors(self): assert out.ndim == 2 @pytest.mark.parametrize("jit", [False, True]) - def test_gradient_marginals_gromov_wasserstein(self, jit: bool): + def test_gradient_marginals_gw(self, jit: bool): """Test gradient w.r.t. probability weights.""" geom_x = pointcloud.PointCloud(self.x) geom_y = pointcloud.PointCloud(self.y) @@ -199,7 +199,7 @@ def reg_gw(a, b, implicit): ) @pytest.mark.fast - def test_gromov_wasserstein_pointcloud(self): + def test_gw_pointcloud(self): """Test basic computations pointclouds.""" def reg_gw(x, y, a, b): @@ -212,7 +212,7 @@ def reg_gw(x, y, a, b): assert not jnp.isnan(reg_gw(self.x, self.y, self.a, self.b)) @pytest.mark.parametrize("lse_mode", [False, True]) - def test_gradient_gromov_wasserstein_pointcloud(self, lse_mode: bool): + def test_gradient_gw_pointcloud(self, lse_mode: bool): """Test gradient w.r.t. pointclouds.""" def reg_gw(x, y, a, b, implicit): @@ -254,7 +254,7 @@ def reg_gw(x, y, a, b, implicit): ) @pytest.mark.parametrize("lse_mode", [False, True]) - def test_gradient_gromov_wasserstein_geometry(self, lse_mode: bool): + def test_gradient_gw_geometry(self, lse_mode: bool): """Test gradient w.r.t. cost matrices.""" def reg_gw(cx, cy, a, b, implicit): @@ -296,7 +296,7 @@ def reg_gw(cx, cy, a, b, implicit): grad_matrices[0][1], grad_matrices[1][1], rtol=1e-02, atol=1e-02 ) - def test_adaptive_threshold(self): + def test_gw_adaptive_threshold(self): """Checking solution is improved with smaller threshold for convergence.""" geom_x = pointcloud.PointCloud(self.x, self.x) geom_y = pointcloud.PointCloud(self.y, self.y) @@ -466,7 +466,7 @@ def initialize(self, rng: jnp.ndarray): self.tau_b = 0.9 @pytest.mark.fast - def test_gromov_wasserstein_pointcloud(self): + def test_gw_pointcloud(self): """Test basic computations pointclouds.""" def reg_gw(x, y, a, b): @@ -487,9 +487,7 @@ def reg_gw(x, y, a, b): assert not jnp.isnan(cost) @pytest.mark.parametrize("gw_unbalanced_correction", [False, True]) - def test_gradient_gromov_wasserstein_pointcloud( - self, gw_unbalanced_correction: bool - ): + def test_gradient_gw_pointcloud(self, gw_unbalanced_correction: bool): """Test gradient w.r.t. pointclouds.""" def reg_gw(x, y, a, b, implicit): @@ -533,9 +531,7 @@ def reg_gw(x, y, a, b, implicit): ) @pytest.mark.parametrize("gw_unbalanced_correction", [False, True]) - def test_gradient_gromov_wasserstein_geometry( - self, gw_unbalanced_correction: bool - ): + def test_gradient_gw_geometry(self, gw_unbalanced_correction: bool): """Test gradient w.r.t. cost matrices.""" def reg_gw(cx, cy, a, b, implicit): From 0f1ad3b109909f7dd04063476ba4c511e4c05622 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Tue, 22 Nov 2022 09:43:52 +0100 Subject: [PATCH 28/34] Update badges --- README.md | 1 + docs/index.rst | 21 ++++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6dede0b38..cb44680a9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ logo # Optimal Transport Tools (OTT) +[![Downloads](https://pepy.tech/badge/ott-jax)](https://pypi.org/project/ott-jax/) [![Tests](https://img.shields.io/github/workflow/status/ott-jax/ott/tests/main)](https://github.com/ott-jax/ott/actions/workflows/tests.yml) [![Docs](https://img.shields.io/readthedocs/ott-jax/latest)](https://ott-jax.readthedocs.io/en/latest/) [![Coverage](https://img.shields.io/codecov/c/github/ott-jax/ott/main)](https://app.codecov.io/gh/ott-jax/ott) diff --git a/docs/index.rst b/docs/index.rst index 24a376771..8623d61d5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,8 @@ +|Downloads| |Tests| |Docs| |Coverage| + Optimal Transport Tools (OTT) documentation =========================================== -`Code `_ on github. +`Code `_ on GitHub. To install, simply run ``pip install ott-jax``. Intro @@ -106,6 +108,23 @@ Packages references + +.. |Downloads| image:: https://pepy.tech/badge/ott-jax + :target: https://pypi.org/project/ott-jax/ + :alt: Documentation + +.. |Tests| image:: https://img.shields.io/github/workflow/status/ott-jax/ott/tests/main + :target: https://github.com/ott-jax/ott/actions/workflows/tests.yml + :alt: Documentation + +.. |Docs| image:: https://img.shields.io/readthedocs/ott-jax/latest + :target: https://ott-jax.readthedocs.io/en/latest/ + :alt: Documentation + +.. |Coverage| image:: https://img.shields.io/codecov/c/github/ott-jax/ott/main + :target: https://app.codecov.io/gh/ott-jax/ott + :alt: Coverage + .. _Just-in-time (JIT) compilation: https://jax.readthedocs.io/en/latest/jax.html#just-in-time-compilation-jit .. _auto-vectorization (VMAP): https://jax.readthedocs.io/en/latest/jax.html#vectorization-vmap .. _automatic: https://jax.readthedocs.io/en/latest/jax.html#automatic-differentiation From 75e358e56fa4233d9087d2e84ce8e740ec2444e6 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Tue, 22 Nov 2022 10:38:49 +0100 Subject: [PATCH 29/34] Add TODOs, fix citation in `index.rst`, move `implicit_diff` --- docs/index.rst | 12 +++++++----- docs/initializers/index.rst | 2 +- docs/initializers/linear.rst | 2 ++ docs/initializers/nn.rst | 2 ++ docs/initializers/quadratic.rst | 2 ++ docs/math.rst | 7 +------ docs/problems/index.rst | 2 +- docs/problems/linear.rst | 2 ++ docs/problems/quadratic.rst | 2 ++ docs/solvers/index.rst | 2 +- docs/solvers/linear.rst | 9 +++++++++ docs/solvers/nn.rst | 2 ++ docs/solvers/quadratic.rst | 2 ++ ott/math/__init__.py | 1 - ott/solvers/linear/__init__.py | 1 + .../linear}/implicit_differentiation.py | 0 ott/solvers/linear/sinkhorn.py | 5 ++--- tests/geometry/graph_test.py | 2 +- 18 files changed, 38 insertions(+), 19 deletions(-) rename ott/{math => solvers/linear}/implicit_differentiation.py (100%) diff --git a/docs/index.rst b/docs/index.rst index 8623d61d5..037233463 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,7 +17,7 @@ such as differentiable approximations to ranking or even clustering. To achieve this, `OTT` rests on two families of tools: The first family consists in *discrete* solvers computing transport between point clouds, using the Sinkhorn :cite:`cuturi:13` and low-rank Sinkhorn :cite:`scetbon:21` algorithms, -and moving up towards Gromov-Wasserstein :cite:`memoli:11`, :cite:`memoli:11`; +and moving up towards Gromov-Wasserstein :cite:`memoli:11,peyre:16`; the second family consists in *continuous* solvers, using suitable neural architectures :cite:`amos:17` coupled with SGD type estimators :cite:`makkuva:20,korotin:21`. @@ -43,20 +43,22 @@ Design Choices automatically in higher level calls (e.g. updates in Gromov-Wasserstein), without requiring any attention from the user. +.. TODO(marcocuturi): add missing package descriptions below + Packages -------- - :ref:`geometry` contains classes to instantiate objects that describe *two point clouds* paired with a *cost* function. Geometry objects are used to describe OT problems, handled by solvers in the :ref:`solvers`. -- :ref:`problems` TODO(marcocuturi) -- :ref:`solvers` TODO(marcocuturi) -- :ref:`initializers` TODO(marcocuturi) +- :ref:`problems` +- :ref:`solvers` +- :ref:`initializers` - :ref:`tools` provides an interface to exploit OT solutions, as produced by solvers in the :ref:`solvers`. Such tasks include computing approximations to Wasserstein distances :cite:`genevay:18,sejourne:19`, approximating OT between GMMs, or computing differentiable sort and quantile operations :cite:`cuturi:19`. -- :ref:`math` TODO(marcocuturi) +- :ref:`math` .. toctree:: :maxdepth: 1 diff --git a/docs/initializers/index.rst b/docs/initializers/index.rst index 4baba57f5..b591be3b2 100644 --- a/docs/initializers/index.rst +++ b/docs/initializers/index.rst @@ -3,7 +3,7 @@ ott.initializers package ======================== -TODO(cuturi): add some nice text here please. +.. TODO(cuturi): add some nice text here please .. currentmodule:: ott.initializers .. automodule:: ott.initializers diff --git a/docs/initializers/linear.rst b/docs/initializers/linear.rst index 8fb5d89ea..ad3143fa1 100644 --- a/docs/initializers/linear.rst +++ b/docs/initializers/linear.rst @@ -3,6 +3,8 @@ ott.initializers.linear package .. currentmodule:: ott.initializers.linear .. automodule:: ott.initializers.linear +.. TODO(marcocuturi): maybe add some text here + Sinkhorn Initializers --------------------- .. autosummary:: diff --git a/docs/initializers/nn.rst b/docs/initializers/nn.rst index 6f439f6a8..2f88e0999 100644 --- a/docs/initializers/nn.rst +++ b/docs/initializers/nn.rst @@ -3,6 +3,8 @@ ott.initializers.nn package .. currentmodule:: ott.initializers.nn .. automodule:: ott.initializers.nn +.. TODO(marcocuturi): maybe add some text here + Neural Initializers ------------------- .. autosummary:: diff --git a/docs/initializers/quadratic.rst b/docs/initializers/quadratic.rst index d3ea718a9..1929bd380 100644 --- a/docs/initializers/quadratic.rst +++ b/docs/initializers/quadratic.rst @@ -3,6 +3,8 @@ ott.initializers.quadratic package .. currentmodule:: ott.initializers.quadratic .. automodule:: ott.initializers.quadratic +.. TODO(marcocuturi): maybe add some text here + Gromov-Wasserstein Initializers ------------------------------- .. autosummary:: diff --git a/docs/math.rst b/docs/math.rst index de58a29e2..20ea2fc3f 100644 --- a/docs/math.rst +++ b/docs/math.rst @@ -5,12 +5,7 @@ ott.math package .. currentmodule:: ott.math .. automodule:: ott.math -Implicit Differentiation ------------------------- -.. autosummary:: - :toctree: _autosummary - - implicit_differentiation.ImplicitDiff +.. TODO(marcocuturi): maybe add some text here Fixed-point Iteration --------------------- diff --git a/docs/problems/index.rst b/docs/problems/index.rst index 462411f7d..16e5ead90 100644 --- a/docs/problems/index.rst +++ b/docs/problems/index.rst @@ -3,7 +3,7 @@ ott.problems package ==================== -TODO(cuturi): add some nice text here please. +.. TODO(marcocuturi): add some nice text here please .. currentmodule:: ott.problems .. automodule:: ott.problems diff --git a/docs/problems/linear.rst b/docs/problems/linear.rst index 4f4ce5cb2..d8b442e15 100644 --- a/docs/problems/linear.rst +++ b/docs/problems/linear.rst @@ -3,6 +3,8 @@ ott.problems.linear package .. currentmodule:: ott.problems.linear .. automodule:: ott.problems.linear +.. TODO(marcocuturi): maybe add some text here + OT Problems ----------- .. autosummary:: diff --git a/docs/problems/quadratic.rst b/docs/problems/quadratic.rst index 900081871..e7e8c32d1 100644 --- a/docs/problems/quadratic.rst +++ b/docs/problems/quadratic.rst @@ -3,6 +3,8 @@ ott.problems.quadratic package .. currentmodule:: ott.problems.quadratic .. automodule:: ott.problems.quadratic +.. TODO(marcocuturi): maybe add some text here + OT Problems ----------- .. autosummary:: diff --git a/docs/solvers/index.rst b/docs/solvers/index.rst index b903983a7..8b7d62532 100644 --- a/docs/solvers/index.rst +++ b/docs/solvers/index.rst @@ -3,7 +3,7 @@ ott.solvers package =================== -TODO(cuturi): add some nice text here please. +.. TODO(marcocuturi): add some nice text here please .. currentmodule:: ott.solvers .. automodule:: ott.solvers diff --git a/docs/solvers/linear.rst b/docs/solvers/linear.rst index 8e39c984e..0605bd7e1 100644 --- a/docs/solvers/linear.rst +++ b/docs/solvers/linear.rst @@ -3,6 +3,8 @@ ott.solvers.linear package .. currentmodule:: ott.solvers.linear .. automodule:: ott.solvers.linear +.. TODO(marcocuturi): maybe add some text here + Sinkhorn Solvers ---------------- .. autosummary:: @@ -31,3 +33,10 @@ Sinkhorn Acceleration acceleration.Momentum acceleration.AndersonAcceleration + +Implicit Differentiation +------------------------ +.. autosummary:: + :toctree: _autosummary + + implicit_differentiation.ImplicitDiff diff --git a/docs/solvers/nn.rst b/docs/solvers/nn.rst index cd28c655f..08bd7fc1a 100644 --- a/docs/solvers/nn.rst +++ b/docs/solvers/nn.rst @@ -3,6 +3,8 @@ ott.solvers.nn package .. currentmodule:: ott.solvers.nn .. automodule:: ott.solvers.nn +.. TODO(marcocuturi): maybe add some text here + Neural Dual ----------- .. autosummary:: diff --git a/docs/solvers/quadratic.rst b/docs/solvers/quadratic.rst index 33b0a9014..9f6ea7a38 100644 --- a/docs/solvers/quadratic.rst +++ b/docs/solvers/quadratic.rst @@ -3,6 +3,8 @@ ott.solvers.quadratic package .. currentmodule:: ott.solvers.quadratic .. automodule:: ott.solvers.quadratic +.. TODO(marcocuturi): maybe add some text here + Gromov-Wasserstein Solvers -------------------------- .. autosummary:: diff --git a/ott/math/__init__.py b/ott/math/__init__.py index 60c303c2e..67aca8931 100644 --- a/ott/math/__init__.py +++ b/ott/math/__init__.py @@ -1,7 +1,6 @@ from . import ( decomposition, fixed_point_loop, - implicit_differentiation, matrix_square_root, unbalanced_functions, utils, diff --git a/ott/solvers/linear/__init__.py b/ott/solvers/linear/__init__.py index 7f9d3a1bb..40034b929 100644 --- a/ott/solvers/linear/__init__.py +++ b/ott/solvers/linear/__init__.py @@ -2,6 +2,7 @@ acceleration, continuous_barycenter, discrete_barycenter, + implicit_differentiation, sinkhorn, sinkhorn_lr, ) diff --git a/ott/math/implicit_differentiation.py b/ott/solvers/linear/implicit_differentiation.py similarity index 100% rename from ott/math/implicit_differentiation.py rename to ott/solvers/linear/implicit_differentiation.py diff --git a/ott/solvers/linear/sinkhorn.py b/ott/solvers/linear/sinkhorn.py index bfc86c4a5..aebee0038 100644 --- a/ott/solvers/linear/sinkhorn.py +++ b/ott/solvers/linear/sinkhorn.py @@ -23,11 +23,10 @@ from ott.geometry import geometry from ott.initializers.linear import initializers as init_lib -from ott.math import fixed_point_loop -from ott.math import implicit_differentiation as implicit_lib -from ott.math import unbalanced_functions +from ott.math import fixed_point_loop, unbalanced_functions from ott.problems.linear import linear_problem, potentials from ott.solvers.linear import acceleration +from ott.solvers.linear import implicit_differentiation as implicit_lib __all__ = ["Sinkhorn", "SinkhornOutput"] diff --git a/tests/geometry/graph_test.py b/tests/geometry/graph_test.py index 97b245a59..58f766890 100644 --- a/tests/geometry/graph_test.py +++ b/tests/geometry/graph_test.py @@ -13,8 +13,8 @@ from ott.geometry import geometry, graph from ott.math import decomposition -from ott.math import implicit_differentiation as implicit_lib from ott.problems.linear import linear_problem +from ott.solvers.linear import implicit_differentiation as implicit_lib from ott.solvers.linear import sinkhorn # we mix both dense/sparse tests From 8c1ed2ae1d662b6327aa2f6d37e5d16939738504 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Tue, 22 Nov 2022 11:03:15 +0100 Subject: [PATCH 30/34] Fix implicit_diff, TODOs in costs --- docs/notebooks/Hessians.ipynb | 2 +- ott/geometry/costs.py | 45 +++++++++++----------- tests/solvers/linear/sinkhorn_diff_test.py | 2 +- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/docs/notebooks/Hessians.ipynb b/docs/notebooks/Hessians.ipynb index 51383ca35..e0ce67c72 100644 --- a/docs/notebooks/Hessians.ipynb +++ b/docs/notebooks/Hessians.ipynb @@ -45,7 +45,7 @@ "\n", "from ott.tools import sinkhorn_divergence\n", "from ott.geometry import pointcloud\n", - "from ott.math import implicit_differentiation as implicit_lib\n", + "from ott.solvers.linear import implicit_differentiation as implicit_lib\n", "import matplotlib.pyplot as plt" ] }, diff --git a/ott/geometry/costs.py b/ott/geometry/costs.py index eefcf1613..7f35046e5 100644 --- a/ott/geometry/costs.py +++ b/ott/geometry/costs.py @@ -49,35 +49,35 @@ class CostFn(abc.ABC): norm: Optional[Callable[[jnp.ndarray], Union[float, jnp.ndarray]]] = None @abc.abstractmethod - def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> float: + def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray: pass # TODO(michalk8): make weights optional? - def barycenter(self, weights: jnp.ndarray, xs: jnp.ndarray) -> float: + def barycenter(self, weights: jnp.ndarray, xs: jnp.ndarray) -> jnp.ndarray: """Barycentric projection. Args: - weights: TODO. - xs: TODO. + weights: Weights of the points. + xs: Points to project. Returns: - TODO. + The barycentric projection. """ raise NotImplementedError("Barycenter is not yet implemented.") @classmethod def _padder(cls, dim: int) -> jnp.ndarray: - """TODO. + """Create a padding vector for easier jitting. Args: - dim: TODO. + dim: Dimensionality of the data. Returns: - TODO. + The padding vector. """ return jnp.zeros((1, dim)) - def __call__(self, x: jnp.ndarray, y: jnp.ndarray) -> float: + def __call__(self, x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray: cost = self.pairwise(x, y) if self.norm is None: return cost @@ -139,7 +139,7 @@ def h_legendre(self, z: jnp.ndarray) -> float: """Legendre transform of :func:`h` when it is convex.""" raise NotImplementedError("`h_legendre` not implemented.") - def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> float: + def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray: """Compute cost as evaluation of :func:`h` on :math:`x-y`.""" return self.h(x - y) @@ -152,7 +152,7 @@ class SqPNorm(TICost): the reference :cite:`boyd:04`, p.93/94. Args: - p: TODO. + p: Power of the p-norm. """ def __init__(self, p: float): @@ -181,14 +181,14 @@ class PNorm(TICost): """p-norm (to the power p) of the difference of two vectors. Args: - p: TODO. + p: Power of the p-norm. """ def __init__(self, p: float): super().__init__() assert p >= 1.0, "p parameter in p-norm should be >= 1.0" self.p = p - # TODO(marcocuturi): fid case when `p=1` + # TODO(marcocuturi): fix case when `p=1` self.q = 1. / (1. - 1. / self.p) if p > 1. else "inf" def h(self, z: jnp.ndarray) -> float: @@ -216,7 +216,7 @@ class Euclidean(CostFn): because the function is not strictly convex (it is linear on rays). """ - def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> float: + def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray: """Compute Euclidean norm.""" return jnp.linalg.norm(x - y) @@ -229,7 +229,7 @@ def norm(self, x: jnp.ndarray) -> Union[float, jnp.ndarray]: """Compute squared Euclidean norm for vector.""" return jnp.sum(x ** 2, axis=-1) - def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> float: + def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray: """Compute minus twice the dot-product between vectors.""" return -2. * jnp.vdot(x, y) @@ -249,14 +249,14 @@ class Cosine(CostFn): """Cosine distance cost function. Args: - ridge: TODO. + ridge: Ridge regularization. """ def __init__(self, ridge: float = 1e-8): super().__init__() self._ridge = ridge - def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> float: + def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray: """Cosine distance between vectors, denominator regularized with ridge.""" ridge = self._ridge x_norm = jnp.linalg.norm(x, axis=-1) @@ -408,10 +408,10 @@ class UnbalancedBures(CostFn): triplets (mass, mean, covariance) raveled as vectors, in that order. Args: - dimension: TODO. - gamma: TODO. - sigma: TODO. - kwargs: TODO. + dimension: Dimensionality of the data. + gamma: KL-divergence regularization for the marginals. + sigma: Entropic regularization. + kwargs: Keyword arguments for :func:`~ott.math.matrix_square_root.sqrtm`. """ def __init__( @@ -431,7 +431,7 @@ def norm(self, x: jnp.ndarray) -> jnp.ndarray: """Compute norm of Gaussian for unbalanced Bures.""" return self._gamma * x[0] - def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> float: + def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray: """Compute dot-product for unbalanced Bures.""" # Sets a few constants gam = self._gamma @@ -481,6 +481,7 @@ def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> float: # If all logdet signs are 1, output value, nan otherwise. # TODO(michalk8): use lax.cond + return jnp.where( sldet_c == 1 and sldet_c_ab == 1 and sldet_ab == 1 and sldet_t_ab == 1, 2 * sig2 * mass_x * mass_y - 2 * (sig2 + gam) * jnp.exp(log_m_pi), diff --git a/tests/solvers/linear/sinkhorn_diff_test.py b/tests/solvers/linear/sinkhorn_diff_test.py index f307ec954..80d9e62c8 100644 --- a/tests/solvers/linear/sinkhorn_diff_test.py +++ b/tests/solvers/linear/sinkhorn_diff_test.py @@ -23,8 +23,8 @@ import pytest from ott.geometry import costs, geometry, grid, pointcloud -from ott.math import implicit_differentiation as implicit_lib from ott.problems.linear import linear_problem +from ott.solvers.linear import implicit_differentiation as implicit_lib from ott.solvers.linear import sinkhorn from ott.tools import transport From 9186c616b7949539daf475cdd2d6dff6c02fa98a Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Tue, 22 Nov 2022 11:11:26 +0100 Subject: [PATCH 31/34] Use `jax.lax.cond` in `UnbalancedBures` --- ott/geometry/costs.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/ott/geometry/costs.py b/ott/geometry/costs.py index 7f35046e5..ad0fdbf8c 100644 --- a/ott/geometry/costs.py +++ b/ott/geometry/costs.py @@ -52,7 +52,6 @@ class CostFn(abc.ABC): def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray: pass - # TODO(michalk8): make weights optional? def barycenter(self, weights: jnp.ndarray, xs: jnp.ndarray) -> jnp.ndarray: """Barycentric projection. @@ -436,8 +435,8 @@ def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray: # Sets a few constants gam = self._gamma sig2 = self._sigma ** 2 - lam = sig2 + gam / 2 - tau = gam / (2 * lam) + lam = sig2 + gam / 2.0 + tau = gam / (2.0 * lam) # Extracts mass, mean vector, covariance matrices mass_x, mass_y = x[0], y[0] @@ -463,29 +462,24 @@ def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray: sldet_c, ldet_c = jnp.linalg.slogdet(c_mat) sldet_t_ab, ldet_t_ab = jnp.linalg.slogdet(tilde_a_b) sldet_ab, ldet_ab = jnp.linalg.slogdet(jnp.matmul(cov_x, cov_y)) - sldet_c_ab, ldet_c_ab = jnp.linalg.slogdet(c_mat - 2 * tilde_a_b / gam) + sldet_c_ab, ldet_c_ab = jnp.linalg.slogdet(c_mat - 2.0 * tilde_a_b / gam) # Gathers all these results to compute log total mass of transport log_m_pi = (0.5 * self._dimension * sig2 / (gam + sig2)) * jnp.log(sig2) - - log_m_pi += (1 / (tau + 1)) * ( + log_m_pi += (1.0 / (tau + 1.0)) * ( jnp.log(mass_x) + jnp.log(mass_y) + ldet_c + 0.5 * (tau * ldet_t_ab - ldet_ab) ) - log_m_pi += -jnp.sum( diff_means * jnp.linalg.solve(cov_x + cov_y + lam * iden, diff_means) - ) / (2 * (tau + 1)) - + ) / (2.0 * (tau + 1.0)) log_m_pi += -0.5 * ldet_c_ab - # If all logdet signs are 1, output value, nan otherwise. - # TODO(michalk8): use lax.cond - - return jnp.where( - sldet_c == 1 and sldet_c_ab == 1 and sldet_ab == 1 and sldet_t_ab == 1, - 2 * sig2 * mass_x * mass_y - 2 * (sig2 + gam) * jnp.exp(log_m_pi), - jnp.nan + # if all logdet signs are 1, output value, nan otherwise + pos_signs = (sldet_c + sldet_c_ab + sldet_t_ab + sldet_t_ab) == 4 + return jax.lax.cond( + pos_signs, lambda: 2 * sig2 * mass_x * mass_y - 2 * + (sig2 + gam) * jnp.exp(log_m_pi), lambda: jnp.nan ) def tree_flatten(self): From f49351ca30d8f93e762bfa0e0625e8bb58afebc9 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Tue, 22 Nov 2022 15:09:05 +0100 Subject: [PATCH 32/34] Fix `UnbalancedBures` --- ott/geometry/costs.py | 46 +++++++++++++++------- tests/solvers/linear/sinkhorn_misc_test.py | 39 ++++++++++++------ 2 files changed, 59 insertions(+), 26 deletions(-) diff --git a/ott/geometry/costs.py b/ott/geometry/costs.py index ad0fdbf8c..42559f1ef 100644 --- a/ott/geometry/costs.py +++ b/ott/geometry/costs.py @@ -400,38 +400,55 @@ def tree_unflatten(cls, aux_data, children): @jax.tree_util.register_pytree_node_class class UnbalancedBures(CostFn): - """Regularized/unbalanced Bures dist between two triplets of (mass,mean,cov). + """Unbalanced Bures distance between two triplets of `(mass, mean, cov)`. - This cost implements the value defined in :cite:`janati:20`, eq. 37, 39, 40. - We follow their notations. It is assumed inputs are given as - triplets (mass, mean, covariance) raveled as vectors, in that order. + This cost uses the notation defined in :cite:`janati:20`, eq. 37, 39, 40. Args: dimension: Dimensionality of the data. - gamma: KL-divergence regularization for the marginals. sigma: Entropic regularization. + gamma: KL-divergence regularization for the marginals. kwargs: Keyword arguments for :func:`~ott.math.matrix_square_root.sqrtm`. """ def __init__( self, dimension: int, - gamma: float = 1.0, + *, sigma: float = 1.0, + gamma: float = 1.0, **kwargs: Any, ): super().__init__() self._dimension = dimension - self._gamma = gamma self._sigma = sigma + self._gamma = gamma self._sqrtm_kw = kwargs def norm(self, x: jnp.ndarray) -> jnp.ndarray: - """Compute norm of Gaussian for unbalanced Bures.""" - return self._gamma * x[0] + """Compute norm of Gaussian for unbalanced Bures. + + Args: + x: Array of shape ``[n_points + n_points + n_dim ** 2,]`` + corresponding to the raveled mass, means and the covariance matrix. + + Returns: + The norm, array of shape ``[n_points,]``. + """ + return self._gamma * x[:, 0] def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray: - """Compute dot-product for unbalanced Bures.""" + """Compute dot-product for unbalanced Bures. + + Args: + x: Array of shape ``[n_points + n_points + n_dim ** 2,]`` + corresponding to the raveled mass, means and the covariance matrix. + y: Array of shape ``[n_points + n_points + n_dim ** 2,]`` + corresponding to the raveled mass, means and the covariance matrix. + + Returns: + The cost. + """ # Sets a few constants gam = self._gamma sig2 = self._sigma ** 2 @@ -477,19 +494,20 @@ def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray: # if all logdet signs are 1, output value, nan otherwise pos_signs = (sldet_c + sldet_c_ab + sldet_t_ab + sldet_t_ab) == 4 + return jax.lax.cond( pos_signs, lambda: 2 * sig2 * mass_x * mass_y - 2 * (sig2 + gam) * jnp.exp(log_m_pi), lambda: jnp.nan ) def tree_flatten(self): - return (), (self._dimension, self._gamma, self._sigma, self._sqrtm_kw) + return (), (self._dimension, self._sigma, self._gamma, self._sqrtm_kw) @classmethod def tree_unflatten(cls, aux_data, children): del children - dim, gamma, sigma, kwargs = aux_data - return cls(dim, gamma=gamma, sigma=sigma, **kwargs) + dim, sigma, gamma, kwargs = aux_data + return cls(dim, sigma=sigma, gamma=gamma, **kwargs) def x_to_means_and_covs(x: jnp.ndarray, @@ -498,7 +516,7 @@ def x_to_means_and_covs(x: jnp.ndarray, Args: x: [num_gaussians, dimension, (1 + dimension)] array of concatenated means - and covariances (raveled) dimension: the dimension of the Gaussians. + and covariances (raveled) dimension: the dimension of the Gaussians. Returns: means: [num_gaussians, dimension] array that holds the means. diff --git a/tests/solvers/linear/sinkhorn_misc_test.py b/tests/solvers/linear/sinkhorn_misc_test.py index fd1664b50..96da5c6bc 100644 --- a/tests/solvers/linear/sinkhorn_misc_test.py +++ b/tests/solvers/linear/sinkhorn_misc_test.py @@ -131,25 +131,40 @@ def initialize(self): self.a = a / jnp.sum(a) self.b = b / jnp.sum(b) - def test_bures_point_cloud(self): + @pytest.mark.parametrize("lse_mode", [False, True]) + @pytest.mark.parametrize("unbalanced,thresh", [(False, 1e-3), (True, 1e-4)]) + def test_bures_point_cloud( + self, rng: jnp.ndarray, lse_mode: bool, unbalanced: bool, thresh: float + ): """Two point clouds of Gaussians, tested with various parameters.""" - threshold = 1e-3 - geom = pointcloud.PointCloud( - self.x, - self.y, - cost_fn=costs.Bures(dimension=self.dim, regularization=1e-4), - epsilon=self.eps + if unbalanced: + rng1, rng2 = jax.random.split(rng, 2) + ws_x = jnp.abs(jax.random.normal(rng1, (self.x.shape[0], 1))) + 1e-1 + ws_y = jnp.abs(jax.random.normal(rng2, (self.y.shape[0], 1))) + 1e-1 + ws_x = ws_x.at[0].set(0.) + x = jnp.concatenate([ws_x, self.x], axis=1) + y = jnp.concatenate([ws_y, self.y], axis=1) + cost_fn = costs.UnbalancedBures(dimension=self.dim, gamma=0.9, sigma=0.98) + else: + x, y = self.x, self.y + cost_fn = costs.Bures(dimension=self.dim, regularization=1e-4) + + geom = pointcloud.PointCloud(x, y, cost_fn=cost_fn, epsilon=self.eps) + + out = sinkhorn.sinkhorn( + geom, a=self.a, b=self.b, lse_mode=lse_mode, threshold=thresh ) - errors = sinkhorn.sinkhorn(geom, a=self.a, b=self.b, lse_mode=False).errors - err = errors[errors > -1][-1] - assert threshold > err + err = out.errors[out.errors > -1][-1] + + assert out.converged + assert thresh > err - def test_regularized_unbalanced_bures(self): + def test_regularized_unbalanced_bures_cost(self): """Tests Regularized Unbalanced Bures.""" x = jnp.concatenate((jnp.array([0.9]), self.x[0, :])) y = jnp.concatenate((jnp.array([1.1]), self.y[0, :])) - rub = costs.UnbalancedBures(self.dim, 1, 0.8) + rub = costs.UnbalancedBures(self.dim, gamma=1.0, sigma=0.8) assert not jnp.any(jnp.isnan(rub(x, y))) assert not jnp.any(jnp.isnan(rub(y, x))) np.testing.assert_allclose(rub(x, y), rub(y, x), rtol=5e-3, atol=5e-3) From 687e5944b5e02be51186334bbeebd4672c582683 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Tue, 22 Nov 2022 15:50:14 +0100 Subject: [PATCH 33/34] Update CI versions --- .github/workflows/lint.yml | 6 +++--- .github/workflows/notebook_tests.yml | 6 +++--- .github/workflows/publish_to_pypi.yml | 4 ++-- .github/workflows/tests.yml | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b280db886..9c091ef1b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -15,13 +15,13 @@ jobs: os: [ubuntu-latest] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python }} - - uses: actions/cache@v2 + - uses: actions/cache@v3 with: path: ~/.cache/pre-commit key: precommit-${{ env.pythonLocation }}-${{ hashFiles('**/.pre-commit-config.yaml') }} diff --git a/.github/workflows/notebook_tests.yml b/.github/workflows/notebook_tests.yml index b89b301e8..a3f327e5d 100644 --- a/.github/workflows/notebook_tests.yml +++ b/.github/workflows/notebook_tests.yml @@ -12,12 +12,12 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ['3.8'] + python-version: [3.8] os: [ubuntu-latest] steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/publish_to_pypi.yml b/.github/workflows/publish_to_pypi.yml index b9efc2088..6d26315fd 100644 --- a/.github/workflows/publish_to_pypi.yml +++ b/.github/workflows/publish_to_pypi.yml @@ -13,9 +13,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: 3.x - name: Install dependencies diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9570b6a7d..be1e3dbd5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -18,8 +18,8 @@ jobs: test_mark: [fast, all] steps: - - uses: actions/checkout@v2 - - uses: actions/setup-python@v2 + - uses: actions/checkout@v3 + - uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} From aea8912df5db6705af684a8b3edf182e85032fb8 Mon Sep 17 00:00:00 2001 From: Michal Klein <46717574+michalk8@users.noreply.github.com> Date: Tue, 22 Nov 2022 16:14:38 +0100 Subject: [PATCH 34/34] Fix UnbalancedBures's norm --- ott/geometry/costs.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ott/geometry/costs.py b/ott/geometry/costs.py index 42559f1ef..94c06112a 100644 --- a/ott/geometry/costs.py +++ b/ott/geometry/costs.py @@ -429,13 +429,14 @@ def norm(self, x: jnp.ndarray) -> jnp.ndarray: """Compute norm of Gaussian for unbalanced Bures. Args: - x: Array of shape ``[n_points + n_points + n_dim ** 2,]`` - corresponding to the raveled mass, means and the covariance matrix. + x: Array of shape ``[n_points + n_points + n_dim ** 2,]``, potentially + batched, corresponding to the raveled mass, means and the covariance + matrix. Returns: - The norm, array of shape ``[n_points,]``. + The norm, array of shape ``[]`` or ``[batch,]`` in the batched case. """ - return self._gamma * x[:, 0] + return self._gamma * x[..., 0] def pairwise(self, x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray: """Compute dot-product for unbalanced Bures.