예제 #1
0
def is_formula_equal(expected, given, samples, cs=True, tolerance=0.01):
    try:
        variables = samples.split('@')[0].split(',')
        sranges = zip(*map(lambda x: map(float, x.split(",")),
                           samples.split('@')[1].split('#')[0].split(':')))
        ranges = dict(zip(variables, sranges))
    except Exception as err:
        raise Exception("is_formula_eq: failed to evaluate samples expression '%s', err=%s" % (samples, str(err)))
    try:
        numsamples = int(samples.split('@')[1].split('#')[1])
    except Exception as err:
        raise Exception("is_formula_eq: failed to evaluate samples expression '%s', bad specification of number of samples, err=%s" % (samples, str(err)))
    if not len(variables)==len(sranges):
        raise Exception("is_formula_eq: bad samples expression '%s', # variables = %s, but # ranges = %s" % (samples, len(variables), len(sranges)))

    for i in range(numsamples):
        vvariables = {}
        for var in ranges:
            value = random.uniform(*ranges[var])
            vvariables[str(var)] = value
        try:
            instructor_result = evaluator(vvariables, dict(), expected, case_sensitive=cs)
        except Exception as err:
            raise Exception("is_formula_eq: failed to evaluate expected instructor result, formula='%s', vvariables=%s, err=%s" % (expected, vvariables, str(err)))
        try:
            student_result = evaluator(vvariables, dict(), given, case_sensitive=cs)
        except Exception as err:
            raise Exception("is_formula_eq: failed to evaluate student result entry, formula='%s', vvariables=%s, err=%s" % (given, vvariables, str(err)))
        if abs(instructor_result-student_result) > tolerance:
            return False
    return True
예제 #2
0
    def test_complex_expression(self):
        """
        Calculate combinations of operators and default functions
        """

        self.assertAlmostEqual(
            calc.evaluator({}, {}, "(2^2+1.0)/sqrt(5e0)*5-1"),
            10.180,
            delta=1e-3
        )
        self.assertAlmostEqual(
            calc.evaluator({}, {}, "1+1/(1+1/(1+1/(1+1)))"),
            1.6,
            delta=1e-3
        )
        self.assertAlmostEqual(
            calc.evaluator({}, {}, "10||sin(7+5)"),
            -0.567, delta=0.01
        )
        self.assertAlmostEqual(
            calc.evaluator({}, {}, "sin(e)"),
            0.41, delta=0.01
        )
        self.assertAlmostEqual(
            calc.evaluator({}, {}, "e^(j*pi)"),
            -1, delta=1e-5
        )
예제 #3
0
 def test_parallel_resistors_with_zero(self):
     """
     Check the behavior of the || operator with 0
     """
     self.assertTrue(numpy.isnan(calc.evaluator({}, {}, '0||1')))
     self.assertTrue(numpy.isnan(calc.evaluator({}, {}, '0.0||1')))
     self.assertTrue(numpy.isnan(calc.evaluator({'x': 0.0}, {}, 'x||1')))
예제 #4
0
 def test_period(self):
     """
     The string '.' should not evaluate to anything.
     """
     with self.assertRaises(ParseException):
         calc.evaluator({}, {}, '.')
     with self.assertRaises(ParseException):
         calc.evaluator({}, {}, '1+.')
예제 #5
0
 def test_mismatched_parens(self):
     """
     Check to see if the evaluator catches mismatched parens
     """
     with self.assertRaisesRegexp(calc.UnmatchedParenthesis, 'opened but never closed'):
         calc.evaluator({}, {}, "(1+2")
     with self.assertRaisesRegexp(calc.UnmatchedParenthesis, 'no matching opening parenthesis'):
         calc.evaluator({}, {}, "(1+2))")
예제 #6
0
def compare_with_tolerance(student_complex, instructor_complex, tolerance=default_tolerance, relative_tolerance=False):
    """
    Compare student_complex to instructor_complex with maximum tolerance tolerance.

     - student_complex    :  student result (float complex number)
     - instructor_complex    :  instructor result (float complex number)
     - tolerance   :  float, or string (representing a float or a percentage)
     - relative_tolerance: bool, to explicitly use passed tolerance as relative

     Note: when a tolerance is a percentage (i.e. '10%'), it will compute that
     percentage of the instructor result and yield a number.

     If relative_tolerance is set to False, it will use that value and the
     instructor result to define the bounds of valid student result:
     instructor_complex = 10, tolerance = '10%' will give [9.0, 11.0].

     If relative_tolerance is set to True, it will use that value and both
     instructor result and student result to define the bounds of valid student
     result:
     instructor_complex = 10, student_complex = 20, tolerance = '10%' will give
     [8.0, 12.0].
     This is typically used internally to compare float, with a
     default_tolerance = '0.001%'.

     Default tolerance of 1e-3% is added to compare two floats for
     near-equality (to handle machine representation errors).
     Default tolerance is relative, as the acceptable difference between two
     floats depends on the magnitude of the floats.
     (http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/)
     Examples:
        In [183]: 0.000016 - 1.6*10**-5
        Out[183]: -3.3881317890172014e-21
        In [212]: 1.9e24 - 1.9*10**24
        Out[212]: 268435456.0
    """
    if isinstance(tolerance, str):
        if tolerance == default_tolerance:
            relative_tolerance = True
        if tolerance.endswith('%'):
            tolerance = evaluator(dict(), dict(), tolerance[:-1]) * 0.01
            if not relative_tolerance:
                tolerance = tolerance * abs(instructor_complex)
        else:
            tolerance = evaluator(dict(), dict(), tolerance)

    if relative_tolerance:
        tolerance = tolerance * max(abs(student_complex), abs(instructor_complex))

    if isinf(student_complex) or isinf(instructor_complex):
        # If an input is infinite, we can end up with `abs(student_complex-instructor_complex)` and
        # `tolerance` both equal to infinity. Then, below we would have
        # `inf <= inf` which is a fail. Instead, compare directly.
        return student_complex == instructor_complex
    else:
        # v1 and v2 are, in general, complex numbers:
        # there are some notes about backward compatibility issue: see responsetypes.get_staff_ans()).
        return abs(student_complex - instructor_complex) <= tolerance
