The most useless program in history -- self writing equilibrium chemical equation

Posted by abushahin on Tue, 22 Feb 2022 13:30:00 +0100

preface

A few days ago, I accidentally saw such a bce library that can balance the coefficients of chemical equations. I thought this was the matching element. I tried according to the examples I saw, and the results
The program design is becoming more and more complex, and I don't know his BCE console Exe how can a program of only 104KB size balance the chemical equation so quickly (it may be lazy, so I didn't study how to do the corresponding solution like the relationship between electron conservation and unknowns).

example

>> P+O2=P2O5
4P+5O2=2P2O5
>> H2O(g)+Fe=Fe3O4+H2
4H2O(g)+3Fe=Fe3O4+4H2
>> CO+Fe2O3=Fe+CO2
3CO+Fe2O3=2Fe+3CO2
>> C2H5OH+O2=CO2+H2O
C2H5OH+3O2=2CO2+3H2O
>> Cl2+<e->=Cl<e->
Cl2+2<e->=2Cl<e->
>> Cu+Fe<3e+>=Cu<2e+>+Fe<2e+>
Cu+2Fe<3e+>=Cu<2e+>+2Fe<2e+>
>> LiOH+H2O2+H2O=Li2O2.H2O2.3H2O
2LiOH+2H2O2+H2O=Li2O2.H2O2.3H2O
>> C{n}H{2n+2}+O2=CO2+H2O
{(n+1)^(-1)}C{n}H{2*n+2}+{(1/2)*(3*n+1)/(n+1)}O2={n/(n+1)}CO2+H2O
>> CH3(CHCH){n}CH3+Cl2=CH3(CHClCHCl){n}CH3
CH3(CHCH){n}CH3+{n}Cl2=CH3(CHClCHCl){n}CH3
>> X-<e->=X<{n}e+>
X-{n}<e->=X<{n}e+>
>> CH4;HCN;NH3;O2;H2O
2HCN+6H2O=2CH4+2NH3+3O2
>> CH4(g);HCN(g);NH3(g);O2(g);H2O(g)
2CH4(g)+2NH3(g)+3O2(g)=2HCN(g)+6H2O(g)

Running screenshot

bce-console.exe running results (pip install bce installation)

Total program running results

Required Library

import re  # Regular expression library for extracting elements and numbers
import string  # Get all white space characters and replace the extra white space characters in the chemical equation to prevent accidents
from sympy import solve, symbols  # Library for solving equations. solve is a solving function, and symbols can generate symbolic variables

step

Here, the balance problem is solved by the conservation of the quantity and type of all chemical elements on the left and right sides of the equation.

Split the left and right sides of the equation

Since other conservation rules are not considered in this paper, the chemical equations of other rules are filtered out first (only the first four examples are left at last).
The left and right here are the contents on the left and right sides of the equal sign of the chemical equation.

if '=' not in ChemicalEquations and ';' in ChemicalEquations:
    all_items = ChemicalEquations.split(';')
    pass  # The automatic total price calculation equation is not done first, but the element balance is done first
elif 'e+>' in ChemicalEquations or 'e->' in ChemicalEquations or '{' in ChemicalEquations or '.' in ChemicalEquations:
    pass  # For the time being, we will not engage in electronic < e + > / < e - > and inclusion/ {n} Calculation of chemical substances, etc
else:
    left, right = ChemicalEquations.split('=')

Split each chemical and the ± number

Chemical composition

To obtain chemical substances, we must first know what chemical element symbols are.
Of course, the chemicals here are extracted from the multiple relationship between electrons and unknowns, so they are very long.
This result is not only chemical substances, but also the ± sign after chemical substances. Although there is no electronic participation, it is generally the + sign.

1,Multiple element:\d|{[A-Za-z0-9+-]}
2,Charge element:<(?:\d|{[A-Za-z0-9+-]})?e[+-]>
3,chemical element:[A-Z][a-z]?\.?
4,Substance status:\([gls]\)  # g is gas, l is liquid and s is solid
5,chemical substances:[+-]?(<(?:\d+|{[A-Za-z0-9+-]+})?e[+-]>|(?:(?:\(?(?:[A-Z][a-z]?\.?)+(?:\d+|{[A-Za-z0-9+-]+})?\)?)+(?:<(?:\d+|{[A-Za-z0-9+-]+})?e[+-]>)?)+)(?:\([gls]\))?([+-]?)
6,[②]Substance:[+-]?(<(?:\d+|{[A-Za-z0-9+-]+})?e[+-]>|(?:[A-Za-z0-9.]+(?:\([A-Za-z0-9]+\))?(?:{[A-Za-z0-9+-]+})?(?:<(?:\d+|{[A-Za-z0-9+-]+})?e[+-]>)?)+)(?:\([gls]\))?([+-]?)

