Skip to content
This repository has been archived by the owner on Feb 3, 2023. It is now read-only.

Commit

Permalink
Get cholmod working with python3 (#17)
Browse files Browse the repository at this point in the history
- Remove reliance on baiji for a single test asset
- blmath.numerics.linalg.cholmod working in python 3
- Set up tox to run tests locally across python versions
- Removed conditional import from OpenDR (and all references to OpenDR)
  • Loading branch information
algrs authored and paulmelnikow committed Sep 20, 2019
1 parent 773f736 commit 03299a1
Show file tree
Hide file tree
Showing 19 changed files with 414 additions and 359 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Changelog
=========

## 1.6.3 (Sep 9, 2019)

- Remove reliance on baiji for a single test asset
- blmath.numerics.linalg.cholmod working in python 3
- Set up tox to run tests locally across python versions
- Removed conditional import from OpenDR (and all references to OpenDR)

## 1.6.2 (Sep 9, 2019)

- Merge Metabolize fork back into [original repo at its new home][repo].
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,15 @@ rake unittest
rake lint
```

Tests are configured to run in both python 2.7 and 3.6 locally via tox as well as in CircleCI.
To run tests in multiple versions of python, run `tox`:

```sh
pip install -r requirements_dev.txt
tox
```

You need to make sure that `python2.7` and `python3.6` are valid commands; this can be done in pyenv via `pyenv global 3.6.5 2.7.15`

Contribute
----------
Expand Down
2 changes: 1 addition & 1 deletion blmath/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = '1.6.2'
__version__ = '1.6.3'
18 changes: 0 additions & 18 deletions blmath/cache.py

This file was deleted.

4 changes: 3 additions & 1 deletion blmath/geometry/primitives/plane.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import six
import numpy as np
from blmath.numerics import vx


class Plane(object):
'''
A 2-D plane in 3-space (not a hyperplane).
Expand Down Expand Up @@ -358,7 +360,7 @@ def pop_euler_path(self, allow_multiple_connected_components=True):

# counting the number of vertices with odd degree
odd = [x for x in self.d if len(self.d[x])&1]
odd.append(list(self.d.keys())[0])
odd.append(next(six.iterkeys(self.d)))
if not allow_multiple_connected_components and len(odd) > 3:
return None
stack = [odd[0]]
Expand Down
22 changes: 10 additions & 12 deletions blmath/geometry/transform/rodrigues.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
import numpy as np
import chumpy as ch

try:
from opendr.geometry import Rodrigues # This lets us import blmath.geometry.normals.NormalizedNx3 etc. whether or not opendr is available. pylint: disable=unused-import
except ImportError:
class Rodrigues(ch.Ch):
dterms = 'rt'

def compute_r(self):
import cv2
return cv2.Rodrigues(self.rt.r)[0]
class Rodrigues(ch.Ch):
dterms = 'rt'

def compute_dr_wrt(self, wrt):
import cv2
if wrt is self.rt:
return cv2.Rodrigues(self.rt.r)[1].T
def compute_r(self):
import cv2
return cv2.Rodrigues(self.rt.r)[0]

def compute_dr_wrt(self, wrt):
import cv2
if wrt is self.rt:
return cv2.Rodrigues(self.rt.r)[1].T


def rodrigues(r, calculate_jacobian=False):
Expand Down
41 changes: 38 additions & 3 deletions blmath/numerics/linalg/cholmod.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ typedef long Int;
#define NATURAL_ORDER 0
#define LOWER_TRIANGULAR -1

#if PY_MAJOR_VERSION >= 3
#define PyInt_AsLong PyLong_AsLong
#endif

static PyObject * cholmod_lchol_c(PyObject *self, PyObject *args);

static PyMethodDef CholmodMethods[] = {
Expand Down Expand Up @@ -115,12 +119,43 @@ void sputil_config (cholmod_common *cm)
}
}

#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"cholmod", /* m_name */
"cholmod methods", /* m_doc */
-1, /* m_size */
CholmodMethods, /* m_methods */
NULL, /* m_reload */
NULL, /* m_traverse */
NULL, /* m_clear */
NULL, /* m_free */
};
#endif

PyMODINIT_FUNC
initcholmod(void)
#if PY_MAJOR_VERSION >= 3
PyMODINIT_FUNC PyInit_cholmod(void)
#else
PyMODINIT_FUNC initcholmod(void)
#endif
{
(void) Py_InitModule("cholmod", CholmodMethods);
PyObject *m;

#if PY_MAJOR_VERSION >= 3
m = PyModule_Create(&moduledef);
if (m == NULL)
return NULL;
#else
m = Py_InitModule("cholmod", CholmodMethods);
if (m == NULL)
return;
#endif

import_array();

#if PY_MAJOR_VERSION >= 3
return m;
#endif
}


Expand Down
Binary file added blmath/numerics/linalg/cholmod.pkl
Binary file not shown.
103 changes: 53 additions & 50 deletions blmath/numerics/linalg/test_cholmod.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,58 @@
import os
import unittest
from baiji.serialization import pickle
import six
from six.moves import cPickle as pickle
import numpy as np
import scipy.sparse as sp
from bltest import attr
from blmath.cache import vc
from blmath.numerics.linalg import lchol

# class TestCholmod(unittest.TestCase):

# def test_random_cholmod(self):
# n_rows = 100
# A0 = 10*sp.rand(n_rows, n_rows, density=0.01, format='csc')
# A = A0*A0.transpose() + sp.eye(n_rows, n_rows)

# [L, L_nonpsd, S] = lchol.lchol(A)

# self.assertTrue(sum((abs(S.T.dot(A.dot(S))-L.dot(L.T))).data) < 1e-5)
# self.assertEqual(L_nonpsd, 0)

# # def test_memory_leak(self):
# # n_rows = 3000
# # A0 = 10*sp.rand(n_rows, n_rows, density=0.001, format='csc')
# # A = A0*A0.transpose() + sp.eye(n_rows, n_rows)
# # # mem0 = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
# # for i in range(50):
# # [chol_L, L_nonpsd, chol_S] = lchol.lchol(A)
# # import gc
# # gc.collect()
# # # mem1 = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
# # #print(mem1 - mem0)
# # self.assertTrue(True)

# @attr('missing_assets')
# def test_cholmod(self):
# A, chol_L, _, cv = pickle.load(vc('/unittest/linalg/cholmod.pkl'))

# c_data = np.ones(len(cv))/len(cv)
# c_rows = cv.flatten()
# c_cols = (np.zeros(len(cv))).astype(np.int32)
# c = sp.csc_matrix((c_data, (c_rows, c_cols)), shape=(A.shape[0], 1))
# Ac = sp.hstack([A, c], format='csc')

# AAc = Ac.dot(Ac.T)

# [chol_L_comp, L_nonpsd, chol_S_comp] = lchol.lchol(AAc)

# right = chol_S_comp.T.dot(AAc.dot(chol_S_comp))
# left = chol_L_comp.dot(chol_L_comp.T)

# self.assertTrue(sum((abs(right-left)).data)) # it's a reordered LLt decomposition
# self.assertEqual(sp.triu(chol_L, k=1).nnz, 0) # it's lower triangular'
# self.assertEqual(L_nonpsd, 0) # the input is positive definite
# # self.assertTrue(sum((abs(chol_L - chol_L_comp)).data) < 1e-1)
# # self.assertTrue(sum((abs(chol_S - chol_S_comp)).data) < 1e-1)
class TestCholmod(unittest.TestCase):

def test_random_cholmod(self):
n_rows = 100
A0 = 10*sp.rand(n_rows, n_rows, density=0.01, format='csc')
A = A0*A0.transpose() + sp.eye(n_rows, n_rows)

[L, L_nonpsd, S] = lchol.lchol(A)

self.assertTrue(sum((abs(S.T.dot(A.dot(S))-L.dot(L.T))).data) < 1e-5)
self.assertEqual(L_nonpsd, 0)

# def test_memory_leak(self):
# n_rows = 3000
# A0 = 10*sp.rand(n_rows, n_rows, density=0.001, format='csc')
# A = A0*A0.transpose() + sp.eye(n_rows, n_rows)
# # mem0 = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
# for i in range(50):
# [chol_L, L_nonpsd, chol_S] = lchol.lchol(A)
# import gc
# gc.collect()
# # mem1 = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
# #print(mem1 - mem0)
# self.assertTrue(True)

def test_cholmod(self):
test_pkl_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'cholmod.pkl')
pickle_kwargs = {}
if six.PY3:
pickle_kwargs['encoding'] = 'latin1'
A, chol_L, _, cv = pickle.load(open(test_pkl_path, 'rb'), **pickle_kwargs)

c_data = np.ones(len(cv))/len(cv)
c_rows = cv.flatten()
c_cols = (np.zeros(len(cv))).astype(np.int32)
c = sp.csc_matrix((c_data, (c_rows, c_cols)), shape=(A.shape[0], 1))
Ac = sp.hstack([A, c], format='csc')

AAc = Ac.dot(Ac.T)

[chol_L_comp, L_nonpsd, chol_S_comp] = lchol.lchol(AAc)

right = chol_S_comp.T.dot(AAc.dot(chol_S_comp))
left = chol_L_comp.dot(chol_L_comp.T)

self.assertTrue(sum((abs(right-left)).data)) # it's a reordered LLt decomposition
self.assertEqual(sp.triu(chol_L, k=1).nnz, 0) # it's lower triangular'
self.assertEqual(L_nonpsd, 0) # the input is positive definite
# self.assertTrue(sum((abs(chol_L - chol_L_comp)).data) < 1e-1)
# self.assertTrue(sum((abs(chol_S - chol_S_comp)).data) < 1e-1)
7 changes: 6 additions & 1 deletion blmath/numerics/test_coercion.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import unittest
import warnings
import numpy as np

class TestCoercion(unittest.TestCase):
Expand Down Expand Up @@ -42,7 +43,11 @@ def test_as_numeric_array_three_by_one(self):
as_numeric_array(v, shape=(3,), allow_none=False)

for v in good_iff_allow_none_and_empty_as_none:
as_numeric_array(v, shape=(3,), allow_none=True, empty_as_none=True)
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
as_numeric_array(v, shape=(3,), allow_none=True, empty_as_none=True)
assert len(w) == 1
assert issubclass(w[-1].category, DeprecationWarning)
with self.assertRaises(ValueError):
as_numeric_array(v, shape=(3,), allow_none=False, empty_as_none=True)

Expand Down
8 changes: 4 additions & 4 deletions blmath/numerics/test_vector_shortcuts.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,14 +191,14 @@ def test_pad_with_ones(self):

def test_pad_with_wrong_dimensions(self):
# NB: on windows, the sizes here will render as 3L, not 3:
with self.assertRaisesRegexp(ValueError, '^Invalid shape \(3L?, 4L?\): pad expects nx3$'): # FIXME pylint: disable=anomalous-backslash-in-string
with self.assertRaisesRegexp(ValueError, r'^Invalid shape \(3L?, 4L?\): pad expects nx3$'): # FIXME pylint: disable=anomalous-backslash-in-string
vx.pad_with_ones(np.array([
[1., 2., 3., 42.],
[2., 3., 4., 42.],
[5., 6., 7., 42.],
]))

with self.assertRaisesRegexp(ValueError, '^Invalid shape \(3L?,\): pad expects nx3$'): # FIXME pylint: disable=anomalous-backslash-in-string
with self.assertRaisesRegexp(ValueError, r'^Invalid shape \(3L?,\): pad expects nx3$'): # FIXME pylint: disable=anomalous-backslash-in-string
vx.pad_with_ones(np.array([1., 2., 3.]))

def test_unpad(self):
Expand All @@ -216,14 +216,14 @@ def test_unpad(self):
)

# NB: on windows, the sizes here will render as 3L, not 3:
with self.assertRaisesRegexp(ValueError, '^Invalid shape \(3L?, 3L?\): unpad expects nx4$'): # FIXME pylint: disable=anomalous-backslash-in-string
with self.assertRaisesRegexp(ValueError, r'^Invalid shape \(3L?, 3L?\): unpad expects nx4$'): # FIXME pylint: disable=anomalous-backslash-in-string
vx.unpad(np.array([
[1., 2., 3.],
[2., 3., 4.],
[5., 6., 7.],
]))

with self.assertRaisesRegexp(ValueError, '^Invalid shape \(4L?,\): unpad expects nx4$'): # FIXME pylint: disable=anomalous-backslash-in-string
with self.assertRaisesRegexp(ValueError, r'^Invalid shape \(4L?,\): unpad expects nx4$'): # FIXME pylint: disable=anomalous-backslash-in-string
vx.unpad(np.array([1., 2., 3., 4.]))

with self.assertRaisesRegexp(ValueError, '^Expected a column of ones$'):
Expand Down
Loading

0 comments on commit 03299a1

Please sign in to comment.