electrokitty 1.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- electrokitty-1.0.2/PKG-INFO +11 -0
- electrokitty-1.0.2/README.md +5 -0
- electrokitty-1.0.2/electrokitty/ElectroKitty_MCMC_sampler.py +189 -0
- electrokitty-1.0.2/electrokitty/ElectroKitty_base_class.py +749 -0
- electrokitty-1.0.2/electrokitty/ElectroKitty_loss_functions.py +345 -0
- electrokitty-1.0.2/electrokitty/ElectroKitty_optimization_controler.py +68 -0
- electrokitty-1.0.2/electrokitty/ElectroKitty_parser.py +183 -0
- electrokitty-1.0.2/electrokitty/ElectroKitty_simulator.py +612 -0
- electrokitty-1.0.2/electrokitty/__init__.py +6 -0
- electrokitty-1.0.2/electrokitty/cpp_code/__init__.py +0 -0
- electrokitty-1.0.2/electrokitty/cpp_code/electokitty_helper_file.cpp +3057 -0
- electrokitty-1.0.2/electrokitty/cpp_code/electrokitty_simulator.cpp +626 -0
- electrokitty-1.0.2/electrokitty/optimization_routines.py +338 -0
- electrokitty-1.0.2/electrokitty.egg-info/PKG-INFO +11 -0
- electrokitty-1.0.2/electrokitty.egg-info/SOURCES.txt +21 -0
- electrokitty-1.0.2/electrokitty.egg-info/dependency_links.txt +1 -0
- electrokitty-1.0.2/electrokitty.egg-info/not-zip-safe +1 -0
- electrokitty-1.0.2/electrokitty.egg-info/requires.txt +6 -0
- electrokitty-1.0.2/electrokitty.egg-info/top_level.txt +2 -0
- electrokitty-1.0.2/setup.cfg +4 -0
- electrokitty-1.0.2/setup.py +34 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 1.2
|
|
2
|
+
Name: electrokitty
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: A simulation and simple analysis tool for electrochemical data
|
|
5
|
+
Home-page: UNKNOWN
|
|
6
|
+
Author: Ožbej Vodeb
|
|
7
|
+
Author-email: ozbej.vodeb@gmail.com
|
|
8
|
+
License: BSD
|
|
9
|
+
Description: More information comming soon
|
|
10
|
+
Platform: UNKNOWN
|
|
11
|
+
Requires-Python: >=3.7
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created on Thu Aug 29 14:58:33 2024
|
|
4
|
+
|
|
5
|
+
@author: ozbejv
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
import multiprocessing
|
|
10
|
+
|
|
11
|
+
class electrokitty_sampler():
|
|
12
|
+
"""
|
|
13
|
+
Class containing the MCMC sampler used in parameter estimation
|
|
14
|
+
"""
|
|
15
|
+
def __init__(self, n_samples, burn_in_per, num_chains,
|
|
16
|
+
multi_processing, bounds, I_data):
|
|
17
|
+
|
|
18
|
+
self.n_samples=n_samples
|
|
19
|
+
self.burn_in_per=burn_in_per
|
|
20
|
+
self.num_chains=num_chains
|
|
21
|
+
self.multi_processing=multi_processing
|
|
22
|
+
self.bounds=bounds
|
|
23
|
+
self.y_sim=None
|
|
24
|
+
self.I_data=I_data
|
|
25
|
+
|
|
26
|
+
def give_y_sim(self, ysim):
|
|
27
|
+
"""
|
|
28
|
+
update the function used to generate the simulation
|
|
29
|
+
"""
|
|
30
|
+
self.y_sim=ysim
|
|
31
|
+
|
|
32
|
+
def prior(self, theta, lower_bound, upper_bound):
|
|
33
|
+
"""
|
|
34
|
+
Function that given the guess and the bounds calculates the prior,
|
|
35
|
+
either 0 or 1
|
|
36
|
+
"""
|
|
37
|
+
p=[]
|
|
38
|
+
|
|
39
|
+
for i in range(len(theta)):
|
|
40
|
+
|
|
41
|
+
if theta[i] >= lower_bound[i] and theta[i] <= upper_bound[i]:
|
|
42
|
+
p.append(1)
|
|
43
|
+
else:
|
|
44
|
+
p.append(-np.inf)
|
|
45
|
+
return min(p)
|
|
46
|
+
|
|
47
|
+
def likelihood(self, theta):
|
|
48
|
+
"""
|
|
49
|
+
Given the guess calculates the likelihood based on a guessian that the guess fits the data
|
|
50
|
+
"""
|
|
51
|
+
i_sim=self.y_sim(theta)
|
|
52
|
+
|
|
53
|
+
N=len(self.I_data)
|
|
54
|
+
|
|
55
|
+
p=-N/2*np.log(2*np.pi*theta[-1]**2)-np.sum((self.I_data-i_sim)**2)/2/theta[-1]**2
|
|
56
|
+
|
|
57
|
+
return p
|
|
58
|
+
|
|
59
|
+
def propose(self, p, covar):
|
|
60
|
+
"""
|
|
61
|
+
A function that given the means and their covariance matrix proposes a guess
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
p_new=np.random.multivariate_normal(p, covar)
|
|
65
|
+
|
|
66
|
+
return p_new
|
|
67
|
+
|
|
68
|
+
def accept(self, p, p_new, lower_bound, upper_bound, acc_rate):
|
|
69
|
+
"""
|
|
70
|
+
Calculates the likelihood of the old and new guess and either accepts it or rejects the new guesss
|
|
71
|
+
returning either
|
|
72
|
+
"""
|
|
73
|
+
theta=p
|
|
74
|
+
acc=0
|
|
75
|
+
|
|
76
|
+
numerator = self.likelihood(p) + (self.prior(p, lower_bound, upper_bound))
|
|
77
|
+
|
|
78
|
+
numerator_new = self.likelihood(p_new) + (self.prior(p_new, lower_bound, upper_bound))
|
|
79
|
+
|
|
80
|
+
if numerator_new >= numerator:
|
|
81
|
+
theta=p_new
|
|
82
|
+
acc_rate+=1
|
|
83
|
+
acc=1
|
|
84
|
+
|
|
85
|
+
elif np.log(np.random.rand()) < (numerator_new-numerator):
|
|
86
|
+
theta=p_new
|
|
87
|
+
acc_rate+=1
|
|
88
|
+
acc=1
|
|
89
|
+
|
|
90
|
+
return theta, acc_rate, acc
|
|
91
|
+
|
|
92
|
+
def update_moms(self, means, M2, current, log_lam, i, p):
|
|
93
|
+
"""
|
|
94
|
+
updates the parameter mean and covariance matrix based on the new guess
|
|
95
|
+
|
|
96
|
+
using HB-MCMC it updates the mean and covariance based on the acceptance rate
|
|
97
|
+
"""
|
|
98
|
+
nexti=i+1
|
|
99
|
+
w=nexti**-0.5
|
|
100
|
+
newme=(1-w)*means+w*current
|
|
101
|
+
db, da = (current-newme), (current-newme)
|
|
102
|
+
newM=(1-w)*M2+w*np.outer(db,da)
|
|
103
|
+
log_lam+=w*(p-0.225)
|
|
104
|
+
return newme, newM, log_lam
|
|
105
|
+
|
|
106
|
+
def single_chain(self, n_samples, means, lower_bound, upper_bound, chain_num):
|
|
107
|
+
"""
|
|
108
|
+
Calculates a single MCMC chain using HBMCMC
|
|
109
|
+
"""
|
|
110
|
+
divisor=max([1,int(0.01*n_samples)])
|
|
111
|
+
results=[]
|
|
112
|
+
|
|
113
|
+
dim=len(means)
|
|
114
|
+
|
|
115
|
+
cov0=1/np.sqrt(dim)*np.eye(dim)
|
|
116
|
+
|
|
117
|
+
results.append(means)
|
|
118
|
+
|
|
119
|
+
M2=cov0
|
|
120
|
+
means=means
|
|
121
|
+
acc=0
|
|
122
|
+
log_lam=0
|
|
123
|
+
acc_total=0
|
|
124
|
+
|
|
125
|
+
for i in range(n_samples):
|
|
126
|
+
x_cur=results[-1]
|
|
127
|
+
|
|
128
|
+
prop_theta=self.propose(x_cur, np.exp(log_lam)*M2)
|
|
129
|
+
|
|
130
|
+
theta, acc_total, acc = self.accept(x_cur, prop_theta, lower_bound, upper_bound, acc_total)
|
|
131
|
+
results.append(theta)
|
|
132
|
+
|
|
133
|
+
means, M2, log_lam = self.update_moms(means, M2, results[-1], log_lam, i, acc_total/(i+1))
|
|
134
|
+
if i%divisor==0:
|
|
135
|
+
print(f"chain num: {chain_num} iteration: {i} acceptance rate: {round(acc_total/(i+1),3)}")
|
|
136
|
+
|
|
137
|
+
return np.array(results)
|
|
138
|
+
|
|
139
|
+
def single_chain_wrap(self, args):
|
|
140
|
+
"""
|
|
141
|
+
Call to execute a single chain
|
|
142
|
+
"""
|
|
143
|
+
return self.single_chain(*args)
|
|
144
|
+
|
|
145
|
+
def start_sampler(self, initial_guess):
|
|
146
|
+
"""
|
|
147
|
+
Call to start samplig from the initial guess
|
|
148
|
+
|
|
149
|
+
This will perform either on a single or multicore depending on what was chosen
|
|
150
|
+
"""
|
|
151
|
+
lower_bound, upper_bound=self.bounds
|
|
152
|
+
|
|
153
|
+
initial_positions=[]
|
|
154
|
+
scatter=np.random.uniform(0.8, 1.2, self.num_chains)
|
|
155
|
+
for index in range(self.num_chains):
|
|
156
|
+
initial_positions.append(scatter[index]*initial_guess)
|
|
157
|
+
|
|
158
|
+
chains=[]
|
|
159
|
+
|
|
160
|
+
if self.multi_processing == False:
|
|
161
|
+
print("Starting MCMC sampling")
|
|
162
|
+
print()
|
|
163
|
+
|
|
164
|
+
for ch_num in range(self.num_chains):
|
|
165
|
+
print()
|
|
166
|
+
print(f"Resolving chain num: {ch_num}")
|
|
167
|
+
print()
|
|
168
|
+
single_chain=self.single_chain(self.n_samples,
|
|
169
|
+
initial_positions[ch_num],
|
|
170
|
+
lower_bound, upper_bound, ch_num)
|
|
171
|
+
chains.append(single_chain)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
else:
|
|
175
|
+
stuff=[]
|
|
176
|
+
for i in range(self.num_chains):
|
|
177
|
+
stuff.append([self.n_samples, initial_positions[i],
|
|
178
|
+
lower_bound, upper_bound, i])
|
|
179
|
+
|
|
180
|
+
pool=multiprocessing.Pool(self.num_chains)
|
|
181
|
+
chains=pool.map(self.single_chain_wrap, stuff)
|
|
182
|
+
pool.close()
|
|
183
|
+
pool.join()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
chains=np.array(chains)
|
|
187
|
+
return chains
|
|
188
|
+
|
|
189
|
+
|