Chemical extraction

left, right = ChemicalEquations.split('=')
items = re.compile(R'[+-]?(<(?:\d+|{[A-Za-z0-9+-]+})?e[+-]>|(?:(?:\(?(?:[A-Z][a-z]?\.?)+(?:\d+|{[A-Za-z0-9+-]+})?\)?)+(?:<(?:\d+|{[A-Za-z0-9+-]+})?e[+-]>)?)+)(?:\([gls]\))?([+-]?)')
left_items, right_items = items.findall(left), items.findall(right)

Here, all chemicals and operators have been extracted. How to use one line of code to get them out of the two-tier list to the one-tier list? It can be achieved with universal anonymous expressions

get_all = lambda _, __ = eval("[]"): __.clear() or [[__.append(____) for ____ in ___ if ____] for ___ in _] and __

Why use _here= eval("[]") instead of _= [] or= What about list? Here's a brief explanation of how python helps us assign values to variables:
python assigns the address of the same value to the variable, that is, two different variable names assign the same value, and their memory address is the same [id(XXX) is the same]. Therefore, if the assignment here is an empty list, it will interfere with other empty lists running at the same time. Therefore, there will be a problem using the empty list as the default parameter. The interpreter will report a highlighted yellow, while the list calculated with eval("[]") will not report a yellow warning. Although this is also an empty list, in order to avoid variable accidents in multiple executions, I executed the list before use Clear() to clear the list, so that the final result will only be executed later. Then, the list behind or is not the desired result, but uses the list to display the list derivation formula, adds the value to the list variable in the derivation formula, and finally uses the and variable to transfer the list value of the extracted element back.

Count the quantity of each chemical element in each chemical substance

Judge whether the types of elements on both sides of the equation are consistent

ELEMENT is used to extract all the chemical elements.
Why not use assert elements = = right directly here_ What about elements? Because the list is ordered, as long as the order is different, the list is different. Here, only the types need to be the same. Therefore, the set is equal or the inverse intersection of the set is empty to judge whether the types of elements on both sides are the same.

ELEMENT = re.compile(R'([A-Z][a-z]?\.?|<(?:\d|{[A-Za-z0-9+-]})?e[+-]>)')
elements = list(set(get_all([ELEMENT.findall(item) for item in left_item_list if item not in list('+-')])))
right_elements = list(set(get_all([ELEMENT.findall(item) for item in right_item_list if item not in list('+-')])))
# assert not (set(elements) ^ set(right_elements)), 'the two elements of the chemical equation are not conserved!'
assert set(elements) == set(right_elements), 'The two elements of the chemical equation are not conserved!'

Calculate the number of elements on the left and right sides and obtain the proportion of each element in each substance

The next step is to count the number of left and right elements, scale_group is used to obtain the quantity of each element in different substances, left_item_dict and right_item_dict is used to obtain the quantity of each element in each substance. First obtain the quantity of elements in the material key, and then reverse the key with elements to obtain the quantity of elements in different substances.

scale_group = {element: {} for element in elements}
left_item_dict = {item: {} for item in left_item_list if item not in list('+-')}
right_item_dict = {item: {} for item in right_item_list if item not in list('+-')}
for item_list, item_dict in ((left_item_list, left_item_dict), (right_item_list, right_item_dict)):
    for item in item_list:
        if item not in list('+-'):
            for element in re.compile(R'([A-Z][a-z]?)(\d*)').findall(item):
                if element[0] not in item_dict[item]:
                    item_dict[item][element[0]] = 0
                item_dict[item][element[0]] += element[1].isdigit() and int(element[1]) or 1
for element in elements:
    for item_dict in (left_item_dict, right_item_dict):
        for item in item_dict:
            if element in item:
                scale_group[element][item] = item_dict[item][element]

Solving the conservation equation of chemical elements

Create symbolic variables by substance name

symbols_list = symbols([item for item in (left_item_list + right_item_list) if item not in list('+-')], positive=True, integer=True, real=True)  # Create element symbols for each chemical
for symbol in symbols_list:
    globals()[str(symbol)] = symbol  # Release the symbolic variable to the global, which is the basis for the following eval() string to variable formula

Create a relationship formula according to the relationship between the total amount of left and right elements

