pyCLINE 0.1.7__py3-none-any.whl
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.
- pyCLINE/__init__.py +22 -0
- pyCLINE/example.py +164 -0
- pyCLINE/generate_data.py +105 -0
- pyCLINE/model.py +1045 -0
- pyCLINE/recovery_methods/__init__.py +15 -0
- pyCLINE/recovery_methods/data_preparation.py +394 -0
- pyCLINE/recovery_methods/nn_training.py +417 -0
- pycline-0.1.7.dist-info/LICENSE +23 -0
- pycline-0.1.7.dist-info/METADATA +40 -0
- pycline-0.1.7.dist-info/RECORD +12 -0
- pycline-0.1.7.dist-info/WHEEL +5 -0
- pycline-0.1.7.dist-info/top_level.txt +1 -0
@@ -0,0 +1,417 @@
|
|
1
|
+
|
2
|
+
import numpy as np
|
3
|
+
import matplotlib.pyplot as plt
|
4
|
+
from tqdm import tqdm
|
5
|
+
|
6
|
+
import torch
|
7
|
+
import torch.nn as nn
|
8
|
+
import torch.optim as optim
|
9
|
+
from torch.utils.data import DataLoader, TensorDataset
|
10
|
+
|
11
|
+
#Error handling
|
12
|
+
class NeuralNetworkError(Exception):
|
13
|
+
"""Error raised for neural network issues."""
|
14
|
+
|
15
|
+
class NeuralNetworkSetupError(NeuralNetworkError):
|
16
|
+
"""Error raised when parameters of the network are 0 but can not be zero."""
|
17
|
+
def __init__(self, parameter_name, parameter, message="parameter has to be greater then zero/be provided."):
|
18
|
+
self.parameter_name = parameter_name
|
19
|
+
self.parameter = parameter
|
20
|
+
self.message = message
|
21
|
+
super().__init__(f'Error: {self.parameter_name} '+self.message+f' Value given: {self.parameter}')
|
22
|
+
|
23
|
+
class NeuralNetworkSetupTrainingError(NeuralNetworkError):
|
24
|
+
"""Error raised when setting up training of the neural network."""
|
25
|
+
def __init__(self, parameter_name, parameter, message="parameter has to be greater then zero."):
|
26
|
+
self.parameter_name = parameter_name
|
27
|
+
self.parameter = parameter
|
28
|
+
self.message = message
|
29
|
+
super().__init__(f'Error: {self.parameter_name} '+self.message+f' Value given: {self.parameter}')
|
30
|
+
|
31
|
+
class NeuralNetworkDataError(NeuralNetworkError):
|
32
|
+
"""Error raised when data for the neural network is incorrect."""
|
33
|
+
def __init__(self, errors):
|
34
|
+
self.errors = errors
|
35
|
+
message = "; ".join(errors)
|
36
|
+
super().__init__(f'Error: {message}')
|
37
|
+
|
38
|
+
|
39
|
+
class FFNN(nn.Module):
|
40
|
+
"""
|
41
|
+
Feedforward Neural Network (FFNN) class.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
nn (torch.nn module): PyTorch neural network module
|
45
|
+
"""
|
46
|
+
def __init__(self, Nin, Nout, Nlayers, Nnodes, activation):
|
47
|
+
super(FFNN, self).__init__()
|
48
|
+
layers = [nn.Linear(Nin, Nnodes), activation()]
|
49
|
+
for _ in range(Nlayers - 1):
|
50
|
+
layers.append(nn.Linear(Nnodes, Nnodes))
|
51
|
+
layers.append(activation())
|
52
|
+
layers.append(nn.Linear(Nnodes, Nout))
|
53
|
+
# layers.append(nn.Sigmoid())
|
54
|
+
self.model = nn.Sequential(*layers)
|
55
|
+
|
56
|
+
def forward(self, x):
|
57
|
+
return self.model(x)
|
58
|
+
|
59
|
+
def init_weights(m):
|
60
|
+
"""
|
61
|
+
Initialize the weights of the neural network.
|
62
|
+
|
63
|
+
Args:
|
64
|
+
m (torch model): PyTorch model
|
65
|
+
"""
|
66
|
+
if isinstance(m, nn.Linear):
|
67
|
+
nn.init.xavier_uniform_(m.weight)
|
68
|
+
nn.init.zeros_(m.bias)
|
69
|
+
|
70
|
+
def configure_FFNN_model(Nin, Nout, Nlayers, Nnodes, activation=nn.SiLU, optimizer_name='Adam', lr=1e-4,
|
71
|
+
loss_fn=nn.MSELoss, summary=False):
|
72
|
+
"""
|
73
|
+
Configure the Feedforward Neural Network (FFNN) model.
|
74
|
+
|
75
|
+
Args:
|
76
|
+
Nin (int): Number of input features
|
77
|
+
Nout (int): Number of output features
|
78
|
+
Nlayers (int): Number of layers
|
79
|
+
Nnodes (int): Number of nodes
|
80
|
+
activation (torch neural network activation function module, optional): Activation function of neural network training. Defaults to nn.SiLU.
|
81
|
+
optimizer_name (str, optional): Optimizer for model training. Defaults to 'Adam'.
|
82
|
+
lr (float, optional): Learning rate for training. Defaults to 1e-4.
|
83
|
+
loss_fn (torch loss function module, optional): Loss function for training. Defaults to nn.MSELoss.
|
84
|
+
summary (bool, optional): If model summary should be generated. Defaults to False.
|
85
|
+
|
86
|
+
Returns:
|
87
|
+
model (torch model): setup FFNN model
|
88
|
+
optimizer (torch optimizer): optimizer for training
|
89
|
+
loss_fn (torch loss function): loss function for training
|
90
|
+
"""
|
91
|
+
if Nin == 0:
|
92
|
+
raise NeuralNetworkSetupError('Nin', Nin)
|
93
|
+
if Nout == 0:
|
94
|
+
raise NeuralNetworkSetupError('Nout', Nout)
|
95
|
+
if Nlayers == 0:
|
96
|
+
raise NeuralNetworkSetupError('Nlayers', Nlayers)
|
97
|
+
if Nnodes == 0:
|
98
|
+
raise NeuralNetworkSetupError('Nnodes', Nnodes)
|
99
|
+
if lr == 0:
|
100
|
+
raise NeuralNetworkSetupError('Learning rate', lr)
|
101
|
+
if optimizer_name == '':
|
102
|
+
raise NeuralNetworkSetupError('Optimizer', optimizer_name)
|
103
|
+
if loss_fn == '' or loss_fn == None:
|
104
|
+
raise NeuralNetworkSetupError('Loss function', loss_fn)
|
105
|
+
|
106
|
+
model = FFNN(Nin, Nout, Nlayers, Nnodes, activation)
|
107
|
+
model.apply(init_weights)
|
108
|
+
if optimizer_name == 'SGD':
|
109
|
+
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=0.9)
|
110
|
+
elif optimizer_name == 'RMSprop':
|
111
|
+
optimizer = optim.RMSprop(model.parameters(), lr=lr, alpha=0.99)
|
112
|
+
elif optimizer_name == 'Adam':
|
113
|
+
optimizer = optim.Adam(model.parameters(), lr=lr, weight_decay=1e-10)
|
114
|
+
elif optimizer_name == 'Nadam':
|
115
|
+
optimizer = optim.Nadam(model.parameters(), lr=lr)
|
116
|
+
elif optimizer_name == 'Adagrad':
|
117
|
+
optimizer = optim.Adagrad(model.parameters(), lr=lr)
|
118
|
+
elif optimizer_name == 'Adadelta':
|
119
|
+
optimizer = optim.Adadelta(model.parameters(), lr=lr)
|
120
|
+
|
121
|
+
loss_fn = loss_fn()
|
122
|
+
|
123
|
+
if summary:
|
124
|
+
print(model)
|
125
|
+
|
126
|
+
return model, optimizer, loss_fn
|
127
|
+
|
128
|
+
# Monitor gradients
|
129
|
+
def monitor_gradients(model):
|
130
|
+
"""
|
131
|
+
Monitor the gradients of the model.
|
132
|
+
|
133
|
+
Args:
|
134
|
+
model (torch model feature): torch model
|
135
|
+
|
136
|
+
Returns:
|
137
|
+
gradients (float): gradients of the model
|
138
|
+
"""
|
139
|
+
for name, param in model.named_parameters():
|
140
|
+
if param.requires_grad:
|
141
|
+
gradients=param.grad.norm()
|
142
|
+
return gradients
|
143
|
+
|
144
|
+
def loss_function(input, target, nc_prediction, nullcline_guess, factor):
|
145
|
+
"""
|
146
|
+
Loss function for the neural network model.
|
147
|
+
|
148
|
+
Args:
|
149
|
+
input (torch tensor): input data
|
150
|
+
target (torch tensor): target data
|
151
|
+
nc_prediction (torch tensor): nullcline prediction data
|
152
|
+
nullcline_guess (torch tensor): nullcline guess data
|
153
|
+
factor (float): penalty factor for loss function
|
154
|
+
|
155
|
+
Returns:
|
156
|
+
loss (float): loss function
|
157
|
+
"""
|
158
|
+
mse_loss = nn.MSELoss()
|
159
|
+
mse_loss_nc = nn.MSELoss()
|
160
|
+
loss_train=mse_loss(input, target)
|
161
|
+
loss_nc = mse_loss_nc(nc_prediction, nullcline_guess)
|
162
|
+
return loss_nc+factor*loss_train
|
163
|
+
|
164
|
+
def train_FFNN_model(model, optimizer, loss_fn, input_train, target_train, input_test, target_test, validation_data,
|
165
|
+
epochs=200, batch_size=64, plot_loss=True, device='cpu', use_progressbar=True, save_evolution=True,
|
166
|
+
loss_target='limit_cycle', nullcline_guess=None, factor=1.0, method=None, minimal_value=0.0, maximal_value=1.0):
|
167
|
+
"""
|
168
|
+
Train the Feedforward Neural Network (FFNN) model.
|
169
|
+
|
170
|
+
Args:
|
171
|
+
model (torch model): configured FFNN model
|
172
|
+
optimizer (torch optimizer): optimizer
|
173
|
+
loss_fn (torch loss function): loss function
|
174
|
+
input_train (pandas dataframe): input training data
|
175
|
+
target_train (pandas dataframe): target training data
|
176
|
+
input_test (pandas dataframe): input testing data
|
177
|
+
target_test (pandas dataframe): target testing data
|
178
|
+
validation_data (pandas dataframe): validation data
|
179
|
+
epochs (int, optional): number of epochs. Defaults to 200.
|
180
|
+
batch_size (int, optional): batch size. Defaults to 64.
|
181
|
+
plot_loss (bool, optional): plot the loss. Defaults to True.
|
182
|
+
device (str, optional): device to train the model on. For 'cuda', necessary GPU and CUDA Toolkit are required. Defaults to 'cpu'.
|
183
|
+
use_progressbar (bool, optional): use progress bar when running the training. Defaults to True.
|
184
|
+
save_evolution (bool, optional): save the evolution of limit cycle and nullcline predictions. Defaults to True.
|
185
|
+
loss_target (str, optional): Decide if the target is not limit cycle, but the inital nullcline seed. Defaults to 'limit_cycle'.
|
186
|
+
nullcline_guess (torch tensor, optional): Torch tensor containing inital nullcline guess. Defaults to None.
|
187
|
+
factor (float, optional): The penalty factor for nullcline guess loss evaluation. Defaults to 1.0.
|
188
|
+
method (str, optional): Method used for prediction, can be derivative or delayed variables. Defaults to None.
|
189
|
+
minimal_value (float, optional): Minimal value of minmax normalization for inbetween prediction. Defaults to 0.0.
|
190
|
+
maximal_value (int, optional): Maximal value of minmax normalization for inbetween prediction. Defaults to 1.0.
|
191
|
+
|
192
|
+
Returns:
|
193
|
+
train_losses (list): training losses
|
194
|
+
val_losses (list): validation losses
|
195
|
+
test_loss (float): testing loss
|
196
|
+
predictions (list): predictions of nullcline structure over all epochs
|
197
|
+
lc_predictions (list): limit cycle predictions over all epochs
|
198
|
+
"""
|
199
|
+
#Error handling
|
200
|
+
if input_train.shape[0] == 0:
|
201
|
+
raise NeuralNetworkDataError(['Input training data is empty.'])
|
202
|
+
if target_train.shape[0] == 0:
|
203
|
+
raise NeuralNetworkDataError(['Target training data is empty.'])
|
204
|
+
if input_test.shape[0] == 0:
|
205
|
+
raise NeuralNetworkDataError(['Input testing data is empty.'])
|
206
|
+
if target_test.shape[0] == 0:
|
207
|
+
raise NeuralNetworkDataError(['Target testing data is empty.'])
|
208
|
+
|
209
|
+
if input_train.shape[0]!=target_train.shape[0]:
|
210
|
+
raise NeuralNetworkDataError(['Input and target training data have different lengths.'])
|
211
|
+
if input_test.shape[0]!=target_test.shape[0]:
|
212
|
+
raise NeuralNetworkDataError(['Input and target testing data have different lengths.'])
|
213
|
+
|
214
|
+
if validation_data[0].shape[0] == 0:
|
215
|
+
raise NeuralNetworkDataError(['Validation input data is empty.'])
|
216
|
+
if validation_data[1].shape[0] == 0:
|
217
|
+
raise NeuralNetworkDataError(['Validation target data is empty.'])
|
218
|
+
if validation_data[0].shape[0]!=validation_data[1].shape[0]:
|
219
|
+
raise NeuralNetworkDataError(['Validation input and target data have different lengths.'])
|
220
|
+
|
221
|
+
if epochs == 0:
|
222
|
+
raise NeuralNetworkSetupTrainingError('epochs', epochs)
|
223
|
+
if batch_size == 0:
|
224
|
+
raise NeuralNetworkSetupTrainingError('batch_size', batch_size)
|
225
|
+
|
226
|
+
# Move model to the specified device
|
227
|
+
model.to(device)
|
228
|
+
if loss_target=='limit_cycle':
|
229
|
+
train_dataset = TensorDataset(torch.tensor(input_train.values, dtype=torch.float32), torch.tensor(target_train.values, dtype=torch.float32))
|
230
|
+
test_dataset = TensorDataset(torch.tensor(input_test.values, dtype=torch.float32), torch.tensor(target_test.values, dtype=torch.float32))
|
231
|
+
val_dataset = TensorDataset(torch.tensor(validation_data[0].values, dtype=torch.float32), torch.tensor(validation_data[1].values, dtype=torch.float32))
|
232
|
+
|
233
|
+
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
|
234
|
+
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
|
235
|
+
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)
|
236
|
+
if loss_target=='nullcline_guess':
|
237
|
+
|
238
|
+
train_dataset = TensorDataset(torch.tensor(input_train.values, dtype=torch.float32), torch.tensor(target_train.values, dtype=torch.float32), torch.tensor(np.array([nullcline_guess]*input_train.shape[0]), dtype=torch.float32))
|
239
|
+
test_dataset = TensorDataset(torch.tensor(input_test.values, dtype=torch.float32), torch.tensor(target_test.values, dtype=torch.float32), torch.tensor(np.array([nullcline_guess]*input_test.shape[0]), dtype=torch.float32))
|
240
|
+
val_dataset = TensorDataset(torch.tensor(validation_data[0].values, dtype=torch.float32), torch.tensor(validation_data[1].values, dtype=torch.float32), torch.tensor(([nullcline_guess]*validation_data[0].shape[0]), dtype=torch.float32))
|
241
|
+
|
242
|
+
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
|
243
|
+
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)
|
244
|
+
val_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False)
|
245
|
+
|
246
|
+
train_losses = []
|
247
|
+
val_losses = []
|
248
|
+
predictions=[]
|
249
|
+
lc_predictions=[]
|
250
|
+
gradients=[]
|
251
|
+
|
252
|
+
for epoch in range(epochs):
|
253
|
+
if use_progressbar:
|
254
|
+
progressbar=tqdm(enumerate(train_loader), total=len(train_loader), desc=f'Epoch {epoch+1}/{epochs}')
|
255
|
+
else:
|
256
|
+
progressbar=enumerate(train_loader)
|
257
|
+
model.train()
|
258
|
+
running_loss = 0.0
|
259
|
+
for batch_idx, data in progressbar:
|
260
|
+
# Move inputs and targets to the specified device
|
261
|
+
if loss_target=='limit_cycle':
|
262
|
+
inputs, targets = data
|
263
|
+
inputs, targets = inputs.to(device), targets.to(device)
|
264
|
+
if loss_target=='nullcline_guess':
|
265
|
+
inputs, targets, nullcline_guess = data
|
266
|
+
inputs, targets, nullcline_guess = inputs.to(device), targets.to(device), nullcline_guess.to(device)
|
267
|
+
|
268
|
+
optimizer.zero_grad()
|
269
|
+
outputs = model(inputs)
|
270
|
+
if loss_target=='limit_cycle':
|
271
|
+
loss = loss_fn(outputs, targets)
|
272
|
+
|
273
|
+
if loss_target=='nullcline_guess':
|
274
|
+
input_null = np.zeros((nullcline_guess.shape[1], model.model[0].in_features))
|
275
|
+
for i in range(model.model[0].in_features):
|
276
|
+
input_null[:,i] = np.linspace(0, 1,nullcline_guess.shape[1])
|
277
|
+
input_null = torch.tensor(input_null, dtype=torch.float32).to(device)
|
278
|
+
nc_prediction = model(input_null)
|
279
|
+
loss = loss_function(outputs, targets, nc_prediction[:,0], nullcline_guess[0,:], factor)
|
280
|
+
loss.backward()
|
281
|
+
optimizer.step()
|
282
|
+
running_loss += loss.item() * inputs.size(0)
|
283
|
+
|
284
|
+
train_loss = running_loss / len(train_loader.dataset)
|
285
|
+
train_losses.append(train_loss)
|
286
|
+
|
287
|
+
model.eval()
|
288
|
+
val_loss = 0.0
|
289
|
+
with torch.no_grad():
|
290
|
+
for data in val_loader:
|
291
|
+
|
292
|
+
if loss_target=='limit_cycle':
|
293
|
+
inputs, targets = data
|
294
|
+
inputs, targets = inputs.to(device), targets.to(device)
|
295
|
+
if loss_target=='nullcline_guess':
|
296
|
+
inputs, targets, nullcline_guess = data
|
297
|
+
inputs, targets, nullcline_guess = inputs.to(device), targets.to(device), nullcline_guess.to(device)
|
298
|
+
|
299
|
+
outputs = model(inputs)
|
300
|
+
if loss_target=='limit_cycle':
|
301
|
+
loss = loss_fn(outputs, targets)
|
302
|
+
|
303
|
+
if loss_target=='nullcline_guess':
|
304
|
+
input_null = np.zeros((nullcline_guess.shape[1], model.model[0].in_features))
|
305
|
+
for i in range(model.model[0].in_features):
|
306
|
+
input_null[:,i] = np.linspace(0, 1,nullcline_guess.shape[1])
|
307
|
+
input_null = torch.tensor(input_null, dtype=torch.float32).to(device)
|
308
|
+
nc_prediction = model(input_null)
|
309
|
+
loss = loss_function(outputs, targets, nc_prediction[:,0], nullcline_guess[0,:], factor)
|
310
|
+
val_loss += loss.item() * inputs.size(0)
|
311
|
+
|
312
|
+
val_loss /= len(val_loader.dataset)
|
313
|
+
val_losses.append(val_loss)
|
314
|
+
|
315
|
+
if save_evolution:
|
316
|
+
_ , prediction_null = nullcline_prediction(model, Nsteps=500, method=method, min_val=minimal_value, max_val=maximal_value)
|
317
|
+
predictions.append(prediction_null)
|
318
|
+
input_prediction=torch.tensor(input_train.values, dtype=torch.float32)
|
319
|
+
with torch.no_grad():
|
320
|
+
output_prediction = model(input_prediction).cpu().numpy()
|
321
|
+
lc_predictions.append(output_prediction)
|
322
|
+
if use_progressbar:
|
323
|
+
progressbar.set_description(f'Epoch [{epoch+1}/{epochs}], Batch [{batch_idx}/{len(train_loader)}], Loss: {train_loss:.4f}')
|
324
|
+
progressbar.refresh()
|
325
|
+
|
326
|
+
if plot_loss:
|
327
|
+
# plt.plot(gradients, label='Gradients')
|
328
|
+
plt.plot(train_losses, label='Train Loss')
|
329
|
+
plt.plot(val_losses, label='Validation Loss')
|
330
|
+
plt.yscale('log')
|
331
|
+
plt.legend()
|
332
|
+
plt.show()
|
333
|
+
|
334
|
+
model.eval()
|
335
|
+
test_loss = 0.0
|
336
|
+
with torch.no_grad():
|
337
|
+
for data in test_loader:
|
338
|
+
if loss_target=='limit_cycle':
|
339
|
+
inputs, targets = data
|
340
|
+
inputs, targets = inputs.to(device), targets.to(device)
|
341
|
+
if loss_target=='nullcline_guess':
|
342
|
+
inputs, targets, nullcline_guess = data
|
343
|
+
inputs, targets, nullcline_guess = inputs.to(device), targets.to(device), nullcline_guess.to(device)
|
344
|
+
|
345
|
+
outputs = model(inputs)
|
346
|
+
if loss_target=='limit_cycle':
|
347
|
+
loss = loss_fn(outputs, targets)
|
348
|
+
|
349
|
+
if loss_target=='nullcline_guess':
|
350
|
+
input_null = np.zeros((nullcline_guess.shape[1], model.model[0].in_features))
|
351
|
+
for i in range(model.model[0].in_features):
|
352
|
+
input_null[:,i] = np.linspace(0, 1,nullcline_guess.shape[1])
|
353
|
+
input_null = torch.tensor(input_null, dtype=torch.float32).to(device)
|
354
|
+
|
355
|
+
nc_prediction = model(input_null)
|
356
|
+
loss = loss_function(outputs, targets, nc_prediction[:,0], nullcline_guess[0,:], factor)
|
357
|
+
test_loss += loss.item() * inputs.size(0)
|
358
|
+
|
359
|
+
test_loss /= len(test_loader.dataset)
|
360
|
+
predictions_evolution=np.array(predictions)
|
361
|
+
lc_predictions = np.array(lc_predictions)
|
362
|
+
return train_losses, val_losses, test_loss, predictions_evolution, lc_predictions
|
363
|
+
|
364
|
+
def nullcline_prediction(model, Nsteps, device='cpu', method=None,min_val=0.0, max_val=1.0):
|
365
|
+
"""
|
366
|
+
Predict the nullcline of the model.
|
367
|
+
|
368
|
+
Args:
|
369
|
+
model (torch model): FFNN model
|
370
|
+
Nsteps (int): Number of steps for discretization of input variable for nullcline prediction
|
371
|
+
device (str, optional): device to train the model on. For 'cuda', necessary GPU and CUDA Toolkit are required. Defaults to 'cpu'.
|
372
|
+
method (str, optional): Method used for prediction, can be derivative or delayed variables. Defaults to None.
|
373
|
+
min_val (float, optional): Minimum value for minmax normalization. Defaults to 0.0.
|
374
|
+
max_val (float, optional): Maximum value for minmax normalization. Defaults to 1.0.
|
375
|
+
|
376
|
+
Returns:
|
377
|
+
input_null (numpy array): input variable for nullcline prediction
|
378
|
+
prediction_null (numpy array): predicted nullcline
|
379
|
+
"""
|
380
|
+
input_null = np.zeros((Nsteps, model.model[0].in_features))
|
381
|
+
if method=='derivative':
|
382
|
+
for i in range(model.model[0].in_features-1):
|
383
|
+
input_null[:,i] = np.linspace(min_val, max_val, Nsteps)
|
384
|
+
else:
|
385
|
+
for i in range(model.model[0].in_features):
|
386
|
+
input_null[:,i] = np.linspace(min_val, max_val,Nsteps)
|
387
|
+
|
388
|
+
input_null = torch.tensor(input_null, dtype=torch.float32).to(device)
|
389
|
+
model.eval()
|
390
|
+
with torch.no_grad():
|
391
|
+
prediction_null = model(input_null).cpu().numpy()
|
392
|
+
return input_null.cpu().numpy(), prediction_null.reshape(Nsteps,)
|
393
|
+
|
394
|
+
def compute_nullcline(fnull, xvar, yvar, Nsteps, df_coef, value_min=0.0, value_max=1.0, normalize=True):
|
395
|
+
"""
|
396
|
+
Compute the nullcline of the model from the ground true function.
|
397
|
+
|
398
|
+
Args:
|
399
|
+
fnull (function): ground true function
|
400
|
+
xvar (str): x variable for nullcline computation
|
401
|
+
yvar (str): y variable for nullcline computation
|
402
|
+
Nsteps (int): Number of steps for discretization of input variable for nullcline prediction
|
403
|
+
df_coef (pandas dataframe): dataframe containing the coefficients of the model
|
404
|
+
value_min (float, optional): Minimum value for minmax normalization. Defaults to 0.0.
|
405
|
+
value_max (float, optional): Maximum value for minmax normalization.. Defaults to 1.0.
|
406
|
+
normalize (bool, optional): If values should be minmax normalized. Defaults to True.
|
407
|
+
|
408
|
+
Returns:
|
409
|
+
xnull (list): x variable for nullcline prediction
|
410
|
+
ynull (list): predicted nullcline
|
411
|
+
"""
|
412
|
+
xnull = np.linspace(df_coef[xvar]['min'],df_coef[xvar]['max'], Nsteps)
|
413
|
+
ynull = fnull(xnull)
|
414
|
+
if normalize:
|
415
|
+
ynull = (ynull - df_coef[yvar]['min'])*(value_max-value_min)/(df_coef[yvar]['max'] - df_coef[yvar]['min'])+value_min
|
416
|
+
|
417
|
+
return xnull, ynull
|
@@ -0,0 +1,23 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025, Bartosz Prokop, Nikita Frolov, Lendert Gelens
|
4
|
+
Laboratory of Dynamics in Biological Systems (DiBS/Gelens lab)
|
5
|
+
Department of Cellular and Mollecular Medicine, KU Leuven
|
6
|
+
|
7
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
8
|
+
of this software and associated documentation files (the "Software"), to deal
|
9
|
+
in the Software without restriction, including without limitation the rights
|
10
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
11
|
+
copies of the Software, and to permit persons to whom the Software is
|
12
|
+
furnished to do so, subject to the following conditions:
|
13
|
+
|
14
|
+
The above copyright notice and this permission notice shall be included in all
|
15
|
+
copies or substantial portions of the Software.
|
16
|
+
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
18
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
19
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
20
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
21
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
22
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
23
|
+
SOFTWARE.
|
@@ -0,0 +1,40 @@
|
|
1
|
+
Metadata-Version: 2.2
|
2
|
+
Name: pyCLINE
|
3
|
+
Version: 0.1.7
|
4
|
+
Summary: This package is the python implementation of the CLINE method introduced by Prokop, Billen, Frolov, Gelens (2025).
|
5
|
+
Author-email: Bartosz Prokop <bartosz.prokop@kuleuven.be>, Nikita Frolov <nikita.frolov@kuleuven.be>, Lendert Gelens <lendert.gelens@kuleuven.be>
|
6
|
+
Project-URL: Homepage, https://pycline-ec8369.pages.gitlab.kuleuven.be/
|
7
|
+
Project-URL: Issues, https://gitlab.kuleuven.be/gelenslab/publications/pycline/-/issues
|
8
|
+
Keywords: model,model identification,nullcline,data-driven,machine learning,deep learning,torch,dynamics,oscillator,nonlinear dynamics,complex systems
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
11
|
+
Classifier: Operating System :: OS Independent
|
12
|
+
Requires-Python: >=3.10
|
13
|
+
Description-Content-Type: text/markdown
|
14
|
+
License-File: LICENSE
|
15
|
+
Requires-Dist: matplotlib>=3.6.2
|
16
|
+
Requires-Dist: numpy<1.25.0,>=1.24.1
|
17
|
+
Requires-Dist: pandas~=1.5.2
|
18
|
+
Requires-Dist: torch>=2.4.1
|
19
|
+
Requires-Dist: tqdm>=4.66.1
|
20
|
+
Requires-Dist: jitcdde>=1.8.1
|
21
|
+
Requires-Dist: scipy>=1.9.3
|
22
|
+
|
23
|
+
# PyCLINE - python package for CLINE
|
24
|
+
|
25
|
+
The `pyCLINE` package is the python package based on the CLINE (**C**omputational **L**earning and **I**dentification of **N**ullclin**E**s).
|
26
|
+
It can be downloaded from PyPI with pip by using
|
27
|
+
|
28
|
+
pip install pyCLINE
|
29
|
+
|
30
|
+
The package allows to recreate all data, models and results shown in XXX, and to apply CLINE to other data sets.
|
31
|
+
In order to generate data used in XXX, a set of different models is being provided under `pyCLINE.model`.
|
32
|
+
Data from these models can be generated using `pyCLINE.generate_data()`.
|
33
|
+
For setting up the data prepartion and adjacent training a neural network, the submodule `pyCLINE.recovery_methods` is used.
|
34
|
+
The submodule contains the module for data_preparation `pyCLINE.recovery_methods.data_preparation` and for neural network training `pyCLINE.recovery_methods.nn_training`.
|
35
|
+
|
36
|
+
For a better understanding, `pyCLINE` also contains the module `pyCLINE.example` which provides four example also found in XXX with step by step instructions on how to setup a CLINE pipeline.
|
37
|
+
|
38
|
+
The structure of `pyCLINE` is shown here:
|
39
|
+
|
40
|
+

