Пример #1
0
class ComplexSector(VariableSamplingSet):
    """
    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())
Пример #2
0
class ComplexRectangle(VariableSamplingSet):
    """
    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
Пример #3
0
 def schema_config(self):
     """Define the configuration options for StringGrader"""
     # Construct the default ItemGrader schema
     schema = super(StringGrader, self).schema_config
     # Append options
     return schema.extend({
         Required('strip', default=True): bool,
         Required('case_sensitive', default=True): bool
     })
Пример #4
0
 def schema_config(self):
     """
     Defines the default config schema for item graders.
     Classes that inherit from ItemGrader should extend this schema.
     """
     schema = super(ItemGrader, self).schema_config
     return schema.extend({
         Required('answers', default=tuple()): self.schema_answers,
         Required('wrong_msg', default=""): str
     })
Пример #5
0
 def schema_answer(self):
     """Defines the schema that a fully-specified answer should satisfy."""
     return Schema({
         Required('expect'):
         self.schema_expect,
         Required('grade_decimal', default=1):
         All(numbers.Number, Range(0, 1)),
         Required('msg', default=''):
         str,
         Required('ok', default='computed'):
         Any('computed', True, False, 'partial')
     })
def NumberRange(number_type=Number):
    """
    Schema that allows for a start and stop, or alternatively, a list [start, stop]
    The type of number can be restricted by specifying number_type=int, for example
    """
    return Schema(Any(
        {
            Required('start', default=1): number_type,
            Required('stop', default=5): number_type
        },
        number_range_alternate(number_type)
    ))
Пример #7
0
 def schema_config(self):
     """Define the configuration options for ListGrader"""
     # Construct the default AbstractGrader schema
     schema = super(ListGrader, self).schema_config
     # Append options
     return schema.extend({
         Required('ordered', default=False):
         bool,
         Required('subgraders'):
         Any(AbstractGrader, [AbstractGrader]),
         Required('grouping', default=[]): [Positive(int)],
         Required('answers', default=[]):
         Any(list, (list, ))  # Allow for a tuple of lists
     })
    def __init__(self, config=None, **kwargs):
        super(IntegralGrader, self).__init__(config, **kwargs)
        self.true_input_positions = self.validate_input_positions(
            self.config['input_positions'])

        # The below are copied from FormulaGrader.__init__

        # Set up the various lists we use
        self.functions, self.random_funcs = construct_functions(
            self.config["whitelist"], self.config["blacklist"],
            self.config["user_functions"])
        self.constants = construct_constants(self.config["user_constants"])
        # TODO I would like to move this into construct_constants at some point,
        # perhaps giving construct_constants and optional argument specifying additional defaults
        if 'infty' not in self.constants:
            self.constants['infty'] = float('inf')

        # Construct the schema for sample_from
        # First, accept all VariableSamplingSets
        # Then, accept any list that RealInterval can interpret
        # Finally, single numbers or tuples of numbers will be handled by DiscreteSet
        schema_sample_from = Schema({
            Required(varname, default=RealInterval()):
            Any(VariableSamplingSet, All(list,
                                         lambda pair: RealInterval(pair)),
                lambda tup: DiscreteSet(tup))
            for varname in self.config['variables']
        })
        self.config['sample_from'] = schema_sample_from(
            self.config['sample_from'])
Пример #9
0
    def __init__(self, config=None, **kwargs):
        """
        Validate the Formulagrader's configuration.
        First, we allow the ItemGrader initializer to construct the function list.
        We then construct the lists of functions, suffixes and constants.
        Finally, we refine the sample_from entry.
        """
        super(FormulaGrader, self).__init__(config, **kwargs)

        # Set up the various lists we use
        self.functions, self.random_funcs = construct_functions(self.config["whitelist"],
                                                                self.config["blacklist"],
                                                                self.config["user_functions"])
        self.constants = construct_constants(self.config["user_constants"])
        self.suffixes = construct_suffixes(self.config["metric_suffixes"])

        # Construct the schema for sample_from
        # First, accept all VariableSamplingSets
        # Then, accept any list that RealInterval can interpret
        # Finally, single numbers or tuples of numbers will be handled by DiscreteSet
        schema_sample_from = Schema({
            Required(varname, default=RealInterval()):
                Any(VariableSamplingSet,
                    All(list, lambda pair: RealInterval(pair)),
                    lambda tup: DiscreteSet(tup))
            for varname in self.config['variables']
        })
        self.config['sample_from'] = schema_sample_from(self.config['sample_from'])
