def to_json(v): """to_json(v) If v has a standard type that cannot be json serialized, it is replaced with a {'_type':..., '_value':...} pair that can be json serialized: complex -> '_type': 'complex' non-complex ndarray (assumes each element can be json serialized) -> '_type': 'ndarray' complex ndarray -> '_type': 'complex_ndarray' sympy.Expr (i.e., any scalar sympy expression) -> '_type': 'sympy' sympy.Matrix -> '_type': 'sympy_matrix' If v is an ndarray, this function preserves its dtype (by adding '_dtype' as a third field in the dictionary). This function does not try to preserve information like the assumptions on variables in a sympy expression. If v can be json serialized or does not have a standard type, then it is returned without change. """ if np.isscalar(v) and np.iscomplexobj(v): return {'_type': 'complex', '_value': {'real': v.real, 'imag': v.imag}} elif isinstance(v, np.ndarray): if np.isrealobj(v): return { '_type': 'ndarray', '_value': v.tolist(), '_dtype': str(v.dtype) } elif np.iscomplexobj(v): return { '_type': 'complex_ndarray', '_value': { 'real': v.real.tolist(), 'imag': v.imag.tolist() }, '_dtype': str(v.dtype) } elif isinstance(v, sympy.Expr): return sympy_to_json(v) elif isinstance(v, sympy.Matrix) or isinstance(v, sympy.ImmutableMatrix): s = [str(a) for a in v.free_symbols] num_rows, num_cols = v.shape M = [] for i in range(0, num_rows): row = [] for j in range(0, num_cols): row.append(str(v[i, j])) M.append(row) return { '_type': 'sympy_matrix', '_value': M, '_variables': s, '_shape': [num_rows, num_cols] } else: return v
def to_json(v): """to_json(v) If v has a standard type that cannot be json serialized, it is replaced with a {'_type':..., '_value':...} pair that can be json serialized: complex -> '_type': 'complex' non-complex ndarray (assumes each element can be json serialized) -> '_type': 'ndarray' complex ndarray -> '_type': 'complex_ndarray' sympy.Expr (i.e., any scalar sympy expression) -> '_type': 'sympy' sympy.Matrix -> '_type': 'sympy_matrix' If v is an ndarray, this function preserves its dtype (by adding '_dtype' as a third field in the dictionary). This function does not try to preserve information like the assumptions on variables in a sympy expression. If v can be json serialized or does not have a standard type, then it is returned without change. """ if np.isscalar(v) and np.iscomplexobj(v): return {'_type': 'complex', '_value': {'real': v.real, 'imag': v.imag}} elif isinstance(v, np.ndarray): if np.isrealobj(v): return {'_type': 'ndarray', '_value': v.tolist(), '_dtype': str(v.dtype)} elif np.iscomplexobj(v): return {'_type': 'complex_ndarray', '_value': {'real': v.real.tolist(), 'imag': v.imag.tolist()}, '_dtype': str(v.dtype)} elif isinstance(v, sympy.Expr): return sympy_to_json(v) elif isinstance(v, sympy.Matrix) or isinstance(v, sympy.ImmutableMatrix): s = [str(a) for a in v.free_symbols] num_rows, num_cols = v.shape M = [] for i in range(0, num_rows): row = [] for j in range(0, num_cols): row.append(str(v[i, j])) M.append(row) return {'_type': 'sympy_matrix', '_value': M, '_variables': s, '_shape': [num_rows, num_cols]} else: return v
def parse(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') variables = get_variables_list(pl.get_string_attrib(element, 'variables', None)) allow_complex = pl.get_boolean_attrib(element, 'allow-complex', False) imaginary_unit = pl.get_string_attrib(element, 'imaginary-unit-for-display', 'i') # Get submitted answer or return parse_error if it does not exist a_sub = data['submitted_answers'].get(name, None) if not a_sub: data['format_errors'][name] = 'No submitted answer.' data['submitted_answers'][name] = None return # Parse the submitted answer and put the result in a string try: # Replace '^' with '**' wherever it appears. In MATLAB, either can be used # for exponentiation. In python, only the latter can be used. a_sub = a_sub.replace('^', '**') # Strip whitespace a_sub = a_sub.strip() # Convert safely to sympy a_sub_parsed = phs.convert_string_to_sympy(a_sub, variables, allow_complex=allow_complex) # If complex numbers are not allowed, raise error if expression has the imaginary unit if (not allow_complex) and (a_sub_parsed.has(sympy.I)): a_sub_parsed = a_sub_parsed.subs(sympy.I, sympy.Symbol(imaginary_unit)) s = 'Your answer was simplified to this, which contains a complex number (denoted ${:s}$): $${:s}$$'.format(imaginary_unit, sympy.latex(a_sub_parsed)) data['format_errors'][name] = s data['submitted_answers'][name] = None return # Store result as json. a_sub_json = phs.sympy_to_json(a_sub_parsed, allow_complex=allow_complex) except phs.HasFloatError as err: s = 'Your answer contains the floating-point number ' + str(err.n) + '. ' s += 'All numbers must be expressed as integers (or ratios of integers). ' s += '<br><br><pre>' + phs.point_to_error(a_sub, err.offset) + '</pre>' data['format_errors'][name] = s data['submitted_answers'][name] = None return except phs.HasComplexError as err: s = 'Your answer contains the complex number ' + str(err.n) + '. ' s += 'All numbers must be expressed as integers (or ratios of integers). ' if allow_complex: s += 'To include a complex number in your expression, write it as the product of an integer with the imaginary unit <code>i</code> or <code>j</code>. ' s += '<br><br><pre>' + phs.point_to_error(a_sub, err.offset) + '</pre>' data['format_errors'][name] = s data['submitted_answers'][name] = None return except phs.HasInvalidExpressionError as err: s = 'Your answer has an invalid expression. ' s += '<br><br><pre>' + phs.point_to_error(a_sub, err.offset) + '</pre>' data['format_errors'][name] = s data['submitted_answers'][name] = None return except phs.HasInvalidFunctionError as err: s = 'Your answer calls an invalid function "' + err.text + '". ' s += '<br><br><pre>' + phs.point_to_error(a_sub, err.offset) + '</pre>' data['format_errors'][name] = s data['submitted_answers'][name] = None return except phs.HasInvalidVariableError as err: s = 'Your answer refers to an invalid variable "' + err.text + '". ' s += '<br><br><pre>' + phs.point_to_error(a_sub, err.offset) + '</pre>' data['format_errors'][name] = s data['submitted_answers'][name] = None return except phs.HasParseError as err: s = 'Your answer has a syntax error. ' s += '<br><br><pre>' + phs.point_to_error(a_sub, err.offset) + '</pre>' data['format_errors'][name] = s data['submitted_answers'][name] = None return except phs.HasEscapeError as err: s = 'Your answer must not contain the character "\\". ' s += '<br><br><pre>' + phs.point_to_error(a_sub, err.offset) + '</pre>' data['format_errors'][name] = s data['submitted_answers'][name] = None return except phs.HasCommentError as err: s = 'Your answer must not contain the character "#". ' s += '<br><br><pre>' + phs.point_to_error(a_sub, err.offset) + '</pre>' data['format_errors'][name] = s data['submitted_answers'][name] = None return except Exception as err: data['format_errors'][name] = 'Invalid format.' data['submitted_answers'][name] = None return # Make sure we can parse the json again try: # Convert safely to sympy phs.json_to_sympy(a_sub_json, allow_complex=allow_complex) # Finally, store the result data['submitted_answers'][name] = a_sub_json except Exception as err: s = 'Your answer was simplified to this, which contains an invalid expression: $${:s}$$'.format(sympy.latex(a_sub_parsed)) data['format_errors'][name] = s data['submitted_answers'][name] = None
def parse(element_html, data): element = lxml.html.fragment_fromstring(element_html) name = pl.get_string_attrib(element, 'answers-name') variables = get_variables_list(pl.get_string_attrib(element, 'variables', None)) allow_complex = pl.get_boolean_attrib(element, 'allow-complex', False) imaginary_unit = pl.get_string_attrib(element, 'imaginary-unit-for-display', 'i') # Get submitted answer or return parse_error if it does not exist a_sub = data['submitted_answers'].get(name, None) if not a_sub: data['format_errors'][name] = 'No submitted answer.' data['submitted_answers'][name] = None return # Parse the submitted answer and put the result in a string try: # Replace '^' with '**' wherever it appears. In MATLAB, either can be used # for exponentiation. In python, only the latter can be used. a_sub = a_sub.replace('^', '**') # Strip whitespace a_sub = a_sub.strip() # Convert safely to sympy a_sub_parsed = phs.convert_string_to_sympy(a_sub, variables, allow_complex=allow_complex) # If complex numbers are not allowed, raise error if expression has the imaginary unit if (not allow_complex) and (a_sub_parsed.has(sympy.I)): a_sub_parsed = a_sub_parsed.subs(sympy.I, sympy.Symbol(imaginary_unit)) s = 'Your answer was simplified to this, which contains a complex number (denoted ${:s}$): $${:s}$$'.format(imaginary_unit, sympy.latex(a_sub_parsed)) data['format_errors'][name] = s data['submitted_answers'][name] = None return # Store result as json. a_sub_json = phs.sympy_to_json(a_sub_parsed, allow_complex=allow_complex) except phs.HasFloatError as err: s = 'Your answer contains the floating-point number ' + str(err.n) + '. ' s += 'All numbers must be expressed as integers (or ratios of integers). ' s += '<br><br><pre>' + phs.point_to_error(a_sub, err.offset) + '</pre>' data['format_errors'][name] = s data['submitted_answers'][name] = None return except phs.HasComplexError as err: s = 'Your answer contains the complex number ' + str(err.n) + '. ' s += 'All numbers must be expressed as integers (or ratios of integers). ' if allow_complex: s += 'To include a complex number in your expression, write it as the product of an integer with the imaginary unit <code>i</code> or <code>j</code>. ' s += '<br><br><pre>' + phs.point_to_error(a_sub, err.offset) + '</pre>' data['format_errors'][name] = s data['submitted_answers'][name] = None return except phs.HasInvalidExpressionError as err: s = 'Your answer has an invalid expression. ' s += '<br><br><pre>' + phs.point_to_error(a_sub, err.offset) + '</pre>' data['format_errors'][name] = s data['submitted_answers'][name] = None return except phs.HasInvalidFunctionError as err: s = 'Your answer calls an invalid function "' + err.text + '". ' s += '<br><br><pre>' + phs.point_to_error(a_sub, err.offset) + '</pre>' data['format_errors'][name] = s data['submitted_answers'][name] = None return except phs.HasInvalidVariableError as err: s = 'Your answer refers to an invalid variable "' + err.text + '". ' s += '<br><br><pre>' + phs.point_to_error(a_sub, err.offset) + '</pre>' data['format_errors'][name] = s data['submitted_answers'][name] = None return except phs.HasParseError as err: s = 'Your answer has a syntax error. ' s += '<br><br><pre>' + phs.point_to_error(a_sub, err.offset) + '</pre>' data['format_errors'][name] = s data['submitted_answers'][name] = None return except phs.HasEscapeError as err: s = 'Your answer must not contain the character "\\". ' s += '<br><br><pre>' + phs.point_to_error(a_sub, err.offset) + '</pre>' data['format_errors'][name] = s data['submitted_answers'][name] = None return except phs.HasCommentError as err: s = 'Your answer must not contain the character "#". ' s += '<br><br><pre>' + phs.point_to_error(a_sub, err.offset) + '</pre>' data['format_errors'][name] = s data['submitted_answers'][name] = None return except Exception: data['format_errors'][name] = 'Invalid format.' data['submitted_answers'][name] = None return # Make sure we can parse the json again try: # Convert safely to sympy phs.json_to_sympy(a_sub_json, allow_complex=allow_complex) # Finally, store the result data['submitted_answers'][name] = a_sub_json except Exception: s = 'Your answer was simplified to this, which contains an invalid expression: $${:s}$$'.format(sympy.latex(a_sub_parsed)) data['format_errors'][name] = s data['submitted_answers'][name] = None