예제 #7
0
 def test_function_case_sensitivity(self):
     """
     Test the case sensitivity of functions
     """
     functions = {'f': lambda x: x,
                  'F': lambda x: x + 1}
     # Test case insensitive evaluation
     # Both evaulations should call the same function
     self.assertEqual(calc.evaluator({}, functions, 'f(6)'),
                      calc.evaluator({}, functions, 'F(6)'))
     # Test case sensitive evaluation
     self.assertNotEqual(calc.evaluator({}, functions, 'f(6)', cs=True),
                         calc.evaluator({}, functions, 'F(6)', cs=True))
예제 #8
0
    def test_simple_funcs(self):
        """
        Subsitution of custom functions
        """
        variables = {'x': 4.712}
        functions = {'id': lambda x: x}
        self.assertEqual(calc.evaluator({}, functions, 'id(2.81)'), 2.81)
        self.assertEqual(calc.evaluator({}, functions, 'id(2.81)'), 2.81)
        self.assertEqual(calc.evaluator(variables, functions, 'id(x)'), 4.712)

        functions.update({'f': numpy.sin})
        self.assertAlmostEqual(calc.evaluator(variables, functions, 'f(x)'),
                               -1, delta=1e-3)
예제 #9
0
    def test_parallel_resistors(self):
        """
        Test the parallel resistor operator ||

        The formula is given by
            a || b || c ...
            = 1 / (1/a + 1/b + 1/c + ...)
        It is the resistance of a parallel circuit of resistors with resistance
        a, b, c, etc&. See if this evaulates correctly.
        """
        self.assertEqual(calc.evaluator({}, {}, '1||1'), 0.5)
        self.assertEqual(calc.evaluator({}, {}, '1||1||2'), 0.4)
        self.assertEqual(calc.evaluator({}, {}, "j||1"), 0.5 + 0.5j)
예제 #10
0
    def test_variable_case_sensitivity(self):
        """
        Test the case sensitivity flag and corresponding behavior
        """
        self.assertEqual(
            calc.evaluator({'R1': 2.0, 'R3': 4.0}, {}, "r1*r3"),
            8.0)

        variables = {'t': 1.0}
        self.assertEqual(calc.evaluator(variables, {}, "t"), 1.0)
        self.assertEqual(calc.evaluator(variables, {}, "T"), 1.0)
        self.assertEqual(calc.evaluator(variables, {}, "t", cs=True), 1.0)
        # Recall 'T' is a default constant, with value 298.15
        self.assertAlmostEqual(calc.evaluator(variables, {}, "T", cs=True),
                               298, delta=0.2)
예제 #11
0
    def test_constants(self):
        """
        Test the default constants provided in calc.py

        which are: j (complex number), e, pi, k, c, T, q
        """

        # Of the form ('expr', python value, tolerance (or None for exact))
        default_variables = [
            ('i', 1j, None),
            ('j', 1j, None),
            ('e', 2.7183, 1e-4),
            ('pi', 3.1416, 1e-4),
            ('k', 1.3806488e-23, 1e-26),  # Boltzmann constant (Joules/Kelvin)
            ('c', 2.998e8, 1e5),  # Light Speed in (m/s)
            ('T', 298.15, 0.01),  # 0 deg C = T Kelvin
            ('q', 1.602176565e-19, 1e-22)  # Fund. Charge (Coulombs)
        ]
        for (variable, value, tolerance) in default_variables:
            fail_msg = "Failed on constant '{0}', not within bounds".format(
                variable
            )
            result = calc.evaluator({}, {}, variable)
            if tolerance is None:
                self.assertEqual(value, result, msg=fail_msg)
            else:
                self.assertAlmostEqual(
                    value, result,
                    delta=tolerance, msg=fail_msg
                )
예제 #12
0
    def test_constants(self):
        """
        Test the default constants provided in calc.py

        which are: j (complex number), e, pi
        """

        # Of the form ('expr', python value, tolerance (or None for exact))
        default_variables = [
            ('i', 1j, None),
            ('j', 1j, None),
            ('e', 2.7183, 1e-4),
            ('pi', 3.1416, 1e-4),
        ]
        for (variable, value, tolerance) in default_variables:
            fail_msg = "Failed on constant '{0}', not within bounds".format(
                variable
            )
            result = calc.evaluator({}, {}, variable)
            if tolerance is None:
                self.assertEqual(value, result, msg=fail_msg)
            else:
                self.assertAlmostEqual(
                    value, result,
                    delta=tolerance, msg=fail_msg
                )
예제 #13
0
 def test_trailing_period(self):
     """
     Test that things like '4.' will be 4 and not throw an error
     """
     try:
         self.assertEqual(4.0, calc.evaluator({}, {}, '4.'))
     except ParseException:
         self.fail("'4.' is a valid input, but threw an exception")
 def _evaluate_row(self, index, row):
     # funcs_dict is used as a required positional argument in the formularesponse grader calc.evaluator. However, it is never used for anything.
     funcs_dict = {}
     if row['submission']==self.metadata['empty_encoding']:
         raise EmptySubmissionException
     else:
         for sample_index, vars_dict in enumerate(self.metadata['vars_dict_list']):
             self.data.loc[index,'eval.'+str(sample_index) ] = calc.evaluator(vars_dict, funcs_dict, row['submission'],case_sensitive=self.metadata['case_sensitive'])
     return
예제 #15
0
    def test_function_case_sensitive(self):
        """
        Test case sensitive evaluation

        Incorrectly capitilized should fail
        Also, it should pick the correct version of a function.
        """
        with self.assertRaisesRegexp(calc.UndefinedVariable, 'SiN'):
            calc.evaluator({}, {}, 'SiN(6)', case_sensitive=True)

        # With case sensitive turned on, it should pick the right function
        functions = {'f': lambda x: x, 'F': lambda x: x + 1}
        self.assertEqual(
            6, calc.evaluator({}, functions, 'f(6)', case_sensitive=True)
        )
        self.assertEqual(
            7, calc.evaluator({}, functions, 'F(6)', case_sensitive=True)
        )
예제 #16
0
def is_formula_equal(expected, given, samples, cs=True, tolerance=0.01):
    variables = samples.split('@')[0].split(',')
    numsamples = int(samples.split('@')[1].split('#')[1])
    sranges = zip(*map(lambda x: map(float, x.split(",")),
                       samples.split('@')[1].split('#')[0].split(':')))
    ranges = dict(zip(variables, sranges))
    for i in range(numsamples):
        vvariables = {}
        for var in ranges:
            value = random.uniform(*ranges[var])
            vvariables[str(var)] = value
        try:
            instructor_result = evaluator(vvariables, dict(), expected, case_sensitive=cs)
            student_result = evaluator(vvariables, dict(), given, case_sensitive=cs)
        except Exception as err:
            raise Exception("is_formula_eq: vvariables=%s, err=%s" % (vvariables, str(err)))
        if abs(instructor_result-student_result) > tolerance:
            return False
    return True
