praiteri commited on
Commit
fe49880
·
1 Parent(s): 704e12d

Start of semester version

Browse files
marimo/app.py CHANGED
@@ -21,6 +21,7 @@ marimo_server = (
21
  .with_app(path="/bc", root="./bomb_calorimetry.py")
22
  .with_app(path="/cv", root="./crystal_violet.py")
23
  .with_app(path="/stats", root="./statistics_lab.py")
 
24
  .with_app(path="/surface", root="./surface_adsorption.py")
25
  )
26
 
@@ -38,21 +39,6 @@ async def download_pdf(pdf_name: str):
38
  )
39
  return {"error": f"PDF not found [../pdfs/{pdf_name}]"}, 404
40
 
41
- # Create a route to display a pdf
42
- @app.get("/pdf/direct/{pdf_name}")
43
- async def get_pdf_direct(pdf_name: str):
44
- pdf_path = Path(f"./pdfs/{pdf_name}")
45
-
46
- if not pdf_path.exists():
47
- raise HTTPException(status_code=404, detail=f"PDF not found {pdf_path}")
48
-
49
- return FileResponse(
50
- path=pdf_path,
51
- media_type="application/pdf",
52
- filename=pdf_name + ".pdf"
53
- )
54
-
55
-
56
  # Mount the marimo server at the root
57
  # This will handle all the routes defined in the marimo server
58
  app.mount("", marimo_server.build())
 
21
  .with_app(path="/bc", root="./bomb_calorimetry.py")
22
  .with_app(path="/cv", root="./crystal_violet.py")
23
  .with_app(path="/stats", root="./statistics_lab.py")
24
+ .with_app(path="/eq", root="./equilibrium.py")
25
  .with_app(path="/surface", root="./surface_adsorption.py")
26
  )
27
 
 
39
  )
40
  return {"error": f"PDF not found [../pdfs/{pdf_name}]"}, 404
41
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  # Mount the marimo server at the root
43
  # This will handle all the routes defined in the marimo server
44
  app.mount("", marimo_server.build())
marimo/bomb_calorimetry.py CHANGED
@@ -7,7 +7,7 @@ app = marimo.App(width="medium")
7
  @app.cell
8
  def _():
9
  import marimo as mo
10
- import pycek_public as cek
11
  lab = cek.bomb_calorimetry(make_plots=True)
12
  return cek, lab, mo
13
 
@@ -95,7 +95,6 @@ def _(cek, lab, mo, reset_button, run_button, sample_selector):
95
 
96
  lab.set_parameters(sample=sample_selector.value)
97
  data = lab.create_data()
98
- print(data)
99
  file_content = lab.write_data_to_string()
100
 
101
  fname = lab.filename_gen.random
@@ -111,7 +110,6 @@ def _(cek, lab, mo, reset_button, run_button, sample_selector):
111
  )
112
 
113
  plot = cek.plotting()
114
- print(data)
115
  image = plot.quick_plot(scatter=data,output="marimo")
116
 
117
  mo.hstack([mo.vstack([mo.md(message),download_button]),image])
 
7
  @app.cell
8
  def _():
9
  import marimo as mo
10
+ import pycek as cek
11
  lab = cek.bomb_calorimetry(make_plots=True)
12
  return cek, lab, mo
13
 
 
95
 
96
  lab.set_parameters(sample=sample_selector.value)
97
  data = lab.create_data()
 
98
  file_content = lab.write_data_to_string()
99
 
100
  fname = lab.filename_gen.random
 
110
  )
111
 
112
  plot = cek.plotting()
 
113
  image = plot.quick_plot(scatter=data,output="marimo")
114
 
115
  mo.hstack([mo.vstack([mo.md(message),download_button]),image])
marimo/crystal_violet.py CHANGED
@@ -7,7 +7,7 @@ app = marimo.App(width="medium")
7
  @app.cell
8
  def _():
9
  import marimo as mo
10
- import pycek_public as cek
11
  lab = cek.crystal_violet(make_plots=True)
12
  return cek, lab, mo
13
 
 
7
  @app.cell
8
  def _():
9
  import marimo as mo
10
+ import pycek as cek
11
  lab = cek.crystal_violet(make_plots=True)
12
  return cek, lab, mo
13
 
