Skip to content

Commit

Permalink
Implementing tests for matrix sampling
Browse files Browse the repository at this point in the history
  • Loading branch information
jolyonb committed Jul 4, 2019
1 parent 295d4c8 commit 34b52c0
Show file tree
Hide file tree
Showing 2 changed files with 331 additions and 39 deletions.
155 changes: 116 additions & 39 deletions mitxgraders/matrixsampling.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@
matrixsampling.py
Contains classes for sampling vector/matrix/tensor values:
* ArraySamplingSet
* RealVectors
* ComplexVectors
* RealMatrices
* ComplexMatrices
* RealTensors
* ComplexTensors
* IdentityMatrixMultiples
* SquareMatrices
* OrthogonalMatrices
Expand All @@ -17,13 +18,14 @@
import abc
import numpy as np

class Unavailable(object):
def rvs(self, dimension):
raise NotImplementedError('This feature requires newer versions of numpy '
'and scipy than are available.')

try:
from scipy.stats import ortho_group, special_ortho_group, unitary_group
except ImportError:
class Unavailable(object):
def rvs(self, dimension):
raise NotImplementedError('This feature requires newer versions of numpy '
'and scipy than are available.')
ortho_group = Unavailable()
special_ortho_group = Unavailable()
unitary_group = Unavailable()
Expand All @@ -37,11 +39,12 @@ def rvs(self, dimension):

# Set the objects to be imported from this grader
__all__ = [
"ArraySamplingSet",
"RealVectors",
"ComplexVectors",
"RealMatrices",
"ComplexMatrices",
"RealTensors",
"ComplexTensors",
"IdentityMatrixMultiples",
"SquareMatrices",
"OrthogonalMatrices",
Expand All @@ -54,15 +57,15 @@ class Retry(Exception):
constraints, and a new random draw should be taken.
"""


class ArraySamplingSet(VariableSamplingSet):
"""
Represents a set from which random array variable samples are taken.
The norm used is standard Euclidean norm: root-sum of all entries in the array.
This is the most low-level array sampling set we have, and is subclassed for various
specific purposes (especially vectors and matrices). As there is presently no subclass
for tensors, this class is not abstract.
specific purposes.
Config:
=======
Expand All @@ -73,26 +76,10 @@ class ArraySamplingSet(VariableSamplingSet):
list [start, stop] or a dictionary {'start':start, 'stop':stop}.
(default [1, 5])
- complex (bool): Whether or not the matrix is complex (default False)
Usage
========
Sample tensors with shape [4, 2, 5]:
>>> real_tensors = ArraySamplingSet(shape=[4, 2, 5])
>>> sample = real_tensors.gen_sample()
>>> sample.shape
(4, 2, 5)
Samples are of class MathArray:
>>> isinstance(sample, MathArray)
True
Specify a range for the tensor's norm:
>>> real_tensors = ArraySamplingSet(shape=[4, 2, 5], norm=[10, 20])
>>> sample = real_tensors.gen_sample()
>>> 10 < np.linalg.norm(sample) < 20
True
"""
# This is an abstract base class
__metaclass__ = abc.ABCMeta

schema_config = Schema({
Required('shape'): is_shape_specification(min_dim=1),
Required('norm', default=[1, 5]): NumberRange(),
Expand All @@ -108,12 +95,24 @@ def __init__(self, config=None, **kwargs):

def gen_sample(self):
"""
Generates a random matrix of shape and norm determined by config. After
Generates an array sample and returns it as a MathArray.
This calls generate_sample, which is the routine that should be subclassed if
needed, rather than this one.
"""
array = self.generate_sample()
return MathArray(array)

def generate_sample(self):
"""
Generates a random array of shape and norm determined by config. After
generation, the apply_symmetry and normalize functions are applied to the result.
These functions may be shadowed by a subclass.
If apply_symmetry or normalize raise the Retry exception, a new sample is
generated, and the procedure starts anew.
Returns a numpy array.
"""
# Loop until a good sample is found
loops = 0
Expand All @@ -134,8 +133,8 @@ def gen_sample(self):
# Normalize the result
array = self.normalize(array)

# Convert the array to a MathArray and return it
return MathArray(array)
# Return the result
return array
except Retry:
continue

Expand Down Expand Up @@ -226,6 +225,84 @@ class ComplexVectors(VectorSamplingSet):
})


class TensorSamplingSet(ArraySamplingSet):
"""
Sampling set of tensors. This is an abstract class; you should use RealTensors or
ComplexTensors instead.
Config:
=======
Same as ArraySamplingSet, but:
- shape must be a tuple with at least 3 dimensions
"""
# This is an abstract base class
__metaclass__ = abc.ABCMeta

schema_config = ArraySamplingSet.schema_config.extend({
Required('shape'): is_shape_specification(min_dim=3)
})


class RealTensors(TensorSamplingSet):
"""
Sampling set of real tensors.
Config:
=======
Same as TensorSamplingSet, but:
- complex is always False
Usage:
======
Sample tensors with shape [4, 2, 5]:
>>> real_tensors = RealTensors(shape=[4, 2, 5])
>>> sample = real_tensors.gen_sample()
>>> sample.shape
(4, 2, 5)
Samples are of class MathArray:
>>> isinstance(sample, MathArray)
True
Specify a range for the tensor's norm:
>>> real_tensors = RealTensors(shape=[4, 2, 5], norm=[10, 20])
>>> sample = real_tensors.gen_sample()
>>> 10 < np.linalg.norm(sample) < 20
True
"""
schema_config = TensorSamplingSet.schema_config.extend({
Required('complex', default=False): False
})


class ComplexTensors(TensorSamplingSet):
"""
Sampling set of complex tensors.
Config:
=======
Same as TensorSamplingSet, but:
- complex is always True
Usage:
======
Sample tensors with shape [4, 2, 5]:
>>> tensors = ComplexTensors(shape=[4, 2, 5])
>>> t = tensors.gen_sample()
>>> t.shape
(4, 2, 5)
Complex tensors have complex components:
>>> np.array_equal(t, np.conj(t))
False
"""
schema_config = TensorSamplingSet.schema_config.extend({
Required('complex', default=True): True
})


class MatrixSamplingSet(ArraySamplingSet):
"""
Base sampling set of matrices. This is an abstract base class; you should
Expand Down Expand Up @@ -409,16 +486,16 @@ class IdentityMatrixMultiples(SquareMatrixSamplingSet):
All(list, Coerce(RealInterval)))
})