예제 #17
0
    def test_variable_case_sensitivity(self):
        """
        Test the case sensitivity flag and corresponding behavior
        """
        self.assertEqual(
            calc.evaluator({'R1': 2.0, 'R3': 4.0}, {}, "r1*r3"),
            8.0
        )

        variables = {'E': 1.0}
        self.assertEqual(
            calc.evaluator(variables, {}, "E", case_sensitive=True),
            1.0
        )
        # Recall 'e' is a default constant, with value 2.718
        self.assertAlmostEqual(
            calc.evaluator(variables, {}, "e", case_sensitive=True),
            2.718, delta=0.02
        )
예제 #18
0
def calculate(request):
    """ Calculator in footer of every page. """
    equation = request.GET["equation"]
    try:
        result = calc.evaluator({}, {}, equation)
    except:
        event = {"error": map(str, sys.exc_info()), "equation": equation}
        track.views.server_track(request, "error:calc", event, page="calc")
        return HttpResponse(json.dumps({"result": "Invalid syntax"}))
    return HttpResponse(json.dumps({"result": str(result)}))
예제 #19
0
def hint_mag(answer_ids, student_answers, new_cmap, old_cmap, anum=0, sign=False):
    global expected

    try:
        aid = answer_ids[0]
    except Exception as err:
        raise Exception(
            "cannot get answer_ids[%d], answer_ids=%s, new_cmap=%s, err=%s" % (anum, answer_ids, new_cmap, err)
        )

    ans = student_answers[aid]
    try:
        ans = float(ans)
    except Exception as err:
        try:
            ans = evaluator({}, {}, ans)
        except Exception as err:
            hint = '<font color="red">Cannot evaluate your answer</font>'
            new_cmap.set_hint_and_mode(aid, hint, "always")
            return

    try:
        if type(expected) == list:
            expect = expected[anum]
        else:
            expect = expected
    except Exception as err:
        raise Exception("expected answer not evaluated, expected=%s, anum=%s, err=%s" % (expected, anum, str(err)))

    # if expect is a dict, then generate hints by range in addition to
    extra_hints = []
    hint = ""
    if type(expect) == dict:
        expect_dict = expect
        expect = expect_dict["val"]
        extra_hints = expect_dict.get("extra_hints", [])

    if new_cmap.is_correct(aid):
        # if correct, make sure answer is close, else direct student to look at solution
        if not is_tight(ans, expect, 0.01):
            hint = '<font color="green">Your answer is accepted as correct, but more than 1% from the expected.  Please check the solutions, and use the expected answer in any further calculations.</font>'

    else:
        hint = is_close(ans, expect)
        if not hint and sign:
            hint = is_sign_correct(ans, expect)

        for eh in extra_hints:
            range = eh.get("range", "")
            if range:
                if in_range(ans, range):
                    hint += "  " + eh["hint"]

    if hint:
        new_cmap.set_hint_and_mode(aid, hint, "always")
예제 #20
0
def calculate(request):
    ''' Calculator in footer of every page. '''
    equation = request.GET['equation']
    try:
        result = calc.evaluator({}, {}, equation)
    except:
        event = {'error': map(str, sys.exc_info()),
                 'equation': equation}
        track.views.server_track(request, 'error:calc', event, page='calc')
        return HttpResponse(json.dumps({'result': 'Invalid syntax'}))
    return HttpResponse(json.dumps({'result': str(result)}))
예제 #21
0
    def test_function_case_insensitive(self):
        """
        Test case insensitive evaluation

        Normal functions with some capitals should be fine
        """
        self.assertAlmostEqual(-0.28,
                               calc.evaluator({}, {},
                                              'SiN(6)',
                                              case_sensitive=False),
                               delta=1e-3)
예제 #22
0
    def test_variable_case_sensitivity(self):
        """
        Test the case sensitivity flag and corresponding behavior
        """
        self.assertEqual(calc.evaluator({
            'R1': 2.0,
            'R3': 4.0
        }, {}, "r1*r3"), 8.0)

        variables = {'t': 1.0}
        self.assertEqual(calc.evaluator(variables, {}, "t"), 1.0)
        self.assertEqual(calc.evaluator(variables, {}, "T"), 1.0)
        self.assertEqual(
            calc.evaluator(variables, {}, "t", case_sensitive=True), 1.0)
        # Recall 'T' is a default constant, with value 298.15
        self.assertAlmostEqual(calc.evaluator(variables, {},
                                              "T",
                                              case_sensitive=True),
                               298,
                               delta=0.2)
예제 #23
0
    def test_exponential_answer(self):
        """
        Test for correct interpretation of scientific notation
        """
        answer = 50
        correct_responses = [
            "50", "50.0", "5e1", "5e+1", "50e0", "50.0e0", "500e-1"
        ]
        incorrect_responses = ["", "3.9", "4.1", "0", "5.01e1"]

        for input_str in correct_responses:
            result = calc.evaluator({}, {}, input_str)
            fail_msg = "Expected '{0}' to equal {1}".format(input_str, answer)
            self.assertEqual(answer, result, msg=fail_msg)

        for input_str in incorrect_responses:
            result = calc.evaluator({}, {}, input_str)
            fail_msg = "Expected '{0}' to not equal {1}".format(
                input_str, answer)
            self.assertNotEqual(answer, result, msg=fail_msg)
예제 #24
0
def calculate(request):
    ''' Calculator in footer of every page. '''
    equation = request.GET['equation']
    try:
        result = calc.evaluator({}, {}, equation)
    except:
        event = {'error': map(str, sys.exc_info()),
                 'equation': equation}
        track.views.server_track(request, 'error:calc', event, page='calc')
        return HttpResponse(json.dumps({'result': 'Invalid syntax'}))
    return HttpResponse(json.dumps({'result': str(result)}))
