pchemlibrary 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pchemlibrary-0.1.0/PKG-INFO +23 -0
- pchemlibrary-0.1.0/README.md +10 -0
- pchemlibrary-0.1.0/pchemlibrary/__init__.py +1 -0
- pchemlibrary-0.1.0/pchemlibrary/pl.py +499 -0
- pchemlibrary-0.1.0/pchemlibrary.egg-info/PKG-INFO +23 -0
- pchemlibrary-0.1.0/pchemlibrary.egg-info/SOURCES.txt +9 -0
- pchemlibrary-0.1.0/pchemlibrary.egg-info/dependency_links.txt +1 -0
- pchemlibrary-0.1.0/pchemlibrary.egg-info/requires.txt +5 -0
- pchemlibrary-0.1.0/pchemlibrary.egg-info/top_level.txt +3 -0
- pchemlibrary-0.1.0/pyproject.toml +15 -0
- pchemlibrary-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pchemlibrary
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Library for teaching physical chemistry using Jupyter Notebooks
|
|
5
|
+
Author-email: Steven Neshyba <sneshyba@gmail.com>
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: numpy
|
|
9
|
+
Requires-Dist: scipy
|
|
10
|
+
Requires-Dist: matplotlib
|
|
11
|
+
Requires-Dist: pint
|
|
12
|
+
Requires-Dist: plotly
|
|
13
|
+
|
|
14
|
+
This is a library of python functions that support Python-language codes for teaching physical chemistry.
|
|
15
|
+
|
|
16
|
+
# Installation
|
|
17
|
+
This has been packaged on PyPI. To install, run
|
|
18
|
+
```sh
|
|
19
|
+
python -m pip install pchemlib
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
# Usage
|
|
23
|
+
See examples in the examples folder.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
This is a library of python functions that support Python-language codes for teaching physical chemistry.
|
|
2
|
+
|
|
3
|
+
# Installation
|
|
4
|
+
This has been packaged on PyPI. To install, run
|
|
5
|
+
```sh
|
|
6
|
+
python -m pip install pchemlib
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
# Usage
|
|
10
|
+
See examples in the examples folder.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .pl import *
|
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from scipy.interpolate import RectBivariateSpline
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import plotly.graph_objects as go
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def Statespace(xspecs,yspecs):
|
|
8
|
+
if hasattr(xspecs[0],'units'): x0 = xspecs[0].magnitude
|
|
9
|
+
else: x0 = xspecs[0]
|
|
10
|
+
if hasattr(xspecs[1],'units'): x1 = xspecs[1].magnitude
|
|
11
|
+
else: x1 = xspecs[1]
|
|
12
|
+
if hasattr(yspecs[0],'units'): y0 = yspecs[0].magnitude
|
|
13
|
+
else: y0 = yspecs[0]
|
|
14
|
+
if hasattr(yspecs[1],'units'): y1 = yspecs[1].magnitude
|
|
15
|
+
else: y1 = yspecs[1]
|
|
16
|
+
|
|
17
|
+
xarray = np.linspace(x0,x1,xspecs[2])
|
|
18
|
+
yarray = np.linspace(y0,y1,yspecs[2])
|
|
19
|
+
ygridtemp,xgridtemp = np.meshgrid(yarray,xarray)
|
|
20
|
+
xgrid = xgridtemp
|
|
21
|
+
ygrid = ygridtemp
|
|
22
|
+
return xgrid, ygrid
|
|
23
|
+
|
|
24
|
+
def plot_surface(Xgrid_in, Ygrid_in, Zgrid_in, color='gray', overlay=False, ax=0):
|
|
25
|
+
"""
|
|
26
|
+
This plot_surface will be deprecated after 2023, to be replaced by plot_surface1
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
# Creates a surface plot in the handle myax
|
|
30
|
+
|
|
31
|
+
if overlay==False:
|
|
32
|
+
fig = plt.figure()
|
|
33
|
+
ax = plt.axes(projection='3d')
|
|
34
|
+
# ax = plt.figure().gca(projection='3d') # Set up a three dimensional graphics window
|
|
35
|
+
|
|
36
|
+
# This strips out units if necessary
|
|
37
|
+
if hasattr(Xgrid_in,'units'):
|
|
38
|
+
Xgrid = Xgrid_in.magnitude
|
|
39
|
+
else:
|
|
40
|
+
Xgrid = Xgrid_in
|
|
41
|
+
|
|
42
|
+
if hasattr(Ygrid_in,'units'):
|
|
43
|
+
Ygrid = Ygrid_in.magnitude
|
|
44
|
+
else:
|
|
45
|
+
Ygrid = Ygrid_in
|
|
46
|
+
|
|
47
|
+
if hasattr(Zgrid_in,'units'):
|
|
48
|
+
Zgrid = Zgrid_in.magnitude
|
|
49
|
+
else:
|
|
50
|
+
Zgrid = Zgrid_in
|
|
51
|
+
|
|
52
|
+
# Check to see if this is a scalar
|
|
53
|
+
if np.size(Zgrid) == 1:
|
|
54
|
+
Zgrid = Zgrid*np.ones(np.shape(Xgrid))
|
|
55
|
+
|
|
56
|
+
# Now plot
|
|
57
|
+
ax.plot_surface(Xgrid, Ygrid, Zgrid, color=color)
|
|
58
|
+
|
|
59
|
+
# Now return the handle
|
|
60
|
+
return ax
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# def plot_surface1(Xgrid_in, Ygrid_in, Zgrid_in, color='gray', labellist=[], title=''):
|
|
64
|
+
|
|
65
|
+
# # Initiates a surface plot
|
|
66
|
+
# fig = plt.figure()
|
|
67
|
+
# ax = plt.axes(projection='3d')
|
|
68
|
+
|
|
69
|
+
# # This strips out units if necessary
|
|
70
|
+
# if hasattr(Xgrid_in,'units'):
|
|
71
|
+
# Xgrid = Xgrid_in.magnitude
|
|
72
|
+
# else:
|
|
73
|
+
# Xgrid = Xgrid_in
|
|
74
|
+
|
|
75
|
+
# if hasattr(Ygrid_in,'units'):
|
|
76
|
+
# Ygrid = Ygrid_in.magnitude
|
|
77
|
+
# else:
|
|
78
|
+
# Ygrid = Ygrid_in
|
|
79
|
+
|
|
80
|
+
# if hasattr(Zgrid_in,'units'):
|
|
81
|
+
# Zgrid = Zgrid_in.magnitude
|
|
82
|
+
# else:
|
|
83
|
+
# Zgrid = Zgrid_in
|
|
84
|
+
|
|
85
|
+
# # Now plot
|
|
86
|
+
# ax.plot_surface(Xgrid, Ygrid, Zgrid, color=color)
|
|
87
|
+
|
|
88
|
+
# if len(labellist) != 0:
|
|
89
|
+
# ax.set_xlabel(labellist[0])
|
|
90
|
+
# ax.set_ylabel(labellist[1])
|
|
91
|
+
# ax.set_zlabel(labellist[2])
|
|
92
|
+
|
|
93
|
+
# if title != '':
|
|
94
|
+
# ax.set_title(title)
|
|
95
|
+
|
|
96
|
+
# # Now return the handle
|
|
97
|
+
# return ax
|
|
98
|
+
|
|
99
|
+
def plot_surface1(Xgrid_in, Ygrid_in, Zgrid_in, color='gray', labellist=[], title='', zrange=[]):
|
|
100
|
+
fig = plot_surfaces(Xgrid_in, Ygrid_in, [Zgrid_in], colorlist=[color], labellist=labellist, titlelist=[title])
|
|
101
|
+
if len(zrange) != 0:
|
|
102
|
+
fig.update_layout(scene = dict(zaxis = dict(range=zrange)))
|
|
103
|
+
return fig
|
|
104
|
+
|
|
105
|
+
def plot_surfaces(Xgrid_in, Ygrid_in, Zgridlist_in, colorlist=[], labellist=[], titlelist=[]):
|
|
106
|
+
|
|
107
|
+
if len(colorlist) == 0:
|
|
108
|
+
print('colorlist has length zero')
|
|
109
|
+
colorlist = ['blues','greens','mint','reds']
|
|
110
|
+
else:
|
|
111
|
+
for i in range(len(colorlist)):
|
|
112
|
+
if colorlist[i] == 'plum': colorlist[i]='purples'
|
|
113
|
+
if colorlist[i] == 'blue': colorlist[i]='blues'
|
|
114
|
+
if colorlist[i] == 'green': colorlist[i]='greens'
|
|
115
|
+
if colorlist[i] == 'red': colorlist[i]='reds'
|
|
116
|
+
if colorlist[i] == 'purple': colorlist[i]='purples'
|
|
117
|
+
|
|
118
|
+
# This strips out units if necessary
|
|
119
|
+
if hasattr(Xgrid_in,'units'):
|
|
120
|
+
Xgrid = Xgrid_in.magnitude
|
|
121
|
+
else:
|
|
122
|
+
Xgrid = Xgrid_in
|
|
123
|
+
|
|
124
|
+
if hasattr(Ygrid_in,'units'):
|
|
125
|
+
Ygrid = Ygrid_in.magnitude
|
|
126
|
+
else:
|
|
127
|
+
Ygrid = Ygrid_in
|
|
128
|
+
|
|
129
|
+
if hasattr(Zgridlist_in[0],'units'):
|
|
130
|
+
Zgridlist = Zgridlist_in
|
|
131
|
+
for i in range(len(Zgridlist)):
|
|
132
|
+
Zgridlist[i] = Zgridlist_in[i].magnitude
|
|
133
|
+
else:
|
|
134
|
+
Zgridlist = Zgridlist_in
|
|
135
|
+
|
|
136
|
+
# Graphing all the surfaces
|
|
137
|
+
if len(Zgridlist) == 1:
|
|
138
|
+
fig = go.Figure(data=[
|
|
139
|
+
go.Surface(x=Xgrid,y=Ygrid,z=Zgridlist[0], colorscale=colorlist[0], showscale=False)])
|
|
140
|
+
|
|
141
|
+
elif len(Zgridlist) == 2:
|
|
142
|
+
fig = go.Figure(data=[
|
|
143
|
+
go.Surface(x=Xgrid,y=Ygrid,z=Zgridlist[1], colorscale=colorlist[1], showscale=False),
|
|
144
|
+
go.Surface(x=Xgrid,y=Ygrid,z=Zgridlist[0], colorscale=colorlist[0], showscale=False)])
|
|
145
|
+
|
|
146
|
+
elif len(Zgridlist) == 3:
|
|
147
|
+
fig = go.Figure(data=[
|
|
148
|
+
go.Surface(x=Xgrid,y=Ygrid,z=Zgridlist[2], colorscale=colorlist[2], showscale=False),
|
|
149
|
+
go.Surface(x=Xgrid,y=Ygrid,z=Zgridlist[1], colorscale=colorlist[1], showscale=False),
|
|
150
|
+
go.Surface(x=Xgrid,y=Ygrid,z=Zgridlist[0], colorscale=colorlist[0], showscale=False)])
|
|
151
|
+
|
|
152
|
+
elif len(Zgridlist) == 4:
|
|
153
|
+
fig = go.Figure(data=[
|
|
154
|
+
go.Surface(x=Xgrid,y=Ygrid,z=Zgridlist[3], colorscale=colorlist[3], showscale=False),
|
|
155
|
+
go.Surface(x=Xgrid,y=Ygrid,z=Zgridlist[2], colorscale=colorlist[2], showscale=False),
|
|
156
|
+
go.Surface(x=Xgrid,y=Ygrid,z=Zgridlist[1], colorscale=colorlist[1], showscale=False),
|
|
157
|
+
go.Surface(x=Xgrid,y=Ygrid,z=Zgridlist[0], colorscale=colorlist[0], showscale=False)])
|
|
158
|
+
|
|
159
|
+
elif len(Zgridlist) >= 5:
|
|
160
|
+
fig = go.Figure(data=[
|
|
161
|
+
go.Surface(x=Xgrid,y=Ygrid,z=Zgridlist[4], colorscale=colorlist[4], showscale=False),
|
|
162
|
+
go.Surface(x=Xgrid,y=Ygrid,z=Zgridlist[3], colorscale=colorlist[3], showscale=False),
|
|
163
|
+
go.Surface(x=Xgrid,y=Ygrid,z=Zgridlist[2], colorscale=colorlist[2], showscale=False),
|
|
164
|
+
go.Surface(x=Xgrid,y=Ygrid,z=Zgridlist[1], colorscale=colorlist[1], showscale=False),
|
|
165
|
+
go.Surface(x=Xgrid,y=Ygrid,z=Zgridlist[0], colorscale=colorlist[0], showscale=False)])
|
|
166
|
+
if len(Zgridlist) > 5:
|
|
167
|
+
print('From plot_multiple_surfaces: too many surfaces to overlap, sorry')
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
mytitlestring = ''
|
|
171
|
+
if len(titlelist) != 0:
|
|
172
|
+
if len(titlelist[0]) != 0:
|
|
173
|
+
# print('option 1')
|
|
174
|
+
for i in range(len(titlelist)):
|
|
175
|
+
mytitlestring += titlelist[i]+'='+str(colorlist[i])+' '
|
|
176
|
+
|
|
177
|
+
if len(labellist) != 0 and len(titlelist) == 0:
|
|
178
|
+
# print('option 2')
|
|
179
|
+
fig.update_layout(scene = dict(
|
|
180
|
+
xaxis_title=labellist[0],
|
|
181
|
+
yaxis_title=labellist[1],
|
|
182
|
+
zaxis_title=labellist[2]))
|
|
183
|
+
|
|
184
|
+
elif len(labellist) != 0 and len(titlelist) != 0:
|
|
185
|
+
# print('option 3')
|
|
186
|
+
fig.update_layout(scene = dict(
|
|
187
|
+
xaxis_title=labellist[0],
|
|
188
|
+
yaxis_title=labellist[1],
|
|
189
|
+
zaxis_title=labellist[2]),
|
|
190
|
+
title=mytitlestring)
|
|
191
|
+
|
|
192
|
+
elif len(labellist) == 0 and len(titlelist) != 0:
|
|
193
|
+
# print('option 4')
|
|
194
|
+
fig.update_layout(
|
|
195
|
+
title=mytitlestring)
|
|
196
|
+
|
|
197
|
+
fig.update_layout(autosize=True)
|
|
198
|
+
fig.update_yaxes(automargin=True)
|
|
199
|
+
mysize = 10
|
|
200
|
+
fig.update_scenes(xaxis = dict(tickfont=dict(size=mysize),titlefont=dict(size=mysize)))
|
|
201
|
+
fig.update_scenes(yaxis = dict(tickfont=dict(size=mysize),titlefont=dict(size=mysize)))
|
|
202
|
+
fig.update_scenes(zaxis = dict(tickfont=dict(size=mysize),titlefont=dict(size=mysize)))
|
|
203
|
+
|
|
204
|
+
return fig
|
|
205
|
+
|
|
206
|
+
def dF_dx(statespace,Fgrid):
|
|
207
|
+
# Returns the partial of F with respect to x (axis 0) holding y (axis 1) constant
|
|
208
|
+
xgrid = statespace[0]
|
|
209
|
+
ygrid = statespace[1]
|
|
210
|
+
dF = np.diff(Fgrid.magnitude,axis=0)
|
|
211
|
+
dx = np.diff(xgrid.magnitude,axis=0)
|
|
212
|
+
dF_dx = dF/dx
|
|
213
|
+
print('Shape of partial derivative =', np.shape(dF_dx))
|
|
214
|
+
try:
|
|
215
|
+
dF_dx *= Fgrid.units/xgrid.units
|
|
216
|
+
# print('Units of partial derivative =', dF_dx.units)
|
|
217
|
+
except:
|
|
218
|
+
print('No units')
|
|
219
|
+
xgridnew = xgrid[1:,:]
|
|
220
|
+
ygridnew = ygrid[1:,:]
|
|
221
|
+
return xgridnew, ygridnew, dF_dx
|
|
222
|
+
|
|
223
|
+
def dF_dy(statespace,Fgrid):
|
|
224
|
+
# Returns the partial of F with respect to y (axis 1) holding x (axis 0) constant
|
|
225
|
+
xgrid = statespace[0]
|
|
226
|
+
ygrid = statespace[1]
|
|
227
|
+
dF = np.diff(Fgrid.magnitude,axis=1)
|
|
228
|
+
dy = np.diff(ygrid.magnitude,axis=1)
|
|
229
|
+
dF_dy = dF/dy
|
|
230
|
+
print('Shape of partial derivative =', np.shape(dF_dy))
|
|
231
|
+
try:
|
|
232
|
+
dF_dy *= Fgrid.units/ygrid.units
|
|
233
|
+
#print('Units of partial derivative =', dF_dy.units)
|
|
234
|
+
except:
|
|
235
|
+
print('No units')
|
|
236
|
+
xgridnew = xgrid[:,1:]
|
|
237
|
+
ygridnew = ygrid[:,1:]
|
|
238
|
+
return xgridnew, ygridnew, dF_dy
|
|
239
|
+
|
|
240
|
+
def func_P_isotherm(V1,V2,n,R,T,AssignQuantity,P_units):
|
|
241
|
+
# Defines an isothermal expansion/contraction function
|
|
242
|
+
Varray = np.linspace(V1,V2)
|
|
243
|
+
Varray = AssignQuantity(Varray,V1.units)
|
|
244
|
+
Parray = n*R*T/Varray
|
|
245
|
+
Parray.ito(P_units)
|
|
246
|
+
return Varray, Parray
|
|
247
|
+
|
|
248
|
+
def func_P_adiabat(V1,V2,n,R,T1,C_V,AssignQuantity,P_units):
|
|
249
|
+
# Defines an adiabatic expansion/contraction function
|
|
250
|
+
V2array = np.linspace(V1,V2)
|
|
251
|
+
V2array = AssignQuantity(V2array,V2.units)
|
|
252
|
+
P1 = n*R*T1/V1
|
|
253
|
+
nR_over_C_V = n*R/C_V
|
|
254
|
+
P2array = P1*(V2array/V1)**(-nR_over_C_V-1)
|
|
255
|
+
P2array.ito(P_units)
|
|
256
|
+
return V2array, P2array
|
|
257
|
+
|
|
258
|
+
def CP_H2Ogas(T,AssignQuantity):
|
|
259
|
+
""" www.engineeringtoolbox.com/water-vapor-d_979.html """
|
|
260
|
+
m = AssignQuantity(0.0067,'J/mol/K^2')
|
|
261
|
+
CP0 = AssignQuantity(33.58,'J/mol/K')
|
|
262
|
+
T0 = AssignQuantity(300,'K')
|
|
263
|
+
CP = CP0 + m*(T-T0)
|
|
264
|
+
return CP
|
|
265
|
+
|
|
266
|
+
def CP_H2Oice(T,AssignQuantity):
|
|
267
|
+
""" www.liquisearch.com/heat_capacity/table_of_specific_heat_capacities """
|
|
268
|
+
CP = AssignQuantity(38.0,'J/mol/K')
|
|
269
|
+
return CP
|
|
270
|
+
|
|
271
|
+
def CP_H2Oliq(T,AssignQuantity):
|
|
272
|
+
""" https://webbook.nist.gov/cgi/cbook.cgi?ID=C7732185&Units=SI&Mask=2#Thermo-Condensed """
|
|
273
|
+
A = AssignQuantity(-203.606,'J/mol/K')
|
|
274
|
+
B = AssignQuantity(1523.290,'J/mol/K^2')
|
|
275
|
+
C = AssignQuantity(-3196.413,'J/mol/K^3')
|
|
276
|
+
D = AssignQuantity(2474.455,'J/mol/K^4')
|
|
277
|
+
E = AssignQuantity(3.855326,'J/mol K')
|
|
278
|
+
t = T/1000
|
|
279
|
+
CP = A + B*t + C*t**2 + D*t**3 + E/t**2
|
|
280
|
+
return CP
|
|
281
|
+
|
|
282
|
+
def Integrator_new(statespace,dF_dx,dF_dy,AssignQuantity,SState=[],Units=[],axis=0):
|
|
283
|
+
"""
|
|
284
|
+
Integrates a differential equation of state to produce F(x,y)
|
|
285
|
+
Assumes quantities have units
|
|
286
|
+
"""
|
|
287
|
+
# from scipy.interpolate import RectBivariateSpline
|
|
288
|
+
# from scipy import interpolate
|
|
289
|
+
# This used to be called Integrator_pint
|
|
290
|
+
|
|
291
|
+
dF_dx_local = dF_dx.to_base_units(); #print('dF_dx.units', dF_dx_local.units)
|
|
292
|
+
dF_dy_local = dF_dy.to_base_units(); #print('dF_dy.units', dF_dy_local.units)
|
|
293
|
+
|
|
294
|
+
xgrid = statespace[0]
|
|
295
|
+
xgrid_local = xgrid.to_base_units()
|
|
296
|
+
ygrid = statespace[1]
|
|
297
|
+
ygrid_local = ygrid.to_base_units()
|
|
298
|
+
|
|
299
|
+
dx = xgrid_local[1,0]-xgrid_local[0,0]
|
|
300
|
+
dy = ygrid_local[0,1]-ygrid_local[0,0]
|
|
301
|
+
|
|
302
|
+
nx,ny = np.shape(xgrid)
|
|
303
|
+
Fgrid = np.zeros(np.shape(xgrid))
|
|
304
|
+
|
|
305
|
+
# If we're getting scalars, convert them
|
|
306
|
+
if np.size(dF_dx_local) == 1:
|
|
307
|
+
dF_dx_local = dF_dx_local*np.ones(np.shape(xgrid_local))
|
|
308
|
+
if np.size(dF_dy) == 1:
|
|
309
|
+
dF_dy_local = dF_dy_local*np.ones(np.shape(xgrid_local))
|
|
310
|
+
|
|
311
|
+
# Branch according to which axis to integrate along first
|
|
312
|
+
if axis==0:
|
|
313
|
+
integral_along_x = np.cumsum(dF_dx_local[:,0])*dx
|
|
314
|
+
for i in range(nx):
|
|
315
|
+
integral_along_y = np.cumsum(dF_dy_local[i,:])*dy
|
|
316
|
+
integral_along_y += integral_along_x[i]
|
|
317
|
+
Fgrid[i,:] = integral_along_y.magnitude
|
|
318
|
+
else:
|
|
319
|
+
integral_along_y = np.cumsum(dF_dy_local[0,:])*dy
|
|
320
|
+
for i in range(ny):
|
|
321
|
+
integral_along_x = np.cumsum(dF_dx_local[:,i])*dx
|
|
322
|
+
integral_along_x += integral_along_y[i]
|
|
323
|
+
Fgrid[:,i] = integral_along_x.magnitude
|
|
324
|
+
|
|
325
|
+
# Apply an offset if desired
|
|
326
|
+
debugging = False
|
|
327
|
+
Fgrid = AssignQuantity(Fgrid,dF_dx_local.units*dx.units)
|
|
328
|
+
if debugging: print('Fgrid.units:', Fgrid.units)
|
|
329
|
+
if len(SState) != 0:
|
|
330
|
+
SState_x = SState[0]; SState_x.ito_base_units()
|
|
331
|
+
if debugging: print('SState_x:', SState_x)
|
|
332
|
+
SState_y = SState[1]; SState_y.ito_base_units()
|
|
333
|
+
if debugging: print('SState_y:', SState_y)
|
|
334
|
+
SState_F = SState[2]; SState_F.ito_base_units()
|
|
335
|
+
if debugging: print('SState_F:', SState_F)
|
|
336
|
+
if debugging: print('Origin', xgrid[0,0],ygrid[0,0])
|
|
337
|
+
if debugging: print('Standard states', SState_x,SState_y)
|
|
338
|
+
|
|
339
|
+
# This shouldn't have to be done, but I can't get the interpolator to work properly
|
|
340
|
+
ix_SS = nx-1 # Just a starting guess, it'll get over-ridden below
|
|
341
|
+
last_deviation = (xgrid[ix_SS,0] - SState_x)**2
|
|
342
|
+
for ix in range(nx):
|
|
343
|
+
deviation = (xgrid[ix,0] - SState_x)**2
|
|
344
|
+
if (deviation < last_deviation):
|
|
345
|
+
last_deviation = deviation
|
|
346
|
+
ix_SS = ix
|
|
347
|
+
iy_SS = ny-1 # Just a starting guess, it'll get over-ridden below
|
|
348
|
+
last_deviation = (ygrid[0,iy_SS] - SState_y)**2
|
|
349
|
+
for iy in range(ny):
|
|
350
|
+
deviation = (ygrid[0,iy] - SState_y)**2
|
|
351
|
+
if (deviation < last_deviation):
|
|
352
|
+
last_deviation = deviation
|
|
353
|
+
iy_SS = iy
|
|
354
|
+
|
|
355
|
+
# Find the value to be subtracted away
|
|
356
|
+
Original_Fgrid_at_standard_state = np.squeeze(Fgrid[ix_SS,iy_SS])
|
|
357
|
+
if debugging: print('Indices closest to standard state', ix_SS, iy_SS)
|
|
358
|
+
if debugging: print('Original Fgrid at standard state', Original_Fgrid_at_standard_state)
|
|
359
|
+
|
|
360
|
+
# Create the new Fgrid
|
|
361
|
+
new_Fgrid = Fgrid -Original_Fgrid_at_standard_state +SState_F
|
|
362
|
+
|
|
363
|
+
if len(Units) != 0:
|
|
364
|
+
new_Fgrid.ito(Units)
|
|
365
|
+
|
|
366
|
+
return(new_Fgrid)
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def Integrator(statespace,dF_dx,dF_dy,AssignQuantity,SState=[],Units=[],axis=0):
|
|
370
|
+
"""
|
|
371
|
+
Integrates a differential equation of state to produce F(x,y)
|
|
372
|
+
"""
|
|
373
|
+
from scipy.interpolate import RectBivariateSpline
|
|
374
|
+
xgrid = statespace[0]
|
|
375
|
+
ygrid = statespace[1]
|
|
376
|
+
xarray = xgrid[:,0]; dx = (xarray[1]-xarray[0]); #print('dx=',dx)
|
|
377
|
+
yarray = ygrid[0,:]; dy = (yarray[1]-yarray[0]); #print('dy=',dy)
|
|
378
|
+
Fgrid = np.zeros(np.shape(xgrid))
|
|
379
|
+
|
|
380
|
+
# Branch according to which axis to integrate along first
|
|
381
|
+
if axis==0:
|
|
382
|
+
integral_along_x = np.cumsum(dF_dx[:,0])*dx
|
|
383
|
+
for i in range(len(xarray)):
|
|
384
|
+
integral_along_y = np.cumsum(dF_dy[i,:])*dy
|
|
385
|
+
integral_along_y += integral_along_x[i]
|
|
386
|
+
Fgrid[i,:] = integral_along_y
|
|
387
|
+
else:
|
|
388
|
+
integral_along_y = np.cumsum(dF_dy[0,:])*dy
|
|
389
|
+
for i in range(len(yarray)):
|
|
390
|
+
integral_along_x = np.cumsum(dF_dx[:,i])*dx
|
|
391
|
+
integral_along_x += integral_along_y[i]
|
|
392
|
+
Fgrid[:,i] = integral_along_x
|
|
393
|
+
|
|
394
|
+
# Assign units if desired
|
|
395
|
+
if len(Units) != 0:
|
|
396
|
+
print('Assigning units:', Units)
|
|
397
|
+
Fgrid = AssignQuantity(Fgrid,Units)
|
|
398
|
+
else:
|
|
399
|
+
Fgrid = AssignQuantity(Fgrid,integral_along_y.units)
|
|
400
|
+
|
|
401
|
+
# Apply an offset if desired
|
|
402
|
+
if len(SState) != 0:
|
|
403
|
+
SState_x = SState[0]
|
|
404
|
+
SState_y = SState[1]
|
|
405
|
+
SState_F = SState[2]
|
|
406
|
+
Fgrid_interpolater = RectBivariateSpline(xgrid[:,0], ygrid[0,:], Fgrid)
|
|
407
|
+
Fgrid_at_standard_state = Fgrid_interpolater(SState_x,SState_y)
|
|
408
|
+
Fgrid_at_standard_state = AssignQuantity(Fgrid_at_standard_state,SState_F.units)
|
|
409
|
+
Fgrid -= Fgrid_at_standard_state
|
|
410
|
+
Fgrid += SState_F
|
|
411
|
+
|
|
412
|
+
return(Fgrid)
|
|
413
|
+
|
|
414
|
+
def StateSpaceInterpolator(statespace,nxarray,nyarray,Fgrid,AssignQuantity=0):
|
|
415
|
+
if type(AssignQuantity) == type:
|
|
416
|
+
#print('I think it is a function')
|
|
417
|
+
useAssignQuantity = True
|
|
418
|
+
else:
|
|
419
|
+
useAssignQuantity = False
|
|
420
|
+
xgrid = statespace[0]
|
|
421
|
+
ygrid = statespace[1]
|
|
422
|
+
Fgrid_interpolater = RectBivariateSpline(xgrid[:,0], ygrid[0,:], Fgrid)
|
|
423
|
+
if np.size(nxarray) == 1:
|
|
424
|
+
nxarray = [nxarray]
|
|
425
|
+
nyarray = [nyarray]
|
|
426
|
+
result = []
|
|
427
|
+
for i in range(len(nxarray)):
|
|
428
|
+
result.append(Fgrid_interpolater(nxarray[i],nyarray[i]))
|
|
429
|
+
result = np.squeeze(result)
|
|
430
|
+
if useAssignQuantity:
|
|
431
|
+
result = AssignQuantity(result,Fgrid.units)
|
|
432
|
+
return np.squeeze(result)
|
|
433
|
+
|
|
434
|
+
def trapz(integrand,x,AssignQuantity=0):
|
|
435
|
+
# Uses numpy's trapz, but with units
|
|
436
|
+
try:
|
|
437
|
+
integrand.units
|
|
438
|
+
result = np.trapz(integrand.magnitude,x.magnitude)
|
|
439
|
+
result = AssignQuantity(result,integrand.units*x.units)
|
|
440
|
+
return result
|
|
441
|
+
except:
|
|
442
|
+
print('Integrating without units')
|
|
443
|
+
result = np.trapz(integrand,x)
|
|
444
|
+
return result
|
|
445
|
+
|
|
446
|
+
# These (drawbox_xx and plot3d) are from Chem 341; plot3d should be replaced by plot_surface1
|
|
447
|
+
|
|
448
|
+
# Draw the box (clumsily)
|
|
449
|
+
from itertools import product, combinations
|
|
450
|
+
def drawbox_xx(xinit,xfinal,y,z,fig=[]):
|
|
451
|
+
if np.size(fig) == 0:
|
|
452
|
+
fig = plt.figure()
|
|
453
|
+
ax = fig.gca(projection='3d')
|
|
454
|
+
ax.set_box_aspect(aspect = (xinit,y,z))
|
|
455
|
+
rext = xfinal/xinit
|
|
456
|
+
r = [0, 1]
|
|
457
|
+
for s, e in combinations(np.array(list(product(r, r, r))), 2):
|
|
458
|
+
if np.sum(np.abs(s-e)) == r[1]-r[0]:
|
|
459
|
+
ax.plot3D(*zip(s, e), color="b")
|
|
460
|
+
ax.plot3D([1.0, rext],[0, 0],[0, 0],color='g')
|
|
461
|
+
ax.plot3D([1.0, rext],[1, 1],[1, 1],color='g')
|
|
462
|
+
ax.plot3D([1.0, rext],[0, 0],[1, 1],color='g')
|
|
463
|
+
ax.plot3D([1.0, rext],[1, 1],[0, 0],color='g')
|
|
464
|
+
ax.plot3D([rext, rext],[0, 1],[0, 0],color='g')
|
|
465
|
+
ax.plot3D([rext, rext],[0, 0],[0, 1],color='g')
|
|
466
|
+
ax.plot3D([rext, rext],[0, 1],[1, 1],color='g')
|
|
467
|
+
ax.plot3D([rext, rext],[1, 1],[0, 1],color='g')
|
|
468
|
+
ax.set_xticks([])
|
|
469
|
+
ax.set_yticks([])
|
|
470
|
+
ax.set_zticks([])
|
|
471
|
+
ax.set_xlabel('x')
|
|
472
|
+
ax.set_ylabel('y')
|
|
473
|
+
ax.set_zlabel('z')
|
|
474
|
+
return fig
|
|
475
|
+
|
|
476
|
+
# Plotting in 3d
|
|
477
|
+
def plot3d(xgrid,ygrid,zgrid,xaxis_title='x',yaxis_title='y',zaxis_title='z'):
|
|
478
|
+
fig = go.Figure(data=go.Surface(x=xgrid,y=ygrid,z=zgrid))
|
|
479
|
+
fig.update_layout(scene = dict(
|
|
480
|
+
xaxis_title=xaxis_title,
|
|
481
|
+
yaxis_title=yaxis_title,
|
|
482
|
+
zaxis_title=zaxis_title))
|
|
483
|
+
fig.show()
|
|
484
|
+
|
|
485
|
+
def f_sigmoid(f1, f2, T, AssignQuantity, T_interval_magnitude=3, T_transition_magnitude=0):
|
|
486
|
+
if T_transition_magnitude == 0:
|
|
487
|
+
T1 = np.max(T)
|
|
488
|
+
T2 = np.min(T)
|
|
489
|
+
Tmid = (T2+T1)/2
|
|
490
|
+
T_transition = AssignQuantity(Tmid,'K')
|
|
491
|
+
else:
|
|
492
|
+
T_transition = AssignQuantity(T_transition_magnitude,'K')
|
|
493
|
+
T_interval = AssignQuantity(T_interval_magnitude,'K')
|
|
494
|
+
sigmoid_arg = (T-T_transition)/T_interval
|
|
495
|
+
sigmoid = 1 - 1.0/(1.0 + np.exp(-sigmoid_arg))
|
|
496
|
+
sigmoid_min = sigmoid[0,0]; #print(sigmoid_min)
|
|
497
|
+
sigmoid_max = sigmoid[-1,0]; #print(sigmoid_max)
|
|
498
|
+
f = (sigmoid-sigmoid_min)*(f2-f1)/(sigmoid_max-sigmoid_min)+f1
|
|
499
|
+
return f
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pchemlibrary
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Library for teaching physical chemistry using Jupyter Notebooks
|
|
5
|
+
Author-email: Steven Neshyba <sneshyba@gmail.com>
|
|
6
|
+
Requires-Python: >=3.8
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
Requires-Dist: numpy
|
|
9
|
+
Requires-Dist: scipy
|
|
10
|
+
Requires-Dist: matplotlib
|
|
11
|
+
Requires-Dist: pint
|
|
12
|
+
Requires-Dist: plotly
|
|
13
|
+
|
|
14
|
+
This is a library of python functions that support Python-language codes for teaching physical chemistry.
|
|
15
|
+
|
|
16
|
+
# Installation
|
|
17
|
+
This has been packaged on PyPI. To install, run
|
|
18
|
+
```sh
|
|
19
|
+
python -m pip install pchemlib
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
# Usage
|
|
23
|
+
See examples in the examples folder.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "pchemlibrary"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Library for teaching physical chemistry using Jupyter Notebooks"
|
|
5
|
+
authors = [{ name = "Steven Neshyba", email = "sneshyba@gmail.com" }]
|
|
6
|
+
readme = "README.md"
|
|
7
|
+
requires-python = ">=3.8"
|
|
8
|
+
dependencies = ['numpy', 'scipy', 'matplotlib', 'pint', 'plotly']
|
|
9
|
+
|
|
10
|
+
[build-system]
|
|
11
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
12
|
+
build-backend = "setuptools.build_meta"
|
|
13
|
+
|
|
14
|
+
[tool.setuptools.packages.find]
|
|
15
|
+
where = ["."]
|