diff options
author | RĂ©mi Flamary <remi.flamary@gmail.com> | 2017-07-26 15:25:53 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-07-26 15:25:53 +0200 |
commit | 7638d019b43e52d17600cac653939e7cd807478c (patch) | |
tree | a77441ddf844d953a3e797a3fab2a1ee3b85bf34 | |
parent | 1cf304cee298e2752ce29c83e5201f593722c3af (diff) | |
parent | 838550ead9cc8a66d9b9c1212c5dda2457dc59a5 (diff) |
Merge pull request #19 from rflamary/pytest
Pytest with 89% coverage
Fixes #19
35 files changed, 632 insertions, 92 deletions
@@ -100,3 +100,6 @@ ENV/ # Mac stuff .DS_Store + +# coverage output folder +cov_html/ diff --git a/.travis.yml b/.travis.yml index dc415a9..ec2b3d2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,6 +13,10 @@ matrix: python: 2.7 before_install: - ./.travis/before_install.sh +before_script: # configure a headless display to test plot generation + - "export DISPLAY=:99.0" + - "sh -e /etc/init.d/xvfb start" + - sleep 3 # give xvfb some time to start # command to install dependencies install: - pip install -r requirements.txt @@ -38,10 +38,10 @@ pep8 : flake8 examples/ ot/ test/ test : FORCE pep8 - python -m py.test -v test/ + python -m py.test -v test/ --cov=ot --cov-report html:cov_html pytest : FORCE - python -m py.test -v test/ + python -m py.test -v test/ --cov=ot uploadpypi : #python setup.py register diff --git a/examples/plot_OTDA_2D.py b/examples/plot_OTDA_2D.py index 1bda59c..f2108c6 100644 --- a/examples/plot_OTDA_2D.py +++ b/examples/plot_OTDA_2D.py @@ -6,6 +6,10 @@ OT for empirical distributions """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import numpy as np import matplotlib.pylab as pl import ot diff --git a/examples/plot_OTDA_classes.py b/examples/plot_OTDA_classes.py index 4d3846a..53e4bae 100644 --- a/examples/plot_OTDA_classes.py +++ b/examples/plot_OTDA_classes.py @@ -6,6 +6,10 @@ OT for domain adaptation """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import matplotlib.pylab as pl import ot diff --git a/examples/plot_OTDA_color_images.py b/examples/plot_OTDA_color_images.py index 75ac5b6..c5ff873 100644 --- a/examples/plot_OTDA_color_images.py +++ b/examples/plot_OTDA_color_images.py @@ -9,6 +9,10 @@ Regularized discrete optimal transport. SIAM Journal on Imaging Sciences, 7(3), 1853-1882. """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import numpy as np from scipy import ndimage import matplotlib.pylab as pl diff --git a/examples/plot_OTDA_mapping.py b/examples/plot_OTDA_mapping.py index a5c2b21..a0d7f8b 100644 --- a/examples/plot_OTDA_mapping.py +++ b/examples/plot_OTDA_mapping.py @@ -9,6 +9,10 @@ OT mapping estimation for domain adaptation [8] Neural Information Processing Systems (NIPS), 2016. """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import numpy as np import matplotlib.pylab as pl import ot diff --git a/examples/plot_OTDA_mapping_color_images.py b/examples/plot_OTDA_mapping_color_images.py index 9710461..8064b25 100644 --- a/examples/plot_OTDA_mapping_color_images.py +++ b/examples/plot_OTDA_mapping_color_images.py @@ -11,6 +11,10 @@ OT for domain adaptation with image color adaptation [6] with mapping estimation """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import numpy as np from scipy import ndimage import matplotlib.pylab as pl diff --git a/examples/plot_OT_1D.py b/examples/plot_OT_1D.py index 2f3b924..0f3a26a 100644 --- a/examples/plot_OT_1D.py +++ b/examples/plot_OT_1D.py @@ -4,9 +4,12 @@ 1D optimal transport ==================== -@author: rflamary """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import numpy as np import matplotlib.pylab as pl import ot diff --git a/examples/plot_OT_2D_samples.py b/examples/plot_OT_2D_samples.py index 75ed7db..023e645 100644 --- a/examples/plot_OT_2D_samples.py +++ b/examples/plot_OT_2D_samples.py @@ -4,9 +4,12 @@ 2D Optimal transport between empirical distributions ==================================================== -@author: rflamary """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import numpy as np import matplotlib.pylab as pl import ot diff --git a/examples/plot_OT_L1_vs_L2.py b/examples/plot_OT_L1_vs_L2.py index 86d902b..dfc9462 100644 --- a/examples/plot_OT_L1_vs_L2.py +++ b/examples/plot_OT_L1_vs_L2.py @@ -8,9 +8,12 @@ Stole the figure idea from Fig. 1 and 2 in https://arxiv.org/pdf/1706.07650.pdf -@author: rflamary """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import numpy as np import matplotlib.pylab as pl import ot diff --git a/examples/plot_WDA.py b/examples/plot_WDA.py index 9eb8693..42789f2 100644 --- a/examples/plot_WDA.py +++ b/examples/plot_WDA.py @@ -4,9 +4,12 @@ Wasserstein Discriminant Analysis ================================= -@author: rflamary """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import numpy as np import matplotlib.pylab as pl diff --git a/examples/plot_barycenter_1D.py b/examples/plot_barycenter_1D.py index ab236e1..875f44c 100644 --- a/examples/plot_barycenter_1D.py +++ b/examples/plot_barycenter_1D.py @@ -4,10 +4,12 @@ 1D Wasserstein barycenter demo ============================== - -@author: rflamary """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import numpy as np import matplotlib.pylab as pl import ot diff --git a/examples/plot_compute_emd.py b/examples/plot_compute_emd.py index 558facb..893eecf 100644 --- a/examples/plot_compute_emd.py +++ b/examples/plot_compute_emd.py @@ -4,9 +4,12 @@ 1D optimal transport ==================== -@author: rflamary """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import numpy as np import matplotlib.pylab as pl import ot diff --git a/ot/__init__.py b/ot/__init__.py index a79a5ce..6d4c4c6 100644 --- a/ot/__init__.py +++ b/ot/__init__.py @@ -4,6 +4,10 @@ """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + # All submodules and packages from . import lp @@ -24,6 +28,6 @@ from .utils import dist, unif, tic, toc, toq __version__ = "0.3.1" -__all__ = ["emd", "emd2", "sinkhorn","sinkhorn2", "utils", 'datasets', +__all__ = ["emd", "emd2", "sinkhorn", "sinkhorn2", "utils", 'datasets', 'bregman', 'lp', 'plot', 'tic', 'toc', 'toq', 'dist', 'unif', 'barycenter', 'sinkhorn_lpl1_mm', 'da', 'optim'] diff --git a/ot/bregman.py b/ot/bregman.py index fe10880..d63c51d 100644 --- a/ot/bregman.py +++ b/ot/bregman.py @@ -3,6 +3,11 @@ Bregman projections for regularized OT """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# Nicolas Courty <ncourty@irisa.fr> +# +# License: MIT License + import numpy as np @@ -103,8 +108,9 @@ def sinkhorn(a, b, M, reg, method='sinkhorn', numItermax=1000, stopThr=1e-9, ver stopThr=stopThr, verbose=verbose, log=log, **kwargs) elif method.lower() == 'sinkhorn_epsilon_scaling': def sink(): - return sinkhorn_epsilon_scaling(a, b, M, reg, numItermax=numItermax, - stopThr=stopThr, verbose=verbose, log=log, **kwargs) + return sinkhorn_epsilon_scaling( + a, b, M, reg, numItermax=numItermax, + stopThr=stopThr, verbose=verbose, log=log, **kwargs) else: print('Warning : unknown method using classic Sinkhorn Knopp') @@ -211,8 +217,9 @@ def sinkhorn2(a, b, M, reg, method='sinkhorn', numItermax=1000, stopThr=1e-9, ve stopThr=stopThr, verbose=verbose, log=log, **kwargs) elif method.lower() == 'sinkhorn_epsilon_scaling': def sink(): - return sinkhorn_epsilon_scaling(a, b, M, reg, numItermax=numItermax, - stopThr=stopThr, verbose=verbose, log=log, **kwargs) + return sinkhorn_epsilon_scaling( + a, b, M, reg, numItermax=numItermax, + stopThr=stopThr, verbose=verbose, log=log, **kwargs) else: print('Warning : unknown method using classic Sinkhorn Knopp') @@ -588,7 +595,7 @@ def sinkhorn_stabilized(a, b, M, reg, numItermax=1000, tau=1e3, stopThr=1e-9, wa cpt = cpt + 1 - #print('err=',err,' cpt=',cpt) + # print('err=',err,' cpt=',cpt) if log: log['logu'] = alpha / reg + np.log(u) log['logv'] = beta / reg + np.log(v) @@ -773,7 +780,7 @@ def sinkhorn_epsilon_scaling(a, b, M, reg, numItermax=100, epsilon0=1e4, numInne loop = False cpt = cpt + 1 - #print('err=',err,' cpt=',cpt) + # print('err=',err,' cpt=',cpt) if log: log['alpha'] = alpha log['beta'] = beta @@ -960,16 +967,16 @@ def unmix(a, D, M, M0, h0, reg, reg0, alpha, numItermax=1000, stopThr=1e-3, verb """ - #M = M/np.median(M) + # M = M/np.median(M) K = np.exp(-M / reg) - #M0 = M0/np.median(M0) + # M0 = M0/np.median(M0) K0 = np.exp(-M0 / reg0) old = h0 err = 1 cpt = 0 - #log = {'niter':0, 'all_err':[]} + # log = {'niter':0, 'all_err':[]} if log: log = {'err': []} @@ -3,6 +3,12 @@ Domain adaptation with optimal transport """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# Nicolas Courty <ncourty@irisa.fr> +# Michael Perrot <michael.perrot@univ-st-etienne.fr> +# +# License: MIT License + import numpy as np from .bregman import sinkhorn from .lp import emd @@ -472,7 +478,7 @@ def joint_OT_mapping_kernel(xs, xt, mu=1, eta=0.001, kerneltype='gaussian', sigm Kp[:ns, :ns] = K # ls regu - #K0 = K1.T.dot(K1)+eta*I + # K0 = K1.T.dot(K1)+eta*I # Kreg=I # RKHS regul @@ -484,7 +490,7 @@ def joint_OT_mapping_kernel(xs, xt, mu=1, eta=0.001, kerneltype='gaussian', sigm I = np.eye(ns) # ls regul - #K0 = K1.T.dot(K1)+eta*I + # K0 = K1.T.dot(K1)+eta*I # Kreg=I # proper kernel ridge diff --git a/ot/datasets.py b/ot/datasets.py index 4371a23..e4fe118 100644 --- a/ot/datasets.py +++ b/ot/datasets.py @@ -2,6 +2,10 @@ Simple example datasets for OT """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import numpy as np import scipy as sp @@ -3,6 +3,10 @@ Dimension reduction with optimal transport """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + from scipy import linalg import autograd.numpy as np from pymanopt.manifolds import Stiefel diff --git a/ot/gpu/__init__.py b/ot/gpu/__init__.py index 40b11c0..c8f9433 100644 --- a/ot/gpu/__init__.py +++ b/ot/gpu/__init__.py @@ -4,4 +4,9 @@ from . import bregman from . import da from .bregman import sinkhorn +# Author: Remi Flamary <remi.flamary@unice.fr> +# Leo Gautheron <https://github.com/aje> +# +# License: MIT License + __all__ = ["bregman", "da", "sinkhorn"] diff --git a/ot/gpu/bregman.py b/ot/gpu/bregman.py index 2302f80..47939c4 100644 --- a/ot/gpu/bregman.py +++ b/ot/gpu/bregman.py @@ -3,6 +3,11 @@ Bregman projections for regularized OT with GPU """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# Leo Gautheron <https://github.com/aje> +# +# License: MIT License + import numpy as np import cudamat diff --git a/ot/gpu/da.py b/ot/gpu/da.py index c66e755..05c580f 100644 --- a/ot/gpu/da.py +++ b/ot/gpu/da.py @@ -3,6 +3,14 @@ Domain adaptation with optimal transport with GPU implementation """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# Nicolas Courty <ncourty@irisa.fr> +# Michael Perrot <michael.perrot@univ-st-etienne.fr> +# Leo Gautheron <https://github.com/aje> +# +# License: MIT License + + import numpy as np from ..utils import unif from ..da import OTDA diff --git a/ot/lp/__init__.py b/ot/lp/__init__.py index db3da78..6e0bdb8 100644 --- a/ot/lp/__init__.py +++ b/ot/lp/__init__.py @@ -3,6 +3,10 @@ Solvers for the original linear program OT problem """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import numpy as np # import compiled emd from .emd_wrap import emd_c, emd2_c diff --git a/ot/lp/emd_wrap.pyx b/ot/lp/emd_wrap.pyx index 46794ab..46c96c1 100644 --- a/ot/lp/emd_wrap.pyx +++ b/ot/lp/emd_wrap.pyx @@ -1,9 +1,12 @@ # -*- coding: utf-8 -*- """ -Created on Thu Sep 11 08:42:08 2014 - -@author: rflamary +Cython linker with C solver """ + +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import numpy as np cimport numpy as np diff --git a/ot/optim.py b/ot/optim.py index adad95e..1d09adc 100644 --- a/ot/optim.py +++ b/ot/optim.py @@ -3,6 +3,10 @@ Optimization algorithms for OT """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import numpy as np from scipy.optimize.linesearch import scalar_search_armijo from .lp import emd @@ -300,7 +304,7 @@ def gcg(a, b, M, reg1, reg2, f, df, G0=None, numItermax=10, numInnerItermax=200, Mi = M + reg2 * df(G) # solve linear program with Sinkhorn - #Gc = sinkhorn_stabilized(a,b, Mi, reg1, numItermax = numInnerItermax) + # Gc = sinkhorn_stabilized(a,b, Mi, reg1, numItermax = numInnerItermax) Gc = sinkhorn(a, b, Mi, reg1, numItermax=numInnerItermax) deltaG = Gc - G @@ -2,6 +2,9 @@ Functions for plotting OT matrices """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License import numpy as np import matplotlib.pylab as pl diff --git a/ot/utils.py b/ot/utils.py index 1dee932..2b2f8b3 100644 --- a/ot/utils.py +++ b/ot/utils.py @@ -2,6 +2,11 @@ """ Various function that can be usefull """ + +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import multiprocessing from functools import reduce import time diff --git a/test/test_bregman.py b/test/test_bregman.py index fd2c972..4a800fd 100644 --- a/test/test_bregman.py +++ b/test/test_bregman.py @@ -1,17 +1,19 @@ +"""Tests for module bregman on OT with bregman projections """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License -import ot import numpy as np - -# import pytest +import ot def test_sinkhorn(): # test sinkhorn n = 100 - np.random.seed(0) + rng = np.random.RandomState(0) - x = np.random.randn(n, 2) + x = rng.randn(n, 2) u = ot.utils.unif(n) M = ot.dist(x, x) @@ -19,16 +21,47 @@ def test_sinkhorn(): G = ot.sinkhorn(u, u, M, 1, stopThr=1e-10) # check constratints - assert np.allclose(u, G.sum(1), atol=1e-05) # cf convergence sinkhorn - assert np.allclose(u, G.sum(0), atol=1e-05) # cf convergence sinkhorn + np.testing.assert_allclose( + u, G.sum(1), atol=1e-05) # cf convergence sinkhorn + np.testing.assert_allclose( + u, G.sum(0), atol=1e-05) # cf convergence sinkhorn + + +def test_sinkhorn_empty(): + # test sinkhorn + n = 100 + rng = np.random.RandomState(0) + + x = rng.randn(n, 2) + u = ot.utils.unif(n) + + M = ot.dist(x, x) + + G, log = ot.sinkhorn([], [], M, 1, stopThr=1e-10, verbose=True, log=True) + # check constratints + np.testing.assert_allclose(u, G.sum(1), atol=1e-05) + np.testing.assert_allclose(u, G.sum(0), atol=1e-05) + + G, log = ot.sinkhorn([], [], M, 1, stopThr=1e-10, + method='sinkhorn_stabilized', verbose=True, log=True) + # check constratints + np.testing.assert_allclose(u, G.sum(1), atol=1e-05) + np.testing.assert_allclose(u, G.sum(0), atol=1e-05) + + G, log = ot.sinkhorn( + [], [], M, 1, stopThr=1e-10, method='sinkhorn_epsilon_scaling', + verbose=True, log=True) + # check constratints + np.testing.assert_allclose(u, G.sum(1), atol=1e-05) + np.testing.assert_allclose(u, G.sum(0), atol=1e-05) def test_sinkhorn_variants(): # test sinkhorn n = 100 - np.random.seed(0) + rng = np.random.RandomState(0) - x = np.random.randn(n, 2) + x = rng.randn(n, 2) u = ot.utils.unif(n) M = ot.dist(x, x) @@ -37,7 +70,68 @@ def test_sinkhorn_variants(): Gs = ot.sinkhorn(u, u, M, 1, method='sinkhorn_stabilized', stopThr=1e-10) Ges = ot.sinkhorn( u, u, M, 1, method='sinkhorn_epsilon_scaling', stopThr=1e-10) + Gerr = ot.sinkhorn(u, u, M, 1, method='do_not_exists', stopThr=1e-10) - # check constratints - assert np.allclose(G0, Gs, atol=1e-05) - assert np.allclose(G0, Ges, atol=1e-05) + # check values + np.testing.assert_allclose(G0, Gs, atol=1e-05) + np.testing.assert_allclose(G0, Ges, atol=1e-05) + np.testing.assert_allclose(G0, Gerr) + + +def test_bary(): + + n_bins = 100 # nb bins + + # Gaussian distributions + a1 = ot.datasets.get_1D_gauss(n_bins, m=30, s=10) # m= mean, s= std + a2 = ot.datasets.get_1D_gauss(n_bins, m=40, s=10) + + # creating matrix A containing all distributions + A = np.vstack((a1, a2)).T + + # loss matrix + normalization + M = ot.utils.dist0(n_bins) + M /= M.max() + + alpha = 0.5 # 0<=alpha<=1 + weights = np.array([1 - alpha, alpha]) + + # wasserstein + reg = 1e-3 + bary_wass = ot.bregman.barycenter(A, M, reg, weights) + + np.testing.assert_allclose(1, np.sum(bary_wass)) + + ot.bregman.barycenter(A, M, reg, log=True, verbose=True) + + +def test_unmix(): + + n_bins = 50 # nb bins + + # Gaussian distributions + a1 = ot.datasets.get_1D_gauss(n_bins, m=20, s=10) # m= mean, s= std + a2 = ot.datasets.get_1D_gauss(n_bins, m=40, s=10) + + a = ot.datasets.get_1D_gauss(n_bins, m=30, s=10) + + # creating matrix A containing all distributions + D = np.vstack((a1, a2)).T + + # loss matrix + normalization + M = ot.utils.dist0(n_bins) + M /= M.max() + + M0 = ot.utils.dist0(2) + M0 /= M0.max() + h0 = ot.unif(2) + + # wasserstein + reg = 1e-3 + um = ot.bregman.unmix(a, D, M, M0, h0, reg, 1, alpha=0.01,) + + np.testing.assert_allclose(1, np.sum(um), rtol=1e-03, atol=1e-03) + np.testing.assert_allclose([0.5, 0.5], um, rtol=1e-03, atol=1e-03) + + ot.bregman.unmix(a, D, M, M0, h0, reg, + 1, alpha=0.01, log=True, verbose=True) diff --git a/test/test_da.py b/test/test_da.py new file mode 100644 index 0000000..dfba83f --- /dev/null +++ b/test/test_da.py @@ -0,0 +1,70 @@ +"""Tests for module da on Domain Adaptation """ + +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + +import numpy as np +import ot + + +def test_otda(): + + n_samples = 150 # nb samples + np.random.seed(0) + + xs, ys = ot.datasets.get_data_classif('3gauss', n_samples) + xt, yt = ot.datasets.get_data_classif('3gauss2', n_samples) + + a, b = ot.unif(n_samples), ot.unif(n_samples) + + # LP problem + da_emd = ot.da.OTDA() # init class + da_emd.fit(xs, xt) # fit distributions + da_emd.interp() # interpolation of source samples + da_emd.predict(xs) # interpolation of source samples + + np.testing.assert_allclose(a, np.sum(da_emd.G, 1)) + np.testing.assert_allclose(b, np.sum(da_emd.G, 0)) + + # sinkhorn regularization + lambd = 1e-1 + da_entrop = ot.da.OTDA_sinkhorn() + da_entrop.fit(xs, xt, reg=lambd) + da_entrop.interp() + da_entrop.predict(xs) + + np.testing.assert_allclose(a, np.sum(da_entrop.G, 1), rtol=1e-3, atol=1e-3) + np.testing.assert_allclose(b, np.sum(da_entrop.G, 0), rtol=1e-3, atol=1e-3) + + # non-convex Group lasso regularization + reg = 1e-1 + eta = 1e0 + da_lpl1 = ot.da.OTDA_lpl1() + da_lpl1.fit(xs, ys, xt, reg=reg, eta=eta) + da_lpl1.interp() + da_lpl1.predict(xs) + + np.testing.assert_allclose(a, np.sum(da_lpl1.G, 1), rtol=1e-3, atol=1e-3) + np.testing.assert_allclose(b, np.sum(da_lpl1.G, 0), rtol=1e-3, atol=1e-3) + + # True Group lasso regularization + reg = 1e-1 + eta = 2e0 + da_l1l2 = ot.da.OTDA_l1l2() + da_l1l2.fit(xs, ys, xt, reg=reg, eta=eta, numItermax=20, verbose=True) + da_l1l2.interp() + da_l1l2.predict(xs) + + np.testing.assert_allclose(a, np.sum(da_l1l2.G, 1), rtol=1e-3, atol=1e-3) + np.testing.assert_allclose(b, np.sum(da_l1l2.G, 0), rtol=1e-3, atol=1e-3) + + # linear mapping + da_emd = ot.da.OTDA_mapping_linear() # init class + da_emd.fit(xs, xt, numItermax=10) # fit distributions + da_emd.predict(xs) # interpolation of source samples + + # nonlinear mapping + da_emd = ot.da.OTDA_mapping_kernel() # init class + da_emd.fit(xs, xt, numItermax=10) # fit distributions + da_emd.predict(xs) # interpolation of source samples diff --git a/test/test_dr.py b/test/test_dr.py new file mode 100644 index 0000000..915012d --- /dev/null +++ b/test/test_dr.py @@ -0,0 +1,59 @@ +"""Tests for module dr on Dimensionality Reduction """ + +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + +import numpy as np +import ot +import pytest + +try: # test if autograd and pymanopt are installed + import ot.dr + nogo = False +except ImportError: + nogo = True + + +@pytest.mark.skipif(nogo, reason="Missing modules (autograd or pymanopt)") +def test_fda(): + + n_samples = 90 # nb samples in source and target datasets + np.random.seed(0) + + # generate gaussian dataset + xs, ys = ot.datasets.get_data_classif('gaussrot', n_samples) + + n_features_noise = 8 + + xs = np.hstack((xs, np.random.randn(n_samples, n_features_noise))) + + p = 1 + + Pfda, projfda = ot.dr.fda(xs, ys, p) + + projfda(xs) + + np.testing.assert_allclose(np.sum(Pfda**2, 0), np.ones(p)) + + +@pytest.mark.skipif(nogo, reason="Missing modules (autograd or pymanopt)") +def test_wda(): + + n_samples = 100 # nb samples in source and target datasets + np.random.seed(0) + + # generate gaussian dataset + xs, ys = ot.datasets.get_data_classif('gaussrot', n_samples) + + n_features_noise = 8 + + xs = np.hstack((xs, np.random.randn(n_samples, n_features_noise))) + + p = 2 + + Pwda, projwda = ot.dr.wda(xs, ys, p, maxiter=10) + + projwda(xs) + + np.testing.assert_allclose(np.sum(Pwda**2, 0), np.ones(p)) diff --git a/test/test_gpu.py b/test/test_gpu.py index 312a2d4..615c2a7 100644 --- a/test/test_gpu.py +++ b/test/test_gpu.py @@ -1,21 +1,34 @@ -import ot +"""Tests for module gpu for gpu acceleration """ + +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + import numpy as np +import ot import time import pytest +try: # test if cudamat installed + import ot.gpu + nogpu = False +except ImportError: + nogpu = True -@pytest.mark.skip(reason="No way to test GPU on travis yet") + +@pytest.mark.skipif(nogpu, reason="No GPU available") def test_gpu_sinkhorn(): - import ot.gpu - def describeRes(r): + rng = np.random.RandomState(0) + + def describe_res(r): print("min:{:.3E}, max::{:.3E}, mean::{:.3E}, std::{:.3E}".format( np.min(r), np.max(r), np.mean(r), np.std(r))) - for n in [5000]: - print(n) - a = np.random.rand(n // 4, 100) - b = np.random.rand(n, 100) + for n_samples in [50, 100, 500, 1000]: + print(n_samples) + a = rng.rand(n_samples // 4, 100) + b = rng.rand(n_samples, 100) time1 = time.time() transport = ot.da.OTDA_sinkhorn() transport.fit(a, b) @@ -26,34 +39,41 @@ def test_gpu_sinkhorn(): G2 = transport.G time3 = time.time() print("Normal sinkhorn, time: {:6.2f} sec ".format(time2 - time1)) - describeRes(G1) + describe_res(G1) print(" GPU sinkhorn, time: {:6.2f} sec ".format(time3 - time2)) - describeRes(G2) + describe_res(G2) + + np.testing.assert_allclose(G1, G2, rtol=1e-5, atol=1e-5) -@pytest.mark.skip(reason="No way to test GPU on travis yet") +@pytest.mark.skipif(nogpu, reason="No GPU available") def test_gpu_sinkhorn_lpl1(): - def describeRes(r): - print("min:{:.3E}, max:{:.3E}, mean:{:.3E}, std:{:.3E}" - .format(np.min(r), np.max(r), np.mean(r), np.std(r))) - - for n in [5000]: - print(n) - a = np.random.rand(n // 4, 100) - labels_a = np.random.randint(10, size=(n // 4)) - b = np.random.rand(n, 100) - time1 = time.time() - transport = ot.da.OTDA_lpl1() - transport.fit(a, labels_a, b) - G1 = transport.G - time2 = time.time() - transport = ot.gpu.da.OTDA_lpl1() - transport.fit(a, labels_a, b) - G2 = transport.G - time3 = time.time() - print("Normal sinkhorn lpl1, time: {:6.2f} sec ".format( - time2 - time1)) - describeRes(G1) - print(" GPU sinkhorn lpl1, time: {:6.2f} sec ".format( - time3 - time2)) - describeRes(G2) + + rng = np.random.RandomState(0) + + def describe_res(r): + print("min:{:.3E}, max:{:.3E}, mean:{:.3E}, std:{:.3E}" + .format(np.min(r), np.max(r), np.mean(r), np.std(r))) + + for n_samples in [50, 100, 500]: + print(n_samples) + a = rng.rand(n_samples // 4, 100) + labels_a = np.random.randint(10, size=(n_samples // 4)) + b = rng.rand(n_samples, 100) + time1 = time.time() + transport = ot.da.OTDA_lpl1() + transport.fit(a, labels_a, b) + G1 = transport.G + time2 = time.time() + transport = ot.gpu.da.OTDA_lpl1() + transport.fit(a, labels_a, b) + G2 = transport.G + time3 = time.time() + print("Normal sinkhorn lpl1, time: {:6.2f} sec ".format( + time2 - time1)) + describe_res(G1) + print(" GPU sinkhorn lpl1, time: {:6.2f} sec ".format( + time3 - time2)) + describe_res(G2) + + np.testing.assert_allclose(G1, G2, rtol=1e-5, atol=1e-5) diff --git a/test/test_optim.py b/test/test_optim.py new file mode 100644 index 0000000..69496a5 --- /dev/null +++ b/test/test_optim.py @@ -0,0 +1,67 @@ +"""Tests for module optim fro OT optimization """ + +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + +import numpy as np +import ot + + +def test_conditional_gradient(): + + n_bins = 100 # nb bins + np.random.seed(0) + # bin positions + x = np.arange(n_bins, dtype=np.float64) + + # Gaussian distributions + a = ot.datasets.get_1D_gauss(n_bins, m=20, s=5) # m= mean, s= std + b = ot.datasets.get_1D_gauss(n_bins, m=60, s=10) + + # loss matrix + M = ot.dist(x.reshape((n_bins, 1)), x.reshape((n_bins, 1))) + M /= M.max() + + def f(G): + return 0.5 * np.sum(G**2) + + def df(G): + return G + + reg = 1e-1 + + G, log = ot.optim.cg(a, b, M, reg, f, df, verbose=True, log=True) + + np.testing.assert_allclose(a, G.sum(1)) + np.testing.assert_allclose(b, G.sum(0)) + + +def test_generalized_conditional_gradient(): + + n_bins = 100 # nb bins + np.random.seed(0) + # bin positions + x = np.arange(n_bins, dtype=np.float64) + + # Gaussian distributions + a = ot.datasets.get_1D_gauss(n_bins, m=20, s=5) # m= mean, s= std + b = ot.datasets.get_1D_gauss(n_bins, m=60, s=10) + + # loss matrix + M = ot.dist(x.reshape((n_bins, 1)), x.reshape((n_bins, 1))) + M /= M.max() + + def f(G): + return 0.5 * np.sum(G**2) + + def df(G): + return G + + reg1 = 1e-3 + reg2 = 1e-1 + + G, log = ot.optim.gcg(a, b, M, reg1, reg2, f, df, verbose=True, log=True) + + np.testing.assert_allclose(a, G.sum(1), atol=1e-05) + np.testing.assert_allclose(b, G.sum(0), atol=1e-05) diff --git a/test/test_ot.py b/test/test_ot.py index 3fa1bc4..acd8718 100644 --- a/test/test_ot.py +++ b/test/test_ot.py @@ -1,9 +1,11 @@ +"""Tests for main module ot """ +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License -import ot import numpy as np - -# import pytest +import ot def test_doctest(): @@ -20,9 +22,9 @@ def test_doctest(): def test_emd_emd2(): # test emd and emd2 for simple identity n = 100 - np.random.seed(0) + rng = np.random.RandomState(0) - x = np.random.randn(n, 2) + x = rng.randn(n, 2) u = ot.utils.unif(n) M = ot.dist(x, x) @@ -30,12 +32,37 @@ def test_emd_emd2(): G = ot.emd(u, u, M) # check G is identity - assert np.allclose(G, np.eye(n) / n) + np.testing.assert_allclose(G, np.eye(n) / n) + # check constratints + np.testing.assert_allclose(u, G.sum(1)) # cf convergence sinkhorn + np.testing.assert_allclose(u, G.sum(0)) # cf convergence sinkhorn w = ot.emd2(u, u, M) + # check loss=0 + np.testing.assert_allclose(w, 0) + + +def test_emd_empty(): + # test emd and emd2 for simple identity + n = 100 + rng = np.random.RandomState(0) + + x = rng.randn(n, 2) + u = ot.utils.unif(n) + + M = ot.dist(x, x) + + G = ot.emd([], [], M) + + # check G is identity + np.testing.assert_allclose(G, np.eye(n) / n) + # check constratints + np.testing.assert_allclose(u, G.sum(1)) # cf convergence sinkhorn + np.testing.assert_allclose(u, G.sum(0)) # cf convergence sinkhorn + w = ot.emd2([], [], M) # check loss=0 - assert np.allclose(w, 0) + np.testing.assert_allclose(w, 0) def test_emd2_multi(): @@ -43,7 +70,6 @@ def test_emd2_multi(): from ot.datasets import get_1D_gauss as gauss n = 1000 # nb bins - np.random.seed(0) # bin positions x = np.arange(n, dtype=np.float64) @@ -51,7 +77,7 @@ def test_emd2_multi(): # Gaussian distributions a = gauss(n, m=20, s=5) # m= mean, s= std - ls = np.arange(20, 1000, 10) + ls = np.arange(20, 1000, 20) nb = len(ls) b = np.zeros((n, nb)) for i in range(nb): @@ -73,4 +99,4 @@ def test_emd2_multi(): emdn = ot.emd2(a, b, M) ot.toc('multi proc : {} s') - assert np.allclose(emd1, emdn) + np.testing.assert_allclose(emd1, emdn) diff --git a/test/test_plot.py b/test/test_plot.py new file mode 100644 index 0000000..f7debee --- /dev/null +++ b/test/test_plot.py @@ -0,0 +1,49 @@ +"""Tests for module plot for visualization """ + +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License + +import numpy as np +import matplotlib +matplotlib.use('Agg') + + +def test_plot1D_mat(): + + import ot + + n_bins = 100 # nb bins + + # bin positions + x = np.arange(n_bins, dtype=np.float64) + + # Gaussian distributions + a = ot.datasets.get_1D_gauss(n_bins, m=20, s=5) # m= mean, s= std + b = ot.datasets.get_1D_gauss(n_bins, m=60, s=10) + + # loss matrix + M = ot.dist(x.reshape((n_bins, 1)), x.reshape((n_bins, 1))) + M /= M.max() + + ot.plot.plot1D_mat(a, b, M, 'Cost matrix M') + + +def test_plot2D_samples_mat(): + + import ot + + n_bins = 50 # nb samples + + mu_s = np.array([0, 0]) + cov_s = np.array([[1, 0], [0, 1]]) + + mu_t = np.array([4, 4]) + cov_t = np.array([[1, -.8], [-.8, 1]]) + + xs = ot.datasets.get_2D_samples_gauss(n_bins, mu_s, cov_s) + xt = ot.datasets.get_2D_samples_gauss(n_bins, mu_t, cov_t) + + G = 1.0 * (np.random.rand(n_bins, n_bins) < 0.01) + + ot.plot.plot2D_samples_mat(xs, xt, G, thr=1e-5) diff --git a/test/test_utils.py b/test/test_utils.py index 3219fce..1bd37cd 100644 --- a/test/test_utils.py +++ b/test/test_utils.py @@ -1,10 +1,13 @@ +"""Tests for module utils for timing and parallel computation """ + +# Author: Remi Flamary <remi.flamary@unice.fr> +# +# License: MIT License import ot import numpy as np -# import pytest - def test_parmap(): @@ -15,11 +18,11 @@ def test_parmap(): a = np.arange(n) - l1 = map(f, a) + l1 = list(map(f, a)) - l2 = ot.utils.parmap(f, a) + l2 = list(ot.utils.parmap(f, a)) - assert np.allclose(l1, l2) + np.testing.assert_allclose(l1, l2) def test_tic_toc(): @@ -32,10 +35,10 @@ def test_tic_toc(): t2 = ot.toq() # test timing - assert np.allclose(0.5, t, rtol=1e-2, atol=1e-2) + np.testing.assert_allclose(0.5, t, rtol=1e-2, atol=1e-2) # test toc vs toq - assert np.allclose(t, t2, rtol=1e-2, atol=1e-2) + np.testing.assert_allclose(t, t2, rtol=1e-2, atol=1e-2) def test_kernel(): @@ -47,7 +50,7 @@ def test_kernel(): K = ot.utils.kernel(x, x) # gaussian kernel has ones on the diagonal - assert np.allclose(np.diag(K), np.ones(n)) + np.testing.assert_allclose(np.diag(K), np.ones(n)) def test_unif(): @@ -56,7 +59,7 @@ def test_unif(): u = ot.unif(n) - assert np.allclose(1, np.sum(u)) + np.testing.assert_allclose(1, np.sum(u)) def test_dist(): @@ -71,6 +74,52 @@ def test_dist(): D[i, j] = np.sum(np.square(x[i, :] - x[j, :])) D2 = ot.dist(x, x) + D3 = ot.dist(x) # dist shoul return squared euclidean - assert np.allclose(D, D2) + np.testing.assert_allclose(D, D2) + np.testing.assert_allclose(D, D3) + + +def test_dist0(): + + n = 100 + M = ot.utils.dist0(n, method='lin_square') + + # dist0 default to linear sampling with quadratic loss + np.testing.assert_allclose(M[0, -1], (n - 1) * (n - 1)) + + +def test_dots(): + + n1, n2, n3, n4 = 100, 50, 200, 100 + + A = np.random.randn(n1, n2) + B = np.random.randn(n2, n3) + C = np.random.randn(n3, n4) + + X1 = ot.utils.dots(A, B, C) + + X2 = A.dot(B.dot(C)) + + np.testing.assert_allclose(X1, X2) + + +def test_clean_zeros(): + + n = 100 + nz = 50 + nz2 = 20 + u1 = ot.unif(n) + u1[:nz] = 0 + u1 = u1 / u1.sum() + u2 = ot.unif(n) + u2[:nz2] = 0 + u2 = u2 / u2.sum() + + M = ot.utils.dist0(n) + + a, b, M2 = ot.utils.clean_zeros(u1, u2, M) + + assert len(a) == n - nz + assert len(b) == n - nz2 |