예제 #25
0
    def test_exponential_answer(self):
        """
        Test for correct interpretation of scientific notation
        """
        answer = 50
        correct_responses = ["50", "50.0", "5e1", "5e+1",
                             "50e0", "50.0e0", "500e-1"]
        incorrect_responses = ["", "3.9", "4.1", "0", "5.01e1"]

        for input_str in correct_responses:
            result = calc.evaluator({}, {}, input_str)
            fail_msg = "Expected '{0}' to equal {1}".format(
                input_str, answer)
            self.assertEqual(answer, result, msg=fail_msg)

        for input_str in incorrect_responses:
            result = calc.evaluator({}, {}, input_str)
            fail_msg = "Expected '{0}' to not equal {1}".format(
                input_str, answer)
            self.assertNotEqual(answer, result, msg=fail_msg)
예제 #26
0
    def test_function_case_insensitive(self):
        """
        Test case insensitive evaluation

        Normal functions with some capitals should be fine
        """
        self.assertAlmostEqual(
            -0.28,
            calc.evaluator({}, {}, 'SiN(6)', case_sensitive=False),
            delta=1e-3
        )
예제 #27
0
    def test_complex_expression(self):
        """
        Calculate combinations of operators and default functions
        """

        self.assertAlmostEqual(calc.evaluator({}, {},
                                              "(2^2+1.0)/sqrt(5e0)*5-1"),
                               10.180,
                               delta=1e-3)
        self.assertAlmostEqual(calc.evaluator({}, {}, "1+1/(1+1/(1+1/(1+1)))"),
                               1.6,
                               delta=1e-3)
        self.assertAlmostEqual(calc.evaluator({}, {}, "10||sin(7+5)"),
                               -0.567,
                               delta=0.01)
        self.assertAlmostEqual(calc.evaluator({}, {}, "sin(e)"),
                               0.41,
                               delta=0.01)
        self.assertAlmostEqual(calc.evaluator({}, {}, "e^(j*pi)"),
                               -1,
                               delta=1e-5)
예제 #28
0
    def test_explicit_sci_notation(self):
        """
        Expressions like 1.6*10^-3 (not 1.6e-3) it should evaluate.
        """
        self.assertEqual(
            calc.evaluator({}, {}, "-1.6*10^-3"),
            -0.0016
        )
        self.assertEqual(
            calc.evaluator({}, {}, "-1.6*10^(-3)"),
            -0.0016
        )

        self.assertEqual(
            calc.evaluator({}, {}, "-1.6*10^3"),
            -1600
        )
        self.assertEqual(
            calc.evaluator({}, {}, "-1.6*10^(3)"),
            -1600
        )
예제 #29
0
    def test_explicit_sci_notation(self):
        """
        Expressions like 1.6*10^-3 (not 1.6e-3) it should evaluate.
        """
        self.assertEqual(
            calc.evaluator({}, {}, "-1.6*10^-3"),
            -0.0016
        )
        self.assertEqual(
            calc.evaluator({}, {}, "-1.6*10^(-3)"),
            -0.0016
        )

        self.assertEqual(
            calc.evaluator({}, {}, "-1.6*10^3"),
            -1600
        )
        self.assertEqual(
            calc.evaluator({}, {}, "-1.6*10^(3)"),
            -1600
        )
def hint_mag(answer_ids, student_answers, new_cmap, old_cmap, anum=0, sign=False):
    global expected

    try:
        aid = answer_ids[0]
    except Exception as err:
        raise Exception('cannot get answer_ids[%d], answer_ids=%s, new_cmap=%s, err=%s' % (anum, answer_ids, new_cmap, err))

    ans = student_answers[aid]
    try:
        ans = float(ans)
    except Exception as err:
        try:
            ans = evaluator({},{}, ans)
        except Exception as err:
            hint = '<font color="red">Cannot evaluate your answer</font>'
            new_cmap.set_hint_and_mode(aid, hint, 'always')
            return

    try:
        if type(expected)==list:
            expect = expected[anum]
        else:
            expect = expected
    except Exception as err:
        raise Exception('expected answer not evaluated, expected=%s, anum=%s, err=%s' % (expected, anum, str(err)))

    # if expect is a dict, then generate hints by range in addition to 
    extra_hints = []
    hint = ''
    if type(expect)==dict:
        expect_dict = expect
        expect = expect_dict['val']
        extra_hints = expect_dict.get('extra_hints', [])

    if new_cmap.is_correct(aid):
        # if correct, make sure answer is close, else direct student to look at solution
        if not is_tight(ans, expect, 0.01):
            hint = '<font color="green">Your answer is accepted as correct, but more than 1% from the expected.  Please check the solutions, and use the expected answer in any further calculations.</font>'

    else:
        hint = is_close(ans, expect)
        if not hint and sign:
            hint = is_sign_correct(ans, expect)

        for eh in extra_hints:
            range = eh.get('range','')
            if range:
                if in_range(ans, range):
                    hint += '  ' + eh['hint']

    if hint:
        new_cmap.set_hint_and_mode(aid, hint, 'always')
예제 #31
0
    def test_undefined_vars(self):
        """
        Check to see if the evaluator catches undefined variables
        """
        variables = {'R1': 2.0, 'R3': 4.0}

        with self.assertRaisesRegexp(calc.UndefinedVariable, r'QWSEKO'):
            calc.evaluator({}, {}, "5+7*QWSEKO")
        with self.assertRaisesRegexp(calc.UndefinedVariable, r'r2'):
            calc.evaluator({'r1': 5}, {}, "r1+r2")
        with self.assertRaisesRegexp(calc.UndefinedVariable, r'r1, r3'):
            calc.evaluator(variables, {}, "r1*r3", case_sensitive=True)
        with self.assertRaisesRegexp(calc.UndefinedVariable, r'did you forget to use \*'):
            calc.evaluator(variables, {}, "R1(R3 + 1)")
