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

Create aliases for numpy properties in the absence of numpy #189

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
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
175 changes: 124 additions & 51 deletions properties/math.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@
from __future__ import print_function
from __future__ import unicode_literals

import numpy as np
try:
import numpy as np
except ImportError:
np = None
try:
import vectormath as vmath
except ImportError:
vmath = None

from six import integer_types, string_types
import vectormath as vmath

from .basic import Property, TOL
from .base import List, Union
from .basic import Bool, Float, Integer, Property, TOL

TYPE_MAPPINGS = {
int: 'i',
Expand All @@ -17,6 +25,59 @@
complex: 'c',
}

PROP_MAPPINGS = {
int: Integer,
float: Float,
bool: Bool,
}

VECTOR_DIRECTIONS = {
'ZERO': [0, 0, 0],
'X': [1, 0, 0],
'Y': [0, 1, 0],
'Z': [0, 0, 1],
'-X': [-1, 0, 0],
'-Y': [0, -1, 0],
'-Z': [0, 0, -1],
'EAST': [1, 0, 0],
'WEST': [-1, 0, 0],
'NORTH': [0, 1, 0],
'SOUTH': [0, -1, 0],
'UP': [0, 0, 1],
'DOWN': [0, 0, -1],
}


def _validate_shape(value):
if not isinstance(value, set):
try:
value = {value}
except TypeError:
# Valid shapes are hashable - we are just deferring errors
value = [value]
for val in value:
if not isinstance(val, tuple):
raise TypeError("{}: Invalid shape - must be a tuple "
"(e.g. ('*', 3) for an array of length-3 "
"arrays)".format(val))
for shp in val:
if shp != '*' and not isinstance(shp, integer_types):
raise TypeError("{}: Invalid shape - values "
"must be '*' or int".format(val))
return value


def _validate_dtype(value):
if not isinstance(value, (list, tuple)):
value = (value,)
if not value:
raise TypeError('No dtype specified - must be int, float, '
'and/or bool')
if any([val not in TYPE_MAPPINGS for val in value]):
raise TypeError('{}: Invalid dtype - must be int, float, '
'and/or bool'.format(value))
return value


class Array(Property):
"""Property for :class:`numpy arrays <numpy.ndarray>`
Expand Down Expand Up @@ -63,26 +124,7 @@ def shape(self, value):
if value is None:
self._shape = value
return
self._shape = self._validate_shape(value)

@staticmethod
def _validate_shape(value):
if not isinstance(value, set):
try:
value = {value}
except TypeError:
# Valid shapes are hashable - we are just deferring errors
value = [value]
for val in value:
if not isinstance(val, tuple):
raise TypeError("{}: Invalid shape - must be a tuple "
"(e.g. ('*', 3) for an array of length-3 "
"arrays)".format(val))
for shp in val:
if shp != '*' and not isinstance(shp, integer_types):
raise TypeError("{}: Invalid shape - values "
"must be '*' or int".format(val))
return value
self._shape = _validate_shape(value)

@property
def dtype(self):
Expand All @@ -94,15 +136,7 @@ def dtype(self):

@dtype.setter
def dtype(self, value):
if not isinstance(value, (list, tuple)):
value = (value,)
if len(value) == 0: #pylint: disable=len-as-condition
raise TypeError('No dtype specified - must be int, float, '
'and/or bool')
if any([val not in TYPE_MAPPINGS for val in value]):
raise TypeError('{}: Invalid dtype - must be int, float, '
'and/or bool'.format(value))
self._dtype = value
self._dtype = _validate_dtype(value)

@property
def info(self):
Expand All @@ -127,10 +161,10 @@ def validate(self, instance, value):
value = self.wrapper(value)
if not isinstance(value, np.ndarray):
raise NotImplementedError(
'Array validation is only implmented for wrappers that are '
'subclasses of numpy.ndarray'
'Array validation is only implemented for wrappers that are '
'subclasses of numpy.ndarray or list'
)
if value.dtype.kind not in (TYPE_MAPPINGS[typ] for typ in self.dtype):
if value.dtype.kind not in (TYPE_MAPPINGS[t] for t in self.dtype):
self.error(instance, value)
if self.shape is None:
return value
Expand All @@ -155,7 +189,6 @@ def equal(self, value_a, value_b):
except TypeError:
return False


def error(self, instance, value, error_class=None, extra=''):
"""Generates a ValueError on setting property to an invalid value"""
error_class = error_class if error_class is not None else ValueError
Expand Down Expand Up @@ -487,18 +520,58 @@ def from_json(value, **kwargs):
return vmath.Vector2Array(value)


VECTOR_DIRECTIONS = {
'ZERO': [0, 0, 0],
'X': [1, 0, 0],
'Y': [0, 1, 0],
'Z': [0, 0, 1],
'-X': [-1, 0, 0],
'-Y': [0, -1, 0],
'-Z': [0, 0, -1],
'EAST': [1, 0, 0],
'WEST': [-1, 0, 0],
'NORTH': [0, 1, 0],
'SOUTH': [0, -1, 0],
'UP': [0, 0, 1],
'DOWN': [0, 0, -1],
}
# The following are aliases for Array and Vector classes if library
# dependencies are not available. This is especially useful for using
# properties in lightweight environments without numpy.
if not np:

def Array(*args, **kwargs): #pylint: disable=invalid-name,function-redefined
"""If numpy not available, Array is replaced with equivalent List"""
shape = _validate_shape(kwargs.pop('shape', ('*',)))
dtype = _validate_dtype(kwargs.pop('dtype', (float, int)))
kwargs['coerce'] = True

def _get_list_prop(list_kw, ind=0):
if ind + 1 == len(shape):
list_kw['prop'] = Union(
doc='',
props=[PROP_MAPPINGS[t]('') for t in dtype],
)
else:
list_kw['prop'] = _get_list_prop(kwargs.copy(), ind+1)
if shape[ind] != '*':
list_kw['min_length'] = list_kw['max_length'] = shape[ind]
return List(*args, **list_kw)

return _get_list_prop(kwargs.copy())


if not vmath:

def Vector3(*args, **kwargs): #pylint: disable=invalid-name,function-redefined
"""If vmath not available, Vector3 is replaced with Array"""
kwargs.pop('length', None)
kwargs['shape'] = (3,)
kwargs['dtype'] = (float,)
return Array(*args, **kwargs)

def Vector2(*args, **kwargs): #pylint: disable=invalid-name,function-redefined
"""If vmath not available, Vector2 is replaced with Array"""
kwargs.pop('length', None)
kwargs['shape'] = (2,)
kwargs['dtype'] = (float,)
return Array(*args, **kwargs)

def Vector3Array(*args, **kwargs): #pylint: disable=invalid-name,function-redefined
"""If vmath not available, Vector3Array is replaced with Array"""
kwargs.pop('length', None)
kwargs['shape'] = ('*', 3)
kwargs['dtype'] = (float,)
return Array(*args, **kwargs)

def Vector2Array(*args, **kwargs): #pylint: disable=invalid-name,function-redefined
"""If vmath not available, Vector2Array is replaced with Array"""
kwargs.pop('length', None)
kwargs['shape'] = ('*', 2)
kwargs['dtype'] = (float,)
return Array(*args, **kwargs)