Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port to Python 3 & NumPy API 1.7 #7

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ build
*.pyc
*.sw*
*.mex*
.vscode
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ or by running the setup file manually
cd pyemd
python setup.py install

Note the code requires Python 2 (Python 3 is not supported) and depends on the
`numpy` and `scipy` packages. So have those installed first. The build will
likely fail if it can't find them. For more information, see:
Note the code requires Python 3 and depends on the `numpy` and `scipy` packages.
So have those installed first. The build will likely fail if it can't find them.
For more information, see:

+ [NumPy](http://www.numpy.org/): Library for efficient matrix math in Python
+ [SciPy](http://www.scipy.org/): Library for more MATLAB-like functionality
Expand Down
10 changes: 5 additions & 5 deletions c_emd/emd.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
#include <emd.h>

struct basic_variable **initialize_flow(int n_x, double *weight_x,
int n_y, double *weight_y,
double **cost);
int n_y, double *weight_y,
double **cost);
struct basic_variable *init_basic(int row, int col, double flow);
void insert_basic(struct basic_variable **basis, int size,
struct basic_variable *node);
Expand Down Expand Up @@ -205,14 +205,14 @@ double emd(int n_x, double *weight_x,
* using the "Northwest Corner Rule"
*/
struct basic_variable **initialize_flow(int n_x, double *weight_x,
int n_y, double *weight_y,
double **cost){
int n_y, double *weight_y,
double **cost){
struct basic_variable **basis;
struct basic_variable *basic;
double *remaining_x;
double *remaining_y;
int fx, fy, b, B;

b = 0;
B = n_x + n_y - 1;

Expand Down
2 changes: 1 addition & 1 deletion c_emd/emd.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
/* EMD - compute earth mover's distance */
/****************************************/

#define EPSILON 1e-12
#define EPSILON 1e-10
#define FALSE 0
#define TRUE 1

Expand Down
29 changes: 19 additions & 10 deletions c_emd/pyemd.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
/* Python Wrapper for EMD */
/**************************/
#include <Python.h>
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <numpy/arrayobject.h>

#include <emd.h>
Expand All @@ -24,15 +25,15 @@ static PyObject *_emd(PyObject *self, PyObject *args) {
if (arg1 == NULL || arg2 == NULL || arg3 == NULL || return_flows == NULL) {
return NULL;
}

// Convert inputs
// Assumes array data is contiguous
n_x = (int) arg1->dimensions[0];
n_y = (int) arg2->dimensions[0];
weight_x = (double *)arg1->data;
weight_y = (double *)arg2->data;
n_x = (int) PyArray_DIMS(arg1)[0];
n_y = (int) PyArray_DIMS(arg2)[0];
weight_x = (double *)PyArray_DATA(arg1);
weight_y = (double *)PyArray_DATA(arg2);
cost = (double **) malloc((size_t) n_x * sizeof(double *));
data_ptr = (double *)arg3->data;
data_ptr = (double *)PyArray_DATA(arg3);
for (i = 0; i < n_x; i++) { cost[i] = data_ptr + i*n_y; }

double **flows_data_ptr = NULL;
Expand All @@ -44,7 +45,7 @@ static PyObject *_emd(PyObject *self, PyObject *args) {
}

distance = emd(n_x, weight_x, n_y, weight_y, cost, flows_data_ptr);

free(cost);
PyObject *retval = NULL;
if (flows_data_ptr) {
Expand Down Expand Up @@ -77,9 +78,17 @@ static PyMethodDef c_emd_methods[] = {
{ NULL, NULL, 0, NULL }
};

void initc_emd(void)
static struct PyModuleDef emdmodule = {
PyModuleDef_HEAD_INIT,
"c_emd",
"Earth Mover's Distance",
0,
c_emd_methods
};

PyMODINIT_FUNC
PyInit_c_emd(void)
{
Py_InitModule3("c_emd", c_emd_methods,
"Earth Mover's Distance");
import_array();
return PyModule_Create(&emdmodule);
}
2 changes: 1 addition & 1 deletion emd/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""
Earth Mover's Distance in Python
"""
from emd import emd
from .emd import emd
35 changes: 35 additions & 0 deletions emd/emd.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,31 @@ def emd(X, Y, X_weights=None, Y_weights=None, distance='euclidean',
@param return_flows : whether to return the flows between cells in addition
to the distance (default False)
"""

if X_weights is not None:
if not isinstance(X_weights, np.ndarray):
raise ValueError("X_weights has to be a Numpy ndarray, not '{}'.".format(type(X_weights)))
if not X_weights.flags['C_CONTIGUOUS']:
raise ValueError("X_weights has to be a C-contiguous array.")
if not X_weights.dtype == np.float64:
raise ValueError("X_weights has to be of dtype double (float64).")
if np.any(X_weights < 0):
raise ValueError("X_weights has to be non-negative.")
if not np.isclose(np.sum(X_weights), 1):
raise ValueError("X_weights has to sum to 1.")

if Y_weights is not None:
if not isinstance(Y_weights, np.ndarray):
raise ValueError("Y_weights has to be a Numpy ndarray, not '{}'.".format(type(Y_weights)))
if not Y_weights.flags['C_CONTIGUOUS']:
raise ValueError("Y_weights has to be a C-contiguous array.")
if not Y_weights.dtype == np.float64:
raise ValueError("Y_weights has to be of dtype double (float64).")
if np.any(Y_weights < 0):
raise ValueError("Y_weights has to be non-negative.")
if not np.isclose(np.sum(Y_weights), 1):
raise ValueError("Y_weights has to sum to 1.")

if distance != 'precomputed':
n = len(X)
m = len(Y)
Expand All @@ -39,6 +64,16 @@ def emd(X, Y, X_weights=None, Y_weights=None, distance='euclidean',
else:
if D is None:
raise ValueError("D must be supplied when distance='precomputed'")

if not isinstance(D, np.ndarray):
raise ValueError("D has to be a Numpy ndarray, not '{}'.".format(type(D)))
if not D.flags['C_CONTIGUOUS']:
raise ValueError("D has to be a C-contiguous array.")
if not D.dtype == np.float64:
raise ValueError("D has to be of dtype double (float64).")
if np.any(D < 0):
raise ValueError("D has to be non-negative.")

n, m = D.shape
if X_weights is None:
X_weights = np.ones(n)/n
Expand Down
6 changes: 3 additions & 3 deletions test/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@
[-1.0, 0.0],
[0.5, 5.0]])

print emd(X, Y)
print(emd(X, Y))

dist, flows = emd(X, Y, return_flows=True)
print flows
print(flows)

dist, flows = emd(X, Z, return_flows=True)
print flows
print(flows)