예제 #32
0
def compare_with_tolerance(complex1, complex2, tolerance=default_tolerance, relative_tolerance=False):
    """
    Compare complex1 to complex2 with maximum tolerance tol.

    If tolerance is type string, then it is counted as relative if it ends in %; otherwise, it is absolute.

     - complex1    :  student result (float complex number)
     - complex2    :  instructor result (float complex number)
     - tolerance   :  string representing a number or float
     - relative_tolerance: bool, used when`tolerance` is float to explicitly use passed tolerance as relative.

     Default tolerance of 1e-3% is added to compare two floats for
     near-equality (to handle machine representation errors).
     Default tolerance is relative, as the acceptable difference between two
     floats depends on the magnitude of the floats.
     (http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/)
     Examples:
        In [183]: 0.000016 - 1.6*10**-5
        Out[183]: -3.3881317890172014e-21
        In [212]: 1.9e24 - 1.9*10**24
        Out[212]: 268435456.0
    """
    if relative_tolerance:
        tolerance = tolerance * max(abs(complex1), abs(complex2))
    elif tolerance.endswith('%'):
        tolerance = evaluator(dict(), dict(), tolerance[:-1]) * 0.01
        tolerance = tolerance * max(abs(complex1), abs(complex2))
    else:
        tolerance = evaluator(dict(), dict(), tolerance)

    if isinf(complex1) or isinf(complex2):
        # If an input is infinite, we can end up with `abs(complex1-complex2)` and
        # `tolerance` both equal to infinity. Then, below we would have
        # `inf <= inf` which is a fail. Instead, compare directly.
        return complex1 == complex2
    else:
        # v1 and v2 are, in general, complex numbers:
        # there are some notes about backward compatibility issue: see responsetypes.get_staff_ans()).
        return abs(complex1 - complex2) <= tolerance
예제 #33
0
    def test_undefined_vars(self):
        """
        Check to see if the evaluator catches undefined variables
        """
        variables = {'R1': 2.0, 'R3': 4.0}

        with self.assertRaisesRegexp(calc.UndefinedVariable, r'QWSEKO'):
            calc.evaluator({}, {}, "5+7*QWSEKO")
        with self.assertRaisesRegexp(calc.UndefinedVariable, r'r2'):
            calc.evaluator({'r1': 5}, {}, "r1+r2")
        with self.assertRaisesRegexp(calc.UndefinedVariable, r'r1, r3'):
            calc.evaluator(variables, {}, "r1*r3", case_sensitive=True)
        with self.assertRaisesRegexp(calc.UndefinedVariable,
                                     r'did you forget to use \*'):
            calc.evaluator(variables, {}, "R1(R3 + 1)")
예제 #34
0
    def test_operator_sanity(self):
        """
        Test for simple things like '5+2' and '5/2'
        """
        var1 = 5.0
        var2 = 2.0
        operators = [('+', 7), ('-', 3), ('*', 10), ('/', 2.5), ('^', 25)]

        for (operator, answer) in operators:
            input_str = "{0} {1} {2}".format(var1, operator, var2)
            result = calc.evaluator({}, {}, input_str)
            fail_msg = "Failed on operator '{0}': '{1}' was not {2}".format(
                operator, input_str, answer)
            self.assertEqual(answer, result, msg=fail_msg)
예제 #35
0
파일: util.py 프로젝트: samH99/LexisNexis
def compare_with_tolerance(v1, v2, tol):
    ''' Compare v1 to v2 with maximum tolerance tol
    tol is relative if it ends in %; otherwise, it is absolute

     - v1    :  student result (number)
     - v2    :  instructor result (number)
     - tol   :  tolerance (string representing a number)

    '''
    relative = tol.endswith('%')
    if relative:
        tolerance_rel = evaluator(dict(), dict(), tol[:-1]) * 0.01
        tolerance = tolerance_rel * max(abs(v1), abs(v2))
    else:
        tolerance = evaluator(dict(), dict(), tol)

    if isinf(v1) or isinf(v2):
        # If an input is infinite, we can end up with `abs(v1-v2)` and
        # `tolerance` both equal to infinity. Then, below we would have
        # `inf <= inf` which is a fail. Instead, compare directly.
        return v1 == v2
    else:
        return abs(v1 - v2) <= tolerance
예제 #36
0
    def test_operator_sanity(self):
        """
        Test for simple things like '5+2' and '5/2'
        """
        var1 = 5.0
        var2 = 2.0
        operators = [('+', 7), ('-', 3), ('*', 10), ('/', 2.5), ('^', 25)]

        for (operator, answer) in operators:
            input_str = "{0} {1} {2}".format(var1, operator, var2)
            result = calc.evaluator({}, {}, input_str)
            fail_msg = "Failed on operator '{0}': '{1}' was not {2}".format(
                operator, input_str, answer)
            self.assertEqual(answer, result, msg=fail_msg)
예제 #37
0
    def assert_function_values(self, fname, ins, outs, tolerance=1e-3):
        """
        Helper function to test many values at once

        Test the accuracy of evaluator's use of the function given by fname
        Specifically, the equality of `fname(ins[i])` against outs[i].
        This is used later to test a whole bunch of f(x) = y at a time
        """

        for (arg, val) in zip(ins, outs):
            input_str = "{0}({1})".format(fname, arg)
            result = calc.evaluator({}, {}, input_str)
            fail_msg = "Failed on function {0}: '{1}' was not {2}".format(
                fname, input_str, val)
            self.assertAlmostEqual(val, result, delta=tolerance, msg=fail_msg)
예제 #38
0
    def test_trig_functions(self):
        """
        Test the trig functions provided in calc.py

        which are: sin, cos, tan, arccos, arcsin, arctan
        """

        angles = ['-pi/4', '0', 'pi/6', 'pi/5', '5*pi/4', '9*pi/4', '1 + j']
        sin_values = [-0.707, 0, 0.5, 0.588, -0.707, 0.707, 1.298 + 0.635j]
        cos_values = [0.707, 1, 0.866, 0.809, -0.707, 0.707, 0.834 - 0.989j]
        tan_values = [-1, 0, 0.577, 0.727, 1, 1, 0.272 + 1.084j]
        # Cannot test tan(pi/2) b/c pi/2 is a float and not precise...

        self.assert_function_values('sin', angles, sin_values)
        self.assert_function_values('cos', angles, cos_values)
        self.assert_function_values('tan', angles, tan_values)

        # Include those where the real part is between -pi/2 and pi/2
        arcsin_inputs = ['-0.707', '0', '0.5', '0.588', '1.298 + 0.635*j']
        arcsin_angles = [-0.785, 0, 0.524, 0.629, 1 + 1j]
        self.assert_function_values('arcsin', arcsin_inputs, arcsin_angles)
        # Rather than a complex number, numpy.arcsin gives nan
        self.assertTrue(numpy.isnan(calc.evaluator({}, {}, 'arcsin(-1.1)')))
        self.assertTrue(numpy.isnan(calc.evaluator({}, {}, 'arcsin(1.1)')))

        # Include those where the real part is between 0 and pi
        arccos_inputs = ['1', '0.866', '0.809', '0.834-0.989*j']
        arccos_angles = [0, 0.524, 0.628, 1 + 1j]
        self.assert_function_values('arccos', arccos_inputs, arccos_angles)
        self.assertTrue(numpy.isnan(calc.evaluator({}, {}, 'arccos(-1.1)')))
        self.assertTrue(numpy.isnan(calc.evaluator({}, {}, 'arccos(1.1)')))

        # Has the same range as arcsin
        arctan_inputs = ['-1', '0', '0.577', '0.727', '0.272 + 1.084*j']
        arctan_angles = arcsin_angles
        self.assert_function_values('arctan', arctan_inputs, arctan_angles)
