Skip to content

Commit

Permalink
Actually allowing arbitrary sequence in from_nodes().
Browse files Browse the repository at this point in the history
Towards #146.

Before, these factory constructor methods **assumed** `nodes` had
a shape, which was not required by the documentation.
  • Loading branch information
dhermes committed Oct 23, 2019
1 parent 9885063 commit f5c7869
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 9 deletions.
29 changes: 24 additions & 5 deletions src/python/bezier/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,7 @@ class Base:
_degree = -1

def __init__(self, nodes, _copy=True):
nodes_np = np.asarray(nodes, order="F")
if nodes_np.ndim != 2:
raise ValueError("Nodes must be 2-dimensional, not", nodes_np.ndim)

nodes_np = _lossless_to_float(nodes_np)
nodes_np = sequence_to_array(nodes)
dimension, _ = nodes_np.shape
self._dimension = dimension
if _copy:
Expand Down Expand Up @@ -102,3 +98,26 @@ def _lossless_to_float(array):
raise ValueError("Array cannot be converted to floating point")

return converted


def sequence_to_array(nodes):
"""Convert a sequence to a Fortran-ordered ``np.float64`` NumPy array.
Args:
nodes (Sequence[Sequence[numbers.Number]]): The control points for a
shape. Must be convertible to a 2D NumPy array of floating point
values, where the columns are the nodes and the rows correspond to
each dimension the shape occurs in.
Returns:
numpy.ndarray: The converted array (or the original if already a
float array).
Raises:
ValueError: If the ``nodes`` are not 2D.
"""
nodes_np = np.asarray(nodes, order="F")
if nodes_np.ndim != 2:
raise ValueError("Nodes must be 2-dimensional, not", nodes_np.ndim)

return _lossless_to_float(nodes_np)
5 changes: 3 additions & 2 deletions src/python/bezier/curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,10 @@ def from_nodes(cls, nodes, _copy=True):
Returns:
Curve: The constructed curve.
"""
_, num_nodes = nodes.shape
nodes_np = _base.sequence_to_array(nodes)
_, num_nodes = nodes_np.shape
degree = cls._get_degree(num_nodes)
return cls(nodes, degree, _copy=_copy)
return cls(nodes_np, degree, _copy=_copy)

@staticmethod
def _get_degree(num_nodes):
Expand Down
5 changes: 3 additions & 2 deletions src/python/bezier/surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -210,9 +210,10 @@ def from_nodes(cls, nodes, _copy=True):
Returns:
Surface: The constructed surface.
"""
_, num_nodes = nodes.shape
nodes_np = _base.sequence_to_array(nodes)
_, num_nodes = nodes_np.shape
degree = cls._get_degree(num_nodes)
return cls(nodes, degree, _copy=_copy)
return cls(nodes_np, degree, _copy=_copy)

@staticmethod
def _get_degree(num_nodes):
Expand Down
9 changes: 9 additions & 0 deletions tests/unit/test_curve.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ def test_from_nodes_factory(self):
self.assertEqual(curve._dimension, 3)
self.assertEqual(curve._nodes, nodes)

def test_from_nodes_factory_non_array(self):
nodes = [[1.0, 1.0, 2.0], [2.0, 3.0, 4.0]]
klass = self._get_target_class()
curve = klass.from_nodes(nodes)
self.assertIsInstance(curve, klass)
self.assertEqual(curve._degree, 2)
self.assertEqual(curve._dimension, 2)
self.assertTrue(np.all(curve._nodes == nodes))

def test__get_degree(self):
klass = self._get_target_class()
self.assertEqual(0, klass._get_degree(1))
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/test_surface.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,16 @@ def test_from_nodes_factory(self):
self.assertEqual(surface._nodes, nodes)
self.assertIsNone(surface._edges)

def test_from_nodes_factory_non_array(self):
nodes = [[0.0, 1.0, 2.0]]
klass = self._get_target_class()
surface = klass.from_nodes(nodes)
self.assertIsInstance(surface, klass)
self.assertEqual(surface._degree, 1)
self.assertEqual(surface._dimension, 1)
self.assertTrue(np.all(surface._nodes == nodes))
self.assertIsNone(surface._edges)

def test___repr__(self):
nodes = np.zeros((3, 15), order="F")
surface = self._make_one(nodes, 4)
Expand Down

0 comments on commit f5c7869

Please sign in to comment.