Paolo commited on
Commit
1ffa54d
·
1 Parent(s): 9f86036

fixes after lab 1

Browse files
marimo/bomb_calorimetry.py CHANGED
@@ -8,6 +8,7 @@ app = marimo.App(width="medium")
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
 
@@ -41,24 +42,14 @@ def _(mo):
41
  @app.cell
42
  def _(lab, mo):
43
  def set_ID(value):
44
- try:
45
- student_number = int(value.strip())
46
- if student_number <= 0:
47
- print(mo.md(f"### Invalid Student ID: {student_ID.value}"))
48
- else:
49
- print(f"Valid Student ID: {student_number}")
50
- lab.set_student_ID(int(value))
51
- except ValueError:
52
- mo.stop(not student_ID.value.isdigit(), mo.md(f"### Invalid Student ID: {student_ID.value}"))
53
- print(mo.md(f"### Invalid Student ID: {student_ID.value}"))
54
-
55
- student_ID = mo.ui.text(value="", label="Student ID:",on_change=set_ID)
56
 
57
  def set_fname(value):
58
  lab._set_filename(value)
59
 
60
- exp_ID = mo.ui.text(value="Automatic", label="Output file:",
61
- on_change=set_fname)
62
 
63
  sample_selector = mo.ui.dropdown(
64
  options=lab.available_samples, value=None, label="Select sample:"
@@ -99,7 +90,7 @@ def _(cek, lab, mo, reset_button, run_button, sample_selector):
99
 
100
  fname = lab.filename_gen.random
101
  message = f"### Running Experiment\n"
102
- for k,v in lab.metadata.items():
103
  message += f"####{k} = {v}\n"
104
  message += f"#### File created = {fname}\n"
105
 
@@ -110,9 +101,9 @@ def _(cek, lab, mo, reset_button, run_button, sample_selector):
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])
116
  return (
117
  data,
118
  download_button,
@@ -129,6 +120,7 @@ def _(cek, lab, mo, reset_button, run_button, sample_selector):
129
  @app.cell
130
  def _():
131
  import numpy as np
 
132
  return (np,)
133
 
134
 
 
8
  def _():
9
  import marimo as mo
10
  import pycek_public as cek
11
+
12
  lab = cek.bomb_calorimetry(make_plots=True)
13
  return cek, lab, mo
14
 
 
42
  @app.cell
43
  def _(lab, mo):
44
  def set_ID(value):
45
+ return cek.set_ID(mo, lab, value)
46
+
47
+ student_ID = mo.ui.text(value="", label="Student ID:", on_change=set_ID)
 
 
 
 
 
 
 
 
 
48
 
49
  def set_fname(value):
50
  lab._set_filename(value)
51
 
52
+ exp_ID = mo.ui.text(value="Automatic", label="Output file:", on_change=set_fname)
 
53
 
54
  sample_selector = mo.ui.dropdown(
55
  options=lab.available_samples, value=None, label="Select sample:"
 
90
 
91
  fname = lab.filename_gen.random
92
  message = f"### Running Experiment\n"
93
+ for k, v in lab.metadata.items():
94
  message += f"####{k} = {v}\n"
95
  message += f"#### File created = {fname}\n"
96
 
 
101
  )
102
 
103
  plot = cek.plotting()
104
+ image = plot.quick_plot(scatter=data, output="marimo")
105
 
106
+ mo.hstack([mo.vstack([mo.md(message), download_button]), image])
107
  return (
108
  data,
109
  download_button,
 
120
  @app.cell
121
  def _():
122
  import numpy as np
123
+
124
  return (np,)
125
 
126
 
marimo/crystal_violet.py CHANGED
@@ -8,6 +8,7 @@ app = marimo.App(width="medium")
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
 
@@ -46,41 +47,44 @@ def _(mo):
46
  @app.cell
47
  def _(lab, mo):
48
  def set_ID(value):
49
- try:
50
- student_number = int(value.strip())
51
- if student_number <= 0:
52
- print(mo.md(f"### Invalid Student ID: {student_ID.value}"))
53
- else:
54
- print(f"Valid Student ID: {student_number}")
55
- lab.set_student_ID(int(value))
56
- except ValueError:
57
- mo.stop(not student_ID.value.isdigit(), mo.md(f"### Invalid Student ID: {student_ID.value}"))
58
- print(mo.md(f"### Invalid Student ID: {student_ID.value}"))
59
-
60
- student_ID = mo.ui.text(value="", label="Student ID:",on_change=set_ID)
61
 
62
  def set_fname(value):
63
  lab._set_filename(value)
64
-
65
  exp_ID = mo.ui.text(value="Automatic", label="Output file:", on_change=set_fname)
66
 
67
- cv_volume = mo.ui.number(start=0,stop=100,step=1,value=None,label="Volume of CV solution (mL)")
68
- oh_volume = mo.ui.number(start=0,stop=100,step=1,value=None,label="Volume of OH solution (mL)")
69
- h2o_volume = mo.ui.number(start=0,stop=100,step=1,value=None,label="Volume of DI water (mL)")
70
- temperature = mo.ui.number(start=0,stop=100,step=1,value=25,label="Temperature (C)")
 
 
 
 
 
 
 
 
71
  run_button = mo.ui.run_button(label="Run Experiment")
72
  reset_button = mo.ui.run_button(label="Reset Counter")
73
 
74
  # Create download button using marimo's download function
75
 
76
- mo.vstack([student_ID,
77
- exp_ID,
78
- cv_volume,
79
- oh_volume,
80
- h2o_volume,
81
- temperature,
82
- run_button,
83
- reset_button])
 
 
 
 
84
  return (
85
  cv_volume,
86
  exp_ID,
@@ -120,15 +124,15 @@ def _(
120
  h2o_vol = h2o_volume.value
121
 
122
  lab.set_parameters(
123
- volumes={'cv': cv_vol, 'oh': oh_vol, 'h2o': h2o_vol},
124
- temperature=temperature.value+273.15
125
  )
126
  data = lab.create_data()
127
  file_content = lab.write_data_to_string()
128
 
129
  fname = lab.filename_gen.random
130
  message = f"### Running Experiment\n"
131
- for k,v in lab.metadata.items():
132
  message += f"####{k} = {v}\n"
133
  message += f"#### File created = {fname}\n"
134
 
@@ -139,9 +143,9 @@ def _(
139
  )
140
 
141
  plot = cek.plotting()
142
- image = plot.quick_plot(scatter=data,output="marimo")
143
 
144
- mo.hstack([mo.vstack([mo.md(message),download_button]),image])
145
  return (
146
  cv_vol,
147
  data,
 
8
  def _():
9
  import marimo as mo
10
  import pycek_public as cek
11
+
12
  lab = cek.crystal_violet(make_plots=True)
13
  return cek, lab, mo
14
 
 
47
  @app.cell
48
  def _(lab, mo):
49
  def set_ID(value):
50
+ return cek.set_ID(mo, lab, value)
51
+
52
+ student_ID = mo.ui.text(value="", label="Student ID:", on_change=set_ID)
 
 
 
 
 
 
 
 
 
53
 
54
  def set_fname(value):
55
  lab._set_filename(value)
56
+
57
  exp_ID = mo.ui.text(value="Automatic", label="Output file:", on_change=set_fname)
58
 
59
+ cv_volume = mo.ui.number(
60
+ start=0, stop=100, step=1, value=None, label="Volume of CV solution (mL)"
61
+ )
62
+ oh_volume = mo.ui.number(
63
+ start=0, stop=100, step=1, value=None, label="Volume of OH solution (mL)"
64
+ )
65
+ h2o_volume = mo.ui.number(
66
+ start=0, stop=100, step=1, value=None, label="Volume of DI water (mL)"
67
+ )
68
+ temperature = mo.ui.number(
69
+ start=0, stop=100, step=1, value=25, label="Temperature (C)"
70
+ )
71
  run_button = mo.ui.run_button(label="Run Experiment")
72
  reset_button = mo.ui.run_button(label="Reset Counter")
73
 
74
  # Create download button using marimo's download function
75
 
76
+ mo.vstack(
77
+ [
78
+ student_ID,
79
+ exp_ID,
80
+ cv_volume,
81
+ oh_volume,
82
+ h2o_volume,
83
+ temperature,
84
+ run_button,
85
+ reset_button,
86
+ ]
87
+ )
88
  return (
89
  cv_volume,
90
  exp_ID,
 
124
  h2o_vol = h2o_volume.value
125
 
126
  lab.set_parameters(
127
+ volumes={"cv": cv_vol, "oh": oh_vol, "h2o": h2o_vol},
128
+ temperature=temperature.value + 273.15,
129
  )
130
  data = lab.create_data()
131
  file_content = lab.write_data_to_string()
132
 
133
  fname = lab.filename_gen.random
134
  message = f"### Running Experiment\n"
135
+ for k, v in lab.metadata.items():
136
  message += f"####{k} = {v}\n"
137
  message += f"#### File created = {fname}\n"
138
 
 
143
  )
144
 
145
  plot = cek.plotting()
146
+ image = plot.quick_plot(scatter=data, output="marimo")
147
 
148
+ mo.hstack([mo.vstack([mo.md(message), download_button]), image])
149
  return (
150
  cv_vol,
151
  data,
marimo/statistics_lab.py CHANGED
@@ -1,6 +1,6 @@
1
  import marimo
2
 
3
- __generated_with = "0.11.0"
4
  app = marimo.App(width="medium")
5
 
6
 
@@ -8,6 +8,7 @@ app = marimo.App(width="medium")
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
 
@@ -44,24 +45,14 @@ def _(mo):
44
  @app.cell
45
  def _(lab, mo):
46
  def set_ID(value):
47
- try:
48
- student_number = int(value.strip())
49
- if student_number <= 0:
50
- print(mo.md(f"### Invalid Student ID: {student_ID.value}"))
51
- else:
52
- print(f"Valid Student ID: {student_number}")
53
- lab.set_student_ID(int(value))
54
- except ValueError:
55
- mo.stop(not student_ID.value.isdigit(), mo.md(f"### Invalid Student ID: {student_ID.value}"))
56
- print(mo.md(f"### Invalid Student ID: {student_ID.value}"))
57
-
58
- student_ID = mo.ui.text(value="", label="Student ID:",on_change=set_ID)
59
 
60
  def set_fname(value):
61
- lab._set_filename(value)
62
 
63
- exp_ID = mo.ui.text(value="Automatic", label="Output file:",
64
- on_change=set_fname)
65
 
66
  sample_selector = mo.ui.dropdown(
67
  options=lab.available_samples, value=None, label="Select task:"
@@ -85,27 +76,35 @@ def _(lab, mo):
85
 
86
 
87
  @app.cell
88
- def _(cek, lab, mo, reset_button, run_button, sample_selector):
89
  if reset_button.value:
90
  lab.ID = 0
91
- lab._set_filename(None)
92
 
93
  image = ""
94
  message = ""
95
  download_button = ""
 
 
 
 
 
96
  if run_button.value:
97
- mo.stop(sample_selector.value is None, mo.md(f"### No sample selected !!"))
98
-
99
- lab.set_parameters(
100
- number_of_values = 12,
101
- sample=sample_selector.value
102
  )
 
 
 
103
  data = lab.create_data()
104
  file_content = lab.write_data_to_string()
105
 
106
- fname = lab.filename_gen.random
107
- message = f"### Running Experiment\n"
108
- for k,v in lab.metadata.items():
 
 
109
  message += f"####{k} = {v}\n"
110
  message += f"#### File created = {fname}\n"
111
 
@@ -116,13 +115,11 @@ def _(cek, lab, mo, reset_button, run_button, sample_selector):
116
  )
117
 
118
  plot = cek.plotting()
119
- image = plot.quick_plot(scatter=data,output="marimo")
120
-
121
- mo.hstack([mo.vstack([mo.md(message),download_button]),image])
122
  return (
123
  data,
124
  download_button,
125
- f,
126
  file_content,
127
  fname,
128
  image,
@@ -133,5 +130,10 @@ def _(cek, lab, mo, reset_button, run_button, sample_selector):
133
  )
134
 
135
 
 
 
 
 
 
136
  if __name__ == "__main__":
137
  app.run()
 
1
  import marimo
2
 
3
+ __generated_with = "0.11.9"
4
  app = marimo.App(width="medium")
5
 
6
 
 
8
  def _():
9
  import marimo as mo
10
  import pycek_public as cek
11
+
12
  lab = cek.stats_lab(make_plots=True)
13
  return cek, lab, mo
14
 
 
45
  @app.cell
46
  def _(lab, mo):
47
  def set_ID(value):
48
+ return cek.set_ID(mo, lab, value)
49
+
50
+ student_ID = mo.ui.text(value="", label="Student ID:", on_change=set_ID)
 
 
 
 
 
 
 
 
 
51
 
52
  def set_fname(value):
53
+ lab.output_file = value
54
 
55
+ exp_ID = mo.ui.text(value="Automatic", label="Output file:", on_change=set_fname)
 
56
 
57
  sample_selector = mo.ui.dropdown(
58
  options=lab.available_samples, value=None, label="Select task:"
 
76
 
77
 
78
  @app.cell
79
+ def _(cek, lab, mo, reset_button, run_button, sample_selector, student_ID):
80
  if reset_button.value:
81
  lab.ID = 0
82
+ lab.output_file = None
83
 
84
  image = ""
85
  message = ""
86
  download_button = ""
87
+ data = None
88
+ file_content = None
89
+ fname = None
90
+ plot = None
91
+ # print(run_button.value)
92
  if run_button.value:
93
+ mo.stop(
94
+ not student_ID.value.isdigit(),
95
+ mo.md(f"### Invalid Student ID: {student_ID.value}"),
 
 
96
  )
97
+ mo.stop(sample_selector.value is None, mo.md("### No sample selected !!"))
98
+
99
+ lab.set_parameters(number_of_values=12, sample=sample_selector.value)
100
  data = lab.create_data()
101
  file_content = lab.write_data_to_string()
102
 
103
+ fname = lab.output_file
104
+ if not fname:
105
+ fname = lab.filename_gen.random
106
+ message = "### Running Experiment\n"
107
+ for k, v in lab.metadata.items():
108
  message += f"####{k} = {v}\n"
109
  message += f"#### File created = {fname}\n"
110
 
 
115
  )
116
 
117
  plot = cek.plotting()
118
+ image = plot.quick_plot(scatter=data, output="marimo")
119
+ mo.hstack([mo.vstack([mo.md(message), download_button]), image])
 
120
  return (
121
  data,
122
  download_button,
 
123
  file_content,
124
  fname,
125
  image,
 
130
  )
131
 
132
 
133
+ @app.cell
134
+ def _():
135
+ return
136
+
137
+
138
  if __name__ == "__main__":
139
  app.run()
marimo/surface_adsorption.py CHANGED
@@ -8,6 +8,7 @@ app = marimo.App(width="medium")
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
 
@@ -43,36 +44,25 @@ def _(mo):
43
  @app.cell
44
  def _(lab, mo):
45
  def set_ID(value):
46
- try:
47
- student_number = int(value.strip())
48
- if student_number <= 0:
49
- print(mo.md(f"### Invalid Student ID: {student_ID.value}"))
50
- else:
51
- print(f"Valid Student ID: {student_number}")
52
- lab.set_student_ID(int(value))
53
- except ValueError:
54
- mo.stop(not student_ID.value.isdigit(), mo.md(f"### Invalid Student ID: {student_ID.value}"))
55
- print(mo.md(f"### Invalid Student ID: {student_ID.value}"))
56
-
57
 
58
- student_ID = mo.ui.text(value="", label="Student ID:",on_change=set_ID)
59
 
60
  def set_fname(value):
61
- lab._set_filename(value)
 
62
  exp_ID = mo.ui.text(value="Automatic", label="Output file:", on_change=set_fname)
63
 
64
- temperature = mo.ui.number(start=0,stop=100,step=1,value=25,label="Temperature (C)")
 
 
65
 
66
  run_button = mo.ui.run_button(label="Run Experiment")
67
  reset_button = mo.ui.run_button(label="Reset Counter")
68
 
69
  # Create download button using marimo's download function
70
 
71
- mo.vstack([student_ID,
72
- exp_ID,
73
- temperature,
74
- run_button,
75
- reset_button])
76
  return (
77
  exp_ID,
78
  reset_button,
@@ -88,45 +78,41 @@ def _(lab, mo):
88
  def _(cek, lab, mo, reset_button, run_button, student_ID, temperature):
89
  if reset_button.value:
90
  lab.ID = 0
91
- lab._set_filename(None)
92
 
93
  image = ""
94
  message = ""
95
  download_button = ""
96
- if run_button.value:
97
- lab.set_parameters(
98
- temperature = temperature.value+273.15
99
- )
100
- data = lab.create_data()
101
- file_content = lab.write_data_to_string()
102
-
103
- fname = lab.filename_gen.random
104
- message = f"### Running Experiment\n"
105
- for k,v in lab.metadata.items():
106
- message += f"####{k} = {v}\n"
107
- message += f"#### File created = {fname}\n"
108
-
109
- download_button = mo.download(
110
- file_content,
111
- filename=fname,
112
- label=f"Download {fname}",
113
- )
114
-
115
- plot = cek.plotting()
116
- image = plot.quick_plot(scatter=data,output="marimo")
117
-
118
- mo.hstack([mo.vstack([mo.md(message),download_button]),image])
119
  return (
120
  data,
121
  download_button,
122
- f,
123
  file_content,
124
  fname,
125
  image,
126
- k,
127
  message,
128
  plot,
129
- v,
130
  )
131
 
132
 
 
8
  def _():
9
  import marimo as mo
10
  import pycek_public as cek
11
+
12
  lab = cek.cek.surface_adsorption(make_plots=True)
13
  return cek, lab, mo
14
 
 
44
  @app.cell
45
  def _(lab, mo):
46
  def set_ID(value):
47
+ return cek.set_ID(mo, lab, value)
 
 
 
 
 
 
 
 
 
 
48
 
49
+ student_ID = mo.ui.text(value="", label="Student ID:", on_change=set_ID)
50
 
51
  def set_fname(value):
52
+ lab.output_file = value
53
+
54
  exp_ID = mo.ui.text(value="Automatic", label="Output file:", on_change=set_fname)
55
 
56
+ temperature = mo.ui.number(
57
+ start=0, stop=100, step=1, value=25, label="Temperature (C)"
58
+ )
59
 
60
  run_button = mo.ui.run_button(label="Run Experiment")
61
  reset_button = mo.ui.run_button(label="Reset Counter")
62
 
63
  # Create download button using marimo's download function
64
 
65
+ mo.vstack([student_ID, exp_ID, temperature, run_button, reset_button])
 
 
 
 
66
  return (
67
  exp_ID,
68
  reset_button,
 
78
  def _(cek, lab, mo, reset_button, run_button, student_ID, temperature):
79
  if reset_button.value:
80
  lab.ID = 0
81
+ lab.output_file = None
82
 
83
  image = ""
84
  message = ""
85
  download_button = ""
86
+ if not run_button.value:
87
+ return
88
+ lab.set_parameters(temperature=temperature.value + 273.15)
89
+ data = lab.create_data()
90
+ file_content = lab.write_data_to_string()
91
+
92
+ fname = lab.filename_gen.random
93
+ message = f"### Running Experiment\n"
94
+ for k, v in lab.metadata.items():
95
+ message += f"####{k} = {v}\n"
96
+ message += f"#### File created = {fname}\n"
97
+
98
+ download_button = mo.download(
99
+ file_content,
100
+ filename=fname,
101
+ label=f"Download {fname}",
102
+ )
103
+
104
+ plot = cek.plotting()
105
+ image = plot.quick_plot(scatter=data, output="marimo")
106
+
107
+ mo.hstack([mo.vstack([mo.md(message), download_button]), image])
 
108
  return (
109
  data,
110
  download_button,
 
111
  file_content,
112
  fname,
113
  image,
 
114
  message,
115
  plot,
 
116
  )
117
 
118
 
pyproject.toml CHANGED
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
  where = ["src"]
7
 
8
  [project]
9
- name = "pycek"
10
  version = "1.0.0"
11
  requires-python = ">= 3.8"
12
  authors = [
@@ -18,8 +18,11 @@ readme = {file = "README.md", content-type = "text/markdown"}
18
  dependencies = [
19
  "numpy",
20
  "scipy",
 
21
  "colorama",
22
  "matplotlib",
 
 
23
  ]
24
 
25
  [project.optional-dependencies]
 
6
  where = ["src"]
7
 
8
  [project]
9
+ name = "pycek_public"
10
  version = "1.0.0"
11
  requires-python = ">= 3.8"
12
  authors = [
 
18
  dependencies = [
19
  "numpy",
20
  "scipy",
21
+ "lmfit",
22
  "colorama",
23
  "matplotlib",
24
+ "marimo",
25
+ "fastapi"
26
  ]
27
 
28
  [project.optional-dependencies]
src/pycek_public/cek_labs.py CHANGED
@@ -1,18 +1,20 @@
1
- import pycek_public as cek
2
  import numpy as np
3
  from typing import Callable, Dict, Optional, Union, Tuple
4
 
5
  from collections import OrderedDict
6
 
7
  from abc import ABC, abstractmethod
 
 
8
  class cek_labs(ABC):
9
- def __init__(self, **kwargs):
10
  self.token = None
11
  self.student_ID = 123456789
12
 
13
  self.noise_level = 1
14
  self.precision = 1
15
-
16
  self.available_samples = []
17
  self.sample_parameters = {}
18
  self.sample = None
@@ -25,18 +27,20 @@ class cek_labs(ABC):
25
  self.output_file = None
26
  self.filename_gen = cek.TempFilenameGenerator()
27
 
28
- self.metadata = OrderedDict({
29
- 'student_ID' : self.student_ID,
30
- 'number_of_values' : self.number_of_values,
31
- 'output_file' : self.output_file,
32
- })
33
-
 
 
34
  self.make_plots = False
35
  self.logger_level = "ERROR"
36
 
37
  # Define some lab specific parameters
38
  # Can overwrite the defaults
39
- for k,w in kwargs.items():
40
  setattr(self, k, w)
41
  np.random.seed(self.student_ID)
42
 
@@ -56,12 +60,12 @@ class cek_labs(ABC):
56
  self.list_of_data_files = []
57
 
58
  def __str__(self):
59
- return f'CHEM2000 Lab: {self.__class__.__name__}'
60
 
61
- def set_student_ID(self,student_ID):
62
- if isinstance(student_ID,int):
63
  self.student_ID = student_ID
64
- elif isinstance(student_ID,str):
65
  student_ID = student_ID.strip()
66
  if student_ID.isdigit():
67
  self.student_ID = int(student_ID)
@@ -78,7 +82,7 @@ class cek_labs(ABC):
78
 
79
  def set_token(self, token):
80
  self.token = token
81
- #print(f"Check: {self._check_token()}")
82
 
83
  def _check_token(self):
84
  if self.token != 23745419:
@@ -89,7 +93,7 @@ class cek_labs(ABC):
89
  for key, value in kwargs.items():
90
  self.metadata[key] = value
91
  return
92
-
93
  def update_metadata_from_attr(self):
94
  for k in self.metadata:
95
  try:
@@ -97,12 +101,12 @@ class cek_labs(ABC):
97
  except:
98
  pass
99
  return
100
-
101
  def set_parameters(self, **kwargs):
102
  """
103
  Set parameters for the lab
104
  """
105
- for k,w in kwargs.items():
106
  if k == "student_ID":
107
  self.set_student_ID(w)
108
  else:
@@ -110,53 +114,55 @@ class cek_labs(ABC):
110
  self.update_metadata_from_attr()
111
  return
112
 
113
- def write_metadata(self,f=None):
114
  """
115
  Write metadata to the data file
116
  """
117
  if f is None:
 
118
  def dump(s):
119
  self.logger.info(s)
 
120
  else:
 
121
  def dump(s):
122
- with open(f, 'a') as file:
123
  file.write(f"# {s}\n")
124
 
125
  for key, value in self.metadata.items():
126
  string = f"{key}"
127
- string = string.replace("_"," ")
128
  string = string[0].upper() + string[1:] + f" = {value}"
129
  dump(string)
130
 
131
- def read_metadata(self,f):
132
  """
133
  Read metadata from the data file
134
-
135
  Return: metadata (dict)
136
  """
137
  metadata = OrderedDict({})
138
 
139
  hash_lines = []
140
- with open(f, 'r') as file:
141
  for line in file:
142
- if line.strip().startswith('#'):
143
- hash_lines.append(line.replace('#','').strip())
144
-
145
  for l in hash_lines:
146
  if ":" in l:
147
- key, value = l.split(':')
148
  elif "=" in l:
149
- key, value = l.split('=')
150
  else:
151
  raise Exception("Unknown separator")
152
- key = key.replace("#","").strip()
153
  metadata[key] = value.strip()
154
-
155
  return metadata
156
 
157
  def write_data_to_file(self, **kwargs):
158
- """
159
- """
160
  if self.output_file is None:
161
  filename = self.filename_gen.random
162
  else:
@@ -164,14 +170,14 @@ class cek_labs(ABC):
164
  self.add_metadata(output_file=filename)
165
 
166
  string = self.write_data_to_string(**kwargs)
167
- with open(filename, 'w') as f:
168
  f.write(string)
169
 
170
- self.list_of_data_files.append( filename )
171
 
172
  return filename
173
 
174
- def write_data_to_string(self,**kwargs):
175
  if "columns" in kwargs:
176
  string = ",".join(kwargs["columns"]) + "\n"
177
  elif "columns" in self.metadata:
@@ -183,21 +189,21 @@ class cek_labs(ABC):
183
  for row in self.data:
184
  # Handle multiple columns
185
  if isinstance(row, (list, tuple, np.ndarray)):
186
- string += ",".join(map(str, row))+"\n"
187
  # Handle single-column case
188
  else:
189
- string += str(row)+"\n"
190
 
191
  # Write metadata
192
  for key, value in self.metadata.items():
193
  s = f"{key}"
194
- s = s.replace("_"," ")
195
  s = s[0].upper() + s[1:] + f" = {value}"
196
  string += f"# {s}\n"
197
 
198
  return string
199
 
200
- def read_data_file(self,filename=None):
201
  if filename is None:
202
  raise ValueError("Filename is missing")
203
 
@@ -221,10 +227,14 @@ class cek_labs(ABC):
221
  from numpy.lib.recfunctions import structured_to_unstructured
222
 
223
  data = np.genfromtxt(
224
- StringIO(data_lines), delimiter=',',
225
- comments='#', names=True,
226
- skip_header=0, dtype=None)
227
-
 
 
 
 
228
  data_array = structured_to_unstructured(data)
229
 
230
  metadata = None
@@ -232,27 +242,28 @@ class cek_labs(ABC):
232
  metadata = OrderedDict({})
233
  for l in comments:
234
  if ":" in l:
235
- key, value = l.split(':')
236
  elif "=" in l:
237
- key, value = l.split('=')
238
  else:
239
  raise Exception("Unknown separator")
240
- key = key.replace("#","").strip()
241
  metadata[key.strip()] = value.strip()
242
 
243
- self.logger.debug("-"*50)
244
- for k,v in metadata.items():
245
  self.logger.debug(f"{k} = {v}")
246
- self.logger.debug("-"*50)
247
  # Output results
248
  # print("Comments:")
249
  # print("\n".join(comments))
250
  # print("\nExtracted Data:")
251
  # print(data_array)
252
  return data_array, header, metadata
253
-
254
  def _cleanup(self, pattern=None):
255
  from pathlib import Path
 
256
  for ff in self.list_of_data_files:
257
  # Check if file exists before deleting
258
  file_path = Path(ff)
@@ -263,19 +274,19 @@ class cek_labs(ABC):
263
 
264
  # Delete multiple files using a pattern
265
  if pattern is not None:
266
- for file_path in Path('.').glob(pattern):
267
  file_path.unlink()
268
 
269
  # def process_file(self, filename=None):
270
  # self.read_data(filename)
271
  # result = self.process_data()
272
  # return result
273
-
274
- def _valid_ID(self,ID):
275
  if ID in ["23745411"]:
276
  return True
277
  return False
278
-
279
  def _round_values(self, values, precision=None):
280
  if precision is None:
281
  precision = self.precision
@@ -287,14 +298,16 @@ class cek_labs(ABC):
287
  else:
288
  precision = int(-np.log10(precision))
289
  elif not isinstance(precision, (int, type(None))):
290
- raise TypeError(f"Precision must be an integer or float, got {type(precision)}")
 
 
291
 
292
  return np.round(values, decimals=precision)
293
-
294
  def _generate_uniform_random(self, lower, upper, n):
295
  return self._round_values(np.random.uniform(lower, upper, n))
296
 
297
- def _generate_normal_random(self,n,prm):
298
  list_of_1d_arrays = []
299
  for p in prm:
300
  values = np.random.normal(p[0], p[1], size=n)
@@ -303,9 +316,9 @@ class cek_labs(ABC):
303
  if len(prm) == 1:
304
  return np.array(self._round_values(values))
305
  else:
306
- return np.column_stack( [*list_of_1d_arrays] )
307
 
308
- def _generate_noise(self,n,noise_level=None,ntype="normal"):
309
  if noise_level == None:
310
  raise ValueError("Missing noise level")
311
  if noise_level <= 0:
@@ -314,10 +327,10 @@ class cek_labs(ABC):
314
  return np.random.normal(0, noise_level, size=n)
315
 
316
  def _generate_data_from_function(self, func, params, nvalues, xrange):
317
- x = np.sort(self._generate_uniform_random(nvalues,*xrange))
318
  y = func(x, *params) + self._generate_noise(nvalues)
319
  y = self._round_values(y)
320
- return np.column_stack((x,y))
321
 
322
  def generate_data_from_function(
323
  self,
@@ -325,11 +338,11 @@ class cek_labs(ABC):
325
  params: Dict,
326
  nvalues: int,
327
  xrange: Optional[Tuple[float, float]] = None,
328
- xspacing: str = 'random',
329
  noise_level: Optional[float] = None,
330
  background: Optional[float] = None,
331
  weights: Optional[bool] = None,
332
- positive: bool = False
333
  ) -> np.ndarray:
334
  """
335
  Generate synthetic data points from a given function with optional noise and background.
@@ -370,7 +383,7 @@ class cek_labs(ABC):
370
  # Validate inputs
371
  if xrange is None:
372
  raise ValueError("xrange must be provided as (min, max) tuple")
373
-
374
  if not isinstance(nvalues, int) or nvalues <= 0:
375
  raise ValueError("nvalues must be a positive integer")
376
 
@@ -388,13 +401,13 @@ class cek_labs(ABC):
388
  # Add optional modifications
389
  if background is not None:
390
  y += background
391
-
392
  if noise_level is not None:
393
- y += self._generate_noise(nvalues,noise_level)
394
-
395
  if positive:
396
- eps = np.power(10.,-self.precision)
397
- y = [ max(eps,np.abs(x)) for x in y ]
398
 
399
  # Note: weights parameter is currently unused
400
  if weights is not None:
@@ -407,21 +420,44 @@ class cek_labs(ABC):
407
 
408
  def create_data_file(self):
409
  data = self.create_data()
410
- self.write_data_to_file(
411
- self.metadata['output_file'],
412
- data, **self.metadata )
413
- return self.metadata['output_file']
414
-
415
  def get_data(self):
416
  return self.data
 
417
  def get_metadata(self):
418
  return self.metadata
419
 
420
  @abstractmethod
421
- def setup_lab(self,**kwargs):
422
  pass
423
 
424
  @abstractmethod
425
  def create_data(self):
426
  pass
427
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import pycek as cek
2
  import numpy as np
3
  from typing import Callable, Dict, Optional, Union, Tuple
4
 
5
  from collections import OrderedDict
6
 
7
  from abc import ABC, abstractmethod
8
+
9
+
10
  class cek_labs(ABC):
11
+ def __init__(self, **kwargs):
12
  self.token = None
13
  self.student_ID = 123456789
14
 
15
  self.noise_level = 1
16
  self.precision = 1
17
+
18
  self.available_samples = []
19
  self.sample_parameters = {}
20
  self.sample = None
 
27
  self.output_file = None
28
  self.filename_gen = cek.TempFilenameGenerator()
29
 
30
+ self.metadata = OrderedDict(
31
+ {
32
+ "student_ID": self.student_ID,
33
+ "number_of_values": self.number_of_values,
34
+ "output_file": self.output_file,
35
+ }
36
+ )
37
+
38
  self.make_plots = False
39
  self.logger_level = "ERROR"
40
 
41
  # Define some lab specific parameters
42
  # Can overwrite the defaults
43
+ for k, w in kwargs.items():
44
  setattr(self, k, w)
45
  np.random.seed(self.student_ID)
46
 
 
60
  self.list_of_data_files = []
61
 
62
  def __str__(self):
63
+ return f"CHEM2000 Lab: {self.__class__.__name__}"
64
 
65
+ def set_student_ID(self, student_ID):
66
+ if isinstance(student_ID, int):
67
  self.student_ID = student_ID
68
+ elif isinstance(student_ID, str):
69
  student_ID = student_ID.strip()
70
  if student_ID.isdigit():
71
  self.student_ID = int(student_ID)
 
82
 
83
  def set_token(self, token):
84
  self.token = token
85
+ # print(f"Check: {self._check_token()}")
86
 
87
  def _check_token(self):
88
  if self.token != 23745419:
 
93
  for key, value in kwargs.items():
94
  self.metadata[key] = value
95
  return
96
+
97
  def update_metadata_from_attr(self):
98
  for k in self.metadata:
99
  try:
 
101
  except:
102
  pass
103
  return
104
+
105
  def set_parameters(self, **kwargs):
106
  """
107
  Set parameters for the lab
108
  """
109
+ for k, w in kwargs.items():
110
  if k == "student_ID":
111
  self.set_student_ID(w)
112
  else:
 
114
  self.update_metadata_from_attr()
115
  return
116
 
117
+ def write_metadata(self, f=None):
118
  """
119
  Write metadata to the data file
120
  """
121
  if f is None:
122
+
123
  def dump(s):
124
  self.logger.info(s)
125
+
126
  else:
127
+
128
  def dump(s):
129
+ with open(f, "a") as file:
130
  file.write(f"# {s}\n")
131
 
132
  for key, value in self.metadata.items():
133
  string = f"{key}"
134
+ string = string.replace("_", " ")
135
  string = string[0].upper() + string[1:] + f" = {value}"
136
  dump(string)
137
 
138
+ def read_metadata(self, f):
139
  """
140
  Read metadata from the data file
141
+
142
  Return: metadata (dict)
143
  """
144
  metadata = OrderedDict({})
145
 
146
  hash_lines = []
147
+ with open(f, "r") as file:
148
  for line in file:
149
+ if line.strip().startswith("#"):
150
+ hash_lines.append(line.replace("#", "").strip())
151
+
152
  for l in hash_lines:
153
  if ":" in l:
154
+ key, value = l.split(":")
155
  elif "=" in l:
156
+ key, value = l.split("=")
157
  else:
158
  raise Exception("Unknown separator")
159
+ key = key.replace("#", "").strip()
160
  metadata[key] = value.strip()
161
+
162
  return metadata
163
 
164
  def write_data_to_file(self, **kwargs):
165
+ """ """
 
166
  if self.output_file is None:
167
  filename = self.filename_gen.random
168
  else:
 
170
  self.add_metadata(output_file=filename)
171
 
172
  string = self.write_data_to_string(**kwargs)
173
+ with open(filename, "w") as f:
174
  f.write(string)
175
 
176
+ self.list_of_data_files.append(filename)
177
 
178
  return filename
179
 
180
+ def write_data_to_string(self, **kwargs):
181
  if "columns" in kwargs:
182
  string = ",".join(kwargs["columns"]) + "\n"
183
  elif "columns" in self.metadata:
 
189
  for row in self.data:
190
  # Handle multiple columns
191
  if isinstance(row, (list, tuple, np.ndarray)):
192
+ string += ",".join(map(str, row)) + "\n"
193
  # Handle single-column case
194
  else:
195
+ string += str(row) + "\n"
196
 
197
  # Write metadata
198
  for key, value in self.metadata.items():
199
  s = f"{key}"
200
+ s = s.replace("_", " ")
201
  s = s[0].upper() + s[1:] + f" = {value}"
202
  string += f"# {s}\n"
203
 
204
  return string
205
 
206
+ def read_data_file(self, filename=None):
207
  if filename is None:
208
  raise ValueError("Filename is missing")
209
 
 
227
  from numpy.lib.recfunctions import structured_to_unstructured
228
 
229
  data = np.genfromtxt(
230
+ StringIO(data_lines),
231
+ delimiter=",",
232
+ comments="#",
233
+ names=True,
234
+ skip_header=0,
235
+ dtype=None,
236
+ )
237
+
238
  data_array = structured_to_unstructured(data)
239
 
240
  metadata = None
 
242
  metadata = OrderedDict({})
243
  for l in comments:
244
  if ":" in l:
245
+ key, value = l.split(":")
246
  elif "=" in l:
247
+ key, value = l.split("=")
248
  else:
249
  raise Exception("Unknown separator")
250
+ key = key.replace("#", "").strip()
251
  metadata[key.strip()] = value.strip()
252
 
253
+ self.logger.debug("-" * 50)
254
+ for k, v in metadata.items():
255
  self.logger.debug(f"{k} = {v}")
256
+ self.logger.debug("-" * 50)
257
  # Output results
258
  # print("Comments:")
259
  # print("\n".join(comments))
260
  # print("\nExtracted Data:")
261
  # print(data_array)
262
  return data_array, header, metadata
263
+
264
  def _cleanup(self, pattern=None):
265
  from pathlib import Path
266
+
267
  for ff in self.list_of_data_files:
268
  # Check if file exists before deleting
269
  file_path = Path(ff)
 
274
 
275
  # Delete multiple files using a pattern
276
  if pattern is not None:
277
+ for file_path in Path(".").glob(pattern):
278
  file_path.unlink()
279
 
280
  # def process_file(self, filename=None):
281
  # self.read_data(filename)
282
  # result = self.process_data()
283
  # return result
284
+
285
+ def _valid_ID(self, ID):
286
  if ID in ["23745411"]:
287
  return True
288
  return False
289
+
290
  def _round_values(self, values, precision=None):
291
  if precision is None:
292
  precision = self.precision
 
298
  else:
299
  precision = int(-np.log10(precision))
300
  elif not isinstance(precision, (int, type(None))):
301
+ raise TypeError(
302
+ f"Precision must be an integer or float, got {type(precision)}"
303
+ )
304
 
305
  return np.round(values, decimals=precision)
306
+
307
  def _generate_uniform_random(self, lower, upper, n):
308
  return self._round_values(np.random.uniform(lower, upper, n))
309
 
310
+ def _generate_normal_random(self, n, prm):
311
  list_of_1d_arrays = []
312
  for p in prm:
313
  values = np.random.normal(p[0], p[1], size=n)
 
316
  if len(prm) == 1:
317
  return np.array(self._round_values(values))
318
  else:
319
+ return np.column_stack([*list_of_1d_arrays])
320
 
321
+ def _generate_noise(self, n, noise_level=None, ntype="normal"):
322
  if noise_level == None:
323
  raise ValueError("Missing noise level")
324
  if noise_level <= 0:
 
327
  return np.random.normal(0, noise_level, size=n)
328
 
329
  def _generate_data_from_function(self, func, params, nvalues, xrange):
330
+ x = np.sort(self._generate_uniform_random(nvalues, *xrange))
331
  y = func(x, *params) + self._generate_noise(nvalues)
332
  y = self._round_values(y)
333
+ return np.column_stack((x, y))
334
 
335
  def generate_data_from_function(
336
  self,
 
338
  params: Dict,
339
  nvalues: int,
340
  xrange: Optional[Tuple[float, float]] = None,
341
+ xspacing: str = "random",
342
  noise_level: Optional[float] = None,
343
  background: Optional[float] = None,
344
  weights: Optional[bool] = None,
345
+ positive: bool = False,
346
  ) -> np.ndarray:
347
  """
348
  Generate synthetic data points from a given function with optional noise and background.
 
383
  # Validate inputs
384
  if xrange is None:
385
  raise ValueError("xrange must be provided as (min, max) tuple")
386
+
387
  if not isinstance(nvalues, int) or nvalues <= 0:
388
  raise ValueError("nvalues must be a positive integer")
389
 
 
401
  # Add optional modifications
402
  if background is not None:
403
  y += background
404
+
405
  if noise_level is not None:
406
+ y += self._generate_noise(nvalues, noise_level)
407
+
408
  if positive:
409
+ eps = np.power(10.0, -self.precision)
410
+ y = [max(eps, np.abs(x)) for x in y]
411
 
412
  # Note: weights parameter is currently unused
413
  if weights is not None:
 
420
 
421
  def create_data_file(self):
422
  data = self.create_data()
423
+ self.write_data_to_file(self.metadata["output_file"], data, **self.metadata)
424
+ return self.metadata["output_file"]
425
+
 
 
426
  def get_data(self):
427
  return self.data
428
+
429
  def get_metadata(self):
430
  return self.metadata
431
 
432
  @abstractmethod
433
+ def setup_lab(self, **kwargs):
434
  pass
435
 
436
  @abstractmethod
437
  def create_data(self):
438
  pass
439
 
440
+ ## --- END STUDENT VERSION -- ##
441
+
442
+ def process_data_file(self, filename):
443
+ self.logger.result(f"Processing file {filename}")
444
+ data, cols, mtd = self.read_data_file(filename)
445
+ result = self.process_data(data, mtd)
446
+ return result
447
+
448
+ @abstractmethod
449
+ def process_data(self, data, **kwargs):
450
+ pass
451
+
452
+
453
+ def set_ID(mo, lab, value):
454
+ try:
455
+ student_number = int(value.strip())
456
+ if student_number <= 0:
457
+ error = f"### Invalid Student ID: {value}"
458
+ print(mo.md(error))
459
+ raise ValueError(error)
460
+ print(mo.md(f"Valid Student ID: {student_number}"))
461
+ lab.set_student_ID(int(value))
462
+ except ValueError:
463
+ print(mo.md(f"### Invalid Student ID: {value}"))
src/pycek_public/plotting.py CHANGED
@@ -53,6 +53,11 @@ class plotting():
53
  # Display the legend
54
  plt.legend()
55
 
 
 
 
 
 
56
  # Show the plot
57
  if output is None:
58
  plt.show()
@@ -62,3 +67,169 @@ class plotting():
62
  plt.savefig(output)
63
  plt.close()
64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
  # Display the legend
54
  plt.legend()
55
 
56
+ ax = plt.gca()
57
+ ax.text(0.5, 0.5, 'TEMPLATE', transform=ax.transAxes,
58
+ fontsize=40, color='gray', alpha=0.5,
59
+ ha='center', va='center', rotation=30)
60
+
61
  # Show the plot
62
  if output is None:
63
  plt.show()
 
67
  plt.savefig(output)
68
  plt.close()
69
 
70
+ ## --- END STUDENT VERSION -- ##
71
+
72
+ def get_t_value(self, ndof, confidence=None):
73
+ """
74
+ Calculate the two-tailed Student's t-value for a given number of degrees of freedom
75
+ and confidence level.
76
+
77
+ Parameters:
78
+ ndof (int): Number of degrees of freedom
79
+ confidence (float): Confidence level, default is 0.95 for 95% confidence
80
+
81
+ Returns:
82
+ float: The critical t-value for the specified parameters
83
+
84
+ Example:
85
+ For 95% confidence and 10 degrees of freedom:
86
+ >>> get_t_value(10)
87
+ 2.2281388519495335
88
+ """
89
+ if confidence is None:
90
+ confidence = self._confidence_level
91
+
92
+ # Calculate alpha (significance level) from confidence level
93
+ # For 95% confidence, alpha = 0.05
94
+ alpha = 1 - confidence
95
+
96
+ # Calculate the t-value using the percent point function (PPF) of the t-distribution
97
+ # We use alpha/2 for two-tailed test and (1 - alpha/2) for the upper tail
98
+ tval = stats.t.ppf(1.0 - alpha/2., ndof)
99
+
100
+ return tval
101
+
102
+ def plot_fit_with_confidence_band(
103
+ self, data, fit_model, popt,
104
+ confidence=None, output=None):
105
+ """
106
+ Plot the data along with the best fit line and its associated confidence band.
107
+
108
+ Parameters:
109
+ x_data (array-like): Independent variable data points
110
+ y_data (array-like): Observed dependent variable values
111
+ fit_model (callable): The model function used in the fit
112
+ popt: Optimal parameter values from the fit
113
+ confidence (float, optional): Confidence level for the confidence band
114
+ (default is 0.95)
115
+
116
+ Returns:
117
+ None: Displays a matplotlib plot with data, fit line, and confidence band
118
+
119
+ Example:
120
+ >>> fit_result = scipy_function_fit(x_data, y_data, linear_model)
121
+ >>> plot_fit_with_confidence_band(x_data, y_data, fit_result)
122
+ """
123
+ x_data = data[:,0]
124
+ y_data = data[:,1]
125
+
126
+ # Calculate degrees of freedom: number of data points minus number of parameters
127
+ ndof = max(0, len(x_data) - len(popt))
128
+
129
+ # Create the plot figure with a specified size
130
+ plt.figure(figsize=(10, 6))
131
+
132
+ # Plot the observed data points as a scatter plot
133
+ plt.scatter(x_data, y_data, color='blue', label='Data')
134
+
135
+ # Generate points for the fitted line
136
+ # Linearly spaced between the minimum and maximum of x_data
137
+ x_fit = np.linspace(min(x_data), max(x_data), 100)
138
+
139
+ # Calculate the fitted y values using the optimal parameters
140
+ y_fit = fit_model(x_fit, *popt)
141
+
142
+ # Plot the fitted line
143
+ plt.plot(x_fit, y_fit, 'r-', label='Best fit')
144
+
145
+ if confidence is not None:
146
+ # Calculate the error for the confidence band based on the data and fit
147
+ y_err = np.sqrt(1/len(x_data) + (x_fit - np.mean(x_data))**2 /
148
+ np.sum((x_data - np.mean(x_data))**2))
149
+
150
+ # t-statistic used to calculate the confidence interval
151
+ tval = self.get_t_value(ndof, confidence)
152
+
153
+ # Calculate the upper and lower bounds of the confidence band
154
+ bounds = np.sqrt(np.sum((y_data - fit_model(x_data, *popt))**2) / ndof)
155
+ y_upper = y_fit + tval * y_err * bounds
156
+ y_lower = y_fit - tval * y_err * bounds
157
+
158
+ # Plot the confidence band by shading the area between the upper and lower bounds
159
+ plt.fill_between(x_fit, y_lower, y_upper,
160
+ color='gray', alpha=0.2,
161
+ label=f'{int(confidence*100)}% Confidence band')
162
+ plt.title(f'Data with Linear Fit and {int(confidence*100)}% Confidence Bands')
163
+ else:
164
+ plt.title(f'Data with Linear Fit')
165
+
166
+ # Add labels and title to the plot
167
+ plt.xlabel('x')
168
+ plt.ylabel('y')
169
+
170
+ # ymin = np.min(y_data) - 0.1*np.abs(np.min(y_data))
171
+ # ymax = np.max(y_data) + 0.1*np.abs(np.max(y_data))
172
+ # ax = plt.gca()
173
+ # ax.set_ylim([ymin, ymax])
174
+
175
+ # Display the legend
176
+ plt.legend()
177
+
178
+ # Show the plot
179
+ if output is None:
180
+ plt.show()
181
+ else:
182
+ plt.savefig(output)
183
+
184
+ def plot_residuals(self, data, fit_model, popt):
185
+ """
186
+ Create a residual plot to visualize the differences between observed and predicted values.
187
+ Residuals are the differences between actual y values and model predictions.
188
+
189
+ Parameters:
190
+ x_data (array-like): Independent variable data points (input for the model)
191
+ y_data (array-like): Observed dependent variable values (true values)
192
+ fit_model (callable): The model function used in the fit
193
+ popt: Optimal parameter values from the fit
194
+ The 'result' dictionary should include at least the 'popt' key, which contains the
195
+ optimal parameters for the fitted model.
196
+
197
+ Returns:
198
+ None: Displays a matplotlib plot of residuals
199
+
200
+ Example:
201
+ >>> fit_result = scipy_function_fit(x_data, y_data, linear_model)
202
+ >>> plot_residuals(x_data, y_data, fit_result)
203
+ """
204
+ x_data = data[:,0]
205
+ y_data = data[:,1]
206
+
207
+ # Calculate model predictions using the fitted parameters ('popt' contains optimal parameters)
208
+ y_pred = fit_model(x_data, *popt)
209
+
210
+ # Calculate residuals (observed y values - predicted y values)
211
+ residuals = y_data - y_pred
212
+
213
+ # Create a new figure with a specified size for the plot
214
+ plt.figure(figsize=(10, 6))
215
+
216
+ # Scatter plot of the residuals vs the x_data points
217
+ # Each point in the scatter plot corresponds to the residual for a specific x_data value
218
+ plt.scatter(x_data, residuals, color='blue', label='Data')
219
+
220
+ # Add a horizontal line at y=0 for reference to see how residuals deviate from 0
221
+ plt.axhline(y=0, color='black', linestyle='--', label='Zero Residuals')
222
+
223
+ # Label the x and y axes
224
+ plt.xlabel('x') # x-axis represents the independent variable
225
+ plt.ylabel('e') # y-axis represents the residuals or errors (observed - predicted)
226
+
227
+ # Add a title to the plot
228
+ plt.title('Plot of the residuals')
229
+
230
+ # Optionally, add a legend to clarify the plot's labels
231
+ plt.legend()
232
+
233
+ # Display the plot
234
+ # plt.savefig("fit_residuals.png")
235
+ plt.show()