The relationship is: in each element, the sum of the number of elements on the left - the sum of the number of elements on the right = 0.
eval(temp_str) is to calculate a string into a symbolic variable equation

solve_list = []
for element in elements:
    temp_str = ''
    temp = []
    for item in left_item_dict:
        index = scale_group[element].get(str(item), 
        if index:
            temp.append(f'{index}*{str(item)}')
    temp_str += '+'.join(temp)
    temp = []
    for item in right_item_dict:
        index = scale_group[element].get(str(item), 
        if index:
            temp.append(f'{index}*{str(item)}')
    if temp:
        temp_str += '-' + '-'.join(temp)
    solve_list.append(eval(temp_str))

Solve the proportional relationship between various chemical substances

res is the solution of the proportional relationship between the coefficients of various chemical substances: {other chemical substances: n * a chemical substance}.
can_zhao is the chemical substance as a reference. Divide each proportion by it to get the pure digital proportion, and set it to 1 to get the digital proportion relationship between them.
It is observed that the relationship proportion numerator solved by the general slove is already the least common multiple, and there will only be one number in the denominator, so the denominator is extracted here, and all proportion numbers are multiplied by the denominator to obtain the proportion group of the least common multiple coefficient.

res = solve(solve_list, symbols_list)
can_zhao = [item for item in symbols_list if str(item) == list(set(list(left_item_dict.keys()) + list(right_item_dict.keys())) - set(list(map(str, res.keys()))))[0]][0]
for item in res:
    res[item] /= can_zhao
res[can_zhao] = 1
fen_mu = re.compile(R'/(\d+)?').findall(str(res))
if fen_mu:
    bs = int(fen_mu[0])
    for item in res:
        res[item] *= bs

The least common multiple of the proportion group is taken as the chemical substance coefficient

Chemical equation for generating balance_ Equations are the chemical equations that need to be balanced

chemical_equations = []
for item_dict in (left_item_dict, right_item_dict):
    all_item = []
    all_index = []
    for item in item_dict:
        all_item.append(item)
        all_index.append(res[[i for i in symbols_list if str(i) == item][0]])
    chemical_equations.append(' + '.join([f'{"" if all_index[i] == 1 else all_index[i]}{all_item[i]}' for i in range(len(item_dict))]))
chemical_equations = ' = '.join(chemical_equations)

Total code

# _*_ coding:utf-8 _*_
# Project: the most useless assembly
# FileName: AutoBalancingChemicalEquations.py
# UserName: user_from_future Blogger
# ComputerUser: user_from_future
# Day: 2022/2/21
# Time: 20:21
# IDE: PyCharm
# In 2022, all bug s will be thrown into the sea to feed sharks! I said it! Irrefutable!

# Automatic balancing chemical equation
# import bce

import re
import string
from sympy import solve, symbols

"""
Logic flow:
1,Split the left and right sides of the equation
2,Split each chemical and+-number
3,Count the quantity of each chemical element in each chemical substance
4,Solving the conservation equation of chemical elements
5,The least common multiple of the proportion group is taken as the chemical substance coefficient
 Element characteristics:
1,Multiple element:\d|{[A-Za-z0-9+-]}
2,Charge element:<(?:\d|{[A-Za-z0-9+-]})?e[+-]>
3,chemical element:[A-Z][a-z]?\.?
4,Substance status:\([gls]\)  # g is gas, l is liquid and s is solid
5,chemical substances:[+-]?(<(?:\d+|{[A-Za-z0-9+-]+})?e[+-]>|(?:(?:\(?(?:[A-Z][a-z]?\.?)+(?:\d+|{[A-Za-z0-9+-]+})?\)?)+(?:<(?:\d+|{[A-Za-z0-9+-]+})?e[+-]>)?)+)(?:\([gls]\))?([+-]?)
6,[②]Substance:[+-]?(<(?:\d+|{[A-Za-z0-9+-]+})?e[+-]>|(?:[A-Za-z0-9.]+(?:\([A-Za-z0-9]+\))?(?:{[A-Za-z0-9+-]+})?(?:<(?:\d+|{[A-Za-z0-9+-]+})?e[+-]>)?)+)(?:\([gls]\))?([+-]?)

Test process:
>> P+O2=P2O5
4P+5O2=2P2O5
>> H2O(g)+Fe=Fe3O4+H2
4H2O(g)+3Fe=Fe3O4+4H2
>> CO+Fe2O3=Fe+CO2
3CO+Fe2O3=2Fe+3CO2
>> C2H5OH+O2=CO2+H2O
C2H5OH+3O2=2CO2+3H2O
>> Cl2+<e->=Cl<e->
Cl2+2<e->=2Cl<e->
>> Cu+Fe<3e+>=Cu<2e+>+Fe<2e+>
Cu+2Fe<3e+>=Cu<2e+>+2Fe<2e+>
>> LiOH+H2O2+H2O=Li2O2.H2O2.3H2O
2LiOH+2H2O2+H2O=Li2O2.H2O2.3H2O
>> C{n}H{2n+2}+O2=CO2+H2O
{(n+1)^(-1)}C{n}H{2*n+2}+{(1/2)*(3*n+1)/(n+1)}O2={n/(n+1)}CO2+H2O
>> CH3(CHCH){n}CH3+Cl2=CH3(CHClCHCl){n}CH3
CH3(CHCH){n}CH3+{n}Cl2=CH3(CHClCHCl){n}CH3
>> X-<e->=X<{n}e+>
X-{n}<e->=X<{n}e+>
>> CH4;HCN;NH3;O2;H2O
2HCN+6H2O=2CH4+2NH3+3O2
>> CH4(g);HCN(g);NH3(g);O2(g);H2O(g)
2CH4(g)+2NH3(g)+3O2(g)=2HCN(g)+6H2O(g)
"""

ChemicalEquationsList = [
    'P+O2=P2O5',                                # 4P+5O2+2P2O5
    'H2O(g)+Fe=Fe3O4+H2',                       # 4H2O(g)+3Fe=Fe3O4+4H2
    'CO+Fe2O3=Fe+CO2',                          # 3CO+Fe2O3=2Fe+3CO2
    'C2H5OH+O2=CO2+H2O',                        # C2H5OH+3O2=2CO2+3H2O
    'Cl2+<e->=Cl<e->',                          # Cl2+2<e->=2Cl<e->
    'Cu+Fe<3e+>=Cu<2e+>+Fe<2e+>',               # Cu+2Fe<3e+>=Cu<2e+>+2Fe<2e+>
    'LiOH+H2O2+H2O=Li2O2.H2O2.3H2O',            # 2LiOH+2H2O2+H2O=Li2O2.H2O2.3H2O
    'C{n}H{2n+2}+O2=CO2+H2O',                   # {(n+1)^(-1)}C{n}H{2*n+2}+{(1/2)*(3*n+1)/(n+1)}O2={n/(n+1)}CO2+H2O
    'CH3(CHCH){n}CH3+Cl2=CH3(CHClCHCl){n}CH3',  # CH3(CHCH){n}CH3+{n}Cl2=CH3(CHClCHCl){n}CH3
    'X-<e->=X<{n}e+>',                          # X-{n}<e->=X<{n}e+>
    'CH4;HCN;NH3;O2;H2O',                       # 2HCN+6H2O=2CH4+2NH3+3O2
    'CH4(g);HCN(g);NH3(g);O2(g);H2O(g)',        # 2CH4(g)+2NH3(g)+3O2(g)=2HCN(g)+6H2O(g)
]


ELEMENT = re.compile(R'([A-Z][a-z]?|<(?:\d|{[A-Za-z0-9+-]})?e[+-]>)')


def max_gys(num1, num2):  # The greatest common divisor of two numbers
    while num2:
        temp = num1 % num2
        num1, num2 = num2, temp
    return num1


def min_gbs(num1, num2):  # Least common multiple of two numbers
    return num1 * num2 // max_gys(num1, num2)


# Convert the contents of the second level list to the first level list
get_all = lambda _, __ = eval("[]"): __.clear() or [[__.append(____) for ____ in ___ if ____] for ___ in _] and __


def handle_double_elements_list_scale(element1, element2, chemicals, scale_group):  # Synchronize the same elements in two element groups
    shared = list(set(chemicals[element1]) & set(chemicals[element2]))
    if shared:
        multiply = min_gbs(scale_group[element1][shared[0]], scale_group[element2][shared[0]])
        shared1 = scale_group[element1][shared[0]]
        for item in scale_group[element1]:
            scale_group[element1][item] *= (multiply // shared1)
        shared2 = scale_group[element2][shared[0]]
        for item in scale_group[element2]:
            scale_group[element2][item] *= (multiply // shared2)
    return element2


def calc_equations(left_item_list, right_item_list):
    elements = list(set(get_all([ELEMENT.findall(item) for item in left_item_list if item not in list('+-')])))
    right_elements = list(set(get_all([ELEMENT.findall(item) for item in right_item_list if item not in list('+-')])))
    assert set(elements) == set(right_elements), 'The two elements of the chemical equation are not conserved!'
    symbols_list = symbols([item for item in (left_item_list + right_item_list) if item not in list('+-')], positive=True, integer=True, real=True)  # Create element symbols for each chemical
    for symbol in symbols_list:
        globals()[str(symbol)] = symbol  # Release the symbolic variable to the global, which is the basis for the following eval() string to variable formula
    scale_group = {element: {} for element in elements}
    left_item_dict = {item: {} for item in left_item_list if item not in list('+-')}
    right_item_dict = {item: {} for item in right_item_list if item not in list('+-')}
    for item_list, item_dict in ((left_item_list, left_item_dict), (right_item_list, right_item_dict)):
        for item in item_list:
            if item not in list('+-'):
                for element in re.compile(R'([A-Z][a-z]?)(\d*)').findall(item):
                    if element[0] not in item_dict[item]:
                        item_dict[item][element[0]] = 0
                    item_dict[item][element[0]] += element[1].isdigit() and int(element[1]) or 1
    for element in elements:
        for item_dict in (left_item_dict, right_item_dict):
            for item in item_dict:
                if element in item:
                    scale_group[element][item] = item_dict[item][element]
    solve_list = []
    for element in elements:
        temp_str = ''
        temp = []
        for item in left_item_dict:
            index = scale_group[element].get(str(item), "")
            if index:
                temp.append(f'{index}*{str(item)}')
        temp_str += '+'.join(temp)
        temp = []
        for item in right_item_dict:
            index = scale_group[element].get(str(item), "")
            if index:
                temp.append(f'{index}*{str(item)}')
        if temp:
            temp_str += '-' + '-'.join(temp)
        solve_list.append(eval(temp_str))
    res = solve(solve_list, symbols_list)
    can_zhao = [item for item in symbols_list if str(item) == list(set(list(left_item_dict.keys()) + list(right_item_dict.keys())) - set(list(map(str, res.keys()))))[0]][0]
    for item in res:
        res[item] /= can_zhao
    res[can_zhao] = 1
    fen_mu = re.compile(R'/(\d+)?').findall(str(res))
    if fen_mu:
        bs = int(fen_mu[0])
        for item in res:
            res[item] *= bs
    chemical_equations = []
    for item_dict in (left_item_dict, right_item_dict):
        all_item = []
        all_index = []
        for item in item_dict:
            all_item.append(item)
            all_index.append(res[[i for i in symbols_list if str(i) == item][0]])
        chemical_equations.append(' + '.join([f'{"" if all_index[i] == 1 else all_index[i]}{all_item[i]}' for i in range(len(item_dict))]))
    chemical_equations = ' = '.join(chemical_equations)
    return chemical_equations


for ChemicalEquations in ChemicalEquationsList:
    for space in string.whitespace:
        ChemicalEquations = ChemicalEquations.replace(space, '')
    if '=' not in ChemicalEquations and ';' in ChemicalEquations:
        all_items = ChemicalEquations.split(';')
        pass  # The automatic total price calculation equation is not done first, but the element balance is done first
    elif 'e+>' in ChemicalEquations or 'e->' in ChemicalEquations or '{' in ChemicalEquations or '.' in ChemicalEquations:
        pass  # For the time being, we will not engage in electronic < e + > / < e - > and inclusion/ {n} Calculation of chemical substances, etc
    else:
        left, right = ChemicalEquations.split('=')
        items = re.compile(R'[+-]?(<(?:\d+|{[A-Za-z0-9+-]+})?e[+-]>|(?:(?:\(?(?:[A-Z][a-z]?\.?)+(?:\d+|{[A-Za-z0-9+-]+})?\)?)+(?:<(?:\d+|{[A-Za-z0-9+-]+})?e[+-]>)?)+)(?:\([gls]\))?([+-]?)')
        left_items, right_items = items.findall(left), items.findall(right)
        print(ChemicalEquations + '\n\t' + calc_equations(list(get_all(left_items)), list(get_all(right_items))))

Concluding remarks

Of course, I can only solve the equation without electron transfer and unknown relationship. The rest are lazy and don't want to do it. Those who are interested can improve my program~
For standard chemical equation balancing results, please install bce library and use bce console Exe program to solve the balance.
The operation may be going in a few days. At that time, I will not be able to reply to various comments in time. As the first and last loose plan, try it at will like this, and finally come to the conclusion that programmers still carry the code ~ (previous knowledge is the tool of modern people)

Topics: Python