Spaces:
Running
Running
Commit ·
5cc02e3
1
Parent(s): a28dfd2
remove build directory...
Browse files- Dockerfile +4 -2
- build/lib/pycek_public/__init__.py +0 -10
- build/lib/pycek_public/bomb_calorimetry.py +0 -113
- build/lib/pycek_public/cek_labs.py +0 -388
- build/lib/pycek_public/crystal_violet.py +0 -75
- build/lib/pycek_public/generate_random_filenames.py +0 -166
- build/lib/pycek_public/logger.py +0 -112
- build/lib/pycek_public/statistics_lab.py +0 -125
- build/lib/pycek_public/surface_adsorption.py +0 -89
Dockerfile
CHANGED
|
@@ -5,10 +5,12 @@ ENV PATH="/home/user/.local/bin:$PATH"
|
|
| 5 |
|
| 6 |
ENV UV_SYSTEM_PYTHON=1
|
| 7 |
WORKDIR /app
|
|
|
|
|
|
|
| 8 |
|
| 9 |
# Copy and install the local repo, then remove it
|
| 10 |
-
|
| 11 |
-
RUN uv pip install
|
| 12 |
COPY --chown=user ./marimo /app
|
| 13 |
RUN chown -R user:user /app
|
| 14 |
USER user
|
|
|
|
| 5 |
|
| 6 |
ENV UV_SYSTEM_PYTHON=1
|
| 7 |
WORKDIR /app
|
| 8 |
+
COPY --chown=user ./deployment/requirements.txt requirements.txt
|
| 9 |
+
RUN uv pip install -r requirements.txt
|
| 10 |
|
| 11 |
# Copy and install the local repo, then remove it
|
| 12 |
+
COPY --chown=user ./ /pycek_public
|
| 13 |
+
RUN uv pip install /pycek_public && rm -rf /pycek_public
|
| 14 |
COPY --chown=user ./marimo /app
|
| 15 |
RUN chown -R user:user /app
|
| 16 |
USER user
|
build/lib/pycek_public/__init__.py
DELETED
|
@@ -1,10 +0,0 @@
|
|
| 1 |
-
from .cek_labs import *
|
| 2 |
-
|
| 3 |
-
from .generate_random_filenames import *
|
| 4 |
-
from .logger import *
|
| 5 |
-
|
| 6 |
-
from .statistics_lab import *
|
| 7 |
-
from .bomb_calorimetry import *
|
| 8 |
-
from .crystal_violet import *
|
| 9 |
-
from .surface_adsorption import *
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/lib/pycek_public/bomb_calorimetry.py
DELETED
|
@@ -1,113 +0,0 @@
|
|
| 1 |
-
import pycek_public as cek
|
| 2 |
-
import numpy as np
|
| 3 |
-
import pprint as pp
|
| 4 |
-
|
| 5 |
-
class bomb_calorimetry(cek.cek_labs):
|
| 6 |
-
def setup_lab(self):
|
| 7 |
-
"""
|
| 8 |
-
Define base information for the lab
|
| 9 |
-
"""
|
| 10 |
-
self.add_metadata(
|
| 11 |
-
laboratory = 'Bomb Calorimetry',
|
| 12 |
-
columns = ["Time (s)","Temperature (K)"]
|
| 13 |
-
)
|
| 14 |
-
|
| 15 |
-
self.available_samples = ['benzoic', 'sucrose', 'naphthalene']
|
| 16 |
-
|
| 17 |
-
self.ignition_time = 20
|
| 18 |
-
self.relaxation_time = 3
|
| 19 |
-
self.number_of_values = 100
|
| 20 |
-
self.noise_level = 0.1
|
| 21 |
-
|
| 22 |
-
self.slope_before = np.random.uniform(0., self.noise_level) / 3
|
| 23 |
-
self.slope_after = np.random.uniform(0., self.noise_level) / 3
|
| 24 |
-
|
| 25 |
-
self.RT = self.R * self.temperature
|
| 26 |
-
|
| 27 |
-
# calorimeter constant (J/K)
|
| 28 |
-
self.calorimeter_constant = {'value':10135,'std_error':0.0}
|
| 29 |
-
self.sample_parameters["co2"] = {
|
| 30 |
-
"mM" : 44.01,
|
| 31 |
-
"dH" : -393.51e3, # co2 enthapy of formation (J/mol/K)
|
| 32 |
-
}
|
| 33 |
-
self.sample_parameters["h2o"] = {
|
| 34 |
-
"mM" : 18.015,
|
| 35 |
-
"dH" : -285.83e3,# h2o enthapy of formation (J/mol/K)
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
self.sample_parameters["benzoic"] = {
|
| 39 |
-
"mM" : 122.123,
|
| 40 |
-
"n1" : 7,
|
| 41 |
-
"n2" : 3,
|
| 42 |
-
"dn" : 7-15/2,
|
| 43 |
-
"dHf" : {'value':-384.8e3,'std_error':0.5e3},
|
| 44 |
-
"dHc" : {'value':-3227.26e3,'std_error':0.2e3},
|
| 45 |
-
}
|
| 46 |
-
self.sample_parameters["sucrose"] = {
|
| 47 |
-
"mM" : 342.3,
|
| 48 |
-
"n1" : 12,
|
| 49 |
-
"n2" : 11,
|
| 50 |
-
"dn" : 0,
|
| 51 |
-
"dHf" : {'value':-2221.2e3,'std_error':0.2e3},
|
| 52 |
-
"dHc" : {'value':-5643.4e3,'std_error':1.8e3},
|
| 53 |
-
}
|
| 54 |
-
self.sample_parameters["naphthalene"] = {
|
| 55 |
-
"mM" : 128.17,
|
| 56 |
-
"n1" : 10,
|
| 57 |
-
"n2" : 4,
|
| 58 |
-
"dn" : 10 - 12,
|
| 59 |
-
"dHf" : {'value':77e3,'std_error':10.0e3},
|
| 60 |
-
"dHc" : {'value':-5160e3,'std_error':20.0e3},
|
| 61 |
-
}
|
| 62 |
-
|
| 63 |
-
def create_data(self):
|
| 64 |
-
"""
|
| 65 |
-
Generate the data
|
| 66 |
-
"""
|
| 67 |
-
if self.sample is None:
|
| 68 |
-
raise Exception("Sample not defined")
|
| 69 |
-
|
| 70 |
-
prm = self.sample_parameters[ self.sample ]
|
| 71 |
-
|
| 72 |
-
self.set_parameters(
|
| 73 |
-
sample = self.sample,
|
| 74 |
-
number_of_values = self.number_of_values,
|
| 75 |
-
)
|
| 76 |
-
|
| 77 |
-
self.mass = np.random.normal(1000, 100)
|
| 78 |
-
self.add_metadata(**{
|
| 79 |
-
'Tablet mass (mg)': self.mass,
|
| 80 |
-
"Ignition time (s)" : self.ignition_time,
|
| 81 |
-
"Sample" : self.sample,
|
| 82 |
-
})
|
| 83 |
-
|
| 84 |
-
moles = self.mass / 1000 / prm["mM"]
|
| 85 |
-
|
| 86 |
-
# combustion enthalpy
|
| 87 |
-
# nH{co2} + mH{h2o} - H = DcH
|
| 88 |
-
DcH = prm["n1"] * self.sample_parameters["co2"]["dH"] + \
|
| 89 |
-
prm["n2"] * self.sample_parameters["h2o"]["dH"] - prm["dHf"]["value"]
|
| 90 |
-
|
| 91 |
-
dH = DcH * moles
|
| 92 |
-
dnrt = moles * self.RT * prm["dn"]
|
| 93 |
-
dU = dH - dnrt
|
| 94 |
-
|
| 95 |
-
deltaT = -dU / self.calorimeter_constant['value']
|
| 96 |
-
|
| 97 |
-
x = np.linspace(0, self.number_of_values, self.number_of_values)
|
| 98 |
-
y = np.random.normal(0, self.noise_level, self.number_of_values)
|
| 99 |
-
|
| 100 |
-
dd = 0.
|
| 101 |
-
T = self.temperature
|
| 102 |
-
for i in range(self.number_of_values):
|
| 103 |
-
if i < self.ignition_time:
|
| 104 |
-
T += self.slope_before
|
| 105 |
-
else:
|
| 106 |
-
T += self.slope_after
|
| 107 |
-
dd = deltaT * (1 - np.exp( - (i - self.ignition_time) / self.relaxation_time) )
|
| 108 |
-
y[i] += T + dd
|
| 109 |
-
|
| 110 |
-
self.data = np.column_stack((x,y))
|
| 111 |
-
|
| 112 |
-
return
|
| 113 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/lib/pycek_public/cek_labs.py
DELETED
|
@@ -1,388 +0,0 @@
|
|
| 1 |
-
import pycek_public as cek
|
| 2 |
-
import numpy as np
|
| 3 |
-
from collections import OrderedDict
|
| 4 |
-
|
| 5 |
-
from abc import ABC, abstractmethod
|
| 6 |
-
class cek_labs(ABC):
|
| 7 |
-
def __init__(self, **kwargs):
|
| 8 |
-
self.token = None
|
| 9 |
-
self.student_ID = 123456789
|
| 10 |
-
|
| 11 |
-
self.noise_level = 1
|
| 12 |
-
self.precision = 1
|
| 13 |
-
|
| 14 |
-
self.available_samples = []
|
| 15 |
-
self.sample_parameters = {}
|
| 16 |
-
self.sample = None
|
| 17 |
-
|
| 18 |
-
self.R = 8.314
|
| 19 |
-
self.NA = 6.022e23
|
| 20 |
-
self.temperature = 298
|
| 21 |
-
|
| 22 |
-
self.number_of_values = 100
|
| 23 |
-
self.output_file = None
|
| 24 |
-
self.filename_gen = cek.TempFilenameGenerator()
|
| 25 |
-
|
| 26 |
-
self.metadata = OrderedDict({
|
| 27 |
-
'student_ID' : self.student_ID,
|
| 28 |
-
'number_of_values' : self.number_of_values,
|
| 29 |
-
'output_file' : self.output_file,
|
| 30 |
-
})
|
| 31 |
-
|
| 32 |
-
self.logger_level = "ERROR"
|
| 33 |
-
|
| 34 |
-
# Define some lab specific parameters
|
| 35 |
-
# Can overwrite the defaults
|
| 36 |
-
for k,w in kwargs.items():
|
| 37 |
-
setattr(self, k, w)
|
| 38 |
-
np.random.seed(self.student_ID)
|
| 39 |
-
|
| 40 |
-
self.logger = cek.setup_logger(level=self.logger_level)
|
| 41 |
-
# self.logger.debug("This is a debug message")
|
| 42 |
-
# self.logger.verbose("This is a verbose message") # New verbose level
|
| 43 |
-
# self.logger.info("This is an info message")
|
| 44 |
-
# self.logger.result("This is an result message")
|
| 45 |
-
# self.logger.warning("This is a warning message")
|
| 46 |
-
# self.logger.error("This is an error message")
|
| 47 |
-
# self.logger.critical("This is a critical message")
|
| 48 |
-
# quit()
|
| 49 |
-
|
| 50 |
-
# Lab specific parameters
|
| 51 |
-
self.setup_lab()
|
| 52 |
-
|
| 53 |
-
def __str__(self):
|
| 54 |
-
return f'CHEM2000 Lab: {self.__class__.__name__}'
|
| 55 |
-
|
| 56 |
-
def set_student_ID(self,student_ID):
|
| 57 |
-
if isinstance(student_ID,int):
|
| 58 |
-
self.student_ID = student_ID
|
| 59 |
-
elif isinstance(student_ID,str):
|
| 60 |
-
if student_ID.isdigit():
|
| 61 |
-
self.student_ID = int(student_ID)
|
| 62 |
-
else:
|
| 63 |
-
raise ValueError("student_ID must be an integer")
|
| 64 |
-
else:
|
| 65 |
-
raise ValueError("student_ID must be an integer")
|
| 66 |
-
np.random.seed(self.student_ID)
|
| 67 |
-
self.update_metadata_from_attr()
|
| 68 |
-
self.logger.debug(f"Initial seed = {np.random.get_state()[1][0]}")
|
| 69 |
-
|
| 70 |
-
def set_token(self, token):
|
| 71 |
-
self.token = token
|
| 72 |
-
#print(f"Check: {self._check_token()}")
|
| 73 |
-
|
| 74 |
-
def _check_token(self):
|
| 75 |
-
if self.token != 23745419:
|
| 76 |
-
return True
|
| 77 |
-
return False
|
| 78 |
-
|
| 79 |
-
def add_metadata(self, **kwargs):
|
| 80 |
-
for key, value in kwargs.items():
|
| 81 |
-
self.metadata[key] = value
|
| 82 |
-
return
|
| 83 |
-
|
| 84 |
-
def update_metadata_from_attr(self):
|
| 85 |
-
for k in self.metadata:
|
| 86 |
-
try:
|
| 87 |
-
self.metadata[k] = getattr(self, k)
|
| 88 |
-
except:
|
| 89 |
-
pass
|
| 90 |
-
return
|
| 91 |
-
|
| 92 |
-
def set_parameters(self, **kwargs):
|
| 93 |
-
"""
|
| 94 |
-
Set parameters for the lab
|
| 95 |
-
"""
|
| 96 |
-
for k,w in kwargs.items():
|
| 97 |
-
if k == "student_ID":
|
| 98 |
-
self.set_student_ID(w)
|
| 99 |
-
else:
|
| 100 |
-
setattr(self, k, w)
|
| 101 |
-
self.update_metadata_from_attr()
|
| 102 |
-
return
|
| 103 |
-
|
| 104 |
-
def write_metadata(self,f=None):
|
| 105 |
-
"""
|
| 106 |
-
Write metadata to the data file
|
| 107 |
-
"""
|
| 108 |
-
if f is None:
|
| 109 |
-
def dump(s):
|
| 110 |
-
self.logger.info(s)
|
| 111 |
-
else:
|
| 112 |
-
def dump(s):
|
| 113 |
-
with open(f, 'a') as file:
|
| 114 |
-
file.write(f"# {s}\n")
|
| 115 |
-
|
| 116 |
-
for key, value in self.metadata.items():
|
| 117 |
-
string = f"{key}"
|
| 118 |
-
string = string.replace("_"," ")
|
| 119 |
-
string = string[0].upper() + string[1:] + f" = {value}"
|
| 120 |
-
dump(string)
|
| 121 |
-
|
| 122 |
-
def read_metadata(self,f):
|
| 123 |
-
"""
|
| 124 |
-
Read metadata from the data file
|
| 125 |
-
|
| 126 |
-
Return: metadata (dict)
|
| 127 |
-
"""
|
| 128 |
-
metadata = OrderedDict({})
|
| 129 |
-
|
| 130 |
-
hash_lines = []
|
| 131 |
-
with open(f, 'r') as file:
|
| 132 |
-
for line in file:
|
| 133 |
-
if line.strip().startswith('#'):
|
| 134 |
-
hash_lines.append(line.replace('#','').strip())
|
| 135 |
-
|
| 136 |
-
for l in hash_lines:
|
| 137 |
-
if ":" in l:
|
| 138 |
-
key, value = l.split(':')
|
| 139 |
-
elif "=" in l:
|
| 140 |
-
key, value = l.split('=')
|
| 141 |
-
else:
|
| 142 |
-
raise Exception("Unknown separator")
|
| 143 |
-
key = key.replace("#","").strip()
|
| 144 |
-
metadata[key] = value.strip()
|
| 145 |
-
|
| 146 |
-
return metadata
|
| 147 |
-
|
| 148 |
-
def write_data_to_file(self, **kwargs):
|
| 149 |
-
"""
|
| 150 |
-
"""
|
| 151 |
-
if self.output_file is None:
|
| 152 |
-
filename = self.filename_gen.random
|
| 153 |
-
else:
|
| 154 |
-
filename = self.output_file
|
| 155 |
-
self.add_metadata(output_file=filename)
|
| 156 |
-
|
| 157 |
-
with open(filename, 'w') as f:
|
| 158 |
-
# Write the column names
|
| 159 |
-
cols = None
|
| 160 |
-
if "columns" in kwargs:
|
| 161 |
-
cols = kwargs["columns"]
|
| 162 |
-
elif "columns" in self.metadata:
|
| 163 |
-
cols = self.metadata["columns" ]
|
| 164 |
-
if cols is not None:
|
| 165 |
-
f.write(",".join(cols) + "\n")
|
| 166 |
-
|
| 167 |
-
# Convert NumPy array to list if needed
|
| 168 |
-
# if isinstance(self.data, np.ndarray):
|
| 169 |
-
# self.data = self.data.tolist()
|
| 170 |
-
|
| 171 |
-
# Write data
|
| 172 |
-
for row in self.data:
|
| 173 |
-
# Handle multiple columns
|
| 174 |
-
if isinstance(row, (list, tuple, np.ndarray)):
|
| 175 |
-
line = ",".join(map(str, row))
|
| 176 |
-
# Handle single-column case
|
| 177 |
-
else:
|
| 178 |
-
line = str(row)
|
| 179 |
-
f.write(line + "\n")
|
| 180 |
-
|
| 181 |
-
# Write the kwargs as metadata
|
| 182 |
-
self.write_metadata(filename)
|
| 183 |
-
|
| 184 |
-
return filename
|
| 185 |
-
|
| 186 |
-
def read_data_file(self,filename=None):
|
| 187 |
-
if filename is None:
|
| 188 |
-
raise ValueError("Filename is missing")
|
| 189 |
-
|
| 190 |
-
# Read file and separate comments from data
|
| 191 |
-
comments = []
|
| 192 |
-
data_lines = []
|
| 193 |
-
|
| 194 |
-
with open(filename, "r") as f:
|
| 195 |
-
for line in f:
|
| 196 |
-
if line.startswith("#"):
|
| 197 |
-
comments.append(line.strip()) # Store comment lines
|
| 198 |
-
else:
|
| 199 |
-
data_lines.append(line.strip()) # Store data lines
|
| 200 |
-
|
| 201 |
-
# Extract header and data
|
| 202 |
-
header = data_lines[0] # First non-comment line is the header
|
| 203 |
-
data_lines = "\n".join(data_lines[0:]) # Join remaining lines as CSV data
|
| 204 |
-
|
| 205 |
-
# Convert CSV data to NumPy array
|
| 206 |
-
from io import StringIO
|
| 207 |
-
from numpy.lib.recfunctions import structured_to_unstructured
|
| 208 |
-
|
| 209 |
-
data = np.genfromtxt(
|
| 210 |
-
StringIO(data_lines), delimiter=',',
|
| 211 |
-
comments='#', names=True,
|
| 212 |
-
skip_header=0, dtype=None)
|
| 213 |
-
|
| 214 |
-
data_array = structured_to_unstructured(data)
|
| 215 |
-
|
| 216 |
-
metadata = None
|
| 217 |
-
if len(comments) > 0:
|
| 218 |
-
metadata = OrderedDict({})
|
| 219 |
-
for l in comments:
|
| 220 |
-
if ":" in l:
|
| 221 |
-
key, value = l.split(':')
|
| 222 |
-
elif "=" in l:
|
| 223 |
-
key, value = l.split('=')
|
| 224 |
-
else:
|
| 225 |
-
raise Exception("Unknown separator")
|
| 226 |
-
key = key.replace("#","").strip()
|
| 227 |
-
metadata[key.strip()] = value.strip()
|
| 228 |
-
|
| 229 |
-
# Output results
|
| 230 |
-
# print("Comments:")
|
| 231 |
-
# print("\n".join(comments))
|
| 232 |
-
# print("\nExtracted Data:")
|
| 233 |
-
# print(data_array)
|
| 234 |
-
return data_array, header, metadata
|
| 235 |
-
|
| 236 |
-
def process_file(self, filename=None):
|
| 237 |
-
self.read_data(filename)
|
| 238 |
-
result = self.process_data()
|
| 239 |
-
return result
|
| 240 |
-
|
| 241 |
-
def _valid_ID(self,ID):
|
| 242 |
-
if ID in ["23745411"]:
|
| 243 |
-
return True
|
| 244 |
-
return False
|
| 245 |
-
|
| 246 |
-
def _round_values(self, values, precision=None):
|
| 247 |
-
if precision is None:
|
| 248 |
-
precision = self.precision
|
| 249 |
-
rounded_values = [round(v, precision) for v in values]
|
| 250 |
-
values = np.array(rounded_values, dtype=float)
|
| 251 |
-
return values
|
| 252 |
-
|
| 253 |
-
def _generate_uniform_random(self, lower, upper, n):
|
| 254 |
-
return self._round_values(np.random.uniform(lower, upper, n))
|
| 255 |
-
|
| 256 |
-
def _generate_normal_random(self,n,prm):
|
| 257 |
-
list_of_1d_arrays = []
|
| 258 |
-
for p in prm:
|
| 259 |
-
values = np.random.normal(p[0], p[1], size=n)
|
| 260 |
-
list_of_1d_arrays.append(self._round_values(values))
|
| 261 |
-
|
| 262 |
-
if len(prm) == 1:
|
| 263 |
-
return np.array(self._round_values(values))
|
| 264 |
-
else:
|
| 265 |
-
return np.column_stack( [*list_of_1d_arrays] )
|
| 266 |
-
|
| 267 |
-
def _generate_noise(self,n,noise_level=None,ntype="normal"):
|
| 268 |
-
if noise_level == None:
|
| 269 |
-
raise ValueError("Missing noise level")
|
| 270 |
-
if noise_level <= 0:
|
| 271 |
-
return np.zeros(n)
|
| 272 |
-
if ntype == "normal":
|
| 273 |
-
return np.random.normal(0, noise_level, size=n)
|
| 274 |
-
|
| 275 |
-
def _generate_data_from_function(self, func, params, nvalues, xrange):
|
| 276 |
-
x = np.sort(self._generate_uniform_random(nvalues,*xrange))
|
| 277 |
-
y = func(x, *params) + self._generate_noise(nvalues)
|
| 278 |
-
y = self._round_values(y)
|
| 279 |
-
return np.column_stack((x,y))
|
| 280 |
-
|
| 281 |
-
import numpy as np
|
| 282 |
-
from typing import Callable, Dict, Optional, Union, Tuple
|
| 283 |
-
|
| 284 |
-
def generate_data_from_function(
|
| 285 |
-
self,
|
| 286 |
-
function: Callable,
|
| 287 |
-
params: Dict,
|
| 288 |
-
nvalues: int,
|
| 289 |
-
xrange: Optional[Tuple[float, float]] = None,
|
| 290 |
-
xspacing: str = 'random',
|
| 291 |
-
noise_level: Optional[float] = None,
|
| 292 |
-
background: Optional[float] = None,
|
| 293 |
-
weights: Optional[bool] = None,
|
| 294 |
-
positive: bool = False
|
| 295 |
-
) -> np.ndarray:
|
| 296 |
-
"""
|
| 297 |
-
Generate synthetic data points from a given function with optional noise and background.
|
| 298 |
-
|
| 299 |
-
Parameters
|
| 300 |
-
----------
|
| 301 |
-
function : callable
|
| 302 |
-
The model function to generate data from. Should accept x values and **kwargs.
|
| 303 |
-
params : dict
|
| 304 |
-
Parameters to pass to the function as keyword arguments.
|
| 305 |
-
nvalues : int
|
| 306 |
-
Number of data points to generate.
|
| 307 |
-
xrange : tuple of float, optional
|
| 308 |
-
Range of x values (min, max). Required if generating data points.
|
| 309 |
-
xspacing : str, default='random'
|
| 310 |
-
Method to space x values. Options:
|
| 311 |
-
- 'linear': Evenly spaced points
|
| 312 |
-
- 'random': Uniformly distributed random points
|
| 313 |
-
noise_level : float, optional
|
| 314 |
-
Standard deviation of Gaussian noise to add to y values.
|
| 315 |
-
background : float, optional
|
| 316 |
-
Constant background level to add to all y values.
|
| 317 |
-
weights : bool, optional
|
| 318 |
-
If True, include weights in output (NOT IMPLEMENTED).
|
| 319 |
-
positive : bool, default=False
|
| 320 |
-
If True, take absolute value of final y values.
|
| 321 |
-
|
| 322 |
-
Returns
|
| 323 |
-
-------
|
| 324 |
-
np.ndarray
|
| 325 |
-
2D array with shape (nvalues, 2) containing (x, y) pairs.
|
| 326 |
-
|
| 327 |
-
Raises
|
| 328 |
-
------
|
| 329 |
-
ValueError
|
| 330 |
-
If xrange is None or invalid xspacing type is provided.
|
| 331 |
-
"""
|
| 332 |
-
# Validate inputs
|
| 333 |
-
if xrange is None:
|
| 334 |
-
raise ValueError("xrange must be provided as (min, max) tuple")
|
| 335 |
-
|
| 336 |
-
if not isinstance(nvalues, int) or nvalues <= 0:
|
| 337 |
-
raise ValueError("nvalues must be a positive integer")
|
| 338 |
-
|
| 339 |
-
# Generate x values
|
| 340 |
-
if xspacing == "linear":
|
| 341 |
-
x = np.linspace(*xrange, nvalues)
|
| 342 |
-
elif xspacing == "random":
|
| 343 |
-
x = np.sort(self._generate_uniform_random(*xrange, nvalues))
|
| 344 |
-
else:
|
| 345 |
-
raise ValueError(f"xspacing must be 'linear' or 'random', got '{xspacing}'")
|
| 346 |
-
|
| 347 |
-
# Generate base y values from function
|
| 348 |
-
y = function(x, **params)
|
| 349 |
-
|
| 350 |
-
# Add optional modifications
|
| 351 |
-
if background is not None:
|
| 352 |
-
y += background
|
| 353 |
-
|
| 354 |
-
if noise_level is not None:
|
| 355 |
-
y += self._generate_noise(nvalues,noise_level)
|
| 356 |
-
|
| 357 |
-
if positive:
|
| 358 |
-
y = np.abs(y)
|
| 359 |
-
|
| 360 |
-
# Note: weights parameter is currently unused
|
| 361 |
-
if weights is not None:
|
| 362 |
-
# TODO: Implement weights handling
|
| 363 |
-
pass
|
| 364 |
-
|
| 365 |
-
y = self._round_values(y)
|
| 366 |
-
|
| 367 |
-
return np.column_stack((x, y))
|
| 368 |
-
|
| 369 |
-
def create_data_file(self):
|
| 370 |
-
data = self.create_data()
|
| 371 |
-
self.write_data_to_file(
|
| 372 |
-
self.metadata['output_file'],
|
| 373 |
-
data, **self.metadata )
|
| 374 |
-
return self.metadata['output_file']
|
| 375 |
-
|
| 376 |
-
def get_data(self):
|
| 377 |
-
return self.data
|
| 378 |
-
def get_metadata(self):
|
| 379 |
-
return self.metadata
|
| 380 |
-
|
| 381 |
-
@abstractmethod
|
| 382 |
-
def setup_lab(self,**kwargs):
|
| 383 |
-
pass
|
| 384 |
-
|
| 385 |
-
@abstractmethod
|
| 386 |
-
def create_data(self):
|
| 387 |
-
pass
|
| 388 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/lib/pycek_public/crystal_violet.py
DELETED
|
@@ -1,75 +0,0 @@
|
|
| 1 |
-
import pycek_public as cek
|
| 2 |
-
import numpy as np
|
| 3 |
-
|
| 4 |
-
class crystal_violet(cek.cek_labs):
|
| 5 |
-
def setup_lab(self):
|
| 6 |
-
"""
|
| 7 |
-
Define base information for the lab.
|
| 8 |
-
They can be overwrite by the user using the kwargs in the constructor or
|
| 9 |
-
by calling the set_parameters method.
|
| 10 |
-
"""
|
| 11 |
-
self.add_metadata(
|
| 12 |
-
laboratory = 'Crystal Violet Lab',
|
| 13 |
-
columns = ["Time (s)","Absorbance"]
|
| 14 |
-
)
|
| 15 |
-
|
| 16 |
-
self.expt_time = 1000
|
| 17 |
-
self.number_of_values = 501
|
| 18 |
-
self.noise_level = 0.1
|
| 19 |
-
self.precision = 4
|
| 20 |
-
|
| 21 |
-
self.activation_energy = 63e3 # J/mol
|
| 22 |
-
self.prefactor = 5.9e9 # 1/M/s
|
| 23 |
-
|
| 24 |
-
# Order wrt CV and OH
|
| 25 |
-
self.alpha = 1.0
|
| 26 |
-
self.beta = 0.75
|
| 27 |
-
self.conc_to_abs = 160e3 # Absorbivity of CV at 590 nm (L/mol/cm)
|
| 28 |
-
self.stock_solutions = {"cv" : 2.5e-5, "oh" : 0.5} # mol/L
|
| 29 |
-
|
| 30 |
-
self.volumes = {"cv" : 10, "oh" : 10, "h2o" : 10.0} # mL
|
| 31 |
-
|
| 32 |
-
def create_data(self):
|
| 33 |
-
"""
|
| 34 |
-
Generate the data
|
| 35 |
-
"""
|
| 36 |
-
self.set_parameters(
|
| 37 |
-
sample = self.sample,
|
| 38 |
-
number_of_values = self.number_of_values,
|
| 39 |
-
)
|
| 40 |
-
|
| 41 |
-
self.add_metadata(**{
|
| 42 |
-
"Temperature (C)" : self.temperature-273.15,
|
| 43 |
-
"Volume of CV (mL)" : self.volumes['cv'],
|
| 44 |
-
"Volume of OH (mL)" : self.volumes['oh'],
|
| 45 |
-
"Volume of H2O (mL)": self.volumes['h2o'],
|
| 46 |
-
})
|
| 47 |
-
|
| 48 |
-
vtot = np.sum( [ x + np.random.normal(0,self.noise_level,1) for x in self.volumes.values() ] )
|
| 49 |
-
initial_concentration_cv = \
|
| 50 |
-
self.stock_solutions["cv"] * self.volumes["cv"] / vtot
|
| 51 |
-
|
| 52 |
-
concetration_oh = \
|
| 53 |
-
self.stock_solutions["oh"] * self.volumes["oh"] / vtot
|
| 54 |
-
|
| 55 |
-
rate_constant = self.prefactor*np.exp(-self.activation_energy/(self.R*(self.temperature)))
|
| 56 |
-
pseudo_rate_constant = rate_constant * np.power(concetration_oh,self.beta)
|
| 57 |
-
|
| 58 |
-
params = {
|
| 59 |
-
"A" : initial_concentration_cv* self.conc_to_abs,
|
| 60 |
-
"k" : pseudo_rate_constant
|
| 61 |
-
}
|
| 62 |
-
|
| 63 |
-
self.data = self.generate_data_from_function(
|
| 64 |
-
lambda x,A,k: A * np.exp(-k * x) ,
|
| 65 |
-
params,
|
| 66 |
-
self.number_of_values,
|
| 67 |
-
xrange = [0, self.expt_time],
|
| 68 |
-
xspacing = 'linear',
|
| 69 |
-
noise_level = self.noise_level,
|
| 70 |
-
positive = True,
|
| 71 |
-
background = 0.1
|
| 72 |
-
)
|
| 73 |
-
|
| 74 |
-
return
|
| 75 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/lib/pycek_public/generate_random_filenames.py
DELETED
|
@@ -1,166 +0,0 @@
|
|
| 1 |
-
import os
|
| 2 |
-
from glob import glob
|
| 3 |
-
import secrets
|
| 4 |
-
import string
|
| 5 |
-
|
| 6 |
-
class TempFilenameGenerator:
|
| 7 |
-
"""
|
| 8 |
-
Generates temporary filenames with .pdb extension and increasing indices.
|
| 9 |
-
Supports both sequential (tmp.{index}.pdb) and random (tmp.{random}.pdb) formats.
|
| 10 |
-
"""
|
| 11 |
-
def __init__(self, directory=".", root="data", ext="csv", random_length=12):
|
| 12 |
-
"""
|
| 13 |
-
Initialize the generator with a target directory.
|
| 14 |
-
|
| 15 |
-
Args:
|
| 16 |
-
directory (str): Directory for the filenames (default: current directory)
|
| 17 |
-
root (str): Root name for the temporary files (default: tmp)
|
| 18 |
-
ext (str): File extension (default: pdb)
|
| 19 |
-
random_length (int): Length of random string in random filenames (default: 12)
|
| 20 |
-
|
| 21 |
-
Example:
|
| 22 |
-
generator = TempFilenameGenerator()
|
| 23 |
-
|
| 24 |
-
# Get sequential filename (e.g., "tmp.0.pdb")
|
| 25 |
-
sequential_file = generator.next
|
| 26 |
-
|
| 27 |
-
# Get random filename (e.g., "tmp.j4k3h2l5m9n8.pdb")
|
| 28 |
-
random_file = generator.random
|
| 29 |
-
"""
|
| 30 |
-
self.directory = directory
|
| 31 |
-
self.root = root
|
| 32 |
-
self.ext = ext
|
| 33 |
-
self.random_length = random_length
|
| 34 |
-
self._current_index = self._find_max_index()
|
| 35 |
-
self._current_filename = None
|
| 36 |
-
|
| 37 |
-
def _find_max_index(self):
|
| 38 |
-
"""Find the highest existing index in the directory."""
|
| 39 |
-
pattern = os.path.join(self.directory, f"{self.root}.*.{self.ext}")
|
| 40 |
-
existing_files = glob(pattern)
|
| 41 |
-
|
| 42 |
-
if not existing_files:
|
| 43 |
-
return -1
|
| 44 |
-
|
| 45 |
-
indices = []
|
| 46 |
-
for filename in existing_files:
|
| 47 |
-
try:
|
| 48 |
-
# Extract index from tmp.{index}.pdb
|
| 49 |
-
index = int(os.path.basename(filename).split('.')[-2])
|
| 50 |
-
indices.append(index)
|
| 51 |
-
except (ValueError, IndexError):
|
| 52 |
-
continue
|
| 53 |
-
|
| 54 |
-
return max(indices) if indices else -1
|
| 55 |
-
|
| 56 |
-
def _generate_random_string(self):
|
| 57 |
-
"""Generate a cryptographically secure random string."""
|
| 58 |
-
alphabet = string.ascii_letters + string.digits
|
| 59 |
-
return ''.join(secrets.choice(alphabet) for _ in range(self.random_length))
|
| 60 |
-
|
| 61 |
-
def delete_files(self):
|
| 62 |
-
"""Delete all temporary files in the directory."""
|
| 63 |
-
pattern = os.path.join(self.directory, f"{self.root}.*.{self.ext}")
|
| 64 |
-
for filename in glob(pattern):
|
| 65 |
-
os.remove(filename)
|
| 66 |
-
self._current_index = -1
|
| 67 |
-
|
| 68 |
-
def copy_last(self, dest):
|
| 69 |
-
"""Copy the last generated file to a destination."""
|
| 70 |
-
if self._current_filename is None:
|
| 71 |
-
raise ValueError("No files have been generated yet")
|
| 72 |
-
os.system(f"cp {self._current_filename} {dest}")
|
| 73 |
-
|
| 74 |
-
@property
|
| 75 |
-
def next(self):
|
| 76 |
-
"""Generate the next filename in sequence."""
|
| 77 |
-
self._current_index += 1
|
| 78 |
-
filename = f"{self.root}.{self._current_index}.{self.ext}"
|
| 79 |
-
self._current_filename = os.path.join(self.directory, filename)
|
| 80 |
-
return self._current_filename
|
| 81 |
-
|
| 82 |
-
@property
|
| 83 |
-
def random(self):
|
| 84 |
-
"""Generate a random filename."""
|
| 85 |
-
random_string = self._generate_random_string()
|
| 86 |
-
filename = f"{self.root}.{random_string}.{self.ext}"
|
| 87 |
-
self._current_filename = os.path.join(self.directory, filename)
|
| 88 |
-
return self._current_filename
|
| 89 |
-
|
| 90 |
-
@property
|
| 91 |
-
def current_index(self):
|
| 92 |
-
"""Get the current index value."""
|
| 93 |
-
return self._current_index
|
| 94 |
-
|
| 95 |
-
@property
|
| 96 |
-
def current(self):
|
| 97 |
-
"""Get the current filename."""
|
| 98 |
-
return self._current_filename
|
| 99 |
-
|
| 100 |
-
# import random
|
| 101 |
-
# import string
|
| 102 |
-
# import os
|
| 103 |
-
# from typing import Optional
|
| 104 |
-
|
| 105 |
-
# def generate_random_filename(
|
| 106 |
-
# extension: Optional[str] = 'csv',
|
| 107 |
-
# length: Optional[int] = 10,
|
| 108 |
-
# prefix: Optional[str] = 'data_',
|
| 109 |
-
# directory: Optional[str] = '',
|
| 110 |
-
# existing_check: Optional[bool] = True
|
| 111 |
-
# ) -> str:
|
| 112 |
-
# """
|
| 113 |
-
# Generate a random filename with optional parameters.
|
| 114 |
-
|
| 115 |
-
# Args:
|
| 116 |
-
# extension (str): File extension to append (e.g., '.txt', '.pdf')
|
| 117 |
-
# length (int): Length of the random string (default: 10)
|
| 118 |
-
# prefix (str): Prefix to add before the random string
|
| 119 |
-
# directory (str): Directory path where the file will be created
|
| 120 |
-
# existing_check (bool): Whether to check if filename already exists
|
| 121 |
-
|
| 122 |
-
# Returns:
|
| 123 |
-
# str: Generated filename
|
| 124 |
-
|
| 125 |
-
# Raises:
|
| 126 |
-
# ValueError: If length is less than 1
|
| 127 |
-
# ValueError: If unable to generate unique filename after 100 attempts
|
| 128 |
-
# """
|
| 129 |
-
# if length < 1:
|
| 130 |
-
# raise ValueError("Length must be at least 1")
|
| 131 |
-
|
| 132 |
-
# # Clean up the extension
|
| 133 |
-
# if extension and not extension.startswith('.'):
|
| 134 |
-
# extension = '.' + extension
|
| 135 |
-
|
| 136 |
-
# # Clean up the directory path
|
| 137 |
-
# if directory:
|
| 138 |
-
# directory = os.path.abspath(directory)
|
| 139 |
-
# if not os.path.exists(directory):
|
| 140 |
-
# os.makedirs(directory)
|
| 141 |
-
|
| 142 |
-
# attempts = 0
|
| 143 |
-
# max_attempts = 100
|
| 144 |
-
|
| 145 |
-
# while True:
|
| 146 |
-
# # Generate random string using ASCII letters and digits
|
| 147 |
-
# random_string = ''.join(
|
| 148 |
-
# random.choices(string.ascii_letters + string.digits, k=length)
|
| 149 |
-
# )
|
| 150 |
-
|
| 151 |
-
# # Combine all parts of the filename
|
| 152 |
-
# filename = f"{prefix}{random_string}{extension}"
|
| 153 |
-
|
| 154 |
-
# # Add directory path if specified
|
| 155 |
-
# if directory:
|
| 156 |
-
# filename = os.path.join(directory, filename)
|
| 157 |
-
|
| 158 |
-
# # Check if file exists (if requested)
|
| 159 |
-
# if not existing_check or not os.path.exists(filename):
|
| 160 |
-
# return filename
|
| 161 |
-
|
| 162 |
-
# attempts += 1
|
| 163 |
-
# if attempts >= max_attempts:
|
| 164 |
-
# raise ValueError(
|
| 165 |
-
# f"Unable to generate unique filename after {max_attempts} attempts"
|
| 166 |
-
# )
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/lib/pycek_public/logger.py
DELETED
|
@@ -1,112 +0,0 @@
|
|
| 1 |
-
import sys
|
| 2 |
-
import logging
|
| 3 |
-
import colorama
|
| 4 |
-
from colorama import Fore, Style
|
| 5 |
-
|
| 6 |
-
# Initialize colorama to work on Windows too
|
| 7 |
-
colorama.init()
|
| 8 |
-
|
| 9 |
-
# Define custom VERBOSE level (between DEBUG-10 and INFO-20)
|
| 10 |
-
VERBOSE = 15
|
| 11 |
-
logging.addLevelName(VERBOSE, 'VERBOSE')
|
| 12 |
-
RESULT = 25
|
| 13 |
-
logging.addLevelName(RESULT, 'RESULT')
|
| 14 |
-
|
| 15 |
-
class ColoredFormatter(logging.Formatter):
|
| 16 |
-
"""Custom formatter that adds colors to log messages based on level with fixed width"""
|
| 17 |
-
COLORS = {
|
| 18 |
-
'DEBUG': Fore.CYAN + Style.BRIGHT,
|
| 19 |
-
'VERBOSE': Fore.BLUE + Style.BRIGHT,
|
| 20 |
-
'INFO': Fore.GREEN + Style.BRIGHT,
|
| 21 |
-
'RESULT' : Fore.RESET + Style.BRIGHT,
|
| 22 |
-
'WARNING': Fore.YELLOW + Style.BRIGHT,
|
| 23 |
-
'ERROR': Fore.RED + Style.BRIGHT,
|
| 24 |
-
'CRITICAL': Fore.MAGENTA + Style.BRIGHT
|
| 25 |
-
}
|
| 26 |
-
LEVEL_NAME_WIDTH = 8
|
| 27 |
-
|
| 28 |
-
def format(self, record):
|
| 29 |
-
# Save original levelname and message
|
| 30 |
-
orig_levelname = record.levelname
|
| 31 |
-
orig_msg = record.msg
|
| 32 |
-
|
| 33 |
-
# Determine the color for the level
|
| 34 |
-
color_code = self.COLORS.get(orig_levelname, '')
|
| 35 |
-
|
| 36 |
-
# Calculate padding for levelname
|
| 37 |
-
padding = ' ' * (self.LEVEL_NAME_WIDTH - len(orig_levelname))
|
| 38 |
-
|
| 39 |
-
# Apply color to levelname with padding
|
| 40 |
-
record.levelname = f"{color_code}{orig_levelname}{padding}{Style.RESET_ALL}"
|
| 41 |
-
|
| 42 |
-
# Apply color to the message as well
|
| 43 |
-
record.msg = f"{color_code}{orig_msg}{Style.RESET_ALL}"
|
| 44 |
-
|
| 45 |
-
# Format the message
|
| 46 |
-
result = super().format(record)
|
| 47 |
-
|
| 48 |
-
# Restore original values
|
| 49 |
-
record.levelname = orig_levelname
|
| 50 |
-
record.msg = orig_msg
|
| 51 |
-
return result
|
| 52 |
-
|
| 53 |
-
class ColoredLogger(logging.Logger):
|
| 54 |
-
"""Custom logger class with verbose method"""
|
| 55 |
-
|
| 56 |
-
def verbose(self, msg, *args, **kwargs):
|
| 57 |
-
"""Log at custom VERBOSE level"""
|
| 58 |
-
if self.isEnabledFor(VERBOSE):
|
| 59 |
-
self._log(VERBOSE, msg, args, **kwargs)
|
| 60 |
-
|
| 61 |
-
def result(self, msg, *args, **kwargs):
|
| 62 |
-
"""Log at custom RESULT level"""
|
| 63 |
-
if self.isEnabledFor(RESULT):
|
| 64 |
-
self._log(RESULT, msg, args, **kwargs)
|
| 65 |
-
|
| 66 |
-
# logging.Logger.result = custom_logging
|
| 67 |
-
|
| 68 |
-
def setup_logger(name='colored_logger', level="INFO"):
|
| 69 |
-
"""Set up and return a colored logger instance"""
|
| 70 |
-
|
| 71 |
-
# Register our custom logger class
|
| 72 |
-
logging.setLoggerClass(ColoredLogger)
|
| 73 |
-
|
| 74 |
-
# Create logger
|
| 75 |
-
logger = logging.getLogger(name)
|
| 76 |
-
|
| 77 |
-
# Set logger level
|
| 78 |
-
try:
|
| 79 |
-
logger.setLevel(getattr(logging, level.upper()))
|
| 80 |
-
except AttributeError:
|
| 81 |
-
logger.setLevel(logging.INFO) # Default to INFO if invalid level
|
| 82 |
-
|
| 83 |
-
# Create console handler
|
| 84 |
-
console_handler = logging.StreamHandler(sys.stdout)
|
| 85 |
-
console_handler.setLevel(level)
|
| 86 |
-
|
| 87 |
-
# Create formatter
|
| 88 |
-
formatter = ColoredFormatter(
|
| 89 |
-
# fmt='%(asctime)s - %(levelname)s - %(message)s',
|
| 90 |
-
# datefmt='%Y-%m-%d %H:%M:%S'
|
| 91 |
-
fmt='%(levelname)8s - %(message)s'
|
| 92 |
-
)
|
| 93 |
-
|
| 94 |
-
# Add formatter to handler
|
| 95 |
-
console_handler.setFormatter(formatter)
|
| 96 |
-
|
| 97 |
-
# Add handler to logger
|
| 98 |
-
logger.addHandler(console_handler)
|
| 99 |
-
|
| 100 |
-
return logger
|
| 101 |
-
|
| 102 |
-
# Example usage
|
| 103 |
-
if __name__ == '__main__':
|
| 104 |
-
logger = setup_logger()
|
| 105 |
-
|
| 106 |
-
# Test all log levels including new VERBOSE level
|
| 107 |
-
logger.debug("This is a debug message")
|
| 108 |
-
logger.verbose("This is a verbose message") # New verbose level
|
| 109 |
-
logger.info("This is an info message")
|
| 110 |
-
logger.warning("This is a warning message")
|
| 111 |
-
logger.error("This is an error message")
|
| 112 |
-
logger.critical("This is a critical message")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/lib/pycek_public/statistics_lab.py
DELETED
|
@@ -1,125 +0,0 @@
|
|
| 1 |
-
import pycek_public as cek
|
| 2 |
-
import numpy as np
|
| 3 |
-
|
| 4 |
-
class stats_lab(cek.cek_labs):
|
| 5 |
-
def setup_lab(self):
|
| 6 |
-
"""
|
| 7 |
-
Define base information for the lab
|
| 8 |
-
"""
|
| 9 |
-
self.add_metadata(
|
| 10 |
-
laboratory = 'Basic Statistics Lab',
|
| 11 |
-
columns = ["X","Y"]
|
| 12 |
-
)
|
| 13 |
-
|
| 14 |
-
self.available_samples = [
|
| 15 |
-
'Averages',
|
| 16 |
-
'Propagation of uncertainty',
|
| 17 |
-
'Comparison of averages',
|
| 18 |
-
'Linear fit',
|
| 19 |
-
'Non linear fit',
|
| 20 |
-
'Detection of outliers',
|
| 21 |
-
]
|
| 22 |
-
|
| 23 |
-
self.sample_parameters['Averages'] = {
|
| 24 |
-
"gen_values" : [
|
| 25 |
-
(1.0, 0.1),
|
| 26 |
-
(12., 2.0)
|
| 27 |
-
],
|
| 28 |
-
'exp_values' : (1.0,9.0),
|
| 29 |
-
}
|
| 30 |
-
|
| 31 |
-
self.sample_parameters['Propagation of uncertainty'] = {
|
| 32 |
-
"gen_values" : [
|
| 33 |
-
(15.0, 1.0),
|
| 34 |
-
(133., 2.0)
|
| 35 |
-
],
|
| 36 |
-
}
|
| 37 |
-
|
| 38 |
-
self.sample_parameters['Comparison of averages'] = {
|
| 39 |
-
"gen_values" : [
|
| 40 |
-
(15.0, 1.0),
|
| 41 |
-
(13.2, 2.0)
|
| 42 |
-
],
|
| 43 |
-
}
|
| 44 |
-
|
| 45 |
-
self.sample_parameters['Linear fit'] = {
|
| 46 |
-
"function" : lambda x,m,q: m*x + q,
|
| 47 |
-
"gen_values" : {'m':12.3 , 'q':1.0},
|
| 48 |
-
"xrange" : [0.0 , 10.0]
|
| 49 |
-
}
|
| 50 |
-
|
| 51 |
-
self.sample_parameters['Non linear fit'] = {
|
| 52 |
-
"nval" : 10,
|
| 53 |
-
"function" : lambda x,E0,K0,Kp,V0: E0 + K0 * x / Kp * ( (V0/x)**Kp / (Kp-1)+1) - K0*V0/(Kp-1),
|
| 54 |
-
"gen_values" : {"E0":-634.2, "K0":12.43, "Kp":4.28, "V0":99.11},
|
| 55 |
-
"xrange" : [50 , 140]
|
| 56 |
-
}
|
| 57 |
-
|
| 58 |
-
self.sample_parameters['Detection of outliers'] = {
|
| 59 |
-
"function" : lambda x,m,q: m*x + q,
|
| 60 |
-
"gen_values" : {'m':2.3 , 'q':0.1},
|
| 61 |
-
"xrange" : [10.0 , 20.0],
|
| 62 |
-
"shift" : 2,
|
| 63 |
-
}
|
| 64 |
-
|
| 65 |
-
def create_data(self):
|
| 66 |
-
"""
|
| 67 |
-
Generate the data
|
| 68 |
-
"""
|
| 69 |
-
if self.sample is None:
|
| 70 |
-
raise Exception("Sample not defined")
|
| 71 |
-
|
| 72 |
-
prm = self.sample_parameters[ self.sample ]
|
| 73 |
-
|
| 74 |
-
if self.user_specified_file is None:
|
| 75 |
-
output_file = self.filename_gen.random
|
| 76 |
-
else:
|
| 77 |
-
output_file = self.output_file
|
| 78 |
-
|
| 79 |
-
self.set_parameters(
|
| 80 |
-
number_of_values = self.number_of_values,
|
| 81 |
-
output_file = output_file,
|
| 82 |
-
)
|
| 83 |
-
|
| 84 |
-
if "noise" in prm:
|
| 85 |
-
self.set_parameters( noise_level = prm["noise"] )
|
| 86 |
-
|
| 87 |
-
self.add_metadata(
|
| 88 |
-
number_of_values = self.number_of_values,
|
| 89 |
-
sample = self.sample,
|
| 90 |
-
)
|
| 91 |
-
|
| 92 |
-
if self.sample in ["Averages", 'Propagation of uncertainty', 'Comparison of averages']:
|
| 93 |
-
data = self._generate_normal_random(self.number_of_values, prm['gen_values'])
|
| 94 |
-
|
| 95 |
-
elif self.sample in ["Linear fit"]:
|
| 96 |
-
data = self.generate_data_from_function(
|
| 97 |
-
prm["function"],
|
| 98 |
-
prm['gen_values'],
|
| 99 |
-
self.number_of_values,
|
| 100 |
-
prm['xrange'],
|
| 101 |
-
noise_level = self.noise_level,
|
| 102 |
-
)
|
| 103 |
-
|
| 104 |
-
elif self.sample in ["Non linear fit"]:
|
| 105 |
-
data = self.generate_data_from_function(
|
| 106 |
-
prm["function"],
|
| 107 |
-
prm['gen_values'],
|
| 108 |
-
self.number_of_values,
|
| 109 |
-
prm['xrange'],
|
| 110 |
-
noise_level = self.noise_level,
|
| 111 |
-
)
|
| 112 |
-
|
| 113 |
-
elif self.sample in ["Detection of outliers"]:
|
| 114 |
-
data = self.generate_data_from_function(
|
| 115 |
-
prm["function"],
|
| 116 |
-
prm['gen_values'],
|
| 117 |
-
self.number_of_values,
|
| 118 |
-
prm['xrange'],
|
| 119 |
-
noise_level = self.noise_level,
|
| 120 |
-
)
|
| 121 |
-
i = np.random.randint(self.number_of_values)
|
| 122 |
-
data[i,1] += prm['shift']
|
| 123 |
-
|
| 124 |
-
return data
|
| 125 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
build/lib/pycek_public/surface_adsorption.py
DELETED
|
@@ -1,89 +0,0 @@
|
|
| 1 |
-
import pycek_public as cek
|
| 2 |
-
import numpy as np
|
| 3 |
-
|
| 4 |
-
class surface_adsorption(cek.cek_labs):
|
| 5 |
-
def setup_lab(self):
|
| 6 |
-
"""
|
| 7 |
-
Define base information for the lab.
|
| 8 |
-
They can be overwrite by the user using the kwargs in the constructor or
|
| 9 |
-
by calling the set_parameters method.
|
| 10 |
-
"""
|
| 11 |
-
self.add_metadata(
|
| 12 |
-
laboratory = 'Surface Adsorption Lab',
|
| 13 |
-
columns = ["Dye added (mg)", "Dye in solution (mol/L)"]
|
| 14 |
-
)
|
| 15 |
-
|
| 16 |
-
self.volume = 1 # L
|
| 17 |
-
self.minDye = 500 # mg
|
| 18 |
-
self.maxDye = 10000 # mg
|
| 19 |
-
|
| 20 |
-
self.sample_parameters = {
|
| 21 |
-
"dH" : -19.51e3, # J/mol
|
| 22 |
-
"dS" : -10, # J/mol/K
|
| 23 |
-
"Q" : 0.0001, # monolayer coverage (mol/m^2)
|
| 24 |
-
"molarMass": 584.910641, # g/mol
|
| 25 |
-
}
|
| 26 |
-
|
| 27 |
-
self.number_of_values = 100
|
| 28 |
-
self.noise_level = 1e-6
|
| 29 |
-
self.precision = 10
|
| 30 |
-
|
| 31 |
-
def create_data(self):
|
| 32 |
-
"""
|
| 33 |
-
Generate the data
|
| 34 |
-
"""
|
| 35 |
-
if self.user_specified_file is None:
|
| 36 |
-
output_file = self.filename_gen.random
|
| 37 |
-
else:
|
| 38 |
-
output_file = self.output_file
|
| 39 |
-
|
| 40 |
-
self.set_parameters(
|
| 41 |
-
sample = self.sample,
|
| 42 |
-
number_of_values = self.number_of_values,
|
| 43 |
-
output_file = output_file,
|
| 44 |
-
)
|
| 45 |
-
|
| 46 |
-
self.add_metadata(**{
|
| 47 |
-
"Temperature (K)" : self.temperature,
|
| 48 |
-
"Volume (L)" : self.volume,
|
| 49 |
-
"Molar mass (g/mol)" : self.sample_parameters["molarMass"],
|
| 50 |
-
"MinDye (mg)" : self.minDye,
|
| 51 |
-
"MaxDye (mg)" : self.maxDye,
|
| 52 |
-
'Number of values' : self.number_of_values,
|
| 53 |
-
})
|
| 54 |
-
|
| 55 |
-
# Langmuir isotherm equilibrium constant
|
| 56 |
-
# Convert to kJ/mol
|
| 57 |
-
lnK = (-self.sample_parameters["dH"] / (self.temperature) + self.sample_parameters["dS"]) / self.R
|
| 58 |
-
K = np.exp(lnK) # in L/mol
|
| 59 |
-
|
| 60 |
-
conversion_factor = 1000 * self.sample_parameters["molarMass"] * self.volume
|
| 61 |
-
conc_range = np.array([self.minDye, self.maxDye]) / conversion_factor
|
| 62 |
-
|
| 63 |
-
data_array = self.generate_data_from_function(
|
| 64 |
-
lambda x,K,Q: ((x*K - K*Q - 1) + np.sqrt((x*K - K*Q - 1)**2 + 4*x*K) ) / (2*K) ,
|
| 65 |
-
{"K":K , "Q":self.sample_parameters["Q"]},
|
| 66 |
-
self.number_of_values,
|
| 67 |
-
xrange = conc_range,
|
| 68 |
-
xspacing = 'linear',
|
| 69 |
-
noise_level = self.noise_level,
|
| 70 |
-
positive = True,
|
| 71 |
-
)
|
| 72 |
-
|
| 73 |
-
data_array[:,0] *= conversion_factor
|
| 74 |
-
# grams of dye added
|
| 75 |
-
# x = np.linspace(self.minDye, self.maxDye, self.number_of_values) / 1000
|
| 76 |
-
# moles = x / self.params["molarMass"] # g
|
| 77 |
-
# initial_concentration = moles / self.volume # mol/L
|
| 78 |
-
# y = self.measure(K, self.params["Q"], initial_concentration)
|
| 79 |
-
|
| 80 |
-
# # Because noise is added to the concentration in solution, but
|
| 81 |
-
# # the concentration on the surface is required in post-processing, which
|
| 82 |
-
# # is very small, we divide by 1000
|
| 83 |
-
|
| 84 |
-
# y = cek.add_noise(y, self.uncertainty/1000)
|
| 85 |
-
|
| 86 |
-
# data_array = np.column_stack((x, y))
|
| 87 |
-
|
| 88 |
-
return data_array
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|