def gen_sample(self):
def generate_sample(self):
"""
Generates an identity matrix of specified dimension multiplied by a random scalar
"""
# Sample the multiplicative constant
scaling = self.config['sampler'].gen_sample()
# Create the numpy matrix
array = scaling * np.eye(self.config['dimension'])
# Return the result as a MathArray
return MathArray(array)
# Return the result
return array


class SquareMatrices(SquareMatrixSamplingSet):
Expand Down Expand Up @@ -723,7 +800,7 @@ class OrthogonalMatrices(SquareMatrixSamplingSet):
Required('unitdet', default=True): bool
})

def gen_sample(self):
def generate_sample(self):
"""
Generates an orthogonal matrix
"""
Expand All @@ -732,8 +809,8 @@ def gen_sample(self):
array = special_ortho_group.rvs(self.config['dimension'])
else:
array = ortho_group.rvs(self.config['dimension'])
# Return the result as a MathArray
return MathArray(array)
# Return the result
return array


class UnitaryMatrices(SquareMatrixSamplingSet):
Expand Down Expand Up @@ -791,7 +868,7 @@ class UnitaryMatrices(SquareMatrixSamplingSet):
Required('unitdet', default=True): bool
})

def gen_sample(self):
def generate_sample(self):
"""
Generates an orthogonal matrix as appropriate
"""
Expand All @@ -801,5 +878,5 @@ def gen_sample(self):
if self.config['unitdet']:
det = np.linalg.det(array)
array /= det**(1/self.config['dimension'])
# Return the result as a MathArray
return MathArray(array)
# Return the result
return array
Loading

0 comments on commit 34b52c0

Please sign in to comment.