예제 #39
0
    def assert_function_values(self, fname, ins, outs, tolerance=1e-3):
        """
        Helper function to test many values at once

        Test the accuracy of evaluator's use of the function given by fname
        Specifically, the equality of `fname(ins[i])` against outs[i].
        This is used later to test a whole bunch of f(x) = y at a time
        """

        for (arg, val) in zip(ins, outs):
            input_str = "{0}({1})".format(fname, arg)
            result = calc.evaluator({}, {}, input_str)
            fail_msg = "Failed on function {0}: '{1}' was not {2}".format(
                fname, input_str, val)
            self.assertAlmostEqual(val, result, delta=tolerance, msg=fail_msg)
예제 #40
0
    def test_trig_functions(self):
        """
        Test the trig functions provided in calc.py

        which are: sin, cos, tan, arccos, arcsin, arctan
        """

        angles = ['-pi/4', '0', 'pi/6', 'pi/5', '5*pi/4', '9*pi/4', '1 + j']
        sin_values = [-0.707, 0, 0.5, 0.588, -0.707, 0.707, 1.298 + 0.635j]
        cos_values = [0.707, 1, 0.866, 0.809, -0.707, 0.707, 0.834 - 0.989j]
        tan_values = [-1, 0, 0.577, 0.727, 1, 1, 0.272 + 1.084j]
        # Cannot test tan(pi/2) b/c pi/2 is a float and not precise...

        self.assert_function_values('sin', angles, sin_values)
        self.assert_function_values('cos', angles, cos_values)
        self.assert_function_values('tan', angles, tan_values)

        # Include those where the real part is between -pi/2 and pi/2
        arcsin_inputs = ['-0.707', '0', '0.5', '0.588', '1.298 + 0.635*j']
        arcsin_angles = [-0.785, 0, 0.524, 0.629, 1 + 1j]
        self.assert_function_values('arcsin', arcsin_inputs, arcsin_angles)
        # Rather than a complex number, numpy.arcsin gives nan
        self.assertTrue(numpy.isnan(calc.evaluator({}, {}, 'arcsin(-1.1)')))
        self.assertTrue(numpy.isnan(calc.evaluator({}, {}, 'arcsin(1.1)')))

        # Include those where the real part is between 0 and pi
        arccos_inputs = ['1', '0.866', '0.809', '0.834-0.989*j']
        arccos_angles = [0, 0.524, 0.628, 1 + 1j]
        self.assert_function_values('arccos', arccos_inputs, arccos_angles)
        self.assertTrue(numpy.isnan(calc.evaluator({}, {}, 'arccos(-1.1)')))
        self.assertTrue(numpy.isnan(calc.evaluator({}, {}, 'arccos(1.1)')))

        # Has the same range as arcsin
        arctan_inputs = ['-1', '0', '0.577', '0.727', '0.272 + 1.084*j']
        arctan_angles = arcsin_angles
        self.assert_function_values('arctan', arctan_inputs, arctan_angles)
예제 #41
0
def compare_with_tolerance(v1, v2, tol=default_tolerance):
    '''
    Compare v1 to v2 with maximum tolerance tol.

    tol is relative if it ends in %; otherwise, it is absolute

     - v1    :  student result (float complex number)
     - v2    :  instructor result (float complex number)
     - tol   :  tolerance (string representing a number)

     Default tolerance of 1e-3% is added to compare two floats for near-equality
     (to handle machine representation errors).
     It is relative, as the acceptable difference between two floats depends on the magnitude of the floats.
     (http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/)
     Examples:
        In [183]: 0.000016 - 1.6*10**-5
        Out[183]: -3.3881317890172014e-21
        In [212]: 1.9e24 - 1.9*10**24
        Out[212]: 268435456.0
    '''
    relative = tol.endswith('%')
    if relative:
        tolerance_rel = evaluator(dict(), dict(), tol[:-1]) * 0.01
        tolerance = tolerance_rel * max(abs(v1), abs(v2))
    else:
        tolerance = evaluator(dict(), dict(), tol)

    if isinf(v1) or isinf(v2):
        # If an input is infinite, we can end up with `abs(v1-v2)` and
        # `tolerance` both equal to infinity. Then, below we would have
        # `inf <= inf` which is a fail. Instead, compare directly.
        return v1 == v2
    else:
        # v1 and v2 are, in general, complex numbers:
        # there are some notes about backward compatibility issue: see responsetypes.get_staff_ans()).
        return abs(v1 - v2) <= tolerance
예제 #42
0
 def test_raises_zero_division_err(self):
     """
     Ensure division by zero gives an error
     """
     with self.assertRaises(ZeroDivisionError):
         calc.evaluator({}, {}, '1/0')
     with self.assertRaises(ZeroDivisionError):
         calc.evaluator({}, {}, '1/0.0')
     with self.assertRaises(ZeroDivisionError):
         calc.evaluator({'x': 0.0}, {}, '1/x')
예제 #43
0
    def test_si_suffix(self):
        """
        Test calc.py's unique functionality of interpreting si 'suffixes'.

        For instance '%' stand for 1/100th so '1%' should be 0.01
        """
        test_mapping = [('4.2%', 0.042)]

        for (expr, answer) in test_mapping:
            tolerance = answer * 1e-6  # Make rel. tolerance, because of floats
            fail_msg = "Failure in testing suffix '{0}': '{1}' was not {2}"
            fail_msg = fail_msg.format(expr[-1], expr, answer)
            self.assertAlmostEqual(calc.evaluator({}, {}, expr),
                                   answer,
                                   delta=tolerance,
                                   msg=fail_msg)
