Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
b057477
add bonneel ot code
ayushkarnawat Jun 16, 2020
be5ea99
fix pickling; parmap not needed since EMD code is multi-threaded
ayushkarnawat Jun 18, 2020
f34da84
compile flags
ayushkarnawat Jun 18, 2020
0ceff02
missing import
ayushkarnawat Jun 18, 2020
18cd7b5
add args if not None
ayushkarnawat Jun 18, 2020
f8d7b5d
compile with -lomp flag
ayushkarnawat Jun 19, 2020
ad5d152
env vars for macos ci
ayushkarnawat Jun 19, 2020
b6f8933
Revert "env vars for macos ci"
ayushkarnawat Jun 19, 2020
3949f06
fix indent
ayushkarnawat Jun 19, 2020
abdf6b2
OpenMP install (via homebrew)
ayushkarnawat Jun 19, 2020
712b3b7
verbose mode (debugging)
ayushkarnawat Jun 19, 2020
6a8e63c
retry ci
ayushkarnawat Jun 19, 2020
4dc4b59
another attempt; is it the env vars?
ayushkarnawat Jun 19, 2020
aae0b8f
pls work now
ayushkarnawat Jun 19, 2020
df29ede
try env vars again
ayushkarnawat Jun 19, 2020
2fbf0fb
move -lomp flag
ayushkarnawat Jun 19, 2020
82f7d8e
env vars don't work correcty (yet)
ayushkarnawat Jun 19, 2020
c91da7c
new flags
ayushkarnawat Jun 19, 2020
9347510
conda env ci
ayushkarnawat Jun 19, 2020
05691aa
curly braces?
ayushkarnawat Jun 19, 2020
e20397b
make miniconda.sh executable
ayushkarnawat Jun 19, 2020
2fdc5f1
add quotes
ayushkarnawat Jun 19, 2020
939599e
export variables instead
ayushkarnawat Jun 19, 2020
24e032c
don't source bash_profile
ayushkarnawat Jun 19, 2020
face1f9
debug what is happening
ayushkarnawat Jun 19, 2020
fb4bc5d
view the root dir (find a better way to do this)
ayushkarnawat Jun 19, 2020
90601bc
where are the env vars saved
ayushkarnawat Jun 19, 2020
922aca8
write env vars to bash_profile
ayushkarnawat Jun 19, 2020
6984fc8
more debugging
ayushkarnawat Jun 19, 2020
f643cf9
remove clang env vars
ayushkarnawat Jun 19, 2020
4b8097e
upgrade setuptools
ayushkarnawat Jun 19, 2020
1493ec5
another attempt
ayushkarnawat Jun 19, 2020
9918713
dont use -openmp flag
ayushkarnawat Jun 19, 2020
ff050cd
use env vars in setup (not through CI)
ayushkarnawat Jun 19, 2020
a3c752a
try flags in .bashrc
ayushkarnawat Jun 19, 2020
2e06fb3
Use C++ compiler if available
ayushkarnawat Jun 19, 2020
9fed619
don't set cc/cxx flags
ayushkarnawat Jun 19, 2020
cb4c6ea
cat the result
ayushkarnawat Jun 19, 2020
6dc30b4
space between flags for ci
ayushkarnawat Jun 19, 2020
9761b8e
export flags w/o .bashrc file
ayushkarnawat Jun 19, 2020
fc7d9c9
why doesn' this work
ayushkarnawat Jun 19, 2020
0d737e7
retry conda
ayushkarnawat Jun 20, 2020
df62139
use conda github actions
ayushkarnawat Jun 20, 2020
e23da19
don't update conda
ayushkarnawat Jun 20, 2020
b3bb3b8
test if env flags were set
ayushkarnawat Jun 20, 2020
15e9ec9
install everything with conda
ayushkarnawat Jun 20, 2020
d951e5e
activate env
ayushkarnawat Jun 20, 2020
b8e7aa6
no variable for env name
ayushkarnawat Jun 20, 2020
f58b87c
use brrew again; set env variables
ayushkarnawat Jun 20, 2020
80c3f53
dont use clang compiler
ayushkarnawat Jun 20, 2020
651ce7e
another attempt
ayushkarnawat Jun 20, 2020
6e46bb4
upgrade cython to use py3
ayushkarnawat Jun 20, 2020
8ade886
force c compiler to compile with c++
ayushkarnawat Jun 20, 2020
c7d9d08
is it a problem with pip
ayushkarnawat Jun 20, 2020
e86cffb
more debugging
ayushkarnawat Jun 20, 2020
e4d7199
use cython 3
ayushkarnawat Jun 20, 2020
ddac87a
upgrade setuptools
ayushkarnawat Jun 20, 2020
ca57804
another round
ayushkarnawat Jun 20, 2020
fc66817
this is annoying
ayushkarnawat Jun 20, 2020
e975a6c
stop recognizing env flags as files
ayushkarnawat Jun 21, 2020
412a93b
[ci] use g++
ayushkarnawat Jun 21, 2020
fd43587
try link args (linking openmp)
ayushkarnawat Jun 22, 2020
1ed55d9
use openmp flag as linker
ayushkarnawat Jun 27, 2020
fbf4459
LDFLAGS are link args
ayushkarnawat Jun 27, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 40 additions & 27 deletions .github/workflows/build_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ jobs:
flake8 examples/ ot/ test/ --count --max-line-length=127 --statistics
- name: Install POT
run: |
pip install -e .
pip install --verbose --no-build-isolation -e .
- name: Run tests
run: |
python -m pytest -v test/ ot/ --doctest-modules --ignore ot/gpu/ --cov=ot
Expand Down Expand Up @@ -71,37 +71,50 @@ jobs:
pip install -U "sklearn"
- name: Install POT
run: |
pip install -e .
pip install --verbose -e .
- name: Run tests
run: |
python -m pytest -v test/ ot/ --ignore ot/gpu/


