def get_limits_and_funcs(integrand_str, lower_str, upper_str, integration_var, varscope, funcscope): """Evals lower/upper limits and gets the functions used in lower/upper/integrand""" lower, lower_used = evaluator(lower_str, variables=varscope, functions=funcscope, suffixes={}, allow_inf=True) upper, upper_used = evaluator(upper_str, variables=varscope, functions=funcscope, suffixes={}, allow_inf=True) varscope[integration_var] = (upper + lower) / 2 _, integrand_used = evaluator(integrand_str, variables=varscope, functions=funcscope, suffixes={}, allow_inf=True) used_funcs = lower_used.functions_used.union( upper_used.functions_used, integrand_used.functions_used) return lower, upper, used_funcs
def test_calcpy(): """Tests of calc.py that aren't covered elsewhere""" # Test unhandled exception def badfunc(a): raise ValueError("Badness!") with raises( CalcError, match= r"There was an error evaluating f\(...\). Its input does not seem to be in its domain." ): evaluator("1+f(2)", {}, {"f": badfunc}, {}) # Test formula with None result = evaluator(None, {}, {}, {}) assert result[0] == approx(float('nan'), nan_ok=True) assert result[1] == set() # Test formulae with parallel operator result = evaluator("1 || 1 || 1", {}, {}, {}) assert result[0] == 1 / 3 assert result[1] == set() result = evaluator("1 || 1 || 0", {}, {}, {}) assert result[0] == approx(float('nan'), nan_ok=True) assert result[1] == set()
def raw_check(self, answer, student_input): """Perform the numerical check of student_input vs answer""" var_samples = gen_symbols_samples(self.config['variables'], self.config['samples'], self.config['sample_from']) func_samples = gen_symbols_samples(self.random_funcs.keys(), self.config['samples'], self.random_funcs) # Make a copy of the functions and variables lists # We'll add the sampled functions/variables in funclist = self.functions.copy() varlist = self.constants.copy() num_failures = 0 for i in range(self.config['samples']): # Update the functions and variables listings with this sample funclist.update(func_samples[i]) varlist.update(var_samples[i]) # Compute expressions expected, _ = evaluator(formula=answer['expect'], case_sensitive=self.config['case_sensitive'], variables=varlist, functions=funclist, suffixes=self.suffixes) student, used_funcs = evaluator(student_input, case_sensitive=self.config['case_sensitive'], variables=varlist, functions=funclist, suffixes=self.suffixes) # Check that the required functions are used # But only the first time! if i == 0: for f in self.config["required_functions"]: ftest = f if not self.config['case_sensitive']: ftest = f.lower() used_funcs = [x.lower() for x in used_funcs] if ftest not in used_funcs: msg = "Invalid Input: Answer must contain the function {}" raise InvalidInput(msg.format(f)) # Check if expressions agree if not within_tolerance(expected, student, self.config['tolerance']): num_failures += 1 if num_failures > self.config["failable_evals"]: return {'ok': False, 'grade_decimal': 0, 'msg': ''} # This response appears to agree with the expected answer return { 'ok': answer['ok'], 'grade_decimal': answer['grade_decimal'], 'msg': answer['msg'] }
def scoped_eval(expression, variables=varlist, functions=funclist, suffixes=self.suffixes, max_array_dim=self.config['max_array_dim']): return evaluator(expression, variables, functions, suffixes, max_array_dim)
def raw_integrand(x): varscope[integration_var] = x value, _ = evaluator(integrand_str, variables=varscope, functions=funcscope, suffixes={}) return value
def raw_integrand(x): varscope[integration_var] = x value, _ = evaluator(integrand_str, case_sensitive=self.config['case_sensitive'], variables=varscope, functions=funcscope, suffixes={}) return value
def test_varnames(): """Test variable names in calc.py""" # Tensor variable names assert evaluator("U^{ijk}", {"U^{ijk}": 2}, {}, {})[0] == 2 assert evaluator("U_{ijk}/2", {"U_{ijk}": 2}, {}, {})[0] == 1 assert evaluator("U_{ijk}^{123}", {"U_{ijk}^{123}": 2}, {}, {})[0] == 2 assert evaluator("U_{ijk}^{123}'''''", {"U_{ijk}^{123}'''''": 2}, {}, {})[0] == 2 assert evaluator("U_{ijk}^2", {"U_{ijk}": 2}, {}, {})[0] == 4 assert evaluator("U^{ijk}^2", {"U^{ijk}": 2}, {}, {})[0] == 4 assert evaluator("U_{ijk}^{123}^2", {"U_{ijk}^{123}": 2}, {}, {})[0] == 4 # Regular variable names assert evaluator("U_cat/2 + Th3_dog__7a_", { "U_cat": 2, "Th3_dog__7a_": 4 }, {}, {})[0] == 5 # tensor subscripts need braces with raises(UnableToParse): assert evaluator("U_123^{ijk}", {}, {}, {}) with raises(UnableToParse): assert evaluator("T_1_{123}^{ijk}", {}, {}, {})
def eval_summand(x): """ Helper function to evaluate the summand at the given value of the summation variable. """ varscope[summation_var] = x value, _ = evaluator(summand_str, variables=varscope, functions=funcscope, suffixes=self.suffixes) del varscope[summation_var] return value
def get_limits_and_funcs(self, expression, lower_str, upper_str, varscope, funcscope): """ Evals lower/upper limits and gets the functions used in limits and integrand/summand. """ lower, lower_used = evaluator(lower_str, variables=varscope, functions=funcscope, suffixes=self.suffixes, allow_inf=True) upper, upper_used = evaluator(upper_str, variables=varscope, functions=funcscope, suffixes=self.suffixes, allow_inf=True) expression_used = parse(expression) used_funcs = lower_used.functions_used.union( upper_used.functions_used, expression_used.functions_used) return lower, upper, used_funcs
def compute_sample(self, sample_dict, functions, suffixes): """Compute the value of this sample""" try: result, _ = evaluator(formula=self.config['formula'], variables=sample_dict, functions=functions, suffixes=suffixes) except CalcError: raise ConfigError("Formula error in dependent sampling formula: " + self.config["formula"]) return result
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
def evaluate_int(self, integrand_str, lower_str, upper_str, integration_var, varscope=None, funcscope=None): varscope = {} if varscope is None else varscope funcscope = {} if funcscope is None else funcscope lower, _ = evaluator(lower_str, case_sensitive=self.config['case_sensitive'], variables=varscope, functions=funcscope, suffixes={}) upper, _ = evaluator(upper_str, case_sensitive=self.config['case_sensitive'], variables=varscope, functions=funcscope, suffixes={}) if isinstance(lower, complex) or isinstance(upper, complex): raise IntegrationError( 'Integration limits must be real but have evaluated to complex numbers.' ) # It is possible that the integration variable might appear in the limits. # Some consider this bad practice, but many students do it and Mathematica allows it. # We're going to edit the varscope below to contain the integration variable. # Let's store the integration variable's initial value in case it has one. int_var_initial = varscope[ integration_var] if integration_var in varscope else None def raw_integrand(x): varscope[integration_var] = x value, _ = evaluator(integrand_str, case_sensitive=self.config['case_sensitive'], variables=varscope, functions=funcscope, suffixes={}) return value if self.config['complex_integrand']: integrand_re = lambda x: real(raw_integrand(x)) integrand_im = lambda x: imag(raw_integrand(x)) result_re = integrate.quad(integrand_re, lower, upper, **self.config['integrator_options']) result_im = integrate.quad(integrand_im, lower, upper, **self.config['integrator_options']) else: errmsg = "Integrand has evaluated to complex number but must evaluate to a real." integrand = check_output_is_real(raw_integrand, IntegrationError, errmsg) result_re = integrate.quad(integrand, lower, upper, **self.config['integrator_options']) result_im = (None, None, {'neval': None}) # Restore the integration variable's initial value now that we are done integrating if int_var_initial is not None: varscope[integration_var] = int_var_initial return result_re, result_im
def cos_summand(n): value, _ = evaluator('(-1)^(n/2)*x^n/fact(n)', {'n': n, 'x': x}) return value
def sin_summand(n): value, _ = evaluator('(-1)^((n-1)/2)*x^n/fact(n)', {'n': n, 'x': x}) return value
def exp_summand(n): value, _ = evaluator('x^n/fact(n)', {'n': n, 'x': x}) return value