marimo/equilibrium.py ADDED
@@ -0,0 +1,303 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import marimo
2
+
3
+ __generated_with = "0.11.7"
4
+ app = marimo.App(width="full")
5
+
6
+
7
+ @app.cell
8
+ def _():
9
+ import marimo as mo
10
+ import numpy as np
11
+ import matplotlib.pyplot as plt
12
+ import copy
13
+
14
+ nspecies = mo.ui.number(2,10,value=2,label="Number of species")
15
+ mo.vstack([
16
+ mo.md("#**Numerical Solution of Equilibrium Problems**").center(),
17
+ nspecies],gap=2)
18
+ return copy, mo, np, nspecies, plt
19
+
20
+
21
+ @app.cell
22
+ def _(nspecies):
23
+ species = [chr(i) for i in range(65, 65 + nspecies.value)]
24
+ return (species,)
25
+
26
+
27
+ @app.cell
28
+ def _(mo, species):
29
+ ss = [ mo.ui.text(value=s) for s in species]
30
+ compounds = mo.ui.array([ *ss ],label=f"Species Names")
31
+
32
+ nn = [ mo.ui.number(-5,5,value=1,label=f"{s}") for s in species]
33
+ stoichiometry = mo.ui.array([
34
+ *nn,
35
+ ],label=f"Stiochiometric coefficients")
36
+
37
+ cc = [ mo.ui.number(-5,5,0.01,value=1,label=f"{s}") for s in species]
38
+ concentrations = mo.ui.array([
39
+ *cc,
40
+ ],label=f"Initial concentrations")
41
+
42
+ mo.hstack([
43
+ compounds,stoichiometry,concentrations
44
+ ],align="start",justify="space-around")
45
+
46
+ return cc, compounds, concentrations, nn, ss, stoichiometry
47
+
48
+
49
+ @app.cell
50
+ def _(compounds, mo, stoichiometry):
51
+ nu = stoichiometry.value
52
+ _tex = ""
53
+ for i,xs in enumerate(compounds.value):
54
+ if nu[i] < 0:
55
+ _tex += f"\\mathrm{{{abs(nu[i])}{xs}}} + " if nu[i] < -1 else f"\\mathrm{{{xs}}} + "
56
+ _tex = _tex.strip().rstrip('+')
57
+ _tex += "\\rightleftharpoons"
58
+ for i,xs in enumerate(compounds.value):
59
+ if nu[i] > 0:
60
+ _tex += f"\\mathrm{{{nu[i]}{xs}}} + " if nu[i] > 1 else f"\\mathrm{{{xs}}} + "
61
+ _tex = _tex.strip().rstrip('+')
62
+
63
+ keq = mo.ui.text(value="12",label="$K_{eq}$")
64
+
65
+ a = mo.md(
66
+ f"""
67
+ ##**Chemical Reaction**\n
68
+ $$\\begin{{align*}}
69
+ {_tex} &
70
+ \\end{{align*}}$$
71
+ """
72
+ )
73
+ b = mo.md(
74
+ f"""
75
+ ##**Equilibrium constant**\n
76
+ {keq}
77
+ """
78
+ )
79
+
80
+ mo.hstack([a,b],align="start",justify="space-around")
81
+
82
+ return a, b, i, keq, nu, xs
83
+
84
+
85
+ @app.cell
86
+ def _(mo, np):
87
+ step = mo.ui.slider(steps=np.logspace(-8,0,90),label="$\delta c$",show_value=True)
88
+ tol = mo.ui.slider(steps=np.logspace(-8,0,90),label="Convergence Threshold",show_value=True)
89
+
90
+ mo.vstack([
91
+ mo.md("##**Optimisation parameters**").center(),
92
+ mo.hstack([
93
+ step,
94
+ tol,]
95
+ ,align="start",justify="space-around")
96
+ ])
97
+
98
+ return step, tol
99
+
100
+
101
+ @app.cell
102
+ def _(np):
103
+ def compute_Q(conc,stoich):
104
+ Q = 1
105
+ for i in range(len(conc)):
106
+ Q *= conc[i]**stoich[i]
107
+ return Q
108
+
109
+ def compute_force(conc,stoich,pkeq):
110
+ Q = compute_Q(conc,stoich)
111
+ return -np.log10(Q) - pkeq
112
+
113
+ def update_concentrations(conc,stoich,force,dc):
114
+ ctmp = np.zeros(len(conc))
115
+ for i in range(len(conc)):
116
+ ctmp[i] = conc[i] +dc*stoich[i]*force
117
+ return ctmp
118
+
119
+ def solve_analytic(conc,keq):
120
+ """
121
+ (b+x) / (a-2x)**2 = c
122
+ """
123
+ a = conc["A"]
124
+ b = conc["B"]
125
+ c = keq
126
+ x0 = (-np.sqrt(8*a*c + 16*b*c + 1) + 4*a*c + 1)/(8*c)
127
+ x1 = (np.sqrt(8*a*c + 16*b*c + 1) + 4*a*c + 1)/(8*c)
128
+ return x0,x1
129
+ return compute_Q, compute_force, solve_analytic, update_concentrations
130
+
131
+
132
+ @app.cell
133
+ def _(
134
+ Dict,
135
+ List,
136
+ NDArray,
137
+ compute_Q,
138
+ compute_force,
139
+ np,
140
+ plt,
141
+ update_concentrations,
142
+ ):
143
+ def solve_equilibrium(
144
+ species: List,
145
+ initial_conc: Dict[str, float],
146
+ stoichiometry: Dict[str, float],
147
+ pK_eq: float,
148
+ dc: float,
149
+ rtol: float = 1e-5,
150
+ max_iterations: int = 10
151
+ ) -> NDArray:
152
+ """
153
+ Solves chemical equilibrium equations using an iterative approach.
154
+
155
+ Args:
156
+ initial_conc: Dictionary of initial concentrations for each species
157
+ stoichiometry: Dictionary of stoichiometric coefficients
158
+ pK_eq: Negative log of equilibrium constant
159
+ dc: Concentration step size for iterations
160
+ rtol: Relative tolerance for convergence
161
+ max_iterations: Maximum number of iterations before stopping
162
+
163
+ Returns:
164
+ NDArray: Array with columns [iteration, conc_A, conc_B, force]
165
+ """
166
+ # Initialize arrays to store results
167
+ ns = len(species)
168
+
169
+ conc = np.zeros(shape=(max_iterations + 1, ns))
170
+ forces = np.zeros(shape=(max_iterations + 1,1))
171
+
172
+ # Set initial values
173
+ conc[0,:] = np.array(initial_conc)
174
+ forces[0] = compute_force(conc[0,:], stoichiometry, pK_eq)
175
+
176
+ # Iterate until convergence or max iterations
177
+ for i in range(max_iterations):
178
+ # Update values
179
+ conc[i+1,:] = update_concentrations(conc[i,:], stoichiometry, forces[i,0], dc)
180
+ forces[i+1] = compute_force(conc[i+1,:], stoichiometry, pK_eq)
181
+ if forces[i+1]*forces[i] < 0:
182
+ dc /=2
183
+ pQ = -np.log10(compute_Q(conc[i+1,:], stoichiometry))
184
+
185
+ # Check convergence
186
+ # if np.isclose(pQ, pK_eq, rtol=rtol):
187
+ if np.abs(forces[i+1]) < rtol:
188
+ # Trim unused array space if converged early
189
+ return conc[:i+2,:], forces[:i + 2]
190
+
191
+ # Return all iterations if no convergence
192
+ return conc[:i+2,:], forces[:i + 2]
193
+
194
+ def plot(x,data,labels=None,refs=None,log=False,axes=None):
195
+ colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
196
+ ncols = data.shape[1]
197
+ plt.figure(figsize=(4,4))
198
+ for i in range(ncols):
199
+ plt.plot(x,data[:,i],label=labels[i],color=colors[i])
200
+
201
+ if refs is not None:
202
+ for i in range(len(refs)):
203
+ plt.axhline(refs[i],linestyle='dashed',color=colors[i])
204
+
205
+ if axes is not None:
206
+ plt.xlabel(axes[0])
207
+ plt.ylabel(axes[1])
208
+ if log:
209
+ plt.yscale("log")
210
+ plt.legend()
211
+ return plt.gca()
212
+ return plot, solve_equilibrium
213
+
214
+
215
+ @app.cell
216
+ def _(compounds, keq, np, plot, solve_equilibrium, species, step, tol):
217
+ # conc = { s: float(conc_all[s].value) for s in species }
218
+ # stoich = { s: nu_all[s].value for s in species }
219
+ def execute(conc_list,stoich_list):
220
+ # conc_list = np.array([float(conc_all[s].value) for s in species])
221
+ # stoich_list = np.array([nu_all[s].value for s in species])
222
+
223
+ pkeq = -np.log10(float(keq.value))
224
+ dc = float(step.value)
225
+ rtol = float(tol.value)
226
+
227
+ final_conc_list, forces = solve_equilibrium(
228
+ species,
229
+ conc_list,
230
+ stoich_list,
231
+ pkeq,dc,rtol,max_iterations=10000)
232
+
233
+ cycles = np.linspace(0,len(forces),len(forces))
234
+ # roots = solve_analytic(conc,float(keq.value))
235
+ # analytic_solution = [ data[0,1] + stoich["A"]*roots[0] , data[0,2] + stoich["B"]*roots[0] ]
236
+
237
+ plot_c = plot(cycles,final_conc_list,labels=compounds.value,axes=["Cycles","Concentration"])
238
+ plot_f = plot(
239
+ cycles, np.abs(forces),
240
+ labels=["Force"],refs=[rtol],log=True,axes=["Cycles","Force"])
241
+
242
+ return final_conc_list, forces, plot_c, plot_f
243
+ return (execute,)
244
+
245
+
246
+ @app.cell
247
+ def _(concentrations, execute, mo, np, stoichiometry):
248
+ final_conc_list, forces, plot_c, plot_f = execute(
249
+ np.array(concentrations.value, dtype=float),
250
+ np.array(stoichiometry.value,dtype=int)
251
+ )
252
+
253
+ mo.vstack([
254
+ mo.md("##**Optimisation Results**").center(),
255
+ mo.hstack([plot_c,plot_f],
256
+ align="start", justify="space-around")])
257
+ return final_conc_list, forces, plot_c, plot_f
258
+
259
+
260
+ @app.cell
261
+ def _(
262
+ compounds,
263
+ compute_Q,
264
+ final_conc_list,
265
+ forces,
266
+ keq,
267
+ mo,
268
+ np,
269
+ stoichiometry,
270
+ ):
271
+ stoich_list = np.array(stoichiometry.value,dtype=int)
272
+ # initial = mo.md(f"""
273
+ # ###**Initial conditions**
274
+ # ####Force = {forces[0,0]:.4e}
275
+ # ####Q = {compute_Q(final_conc_list[0,:],stoich_list):.4e}
276
+ # ####Concentrations:<br> {" ".join([f"* [{xx}] = {final_conc_list[0,i]:.4e}<br>" for i,xx in enumerate(compounds.value)])}
277
+ # """)
278
+
279
+ final_0 = mo.md(f"""
280
+ Force = {forces[-1,0]:.4e}<br>
281
+ Q = {compute_Q(final_conc_list[-1,:],stoich_list):.4e}<br>
282
+ Keq = {float(keq.value):.4e}
283
+ """)
284
+ final_1 = mo.md(f"""
285
+ Concentrations:
286
+ {" ".join([f"<br>[{xx}] = {final_conc_list[-1,i]:.4e}" for i,xx in enumerate(compounds.value)])}
287
+ """)
288
+
289
+ mo.vstack([
290
+ mo.md('##**Final conditions**').center(),
291
+ mo.hstack([final_0,final_1],align="start",justify="space-between")
292
+ ],justify="space-around")
293
+
294
+ return final_0, final_1, stoich_list
295
+
296
+
297
+ @app.cell
298
+ def _():
299
+ return
300
+
301
+
302
+ if __name__ == "__main__":
303
+ app.run()
marimo/equilibrium_basic.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import marimo
2
+
3
+ __generated_with = "0.11.7"
4
+ app = marimo.App(width="medium")
5
+
6
+
7
+ @app.cell
8
+ def _():
9
+ import marimo as mo
10
+ import numpy as np
11
+ import matplotlib.pyplot as plt
12
+
13
+ conc_a = mo.ui.text(value="0.2",label="$[\mathrm{A}]_0$")
14
+ conc_b = mo.ui.text(value="0.1",label="$[\mathrm{B}]_0$")
15
+ keq = mo.ui.text(value="12",label="$K_{eq}$")
16
+
17
+ step = mo.ui.slider(steps=np.logspace(-8,0,90),label="$\delta c$",show_value=True)
18
+ tol = mo.ui.slider(steps=np.logspace(-8,0,90),label="Convergence Threshold",show_value=True)
19
+
20
+ mo.md(
21
+ f"""
22
+ ##**Initial conditions**
23
+
24
+ {conc_a} {conc_b} {keq}\n
25
+
26
+ ##**Chemical Equilibrium Solver Parameters**
27
+
28
+ {step} {tol}
29
+ """
30
+ )
31
+ return conc_a, conc_b, keq, mo, np, plt, step, tol
32
+
33
+
34
+ @app.cell
35
+ def _(np):
36
+ def compute_Q(conc,stoich):
37
+ Q = 1
38
+ for c in conc:
39
+ Q *= conc[c]**stoich[c]
40
+ return Q
41
+
42
+ def compute_force(conc,stoich,pkeq):
43
+ Q = compute_Q(conc,stoich)
44
+ return -np.log10(Q) - pkeq
45
+
46
+ def update_concentrations(conc,stoich,force,dc):
47
+ for c in conc:
48
+ conc[c] += dc*stoich[c]*force
49
+ return conc
50
+
51
+ def solve_analytic(conc,keq):
52
+ """
53
+ (b+x) / (a-2x)**2 = c
54
+ """
55
+ a = conc["A"]
56
+ b = conc["B"]
57
+ c = keq
58
+ x0 = (-np.sqrt(8*a*c + 16*b*c + 1) + 4*a*c + 1)/(8*c)
59
+ x1 = (np.sqrt(8*a*c + 16*b*c + 1) + 4*a*c + 1)/(8*c)
60
+ return x0,x1
61
+ return compute_Q, compute_force, solve_analytic, update_concentrations
62
+
63
+
64
+ @app.cell
65
+ def _(
66
+ Dict,
67
+ NDArray,
68
+ compute_Q,
69
+ compute_force,
70
+ conc_a,
71
+ conc_b,
72
+ keq,
73
+ mo,
74
+ np,
75
+ plt,
76
+ solve_analytic,
77
+ step,
78
+ tol,
79
+ update_concentrations,
80
+ ):
81
+ conc = {
82
+ "A": float(conc_a.value),
83
+ "B": float(conc_b.value),
84
+ }
85
+
86
+ stoich = {
87
+ "A":-2,
88
+ "B":1,
89
+ }
90
+
91
+ pkeq = -np.log10(float(keq.value))
92
+ dc = float(step.value)
93
+ rtol = float(tol.value)
94
+
95
+ # print(conc,np.log10(compute_Q(conc,stoich)),pkeq)
96
+
97
+ initial = mo.md(
98
+ f"""
99
+ ##**Initial conditions**
100
+ $Q$ = {compute_Q(conc,stoich):.4e} &nbsp; &nbsp;
101
+ Initial force = {compute_force(conc, stoich, pkeq):.4e}
102
+ """
103
+ )
104
+
105
+ def solve_equilibrium(
106
+ initial_conc: Dict[str, float],
107
+ stoichiometry: Dict[str, float],
108
+ pK_eq: float,
109
+ dc: float,
110
+ rtol: float = 1e-5,
111
+ max_iterations: int = 10
112
+ ) -> NDArray:
113
+ """
114
+ Solves chemical equilibrium equations using an iterative approach.
115
+
116
+ Args:
117
+ initial_conc: Dictionary of initial concentrations for each species
118
+ stoichiometry: Dictionary of stoichiometric coefficients
119
+ pK_eq: Negative log of equilibrium constant
120
+ dc: Concentration step size for iterations
121
+ rtol: Relative tolerance for convergence
122
+ max_iterations: Maximum number of iterations before stopping
123
+
124
+ Returns:
125
+ NDArray: Array with columns [iteration, conc_A, conc_B, force]
126
+ """
127
+ # Initialize arrays to store results
128
+ iterations = np.zeros(max_iterations + 1)
129
+ conc_A = np.zeros(max_iterations + 1)
130
+ conc_B = np.zeros(max_iterations + 1)
131
+ forces = np.zeros(max_iterations + 1)
132
+
133
+ # Set initial values
134
+ conc = initial_conc.copy()
135
+ force_0 = compute_force(conc, stoichiometry, pK_eq)
136
+ conc_A[0] = conc['A']
137
+ conc_B[0] = conc['B']
138
+ forces[0] = force_0
139
+
140
+ # Iterate until convergence or max iterations
141
+ for i in range(max_iterations):
142
+ # Update values
143
+ conc = update_concentrations(conc, stoichiometry, forces[i], dc)
144
+ force = compute_force(conc, stoichiometry, pK_eq)
145
+ # if force*forces[i] < 0:
146
+ # dc /=2
147
+ pQ = -np.log10(compute_Q(conc, stoichiometry))
148
+
149
+ # Store results
150
+ iterations[i + 1] = i + 1
151
+ conc_A[i + 1] = conc['A']
152
+ conc_B[i + 1] = conc['B']
153
+ forces[i + 1] = force
154
+
155
+ # Check convergence
156
+ # if np.isclose(pQ, pK_eq, rtol=rtol):
157
+ if np.abs(force) < rtol:
158
+ # Trim unused array space if converged early
159
+ return np.column_stack([
160
+ iterations[:i + 2],
161
+ conc_A[:i + 2],
162
+ conc_B[:i + 2],
163
+ forces[:i + 2]
164
+ ])
165
+
166
+ # Return all iterations if no convergence
167
+ return np.column_stack([iterations, conc_A, conc_B, forces])
168
+
169
+ def plot(data,labels=None,refs=None,log=False,axes=None):
170
+ ncols = data.shape[1]
171
+ colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
172
+ plt.figure(figsize=(4,4))
173
+ for i in range(0,ncols-1):
174
+ plt.plot(data[:,0],data[:,i+1],label=labels[i],color=colors[i])
175
+
176
+ if refs is not None:
177
+ for i in range(len(refs)):
178
+ plt.axhline(refs[i],linestyle='dashed',label=labels[i]+"$_{exact}$",color=colors[i])
179
+
180
+ if axes is not None:
181
+ plt.xlabel(axes[0])
182
+ plt.ylabel(axes[1])
183
+ if log:
184
+ plt.yscale("log")
185
+ plt.legend()
186
+ return plt.gca()
187
+
188
+
189
+ data = solve_equilibrium(conc,stoich,pkeq,dc,rtol,max_iterations=1000)
190
+ final_conc = {"A":data[-1,1] , "B":data[-1,2]}
191
+
192
+ roots = solve_analytic(conc,float(keq.value))
193
+ analytic_solution = [ data[0,1] + stoich["A"]*roots[0] , data[0,2] + stoich["B"]*roots[0] ]
194
+
195
+
196
+ plot_c = plot(data[:,0:3],labels=["[A]","[B]"],refs=analytic_solution,axes=["Cycles","Concentration"])
197
+ plot_f = plot(
198
+ np.column_stack([data[:,0],np.abs(data[:,3])]),
199
+ labels=["Force"],refs=[rtol],log=True,axes=["Cycles","Force"])
200
+ final = mo.md(
201
+ f"""
202
+ ##**Final conditions**
203
+
204
+ $[A]_f$ = {final_conc["A"]:.4e} &nbsp; &nbsp;
205
+ $[B]_f$ = {final_conc["B"]:.4e} &nbsp; &nbsp;
206
+ $Q$ = {compute_Q(final_conc,stoich):.4e} &nbsp; &nbsp;
207
+ $K_{{eq}}$ = {float(keq.value):.4e} \n
208
+ Final force = {compute_force(final_conc,stoich,pkeq):.4e} &nbsp; &nbsp;
209
+ Force threshold = {float(tol.value):.4e}
210
+
211
+ """
212
+ )
213
+
214
+ mo.vstack([initial,final,
215
+ mo.hstack([plot_c,plot_f])
216
+ ])
217
+
218
+ return (
219
+ analytic_solution,
220
+ conc,
221
+ data,
222
+ dc,
223
+ final,
224
+ final_conc,
225
+ initial,
226
+ pkeq,
227
+ plot,
228
+ plot_c,
229
+ plot_f,
230
+ roots,
231
+ rtol,
232
+ solve_equilibrium,
233
+ stoich,
234
+ )
235
+
236
+
237
+ if __name__ == "__main__":
238
+ app.run()
marimo/index.html CHANGED
@@ -103,32 +103,26 @@
103
  <p style="text-align: center">Version 1 - 20/02/2025</p>
