modulation-da 0.0.1__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.
- modulation_da/__init__.py +7 -0
- modulation_da/current_calculator.py +71 -0
- modulation_da/exempleBeltako.py +638 -0
- modulation_da/main.py +77 -0
- modulation_da/perturbations.py +103 -0
- modulation_da/pinn_model.py +243 -0
- modulation_da/quantum_config.py +50 -0
- modulation_da/quantum_solver.py +245 -0
- modulation_da/quantum_system.py +51 -0
- modulation_da/results_manager.py +56 -0
- modulation_da/visualization.py +48 -0
- modulation_da-0.0.1.dist-info/METADATA +90 -0
- modulation_da-0.0.1.dist-info/RECORD +16 -0
- modulation_da-0.0.1.dist-info/WHEEL +5 -0
- modulation_da-0.0.1.dist-info/entry_points.txt +2 -0
- modulation_da-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,638 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import tensorflow as tf
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import os
|
|
5
|
+
from time import time
|
|
6
|
+
import scipy.optimize
|
|
7
|
+
|
|
8
|
+
# Configuration TensorFlow
|
|
9
|
+
tf.random.set_seed(1234)
|
|
10
|
+
np.random.seed(1234)
|
|
11
|
+
|
|
12
|
+
class QuantumPINNSolver:
|
|
13
|
+
def __init__(self):
|
|
14
|
+
# Paramètres physiques
|
|
15
|
+
self.e_charge = 1.602176634e-19
|
|
16
|
+
self.hbar = 6.582119514e-16 # eV·s
|
|
17
|
+
self.KB = 8.6173303e-5 # eV/K
|
|
18
|
+
self.pi = np.pi
|
|
19
|
+
|
|
20
|
+
# Paramètres du système
|
|
21
|
+
self.n = 3 # nombre de modes
|
|
22
|
+
self.Ne = 20 # discrétisation en énergie
|
|
23
|
+
self.N = 15000 #15000 # points de collocation
|
|
24
|
+
self.N_0 = 2000 # points de condition initiale
|
|
25
|
+
self.N_pred = 5000 # points de prédiction
|
|
26
|
+
self.N_train_adam = 90000 # époques d'entraînement Adam
|
|
27
|
+
self.N_train_lbfgs = 3000 # époques d'entraînement L-BFGS
|
|
28
|
+
|
|
29
|
+
# Paramètres énergétiques (eV)
|
|
30
|
+
self.E_min = -2.5
|
|
31
|
+
self.E_max = 2.5
|
|
32
|
+
self.dE = (self.E_max - self.E_min) / self.Ne
|
|
33
|
+
self.Ef = 0.0
|
|
34
|
+
self.Temp = 10
|
|
35
|
+
self.KBT = self.KB * self.Temp
|
|
36
|
+
|
|
37
|
+
# Paramètres du système quantique (eV)
|
|
38
|
+
self.GammaL = 0.05
|
|
39
|
+
self.GammaR = 0.05
|
|
40
|
+
self.molE = 0.7
|
|
41
|
+
self.Eg = 2 * self.molE
|
|
42
|
+
self.tm = 0.1
|
|
43
|
+
|
|
44
|
+
# Paramètres temporels (s)
|
|
45
|
+
self.Power = 1.0e-15
|
|
46
|
+
self.t_min = 0 * self.Power
|
|
47
|
+
self.t_max = 100 * self.Power
|
|
48
|
+
|
|
49
|
+
# Paramètres de perturbation gaussienne
|
|
50
|
+
self.cent = 15 * self.Power # s
|
|
51
|
+
self.he1 = 0.500 # eV
|
|
52
|
+
self.he2 = 0.00 # eV
|
|
53
|
+
self.W0 = 5.0 * self.Power # s
|
|
54
|
+
self.W1 = self.Eg / self.hbar # rad/s
|
|
55
|
+
self.tau = 6 * self.Power # s
|
|
56
|
+
|
|
57
|
+
# NORMALISATION PHYSIQUE
|
|
58
|
+
self._setup_normalization()
|
|
59
|
+
self._setup_system()
|
|
60
|
+
|
|
61
|
+
def _setup_normalization(self):
|
|
62
|
+
"""Configure les échelles de normalisation basées sur la physique"""
|
|
63
|
+
# Échelles caractéristiques du système
|
|
64
|
+
self.E_scale = abs(self.molE) # Échelle d'énergie caractéristique (eV)
|
|
65
|
+
self.t_scale = self.hbar / self.E_scale # Échelle de temps caractéristique (s)
|
|
66
|
+
self.psi_scale = 1.0 # Les fonctions d'onde sont sans dimension
|
|
67
|
+
# FACTEURS D'ÉCHELLE POUR LES UNITÉS PHYSIQUES
|
|
68
|
+
# Le courant normalisé doit être multiplié par ce facteur pour obtenir des Ampères
|
|
69
|
+
self.current_scale = self.e_charge / self.t_scale # A
|
|
70
|
+
print(f"Échelles de normalisation:")
|
|
71
|
+
print(f" Énergie: {self.E_scale:.3f} eV")
|
|
72
|
+
print(f" Temps: {self.t_scale:.2e} s")
|
|
73
|
+
print(f" Temps physique max: {self.t_max:.2e} s")
|
|
74
|
+
print(f" Temps normalisé max: {self.t_max/self.t_scale:.3f}")
|
|
75
|
+
print(f" Facteur de conversion du courant: {self.current_scale:.6e} A")
|
|
76
|
+
|
|
77
|
+
# Vérification de cohérence dimensionnelle
|
|
78
|
+
hbar_over_t_scale = self.hbar / self.t_scale # eV
|
|
79
|
+
print(f" Vérification: ℏ/t_scale = {hbar_over_t_scale:.3f} eV = E_scale = {self.E_scale:.3f} eV ✓")
|
|
80
|
+
|
|
81
|
+
def _setup_system(self):
|
|
82
|
+
"""Initialise les matrices du système avec normalisation"""
|
|
83
|
+
# Hamiltonien non perturbé (normalisé)
|
|
84
|
+
self.H_0 = np.array([
|
|
85
|
+
[-self.molE/self.E_scale, 0.0, 0.0],
|
|
86
|
+
[0.0, self.molE/self.E_scale, self.tm/self.E_scale],
|
|
87
|
+
[0.0, self.tm/self.E_scale, self.molE/self.E_scale]
|
|
88
|
+
], dtype=complex)
|
|
89
|
+
|
|
90
|
+
# Auto-énergies (normalisées)
|
|
91
|
+
SEL1 = np.zeros((self.n, self.n), dtype=complex)
|
|
92
|
+
SEL1[0, 0] = -1j * self.GammaL/self.E_scale
|
|
93
|
+
SEL1[1, 1] = -1j * self.GammaL/self.E_scale
|
|
94
|
+
|
|
95
|
+
SEL2 = np.zeros((self.n, self.n), dtype=complex)
|
|
96
|
+
SEL2[2, 2] = -1j * self.GammaR/self.E_scale
|
|
97
|
+
|
|
98
|
+
self.SELF = SEL1 + SEL2
|
|
99
|
+
|
|
100
|
+
# Normalisation des autres paramètres
|
|
101
|
+
self.E_min_norm = self.E_min / self.E_scale
|
|
102
|
+
self.E_max_norm = self.E_max / self.E_scale
|
|
103
|
+
self.dE_norm = self.dE / self.E_scale
|
|
104
|
+
self.Ef_norm = self.Ef / self.E_scale
|
|
105
|
+
self.KBT_norm = self.KBT / self.E_scale
|
|
106
|
+
|
|
107
|
+
self.t_min_norm = self.t_min / self.t_scale
|
|
108
|
+
self.t_max_norm = self.t_max / self.t_scale
|
|
109
|
+
|
|
110
|
+
# Paramètres de perturbation normalisés
|
|
111
|
+
self.cent_norm = self.cent / self.t_scale
|
|
112
|
+
self.he1_norm = self.he1 / self.E_scale
|
|
113
|
+
self.he2_norm = self.he2 / self.E_scale
|
|
114
|
+
self.W0_norm = self.W0 / self.t_scale
|
|
115
|
+
self.W1_norm = self.W1 * self.t_scale # W1 était en rad/s, maintenant sans dimension
|
|
116
|
+
self.tau_norm = self.tau / self.t_scale
|
|
117
|
+
|
|
118
|
+
# Résolution des états stationnaires avec énergies normalisées
|
|
119
|
+
self._solve_stationary_states()
|
|
120
|
+
|
|
121
|
+
def fermi_dirac(self, E_norm, mu_norm, KBT_norm):
|
|
122
|
+
"""Fonction de Fermi-Dirac avec paramètres normalisés"""
|
|
123
|
+
return 1.0 / (1.0 + np.exp((E_norm - mu_norm) / KBT_norm))
|
|
124
|
+
|
|
125
|
+
def _solve_stationary_states(self):
|
|
126
|
+
"""Résout les états stationnaires pour chaque énergie normalisée"""
|
|
127
|
+
self.stock_psi = np.zeros((self.Ne, self.n, self.n), dtype=complex)
|
|
128
|
+
vecs = [np.eye(self.n)[:, j:j+1] * np.sqrt(self.GammaL/self.E_scale) for j in range(self.n)]
|
|
129
|
+
|
|
130
|
+
for i in range(self.Ne):
|
|
131
|
+
E_norm = self.E_min_norm + i * self.dE_norm
|
|
132
|
+
a = E_norm * np.eye(self.n, dtype=complex)
|
|
133
|
+
GR = np.linalg.inv(a - self.H_0 - self.SELF)
|
|
134
|
+
|
|
135
|
+
for j in range(self.n):
|
|
136
|
+
vec = vecs[j]
|
|
137
|
+
psi_st = GR @ vec
|
|
138
|
+
self.stock_psi[i, j, :] = psi_st[:, 0]
|
|
139
|
+
|
|
140
|
+
def perturb_gauss(self, t_norm):
|
|
141
|
+
"""Calcule la perturbation gaussienne avec temps normalisé"""
|
|
142
|
+
How = np.zeros((self.n, self.n), dtype=complex)
|
|
143
|
+
valeur = (-self.he1_norm * np.cos(self.W1_norm * (t_norm - self.cent_norm)) *
|
|
144
|
+
np.exp(-((t_norm - self.cent_norm)**2) / (2 * self.W0_norm**2)) -
|
|
145
|
+
self.he2_norm * np.cos(self.W1_norm * (t_norm - self.cent_norm - self.tau_norm)) *
|
|
146
|
+
np.exp(-((t_norm - self.cent_norm - self.tau_norm)**2) / (2 * self.W0_norm**2)))
|
|
147
|
+
How[0, 1] = valeur
|
|
148
|
+
How[1, 0] = valeur
|
|
149
|
+
return How
|
|
150
|
+
|
|
151
|
+
def create_model(self, num_hidden_layer=4, num_neurons_per_layer=64):
|
|
152
|
+
model = tf.keras.Sequential()
|
|
153
|
+
model.add(tf.keras.layers.InputLayer(input_shape=(1,)))
|
|
154
|
+
for _ in range(num_hidden_layer):
|
|
155
|
+
model.add(tf.keras.layers.Dense(num_neurons_per_layer, activation='tanh',
|
|
156
|
+
kernel_initializer='glorot_normal'))
|
|
157
|
+
model.add(tf.keras.layers.Dense(2 * self.n))
|
|
158
|
+
return model
|
|
159
|
+
|
|
160
|
+
def physics_loss(self, model, t_colloc_norm, E_norm, psi_st_tensor, HP_tensor, H_t_r, H_t_i):
|
|
161
|
+
"""Calcule la perte physique basée sur l'EDP avec unités cohérentes"""
|
|
162
|
+
with tf.GradientTape(persistent=True) as tape:
|
|
163
|
+
tape.watch(t_colloc_norm)
|
|
164
|
+
psi_pred = model(t_colloc_norm)
|
|
165
|
+
u = psi_pred[:, :self.n]
|
|
166
|
+
v = psi_pred[:, self.n:]
|
|
167
|
+
|
|
168
|
+
u_t = tape.batch_jacobian(u, t_colloc_norm)[:, :, 0]
|
|
169
|
+
v_t = tape.batch_jacobian(v, t_colloc_norm)[:, :, 0]
|
|
170
|
+
del tape
|
|
171
|
+
|
|
172
|
+
# Terme source S(t)
|
|
173
|
+
E_complex = tf.cast(E_norm, tf.complex64)
|
|
174
|
+
t_complex = tf.cast(tf.squeeze(t_colloc_norm), tf.complex64)
|
|
175
|
+
phase = tf.exp(-1j * E_complex * t_complex)
|
|
176
|
+
phase = tf.expand_dims(phase, axis=-1)
|
|
177
|
+
|
|
178
|
+
psi_st_broadcast = tf.transpose(psi_st_tensor)
|
|
179
|
+
expo_term = phase * psi_st_broadcast
|
|
180
|
+
expo_term = tf.expand_dims(expo_term, axis=-1)
|
|
181
|
+
|
|
182
|
+
S_complex = tf.matmul(HP_tensor, expo_term)
|
|
183
|
+
S_complex = tf.squeeze(S_complex, axis=-1)
|
|
184
|
+
S_r = tf.math.real(S_complex)
|
|
185
|
+
S_i = tf.math.imag(S_complex)
|
|
186
|
+
|
|
187
|
+
# Équations résiduelles
|
|
188
|
+
u_exp = tf.expand_dims(u, axis=-1)
|
|
189
|
+
v_exp = tf.expand_dims(v, axis=-1)
|
|
190
|
+
|
|
191
|
+
r_u = u_t - tf.squeeze(tf.matmul(H_t_r, v_exp), axis=-1) - tf.squeeze(tf.matmul(H_t_i, u_exp), axis=-1) - S_i
|
|
192
|
+
r_v = -v_t - tf.squeeze(tf.matmul(H_t_r, u_exp), axis=-1) + tf.squeeze(tf.matmul(H_t_i, v_exp), axis=-1) - S_r
|
|
193
|
+
|
|
194
|
+
pde_loss = tf.reduce_mean(tf.square(r_u)) + tf.reduce_mean(tf.square(r_v))
|
|
195
|
+
return pde_loss
|
|
196
|
+
|
|
197
|
+
def initial_conditions_loss(self, model, t_0_norm):
|
|
198
|
+
"""Perte pour les conditions initiales (nulles)"""
|
|
199
|
+
psi_pred_0 = model(t_0_norm)
|
|
200
|
+
u_0 = psi_pred_0[:, :self.n]
|
|
201
|
+
v_0 = psi_pred_0[:, self.n:]
|
|
202
|
+
ic_loss = tf.reduce_mean(tf.square(u_0)) + tf.reduce_mean(tf.square(v_0))
|
|
203
|
+
return ic_loss
|
|
204
|
+
|
|
205
|
+
def total_loss(self, model, t_colloc_norm, t_0_norm, E_norm, psi_st_tensor, HP_tensor, H_t_r, H_t_i):
|
|
206
|
+
"""Perte totale combinant physique et conditions initiales"""
|
|
207
|
+
pde_loss = self.physics_loss(model, t_colloc_norm, E_norm, psi_st_tensor, HP_tensor, H_t_r, H_t_i)
|
|
208
|
+
ic_loss = self.initial_conditions_loss(model, t_0_norm)
|
|
209
|
+
|
|
210
|
+
lambda_pde = 1.0
|
|
211
|
+
lambda_ic = 1.0
|
|
212
|
+
|
|
213
|
+
return lambda_pde * pde_loss + lambda_ic * ic_loss, pde_loss, ic_loss
|
|
214
|
+
|
|
215
|
+
def get_weights(self, model):
|
|
216
|
+
"""Extrait les poids du modèle sous forme de vecteur 1D"""
|
|
217
|
+
weights = []
|
|
218
|
+
for layer in model.layers:
|
|
219
|
+
if hasattr(layer, 'get_weights') and layer.get_weights():
|
|
220
|
+
for w in layer.get_weights():
|
|
221
|
+
weights.append(w.flatten())
|
|
222
|
+
return np.concatenate(weights) if weights else np.array([])
|
|
223
|
+
|
|
224
|
+
def set_weights(self, model, weights_1d):
|
|
225
|
+
"""Restaure les poids du modèle à partir d'un vecteur 1D"""
|
|
226
|
+
idx = 0
|
|
227
|
+
for layer in model.layers:
|
|
228
|
+
if hasattr(layer, 'get_weights') and layer.get_weights():
|
|
229
|
+
layer_weights = []
|
|
230
|
+
for w in layer.get_weights():
|
|
231
|
+
size = w.size
|
|
232
|
+
new_w = weights_1d[idx:idx+size].reshape(w.shape)
|
|
233
|
+
layer_weights.append(new_w)
|
|
234
|
+
idx += size
|
|
235
|
+
layer.set_weights(layer_weights)
|
|
236
|
+
|
|
237
|
+
def lbfgs_loss_func(self, weights_1d, model, t_colloc_norm, t_0_norm, E_norm,
|
|
238
|
+
psi_st_tensor, HP_tensor, H_t_r, H_t_i):
|
|
239
|
+
"""Fonction de perte pour L-BFGS"""
|
|
240
|
+
# Restaurer les poids
|
|
241
|
+
self.set_weights(model, weights_1d)
|
|
242
|
+
|
|
243
|
+
# Calculer la perte
|
|
244
|
+
with tf.GradientTape() as tape:
|
|
245
|
+
loss, pde_loss, ic_loss = self.total_loss(
|
|
246
|
+
model, t_colloc_norm, t_0_norm, E_norm, psi_st_tensor, HP_tensor, H_t_r, H_t_i
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
# Calculer les gradients
|
|
250
|
+
gradients = tape.gradient(loss, model.trainable_variables)
|
|
251
|
+
|
|
252
|
+
# Convertir les gradients en vecteur 1D
|
|
253
|
+
grad_1d = []
|
|
254
|
+
for grad in gradients:
|
|
255
|
+
if grad is not None:
|
|
256
|
+
grad_1d.append(grad.numpy().flatten())
|
|
257
|
+
grad_1d = np.concatenate(grad_1d) if grad_1d else np.array([])
|
|
258
|
+
|
|
259
|
+
return float(loss.numpy()), grad_1d.astype(np.float64)
|
|
260
|
+
|
|
261
|
+
def train_model_hybrid(self, model, t_colloc_norm, t_0_norm, E_norm, psi_st_tensor,
|
|
262
|
+
HP_tensor, H_t_r, H_t_i):
|
|
263
|
+
"""Entraîne le modèle avec Adam puis L-BFGS"""
|
|
264
|
+
|
|
265
|
+
# Phase 1: Entraînement avec Adam
|
|
266
|
+
print("Phase 1: Entraînement avec Adam...")
|
|
267
|
+
|
|
268
|
+
# Optimiseur Adam avec taux d'apprentissage adaptatif
|
|
269
|
+
initial_lr = 1e-3
|
|
270
|
+
lr_schedule = tf.keras.optimizers.schedules.ExponentialDecay(
|
|
271
|
+
initial_learning_rate=initial_lr,
|
|
272
|
+
decay_steps=30000,
|
|
273
|
+
decay_rate=0.9,
|
|
274
|
+
staircase=True
|
|
275
|
+
)
|
|
276
|
+
optimizer_adam = tf.keras.optimizers.Adam(learning_rate=lr_schedule)
|
|
277
|
+
|
|
278
|
+
@tf.function
|
|
279
|
+
def adam_train_step():
|
|
280
|
+
with tf.GradientTape() as tape:
|
|
281
|
+
loss, pde_loss, ic_loss = self.total_loss(
|
|
282
|
+
model, t_colloc_norm, t_0_norm, E_norm, psi_st_tensor, HP_tensor, H_t_r, H_t_i
|
|
283
|
+
)
|
|
284
|
+
gradients = tape.gradient(loss, model.trainable_variables)
|
|
285
|
+
optimizer_adam.apply_gradients(zip(gradients, model.trainable_variables))
|
|
286
|
+
return loss, pde_loss, ic_loss
|
|
287
|
+
|
|
288
|
+
adam_history = {'total_loss': [], 'pde_loss': [], 'ic_loss': []}
|
|
289
|
+
t_start_adam = time()
|
|
290
|
+
|
|
291
|
+
for epoch in range(self.N_train_adam):
|
|
292
|
+
loss, pde_loss, ic_loss = adam_train_step()
|
|
293
|
+
|
|
294
|
+
if epoch % 1000 == 0:
|
|
295
|
+
print(f"Adam Epoch {epoch:5d}: Total = {loss:.6e}, PDE = {pde_loss:.6e}, IC = {ic_loss:.6e}")
|
|
296
|
+
|
|
297
|
+
adam_history['total_loss'].append(float(loss))
|
|
298
|
+
adam_history['pde_loss'].append(float(pde_loss))
|
|
299
|
+
adam_history['ic_loss'].append(float(ic_loss))
|
|
300
|
+
|
|
301
|
+
# Critère d'arrêt précoce pour Adam
|
|
302
|
+
if epoch > 1000 and loss < 1e-4:
|
|
303
|
+
print(f"Arrêt précoce d'Adam à l'époque {epoch}")
|
|
304
|
+
break
|
|
305
|
+
|
|
306
|
+
print(f"Temps d'entraînement Adam: {time() - t_start_adam:.2f} secondes")
|
|
307
|
+
print(f"Perte finale Adam: {loss:.6e}")
|
|
308
|
+
|
|
309
|
+
# Phase 2: Raffinement avec L-BFGS
|
|
310
|
+
print("\nPhase 2: Raffinement avec L-BFGS...")
|
|
311
|
+
|
|
312
|
+
t_start_lbfgs = time()
|
|
313
|
+
|
|
314
|
+
# Poids initiaux pour L-BFGS
|
|
315
|
+
initial_weights = self.get_weights(model)
|
|
316
|
+
|
|
317
|
+
# Configuration L-BFGS
|
|
318
|
+
lbfgs_options = {
|
|
319
|
+
'maxiter': self.N_train_lbfgs,
|
|
320
|
+
'maxfun': self.N_train_lbfgs * 2,
|
|
321
|
+
'ftol': 1e-12,
|
|
322
|
+
'gtol': 1e-12,
|
|
323
|
+
'eps': 1e-8,
|
|
324
|
+
'disp': True
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
# Variables pour suivre les itérations L-BFGS
|
|
328
|
+
self.lbfgs_iter = 0
|
|
329
|
+
self.lbfgs_losses = []
|
|
330
|
+
|
|
331
|
+
def callback_func(xk):
|
|
332
|
+
self.lbfgs_iter += 1
|
|
333
|
+
if self.lbfgs_iter % 100 == 0:
|
|
334
|
+
# Calculer la perte actuelle pour l'affichage
|
|
335
|
+
self.set_weights(model, xk)
|
|
336
|
+
loss, pde_loss, ic_loss = self.total_loss(
|
|
337
|
+
model, t_colloc_norm, t_0_norm, E_norm, psi_st_tensor, HP_tensor, H_t_r, H_t_i
|
|
338
|
+
)
|
|
339
|
+
print(f"L-BFGS Iter {self.lbfgs_iter:4d}: Total = {loss:.6e}, PDE = {pde_loss:.6e}, IC = {ic_loss:.6e}")
|
|
340
|
+
self.lbfgs_losses.append(float(loss))
|
|
341
|
+
|
|
342
|
+
# Optimisation L-BFGS
|
|
343
|
+
try:
|
|
344
|
+
result = scipy.optimize.minimize(
|
|
345
|
+
fun=lambda w: self.lbfgs_loss_func(w, model, t_colloc_norm, t_0_norm,
|
|
346
|
+
E_norm, psi_st_tensor, HP_tensor, H_t_r, H_t_i),
|
|
347
|
+
x0=initial_weights.astype(np.float64),
|
|
348
|
+
method='L-BFGS-B',
|
|
349
|
+
jac=True,
|
|
350
|
+
callback=callback_func,
|
|
351
|
+
options=lbfgs_options
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
# Restaurer les poids optimaux
|
|
355
|
+
self.set_weights(model, result.x)
|
|
356
|
+
|
|
357
|
+
print(f"L-BFGS terminé: {result.message}")
|
|
358
|
+
print(f"Nombre d'itérations: {result.nit}")
|
|
359
|
+
print(f"Perte finale: {result.fun:.6e}")
|
|
360
|
+
|
|
361
|
+
except Exception as e:
|
|
362
|
+
print(f"Erreur L-BFGS: {e}")
|
|
363
|
+
print("Utilisation des poids d'Adam comme solution finale")
|
|
364
|
+
|
|
365
|
+
print(f"Temps d'entraînement L-BFGS: {time() - t_start_lbfgs:.2f} secondes")
|
|
366
|
+
|
|
367
|
+
# Évaluation finale
|
|
368
|
+
final_loss, final_pde_loss, final_ic_loss = self.total_loss(
|
|
369
|
+
model, t_colloc_norm, t_0_norm, E_norm, psi_st_tensor, HP_tensor, H_t_r, H_t_i
|
|
370
|
+
)
|
|
371
|
+
print(f"Perte finale combinée: Total = {final_loss:.6e}, PDE = {final_pde_loss:.6e}, IC = {final_ic_loss:.6e}")
|
|
372
|
+
|
|
373
|
+
# Historique combiné
|
|
374
|
+
combined_history = {
|
|
375
|
+
'adam_history': adam_history,
|
|
376
|
+
'lbfgs_losses': self.lbfgs_losses,
|
|
377
|
+
'final_loss': float(final_loss),
|
|
378
|
+
'final_pde_loss': float(final_pde_loss),
|
|
379
|
+
'final_ic_loss': float(final_ic_loss)
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
return combined_history
|
|
383
|
+
|
|
384
|
+
def solve_for_energy_mode(self, i_energy, j_mode):
|
|
385
|
+
"""Résout pour un mode et une énergie spécifiques"""
|
|
386
|
+
E_norm = self.E_min_norm + i_energy * self.dE_norm
|
|
387
|
+
|
|
388
|
+
# État stationnaire
|
|
389
|
+
psi_st = self.stock_psi[i_energy, j_mode, :].reshape((self.n, 1))
|
|
390
|
+
psi_st_tensor = tf.convert_to_tensor(psi_st, dtype=tf.complex64)
|
|
391
|
+
|
|
392
|
+
# Grille temporelle pour collocation (normalisée)
|
|
393
|
+
#t_colloc_norm = tf.reshape(tf.linspace(self.t_min_norm, self.t_max_norm, self.N), (-1, 1))
|
|
394
|
+
t_colloc_norm = tf.random.uniform((self.N, 1), minval=self.t_min_norm, maxval=self.t_max_norm)
|
|
395
|
+
t_0_norm = tf.zeros((self.N_0, 1), dtype=tf.float32) + self.t_min_norm
|
|
396
|
+
|
|
397
|
+
# Hamiltoniens perturbés (normalisés)
|
|
398
|
+
HP_list = []
|
|
399
|
+
for k in range(self.N):
|
|
400
|
+
t_k_norm = float(t_colloc_norm[k, 0])
|
|
401
|
+
HP_k = self.perturb_gauss(t_k_norm)
|
|
402
|
+
HP_list.append(HP_k)
|
|
403
|
+
|
|
404
|
+
HP_tensor = tf.convert_to_tensor(np.stack(HP_list), dtype=tf.complex64)
|
|
405
|
+
|
|
406
|
+
# Hamiltonien total (normalisé)
|
|
407
|
+
H_0_tensor = tf.convert_to_tensor(self.H_0, dtype=tf.complex64)
|
|
408
|
+
SELF_tensor = tf.convert_to_tensor(self.SELF, dtype=tf.complex64)
|
|
409
|
+
|
|
410
|
+
H_0_expanded = tf.repeat(tf.expand_dims(H_0_tensor, axis=0), self.N, axis=0)
|
|
411
|
+
SELF_expanded = tf.repeat(tf.expand_dims(SELF_tensor, axis=0), self.N, axis=0)
|
|
412
|
+
|
|
413
|
+
H_t = HP_tensor + H_0_expanded + SELF_expanded
|
|
414
|
+
H_t_r = tf.math.real(H_t)
|
|
415
|
+
H_t_i = tf.math.imag(H_t)
|
|
416
|
+
|
|
417
|
+
# Création et entraînement du modèle
|
|
418
|
+
model = self.create_model()
|
|
419
|
+
history = self.train_model_hybrid(
|
|
420
|
+
model, t_colloc_norm, t_0_norm, E_norm, psi_st_tensor, HP_tensor, H_t_r, H_t_i
|
|
421
|
+
)
|
|
422
|
+
|
|
423
|
+
return model, history, E_norm, psi_st_tensor
|
|
424
|
+
|
|
425
|
+
def predict_and_compute_current(self, model, E_norm, psi_st_tensor):
|
|
426
|
+
"""Fait des prédictions et calcule le courant"""
|
|
427
|
+
# Grille temporelle pour prédiction (normalisée)
|
|
428
|
+
t_pred_norm_vals = np.linspace(self.t_min_norm, self.t_max_norm, self.N_pred)
|
|
429
|
+
t_pred_norm = tf.convert_to_tensor(t_pred_norm_vals.reshape(-1, 1), dtype=tf.float32)
|
|
430
|
+
|
|
431
|
+
# Hamiltoniens pour prédiction (normalisés)
|
|
432
|
+
HP_pred_list = []
|
|
433
|
+
for s in range(self.N_pred):
|
|
434
|
+
t_s_norm = t_pred_norm_vals[s]
|
|
435
|
+
HP_s = self.perturb_gauss(t_s_norm)
|
|
436
|
+
HP_pred_list.append(HP_s)
|
|
437
|
+
|
|
438
|
+
HP_pred = tf.convert_to_tensor(np.stack(HP_pred_list), dtype=tf.complex64)
|
|
439
|
+
|
|
440
|
+
# Prédiction du réseau
|
|
441
|
+
psi_pred = model(t_pred_norm) # [N_pred, 2n]
|
|
442
|
+
u_pred = psi_pred[:, :self.n]
|
|
443
|
+
v_pred = psi_pred[:, self.n:]
|
|
444
|
+
psi_p = tf.complex(u_pred, v_pred) # [N_pred, n]
|
|
445
|
+
|
|
446
|
+
# Ajout de l'état stationnaire avec phase (normalisée)
|
|
447
|
+
arg = -E_norm * t_pred_norm_vals # Argument sans dimension
|
|
448
|
+
phase = tf.exp(1j * tf.cast(arg, tf.complex64))
|
|
449
|
+
phase = tf.expand_dims(phase, axis=-1) # [N_pred, 1]
|
|
450
|
+
|
|
451
|
+
psi_st_broadcast = tf.transpose(psi_st_tensor) # [1, n]
|
|
452
|
+
psi_total = psi_p + phase * psi_st_broadcast # [N_pred, n]
|
|
453
|
+
|
|
454
|
+
return psi_total, t_pred_norm_vals, HP_pred
|
|
455
|
+
|
|
456
|
+
def run_full_simulation(self):
|
|
457
|
+
"""Lance la simulation complète"""
|
|
458
|
+
os.makedirs("TEST1", exist_ok=True)
|
|
459
|
+
|
|
460
|
+
J_current = tf.zeros((self.N_pred,), dtype=tf.complex64)
|
|
461
|
+
all_histories = []
|
|
462
|
+
|
|
463
|
+
for i in range(self.Ne):
|
|
464
|
+
print(f"\n=== Énergie {i+1}/{self.Ne} ===")
|
|
465
|
+
|
|
466
|
+
courant_mode = tf.zeros((self.N_pred,), dtype=tf.complex64)
|
|
467
|
+
|
|
468
|
+
for j in range(self.n):
|
|
469
|
+
print(f"\nMode {j+1}/{self.n}")
|
|
470
|
+
|
|
471
|
+
model, history, E_norm, psi_st_tensor = self.solve_for_energy_mode(i, j)
|
|
472
|
+
all_histories.append(history)
|
|
473
|
+
|
|
474
|
+
psi_total, t_pred_norm_vals, HP_pred = self.predict_and_compute_current(model, E_norm, psi_st_tensor)
|
|
475
|
+
|
|
476
|
+
# Calcul du courant pour ce mode
|
|
477
|
+
fermi_factor = self.fermi_dirac(E_norm, self.Ef_norm, self.KBT_norm)
|
|
478
|
+
fermi_tensor = tf.convert_to_tensor(fermi_factor, dtype=tf.complex64)
|
|
479
|
+
|
|
480
|
+
# Hamiltonien total pour le courant (normalisé)
|
|
481
|
+
H_0_tensor = tf.convert_to_tensor(self.H_0, dtype=tf.complex64)
|
|
482
|
+
SELF_tensor = tf.convert_to_tensor(self.SELF, dtype=tf.complex64)
|
|
483
|
+
|
|
484
|
+
H_0_pred = tf.repeat(tf.expand_dims(H_0_tensor, axis=0), self.N_pred, axis=0)
|
|
485
|
+
SELF_pred = tf.repeat(tf.expand_dims(SELF_tensor, axis=0), self.N_pred, axis=0)
|
|
486
|
+
|
|
487
|
+
HT_pred = HP_pred + H_0_pred + SELF_pred
|
|
488
|
+
|
|
489
|
+
# Courant: J ∝ ψ₁* H₁₂ ψ₂
|
|
490
|
+
psi1_conj = tf.math.conj(psi_total[:, 1])
|
|
491
|
+
H12 = HT_pred[:, 1, 2]
|
|
492
|
+
psi2 = psi_total[:, 2]
|
|
493
|
+
|
|
494
|
+
courant_mode += fermi_tensor * psi1_conj * H12 * psi2
|
|
495
|
+
|
|
496
|
+
J_current += self.dE_norm * (1.0 / (2.0 * self.pi)) * courant_mode
|
|
497
|
+
|
|
498
|
+
J_current_norm = J_current
|
|
499
|
+
|
|
500
|
+
# Courant final
|
|
501
|
+
CJJ = -2.0 * tf.math.imag(J_current_norm)
|
|
502
|
+
|
|
503
|
+
# Sauvegarde
|
|
504
|
+
J_current_np = J_current_norm.numpy()
|
|
505
|
+
CJJ_np = CJJ.numpy()
|
|
506
|
+
# Conversion en unités physiques
|
|
507
|
+
CJJ_physical_np = CJJ_np * self.current_scale # Courant en Ampères
|
|
508
|
+
t_physical_vals = t_pred_norm_vals * self.t_scale # Temps en secondes
|
|
509
|
+
|
|
510
|
+
#np.savetxt("TEST1/COURANT", J_current_np)
|
|
511
|
+
#np.savetxt("TEST1/CJJ", CJJ_np)
|
|
512
|
+
# Sauvegarde des données normalisées
|
|
513
|
+
np.savetxt("TEST1/COURANT_normalized", J_current_np)
|
|
514
|
+
np.savetxt("TEST1/CJJ_normalized", CJJ_np)
|
|
515
|
+
np.savetxt("TEST1/time_normalized", t_pred_norm_vals)
|
|
516
|
+
|
|
517
|
+
# Sauvegarde des données physiques
|
|
518
|
+
np.savetxt("TEST1/CJJ_physical", CJJ_physical_np)
|
|
519
|
+
np.savetxt("TEST1/time_physical", t_physical_vals)
|
|
520
|
+
|
|
521
|
+
# Sauvegarde combinée pour facilité d'usage
|
|
522
|
+
np.savetxt("TEST1/current_data_normalized.dat",
|
|
523
|
+
np.column_stack((t_pred_norm_vals, CJJ_np)),
|
|
524
|
+
header="Time_normalized Current_normalized", fmt='%.10e')
|
|
525
|
+
np.savetxt("TEST1/current_data_physical.dat",
|
|
526
|
+
np.column_stack((t_physical_vals, CJJ_physical_np)),
|
|
527
|
+
header="Time_seconds Current_amperes", fmt='%.10e')
|
|
528
|
+
|
|
529
|
+
# Visualisation améliorée
|
|
530
|
+
fig, axes = plt.subplots(2, 2, figsize=(15, 10))
|
|
531
|
+
|
|
532
|
+
# Graphique du courant
|
|
533
|
+
# Graphique du courant normalisé
|
|
534
|
+
axes[0, 0].plot(t_pred_norm_vals, CJJ_np, label='Courant normalisé', color='navy', linewidth=2)
|
|
535
|
+
axes[0, 0].set_xlabel("Temps normalisé (sans dimension)", fontsize=14)
|
|
536
|
+
axes[0, 0].set_ylabel("Courant normalisé (sans dimension)", fontsize=14)
|
|
537
|
+
axes[0, 0].set_title("Courant normalisé", fontsize=14)
|
|
538
|
+
axes[0, 0].grid(True, alpha=0.3)
|
|
539
|
+
|
|
540
|
+
#axes[0, 0].plot(t_pred_norm_vals, CJJ_np, label='Courant CJJ(t)', color='navy', linewidth=2)
|
|
541
|
+
#axes[0, 0].set_xlabel("Time (unitless)", fontsize=14)
|
|
542
|
+
#axes[0, 0].set_ylabel("Current (unitless)", fontsize=14)
|
|
543
|
+
#axes[0, 0].set_title("Current variation over time", fontsize=14)
|
|
544
|
+
#axes[0, 0].grid(True, alpha=0.3)
|
|
545
|
+
|
|
546
|
+
# Composantes du courant complexe
|
|
547
|
+
axes[0, 1].plot(t_pred_norm_vals, np.real(J_current_np), label='Re(J)', color='red', linewidth=1)
|
|
548
|
+
axes[0, 1].plot(t_pred_norm_vals, np.imag(J_current_np), label='Im(J)', color='green', linewidth=1)
|
|
549
|
+
axes[0, 1].set_xlabel("Temps (unitless)", fontsize=14)
|
|
550
|
+
axes[0, 1].set_ylabel("J_current", fontsize=14)
|
|
551
|
+
axes[0, 1].set_title("Composantes du courant", fontsize=14)
|
|
552
|
+
axes[0, 1].legend()
|
|
553
|
+
axes[0, 1].grid(True, alpha=0.3)
|
|
554
|
+
# Convergence Adam (premier mode/énergie comme exemple)
|
|
555
|
+
if all_histories:
|
|
556
|
+
first_history = all_histories[0]
|
|
557
|
+
if 'adam_history' in first_history:
|
|
558
|
+
adam_hist = first_history['adam_history']
|
|
559
|
+
axes[1, 0].semilogy(adam_hist['total_loss'], label='Total Loss', color='blue', linewidth=1)
|
|
560
|
+
axes[1, 0].semilogy(adam_hist['pde_loss'], label='PDE Loss', color='orange', linewidth=1)
|
|
561
|
+
axes[1, 0].semilogy(adam_hist['ic_loss'], label='IC Loss', color='purple', linewidth=1)
|
|
562
|
+
axes[1, 0].set_xlabel("Epochs Adam", fontsize=12)
|
|
563
|
+
axes[1, 0].set_ylabel("Loss", fontsize=12)
|
|
564
|
+
axes[1, 0].set_title("Convergence Adam", fontsize=12)
|
|
565
|
+
axes[1, 0].legend()
|
|
566
|
+
axes[1, 0].grid(True, alpha=0.3)
|
|
567
|
+
|
|
568
|
+
# Graphique du courant physique
|
|
569
|
+
axes[1, 1].plot(t_physical_vals * 1e15, CJJ_physical_np,
|
|
570
|
+
label='Courant physique', color='darkred', linewidth=2)
|
|
571
|
+
axes[1, 1].set_xlabel("Temps (fs)", fontsize=12)
|
|
572
|
+
axes[1, 1].set_ylabel("Courant (A)", fontsize=12)
|
|
573
|
+
axes[1, 1].set_title("Courant physique", fontsize=12)
|
|
574
|
+
axes[1, 1].grid(True, alpha=0.3)
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
# Perturbation gaussienne
|
|
578
|
+
# Figure séparée pour la perturbation gaussienne
|
|
579
|
+
plt.figure(figsize=(10, 6))
|
|
580
|
+
t_pert = np.linspace(self.t_min_norm, self.t_max_norm, 1000)
|
|
581
|
+
pert_values = []
|
|
582
|
+
for t in t_pert:
|
|
583
|
+
H_pert = self.perturb_gauss(t)
|
|
584
|
+
pert_values.append(np.real(H_pert[0, 1])) # Élément (0,1) réel
|
|
585
|
+
|
|
586
|
+
plt.plot(t_pert, pert_values, label='Perturbation H₀₁(t)', color='darkgreen', linewidth=2)
|
|
587
|
+
plt.xlabel("Temps normalisé (sans dimension)", fontsize=14)
|
|
588
|
+
plt.ylabel("Amplitude normalisée", fontsize=14)
|
|
589
|
+
plt.title("Perturbation gaussienne", fontsize=16)
|
|
590
|
+
plt.grid(True, alpha=0.3)
|
|
591
|
+
plt.tight_layout()
|
|
592
|
+
plt.savefig("TEST1/perturbation_gaussian.png", dpi=300, bbox_inches='tight')
|
|
593
|
+
plt.show()
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
|
|
597
|
+
plt.tight_layout()
|
|
598
|
+
plt.savefig("TEST1/quantum_simulation_results.png", dpi=300, bbox_inches='tight')
|
|
599
|
+
plt.show()
|
|
600
|
+
|
|
601
|
+
# Statistiques de convergence
|
|
602
|
+
print("\n=== RÉSULTATS DE LA SIMULATION ===")
|
|
603
|
+
print(f"Courant normalisé maximal: {np.max(np.abs(CJJ_np)):.6e}")
|
|
604
|
+
print(f"Courant physique maximal: {np.max(np.abs(CJJ_physical_np)):.6e} A")
|
|
605
|
+
print(f"Courant physique maximal: {np.max(np.abs(CJJ_physical_np)):.6e} A")
|
|
606
|
+
print(f"Courant normalisé final: {CJJ_np[-1]:.6e}")
|
|
607
|
+
print(f"Courant physique final: {CJJ_physical_np[-1]:.6e} A")
|
|
608
|
+
print(f"Facteur de conversion: {self.current_scale:.6e} A")
|
|
609
|
+
# Résumé des pertes finales
|
|
610
|
+
final_losses = [hist.get('final_loss', 0) for hist in all_histories]
|
|
611
|
+
if final_losses:
|
|
612
|
+
print(f"Perte moyenne finale: {np.mean(final_losses):.6e}")
|
|
613
|
+
print(f"Perte maximale finale: {np.max(final_losses):.6e}")
|
|
614
|
+
|
|
615
|
+
return {
|
|
616
|
+
'current_normalized': CJJ_np,
|
|
617
|
+
'current_physical': CJJ_physical_np,
|
|
618
|
+
'time_normalized': t_pred_norm_vals,
|
|
619
|
+
'time_physical': t_physical_vals,
|
|
620
|
+
'complex_current': J_current_np,
|
|
621
|
+
'current_scale': self.current_scale,
|
|
622
|
+
'histories': all_histories
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
# Point d'entrée principal
|
|
626
|
+
if __name__ == "__main__":
|
|
627
|
+
print("=== SIMULATEUR QUANTUM PINN ===")
|
|
628
|
+
print("Initialisation du solveur...")
|
|
629
|
+
|
|
630
|
+
solver = QuantumPINNSolver()
|
|
631
|
+
|
|
632
|
+
print("\nDémarrage de la simulation complète...")
|
|
633
|
+
results = solver.run_full_simulation()
|
|
634
|
+
|
|
635
|
+
print("\n=== SIMULATION TERMINÉE ===")
|
|
636
|
+
print("Résultats sauvegardés dans le dossier TEST1/")
|
|
637
|
+
print("Graphiques générés: quantum_simulation_results.png")
|
|
638
|
+
|