Package: pHcalc
pHcalcpHcalcis a Python library for systematically calculating solution pH,
distribution diagrams, and titration curves.This package is Python3 compatible withdependenciesonly on Numpy and
Scipy. If you will be plotting the data, then there is anoptional
dependencyon Matplotlib as well.DependenciesNumpy >= 1.10Scipy >= 0.17Optional PackagesMatplotlib >= 1.5InstallationpHcalcis available viapip,conda, or theGitHub repo(most
recent).From PyPI:$ pip install pHcalcVia Conda:$ conda install -c rnelsonchem pHcalcIf you havegitinstalled on your system, then you can get the most
recent, unrelased version from theGitHub repo:$ pip install git+https://github.com/rnelsonchem/pHcalc.gitBackgroundpHcalccalculates the pH of a complex system of potentially strong and weak
acids and bases using a systematic equilibrium solution method. This method is
described in detail inthe Journal of Chemical Educationand in thisChemWiki article, for example. (There is also another, older Pascal program
calledPHCALC, which uses matrix algebra to accomplish the same task. To the
best of my knowledge, the source code for this program is no longer
available.)Basically, this method finds the optimum pH for the mixture by systematically
adjusting the pH until a charge balance is achieved, i.e. the concentrations
of positively charged ions equals the charge for the negatively charged ions.
For (polyprotic) weak acids, the fractional distribution of the species
at a given pH value is determined. Multiplying this by the concentration of
acid in solution provides the concentration of each acidic species in the
system, and these concentrations are used to balance the charge.Using this methodology bases and strong acids can be described using inert,
charged species. These are ions that do not react with water, such as Na+and Cl-. In this context, any Cl-in solution must be charged balanced
with an appropriate amount of H3O+, which would define HCl in solution.
Na+must be offset by an equivalent amount of OH-, which defines a
solution of NaOH. A 1:1 combination of Na+and H2CO3would describe a
solution of NaHCO3, the additional equivalent of OH-is implied by the
charge imbalance.Example UsagepHcalcdefines three classes - Acid, Inert, and System - which are used in
calculating the pH of the system. H3O+and OH-are never explicitly
defined. The H3O+concentration is adjusted internally, and OH-is
calculated using KW.>>>frompHcalcimportAcid,Inert,System>>>importnumpyasnp>>>importmatplotlib.pyplotasplt# Optional for plotting belowpH of 0.01 M HClFirst of all, HCl completely dissociates in water to give equal amounts of
H3O+and Cl-. Because H3O+is adjusted internally, all you need to define
is Cl-. This implies a single equivalent of H3O+in order to balance the
charge of the system.>>>cl=Inert(charge=-1,conc=0.01)>>>system=System(cl)>>>system.pHsolve()>>>print(system.pH)# Should print 1.9999pH of 1e-8 M HClThis is a notoriously tricky example for introductory chemistry students;
however,pHcalchandles it nicely.>>>cl=Inert(charge=-1,conc=1e-8)>>>system=System(cl)>>>system.pHsolve()>>>print(system.pH)# Should print 6.978295898 (NOT 8!)pH of 0.01 M NaOHThis example is very similar to our HCl example, except that our Inert
species must have a positive charge. The charge balance is achieved internally
by the System using an equivalent amount of OH-.>>>na=Inert(charge=1,conc=0.01)>>>system=System(na)>>>system.pHsolve()>>>print(system.pH)# Should print 12.00000pH of 0.01 M HFHere we will use an Acid object instance to define the weak acid HF, which has
a Kaof 6.76e-4 and a pKaof 3.17. You can use either value when you
create the Acid instance. When defining an Acid species, you must always
define achargekeyword argument, which is the charge of thefully
protonated species.>>>hf=Acid(Ka=6.76e-4,charge=0,conc=0.01)>>># hf = Acid(pKa=3.17, charge=0, conc=0.01) will also work>>>system=System(hf)>>>system.pHsolve()>>>print(system.pH)# Should print 2.6413261pH of 0.01 M NaFThis system consist of a 1:1 mixture of an HF Acid instance and a Na+Inert instance. The System object can be instantiated with an arbitrary
number of Acids and Inert objects. Again, there is an implied equivalent of
OH-necessary to balance the charge of the system.>>>hf=Acid(Ka=6.76e-4,charge=0,conc=0.01)>>>na=Inert(charge=1,conc=0.01)>>>system=System(hf,na)>>>system.pHsolve()>>>print(system.pH)# Should print 7.5992233pH of 0.01 M H2CO3The Kaand pKaattributes can also accept lists of values for polyprotic
species.>>>carbonic=Acid(pKa=[6.35,10.33],charge=0,conc=0.01)>>>system=System(carbonic)>>>system.pHsolve()>>>print(system.pH)# Should print 4.176448pH of 0.01 M Alanine Zwitterion FormAlanine has two pKa values, 2.35 and 9.69, but the fully protonated form is
positively charged. In order to define the neutral zwitterion, only the
positively charged Acid object needs to be defined. The charge balance in this
case implies a single equivalent of OH-.>>>ala=Acid(pKa=[2.35,9.69],charge=1,conc=0.01)>>>system=System(ala)>>>system.pHsolve()>>>print(system.pH)# Should print 6.0991569pH of 0.01 M (NH4)3PO4This is equivalent to a 1:3 mixture of H3PO4and NH4+, both of which are
defined by Acid objects. Three equivalents of OH-are implied to balance the
charge of the system.>>>phos=Acid(pKa=[2.148,7.198,12.319],charge=0,conc=0.01)>>>nh4=Acid(pKa=9.25,charge=1,conc=0.01*3)>>>system=System(phos,nh4)>>>system.pHsolve()>>>print(system.pH)# Should print 8.95915298Distribution DiagramsAcid objects also define a function calledalpha, which calculates the
fractional distribution of species at a given pH. This function can be used to
create distribution diagrams for weak acid species.alphatakes a single
argument, which is a single pH value or a Numpy array of values. For a single
pH value, the function returns a Numpy array of fractional distributions
ordered from most acid to least acidic species.>>>phos=Acid(pKa=[2.148,7.198,12.319],charge=0,conc=0.01)>>>phos.alpha(7.0)array([8.6055e-06,6.1204e-01,3.8795e-01,1.8611e-06])>>># This is H3PO4, H2PO4-, HPO4_2-, and HPO4_3-For a Numpy array og pH values, a 2D array of fractional distribution values
is returned, where each row is a series of distributions for each given pH.
The 2D returned array can be used to plot a distribution diagram.>>>phos=Acid(pKa=[2.148,7.198,12.319],charge=0,conc=0.01)>>>phs=np.linspace(0,14,1000)>>>fracs=phos.alpha(phs)>>>plt.plot(phs,fracs)>>>plt.legend(['H3PO4','H2PO4^1-','HPO4^2-','PO4^3-'])>>>plt.show()Titration CurvesUsing a simple loop, we can also construct arbitrary titration curves as well.
In this example, we will titrate H3PO4with NaOH. Theguess_estkeyword
argument for theSystem.pHsolvemethod forces the calculation of a best
guess for starting the pH optimization algorithm. This may speed up the
evaluation of the pH and can also be used if the minimizer throws an error
during the pH calculation.>>>na_moles=np.linspace(1e-8,5.e-3,500)>>>sol_volume=1.# Liter>>>phos=Acid(pKa=[2.148,7.198,12.375],charge=0,conc=1.e-3)>>>phs=[]>>>formolinna_moles:>>>na=Inert(charge=1,conc=mol/sol_volume)>>>system=System(phos,na)>>>system.pHsolve(guess_est=True)>>>phs.append(system.pH)>>>plt.plot(na_moles,phs)>>>plt.show()