104
  </a>
105
  <a href="/stats" class="lab-card">
106
- <h3 style="text-align: center">Statistics Lab</h3>
107
  <p style="text-align: center">Basic statistical concepts and Python introduction<br>(Week 2)</p>
108
  </a>
109
  <a href="/bc" class="lab-card">
110
- <h3 style="text-align: center">Bomb Calorimetry Lab</h3>
111
  <p style="text-align: center">Thermodynamics and heat measurements<br>(Week 4)</p>
112
  </a>
113
  <a href="/cv" class="lab-card">
114
- <h3 style="text-align: center">Crystal Violet Lab</h3>
115
  <p style="text-align: center">Chemical kinetics and reaction rates<br>(Week 8)</p>
116
  </a>
 
 
 
 
117
  <a href="/surface" class="lab-card">
118
- <h3 style="text-align: center">Surface Adsorption Lab</h3>
119
- <p style="text-align: center">Equilibrium and surface chemistry<br>(Week 10)</p>
120
  </a>
121
  </div>
122
-
123
- <!--
124
- <embed
125
- src="/pdf/direct/calendar.pdf"
126
- type="application/pdf"
127
- width="100%"
128
- height="600px"
129
- />
130
- -->
131
-
132
 
133
  <h2 style="text-align: center"><strong>Check the unit outline for when the reports are due</strong></h2>