|
@@ -0,0 +1,12 @@
|
|
1
|
+
pyCLINE/__init__.py,sha256=Z38oxkRTnb_EiU31_03Ba2bUKs1S2PEajMMGtlUOlf8,726
|
2
|
+
pyCLINE/example.py,sha256=pU-5tT7MA3qlygbsa-_CsjfZRUH5A66mJV7-lDMMP6c,8300
|
3
|
+
pyCLINE/generate_data.py,sha256=mQd5e-qyRv3iDecrYrPELOkR_JtMwxBaC_hBzf7Ddho,3642
|
4
|
+
pyCLINE/model.py,sha256=Qq5sQd7bXmI7efoN_IidWjfHAB--ZskEzGDkZYKsE38,35337
|
5
|
+
pyCLINE/recovery_methods/__init__.py,sha256=MQ9ZF_SVZNBJkZ0cyM5zXimiug9yu42lHBCnOMYw080,488
|
6
|
+
pyCLINE/recovery_methods/data_preparation.py,sha256=59DBwMhfkEmi3icb21smugCbhs99syUpAYAuwPQZ6u4,19708
|
7
|
+
pyCLINE/recovery_methods/nn_training.py,sha256=i8OeY72UcdGYmI_cloKnK2uqvaxLGUtMqT56-MgFVx8,20357
|
8
|
+
pycline-0.1.7.dist-info/LICENSE,sha256=6XV86fklwr93DuwtgX05Jg3n25_0c726oBhoSMn1aoc,1245
|
9
|
+
pycline-0.1.7.dist-info/METADATA,sha256=pxAt1XNClFORBZYPhZcHu-QuoY6bSYMVJ_FWPeQlwxQ,2245
|
10
|
+
pycline-0.1.7.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
|
11
|
+
pycline-0.1.7.dist-info/top_level.txt,sha256=w0zzQfaPH2RNTWfJ_lsPf-EbkvT6m3quM69exCTMBvU,8
|
12
|
+
pycline-0.1.7.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
pyCLINE
|