@@ -41,8 +41,6 @@ def __init__(
41
41
super (_GENETIC , self ).__init__ ()
42
42
self .objectives = objectives
43
43
self .control_parameters = control_parameters
44
- self .time_interval = time_interval
45
- self .time_options = time_options
46
44
self .alg_kwargs = alg_kwargs
47
45
self ._integrator_kwargs = integrator_kwargs
48
46
self ._rtol = self ._integrator_kwargs .get ("rtol" , 1e-5 )
@@ -55,7 +53,7 @@ def __init__(
55
53
self .N_parents = int (np .floor (self .N_pop * self .parent_rate ))
56
54
self .N_survivors = max (1 , int (self .N_parents * alg_kwargs .get ('survival_rate' , 1.0 )))
57
55
self .N_offspring = self .N_pop - self .N_survivors
58
- self .mutation_rate = alg_kwargs .get ('mutation_rate' , 0.1 )
56
+ self .mutation_rate = alg_kwargs .get ('mutation_rate' , 0.2 )
59
57
self .workers = alg_kwargs .get ('workers' , 1 )
60
58
61
59
self ._Hd_lst , self ._Hc_lst = [], []
@@ -138,8 +136,10 @@ def create_pulse_func(idx):
138
136
self ._integrator_kwargs = integrator_kwargs
139
137
self ._rtol = self ._integrator_kwargs .get ("rtol" , 1e-5 )
140
138
self ._atol = self ._integrator_kwargs .get ("atol" , 1e-5 )
141
-
142
- self ._step_duration = 10
139
+ #self.max_episode_time = time_interval.evo_time # maximum time for an episode
140
+ #self.max_steps = time_interval.n_tslots # maximum number of steps in an episode
141
+ self ._step_duration = 2
142
+
143
143
# create the solver
144
144
if self ._Hd_lst [0 ].issuper :
145
145
self ._fid_type = self ._alg_kwargs .get ("fid_type" , "TRACEDIFF" )
@@ -169,25 +169,26 @@ def _infid(self, args):
169
169
X = self ._solver .run (
170
170
self ._state , [0.0 , self ._step_duration ], args = args
171
171
).final_state
172
- self ._state = X
172
+ # self._state = X
173
173
174
- target_dm = qt .ket2dm (self ._target )
175
- if self ._fid_type == "TRACEDIFF" :
176
- diff = X - target_dm
177
- # to prevent if/else in qobj.dag() and qobj.tr()
178
- diff_dag = qt .Qobj (diff .data .adjoint (), dims = diff .dims )
179
- g = 1 / 2 * (diff_dag * diff ).data .trace ()
180
- infid = np .real (self ._norm_fac * g )
174
+ # target_dm = qt.ket2dm(self._target)
175
+ # if self._fid_type == "TRACEDIFF":
176
+ # diff = X - target_dm
177
+ # # to prevent if/else in qobj.dag() and qobj.tr()
178
+ # diff_dag = qt.Qobj(diff.data.adjoint(), dims=diff.dims)
179
+ # g = 1 / 2 * (diff_dag * diff).data.trace()
180
+ # infid = np.real(self._norm_fac * g)
181
+ # else:
182
+ # g = self._norm_fac * self._target.overlap(X)
183
+ # if self._fid_type == "PSU": # f_PSU (drop global phase)
184
+ # infid = 1 - np.abs(g)
185
+ # elif self._fid_type == "SU": # f_SU (incl global phase)
186
+ # infid = 1 - np.real(g)
187
+ if self ._target .isket :
188
+ return 1 - qt .fidelity (self ._target , X )
181
189
else :
182
- g = self ._norm_fac * self ._target .overlap (X )
183
- if self ._fid_type == "PSU" : # f_PSU (drop global phase)
184
- infid = 1 - np .abs (g )
185
- elif self ._fid_type == "SU" : # f_SU (incl global phase)
186
- infid = 1 - np .real (g )
187
- return infid
190
+ return 1 - qt .fidelity (qt .ket2dm (self ._target ), X )
188
191
189
-
190
- "Initialize a first population"
191
192
def initial_population (self ):
192
193
"""Randomly generates an initial popuation to act as generation 0.
193
194
@@ -312,50 +313,58 @@ def mutate(self, population):
312
313
mutated_population = np .copy (population )
313
314
# for some reason clipped normal noise works better.
314
315
# mutated_population[row_indices, col_indices] = np.random.uniform(-1,1,size=number_of_mutations) # for uniform random mutation
315
- mutated_population [row_indices , col_indices ] = np .clip (population [row_indices , col_indices ] + np .random .normal (loc = 0 , scale = 0.2 , size = number_of_mutations ), a_min = - 1 , a_max = 1 ) # for normal mutation
316
+ mutated_population [row_indices , col_indices ] = np .clip (
317
+ population [row_indices , col_indices ] + np .random .normal (loc = 0 , scale = 0.2 , size = number_of_mutations ), a_min = - 1 , a_max = 1 )
316
318
return mutated_population
317
319
318
320
319
321
def optimize (self , iterations = 1000 ):
320
- population = self .initial_population ()
321
- best_fitness = - np .inf
322
- best_chromosome = None
323
- fitness_history = []
324
-
325
- for count in range (iterations ):
326
- print (f"Generation { count + 1 } /{ iterations } " )
327
-
322
+ population = self .initial_population () # initialise a random seroth generation
323
+ bench = - 1e10 # the initial benchmark fitness - we assume fitness >= 0
324
+ done = False # terminal flag
325
+ count = 0 # iteration count
326
+ maxfit_hist = [] # to record the maximum fitness at each gen during optimizing
327
+ while not done :
328
328
fitness_ls = []
329
329
for chromosome in population :
330
+ # The fitness function should be able to take a raw chromosome (whose constituent values will be \in [-1, 1])
331
+ # scale the values to get the true parameter values, then use those parameters to do "stuff" (in our case
332
+ # build a control pulse and simulate the dynamics) then return a figure of merit which tells us how good
333
+ # this set of parameters has performed (could be fidelity to a target state for example).
334
+ # essentially all of the problem under consideration happens inside fitness_func
330
335
alphas = [
331
336
((chromosome [i ] + 1 ) / 2 * (self ._ubound [i ] - self ._lbound [i ]) + self ._lbound [i ])
332
337
for i in range (len (chromosome ))
333
338
]
334
- args = {f"alpha{ i + 1 } " : float (val ) for i , val in enumerate (alphas )} # force float
335
- infid_val = self ._infid (args )
336
- fitness_ls .append (infid_val )
337
-
339
+ args = {f"alpha{ i + 1 } " : float (val ) for i , val in enumerate (alphas )}
340
+ f = 1 - self ._infid (args )
341
+ fitness_ls .append (f )
338
342
fitness = np .array (fitness_ls )
339
- max_fit = np .min (fitness )
340
- fitness_history .append (max_fit )
341
-
342
- if max_fit > best_fitness :
343
- best_fitness = max_fit
344
- best_index = np .argmax (fitness )
345
- best_chromosome = population [best_index , :]
346
-
347
- print (f" Best infidelity: { - max_fit :.6f} , Avg fitness: { np .mean (fitness ):.6f} " )
348
-
349
- survivors , survivor_fitness = self .darwin (population , fitness )
343
+ max_fit = np .max (fitness )
344
+ if max_fit > bench :
345
+ print (" EPISODE: {}, average cost: {}, Best Cost: {}" .format (count , np .round (np .average (fitness ), decimals = 4 ),
346
+ np .max (fitness )))
347
+ # update benchmark and add max fitness to history
348
+ bench = max_fit
349
+ maxfit_hist .append (max_fit )
350
+ best_index = np .argmax (fitness ) # get best index
351
+ best_chromosome = population [best_index ,:] # so that we can get the best chromosome
352
+
353
+ # May want to checkpoint here
354
+ if count == iterations : # if max iterations reached
355
+ done = True
356
+
357
+ survivors , survivor_fitness = self .darwin (population ,fitness )
350
358
mothers , fathers = self .pairing (survivors , survivor_fitness )
351
359
offspring = self .mating_procedure (ma = mothers , da = fathers )
352
- unmutated_next_gen = self .build_next_gen (survivors , offspring )
360
+ unmutated_next_gen = self .build_next_gen (survivors ,offspring )
353
361
mutated_next_gen = self .mutate (unmutated_next_gen )
354
362
population = mutated_next_gen
363
+ count += 1
364
+
365
+ return max_fit , best_chromosome , maxfit_hist
355
366
356
- return best_fitness , best_chromosome , fitness_history
357
367
358
-
359
368
def _save_result (self ):
360
369
"""
361
370
Save the results of the optimization process, including the optimized
0 commit comments