134
 
 
103
  <p style="text-align: center">Version 1 - 20/02/2025</p>
104
  </a>
105
  <a href="/stats" class="lab-card">
106
+ <h3 style="text-align: center">Statistics</h3>
107
  <p style="text-align: center">Basic statistical concepts and Python introduction<br>(Week 2)</p>
108
  </a>
109
  <a href="/bc" class="lab-card">
110
+ <h3 style="text-align: center">Bomb Calorimetry</h3>
111
  <p style="text-align: center">Thermodynamics and heat measurements<br>(Week 4)</p>
112
  </a>
113
  <a href="/cv" class="lab-card">
114
+ <h3 style="text-align: center">Crystal Violet</h3>
115
  <p style="text-align: center">Chemical kinetics and reaction rates<br>(Week 8)</p>
116
  </a>
117
+ <a href="/eq" class="lab-card">
118
+ <h3 style="text-align: center">Chemical Equilibrium</h3>
119
+ <p style="text-align: center">Numerical solution of equilibrium problems<br>(Week 10)</p>
120
+ </a>
121
  <a href="/surface" class="lab-card">
122
+ <h3 style="text-align: center">Surface Adsorption</h3>
123
+ <p style="text-align: center">Equilibrium and surface chemistry<br>(Week 12)</p>
124
  </a>