Пример #10
0
 def schema_config(self):
     """
     Defines the default config schema for abstract graders.
     Classes that inherit from AbstractGrader should extend this schema.
     """
     return Schema({
         Required('debug', default=False):
         bool  # Use to turn on debug output
     })
Пример #11
0
class DependentSampler(VariableSamplingSet):
    """
    Represents a variable that depends on other variables.

    You must initialize with the list of variables this depends on as well as the formula
    for this variable. Note that only base formulas and suffixes are available for this
    computation.

    Usage
    =====
    Specify a single value
    >>> ds = DependentSampler(depends=['x', 'y', 'z'], formula="sqrt(x^2+y^2+z^2)")
    """

    # Take in an individual or tuple of numbers
    schema_config = Schema({
        Required('depends'): [str],
        Required('formula'): str,
        Required('case_sensitive', default=True): bool
    })

    def gen_sample(self):
        """Return a random entry from the given set"""
        raise Exception(
            "DependentSampler must be invoked with compute_sample.")

    def compute_sample(self, sample_dict):
        """Compute the value of this sample"""
        try:
            result, _ = evaluator(formula=self.config['formula'],
                                  case_sensitive=self.config['case_sensitive'],
                                  variables=sample_dict,
                                  functions=DEFAULT_FUNCTIONS,
                                  suffixes=DEFAULT_SUFFIXES)
        except CalcError:
            raise ConfigError("Formula error in dependent sampling formula: " +
                              self.config["formula"])

        return result
Пример #12
0
 def schema_config(self):
     """Define the configuration options for NumericalGrader"""
     # Construct the default FormulaGrader schema
     schema = super(NumericalGrader, self).schema_config
     # Modify the default FormulaGrader options
     return schema.extend({
         Required('user_functions', default={}): {Extra: is_callable},
         Required('tolerance', default='5%'): Any(PercentageString, NonNegative(Number)),
         Required('samples', default=1): 1,
         Required('variables', default=[]): [],
         Required('sample_from', default={}): {},
         Required('failable_evals', default=0): 0
     })
Пример #13
0
 def schema_config(self):
     """Define the configuration options for SingleListGrader"""
     # Construct the default ItemGrader schema
     schema = super(SingleListGrader, self).schema_config
     # Append options, replacing the ItemGrader 'answers' key
     return schema.extend({
         Required('ordered', default=False):
         bool,
         Required('length_error', default=False):
         bool,
         Required('delimiter', default=','):
         str,
         Required('partial_credit', default=True):
         bool,
         Required('subgrader'):
         ItemGrader,
         Required('answers', default=[]):
         Any(list, (list, ))  # Allow for a tuple of lists
     })
Пример #14
0
 def schema_config(self):
     """Define the configuration options for IntegralGrader"""
     # Construct the default AbstractGrader schema
     schema = super(IntegralGrader, self).schema_config
     default_input_positions = {
         'lower': 1,
         'upper': 2,
         'integrand': 3,
         'integration_variable': 4
     }
     # Append options
     return schema.extend({
         Required('answers'): {
             Required('lower'): str,
             Required('upper'): str,
             Required('integrand'): str,
             Required('integration_variable'): str
         },
         Required('input_positions', default=default_input_positions): {
             Required('lower', default=None):
             Any(None, Positive(int)),
             Required('upper', default=None):
             Any(None, Positive(int)),
             Required('integrand', default=None):
             Any(None, Positive(int)),
             Required('integration_variable', default=None):
             Any(None, Positive(int)),
         },
         Required('integrator_options', default={'full_output': 1}): {
             Required('full_output', default=1): 1,
             Extra: object
         },
         Required('complex_integrand', default=False):
         bool,
         # Most of the below are copied from FormulaGrader
         Required('user_functions', default={}): {
             Extra: Any(is_callable, [is_callable], FunctionSamplingSet)
         },
         Required('user_constants', default={}): {
             Extra: Number
         },
         Required('blacklist', default=[]): [str],
         Required('whitelist', default=[]): [Any(str, None)],
         Required('tolerance', default='0.01%'):
         Any(PercentageString, NonNegative(Number)),
         Required('case_sensitive', default=True):
         bool,
         Required('samples', default=1):
         Positive(int),  # default changed to 1
         Required('variables', default=[]): [str],
         Required('sample_from', default={}):
         dict,
         Required('failable_evals', default=0):
         NonNegative(int)
     })
