Conductive Titration Endpoint Determination/Analysis
Stephen Lukacs (2) iquanta.org/instruct/python
"""
reference: https://iquanta.org/instruct/python ::: Chemistry 07: Conductivity Titration ::: Stephen Lukacs, Ph.D. ©2023-03-07
"""
from py4web import URL, request
from ombott.request_pkg.helpers import FileUpload
from yatl.helpers import *
from iquanta.mcp import is_str_float, str_to_float, extra_x
from iquanta.chmpy import convert_vernier_raw_file__to__data
from numpy import array, mean, std, polyfit, diag, sqrt
from scipy import optimize
import plotly.graph_objects as go
BR, B = TAG['br/'], TAG['b']
demo_data1 = "Vernier Format 2\nuntitled.txt 5/17/2021 2:29:58\nRun 1\nvol NaOH\tConductivity\t\nv\tC\t\nml\t\xb5S/cm\t\n\n0\t2823\t\n.95\t3019\t\n1.97\t3167\t\n2.95\t3329\t\n3.85\t3465\t\n4.8\t3584\t\n5.99\t3755\t\n6.93\t3859\t\n7.95\t3992\t\n9.13\t4154\t\n10\t4251\t\n10.85\t4342\t\n11.89\t4464\t\n12.9\t4693\t\n14.02\t5047\t\n14.99\t5361\t\n15.9\t5701\t\n16.97\t5935\t\n17.9\t6318\t\n18.92\t6628\t\n19.83\t6852\t\n20.95\t7173\t\n21.87\t7409\t\n22.92\t7713\t\n23.92\t7975\t\n24.98\t8216\t\n25.88\t8448\t\n"
demo_data2 = "Vernier Format 2\r\nhL5c.txt 10/25/2022 12:25:25\r\nRun 1\r\nvol NaOH\tConductivity\t\r\nv\tC\t\r\n\t\xb5S/cm\t\r\n\r\n0\t2179\t\r\n1.03\t2270\t\r\n2.01\t2368\t\r\n3.07\t2473\t\r\n4.11\t2610\t\r\n4.97\t2696\t\r\n6.04\t2809\t\r\n7.01\t2901\t\r\n8\t2990\t\r\n9.01\t3084\t\r\n10.07\t3183\t\r\n11.2\t3281\t\r\n12.1\t3356\t\r\n13.03\t3440\t\r\n14.1\t3594\t\r\n15.04\t3836\t\r\n16.04\t4126\t\r\n17.1\t4375\t\r\n18.1\t4618\t\r\n19.01\t4831\t\r\n20.04\t5061\t\r\n20.94\t5251\t\r\n22.1\t5519\t\r\n23.1\t5713\t\r\n24.04\t5909\t\r\n25.04\t6113\t\r\n26.05\t6289\t\r\n27.13\t6489\t\r\n28.11\t6676\t\r\n29.1\t6846\t\r\n30.03\t7028\t\r\n31.04\t7217\t\r\n32.04\t7373\t\r\n33.04\t7541\t\r\n"
demo_data3 = "Vernier Format 2\nTrial 3 mb.txt 6/28/2021 10:6:11\nRun 1\nvol NaOH\tConductivity\t\nv\tC\t\nmL\t\xb5S/cm\t\n\n0.0\t401\t\n0.48\t355\t\n1.08\t391\t\n1.55\t454\t\n2.18\t548\t\n2.70\t639\t\n3.28\t733\t\n3.79\t818\t\n4.40\t925\t\n4.98\t1018\t\n5.55\t1127\t\n5.99\t1202\t\n6.48\t1277\t\n6.99\t1357\t\n7.52\t1434\t\n7.98\t1501\t\n8.55\t1581\t\n9.09\t1654\t\n9.68\t1730\t\n10.31\t1818\t\n10.88\t1894\t\n11.48\t1974\t\n11.98\t2030\t\n12.49\t2096\t\n12.98\t2157\t\n13.49\t2213\t\n13.99\t2269\t\n14.48\t2330\t\n14.99\t2389\t\n15.48\t2441\t\n15.97\t2496\t\n16.48\t2571\t\n16.97\t2628\t\n17.51\t2690\t\n17.99\t2742\t\n18.58\t2826\t\n18.99\t2971\t\n19.55\t3146\t\n20.17\t3337\t\n20.64\t3503\t\n21.21\t3696\t\n21.78\t3865\t\n22.38\t4080\t\n22.90\t4243\t\n23.47\t4401\t\n23.97\t4550\t\n24.46\t4697\t\n24.95\t4843\t\n25.41\t4972\t\n\n\nVernier Format 2\nTrial 2 mb.txt 6/28/2021 9:41:49\nRun 1\nVol NaOH\tConductivity\t\nV\tC\t\nmL\t\xb5S/cm\t\n\n0\t291\t\n0.70\t448\t\n1.27\t374\t\n1.81\t583\t\n2.48\t663\t\n2.88\t734\t\n3.47\t807\t\n3.87\t879\t\n4.40\t957\t\n4.86\t1023\t\n5.41\t1122\t\n5.88\t1181\t\n6.48\t1268\t\n6.88\t1325\t\n7.31\t1383\t\n7.81\t1446\t\n8.40\t1525\t\n8.96\t1592\t\n9.48\t1660\t\n9.97\t1717\t\n10.47\t1775\t\n10.92\t1831\t\n11.47\t1896\t\n11.97\t1949\t\n12.48\t2010\t\n12.98\t2074\t\n13.48\t2120\t\n13.97\t2169\t\n14.48\t2164\t\n14.98\t2272\t\n15.48\t2325\t\n15.91\t2370\t\n16.41\t2422\t\n16.97\t2471\t\n17.48\t2553\t\n17.93\t2594\t\n18.40\t2635\t\n18.88\t2681\t\n19.45\t2731\t\n19.92\t2775\t\n20.38\t2810\t\n20.88\t2854\t\n21.41\t2898\t\n21.90\t2940\t\n22.48\t2986\t\n22.88\t3021\t\n23.38\t3060\t\n23.81\t3095\t\n24.47\t3137\t\n24.98\t3182\t\n25.48\t3220\t\n25.97\t3257\t\n26.48\t3301\t\n26.91\t3349\t\n27.38\t3438\t\n27.81\t3562\t\n28.30\t3685\t\n28.88\t3828\t\n29.38\t3965\t\n29.89\t4110\t\n30.48\t4239\t\n30.90\t4357\t\n31.40\t4477\t\n31.88\t4595\t\n32.47\t4723\t\n32.88\t4830\t\n33.39\t4949\t\n33.90\t5069\t\n34.48\t5195\t\n34.88\t5286\t\n35.40\t5430\t\n"
demo_data4 = "Vernier Format 2\nlab7t3con.txt 7/26/2021 2:24:12\nRun 1\nvolNaOH\tConductivity\t\nv\tC\t\nmL\t\xb5S/cm\t\n\n0\t325\t\n.38\t301\t\n.95\t306\t\n1.34\t337\t\n1.84\t381\t\n2.36\t443\t\n2.8\t499\t\n3.35\t\t\n3.87\t610\t\n4.3\t661\t\n4.84\t725\t\n5.35\t783\t\n5.85\t840\t\n6.3\t893\t\n6.85\t949\t\n7.35\t1002\t\n7.74\t1095\t\n8.36\t1128\t\n8.74\t1215\t\n9.34\t1236\t\n9.95\t1293\t\n10.36\t1342\t\n10.77\t1383\t\n11.3\t1434\t\n11.83\t1482\t\n12.35\t1535\t\n12.95\t1591\t\n13.26\t1625\t\n13.76\t1668\t\n14.35\t1723\t\n14.85\t1770\t\n15.3\t1815\t\n15.85\t1852\t\n16.36\t1894\t\n16.84\t1943\t\n17.25\t1983\t\n17.86\t2037\t\n18.35\t2151\t\n18.85\t2249\t\n19.36\t2711\t\n19.9\t2558\t\n20.26\t2700\t\n20.74\t2761\t\n21.26\t2847\t\n21.8\t3037\t\n22.27\t3190\t\n22.8\t3312\t\n23.35\t3433\t\n23.8\t3558\t\n24.37\t3674\t\n24.85\t3789\t\n25.26\t3895\t\n25.85\t4055\t\n26.24\t4154\t\n26.9\t4276\t\n27.35\t4368\t\n27.85\t4494\t\n28.25\t4471\t\n28.86\t4729\t\n29.35\t5034\t\n29.95\t4961\t\n30.45\t5181\t\n31.06\t5210\t\n31.42\t5297\t\nVernier Format 2\nlab7t3con.txt 7/26/2021 2:24:12\nRun 2\nvolNaOH\tConductivity\t\nv\tC\t\nmL\t\xb5S/cm\t\n\n0\t247\t\n.47\t218\t\n.95\t243\t\n1.48\t293\t\n1.96\t349\t\n2.45\t409\t\n3.06\t482\t\n3.48\t537\t\n3.98\t594\t\n4.46\t660\t\n4.98\t723\t\n5.51\t786\t\n5.95\t830\t\n6.45\t898\t\n6.97\t954\t\n7.56\t1015\t\n8.06\t1078\t\n8.56\t1145\t\n9.06\t1202\t\n9.51\t1327\t\n9.98\t1480\t\n10.45\t1634\t\n11.06\t1811\t\n11.46\t1958\t\n12.06\t2130\t\n12.56\t2628\t\n13.06\t2433\t\n13.56\t2612\t\n14.46\t2747\t\n14.47\t2882\t\n15.01\t3028\t\n15.52\t3169\t\n16.05\t3313\t\n16.47\t3438\t\n17.06\t3584\t\n17.47\t3711\t\n18.06\t3844\t\n18.56\t4001\t\n19.06\t4119\t\n19.49\t4241\t\n19.97\t4350\t\n20.45\t4476\t\n21.06\t4620\t\n21.46\t4725\t\n22.01\t4852\t\n22.46\t4954\t\n22.97\t5073\t\n23.51\t5194\t\n23.97\t5308\t\n24.47\t5433\t\n24.95\t5527\t\n25.01\t5663\t\n25.51\t\t\n25.96\t5760\t\n26.45\t5850\t\n26.95\t5952\t\n27.51\t6064\t\n28.01\t6168\t\n28.56\t6272\t\n29.06\t6383\t\n29.56\t6478\t\n30.01\t6567\t\n30.46\t6658\t\n31.01\t6758\t\n31.46\t6855\t\n31.96\t6944\t\n32.45\t7029\t\n"
demo_data5 = "Vernier Format 2\nlab 7 conductivity.txt 7/10/2021 2:25:54\nRun 1\nvol of NaOH\tConductivity\t\nv\tC\t\nmL\t\xb5S/cm\t\n\n0.52\t218\t\n1.00\t249\t\n1.49\t308\t\n1.99\t370\t\n2.52\t442\t\n2.99\t512\t\n3.56\t589\t\n3.99\t654\t\n4.52\t723\t\n5.00\t792\t\n5.55\t860\t\n6.00\t925\t\n6.49\t982\t\n6.99\t1049\t\n7.55\t1136\t\n8.00\t1194\t\n8.54\t1258\t\n9.00\t1340\t\n9.50\t1519\t\n10.01\t1699\t\n10.56\t1894\t\n11.00\t2058\t\n11.57\t2237\t\n11.99\t2385\t\n12.03\t2599\t\n12.53\t2600\t\n13.07\t2774\t\n13.52\t2918\t\n14.05\t3092\t\n14.49\t3227\t\n15.05\t3397\t\n15.51\t3540\t\n15.99\t3677\t\n16.52\t3843\t\n16.99\t3989\t\n17.57\t4161\t\n18.57\t4315\t\n18.52\t4427\t\n19.02\t4560\t\n19.53\t4699\t\n19.99\t4823\t\n20.51\t4965\t\n21.01\t5094\t\n21.52\t5227\t\n22.01\t5347\t\n22.55\t5504\t\n22.99\t5606\t\n23.01\t5740\t\n24.00\t5858\t\n24.54\t5975\t\n25.00\t6088\t\nVernier Format 2\nlab 7 conductivity.txt 7/10/2021 2:25:54\nRun 2\nvol of NaOH\tConductivity\t\nv\tC\t\nmL\t\xb5S/cm\t\n\n0.51\t406\t\n1.00\t51\t\n1.49\t444\t\n2.07\t514\t\n2.51\t571\t\n2.99\t632\t\n3.57\t715\t\n4.00\t781\t\n5.05\t928\t\n5.54\t999\t\n6.02\t1066\t\n6.53\t1149\t\n7.01\t1216\t\n7.56\t1287\t\n8.04\t1353\t\n8.58\t1418\t\n9.01\t1475\t\n9.50\t1536\t\n9.99\t1594\t\n10.57\t1668\t\n10.98\t1712\t\n11.52\t1777\t\n12.04\t1832\t\n12.54\t1894\t\n13.00\t1952\t\n13.50\t2004\t\n14.01\t2057\t\n14.51\t2105\t\n15.02\t2157\t\n15.55\t2218\t\n15.99\t2256\t\n15.56\t2322\t\n17.00\t2362\t\n17.50\t2407\t\n18.02\t2456\t\n18.57\t2508\t\n19.08\t2583\t\n19.50\t2626\t\n20.00\t2665\t\n20.54\t2714\t\n21.02\t2753\t\n21.56\t2799\t\n22.00\t2831\t\n22.51\t2881\t\n22.99\t2917\t\n23.57\t2966\t\n24.00\t3000\t\n24.51\t3043\t\n25.00\t3083\t\n25.53\t3124\t\n26.05\t3165\t\n26.52\t3198\t\n27.00\t3257\t\n27.52\t3350\t\n28.00\t3468\t\n28.50\t3596\t\n29.01\t3731\t\n29.57\t3872\t\n30.00\t4003\t\n30.49\t4132\t\n31.00\t4259\t\n31.51\t4385\t\n32.01\t4506\t\n32.50\t4622\t\n33.02\t4746\t\n33.50\t4861\t\n34.03\t4993\t\n34.51\t5106\t\n35.00\t5220\t\n35.51\t5322\t\n36.01\t5466\t\n36.49\t5561\t\n37.06\t5694\t\n"
demo_data6 = "Vernier Format 2\n L5 biden NaOH conductivity.txt 2/4/2022 5:13:51\nRun 1\nvol NaOH\tConductivity\t\nv\tC\t\nmL\t\xb5S/cm\t\n\n0\t1274\t\n.88\t1511\t\n1.88\t1686\t\n2.88\t1792\t\n3.88\t1900\t\n4.88\t1999\t\n5.88\t2083\t\n6.88\t2230\t\n7.88\t2241\t\n8.88\t2339\t\n9.88\t2412\t\n10.88\t2490\t\n11.88\t2614\t\n12.88\t2699\t\n13.88\t2862\t\n14.88\t3074\t\n15.88\t3233\t\n16.88\t3447\t\n17.88\t3639\t\n18.88\t3895\t\n19.88\t3987\t\n20.88\t4180\t\n21.88\t4368\t\n22.88\t4609\t\n23.88\t4728\t\n24.88\t4836\t\n25.88\t4984\t\n26.88\t5231\t\n27.88\t5322\t\n28.88\t5506\t\n29.88\t5692\t\n"
<page_scripts>
<style>
input[type=text] { height: 30px; width: 70px; text-align: center; border-radius: 7px; }
input[type=file]::file-selector-button { width: 170px; border-radius: 7px; }
textarea { margin: 0px 2px; width: 410px; height: 230px; font-size: 12pt; border-radius: 5px; }
p { margin: 2px 0px; padding: 8px; border-radius: 10px; border: 2px solid silver; }
div.error { display: block; color: red; font-weight: bold; font-size: 16pt; background-color: white; }
/*textarea { margin: 0px 2px; width: 310px; height: 230px; border-radius: 5px; }*/
</style>
<script>
/*back-quote in javascript is a literal string just as r-string is in python. and a literal string is what we want because we do not want anything system processing these following strings until this code is presented to the client-side javascript processor...*/
var demo1 = [`{{=demo_data1}}`]
var demo2 = [`{{=demo_data2}}`];
var demo3 = [`{{=demo_data3}}`];
var demo4 = [`{{=demo_data4}}`];
var demo5 = [`{{=demo_data5}}`];
var demo6 = [`{{=demo_data6}}`];
jQuery(function() {
jQuery('select#demo').change(function(obj) {
var select = jQuery(this);
console.log('changed: "'+select.prop('id')+'", '+select.val());
if (select.val() == 'clear') {
jQuery('textarea[name="txtfile"]').val("");
} else if (select.val() == 'demo1') {
jQuery('textarea[name="txtfile"]').val(demo1[0]);
} else if (select.val() == 'demo2') {
jQuery('textarea[name="txtfile"]').val(demo2[0]);
} else if (select.val() == 'demo3') {
jQuery('textarea[name="txtfile"]').val(demo3[0]);
} else if (select.val() == 'demo4') {
jQuery('textarea[name="txtfile"]').val(demo4[0]);
} else if (select.val() == 'demo5') {
jQuery('textarea[name="txtfile"]').val(demo5[0]);
} else if (select.val() == 'demo6') {
jQuery('textarea[name="txtfile"]').val(demo6[0]);
}
});
});
jQuery(document).ready( function () {
var sx = "jQuery.version: "+jQuery.fn.jquery; //+" | jQuery.ui.version: "+jQuery.ui.version;
console.log("jQuery.document.ready from Chemistry07.js begin..."+sx);
//jQuery('textarea[name="txtfile"]').val(demo1[0]); jQuery('textarea[name="unknowns"]').val(demo1[1]);
console.log("reference: https://iquanta.org/instruct/python ::: Chemistry 07: Conductivity Titration ::: Stephen Lukacs, Ph.D. ©2023-03-07");
console.log("jQuery.document.ready from Chemistry07.js end");
});
</script>
</page_scripts>
rtn = FORM(_action=None, _method="post", _enctype="multipart/form-data")
data, txttype, txt = convert_vernier_raw_file__to__data(request.POST.get('labQ2file') if request.POST.get('labQ2file') else request.forms.get('txtfile'), False)
rtn.append(CAT(DIV(DIV("Designed to upload LabQuest2 text (txt) outputs...", BR(), INPUT(_type="file", _name="labQ2file", _style="width:500px; font-size: 12pt; background-color:yellow;"), XML(", Copy in Demo: "), SELECT(OPTION(""), OPTION("Reset/Clear All Inputs...", _value="clear"), OPTION("Demo 1: Standardizing NaOH with KHP.", _value="demo1"), OPTION("Demo 2: Standardizing NaOH with KHP.", _value="demo2"), OPTION("Demo 3: Two-Trial Vinegar with Standardized NaOH.", _value="demo3"), OPTION("Demo 4: Two-Trial Vinegar with Standardized NaOH.", _value="demo4"), OPTION("Demo 5: Two-Trial Vinegar with Standardized NaOH.", _value="demo5"), OPTION("Demo 6: Standardizing NaOH with KHP and sloppy endpoint.", _value="demo6"), _id="demo"), _style="background-color:none;"), DIV(XML("<b>OR</b>, manually enter data below..."), BR(), "(x, y) Data Entry.. .", BR(), TEXTAREA(txt if txt else demo_data1, _name="txtfile"), BR(), "Enter for example \"Run 1\" at the top of each run or trial.", _style="float:left; max-width:440px; margin-right:12px; background-color:none;"), DIV("Column for X Series:", INPUT(_type="text", _class="integer", _name="xseries", _value=xseries if ('xseries' in locals()) else 1), XML(" "), "Column for Y Series:", INPUT(_type="text", _class="integer", _name="yseries", _value=yseries if ('yseries' in locals()) else 2), BR(), INPUT(_type="submit", _value="Upload Data"), ", or, just Upload to run the demonstration.", "" if (txt is None) else CAT(" ", SPAN(txttype, _style="font-weight:bold; color:maroon;")), _style="float:left; max-width:800px; background-color:none;"), DIV(_style="float:none; clear:both;"))))
def fit_polynomial(x, y, deg=1):
fit, ymean = polyfit(x, y, deg, full=True), mean(y)
#RSquared verified with Mathematica LinearModelFit["RSquared"]
SSres, SStot = fit[1][0], sum([(d - ymean)**2 for d in y])
return fit[0], (1 - SSres / SStot)
linear = lambda x, m, b: m*x + b
residual_linear = lambda p, x, y: y - linear(x, *p)
conductance = lambda x, a, b, c: a*(x**1.5) + b*x + c
conductance_params = lambda x, params: conductance(x, *params)
residual_conductance = lambda p, x, y: y - conductance(x, *p)
def fit_conductance(x, y, initial_values=[-25., 250., 1000.]):
if isinstance(x, (list,tuple,)):
x = array(x)
if isinstance(y, (list,tuple,)):
y = array(y)
#return optimize.leastsq(residual_conductance, initial_values, args=(x, y))
#[a, b, c], conv = optimize.leastsq(residual_conductance, initial_values, args=(x, y))
params, conv = optimize.leastsq(residual_conductance, initial_values, args=(x, y), full_output=True)[:2]
residuals, ymean = [ residual_conductance(params, x, y)**2 for x, y in zip(x, y) ], mean(y)
SSres, SStot = sum(residuals), sum([(d - ymean)**2 for d in y])
correlation = (1 - SSres / SStot)
params_stddev = sqrt(diag(conv))
return params, correlation, params_stddev
def find_before_after_endpoint(data):
xx = [ ]
for i in range(5, len(data)-6):
xL, yL = [ d[0] for d in data[:i+1] ], [ d[1] for d in data[:i+1] ]
xR, yR = [ d[0] for d in data[i:] ], [ d[1] for d in data[i:] ]
left = fit_conductance(xL, yL)
right = fit_conductance(xR, yR)
xx.append(((i, data[i],), (i+1, data[i+1],), (2. - (left[1] + right[1])), left, right,))
ep, min_correlation = None, 99.
for x in xx:
if (x[2] < min_correlation):
min_correlation = x[2]
ep = x
return ep, xx
if (data is not None):
#rtn.append(CAT(len(data), " ... ", str(data)))
fig = go.Figure()
#fig = make_subplots(rows=len(data), cols=1, x_title='Volume Titrant (mL)', vertical_spacing=0.04, subplot_titles=[f"Trial {t}" for t in range(1, len(data)+1)], specs={{{"secondary_y": True}] for t in range(len(data))])
for trial, data in enumerate(data, 1):
data = data['data']
ba, all_fits = find_before_after_endpoint(data)
xL, yL = [ d[0] for d in data[:ba[0][0]+1] ], [ d[1] for d in data[:ba[0][0]+1] ]
xR, yR = [ d[0] for d in data[ba[1][0]:] ], [ d[1] for d in data[ba[1][0]:] ]
p = P(B(f"Trial #{trial}. Conductivity/Conductance .. ."), BR())
#rtn.append(CAT(B("data: "), str(data), BR(), B("ba: "), len(ba), "...", str(ba), BR()*2))
p.append(CAT(B("all_fits summation: "), XML(', '.join([ f'[{x[0][1][0]},{x[1][1][0]}]→<b>{x[2]:.6f}</b>' for x in all_fits])), BR()))#, B("before/after endpoint (ba): "), len(ba), "...", str(ba)))#, BR(), B("left data: "), str(xL), BR(), str(yL), BR(), B("right data: "), str(xR), BR(), str(yR), BR()*2))
p.append(CAT(SPAN("before/after endpoint (ba) summation: ", _style=""), SPAN(XML(f'{ba[0]}, {ba[1]} → <b>{ba[2]:.6f}</b>'), _style="font-size:20pt;"), BR()))
"""experiment in testing for correlation calculations against the leastsq and polyfit to a line and fit_conductance all for the left.
xLA, yLA, ymean = array(xL), array(yL), mean(yL)
left_lssq = optimize.leastsq(residual_linear, [100.,2189.], args=(xLA, yLA), full_output=True)
residuals = [ residual_linear(left_lssq[0], x, y)**2 for x,y in zip(xLA, yLA) ]
SStot = sum([(d - ymean)**2 for d in yLA])
left_lssq_correlation = 1 - (sum(residuals) / SStot)
left_line = polyfit(xLA, yLA, 1, full=True)
left_line_correlation = 1 - (left_line[1][0] / SStot)
left_cond = fit_conductance(xLA, yLA)
rtn.append(CAT(B("leastsq::: "), str(left_lssq), B(" ... residual: "), sum(residuals), B(", left_lssq_correlation: "), left_lssq_correlation, B(", param_stddev: "), sqrt(diag(left_lssq[1])), BR(), B("polyfit::: "), str(left_line), B(" ... residual: "), left_line[1][0], B(", left_line_correlation: "), left_line_correlation, BR(), B("fit_conductance::: "), str(left_cond)))
"""
left, right = ba[3], ba[4]
p.append(CAT(SPAN(XML(f'L<sub>left</sub> = ({left[0][0]:.4e})*V<sup>(3/2)</sup> + ({left[0][1]:.4e})*V + ({left[0][2]:.3f})'), _style="font-size:20pt; font-weight:bold;"), BR()))
p.append(CAT(SPAN(XML(f'L<sub>right</sub> = ({right[0][0]:.4e})*V<sup>(3/2)</sup> + ({right[0][1]:.4e})*V + ({right[0][2]:.3f})'), _style="font-size:20pt; font-weight:bold;"), BR()))
left_params, right_params = left[0], right[0]
intersection_params = [ (left_params[0]-right_params[0]), (left_params[1]-right_params[1]), (left_params[2]-right_params[2]) ] #has to be a list and not a tuple
#endpoint = optimize.fsolve(conductance_params, ba[0][1][0], args=intersection_params)[0]
endpoint = optimize.fsolve(conductance_params, ba[0][1][0], args=intersection_params)[0]
p.append(CAT(SPAN(f'computed end/equivalent point volume, ', _style="font-weight:normal;"), SPAN(XML(f'V<sub>endpoint</sub> = {endpoint:.2f} mL Titrant '), _style="font-size:20pt; font-weight:bold;"), SPAN(' (red dot below)', _style="font-size:12pt; color:red;")))
rtn.append(p)
x, y = [ d[0] for d in data ], [ d[1] for d in data ]
#to be efficient, only add extra x's between the before and after endpoint.
xss = extra_x([ ba[0][1][0], endpoint, ba[1][1][0] ], 9) #adds 9 points between the three x inputs, inclusively.
xLeftP1, xRightM1 = xL + xss[1:], xss[:-1] + xR
#rtn.append(CAT(BR(), str(xLeftP1), BR(), str(xRightM1)))
fig.add_trace(go.Scatter(x=xLeftP1, y=[conductance(d, *left_params) for d in xLeftP1], mode='lines', name="Left Fit", line=go.scatter.Line(width=3, color="green", dash="solid")))
fig.add_trace(go.Scatter(x=xRightM1, y=[conductance(d, *right_params) for d in xRightM1], mode='lines', name="Right Fit", line=go.scatter.Line(width=3, color="dodgerblue", dash="solid")))
fig.add_trace(go.Scatter(x=[endpoint], y=[conductance(endpoint, *left_params)], mode='markers', name="endpoint", marker=go.scatter.Marker(size=12, color="red")))
fig.add_trace(go.Scatter(x=x, y=y, mode='markers', name="data", marker=go.scatter.Marker(color="Black")))
fig.update_layout(xaxis=go.XAxis(title="Volume Titrant, V (mL)"), yaxis=go.YAxis(title="Conductivity, L (uS/cm)", anchor='x', side='left'))
fig.update_layout(title_text="Conductivity/Conductance Titration Endpoint Determination Calibration Curves", height=1050, margin=go.layout.Margin(l=25, r=25, b=60, t=60, pad=0), plot_bgcolor="#f5f5f5", paper_bgcolor="White", showlegend=True)
html = fig.to_html()
rtn.append(XML(html[html.find('<div>'):html.rfind('</div>')+6].replace('<div>', '<div id="plotly">')))
dv = DIV(H3("Understanding the Endpoint Determination/Analysis of Conductivity Titrations"), "As can be seen an aburpt change in slope occurs when the endpoint is passed with acid/base neutralization reactions using a conductivity probe or sensor. This aburpt change occurs because of the 2 to 4 times higher mobilities of the H+ and OH- ions of acid and base ions, respectively, of neutralization reactions. The mobilities of all the other ions are less than a hundred and can be verified under, ", A("Determination of Total Electrolytic Concentrations of Unknown Water Samples", _href=URL("python/exec/21"), _target="determination"), ".", *[BR()]*2, "We can exploit that aburpt change of neutralization reactions by using the conductivity probe and the physical properties of their mobilities to fit each side of the endpoint with its own fitted expression, or, \(L = a C^{3/2} + b C + c\), as derived and explained in the latter article. Therefore, this program scans each region between each pair of successive datapoints, performs a \(C^{3/2}\) fit on the left and right side of those consecutive pairs and computes a minimized sum of the least squares value. The left and right fits with the lowest least squares value is the best fit on both sides and the entire titration. The \(C^{3/2}\) fit parameters for both the left and right sides are then set equal to each other and a root-finder determines their intersection. The intersection of these two \(C^{3/2}\) fits is where the end or equivalence point is located, in mL of titrant. A careful study of the below algorithm will confirm the latter summary.", *[BR()]*2, "In conclusion, endpoint determinations using conductivity of neutralization reactions and respective titrations present a more physically relevant and concrete analytical solution then can be derived with potentiometric or pH probes.", _style="")
rtn.append(CAT(dv, BR(), "lecture by Stephen Lukacs, Ph.D., ©2011 - 2023; updated: March 7, 2023. all data confirmed via ", A("lecture_data_analysis.nb", _href=URL('static/pdf/lecture_data_analysis8.pdf'), _target="data_analysis"), "."))