class ComplexSector(ScalarSamplingSet): """ Represents an annular sector in the complex plane from which to sample, based on a given range of modulus and argument. Config: modulus (list): Range for the modulus (default [1,3]) argument (list): Range for the argument (default [0,pi/2]) Usage ===== Sample from the unit circle >>> sect = ComplexSector(modulus=[0,1], argument=[-np.pi,np.pi]) """ schema_config = Schema({ Required('modulus', default=[1, 3]): NumberRange(), Required('argument', default=[0, np.pi / 2]): NumberRange() }) def __init__(self, config=None, **kwargs): """ Configure the class as normal, then set up the modulus and argument parts as RealInterval objects """ super(ComplexSector, self).__init__(config, **kwargs) self.modulus = RealInterval(self.config['modulus']) self.argument = RealInterval(self.config['argument']) def gen_sample(self): """Generates a random sample in the defined annular sector in the complex plane""" return self.modulus.gen_sample() * np.exp( 1j * self.argument.gen_sample())
class ComplexRectangle(ScalarSamplingSet): """ Represents a rectangle in the complex plane from which to sample. Config: re (list): Range for the real component (default [1,3]) im (list): Range for the imaginary component (default [1,3]) Usage ===== >>> rect = ComplexRectangle(re=[1,4], im=[-5,0]) """ schema_config = Schema({ Required('re', default=[1, 3]): NumberRange(), Required('im', default=[1, 3]): NumberRange() }) def __init__(self, config=None, **kwargs): """ Configure the class as normal, then set up the real and imaginary parts as RealInterval objects """ super(ComplexRectangle, self).__init__(config, **kwargs) self.re = RealInterval(self.config['re']) self.im = RealInterval(self.config['im']) def gen_sample(self): """Generates a random sample in the defined rectangle in the complex plane""" return self.re.gen_sample() + self.im.gen_sample() * 1j
class RealInterval(ScalarSamplingSet): """ Represents an interval of real numbers from which to sample. Config: start (float): Lower end of the range (default 1) stop (float): Upper end of the range (default 5) Usage ===== Generate random floats betweens -2 and 4 >>> ri = RealInterval(start=-2, stop=4) You can also initialize with an interval as a list: >>> ri = RealInterval([-2,4]) """ schema_config = NumberRange() def __init__(self, config=None, **kwargs): """ Validate the specified configuration. First apply the voluptuous validation. Then ensure that the start and stop are the right way around. """ super(RealInterval, self).__init__(config, **kwargs) if self.config['start'] > self.config['stop']: self.config['start'], self.config['stop'] = self.config[ 'stop'], self.config['start'] def gen_sample(self): """Returns a random real number in the range [start, stop]""" start, stop = self.config['start'], self.config['stop'] return start + (stop - start) * np.random.random_sample()
class IntegerRange(ScalarSamplingSet): """ Represents an interval of integers from which to sample. Config: start (int): Lower end of the range (default 1) stop (int): Upper end of the range (default 5) Both start and stop are included in the interval. Usage ===== Generate random integers betweens -2 and 4 >>> integer = IntegerRange(start=-2, stop=4) You can also initialize with an interval: >>> integer = IntegerRange([-2,4]) """ schema_config = NumberRange(int) def __init__(self, config=None, **kwargs): """ Validate the specified configuration. First apply the voluptuous validation. Then ensure that the start and stop are the right way around. """ super(IntegerRange, self).__init__(config, **kwargs) if self.config['start'] > self.config['stop']: self.config['start'], self.config['stop'] = self.config[ 'stop'], self.config['start'] def gen_sample(self): """Returns a random integer in range(start, stop)""" return np.random.randint(low=self.config['start'], high=self.config['stop'] + 1)
class RealMathArrays(VariableSamplingSet): """ Represents a collection of real arrays with specified norm from which to draw random samples. The norm used is standard Euclidean norm: root-sum of all entries in the array. Config: ======= shape (int|(int)|[int]): the array shape norm ([start, stop]): Real interval from which to sample the array's norm defaults to [1, 5] Usage ======== Sample tensors with shape [4, 2, 5]: >>> real_tensors = RealMathArrays(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 = RealMathArrays(shape=[4, 2, 5], norm=[10, 20]) >>> sample = real_tensors.gen_sample() >>> 10 < np.linalg.norm(sample) < 20 True """ schema_config = Schema({ Required('shape'): is_shape_specification(min_dim=1), Required('norm', default=[1, 5]): NumberRange() }) def __init__(self, config=None, **kwargs): """ Configure the class as normal, then set up norm as a RealInterval """ super(RealMathArrays, self).__init__(config, **kwargs) self.norm = RealInterval(self.config['norm']) def gen_sample(self): """ Generates a random matrix of shape and norm determined by config. """ desired_norm = self.norm.gen_sample() # construct an array with entries in [-0.5, 0.5) array = np.random.random_sample(self.config['shape']) - 0.5 actual_norm = np.linalg.norm(array) # convert the array to a matrix with desired norm return MathArray(array) * desired_norm / actual_norm
class ArraySamplingSet(VariableSamplingSet): """ Represents a set from which random array variable samples are taken. The norm used is standard Euclidean norm: root-square-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. While we cannot make this class abstract, we strongly discourage its use. Config: ======= - shape (int|(int)|[int]): Dimensions of the array, specified as a list or tuple of the dimensions in each index as (n_1, n_2, ...). Can also use an integer to select a vector of that length. (required; no default) - norm ([start, stop]): Range for the overall norm of the array. Can be a list [start, stop] or a dictionary {'start':start, 'stop':stop}. (default [1, 5]) - complex (bool): Whether or not the matrix is complex (default False) """ schema_config = Schema({ Required('shape'): is_shape_specification(min_dim=1), Required('norm', default=[1, 5]): NumberRange(), Required('complex', default=False): bool }) def __init__(self, config=None, **kwargs): """ Configure the class as normal, then set up norm as a RealInterval """ super(ArraySamplingSet, self).__init__(config, **kwargs) self.norm = RealInterval(self.config['norm']) def gen_sample(self): """ 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 while loops < 100: loops += 1 # Construct an array with entries in [-0.5, 0.5) array = np.random.random_sample(self.config['shape']) - 0.5 # Make the array complex if needed if self.config['complex']: imarray = np.random.random_sample(self.config['shape']) - 0.5 array = array + 1j*imarray try: # Apply any symmetries to the array array = self.apply_symmetry(array) # Normalize the result array = self.normalize(array) # Return the result return array except Retry: continue raise ValueError('Unable to construct sample for {}' .format(type(self).__name__)) # pragma: no cover def apply_symmetry(self, array): """ Applies the required symmetries to the array. This method exists to be shadowed by subclasses. """ return array def normalize(self, array): """ Normalizes the array to fall into the desired norm. This method can be shadowed by subclasses. """ actual_norm = np.linalg.norm(array) desired_norm = self.norm.gen_sample() return array * desired_norm / actual_norm