Last active
December 17, 2015 17:29
-
-
Save battis/5646206 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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