macos:
runs-on: macOS-latest
strategy:
max-parallel: 4
matrix:
python-version: [3.7]

steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
pip install pytest "pytest-cov<2.6"
pip install -U "sklearn"
- name: Install POT
run: |
pip install -e .
- name: Run tests
run: |
python -m pytest -v test/ ot/ --doctest-modules --ignore ot/gpu/ --cov=ot
runs-on: macOS-latest
strategy:
max-parallel: 4
matrix:
python-version: [3.7]

steps:
- uses: actions/checkout@v1
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
# install OpenMP (via homebrew)
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
brew install libomp
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you also keep a macos build without openmp so we can check it works on a fresh machine too? thx

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes indeed i would keep the classical build on macosx and add a build/test with openmp

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was planning on doing this (just after I fixed the issues with the full build)

# install (necessary) recipes
python -m pip install -U pip setuptools
pip install -U setuptools
pip install -r requirements.txt
pip install pytest "pytest-cov<2.6"
pip install -U "sklearn"
- name: Set environment variables
run: |
echo '::set-env name=CC::/usr/bin/g++' # hacky(!!) force c++ compiler
echo '::set-env name=CXX::/usr/bin/g++'
echo '::set-env name=CPPFLAGS::"-Xpreprocessor -fopenmp"'
echo '::set-env name=CFLAGS::"-I/usr/local/opt/libomp/include"'
echo '::set-env name=CXXFLAGS::"-I/usr/local/opt/libomp/include"'
echo '::set-env name=LDFLAGS::"-Wl,-rpath,/usr/local/opt/libomp/lib -L/usr/local/opt/libomp/lib -lomp"'
- name: Install POT
run: |
pip install --verbose -e .
- name: Run tests
run: |
python -m pytest -v test/ ot/ --doctest-modules --ignore ot/gpu/ --cov=ot


windows:
Expand All @@ -125,7 +138,7 @@ jobs:
pip install -U "sklearn"
- name: Install POT
run: |
pip install -e .
pip install --verbose -e .
- name: Run tests
run: |
python -m pytest -v test/ ot/ --doctest-modules --ignore ot/gpu/ --cov=ot
14 changes: 7 additions & 7 deletions ot/lp/EMD.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,22 @@
#ifndef EMD_H
#define EMD_H

#include <iostream>
#include <vector>
#include "network_simplex_simple.h"

using namespace lemon;
typedef unsigned int node_id_type;

typedef int64_t arc_id_type; // handle (n1*n2+n1+n2) nodes (I64_MAX=3037000500^2)
typedef double supply_type; // handle sum of supplies and demand (should be signed)
typedef double cost_type; // handle number of arcs * maximum cost (should be signed)

enum ProblemType {
INFEASIBLE,
OPTIMAL,
UNBOUNDED,
MAX_ITER_REACHED
};

int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double* alpha, double* beta, double *cost, int maxIter);


int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G,
double* alpha, double* beta, double *cost, int maxIter);

#endif
#endif // EMD_H
75 changes: 36 additions & 39 deletions ot/lp/EMD_wrapper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,100 +9,97 @@
*
* Please give relevant credit to the original author (Nicolas Bonneel) if
* you use this code for a publication.
*
*/

#include <iostream>
#include "EMD.h"


