Skip to content

Instantly share code, notes, and snippets.

@battis
Last active December 17, 2015 17:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save battis/5646206 to your computer and use it in GitHub Desktop.
Save battis/5646206 to your computer and use it in GitHub Desktop.
{
"metadata": {
"name": "Chemical Equation Balancer"
},
"nbformat": 3,
"nbformat_minor": 0,
"worksheets": [
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": "Some sample output from this script:\n\n<pre><span style=\"color: blue;\">Please enter an unbalanced chemical equation in the following format:\n\n C3H8 + O2 --> H2O + CO2\n\n(Note that spaces do not matter, except that you must enter molecular\nformulae as a single word. Subscripts are inferred by placement within the\nformula and a subscript of 1 is implicit and does not need to be entered.\nPlease use --> to indicate net forward action.)</span>\n\nC3H8 + O2 --> H2O + CO2\n\n<span style=\"color: blue;\">The balanced equation:\n\nH_8C_3 + 5(O_2) --> 4(H_2O) + 3(CO_2)\n(1) (2) (3) (4) \n\nWhich reactant or product's mass is known?</span>\n\n3\n\n<span style=\"color: blue;\">What is the mass of H_2O?\n\n2.25mg\n\nHow many significant digits?</span>\n\n2\n\n<span style=\"color: blue;\">1.38 mg H_8C_3 + 5.0 mg O_2 --> 2.25 mg H_2O + 4.12 mg CO_2</span></pre>"
},
{
"cell_type": "code",
"collapsed": false,
"input": "'''---------------------------------------------------------------------\n Shared class code provided by Mr. Battis\n---------------------------------------------------------------------'''\n\ndef generatePeriodicTable():\n periodicTable = dict()\n periodicTable['H'] = 1.0079400000\n periodicTable['He'] = 4.0026020000\n periodicTable['Li'] = 6.9410000000\n periodicTable['Be'] = 9.0121820000\n periodicTable['B'] = 10.8110000000\n periodicTable['C'] = 12.0107000000\n periodicTable['N'] = 14.0067000000\n periodicTable['O'] = 15.9994000000\n periodicTable['F'] = 18.9994000000\n periodicTable['Ne'] = 20.1797000000\n periodicTable['Na'] = 22.9897692800\n periodicTable['Mg'] = 24.3050000000\n periodicTable['Al'] = 26.9815386000\n periodicTable['Si'] = 28.0855000000\n periodicTable['P'] = 30.9737620000\n periodicTable['S'] = 32.0650000000\n periodicTable['Cl'] = 35.4530000000\n periodicTable['Ar'] = 39.9480000000\n periodicTable['K'] = 39.0983000000\n periodicTable['Ca'] = 40.0780000000\n periodicTable['Sc'] = 44.9559120000\n periodicTable['Ti'] = 47.8670000000\n periodicTable['V'] = 50.9415000000\n periodicTable['Cr'] = 51.9961000000\n periodicTable['Mn'] = 54.9380450000\n periodicTable['Fe'] = 55.8450000000\n periodicTable['Co'] = 58.9331950000\n periodicTable['Ni'] = 58.6934000000\n periodicTable['Cu'] = 63.5460000000\n periodicTable['Zn'] = 65.3800000000\n periodicTable['Ga'] = 69.7230000000\n periodicTable['Ge'] = 72.6400000000\n periodicTable['As'] = 74.9216000000\n periodicTable['Se'] = 78.9600000000\n periodicTable['Br'] = 79.9040000000\n periodicTable['Kr'] = 83.7980000000\n periodicTable['Rb'] = 85.4678000000\n periodicTable['Sr'] = 87.6200000000\n periodicTable['Y'] = 88.9058500000\n periodicTable['Zr'] = 91.2240000000\n periodicTable['Nb'] = 92.9063800000\n periodicTable['Mo'] = 95.9600000000\n periodicTable['Tc'] = 98.0000000000\n periodicTable['Ru'] = 101.0700000000\n periodicTable['Rh'] = 102.9055000000\n periodicTable['Pd'] = 106.4200000000\n periodicTable['Ag'] = 107.8682000000\n periodicTable['Cd'] = 112.4110000000\n periodicTable['In'] = 114.8180000000\n periodicTable['Sn'] = 118.7100000000\n periodicTable['Sb'] = 121.7600000000\n periodicTable['Te'] = 127.6000000000\n periodicTable['I'] = 126.9044700000\n periodicTable['Xe'] = 131.2930000000\n periodicTable['Cs'] = 132.9054519000\n periodicTable['Ba'] = 137.3270000000\n periodicTable['La'] = 138.9054700000\n periodicTable['Ce'] = 140.1160000000\n periodicTable['Pr'] = 140.9076500000\n periodicTable['Nd'] = 144.2420000000\n periodicTable['Pm'] = 145.0000000000\n periodicTable['Sm'] = 150.3600000000\n periodicTable['Eu'] = 151.9640000000\n periodicTable['Gd'] = 157.2500000000\n periodicTable['Tb'] = 158.9253500000\n periodicTable['Dy'] = 162.5001000000\n periodicTable['Ho'] = 164.9303200000\n periodicTable['Er'] = 167.2590000000\n periodicTable['Tm'] = 168.9342100000\n periodicTable['Yb'] = 173.0540000000\n periodicTable['Lu'] = 174.9668000000\n periodicTable['Hf'] = 178.4900000000\n periodicTable['Ta'] = 180.9478800000\n periodicTable['W'] = 183.8400000000\n periodicTable['Re'] = 186.2070000000\n periodicTable['Os'] = 190.2300000000\n periodicTable['Ir'] = 192.2170000000\n periodicTable['Pt'] = 192.0840000000\n periodicTable['Au'] = 196.9665690000\n periodicTable['Hg'] = 200.5900000000\n periodicTable['Tl'] = 204.3833000000\n periodicTable['Pb'] = 207.2000000000\n periodicTable['Bi'] = 208.9804010000\n periodicTable['Po'] = 210.0000000000\n periodicTable['At'] = 210.0000000000\n periodicTable['Rn'] = 220.0000000000\n periodicTable['Fr'] = 223.0000000000\n periodicTable['Ra'] = 226.0000000000\n periodicTable['Ac'] = 227.0000000000\n periodicTable['Th'] = 232.0380600000\n periodicTable['Pa'] = 231.0358800000\n periodicTable['U'] = 238.0289100000\n periodicTable['Np'] = 237.0000000000\n periodicTable['Pu'] = 244.0000000000\n periodicTable['Am'] = 243.0000000000\n periodicTable['Cm'] = 247.0000000000\n periodicTable['Bk'] = 247.0000000000\n periodicTable['Cf'] = 251.0000000000\n periodicTable['Es'] = 252.0000000000\n periodicTable['Fm'] = 257.0000000000\n periodicTable['Md'] = 258.0000000000\n periodicTable['No'] = 259.0000000000\n periodicTable['Lr'] = 262.0000000000\n periodicTable['Rf'] = 261.0000000000\n periodicTable['Db'] = 262.0000000000\n periodicTable['Sg'] = 266.0000000000\n periodicTable['Bh'] = 264.0000000000\n periodicTable['Hs'] = 277.0000000000\n periodicTable['Mt'] = 268.0000000000\n periodicTable['Ds'] = 271.0000000000\n periodicTable['Rg'] = 272.0000000000\n periodicTable['Uub'] = 285.0000000000\n periodicTable['Uut'] = 284.0000000000\n periodicTable['Uuq'] = 289.0000000000\n periodicTable['Uup'] = 288.0000000000\n periodicTable['Uuh'] = 292.0000000000\n periodicTable['Uuo'] = 294.0000000000\n return periodicTable\n\n'''Rendered obsolete by Sean and Justin\ndef printEquation(equation):\n for side in equation:\n for molecule in side:\n operandText = generateOperandText(molecule)\n print(operandText, sep = '', end = '')\n print(' is',len(operandText), 'long')\n if molecule != side[-1]:\n print(' + ', sep = '', end = '')\n if side != equation[-1]:\n print(' --> ', sep = '', end = '')\n print()\n\ndef generateOperandText(operand):\n operandText = ''\n if operand['coefficient'] > 1:\n operandText = operandText + str(operand['coefficient']) + '('\n operandText = operandText + generateMoleculeText(operand['formula'])\n if operand['coefficient'] > 1:\n operandText = operandText + ')'\n return operandText\n\ndef generateMoleculeText(molecule):\n # FIXME why does this print in the wrong order?\n moleculeText = ''\n for symbol, subscript in molecule.items():\n if subscript > 1:\n moleculeText = moleculeText + symbol + '_' + str(subscript)\n else:\n moleculeText = moleculeText + symbol\n return moleculeText\n'''\n\n'''---------------------------------------------------------------------\n User Input, Damion Nsiah and Robert Yuen\n---------------------------------------------------------------------'''\n\n''' As provided\nequation = input (\"Enter an equation:\")\nsplitequation = equation.split('--->')\nfor element in splitequation:\n print(element.split('+'))\n'''\n\n'''Modified by Mr. Battis to fit into the group code'''\ndef getUserInput():\n print('''\nPlease enter an unbalanced chemical equation in the following format:\n\n C3H8 + O2 --> H2O + CO2\n\n(Note that spaces do not matter, except that you must enter molecular\nformulae as a single word. Subscripts are inferred by placement within the\nformula and a subscript of 1 is implicit and does not need to be entered.\nPlease use --> to indicate net forward action.)\n''')\n equation = input()\n splitequation = equation.split('-->')\n for i in list(range(len(splitequation))):\n splitequation[i] = splitequation[i].split('+')\n for j in list(range(len(splitequation[i]))):\n splitequation[i][j] = splitequation[i][j].strip()\n return splitequation\n\n'''---------------------------------------------------------------------\n User Input, Debby Yip and Ian Martin\n---------------------------------------------------------------------'''\n\n''' As provided\ntext = '4Na2K3HBrO4'\n\ni = 0\nwhile text[i].isdigit():\n i = i + 1\ncoefficient = text[0:i]\ntext = text[i:]\n\ni = 1\nwhile text[i].islower():\n i = i + 1\nsymbol = text[0:i]\ntext = text[i:]\n\ni = 0\nwhile text[i].isdigit():\n i = i + 1\nsubscript = text[0:i]\nif subscript == '':\n subscript = '1'\ntext = text[i:]\n\ni = 1\nwhile text[i].islower():\n i = i + 1\nsymbol2 = text[0:i]\ntext = text[i:]\n\ni = 0\nwhile text[i].isdigit():\n i = i + 1\nsubscript2 = text[0:i]\nif subscript2 == '':\n subscript2 = '1'\ntext = text[i:]\n\ni = 1\nwhile text[i].islower():\n i = i + 1\nsymbol3 = text[0:i]\ntext = text[i:]\n\ni = 0\nwhile text[i].isdigit():\n i = i + 1\nsubscript3 = text[0:i]\nif subscript3 == '':\n subscript3 = '1'\ntext = text[i:]\n\ni = 1\nwhile text[i].islower():\n i = i + 1\nsymbol4 = text[0:i]\ntext = text[i:]\n\ni = 0\nwhile text[i].isdigit():\n i = i + 1\nsubscript4 = text[0:i]\nif subscript4 == '':\n subscript4 = '1'\ntext = text[i:]\n\ni = 1\nwhile text[i].islower():\n i = i + 1\nsymbol5 = text[0:i]\ntext = text[i:]\n\ni = 0\nwhile text[i].isdigit():\n i = i + 1\nsubscript5 = text[0:i]\nif subscript5 == '':\n subscript5 = '1'\ntext = text[i:]\n\nprint(coefficient, symbol, subscript, symbol2, subscript2, symbol3, subscript3, symbol4, subscript4, symbol5, subscript5, text)\n'''\n\n'''Modified by Mr. Battis to fit into the group code'''\ndef parseMolecule(text):\n molecule = dict()\n\n i = 0\n while text[i].isdigit():\n i = i + 1\n coefficient = text[0:i]\n if coefficient == '':\n coefficient = '1'\n text = text[i:]\n molecule['coefficient'] = int(coefficient)\n\n molecule['formula'] = dict()\n while len(text) > 0:\n i = 1\n while i < len(text) and text[i].islower():\n i = i + 1\n symbol = text[0:i]\n text = text[i:]\n\n i = 0\n susbcript = ''\n while i < len(text) and text[i].isdigit():\n i = i + 1\n subscript = text[0:i]\n if subscript == '':\n subscript = '1'\n text = text[i:]\n molecule['formula'][symbol] = int(subscript)\n \n return molecule\n\ndef parseSplitEquation(splitEquation):\n for i in list(range(len(splitEquation))):\n for j in list(range(len(splitEquation[i]))):\n splitEquation[i][j] = parseMolecule(splitEquation[i][j])\n return splitEquation\n\n'''---------------------------------------------------------------------\n Equation Balancing, Julie Geng and Mr. Battis\n---------------------------------------------------------------------'''\n\ndef matrixFromEquation(equation):\n '''\n Create a matrix that represents N linear equations for each of the N\n molecular coefficients of the unbalanced equation, of the form\n\n a + b - x = y\n\n with one linear equation representing the relationship among the\n molecules for each element present in the equation\n '''\n # make a copy of equation so we don't accidentally change it (lists are mutable!)\n myEquation = equation[:]\n\n # algebraically subtract all but the last product from the reactants\n algebraicEquation = myEquation[0] + myEquation[1]\n subtractedMolecules = list(range(len(myEquation[0]), len(algebraicEquation) - 1))\n for i in subtractedMolecules:\n algebraicEquation[i]['coefficient'] *= -1\n\n # build our matrix as a dictionary\n matrix = dict()\n # ...creating a row for each element's linear equation as we first encounter it\n for molecule in algebraicEquation:\n for symbol, subscript in molecule['formula'].items():\n if not symbol in matrix: # meaning we haven't yet processed it\n matrix[symbol] = list()\n for m in algebraicEquation:\n if symbol in m['formula']:\n matrix[symbol].append(m['coefficient'] * m['formula'][symbol])\n else:\n matrix[symbol].append(0)\n\n # convert the dictionary into a list\n finalMatrix = list()\n for symbol, row in matrix.items():\n finalMatrix.append(row)\n\n return finalMatrix\n\n'''as defined by Julie\ndef swapRows(m):\n for row in list(range(len(m))):\n if m[row][row] == 0 and row+1 <= len(m):\n temp= m[row][:]\n m[row]=m[row+1][:]\n m[row+1]= temp\n return m\n'''\n\n'''tweaked by Mr. Battis'''\ndef swapRows(m):\n for row in list(range(len(m))):\n if m[row][row] == 0:\n swapRow = row\n while swapRow < len(m) and m[swapRow][row] == 0:\n swapRow = swapRow + 1\n if row != swapRow and swapRow < len(m):\n temp = m[row][:]\n m[row] = m[swapRow][:]\n m[swapRow] = temp\n return m\n \n\n#work cited: http://www.engineering.ucsb.edu/~hpscicom/projects/gauss/introge.pdf\ndef myGauss(m):\n #eliminate columns\n for col in list(range(len(m[0]))):\n for row in list(range(col+1, len(m))):\n r = [(rowValue * (-(m[row][col] / m[col][col]))) for rowValue in m[col]]\n m[row] = [sum(pair) for pair in zip(m[row], r)]\n\n #now backsolve by substitution\n ans = []\n m.reverse() #makes it easier to backsolve\n for sol in range(len(m)):\n if sol == 0:\n ans.append(m[sol][-1] / m[sol][-2])\n else:\n inner = 0\n #substitute in all known coefficients\n for x in range(sol):\n inner += (ans[x]*m[sol][-2-x])\n #the equation is now reduced to ax + b = c form\n #solve with (c - b) / a\n ans.append((m[sol][-1]-inner)/m[sol][-sol-2])\n ans.reverse()\n return ans \n\ndef computeCoefficients(matrix, diagonalValues):\n '''\n Working from the original matrix and its diagonal form (provided,\n no doubt, by gaussianElimination()), compute the whole number, simplest\n coefficients, based on the linear equations represented in the\n matrices.\n '''\n # collect rational solution to matrix\n coefficients = diagonalValues[:]\n coefficients.append(1) # don't forget our last coefficient!\n\n # calculate multiplier to convert rational numbers to whole numbers\n multiplier = 1\n for row in matrix:\n for column in row:\n if column != 0:\n multiplier = multiplier * abs(column)\n\n # apply multiplier to solutions\n coefficientCount = list(range(len(coefficients)))\n for i in coefficientCount:\n # we _should_ get a whole number, but we may have lost precision, so we round\n coefficients[i] = round(coefficients[i] * multiplier)\n\n # calculate the greatest common divisor of all the coefficients\n import fractions\n gcd = coefficients[0]\n for i in coefficientCount:\n gcd = fractions.gcd(gcd, coefficients[i])\n\n # divide all coefficients by the greatest common divisor\n for i in coefficientCount:\n # these are whole numbers, lets force them to be ints, rather than floats!\n coefficients[i] = int(coefficients[i] / gcd)\n\n return coefficients\n\ndef balanceEquation(equation):\n '''\n Given an unbalanced chemical equation in our \"standard\" form -- a\n list of lists of dictionaries of dictionaries -- calculate the\n molecular coefficients necessary to balance that equation and\n return a copy of the balanced equation.\n '''\n\n # generate a matrix of linear equations for the coefficients\n matrix = matrixFromEquation(equation);\n\n # make sure we have a non-zero value in m[col][col] (the diagonal)\n swappedMatrix = swapRows(matrix)\n \n # use Gaussian elimination to solve for each coefficient\n diagonalValues = myGauss(swappedMatrix)\n\n # compute the simplest, whole number coefficients\n coefficients = computeCoefficients(matrix, diagonalValues)\n\n # make a copy of the equation (lists are mutable!)\n balancedEquation = equation[:]\n\n # assign coefficients to the balancedEquation\n coefficientCount = list(range(len(coefficients)))\n for i in coefficientCount:\n if i < len(balancedEquation[0]):\n balancedEquation[0][i]['coefficient'] = coefficients[i]\n else:\n balancedEquation[1][i - len(balancedEquation[0])]['coefficient'] = coefficients[i]\n\n return balancedEquation\n\n'''---------------------------------------------------------------------\n Get known mass from user, Sean Bellefeuille and Justin Nsiah\n---------------------------------------------------------------------'''\n\ndef printEquation(equation):\n for side in equation:\n for molecule in side:\n # printOperand(molecule)\n operandText = generateOperandText(molecule)\n print(operandText, sep = '', end = '')\n if molecule != side[-1]:\n print(' + ', sep = '', end = '')\n if side != equation[-1]:\n print(' --> ', sep = '', end = '')\n print()\n \ndef printAlternaEquation(equation):\n x = 1\n for side in equation:\n for molecule in side:\n # printOperand(molecule)\n operandText = generateOperandText(molecule)\n print( '(', x ,')', sep = '', end = '')\n x = x + 1\n spacePadding = ''\n for i in list(range(2,len(operandText)+2)):\n spacePadding = spacePadding + ' '\n print(spacePadding, sep = '', end = '')\n if side != equation[-1]:\n print(' ', sep = '', end = '')\n print()\n\ndef generateOperandText(operand):\n operandText = ''\n if operand['coefficient'] > 1:\n # operand['coefficient'], '(', sep = '', end = '')\n operandText = operandText + str(operand['coefficient']) + '('\n operandText = operandText + generateMoleculeText(operand['formula'])\n if operand['coefficient'] > 1:\n # print(')', sep = '', end = '')\n operandText = operandText + ')'\n return operandText\n\ndef generateMoleculeText(molecule):\n # FIXME why does this print in the wrong order?\n moleculeText = ''\n for symbol, subscript in molecule.items():\n if subscript > 1:\n # print(symbol, subscript, sep = '_', end = '')\n moleculeText = moleculeText + symbol + '_' + str(subscript)\n else:\n # print(symbol, sep = '', end = '')\n moleculeText = moleculeText + symbol\n return moleculeText\n\n'''Added by Mr. Battis for compatibility with group code'''\ndef getKnownMass(equation):\n print('''\nThe balanced equation:\n''')\n printEquation(equation)\n printAlternaEquation(equation)\n print('''\nWhich reactant or product\\'s mass is known?\n''')\n knownOperand = int(input())\n knownOperandFormula = dict()\n if knownOperand > len(equation[0]):\n knownOperandFormula = equation[1][knownOperand - len(equation[0]) - 1]\n else:\n knownOperandFormula = equation[0][knownOperand - 1]\n\n print('''\nWhat is the mass of ''', generateMoleculeText(knownOperandFormula['formula']),'''?\n''', sep='')\n massText = input()\n i = 0\n decimals = 0\n while (massText[i].isdigit() or massText[i] == '.') and decimals < 2:\n i = i + 1\n if massText[i] == '.':\n decimals = decimals + 1\n mass = massText[0:i]\n if mass == '':\n mass = '1'\n mass = float(mass)\n unit = massText[i:].strip()\n masses = list()\n for i in list(range(len(equation))):\n masses.append(list())\n for j in list(range(len(equation[i]))):\n if knownOperand == j + 1 or (i == 1 and knownOperand == len(equation[0]) + j + 1):\n masses[i].append({'mass': mass, 'unit': unit})\n else:\n masses[i].append({'mass': -1, 'unit': '?'})\n return {'knownOperand': knownOperand, 'masses': masses}\n\n'''---------------------------------------------------------------------\n Calculate known masses, Jeremy Lee and Eddie Chen\n---------------------------------------------------------------------'''\n\ndef calculateIdealMasses(equation):\n idealMasses = list()\n for i in list(range(len(equation))):\n idealMasses.append(list())\n for j in list(range(len(equation[i]))):\n idealMasses[i].append(0)\n for symbol, subscript in equation[i][j]['formula'].items():\n idealMasses[i][j] = idealMasses[i][j] + equation[i][j]['coefficient'] * periodicTable[symbol] * subscript\n return idealMasses\n\ndef calculateRatios(idealMasses, knownOperand):\n knownIdealMass = 0\n if knownOperand > len(idealMasses[0]):\n knownIdealMass = idealMasses[1][knownOperand - len(idealMasses[0]) - 1]\n else:\n knownIdealMass = idealMasses[0][knownOperand - 1]\n\n ratios = list()\n for side in idealMasses:\n ratios.append(list())\n for operand in side:\n ratios[-1].append(operand / knownIdealMass)\n\n return ratios\n\ndef calculateMasses(ratios, masses, knownOperand):\n knownMass = dict()\n if knownOperand > len(masses[0]):\n knownMass = masses[1][knownOperand - len(masses[0]) - 1]\n else:\n knownMass = masses[0][knownOperand - 1]\n for i in list(range(len(masses))):\n for j in list(range(len(masses[i]))):\n masses[i][j]['mass'] = knownMass['mass'] * ratios[i][j];\n masses[i][j]['unit'] = knownMass['unit']\n\n return masses\n\n'''Added by Mr. Battis for compatibility with group code'''\ndef calculateMassesFromKnown(equation, knownOperand, knownMasses):\n idealMasses = calculateIdealMasses(equation)\n ratios = calculateRatios(idealMasses, knownOperand)\n calculatedMasses = calculateMasses(ratios, knownMasses, knownOperand)\n return calculatedMasses\n\n'''---------------------------------------------------------------------\n Pulling it all together...\n---------------------------------------------------------------------'''\n\nperiodicTable = generatePeriodicTable()\n\ndef printMasses(equation, masses):\n print('''\nHow many significant digits?\n''')\n sigFig = int(input())\n \n text = '''\n'''\n for i in list(range(len(equation))):\n for j in list(range(len(equation[i]))):\n text = text + str(round(masses[i][j]['mass'], sigFig)) + ' ' + masses[i][j]['unit'] + ' ' + generateMoleculeText(equation[i][j]['formula'])\n if j + 1 != len(equation[i]):\n text = text + ' + '\n if i + 1 != len (equation):\n text = text + ' --> '\n print(text)\n \n\ndef main():\n splitEquation = getUserInput()\n equation = parseSplitEquation(splitEquation)\n balancedEquation = balanceEquation(equation)\n knowns = getKnownMass(balancedEquation)\n masses = calculateMassesFromKnown(balancedEquation, knowns['knownOperand'], knowns['masses'])\n printMasses(equation, masses)\n\nmain()\n",
"language": "python",
"metadata": {},
"outputs": []
}
],
"metadata": {}
}
]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment