diff --git a/common/lib/chem/chem/__init__.py b/common/lib/chem/chem/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/common/lib/chem/chem/chemcalc.py b/common/lib/chem/chem/chemcalc.py
deleted file mode 100644
index 81c71177ea..0000000000
--- a/common/lib/chem/chem/chemcalc.py
+++ /dev/null
@@ -1,459 +0,0 @@
-from __future__ import absolute_import, division
-from fractions import Fraction
-
-import markupsafe
-import nltk
-from nltk.tree import Tree
-from pyparsing import Literal, OneOrMore, ParseException, StringEnd
-from six.moves import map
-from six.moves import range
-from six.moves import zip
-from functools import reduce
-
-
-ARROWS = ('<->', '->')
-
-# Defines a simple pyparsing tokenizer for chemical equations
-elements = ['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'B', 'Ba', 'Be',
- 'Bh', 'Bi', 'Bk', 'Br', 'C', 'Ca', 'Cd', 'Ce', 'Cf', 'Cl', 'Cm',
- 'Cn', 'Co', 'Cr', 'Cs', 'Cu', 'Db', 'Ds', 'Dy', 'Er', 'Es', 'Eu',
- 'F', 'Fe', 'Fl', 'Fm', 'Fr', 'Ga', 'Gd', 'Ge', 'H', 'He', 'Hf',
- 'Hg', 'Ho', 'Hs', 'I', 'In', 'Ir', 'K', 'Kr', 'La', 'Li', 'Lr',
- 'Lu', 'Lv', 'Md', 'Mg', 'Mn', 'Mo', 'Mt', 'N', 'Na', 'Nb', 'Nd',
- 'Ne', 'Ni', 'No', 'Np', 'O', 'Os', 'P', 'Pa', 'Pb', 'Pd', 'Pm',
- 'Po', 'Pr', 'Pt', 'Pu', 'Ra', 'Rb', 'Re', 'Rf', 'Rg', 'Rh', 'Rn',
- 'Ru', 'S', 'Sb', 'Sc', 'Se', 'Sg', 'Si', 'Sm', 'Sn', 'Sr', 'Ta',
- 'Tb', 'Tc', 'Te', 'Th', 'Ti', 'Tl', 'Tm', 'U', 'Uuo', 'Uup',
- 'Uus', 'Uut', 'V', 'W', 'Xe', 'Y', 'Yb', 'Zn', 'Zr']
-digits = list(map(str, list(range(10))))
-symbols = list("[](){}^+-/")
-phases = ["(s)", "(l)", "(g)", "(aq)"]
-tokens = reduce(lambda a, b: a ^ b, list(map(Literal, elements + digits + symbols + phases)))
-tokenizer = OneOrMore(tokens) + StringEnd()
-
-# HTML, Text are temporarily copied from openedx.core.djangolib.markup
-# These libraries need to be moved out of edx-platform to be used by
-# other applications.
-# See LEARNER-5853 for more details.
-Text = markupsafe.escape # pylint: disable=invalid-name
-
-
-def HTML(html): # pylint: disable=invalid-name
- return markupsafe.Markup(html)
-
-
-def _orjoin(l):
- return "'" + "' | '".join(l) + "'"
-
-
-# Defines an NLTK parser for tokenized expressions
-grammar = """
- S -> multimolecule | multimolecule '+' S
- multimolecule -> count molecule | molecule
- count -> number | number '/' number
- molecule -> unphased | unphased phase
- unphased -> group | paren_group_round | paren_group_square
- element -> """ + _orjoin(elements) + """
- digit -> """ + _orjoin(digits) + """
- phase -> """ + _orjoin(phases) + """
- number -> digit | digit number
- group -> suffixed | suffixed group
- paren_group_round -> '(' group ')'
- paren_group_square -> '[' group ']'
- plus_minus -> '+' | '-'
- number_suffix -> number
- ion_suffix -> '^' number plus_minus | '^' plus_minus
- suffix -> number_suffix | number_suffix ion_suffix | ion_suffix
- unsuffixed -> element | paren_group_round | paren_group_square
-
- suffixed -> unsuffixed | unsuffixed suffix
-"""
-parser = nltk.ChartParser(nltk.CFG.fromstring(grammar))
-
-
-def _clean_parse_tree(tree):
- """
- The parse tree contains a lot of redundant
- nodes. E.g. paren_groups have groups as children, etc. This will
- clean up the tree.
- """
- def unparse_number(n):
- """
- Go from a number parse tree to a number
- """
- if len(n) == 1:
- rv = n[0][0]
- else:
- rv = n[0][0] + unparse_number(n[1])
- return rv
-
- def null_tag(n):
- """
- Remove a tag
- """
- return n[0]
-
- def ion_suffix(n):
- """
- 1. "if" part handles special case
- 2. "else" part is general behaviour
- """
- if n[1:][0].label() == 'number' and n[1:][0][0][0] == '1':
- # if suffix is explicitly 1, like ^1-
- # strip 1, leave only sign: ^-
- return nltk.tree.Tree(n.label(), n[2:])
- else:
- return nltk.tree.Tree(n.label(), n[1:])
-
- dispatch = {'number': lambda x: nltk.tree.Tree("number", [unparse_number(x)]),
- 'unphased': null_tag,
- 'unsuffixed': null_tag,
- 'number_suffix': lambda x: nltk.tree.Tree('number_suffix', [unparse_number(x[0])]),
- 'suffixed': lambda x: len(x) > 1 and x or x[0],
- 'ion_suffix': ion_suffix,
- 'paren_group_square': lambda x: nltk.tree.Tree(x.label(), x[1]),
- 'paren_group_round': lambda x: nltk.tree.Tree(x.label(), x[1])}
-
- if isinstance(tree, str):
- return tree
-
- old_node = None
- # This loop means that if a node is processed, and returns a child,
- # the child will be processed.
- while tree.label() in dispatch and tree.label() != old_node:
- old_node = tree.label()
- tree = dispatch[tree.label()](tree)
-
- children = []
- for child in tree:
- child = _clean_parse_tree(child)
- children.append(child)
-
- tree = nltk.tree.Tree(tree.label(), children)
-
- return tree
-
-
-def _merge_children(tree, tags):
- """
- nltk, by documentation, cannot do arbitrary length groups.
- Instead of: (group 1 2 3 4)
- It has to handle this recursively: (group 1 (group 2 (group 3 (group 4))))
- We do the cleanup of converting from the latter to the former.
- """
- if tree is None:
- # There was a problem--shouldn't have empty trees (NOTE: see this with input e.g. 'H2O(', or 'Xe+').
- raise ParseException("Shouldn't have empty trees")
-
- if isinstance(tree, str):
- return tree
-
- merged_children = []
- done = False
-
- # Merge current tag
- while not done:
- done = True
- for child in tree:
- if isinstance(child, nltk.tree.Tree) and child.label() == tree.label() and tree.label() in tags:
- merged_children = merged_children + list(child)
- done = False
- else:
- merged_children = merged_children + [child]
- tree = nltk.tree.Tree(tree.label(), merged_children)
- merged_children = []
-
- # And recurse
- children = []
- for child in tree:
- children.append(_merge_children(child, tags))
-
- return nltk.tree.Tree(tree.label(), children)
-
-
-def _render_to_html(tree):
- """
- Renders a cleaned tree to HTML
- """
- def molecule_count(tree, children):
- # If an integer, return that integer
- if len(tree) == 1:
- return tree[0][0]
- # If a fraction, return the fraction
- if len(tree) == 3:
- return HTML(" {num}⁄{den} ").format(num=tree[0][0], den=tree[2][0])
- return "Error"
-
- def subscript(tree, children):
- return HTML("{sub}").format(sub=children)
-
- def superscript(tree, children):
- return HTML("{sup}").format(sup=children)
-
- def round_brackets(tree, children):
- return HTML("({insider})").format(insider=children)
-
- def square_brackets(tree, children):
- return HTML("[{insider}]").format(insider=children)
-
- dispatch = {'count': molecule_count,
- 'number_suffix': subscript,
- 'ion_suffix': superscript,
- 'paren_group_round': round_brackets,
- 'paren_group_square': square_brackets}
-
- if isinstance(tree, str):
- return tree
- else:
- children = HTML("").join(map(_render_to_html, tree))
- if tree.label() in dispatch:
- return dispatch[tree.label()](tree, children)
- else:
- return children.replace(' ', '')
-
-
-def render_to_html(eq):
- """
- Render a chemical equation string to html.
-
- Renders each molecule separately, and returns invalid input wrapped in a .
- """
- def err(s):
- """
- Render as an error span
- """
- return HTML('{0}').format(s)
-
- def render_arrow(arrow):
- """
- Turn text arrows into pretty ones
- """
- if arrow == '->':
- return HTML(u'\u2192')
- if arrow == '<->':
- return HTML(u'\u2194')
-
- # this won't be reached unless we add more arrow types, but keep it to avoid explosions when
- # that happens. HTML-escape this unknown arrow just in case.
- return Text(arrow)
-
- def render_expression(ex):
- """
- Render a chemical expression--no arrows.
- """
- try:
- return _render_to_html(_get_final_tree(ex))
- except ParseException:
- return err(ex)
-
- def spanify(s):
- return HTML(u'{0}').format(s)
-
- left, arrow, right = split_on_arrow(eq)
- if arrow == '':
- # only one side
- return spanify(render_expression(left))
-
- return spanify(render_expression(left) + render_arrow(arrow) + render_expression(right))
-
-
-def _get_final_tree(s):
- """
- Return final tree after merge and clean.
-
- Raises pyparsing.ParseException if s is invalid.
- """
- try:
- tokenized = tokenizer.parseString(s)
- parsed = parser.parse(tokenized)
- merged = _merge_children(next(parsed), {'S', 'group'})
- final = _clean_parse_tree(merged)
- return final
- except StopIteration:
- # This happens with an empty tree- see this with input e.g. 'H2O(', or 'Xe+').
- raise ParseException("Shouldn't have empty trees")
-
-
-def _check_equality(tuple1, tuple2):
- """
- return True if tuples of multimolecules are equal
- """
- list1 = list(tuple1)
- list2 = list(tuple2)
-
- # Hypo: trees where are levels count+molecule vs just molecule
- # cannot be sorted properly (tested on test_complex_additivity)
- # But without factors and phases sorting seems to work.
-
- # Also for lists of multimolecules without factors and phases
- # sorting seems to work fine.
- list1.sort()
- list2.sort()
- return list1 == list2
-
-
-def compare_chemical_expression(s1, s2, ignore_state=False):
- """
- It does comparison between two expressions.
- It uses divide_chemical_expression and check if division is 1
- """
- return divide_chemical_expression(s1, s2, ignore_state) == 1
-
-
-def divide_chemical_expression(s1, s2, ignore_state=False):
- """
- Compare two chemical expressions for equivalence up to a multiplicative factor:
-
- - If they are not the same chemicals, returns False.
- - If they are the same, "divide" s1 by s2 to returns a factor x such that s1 / s2 == x as a Fraction object.
- - if ignore_state is True, ignores phases when doing the comparison.
-
- Examples:
- divide_chemical_expression("H2O", "3H2O") -> Fraction(1,3)
- divide_chemical_expression("3H2O", "H2O") -> 3 # actually Fraction(3, 1), but compares == to 3.
- divide_chemical_expression("2H2O(s) + 2CO2", "H2O(s)+CO2") -> 2
- divide_chemical_expression("H2O(s) + CO2", "3H2O(s)+2CO2") -> False
-
- Implementation sketch:
- - extract factors and phases to standalone lists,
- - compare expressions without factors and phases,
- - divide lists of factors for each other and check
- for equality of every element in list,
- - return result of factor division
-
- """
-
- # parsed final trees
- treedic = {
- '1': _get_final_tree(s1),
- '2': _get_final_tree(s2)
- }
-
- # strip phases and factors
- # collect factors in list
- for i in ('1', '2'):
- treedic[i + ' cleaned_mm_list'] = []
- treedic[i + ' factors'] = []
- treedic[i + ' phases'] = []
- for el in treedic[i].subtrees(filter=lambda t: t.label() == 'multimolecule'):
- count_subtree = [t for t in el.subtrees() if t.label() == 'count']
- group_subtree = [t for t in el.subtrees() if t.label() == 'group']
- phase_subtree = [t for t in el.subtrees() if t.label() == 'phase']
- if count_subtree:
- if len(count_subtree[0]) > 1:
- treedic[i + ' factors'].append(
- int(count_subtree[0][0][0]) /
- int(count_subtree[0][2][0]))
- else:
- treedic[i + ' factors'].append(int(count_subtree[0][0][0]))
- else:
- treedic[i + ' factors'].append(1.0)
- if phase_subtree:
- treedic[i + ' phases'].append(phase_subtree[0][0])
- else:
- treedic[i + ' phases'].append(' ')
- treedic[i + ' cleaned_mm_list'].append(
- Tree('multimolecule', [Tree('molecule', group_subtree)]))
-
- # order of factors and phases must mirror the order of multimolecules,
- # use 'decorate, sort, undecorate' pattern
- treedic['1 cleaned_mm_list'], treedic['1 factors'], treedic['1 phases'] = list(zip(
- *sorted(zip(treedic['1 cleaned_mm_list'], treedic['1 factors'], treedic['1 phases']))))
-
- treedic['2 cleaned_mm_list'], treedic['2 factors'], treedic['2 phases'] = list(zip(
- *sorted(zip(treedic['2 cleaned_mm_list'], treedic['2 factors'], treedic['2 phases']))))
-
- # check if expressions are correct without factors
- if not _check_equality(treedic['1 cleaned_mm_list'], treedic['2 cleaned_mm_list']):
- return False
-
- # phases are ruled by ingore_state flag
- if not ignore_state: # phases matters
- if treedic['1 phases'] != treedic['2 phases']:
- return False
-
- if any(
- [
- x / y - treedic['1 factors'][0] / treedic['2 factors'][0]
- for (x, y) in zip(treedic['1 factors'], treedic['2 factors'])
- ]
- ):
- # factors are not proportional
- return False
- else:
- # return ratio
- return Fraction(treedic['1 factors'][0] / treedic['2 factors'][0])
-
-
-def split_on_arrow(eq):
- """
- Split a string on an arrow. Returns left, arrow, right. If there is no arrow, returns the
- entire eq in left, and '' in arrow and right.
-
- Return left, arrow, right.
- """
- # order matters -- need to try <-> first
- for arrow in ARROWS:
- left, a, right = eq.partition(arrow)
- if a != '':
- return left, a, right
-
- return eq, '', ''
-
-
-def chemical_equations_equal(eq1, eq2, exact=False):
- """
- Check whether two chemical equations are the same. (equations have arrows)
-
- If exact is False, then they are considered equal if they differ by a
- constant factor.
-
- arrows matter: -> and <-> are different.
-
- e.g.
- chemical_equations_equal('H2 + O2 -> H2O2', 'O2 + H2 -> H2O2') -> True
- chemical_equations_equal('H2 + O2 -> H2O2', 'O2 + 2H2 -> H2O2') -> False
-
- chemical_equations_equal('H2 + O2 -> H2O2', 'O2 + H2 <-> H2O2') -> False
-
- chemical_equations_equal('H2 + O2 -> H2O2', '2 H2 + 2 O2 -> 2 H2O2') -> True
- chemical_equations_equal('H2 + O2 -> H2O2', '2 H2 + 2 O2 -> 2 H2O2', exact=True) -> False
-
-
- If there's a syntax error, we return False.
- """
-
- left1, arrow1, right1 = split_on_arrow(eq1)
- left2, arrow2, right2 = split_on_arrow(eq2)
-
- if arrow1 == '' or arrow2 == '':
- return False
-
- # TODO: may want to be able to give student helpful feedback about why things didn't work.
- if arrow1 != arrow2:
- # arrows don't match
- return False
-
- try:
- factor_left = divide_chemical_expression(left1, left2)
- if not factor_left:
- # left sides don't match
- return False
-
- factor_right = divide_chemical_expression(right1, right2)
- if not factor_right:
- # right sides don't match
- return False
-
- if factor_left != factor_right:
- # factors don't match (molecule counts to add up)
- return False
-
- if exact and factor_left != 1:
- # want an exact match.
- return False
-
- return True
- except ParseException:
- # Don't want external users to have to deal with parsing exceptions. Just return False.
- return False
diff --git a/common/lib/chem/chem/chemtools.py b/common/lib/chem/chem/chemtools.py
deleted file mode 100644
index 19405bdcad..0000000000
--- a/common/lib/chem/chem/chemtools.py
+++ /dev/null
@@ -1,207 +0,0 @@
-"""This module originally includes functions for grading Vsepr problems.
-
-Also, may be this module is the place for other chemistry-related grade functions. TODO: discuss it.
-"""
-
-from __future__ import absolute_import
-import itertools
-import json
-import unittest
-
-
-def vsepr_parse_user_answer(user_input):
- """
- user_input is json generated by vsepr.js from dictionary.
- There are must be only two keys in original user_input dictionary: "geometry" and "atoms".
- Format: u'{"geometry": "AX3E0","atoms":{"c0": "B","p0": "F","p1": "B","p2": "F"}}'
- Order of elements inside "atoms" subdict does not matters.
- Return dict from parsed json.
-
- "Atoms" subdict stores positions of atoms in molecule.
- General types of positions:
- c0 - central atom
- p0..pN - peripheral atoms
- a0..aN - axial atoms
- e0..eN - equatorial atoms
-
- Each position is dictionary key, i.e. user_input["atoms"]["c0"] is central atom, user_input["atoms"]["a0"] is one of axial atoms.
-
- Special position only for AX6 (Octahedral) geometry:
- e10, e12 - atom pairs opposite the central atom,
- e20, e22 - atom pairs opposite the central atom,
- e1 and e2 pairs lying crosswise in equatorial plane.
-
- In user_input["atoms"] may be only 3 set of keys:
- (c0,p0..pN),
- (c0, a0..aN, e0..eN),
- (c0, a0, a1, e10,e11,e20,e21) - if geometry is AX6.
- """
- return json.loads(user_input)
-
-
-def vsepr_build_correct_answer(geometry, atoms):
- """
- geometry is string.
- atoms is dict of atoms with proper positions.
- Example:
-
- correct_answer = vsepr_build_correct_answer(geometry="AX4E0", atoms={"c0": "N", "p0": "H", "p1": "(ep)", "p2": "H", "p3": "H"})
-
- returns a dictionary composed from input values:
- {'geometry': geometry, 'atoms': atoms}
- """
- return {'geometry': geometry, 'atoms': atoms}
-
-
-def vsepr_grade(user_input, correct_answer, convert_to_peripheral=False):
- """
- This function does comparison between user_input and correct_answer.
-
- Comparison is successful if all steps are successful:
-
- 1) geometries are equal
- 2) central atoms (index in dictionary 'c0') are equal
- 3):
- In next steps there is comparing of corresponding subsets of atom positions: equatorial (e0..eN), axial (a0..aN) or peripheral (p0..pN)
-
- If convert_to_peripheral is True, then axial and equatorial positions are converted to peripheral.
- This means that user_input from:
- "atoms":{"c0": "Br","a0": "test","a1": "(ep)","e10": "H","e11": "(ep)","e20": "H","e21": "(ep)"}}' after parsing to json
- is converted to:
- {"c0": "Br", "p0": "(ep)", "p1": "test", "p2": "H", "p3": "H", "p4": "(ep)", "p6": "(ep)"}
- i.e. aX and eX -> pX
-
- So if converted, p subsets are compared,
- if not a and e subsets are compared
- If all subsets are equal, grade succeeds.
-
- There is also one special case for AX6 geometry.
- In this case user_input["atoms"] contains special 3 symbol keys: e10, e12, e20, and e21.
- Correct answer for this geometry can be of 3 types:
- 1) c0 and peripheral
- 2) c0 and axial and equatorial
- 3) c0 and axial and equatorial-subset-1 (e1X) and equatorial-subset-2 (e2X)
-
- If correct answer is type 1 or 2, then user_input is converted from type 3 to type 2 (or to type 1 if convert_to_peripheral is True)
-
- If correct_answer is type 3, then we done special case comparison. We have 3 sets of atoms positions both in user_input and correct_answer: axial, eq-1 and eq-2.
- Answer will be correct if these sets are equals for one of permutations. For example, if :
- user_axial = correct_eq-1
- user_eq-1 = correct-axial
- user_eq-2 = correct-eq-2
-
- """
- if user_input['geometry'] != correct_answer['geometry']:
- return False
-
- if user_input['atoms']['c0'] != correct_answer['atoms']['c0']:
- return False
-
- if convert_to_peripheral:
- # convert user_input from (a,e,e1,e2) to (p)
- # correct_answer must be set in (p) using this flag
- c0 = user_input['atoms'].pop('c0')
- user_input['atoms'] = {'p' + str(i): v for i, v in enumerate(user_input['atoms'].values())}
- user_input['atoms']['c0'] = c0
-
- # special case for AX6
- if 'e10' in correct_answer['atoms']: # need check e1x, e2x symmetry for AX6..
- a_user = {}
- a_correct = {}
- for ea_position in ['a', 'e1', 'e2']: # collecting positions:
- a_user[ea_position] = [v for k, v in user_input['atoms'].items() if k.startswith(ea_position)]
- a_correct[ea_position] = [v for k, v in correct_answer['atoms'].items() if k.startswith(ea_position)]
-
- correct = [sorted(a_correct['a'])] + [sorted(a_correct['e1'])] + [sorted(a_correct['e2'])]
- for permutation in itertools.permutations(['a', 'e1', 'e2']):
- if correct == [sorted(a_user[permutation[0]])] + [sorted(a_user[permutation[1]])] + [sorted(a_user[permutation[2]])]:
- return True
- return False
-
- else: # no need to check e1x,e2x symmetry - convert them to ex
- if 'e10' in user_input['atoms']: # e1x exists, it is AX6.. case
- e_index = 0
- for k, v in user_input['atoms'].items():
- if len(k) == 3: # e1x
- del user_input['atoms'][k]
- user_input['atoms']['e' + str(e_index)] = v
- e_index += 1
-
- # common case
- for ea_position in ['p', 'a', 'e']:
- # collecting atoms:
- a_user = [v for k, v in user_input['atoms'].items() if k.startswith(ea_position)]
- a_correct = [v for k, v in correct_answer['atoms'].items() if k.startswith(ea_position)]
- # print a_user, a_correct
- if len(a_user) != len(a_correct):
- return False
- if sorted(a_user) != sorted(a_correct):
- return False
-
- return True
-
-
-class Test_Grade(unittest.TestCase):
- ''' test grade function '''
-
- def test_incorrect_geometry(self):
- correct_answer = vsepr_build_correct_answer(geometry="AX4E0", atoms={"c0": "N", "p0": "H", "p1": "(ep)", "p2": "H", "p3": "H"})
- user_answer = vsepr_parse_user_answer(u'{"geometry": "AX3E0","atoms":{"c0": "B","p0": "F","p1": "B","p2": "F"}}')
- self.assertFalse(vsepr_grade(user_answer, correct_answer))
-
- def test_correct_answer_p(self):
- correct_answer = vsepr_build_correct_answer(geometry="AX4E0", atoms={"c0": "N", "p0": "H", "p1": "(ep)", "p2": "H", "p3": "H"})
- user_answer = vsepr_parse_user_answer(u'{"geometry": "AX4E0","atoms":{"c0": "N","p0": "H","p1": "(ep)","p2": "H", "p3": "H"}}')
- self.assertTrue(vsepr_grade(user_answer, correct_answer))
-
- def test_correct_answer_ae(self):
- correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "test", "a1": "(ep)", "e0": "H", "e1": "H", "e2": "(ep)", "e3": "(ep)"})
- user_answer = vsepr_parse_user_answer(u'{"geometry": "AX6E0","atoms":{"c0": "Br","a0": "test","a1": "(ep)","e10": "H","e11": "H","e20": "(ep)","e21": "(ep)"}}')
- self.assertTrue(vsepr_grade(user_answer, correct_answer))
-
- def test_correct_answer_ae_convert_to_p_but_input_not_in_p(self):
- correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "test", "e0": "H", "e1": "H", "e2": "(ep)", "e3": "(ep)"})
- user_answer = vsepr_parse_user_answer(u'{"geometry": "AX6E0","atoms":{"c0": "Br","a0": "test","a1": "(ep)","e10": "H","e11": "(ep)","e20": "H","e21": "(ep)"}}')
- self.assertFalse(vsepr_grade(user_answer, correct_answer, convert_to_peripheral=True))
-
- def test_correct_answer_ae_convert_to_p(self):
- correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "p0": "(ep)", "p1": "test", "p2": "H", "p3": "H", "p4": "(ep)", "p6": "(ep)"})
- user_answer = vsepr_parse_user_answer(u'{"geometry": "AX6E0","atoms":{"c0": "Br","a0": "test","a1": "(ep)","e10": "H","e11": "(ep)","e20": "H","e21": "(ep)"}}')
- self.assertTrue(vsepr_grade(user_answer, correct_answer, convert_to_peripheral=True))
-
- def test_correct_answer_e1e2_in_a(self):
- correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "(ep)", "e10": "H", "e11": "H", "e20": "H", "e21": "H"})
- user_answer = vsepr_parse_user_answer(u'{"geometry": "AX6E0","atoms":{"c0": "Br","a0": "(ep)","a1": "(ep)","e10": "H","e11": "H","e20": "H","e21": "H"}}')
- self.assertTrue(vsepr_grade(user_answer, correct_answer))
-
- def test_correct_answer_e1e2_in_e1(self):
- correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "(ep)", "e10": "H", "e11": "H", "e20": "H", "e21": "H"})
- user_answer = vsepr_parse_user_answer(u'{"geometry": "AX6E0","atoms":{"c0": "Br","a0": "H","a1": "H","e10": "(ep)","e11": "(ep)","e20": "H","e21": "H"}}')
- self.assertTrue(vsepr_grade(user_answer, correct_answer))
-
- def test_correct_answer_e1e2_in_e2(self):
- correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "(ep)", "e10": "H", "e11": "H", "e20": "H", "e21": "H"})
- user_answer = vsepr_parse_user_answer(u'{"geometry": "AX6E0","atoms":{"c0": "Br","a0": "H","a1": "H","e10": "H","e11": "H","e20": "(ep)","e21": "(ep)"}}')
- self.assertTrue(vsepr_grade(user_answer, correct_answer))
-
- def test_incorrect_answer_e1e2(self):
- correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "(ep)", "e10": "H", "e11": "H", "e20": "H", "e21": "H"})
- user_answer = vsepr_parse_user_answer(u'{"geometry": "AX6E0","atoms":{"c0": "Br","a0": "H","a1": "H","e10": "(ep)","e11": "H","e20": "H","e21": "(ep)"}}')
- self.assertFalse(vsepr_grade(user_answer, correct_answer))
-
- def test_incorrect_c0(self):
- correct_answer = vsepr_build_correct_answer(geometry="AX6E0", atoms={"c0": "Br", "a0": "(ep)", "a1": "test", "e0": "H", "e1": "H", "e2": "H", "e3": "(ep)"})
- user_answer = vsepr_parse_user_answer(u'{"geometry": "AX6E0","atoms":{"c0": "H","a0": "test","a1": "(ep)","e0": "H","e1": "H","e2": "(ep)","e3": "H"}}')
- self.assertFalse(vsepr_grade(user_answer, correct_answer))
-
-
-def suite():
-
- testcases = [Test_Grade]
- suites = []
- for testcase in testcases:
- suites.append(unittest.TestLoader().loadTestsFromTestCase(testcase))
- return unittest.TestSuite(suites)
-
-if __name__ == "__main__":
- unittest.TextTestRunner(verbosity=2).run(suite())
diff --git a/common/lib/chem/chem/miller.py b/common/lib/chem/chem/miller.py
deleted file mode 100644
index c0b5868cf4..0000000000
--- a/common/lib/chem/chem/miller.py
+++ /dev/null
@@ -1,277 +0,0 @@
-""" Calculation of Miller indices """
-
-from __future__ import absolute_import
-import decimal
-import fractions as fr
-import json
-import math
-
-import numpy as np
-from six.moves import map
-from six.moves import range
-from functools import reduce
-
-
-def lcm(a, b):
- """
- Returns least common multiple of a, b
-
- Args:
- a, b: floats
-
- Returns:
- float
- """
- return a * b / fr.gcd(a, b)
-
-
-def segment_to_fraction(distance):
- """
- Converts lengths of which the plane cuts the axes to fraction.
-
- Tries convert distance to closest nicest fraction with denominator less or
- equal than 10. It is
- purely for simplicity and clearance of learning purposes. Jenny: 'In typical
- courses students usually do not encounter indices any higher than 6'.
-
- If distance is not a number (numpy nan), it means that plane is parallel to
- axis or contains it. Inverted fraction to nan (nan is 1/0) = 0 / 1 is
- returned
-
- Generally (special cases):
-
- a) if distance is smaller than some constant, i.g. 0.01011,
- than fraction's denominator usually much greater than 10.
-
- b) Also, if student will set point on 0.66 -> 1/3, so it is 333 plane,
- But if he will slightly move the mouse and click on 0.65 -> it will be
- (16,15,16) plane. That's why we are doing adjustments for points coordinates,
- to the closest tick, tick + tick / 2 value. And now UI sends to server only
- values multiple to 0.05 (half of tick). Same rounding is implemented for
- unittests.
-
- But if one will want to calculate miller indices with exact coordinates and
- with nice fractions (which produce small Miller indices), he may want shift
- to new origin if segments are like S = (0.015, > 0.05, >0.05) - close to zero
- in one coordinate. He may update S to (0, >0.05, >0.05) and shift origin.
- In this way he can receive nice small fractions. Also there is can be
- degenerated case when S = (0.015, 0.012, >0.05) - if update S to (0, 0, >0.05) -
- it is a line. This case should be considered separately. Small nice Miller
- numbers and possibility to create very small segments can not be implemented
- at same time).
-
-
- Args:
- distance: float distance that plane cuts on axis, it must not be 0.
- Distance is multiple of 0.05.
-
- Returns:
- Inverted fraction.
- 0 / 1 if distance is nan
-
- """
- if np.isnan(distance):
- return fr.Fraction(0, 1)
- else:
- fract = fr.Fraction(distance).limit_denominator(10)
- return fr.Fraction(fract.denominator, fract.numerator)
-
-
-def sub_miller(segments):
- '''
- Calculates Miller indices from segments.
-
- Algorithm:
-
- 1. Obtain inverted fraction from segments
-
- 2. Find common denominator of inverted fractions
-
- 3. Lead fractions to common denominator and throws denominator away.
-
- 4. Return obtained values.
-
- Args:
- List of 3 floats, meaning distances that plane cuts on x, y, z axes.
- Any float not equals zero, it means that plane does not intersect origin,
- i. e. shift of origin has already been done.
-
- Returns:
- String that represents Miller indices, e.g: (-6,3,-6) or (2,2,2)
- '''
- fracts = [segment_to_fraction(segment) for segment in segments]
- common_denominator = reduce(lcm, [fract.denominator for fract in fracts])
- miller_indices = ([
- fract.numerator * math.fabs(common_denominator) / fract.denominator
- for fract in fracts
- ])
- return'(' + ','.join(map(str, list(map(decimal.Decimal, miller_indices)))) + ')'
-
-
-def miller(points):
- """
- Calculates Miller indices from points.
-
- Algorithm:
-
- 1. Calculate normal vector to a plane that goes trough all points.
-
- 2. Set origin.
-
- 3. Create Cartesian coordinate system (Ccs).
-
- 4. Find the lengths of segments of which the plane cuts the axes. Equation
- of a line for axes: Origin + (Coordinate_vector - Origin) * parameter.
-
- 5. If plane goes trough Origin:
-
- a) Find new random origin: find unit cube vertex, not crossed by a plane.
-
- b) Repeat 2-4.
-
- c) Fix signs of segments after Origin shift. This means to consider
- original directions of axes. I.g.: Origin was 0,0,0 and became
- new_origin. If new_origin has same Y coordinate as Origin, then segment
- does not change its sign. But if new_origin has another Y coordinate than
- origin (was 0, became 1), than segment has to change its sign (it now
- lies on negative side of Y axis). New Origin 0 value of X or Y or Z
- coordinate means that segment does not change sign, 1 value -> does
- change. So new sign is (1 - 2 * new_origin): 0 -> 1, 1 -> -1
-
- 6. Run function that calculates miller indices from segments.
-
- Args:
- List of points. Each point is list of float coordinates. Order of
- coordinates in point's list: x, y, z. Points are different!
-
- Returns:
- String that represents Miller indices, e.g: (-6,3,-6) or (2,2,2)
- """
-
- N = np.cross(points[1] - points[0], points[2] - points[0])
- O = np.array([0, 0, 0])
- P = points[0] # point of plane
- Ccs = list(map(np.array, [[1.0, 0, 0], [0, 1.0, 0], [0, 0, 1.0]]))
- segments = ([
- np.dot(P - O, N) / np.dot(ort, N) if np.dot(ort, N) != 0
- else np.nan for ort in Ccs
- ])
- if any(x == 0 for x in segments): # Plane goes through origin.
- vertices = [
- # top:
- np.array([1.0, 1.0, 1.0]),
- np.array([0.0, 0.0, 1.0]),
- np.array([1.0, 0.0, 1.0]),
- np.array([0.0, 1.0, 1.0]),
- # bottom, except 0,0,0:
- np.array([1.0, 0.0, 0.0]),
- np.array([0.0, 1.0, 0.0]),
- np.array([1.0, 1.0, 1.0]),
- ]
- for vertex in vertices:
- if np.dot(vertex - O, N) != 0: # vertex not in plane
- new_origin = vertex
- break
- # obtain new axes with center in new origin
- X = np.array([1 - new_origin[0], new_origin[1], new_origin[2]])
- Y = np.array([new_origin[0], 1 - new_origin[1], new_origin[2]])
- Z = np.array([new_origin[0], new_origin[1], 1 - new_origin[2]])
- new_Ccs = [X - new_origin, Y - new_origin, Z - new_origin]
- segments = ([np.dot(P - new_origin, N) / np.dot(ort, N) if
- np.dot(ort, N) != 0 else np.nan for ort in new_Ccs])
- # fix signs of indices: 0 -> 1, 1 -> -1 (
- segments = (1 - 2 * new_origin) * segments
-
- return sub_miller(segments)
-
-
-def grade(user_input, correct_answer):
- '''
- Grade crystallography problem.
-
- Returns true if lattices are the same and Miller indices are same or minus
- same. E.g. (2,2,2) = (2, 2, 2) or (-2, -2, -2). Because sign depends only
- on student's selection of origin.
-
- Args:
- user_input, correct_answer: json. Format:
-
- user_input: {"lattice":"sc","points":[["0.77","0.00","1.00"],
- ["0.78","1.00","0.00"],["0.00","1.00","0.72"]]}
-
- correct_answer: {'miller': '(00-1)', 'lattice': 'bcc'}
-
- "lattice" is one of: "", "sc", "bcc", "fcc"
-
- Returns:
- True or false.
- '''
- def negative(m):
- """
- Change sign of Miller indices.
-
- Args:
- m: string with meaning of Miller indices. E.g.:
- (-6,3,-6) -> (6, -3, 6)
-
- Returns:
- String with changed signs.
- """
- output = ''
- i = 1
- while i in range(1, len(m) - 1):
- if m[i] in (',', ' '):
- output += m[i]
- elif m[i] not in ('-', '0'):
- output += '-' + m[i]
- elif m[i] == '0':
- output += m[i]
- else:
- i += 1
- output += m[i]
- i += 1
- return '(' + output + ')'
-
- def round0_25(point):
- """
- Rounds point coordinates to closest 0.5 value.
-
- Args:
- point: list of float coordinates. Order of coordinates: x, y, z.
-
- Returns:
- list of coordinates rounded to closes 0.5 value
- """
- rounded_points = []
- for coord in point:
- base = math.floor(coord * 10)
- fractional_part = (coord * 10 - base)
- aliquot0_25 = math.floor(fractional_part / 0.25)
- if aliquot0_25 == 0.0:
- rounded_points.append(base / 10)
- if aliquot0_25 in (1.0, 2.0):
- rounded_points.append(base / 10 + 0.05)
- if aliquot0_25 == 3.0:
- rounded_points.append(base / 10 + 0.1)
- return rounded_points
-
- user_answer = json.loads(user_input)
-
- if user_answer['lattice'] != correct_answer['lattice']:
- return False
-
- points = [list(map(float, p)) for p in user_answer['points']]
-
- if len(points) < 3:
- return False
-
- # round point to closes 0.05 value
- points = [round0_25(point) for point in points]
-
- points = [np.array(point) for point in points]
- # print miller(points), (correct_answer['miller'].replace(' ', ''),
- # negative(correct_answer['miller']).replace(' ', ''))
- if miller(points) in (correct_answer['miller'].replace(' ', ''), negative(correct_answer['miller']).replace(' ', '')):
- return True
-
- return False
diff --git a/common/lib/chem/chem/tests.py b/common/lib/chem/chem/tests.py
deleted file mode 100644
index 848859e6ff..0000000000
--- a/common/lib/chem/chem/tests.py
+++ /dev/null
@@ -1,504 +0,0 @@
-from __future__ import absolute_import, print_function
-import codecs
-import unittest
-from fractions import Fraction
-
-import chem.miller
-
-from .chemcalc import chemical_equations_equal, compare_chemical_expression, divide_chemical_expression, render_to_html
-
-LOCAL_DEBUG = None
-
-
-def log(msg, output_type=None):
- """Logging function for tests"""
- if LOCAL_DEBUG:
- print(msg)
- if output_type == 'html':
- f.write(msg + '\n
\n')
-
-
-class Test_Compare_Equations(unittest.TestCase):
- def test_simple_equation(self):
- self.assertTrue(chemical_equations_equal('H2 + O2 -> H2O2',
- 'O2 + H2 -> H2O2'))
- # left sides don't match
- self.assertFalse(chemical_equations_equal('H2 + O2 -> H2O2',
- 'O2 + 2H2 -> H2O2'))
- # right sides don't match
- self.assertFalse(chemical_equations_equal('H2 + O2 -> H2O2',
- 'O2 + H2 -> H2O'))
-
- # factors don't match
- self.assertFalse(chemical_equations_equal('H2 + O2 -> H2O2',
- 'O2 + H2 -> 2H2O2'))
-
- def test_different_factor(self):
- self.assertTrue(chemical_equations_equal('H2 + O2 -> H2O2',
- '2O2 + 2H2 -> 2H2O2'))
-
- self.assertFalse(
- chemical_equations_equal(
- '2H2 + O2 -> H2O2',
- '2O2 + 2H2 -> 2H2O2',
- )
- )
-
- def test_different_arrows(self):
- self.assertTrue(chemical_equations_equal('H2 + O2 -> H2O2',
- '2O2 + 2H2 -> 2H2O2'))
-
- self.assertFalse(chemical_equations_equal('H2 + O2 -> H2O2',
- 'O2 + H2 <-> 2H2O2'))
-
- def test_exact_match(self):
- self.assertTrue(chemical_equations_equal('H2 + O2 -> H2O2',
- '2O2 + 2H2 -> 2H2O2'))
-
- self.assertFalse(
- chemical_equations_equal(
- 'H2 + O2 -> H2O2',
- '2O2 + 2H2 -> 2H2O2',
- exact=True,
- )
- )
-
- # order still doesn't matter
- self.assertTrue(chemical_equations_equal('H2 + O2 -> H2O2',
- 'O2 + H2 -> H2O2', exact=True))
-
- def test_syntax_errors(self):
- self.assertFalse(chemical_equations_equal('H2 + O2 a-> H2O2',
- '2O2 + 2H2 -> 2H2O2'))
-
- self.assertFalse(chemical_equations_equal('H2O( -> H2O2',
- 'H2O -> H2O2'))
-
- self.assertFalse(chemical_equations_equal('H2 + O2 ==> H2O2', # strange arrow
- '2O2 + 2H2 -> 2H2O2'))
-
-
-class Test_Compare_Expressions(unittest.TestCase):
-
- def test_compare_incorrect_order_of_atoms_in_molecule(self):
- self.assertFalse(compare_chemical_expression("H2O + CO2", "O2C + OH2"))
-
- def test_compare_same_order_no_phases_no_factors_no_ions(self):
- self.assertTrue(compare_chemical_expression("H2O + CO2", "CO2+H2O"))
-
- def test_compare_different_order_no_phases_no_factors_no_ions(self):
- self.assertTrue(compare_chemical_expression("H2O + CO2", "CO2 + H2O"))
-
- def test_compare_different_order_three_multimolecule(self):
- self.assertTrue(compare_chemical_expression("H2O + Fe(OH)3 + CO2", "CO2 + H2O + Fe(OH)3"))
-
- def test_compare_same_factors(self):
- self.assertTrue(compare_chemical_expression("3H2O + 2CO2", "2CO2 + 3H2O "))
-
- def test_compare_different_factors(self):
- self.assertFalse(compare_chemical_expression("2H2O + 3CO2", "2CO2 + 3H2O "))
-
- def test_compare_correct_ions(self):
- self.assertTrue(compare_chemical_expression("H^+ + OH^-", " OH^- + H^+ "))
-
- def test_compare_wrong_ions(self):
- self.assertFalse(compare_chemical_expression("H^+ + OH^-", " OH^- + H^- "))
-
- def test_compare_parent_groups_ions(self):
- self.assertTrue(compare_chemical_expression("Fe(OH)^2- + (OH)^-", " (OH)^- + Fe(OH)^2- "))
-
- def test_compare_correct_factors_ions_and_one(self):
- self.assertTrue(compare_chemical_expression("3H^+ + 2OH^-", " 2OH^- + 3H^+ "))
-
- def test_compare_wrong_factors_ions(self):
- self.assertFalse(compare_chemical_expression("2H^+ + 3OH^-", " 2OH^- + 3H^+ "))
-
- def test_compare_float_factors(self):
- self.assertTrue(compare_chemical_expression("7/2H^+ + 3/5OH^-", " 3/5OH^- + 7/2H^+ "))
-
- # Phases tests
- def test_compare_phases_ignored(self):
- self.assertTrue(compare_chemical_expression(
- "H2O(s) + CO2", "H2O+CO2", ignore_state=True))
-
- def test_compare_phases_not_ignored_explicitly(self):
- self.assertFalse(compare_chemical_expression(
- "H2O(s) + CO2", "H2O+CO2", ignore_state=False))
-
- def test_compare_phases_not_ignored(self): # same as previous
- self.assertFalse(compare_chemical_expression(
- "H2O(s) + CO2", "H2O+CO2"))
-
- # all in one cases
- def test_complex_additivity(self):
- self.assertTrue(compare_chemical_expression(
- "5(H1H212)^70010- + 2H20 + 7/2HCl + H2O",
- "7/2HCl + 2H20 + H2O + 5(H1H212)^70010-"))
-
- def test_complex_additivity_wrong(self):
- self.assertFalse(compare_chemical_expression(
- "5(H1H212)^70010- + 2H20 + 7/2HCl + H2O",
- "2H20 + 7/2HCl + H2O + 5(H1H212)^70011-"))
-
- def test_complex_all_grammar(self):
- self.assertTrue(compare_chemical_expression(
- "5[Ni(NH3)4]^2+ + 5/2SO4^2-",
- "5/2SO4^2- + 5[Ni(NH3)4]^2+"))
-
- # special cases
-
- def test_compare_one_superscript_explicitly_set(self):
- self.assertTrue(compare_chemical_expression("H^+ + OH^1-", " OH^- + H^+ "))
-
- def test_compare_equal_factors_differently_set(self):
- self.assertTrue(compare_chemical_expression("6/2H^+ + OH^-", " OH^- + 3H^+ "))
-
- def test_compare_one_subscript_explicitly_set(self):
- self.assertFalse(compare_chemical_expression("H2 + CO2", "H2 + C102"))
-
-
-class Test_Divide_Expressions(unittest.TestCase):
- ''' as compare_ use divide_,
- tests here must consider different
- division (not equality) cases '''
-
- def test_divide_by_zero(self):
- self.assertFalse(divide_chemical_expression(
- "0H2O", "H2O"))
-
- def test_divide_wrong_factors(self):
- self.assertFalse(divide_chemical_expression(
- "5(H1H212)^70010- + 10H2O", "5H2O + 10(H1H212)^70010-"))
-
- def test_divide_right(self):
- self.assertEqual(divide_chemical_expression(
- "5(H1H212)^70010- + 10H2O", "10H2O + 5(H1H212)^70010-"), 1)
-
- def test_divide_wrong_reagents(self):
- self.assertFalse(divide_chemical_expression(
- "H2O + CO2", "CO2"))
-
- def test_divide_right_simple(self):
- self.assertEqual(divide_chemical_expression(
- "H2O + CO2", "H2O+CO2"), 1)
-
- def test_divide_right_phases(self):
- self.assertEqual(divide_chemical_expression(
- "H2O(s) + CO2", "2H2O(s)+2CO2"), Fraction(1, 2))
-
- def test_divide_right_phases_other_order(self):
- self.assertEqual(divide_chemical_expression(
- "2H2O(s) + 2CO2", "H2O(s)+CO2"), 2)
-
- def test_divide_wrong_phases(self):
- self.assertFalse(divide_chemical_expression(
- "H2O(s) + CO2", "2H2O+2CO2(s)"))
-
- def test_divide_wrong_phases_but_phases_ignored(self):
- self.assertEqual(divide_chemical_expression(
- "H2O(s) + CO2", "2H2O+2CO2(s)", ignore_state=True), Fraction(1, 2))
-
- def test_divide_order(self):
- self.assertEqual(divide_chemical_expression(
- "2CO2 + H2O", "2H2O+4CO2"), Fraction(1, 2))
-
- def test_divide_fract_to_int(self):
- self.assertEqual(divide_chemical_expression(
- "3/2CO2 + H2O", "2H2O+3CO2"), Fraction(1, 2))
-
- def test_divide_fract_to_frac(self):
- self.assertEqual(divide_chemical_expression(
- "3/4CO2 + H2O", "2H2O+9/6CO2"), Fraction(1, 2))
-
- def test_divide_fract_to_frac_wrog(self):
- self.assertFalse(divide_chemical_expression(
- "6/2CO2 + H2O", "2H2O+9/6CO2"), 2)
-
-
-class Test_Render_Equations(unittest.TestCase):
- """
- Tests to validate the HTML rendering of plaintext (input) equations
- """
- # pylint: disable=line-too-long
- def test_render1(self):
- test_string = "H2O + CO2"
- out = render_to_html(test_string)
- correct = u'H2O+CO2'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_render_uncorrect_reaction(self):
- test_string = "O2C + OH2"
- out = render_to_html(test_string)
- correct = u'O2C+OH2'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_render2(self):
- test_string = "CO2 + H2O + Fe(OH)3"
- out = render_to_html(test_string)
- correct = u'CO2+H2O+Fe(OH)3'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_render3(self):
- test_string = "3H2O + 2CO2"
- out = render_to_html(test_string)
- correct = u'3H2O+2CO2'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_render4(self):
- test_string = "H^+ + OH^-"
- out = render_to_html(test_string)
- correct = u'H++OH-'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_render5(self):
- test_string = "Fe(OH)^2- + (OH)^-"
- out = render_to_html(test_string)
- correct = u'Fe(OH)2-+(OH)-'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_render6(self):
- test_string = "7/2H^+ + 3/5OH^-"
- out = render_to_html(test_string)
- correct = u'7⁄2H++3⁄5OH-'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_render7(self):
- test_string = "5(H1H212)^70010- + 2H2O + 7/2HCl + H2O"
- out = render_to_html(test_string)
- correct = u'5(H1H212)70010-+2H2O+7⁄2HCl+H2O'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_render8(self):
- test_string = "H2O(s) + CO2"
- out = render_to_html(test_string)
- correct = u'H2O(s)+CO2'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_render9(self):
- test_string = "5[Ni(NH3)4]^2+ + 5/2SO4^2-"
- out = render_to_html(test_string)
- correct = u'5[Ni(NH3)4]2++5⁄2SO42-'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_render_error(self):
- test_string = "5.2H20"
- out = render_to_html(test_string)
- correct = u'5.2H20'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_render_simple_round_brackets(self):
- test_string = "(Ar)"
- out = render_to_html(test_string)
- correct = u'(Ar)'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_render_simple_square_brackets(self):
- test_string = "[Ar]"
- out = render_to_html(test_string)
- correct = u'[Ar]'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_render_eq1(self):
- test_string = "H^+ + OH^- -> H2O"
- out = render_to_html(test_string)
- correct = u'H++OH-\u2192H2O'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_render_eq2(self):
- test_string = "H^+ + OH^- <-> H2O"
- out = render_to_html(test_string)
- correct = u'H++OH-\u2194H2O'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_render_eq3(self):
- test_string = "H^+ + OH^- <= H2O" # unsupported arrow
- out = render_to_html(test_string)
- correct = u'H^+ + OH^- <= H2O'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_render_eq4(self):
- test_string = "[H^+] + OH^- <-> (H2O)" # with brackets
- out = render_to_html(test_string)
- correct = u'[H+]+OH-\u2194(H2O)'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
- def test_escaping(self):
- """
- Tests that invalid input is escaped.
- """
- test_string = ""
- out = render_to_html(test_string)
- correct = u'<script>f()</script>'
- log(out + ' ------- ' + correct, 'html')
- self.assertEqual(out, correct)
-
-
-class Test_Crystallography_Miller(unittest.TestCase):
- """Tests for crystallography grade function."""
- # pylint: disable=line-too-long
- def test_empty_points(self):
- user_input = '{"lattice": "bcc", "points": []}'
- self.assertFalse(chem.miller.grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'}))
-
- def test_only_one_point(self):
- user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"]]}'
- self.assertFalse(chem.miller.grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'}))
-
- def test_only_two_points(self):
- user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"], ["0.00", "0.50", "0.00"]]}'
- self.assertFalse(chem.miller.grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'}))
-
- def test_1(self):
- user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"], ["0.00", "0.50", "0.00"], ["0.00", "0.00", "0.50"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'}))
-
- def test_2(self):
- user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.00"], ["0.00", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(1,1,1)', 'lattice': 'bcc'}))
-
- def test_3(self):
- user_input = '{"lattice": "bcc", "points": [["1.00", "0.50", "1.00"], ["1.00", "1.00", "0.50"], ["0.50", "1.00", "1.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(2,2,2)', 'lattice': 'bcc'}))
-
- def test_4(self):
- user_input = '{"lattice": "bcc", "points": [["0.33", "1.00", "0.00"], ["0.00", "0.664", "0.00"], ["0.00", "1.00", "0.33"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(-3, 3, -3)', 'lattice': 'bcc'}))
-
- def test_5(self):
- """ return true only in case points coordinates are exact.
- But if they transform to closest 0.05 value it is not true"""
- user_input = '{"lattice": "bcc", "points": [["0.33", "1.00", "0.00"], ["0.00", "0.33", "0.00"], ["0.00", "1.00", "0.33"]]}'
- self.assertFalse(chem.miller.grade(user_input, {'miller': '(-6,3,-6)', 'lattice': 'bcc'}))
-
- def test_6(self):
- user_input = '{"lattice": "bcc", "points": [["0.00", "0.25", "0.00"], ["0.25", "0.00", "0.00"], ["0.00", "0.00", "0.25"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(4,4,4)', 'lattice': 'bcc'}))
-
- def test_7(self): # goes throug origin
- user_input = '{"lattice": "bcc", "points": [["0.00", "1.00", "0.00"], ["1.00", "0.00", "0.00"], ["0.50", "1.00", "0.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(0,0,-1)', 'lattice': 'bcc'}))
-
- def test_8(self):
- user_input = '{"lattice": "bcc", "points": [["0.00", "1.00", "0.50"], ["1.00", "0.00", "0.50"], ["0.50", "1.00", "0.50"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(0,0,2)', 'lattice': 'bcc'}))
-
- def test_9(self):
- user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "1.00"], ["0.00", "1.00", "1.00"], ["1.00", "0.00", "0.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(1,1,0)', 'lattice': 'bcc'}))
-
- def test_10(self):
- user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "1.00"], ["0.00", "0.00", "0.00"], ["0.00", "1.00", "1.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(1,1,-1)', 'lattice': 'bcc'}))
-
- def test_11(self):
- user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.50"], ["1.00", "1.00", "0.00"], ["0.00", "1.00", "0.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(0,1,2)', 'lattice': 'bcc'}))
-
- def test_12(self):
- user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.50"], ["0.00", "0.00", "0.50"], ["1.00", "1.00", "1.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(0,1,-2)', 'lattice': 'bcc'}))
-
- def test_13(self):
- user_input = '{"lattice": "bcc", "points": [["0.50", "0.00", "0.00"], ["0.50", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(2,0,1)', 'lattice': 'bcc'}))
-
- def test_14(self):
- user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["0.00", "0.00", "1.00"], ["0.50", "1.00", "0.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(2,-1,0)', 'lattice': 'bcc'}))
-
- def test_15(self):
- user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "1.00", "1.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(1,-1,1)', 'lattice': 'bcc'}))
-
- def test_16(self):
- user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.00"], ["0.00", "1.00", "0.00"], ["1.00", "1.00", "1.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(1,1,-1)', 'lattice': 'bcc'}))
-
- def test_17(self):
- user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "0.00", "1.00"], ["1.00", "1.00", "0.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(-1,1,1)', 'lattice': 'bcc'}))
-
- def test_18(self):
- user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "1.00", "1.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(1,-1,1)', 'lattice': 'bcc'}))
-
- def test_19(self):
- user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(-1,1,0)', 'lattice': 'bcc'}))
-
- def test_20(self):
- user_input = '{"lattice": "bcc", "points": [["1.00", "0.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(1,0,1)', 'lattice': 'bcc'}))
-
- def test_21(self):
- user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["0.00", "1.00", "0.00"], ["1.00", "0.00", "1.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(-1,0,1)', 'lattice': 'bcc'}))
-
- def test_22(self):
- user_input = '{"lattice": "bcc", "points": [["0.00", "1.00", "0.00"], ["1.00", "1.00", "0.00"], ["0.00", "0.00", "1.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(0,1,1)', 'lattice': 'bcc'}))
-
- def test_23(self):
- user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "0.00", "0.00"], ["1.00", "1.00", "1.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(0,-1,1)', 'lattice': 'bcc'}))
-
- def test_24(self):
- user_input = '{"lattice": "bcc", "points": [["0.66", "0.00", "0.00"], ["0.00", "0.66", "0.00"], ["0.00", "0.00", "0.66"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(3,3,3)', 'lattice': 'bcc'}))
-
- def test_25(self):
- user_input = u'{"lattice":"","points":[["0.00","0.00","0.01"],["1.00","1.00","0.01"],["0.00","1.00","1.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(1,-1,1)', 'lattice': ''}))
-
- def test_26(self):
- user_input = u'{"lattice":"","points":[["0.00","0.01","0.00"],["1.00","0.00","0.00"],["0.00","0.00","1.00"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(0,-1,0)', 'lattice': ''}))
-
- def test_27(self):
- """ rounding to 0.35"""
- user_input = u'{"lattice":"","points":[["0.33","0.00","0.00"],["0.00","0.33","0.00"],["0.00","0.00","0.33"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(3,3,3)', 'lattice': ''}))
-
- def test_28(self):
- """ rounding to 0.30"""
- user_input = u'{"lattice":"","points":[["0.30","0.00","0.00"],["0.00","0.30","0.00"],["0.00","0.00","0.30"]]}'
- self.assertTrue(chem.miller.grade(user_input, {'miller': '(10,10,10)', 'lattice': ''}))
-
- def test_wrong_lattice(self):
- user_input = '{"lattice": "bcc", "points": [["0.00", "0.00", "0.00"], ["1.00", "0.00", "0.00"], ["1.00", "1.00", "1.00"]]}'
- self.assertFalse(chem.miller.grade(user_input, {'miller': '(3,3,3)', 'lattice': 'fcc'}))
-
-
-def suite():
-
- testcases = [Test_Compare_Expressions,
- Test_Divide_Expressions,
- Test_Render_Equations,
- Test_Crystallography_Miller]
- suites = []
- for testcase in testcases:
- suites.append(unittest.TestLoader().loadTestsFromTestCase(testcase))
- return unittest.TestSuite(suites)
-
-if __name__ == "__main__":
- LOCAL_DEBUG = True
- with codecs.open('render.html', 'w', encoding='utf-8') as f:
- unittest.TextTestRunner(verbosity=2).run(suite())
- # open render.html to look at rendered equations
diff --git a/common/lib/chem/setup.py b/common/lib/chem/setup.py
deleted file mode 100644
index d297ce9217..0000000000
--- a/common/lib/chem/setup.py
+++ /dev/null
@@ -1,15 +0,0 @@
-from __future__ import absolute_import
-from setuptools import setup
-
-setup(
- name="chem",
- version="0.3.0",
- packages=["chem"],
- install_requires=[
- "pyparsing==2.2.0",
- "numpy",
- "scipy",
- "nltk",
- "markupsafe", # Should be replaced by other utilities. See LEARNER-5853 for more details.
- ],
-)
diff --git a/docs/conf.py b/docs/conf.py
index a3c8bcd0e3..685d68818b 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -27,7 +27,6 @@ sys.path.append(root / "docs")
sys.path.append(root / "cms/djangoapps")
sys.path.append(root / "common/djangoapps")
sys.path.append(root / "common/lib/capa")
-sys.path.append(root / "common/lib/chem")
sys.path.append(root / "common/lib/safe_lxml")
sys.path.append(root / "common/lib/symmath")
sys.path.append(root / "common/lib/xmodule")
@@ -236,7 +235,6 @@ autodoc_mock_imports = [
modules = {
'cms': 'cms',
'common/lib/capa/capa': 'common/lib/capa',
- 'common/lib/chem/chem': 'common/lib/chem',
'common/lib/safe_lxml/safe_lxml': 'common/lib/safe_lxml',
'common/lib/symmath/symmath': 'common/lib/symmath',
'common/lib/xmodule/xmodule': 'common/lib/xmodule',
diff --git a/docs/docstrings/common_lib.rst b/docs/docstrings/common_lib.rst
index c1cc729fab..419063bd5b 100644
--- a/docs/docstrings/common_lib.rst
+++ b/docs/docstrings/common_lib.rst
@@ -9,7 +9,6 @@ out from edx-platform into separate packages at some point.
:maxdepth: 2
common/lib/capa/modules
- common/lib/chem/modules
common/lib/safe_lxml/modules
common/lib/symmath/modules
common/lib/xmodule/modules