Пример #15
0
 def schema_config(self):
     """Define the configuration options for FormulaGrader"""
     # Construct the default ItemGrader schema
     schema = super(FormulaGrader, self).schema_config
     # Append options
     forbidden_default = "Invalid Input: This particular answer is forbidden"
     return schema.extend({
         Required('user_functions', default={}):
             {Extra: Any(is_callable, [is_callable], FunctionSamplingSet)},
         Required('user_constants', default={}): {Extra: Number},
         Required('blacklist', default=[]): [str],
         Required('whitelist', default=[]): [Any(str, None)],
         Required('forbidden_strings', default=[]): [str],
         Required('forbidden_message', default=forbidden_default): str,
         Required('required_functions', default=[]): [str],
         Required('tolerance', default='0.01%'): Any(PercentageString, NonNegative(Number)),
         Required('case_sensitive', default=True): bool,
         Required('metric_suffixes', default=False): bool,
         Required('samples', default=5): Positive(int),
         Required('variables', default=[]): [str],
         Required('sample_from', default={}): dict,
         Required('failable_evals', default=0): NonNegative(int)
     })
Пример #16
0
class RandomFunction(FunctionSamplingSet):  # pylint: disable=too-few-public-methods
    """
    Generates a random well-behaved function on demand.

    Currently implemented as a sum of trigonometric functions with random amplitude,
    frequency and phase. You can control the center and amplitude of the resulting
    oscillations by specifying center and amplitude.

    Config:
        input_dim (int): Number of input arguments. 1 is a unary function (default 1)
        output_dim (int): Number of output dimensions. 1 = scalar, more than 1 is a vector
            (default 1)
        num_terms (int): Number of random sinusoid terms to add together (default 3)
        center (float): Center around which oscillations occur (default 0)
        amplitude (float): Maximum amplitude of the function (default 10)

    Usage
    =====
    Generate a random continous function
    >>> funcs = RandomFunction()

    By default, the generated functions are R-->R. You can specify the
    input and output dimensions:
    >>> funcs = RandomFunction(input_dim=3, output_dim=2)

    To control the range of the function, specify a center and amplitude. The bounds of
    the function will be center - amplitude < func(x) < center + amplitude.
    The following will give oscillations between 0 and 1.
    >>> funcs = RandomFunction(center=0.5, amplitude=0.5)
    """

    schema_config = Schema({
        Required('input_dim', default=1): Positive(int),
        Required('output_dim', default=1): Positive(int),
        Required('num_terms', default=3): Positive(int),
        Required('center', default=0): Number,
        Required('amplitude', default=10): Positive(Number)
    })

    def gen_sample(self):
        """
        Returns a randomly chosen 'nice' function.

        The output is a vector with output_dim dimensions:
        Y^i = sum_{jk} A^i_{jk} sin(B^i_{jk} X_k + C^i_{jk})

        i ranges from 1 to output_dim
        j ranges from 1 to num_terms
        k ranges from 1 to input_dim
        """
        # Generate arrays of random values for A, B and C
        output_dim = self.config['output_dim']
        input_dim = self.config['input_dim']
        num_terms = self.config['num_terms']
        # Amplitudes A range from 0.5 to 1
        A = np.random.rand(output_dim, num_terms, input_dim) / 2 + 0.5
        # Angular frequencies B range from -pi to pi
        B = 2 * np.pi * (np.random.rand(output_dim, num_terms, input_dim) -
                         0.5)
        # Phases C range from 0 to 2*pi
        C = 2 * np.pi * np.random.rand(output_dim, num_terms, input_dim)

        def f(*args):
            """Function that generates the random values"""
            # Check that the dimensions are correct
            if len(args) != input_dim:
                msg = "Expected {} arguments, but received {}".format(
                    input_dim, len(args))
                raise ConfigError(msg)

            # Turn the inputs into an array
            xvec = np.array(args)
            # Repeat it into the shape of A, B and C
            xarray = np.tile(xvec, (output_dim, num_terms, 1))
            # Compute the output matrix
            output = A * np.sin(B * xarray + C)
            # Sum over the j and k terms
            # We have an old version of numpy going here, so we can't use
            # fullsum = np.sum(output, axis=(1, 2))
            fullsum = np.sum(np.sum(output, axis=2), axis=1)

            # Scale and translate to fit within center and amplitude
            fullsum = fullsum * self.config["amplitude"] / self.config[
                "num_terms"]
            fullsum += self.config["center"]

            # Return the result
            return fullsum if output_dim > 1 else fullsum[0]

        return f