int EMD_wrap(int n1, int n2, double *X, double *Y, double *D, double *G,
double* alpha, double* beta, double *cost, int maxIter) {
// beware M and C anre strored in row major C style!!!
int n, m, i, cur;
double* alpha, double* beta, double *cost, int maxIter) {
// Beware M and C are stored in row major C style!!!
int n; // number of nonzero coordinates (row)
int m; // number of nonzero coordinates (column)
int cur;

typedef FullBipartiteDigraph Digraph;
DIGRAPH_TYPEDEFS(FullBipartiteDigraph);

// Get the number of non zero coordinates for r and c
n=0;
// Count number of non zero coordinates for source (row) and target (column)
n = 0;
for (int i=0; i<n1; i++) {
double val=*(X+i);
double val = *(X+i);
if (val>0) {
n++;
}else if(val<0){
} else if (val<0) {
return INFEASIBLE;
}
}
m=0;
m = 0;
for (int i=0; i<n2; i++) {
double val=*(Y+i);
if (val>0) {
double val = *(Y+i);
if (val > 0) {
m++;
}else if(val<0){
} else if (val < 0) {
return INFEASIBLE;
}
}

// Define the graph

std::vector<int> indI(n), indJ(m);
std::vector<double> weights1(n), weights2(m);
std::vector<int> indI(n);
std::vector<int> indJ(m);
// Works faster with integer weights, however weights can be a density (aka
// double).
std::vector<supply_type> weights1(n);
std::vector<supply_type> weights2(m);
Digraph di(n, m);
NetworkSimplexSimple<Digraph,double,double, node_id_type> net(di, true, n+m, n*m, maxIter);

// Set supply and demand, don't account for 0 values (faster)
NetworkSimplexSimple<Digraph, supply_type, cost_type, arc_id_type> net(di, true, n+m, n*m, maxIter);

// Set supply and demand; don't account for 0 values (faster)
cur=0;
for (int i=0; i<n1; i++) {
double val=*(X+i);
double val = *(X+i);
if (val>0) {
weights1[ cur ] = val;
weights1[cur] = val;
indI[cur++]=i;
}
}

// Demand is actually negative supply...

// Demand is actually negative supply
cur=0;
for (int i=0; i<n2; i++) {
double val=*(Y+i);
double val = *(Y+i);
if (val>0) {
weights2[ cur ] = -val;
weights2[cur] = -val;
indJ[cur++]=i;
}
}


net.supplyMap(&weights1[0], n, &weights2[0], m);

// Set the cost of each edge
for (int i=0; i<n; i++) {
for (int j=0; j<m; j++) {
double val=*(D+indI[i]*n2+indJ[j]);
cost_type val = *(D+indI[i]*n2+indJ[j]);
net.setCost(di.arcFromId(i*m+j), val);
}
}


// Solve the problem with the network simplex algorithm

int ret=net.run();
if (ret==(int)net.OPTIMAL || ret==(int)net.MAX_ITER_REACHED) {
*cost = 0;
Arc a; di.first(a);
// Solve the problem using the network simplex algorithm
int ret = net.run();
*cost = net.totalCost();
if (ret == (int)net.OPTIMAL) {
// *cost = 0;
Arc a;
di.first(a);
for (; a != INVALID; di.next(a)) {
int i = di.source(a);
int j = di.target(a);
double flow = net.flow(a);
*cost += flow * (*(D+indI[i]*n2+indJ[j-n]));
// *cost += flow * (*(D+indI[i]*n2+indJ[j-n]));
*(G+indI[i]*n2+indJ[j-n]) = flow;
*(alpha + indI[i]) = -net.potential(i);
*(beta + indJ[j-n]) = net.potential(j);
}

}


return ret;
}

15 changes: 2 additions & 13 deletions ot/lp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,10 +371,6 @@ def emd2(a, b, M, processes=multiprocessing.cpu_count(),
b = np.asarray(b, dtype=np.float64)
M = np.asarray(M, dtype=np.float64)

# problem with pikling Forks
if sys.platform.endswith('win32'):
processes = 1

# if empty array given then use uniform distributions
if len(a) == 0:
a = np.ones((M.shape[0],), dtype=np.float64) / M.shape[0]
Expand Down Expand Up @@ -421,16 +417,9 @@ def f(b):
check_result(result_code)
return cost

if len(b.shape) == 1:
if b.shape[0] == 1:
return f(b)
nb = b.shape[1]

if processes > 1:
res = parmap(f, [b[:, i] for i in range(nb)], processes)
else:
res = list(map(f, [b[:, i].copy() for i in range(nb)]))

return res
return list(map(f, [b[:, i].copy() for i in range(b.shape[1])]))


def free_support_barycenter(measures_locations, measures_weights, X_init, b=None, weights=None, numItermax=100,
Expand Down
36 changes: 19 additions & 17 deletions ot/lp/emd_wrap.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ import warnings


cdef extern from "EMD.h":
int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G, double* alpha, double* beta, double *cost, int maxIter) nogil
cdef enum ProblemType: INFEASIBLE, OPTIMAL, UNBOUNDED, MAX_ITER_REACHED
int EMD_wrap(int n1,int n2, double *X, double *Y,double *D, double *G,
double* alpha, double* beta, double *cost, int maxIter) nogil
cdef enum ProblemType: INFEASIBLE, OPTIMAL, UNBOUNDED


def check_result(result_code):
Expand All @@ -31,18 +32,18 @@ def check_result(result_code):
message = "Problem infeasible. Check that a and b are in the simplex"
elif result_code == UNBOUNDED:
message = "Problem unbounded"
elif result_code == MAX_ITER_REACHED:
message = "numItermax reached before optimality. Try to increase numItermax."
warnings.warn(message)
return message

@cython.boundscheck(False)
@cython.wraparound(False)
def emd_c(np.ndarray[double, ndim=1, mode="c"] a, np.ndarray[double, ndim=1, mode="c"] b, np.ndarray[double, ndim=2, mode="c"] M, int max_iter):
def emd_c(np.ndarray[double, ndim=1, mode="c"] a,
np.ndarray[double, ndim=1, mode="c"] b,
np.ndarray[double, ndim=2, mode="c"] M,
int max_iter):
"""
Solves the Earth Movers distance problem and returns the optimal transport matrix

gamm=emd(a,b,M)
Solves the Earth Movers distance problem and returns the optimal
transport matrix :math:`gamma=emd(a,b,M)`.

.. math::
\gamma = arg\min_\gamma <\gamma,M>_F
Expand Down Expand Up @@ -82,7 +83,6 @@ def emd_c(np.ndarray[double, ndim=1, mode="c"] a, np.ndarray[double, ndim=1, mod
-------
gamma: (ns x nt) numpy.ndarray
Optimal transportation matrix for the given parameters

"""
cdef int n1= M.shape[0]
cdef int n2= M.shape[1]
Expand All @@ -109,10 +109,12 @@ def emd_c(np.ndarray[double, ndim=1, mode="c"] a, np.ndarray[double, ndim=1, mod
# init OT matrix
G=np.zeros([n1, n2])

# calling the function
# solve lp, allowing multi-thread ops
with nogil:
result_code = EMD_wrap(n1, n2, <double*> a.data, <double*> b.data, <double*> M.data, <double*> G.data, <double*> alpha.data, <double*> beta.data, <double*> &cost, max_iter)

result_code = EMD_wrap(n1, n2, <double*> a.data, <double*> b.data,
<double*> M.data, <double*> G.data,
<double*> alpha.data, <double*> beta.data,
<double*> &cost, max_iter)
return G, cost, alpha, beta, result_code


Expand All @@ -126,7 +128,7 @@ def emd_1d_sorted(np.ndarray[double, ndim=1, mode="c"] u_weights,
double p=1.):
r"""
Solves the Earth Movers distance problem between sorted 1d measures and
returns the OT matrix and the associated cost
returns the OT matrix and the associated cost.

Parameters
----------
Expand All @@ -144,7 +146,7 @@ def emd_1d_sorted(np.ndarray[double, ndim=1, mode="c"] u_weights,
`'sqeuclidean'`, `'minkowski'`, `'cityblock'`, or `'euclidean'` metrics
are used.
p: float, optional (default=1.0)
The p-norm to apply for if metric='minkowski'
The p-norm to apply for if metric='minkowski'

Returns
-------
Expand All @@ -153,8 +155,8 @@ def emd_1d_sorted(np.ndarray[double, ndim=1, mode="c"] u_weights,
indices: (n, 2) ndarray, int64
Indices of the values stored in gamma for the Optimal transportation
matrix
cost
cost associated to the optimal transportation
cost: float
Cost associated to the optimal transportation
"""
cdef double cost = 0.
cdef Py_ssize_t n = u_weights.shape[0]
Expand All @@ -170,7 +172,7 @@ def emd_1d_sorted(np.ndarray[double, ndim=1, mode="c"] u_weights,
cdef np.ndarray[double, ndim=1, mode="c"] G = np.zeros((n + m - 1, ),
dtype=np.float64)
cdef np.ndarray[long, ndim=2, mode="c"] indices = np.zeros((n + m - 1, 2),
dtype=np.int)
dtype=np.int)
cdef Py_ssize_t cur_idx = 0
while True:
if metric == 'sqeuclidean':
Expand Down
Loading