예제 #44
0
    def test_number_input(self):
        """
        Test different kinds of float inputs

        See also
          test_trailing_period (slightly different)
          test_exponential_answer
          test_si_suffix
        """
        easy_eval = lambda x: calc.evaluator({}, {}, x)

        self.assertEqual(easy_eval("13"), 13)
        self.assertEqual(easy_eval("3.14"), 3.14)
        self.assertEqual(easy_eval(".618033989"), 0.618033989)

        self.assertEqual(easy_eval("-13"), -13)
        self.assertEqual(easy_eval("-3.14"), -3.14)
        self.assertEqual(easy_eval("-.618033989"), -0.618033989)
예제 #45
0
    def test_si_suffix(self):
        """
        Test calc.py's unique functionality of interpreting si 'suffixes'.

        For instance 'k' stand for 'kilo-' so '1k' should be 1,000
        """
        test_mapping = [('4.2%', 0.042), ('2.25k', 2250), ('8.3M', 8300000),
                        ('9.9G', 9.9e9), ('1.2T', 1.2e12), ('7.4c', 0.074),
                        ('5.4m', 0.0054), ('8.7u', 0.0000087),
                        ('5.6n', 5.6e-9), ('4.2p', 4.2e-12)]

        for (expr, answer) in test_mapping:
            tolerance = answer * 1e-6  # Make rel. tolerance, because of floats
            fail_msg = "Failure in testing suffix '{0}': '{1}' was not {2}"
            fail_msg = fail_msg.format(expr[-1], expr, answer)
            self.assertAlmostEqual(calc.evaluator({}, {}, expr),
                                   answer,
                                   delta=tolerance,
                                   msg=fail_msg)
예제 #46
0
파일: util.py 프로젝트: iivic/BoiseStateX
def compare_with_tolerance(student_complex, instructor_complex, tolerance=default_tolerance, relative_tolerance=False):
    """
    Compare student_complex to instructor_complex with maximum tolerance tolerance.

     - student_complex    :  student result (float complex number)
     - instructor_complex    :  instructor result (float complex number)
     - tolerance   :  float, or string (representing a float or a percentage)
     - relative_tolerance: bool, to explicitly use passed tolerance as relative

     Note: when a tolerance is a percentage (i.e. '10%'), it will compute that
     percentage of the instructor result and yield a number.

     If relative_tolerance is set to False, it will use that value and the
     instructor result to define the bounds of valid student result:
     instructor_complex = 10, tolerance = '10%' will give [9.0, 11.0].

     If relative_tolerance is set to True, it will use that value and both
     instructor result and student result to define the bounds of valid student
     result:
     instructor_complex = 10, student_complex = 20, tolerance = '10%' will give
     [8.0, 12.0].
     This is typically used internally to compare float, with a
     default_tolerance = '0.001%'.

     Default tolerance of 1e-3% is added to compare two floats for
     near-equality (to handle machine representation errors).
     Default tolerance is relative, as the acceptable difference between two
     floats depends on the magnitude of the floats.
     (http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/)
     Examples:
        In [183]: 0.000016 - 1.6*10**-5
        Out[183]: -3.3881317890172014e-21
        In [212]: 1.9e24 - 1.9*10**24
        Out[212]: 268435456.0
    """
    if isinstance(tolerance, str):
        if tolerance == default_tolerance:
            relative_tolerance = True
        if tolerance.endswith('%'):
            tolerance = evaluator(dict(), dict(), tolerance[:-1]) * 0.01
            if not relative_tolerance:
                tolerance = tolerance * abs(instructor_complex)
        else:
            tolerance = evaluator(dict(), dict(), tolerance)

    if relative_tolerance:
        tolerance = tolerance * max(abs(student_complex), abs(instructor_complex))

    if isinf(student_complex) or isinf(instructor_complex):
        # If an input is infinite, we can end up with `abs(student_complex-instructor_complex)` and
        # `tolerance` both equal to infinity. Then, below we would have
        # `inf <= inf` which is a fail. Instead, compare directly.
        return student_complex == instructor_complex

    # because student_complex and instructor_complex are not necessarily
    # complex here, we enforce it here:
    student_complex = complex(student_complex)
    instructor_complex = complex(instructor_complex)

    # if both the instructor and student input are real,
    # compare them as Decimals to avoid rounding errors
    if not (instructor_complex.imag or student_complex.imag):
        # if either of these are not a number, short circuit and return False
        if isnan(instructor_complex.real) or isnan(student_complex.real):
            return False
        student_decimal = Decimal(str(student_complex.real))
        instructor_decimal = Decimal(str(instructor_complex.real))
        tolerance_decimal = Decimal(str(tolerance))
        return abs(student_decimal - instructor_decimal) <= tolerance_decimal

    else:
        # v1 and v2 are, in general, complex numbers:
        # there are some notes about backward compatibility issue: see responsetypes.get_staff_ans()).
        return abs(student_complex - instructor_complex) <= tolerance
