Using the Density of Water to Calibrate Volumetric Glassware
Stephen Lukacs (2) iquanta.org/instruct/python
"""
reference: https://iquanta.org/instruct/python ::: Chemistry 1, The Density of Water ::: Stephen Lukacs, Ph.D. ©2023-10-22
"""
from py4web import URL, request
from yatl.helpers import *
from iquanta.mcp import is_str_float, str_to_float
from numpy import mean, polyfit
import plotly.graph_objects as go
BR, B = TAG['br/'], TAG['b']
<page_scripts>
<style>
</style>
</page_scripts>
def doFit(x, y, deg):
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)
data1 = ((0, 0.99987,), (4.0, 1.00000,), (4.4, 0.99999,), (10, 0.99975,), (15.6, 0.99907,), (21, 0.99802,), (26.7, 0.99669,), (32.2, 0.99510,), (37.8, 0.99318,), (48.9, 0.98870,), (60, 0.98338,), (71.1, 0.97729,), (82.2, 0.97056,), (93.3, 0.96333,), (100, 0.95865,),)
data2 = ((0, 0.9998395,), (4, 0.9999720,), (10, 0.9997026,), (15, 0.9991026,), (20, 0.9982071,), (22, 0.9977735,), (25, 0.9970479,), (30, 0.9956502,), (40, 0.9922,), (60, 0.9832,), (80, 0.9718,), (100, 0.9584,),)
data3 = ((10, 0.9997026,), (11, 0.9996084,), (12, 0.9995004,), (13, 0.9993801,), (14, 0.9992474,), (15, 0.9991026,), (16, 0.9989460,), (17, 0.9987779,), (18, 0.9985986,), (19, 0.9984082,), (20, 0.9982071,), (21, 0.9979955,), (22, 0.9977735,), (23, 0.9975415,), (24, 0.9972995,), (25, 0.9970479,), (26, 0.9967867,), (27, 0.9965162,), (28, 0.9962365,), (29, 0.9959478,), (30, 0.9956502,),)
data4 = ((0, 0.999842), (1, 0.999901), (2, 0.999942), (3, 0.999966), (4, 0.999974), (5, 0.999966), (6, 0.999942), (7, 0.999904), (8, 0.999805), (9, 0.999783), (10, 0.999702), (11, 0.999607), (12, 0.999500), (13, 0.999379), (14, 0.999247), (15, 0.999102), (16, 0.998945), (17, 0.998777), (18, 0.998598), (19, 0.998408), (20, 0.998207), (21, 0.997995), (22, 0.997773), (23, 0.997541), (24, 0.997299), (25, 0.997047), (26, 0.996786), (27, 0.996515), (28, 0.996235), (29, 0.995947), (30, 0.995649), (37, 0.993329),)
data5 = ((0, 0.99982), (1, 0.99989), (2, 0.99994), (3, 0.99998), (4, 1.0), (5, 1.0), (6, 0.99999), (7, 0.99996), (8, 0.99991), (9, 0.99985), (10, 0.99977), (11, 0.99968), (12, 0.99958), (13, 0.99946), (14, 0.99933), (15, 0.99919), (16, 0.99903), (17, 0.99886), (18, 0.99868), (19, 0.99849), (20, 0.99829), (21, 0.99808), (22, 0.99786), (23, 0.99762), (24, 0.99738), (25, 0.99713), (26, 0.99686), (27, 0.99659), (28, 0.99631), (29, 0.99602), (30, 0.99571), (31, 0.99541), (32, 0.99509), (33, 0.99476), (34, 0.99443), (35, 0.99408), (36, 0.99373), (37, 0.99337), (38, 0.993), (39, 0.99263), (40, 0.99225), (41, 0.99186), (42, 0.99146), (43, 0.99105), (44, 0.99064), (45, 0.99022), (46, 0.9898), (47, 0.98936), (48, 0.98892), (49, 0.98847), (50, 0.98802), (51, 0.98756), (52, 0.98709), (53, 0.98662), (54, 0.98614), (55, 0.98565), (56, 0.98516), (57, 0.98466), (58, 0.98416), (59, 0.98364), (60, 0.98313), (61, 0.9826), (62, 0.98207), (63, 0.98154), (64, 0.981), (65, 0.98045), (66, 0.9799), (67, 0.97934), (68, 0.97878), (69, 0.97821), (70, 0.97763), (71, 0.97705), (72, 0.97647), (73, 0.97588), (74, 0.97528), (75, 0.97468), (76, 0.97408), (77, 0.97346), (78, 0.97285), (79, 0.97223), (80, 0.9716), (81, 0.97097), (82, 0.97033), (83, 0.96969), (84, 0.96904), (85, 0.96839), (86, 0.96773), (87, 0.96707), (88, 0.96641), (89, 0.96574), (90, 0.96506), (91, 0.96438), (92, 0.9637), (93, 0.96301), (94, 0.96231), (95, 0.96162), (96, 0.96091), (97, 0.9602), (98, 0.95949), (99, 0.95878), (100, 0.95805),)
#data = data1 + data2 + data3 + data4 + data5#adding lists makes one flattened list of (x,y) pairs
data = data5
x, y = [d[0] for d in data], [d[1] for d in data]
fit2nd, RSquared2nd = doFit(x, y, 2)
eq2nd = '{0:6E}*T^2 + {1:6E}*T + {2:6f}'.format(*fit2nd)
fit3rd, RSquared3rd = doFit(x, y, 3)
eq3rd = '{0:6E}*T^3 + {1:6E}*T^2 + {2:6E}*T + {3:6f}'.format(*fit3rd)
fit4th, RSquared4th = doFit(x, y, 4)
eq4th = '{0:6E}*T^4 + {1:6E}*T^3 + {2:6E}*T^2 + {3:6E}*T + {4:6f}'.format(*fit4th)
rtn = FORM(_method="post")
if ('temperature' in request.forms) and isinstance(request.forms['temperature'], str) and (request.forms['temperature'].strip() != ''):
try:
temperature = float(request.forms['temperature'])
except:
temperature = 22.5
s2, s3, s4 = eq2nd.replace('T', f'{temperature}').replace('^', '**'), eq3rd.replace('T', f'{temperature}').replace('^', '**'), eq4th.replace('T', f'{temperature}').replace('^', '**')
density = round(eval(s4), 7)
cat = CAT('the density of water is ', B(density, ' g/mL '), ' at ', B(XML(f'{temperature}°C')))
if ('mass' in request.forms) and isinstance(request.forms['mass'], str) and (request.forms['mass'].strip() != ''):
try:
mass = float(request.forms['mass'])
cat.append(CAT(', the calibrated volume of water is ', B(round(mass / density, 3), ' mL '), ' at ', B(XML(f'{temperature}°C'))))
except:
pass
rtn.append(P(cat, B("."), _style="margin: 0px; padding: 6px 0px; font-size: 16pt;",))
rtn.append(STYLE("input[type=text] { height: 30px; width: 120px; padding: 0px 2px; text-align: center; }"))
rtn.append(CAT(BR(), "Enter the temperature of the purified water: ", INPUT(_type="text", _name="temperature", _value=f"{temperature if ('temperature' in locals()) else 22.5}"), XML('°C, Enter the mass to calibrate: '), INPUT(_type="text", _name="mass", _value=f"{mass if ('mass' in locals()) else 100.0}"), XML("g "), INPUT(_type="submit", _value="Compute", _style="border: 2px solid gray;")))
rtn.append(CAT(*[BR()]*2, "The following equation is a fit to only the \"Valves 5\" data for it is the most complete and consistent compared to the other references:", BR(), XML("<b>d<sub>H2O</sub>(T(°C)) = "), eq4th, XML(" g/mL</b> ... R<sup>2</sup> = "), f'{RSquared4th:.8f}'))
fig = go.Figure()
xss = sorted(set([d[0] for d in data])) #x sorted set unique x
#using eval on the string eq3rd...
#s3 = eval(eq3rd.replace('^', '**'))
#or, using a lambda function...
#s3x = lambda T: fit3rd[0]*T**3 + fit3rd[1]*T**2 + fit3rd[2]*T + fit3rd[3]
s4x = lambda T: fit4th[0]*T**4 + fit4th[1]*T**3 + fit4th[2]*T**2 + fit4th[3]*T + fit4th[4]
#
fig.add_trace(go.Scatter(x=[d[0] for d in data1], y=[d[1] for d in data1], mode="markers", name="U.S.A. 1"))
fig.add_trace(go.Scatter(x=[d[0] for d in data2], y=[d[1] for d in data2], mode="markers", name="OpenStax 2", marker=go.scatter.Marker(symbol='x')))
fig.add_trace(go.Scatter(x=[d[0] for d in data3], y=[d[1] for d in data3], mode="markers", name="Harris 3", marker=go.scatter.Marker(symbol='diamond')))
fig.add_trace(go.Scatter(x=[d[0] for d in data4], y=[d[1] for d in data4], mode="markers", name="Harris 4", marker=go.scatter.Marker(symbol='cross')))
fig.add_trace(go.Scatter(x=xss, y=[s4x(d) for d in xss], mode="lines", name="fit", line=go.scatter.Line(width=2)))
fig.add_trace(go.Scatter(x=[d[0] for d in data5], y=[d[1] for d in data5], mode="markers", name="Valves 5", marker=go.scatter.Marker(size=3.5, symbol='square')))
fig.update_layout(xaxis=go.XAxis(title="Temperature (°C)"), yaxis=go.YAxis(title="Density (g/mL)"))
fig.update_layout(title_text="Density of Water at various Temperatures", height=750)
#fig = px.scatter(x=[d[0] for d in data1], y=[d[1] for d in data1])
html = fig.to_html()
rtn.append(CAT(BR(), XML(html[html.find('<div>'):html.rfind('</div>')+6])))
t1 = A('website', _href="https://www.usgs.gov/special-topics/water-science-school/science/water-density", _target="waterdensity")
t2 = A('website', _href="https://www.vip-ltd.co.uk/Expansion/Density_Of_Water_Tables.pdf", _target="waterdensity")
rtn.append(CAT(BR(), "The data, its temperature in °C, and its corresponding density of water:", OL(LI(XML(f"U.S. Department of the Interior, Bureau of Reclaimation, 1977, Ground Water Manual, from The Water Encyclopedia, Third Edition, Hydrologic Data and Internet Resources, Edited by Pedro Fierro, Jr. and Evan K. Nyler, (2007), {t1} → {data1}.")), LI(XML(f"OpenStax Chemistry: Atoms First 2ed. → {data2}.")), LI(XML(f"Harris, Daniel C. and Lucy, Charles A., Quantitative Chemical Analysis, 9th ed., (2016), W.H. Freeman and Company, New York, NY. → {data3}.")), LI(XML(f"Harris, Daniel C. and Lucy, Charles A., Quantitative Chemical Analysis, 10th ed., (2020), W.H. Freeman and Company, New York, NY. → {data4}.")), LI(XML(f"Valves Instruments Plus Ltd, Astley, Manchester, {t2} → {data5}.")))))