Data structures¶
MCounter¶
- class simu.MCounter¶
This is a slight extention of the
Collections.Counterclass to also allow multiplication with scalar numbers:>>> a = MCounter({"a": 1}) >>> b = MCounter({"b": 1}) >>> a + 2.5 * b MCounter({'b': 2.5, 'a': 1})
Note that we stretch the use of MCounter to allow floats. Therefore the
elementsmethod is removed as a compromise in design.- elements()¶
Iterator over elements repeating each as many times as its count.
>>> c = Counter('ABCABC') >>> sorted(c.elements()) ['A', 'A', 'B', 'B', 'C', 'C']
Knuth’s example for prime factors of 1836: 2**2 * 3**3 * 17**1
>>> import math >>> prime_factors = Counter({2: 2, 3: 3, 17: 1}) >>> math.prod(prime_factors.elements()) 1836
Note, if an element’s count has been set to zero or is a negative number, elements() will ignore it.
QuantityDict¶
- class simu.QuantityDict¶
Many properties on process modelling level are vectorial. This includes any species-specific properties, such as for instance mole fractions, chemical potentials or partial enthalpy. By keeping such data in instances of this class, they can always be accessed as a dictionary, using the bracket-operator (
__get_item__).Additionally, this class supports most arithmetic operations, such as
+, -, *, /, **- all of them interpreted element-wise. As two instances can have deviating keys (mostly species), there are some rules:missing elements are assumed as zero, and structural zeros are omitted, i.e.
>>> a = QuantityDict({ ... "A": Quantity("1 m"), ... "B": Quantity("50 cm")}) >>> b = QuantityDict({ ... "B": Quantity("1 m"), ... "C": Quantity("50 cm")}) >>> y = a + b >>> for key, value in y.items(): print(f"{key}: {value:~}") A: 1 m B: 150.0 cm C: 50 cm
>>> y = a * b >>> for key, value in y.items(): print(f"{key}: {value:~}") B: 50 cm * m
A missing denominator element in division directly raises
ZeroDivisionError>>> y = a / b Traceback (most recent call last): ... ZeroDivisionError: Missing denominator element in QuantityDict division
Operations can be mixed with scalar Quantities
>>> y = a["A"] * b >>> for key, value in y.items(): print(f"{key}: {value:~}") B: 1 m ** 2 C: 50 cm * m
floats as second operands in binary operators act as dimensionless quantities
>>> y = 3 * a >>> for key, value in y.items(): print(f"{key}: {value:~}") A: 3 m B: 150 cm
>>> y = 3 + a Traceback (most recent call last): ... pint.errors.DimensionalityError: ...
- classmethod from_vector_quantity( ) Self¶
As the magnitude of a
Quantitycan be a container itself, this convenience factory method combines such a vector quantity with a set of given keys into aQuantityDictobject>>> raw = Quantity([1, 2, 3], "m") >>> dic = QuantityDict.from_vector_quantity(raw, ["A", "B", "C"]) >>> for name, value in dic.items(): ... print(f"{name}: {value:~}") A: 1 m B: 2 m C: 3 m
ParameterDictionary¶
- class simu.ParameterDictionary¶
This class is a nested dictionary of SymbolQuantities to represent parameters with functionality to be populated using the
register_*methods.- class SparseArray¶
This helper class represents a nexted dictionary that contains an arbitrary level of nested keys to address a value that is represented by a quantity.
- __init__(order)¶
- register_scalar(key: str, unit: str)¶
Create a scalar quantity and add the structure to the dictionary. The given unit is converted to base units before being applied. Calling the method returns the created quantity
>>> pdict = ParameterDictionary() >>> print(pdict.register_scalar("speed", "cm/h")) speed meter / second
In this output,
speedis the name of thecasadi.SXnode representing the magnitude of returned Quantity. The dictionary then contains the following entry:>>> print(pdict) {'speed': <Quantity(speed, 'meter / second')>}
- register_vector( ) Quantity¶
Create a quantity vector with symbols and add the structure to the dictionary. The given unit is converted to base units before being applied. Calling the method returns the created quantity
>>> pdict = ParameterDictionary() >>> print(pdict.register_vector("velocity", "xyz", "knot")) [velocity.x, velocity.y, velocity.z] meter / second
The dictionary then contains the following entries:
>>> from pprint import pprint >>> pprint(pdict) {'velocity': {'x': <Quantity(velocity.x, 'meter / second')>, 'y': <Quantity(velocity.y, 'meter / second')>, 'z': <Quantity(velocity.z, 'meter / second')>}}
- register_sparse_matrix( ) NestedMap[Quantity]¶
Create a sparse matrix quantity and add the structure to the dictionary. The given unit is converted to base units before being applied.
>>> pdict = ParameterDictionary() >>> binaries = [("H2O", "CO2"), ("H2O", "CH4")] >>> from pprint import pprint >>> pprint(pdict.register_sparse_matrix("K_ij", binaries, "K")) {'H2O': {'CH4': <Quantity(K_ij.H2O.CH4, 'kelvin')>, 'CO2': <Quantity(K_ij.H2O.CO2, 'kelvin')>}}
After above call, the dictionary contains the following entries:
>>> from pprint import pprint >>> pprint(pdict) {'K_ij': {'H2O': {'CH4': <Quantity(K_ij.H2O.CH4, 'kelvin')>, 'CO2': <Quantity(K_ij.H2O.CO2, 'kelvin')>}}}
- register_sparse_3d( ) NestedMap[Quantity]¶
Create a sparse 3d matrix quantity and add the structure to the dictionary. The given unit is converted to base units before being applied.
>>> pdict = ParameterDictionary() >>> ternaries = [("A", "B", "C"), ("A", "C", "D")] >>> from pprint import pprint >>> pprint(pdict.register_sparse_3d("C", ternaries, "K")) {'A': {'B': {'C': <Quantity(C.A.B.C, 'kelvin')>}, 'C': {'D': <Quantity(C.A.C.D, 'kelvin')>}}}
After above call, the dictionary contains the following entries:
>>> from pprint import pprint >>> pprint(pdict) {'C': {'A': {'B': {'C': <Quantity(C.A.B.C, 'kelvin')>}, 'C': {'D': <Quantity(C.A.C.D, 'kelvin')>}}}}
- get_quantity(*keys)¶
Extract a quantity from the given sequence of key. Being a nested dictionary, each key from the argument list is used to navigate into the structure. The value of the most inner addressed key is returned. For normal usage, this should be of type
Quantity.
- get_vector_quantity(*keys)¶
Extract a vector quantity from the given sequence of keys. The method extracts the values of the structure below the sequence of argument keys, and concatenates them as a single vector property.
Formula parser¶
- class simu.core.utilities.molecules.FormulaParser¶
This class implements the functionality to analyse chemical sum formulae. The atomic composition and molecular weight can be obtained.
- __init__()¶
- property atomic_weights: Mapping[str, Quantity]¶
Return a dictionary with all elements mapped to their molecular weight
- parse(formula: str) MCounter¶
Parse a formula and return a Counter object with the atomic symbols as keys and the number of occurances as values:
- Plain formulae:
>>> parser = FormulaParser() >>> parser.parse("H3PO4") MCounter({'O': 4, 'H': 3, 'P': 1}) >>> parser.parse("KMnO4") MCounter({'O': 4, 'K': 1, 'Mn': 1}) >>> parser.parse("FISH") MCounter({'F': 1, 'I': 1, 'S': 1, 'H': 1})
- With parantheses:
>>> parser.parse("(NH4)2HPO4") MCounter({'H': 9, 'O': 4, 'N': 2, 'P': 1})
- With structure:
>>> parser.parse("CH3-(CH2)3-CH=O>") MCounter({'H': 10, 'C': 5, 'O': 1}) >>> parser.parse("|N≡N|") MCounter({'N': 2}) >>> parser.parse("<O=O>") MCounter({'O': 2})
- With charge:
>>> parser.parse("SO4:2-") MCounter({'O': 4, 'S': 1})
- With a complex:
>>> parser.parse("Na(UO2)3[Zn(H2O)6](CH3CO2)9") MCounter({'H': 39, 'O': 30, 'C': 18, 'U': 3, 'Na': 1, 'Zn': 1})
- With crystal water:
>>> parser.parse("CuSO4·5H2O") MCounter({'H': 10, 'O': 9, 'Cu': 1, 'S': 1})
- With crystal water and another solvent:
>>> parser.parse("CuSO4·3H2O·2(CH3)-COOH") MCounter({'H': 14, 'O': 11, 'C': 4, 'Cu': 1, 'S': 1})