예제 #47
0
def compare_with_tolerance(student_complex,
                           instructor_complex,
                           tolerance=default_tolerance,
                           relative_tolerance=False):
    """
    Compare student_complex to instructor_complex with maximum tolerance tolerance.

     - student_complex    :  student result (float complex number)
     - instructor_complex    :  instructor result (float complex number)
     - tolerance   :  float, or string (representing a float or a percentage)
     - relative_tolerance: bool, to explicitly use passed tolerance as relative

     Note: when a tolerance is a percentage (i.e. '10%'), it will compute that
     percentage of the instructor result and yield a number.

     If relative_tolerance is set to False, it will use that value and the
     instructor result to define the bounds of valid student result:
     instructor_complex = 10, tolerance = '10%' will give [9.0, 11.0].

     If relative_tolerance is set to True, it will use that value and both
     instructor result and student result to define the bounds of valid student
     result:
     instructor_complex = 10, student_complex = 20, tolerance = '10%' will give
     [8.0, 12.0].
     This is typically used internally to compare float, with a
     default_tolerance = '0.001%'.

     Default tolerance of 1e-3% is added to compare two floats for
     near-equality (to handle machine representation errors).
     Default tolerance is relative, as the acceptable difference between two
     floats depends on the magnitude of the floats.
     (http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/)
     Examples:
        In [183]: 0.000016 - 1.6*10**-5
        Out[183]: -3.3881317890172014e-21
        In [212]: 1.9e24 - 1.9*10**24
        Out[212]: 268435456.0
    """
    if isinstance(tolerance, str):
        if tolerance == default_tolerance:
            relative_tolerance = True
        if tolerance.endswith('%'):
            tolerance = evaluator(dict(), dict(), tolerance[:-1]) * 0.01
            if not relative_tolerance:
                tolerance = tolerance * abs(instructor_complex)
        else:
            tolerance = evaluator(dict(), dict(), tolerance)

    if relative_tolerance:
        tolerance = tolerance * max(abs(student_complex),
                                    abs(instructor_complex))

    if isinf(student_complex) or isinf(instructor_complex):
        # If an input is infinite, we can end up with `abs(student_complex-instructor_complex)` and
        # `tolerance` both equal to infinity. Then, below we would have
        # `inf <= inf` which is a fail. Instead, compare directly.
        return student_complex == instructor_complex
    else:
        # v1 and v2 are, in general, complex numbers:
        # there are some notes about backward compatibility issue: see responsetypes.get_staff_ans()).
        decimal_places = None
        # count the "decimal_places" for "student_complex". e.g, for
        # "student_complex" value "152.3667" the "decimal_places" will be
        # 4 as there are 4 digits "3667" after decimal
        if isinstance(student_complex, float):
            decimal_places = Decimal(
                str(student_complex)).as_tuple().exponent * -1  # pylint: disable=E1103

        abs_value = abs(student_complex - instructor_complex)

        # decimal_places could be NaN in some cases
        if decimal_places and isinstance(decimal_places, int):
            # abs_value contains 17 digits exponent value so
            # round it up to "decimal_places"
            abs_value = round(abs_value, decimal_places)
        return abs_value <= tolerance
예제 #48
0
 def test_trailing_period(self):
     """
     Test that things like '4.' will be 4 and not throw an error
     """
     self.assertEqual(4.0, calc.evaluator({}, {}, '4.'))
예제 #49
0
 def easy_eval(x):
     return calc.evaluator({}, {}, x)
예제 #50
0
    def test_simple_vars(self):
        """
        Substitution of variables into simple equations
        """
        variables = {
            'x': 9.72,
            'y': 7.91,
            'loooooong': 6.4,
            "f_0'": 2.0,
            "T_{ijk}^{123}''": 5.2
        }

        # Should not change value of constant
        # even with different numbers of variables...
        self.assertEqual(calc.evaluator({'x': 9.72}, {}, '13'), 13)
        self.assertEqual(calc.evaluator({'x': 9.72, 'y': 7.91}, {}, '13'), 13)
        self.assertEqual(calc.evaluator(variables, {}, '13'), 13)

        # Easy evaluation
        self.assertEqual(calc.evaluator(variables, {}, 'x'), 9.72)
        self.assertEqual(calc.evaluator(variables, {}, 'y'), 7.91)
        self.assertEqual(calc.evaluator(variables, {}, 'loooooong'), 6.4)
        self.assertEqual(calc.evaluator(variables, {}, "f_0'"), 2.0)
        self.assertEqual(calc.evaluator(variables, {}, "T_{ijk}^{123}''"), 5.2)

        # Test a simple equation
        self.assertAlmostEqual(
            calc.evaluator(variables, {}, '3*x-y'),
            21.25,
            delta=0.01  # = 3 * 9.72 - 7.91
        )
        self.assertAlmostEqual(calc.evaluator(variables, {}, 'x*y'),
                               76.89,
                               delta=0.01)

        self.assertEqual(calc.evaluator({'x': 9.72, 'y': 7.91}, {}, "13"), 13)
        self.assertEqual(calc.evaluator(variables, {}, "13"), 13)
        self.assertEqual(
            calc.evaluator(
                {
                    'a': 2.2997471478310274,
                    'k': 9,
                    'm': 8,
                    'x': 0.6600949841121
                }, {}, "5"), 5)
예제 #51
0
    def test_calc(self):
        variables = {'R1': 2.0, 'R3': 4.0}
        functions = {'sin': numpy.sin, 'cos': numpy.cos}

        self.assertTrue(abs(calc.evaluator(variables, functions, "10000||sin(7+5)+0.5356")) < 0.01)
        self.assertEqual(calc.evaluator({'R1': 2.0, 'R3': 4.0}, {}, "13"), 13)
        self.assertEqual(calc.evaluator(variables, functions, "13"), 13)
        self.assertEqual(calc.evaluator({'a': 2.2997471478310274, 'k': 9, 'm': 8, 'x': 0.66009498411213041}, {}, "5"), 5)
        self.assertEqual(calc.evaluator({}, {}, "-1"), -1)
        self.assertEqual(calc.evaluator({}, {}, "-0.33"), -.33)
        self.assertEqual(calc.evaluator({}, {}, "-.33"), -.33)
        self.assertEqual(calc.evaluator(variables, functions, "R1*R3"), 8.0)
        self.assertTrue(abs(calc.evaluator(variables, functions, "sin(e)-0.41")) < 0.01)
        self.assertTrue(abs(calc.evaluator(variables, functions, "k*T/q-0.025")) < 0.001)
        self.assertTrue(abs(calc.evaluator(variables, functions, "e^(j*pi)") + 1) < 0.00001)
        self.assertTrue(abs(calc.evaluator(variables, functions, "j||1") - 0.5 - 0.5j) < 0.00001)
        variables['t'] = 1.0
        # Use self.assertAlmostEqual here...
        self.assertTrue(abs(calc.evaluator(variables, functions, "t") - 1.0) < 0.00001)
        self.assertTrue(abs(calc.evaluator(variables, functions, "T") - 1.0) < 0.00001)
        self.assertTrue(abs(calc.evaluator(variables, functions, "t", cs=True) - 1.0) < 0.00001)
        self.assertTrue(abs(calc.evaluator(variables, functions, "T", cs=True) - 298) < 0.2)
        # Use self.assertRaises here...
        exception_happened = False
        try:
            calc.evaluator({}, {}, "5+7 QWSEKO")
        except:
            exception_happened = True
        self.assertTrue(exception_happened)

        try:
            calc.evaluator({'r1': 5}, {}, "r1+r2")
        except calc.UndefinedVariable:
            pass

        self.assertEqual(calc.evaluator(variables, functions, "r1*r3"), 8.0)

        exception_happened = False
        try:
            calc.evaluator(variables, functions, "r1*r3", cs=True)
        except:
            exception_happened = True
        self.assertTrue(exception_happened)