125
  </div>
 
 
 
 
 
 
 
 
 
 
126
 
127
  <h2 style="text-align: center"><strong>Check the unit outline for when the reports are due</strong></h2>
128
 
marimo/pdfs/LabManualCHEM2000-1.pdf CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:7d4837ecf82f7380646b50441cd3ddfade7adc0627e1dcac8e091d00e53338e7
3
- size 3218438
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:1edb8d09bfc9d1d3102d3c30f0530ad10a26f189f0dd9077fa86a6946018e6b9
3
+ size 4779263
marimo/statistics_lab.py CHANGED
@@ -7,7 +7,7 @@ app = marimo.App(width="medium")
7
  @app.cell
8
  def _():
9
  import marimo as mo
10
- import pycek_public as cek
11
  lab = cek.stats_lab(make_plots=True)
12
  return cek, lab, mo
13
 
 
7
  @app.cell
8
  def _():
9
  import marimo as mo
10
+ import pycek as cek
11
  lab = cek.stats_lab(make_plots=True)
12
  return cek, lab, mo
13
 
marimo/surface_adsorption.py CHANGED
@@ -7,7 +7,7 @@ app = marimo.App(width="medium")
7
  @app.cell
8
  def _():
9
  import marimo as mo
10
- import pycek_public as cek
11
  lab = cek.cek.surface_adsorption(make_plots=True)
12
  return cek, lab, mo
13
 
 
7
  @app.cell
8
  def _():
9
  import marimo as mo
10
+ import pycek as cek
11
  lab = cek.cek.surface_adsorption(make_plots=True)
12
  return cek, lab, mo
13
 
src/pycek_public/cek_labs.py CHANGED
@@ -73,6 +73,9 @@ class cek_labs(ABC):
73
  self.update_metadata_from_attr()
74
  self.logger.critical(f"Initial seed = {np.random.get_state()[1][0]}")
75
 
 
 
 
76
  def set_token(self, token):
77
  self.token = token
78
  #print(f"Check: {self._check_token()}")
 
73
  self.update_metadata_from_attr()
74
  self.logger.critical(f"Initial seed = {np.random.get_state()[1][0]}")
75
 
76
+ def reset(self):
77
+ np.random.seed(self.student_ID)
78
+
79
  def set_token(self, token):
80
  self.token = token
81
  #print(f"Check: {self._check_token()}")