diff --git a/Autotune/autotune.py b/Autotune/autotune.py new file mode 100644 index 0000000..68895cd --- /dev/null +++ b/Autotune/autotune.py @@ -0,0 +1,621 @@ +# -*- coding: utf-8 -*- +""" +Created on Wed Sep 30 16:26:21 2020 + + +@author: Jules +""" +import numpy as np +# import pandas as pd +import os +import time +from collections import namedtuple + +#from . import logger +import re +from pyEPR.ansys import Optimetrics +from pyEPR import DistributedAnalysis, QuantumAnalysis + +# Bayesian optimization +from bayes_opt import BayesianOptimization, UtilityFunction +from bayes_opt import SequentialDomainReductionTransformer + +### TODO : Creer les classes transmon, memory, purcell, readout, etc +# class Transmon(): +# def __init__(self, freq, Q, Ker, line,...): +# self.freq = freq + +### TODO : compléter les doc + +### TODO : ask raph if we should use another timestamp function +### Mettre 0 devant le chiffre si jamais il n'y a qu'un chiffre +def _timestamp_name(name): + secondsSinceEpoch = time.time() + timeObj = time.localtime(secondsSinceEpoch) + timestamp = '%d%d%d_%d%d%d' % (timeObj.tm_year,timeObj.tm_mon,timeObj.tm_mday, + timeObj.tm_hour, timeObj.tm_min, timeObj.tm_sec) + return timestamp+'_'+name + + +class Autotune(): + """ + Class made to autotune a chip + See autotune_test_optimizatoin for an example of use + Steps : + 1) Create a HFSS design. Make sure to add a line on each resonator you + wish to tune. Make sure to run the HFSS setup once. + + 2) Inquire the project_info but add the resonators too: + pinfo.resonators['resonator1'] = {'line' : 'line_res1'} + + If you just want to get HFSS modes, follow steps a. + If you want to autotune your chip, follow steps b. + + 3.a) Inquire target_Qs, target_freqs... to tell which hfss modes + you wish to get. (see init for syntax) + + 4.a) Inquire var_name and x that will contain the names of the HFSS + variables that you wish to set to the value x. + + 5.a) Launch auto = Autotune(pinfo, target_freqs, target_Qs, + target_kers, target_chis, var_name) and do : + + auto.get_sorted_modes(x) + + 3.b) Inquire target_Qs, target_freqs... to tell which hfss modes + you wish to optimize. (see init for syntax) + + 4.b) Inquire var_name that will contain the names of the variables that + will be tuned. Try to limit yourself to 4 variables max because + optimizing more than 4 parameters if very long and unprecise. + You should probably optimize your parameters 3 by 3. + + 5.b) Launch auto = Autotune(pinfo, target_freqs, target_Qs, + target_kers, target_chis, var_name) + + 6.b) Inquire bounds that will set the bounds for the variables and + make sure that bounds.keys are the same as var_name. + + 7.b) Do auto.optimize(bounds) + + Tips ! + When various functinos are used, he Autotune library might contain some + methods and variables that might be usefull. Here is a non exhaustive list + of them : + - self.epr_hfss = DsitributedAnalysis + - self.epr = QuantumAnalysis + - self.project_info = ProjectInfo + - self.unsorted_FQK + - self.sorted_FQK + - self.var_list + - self.var_name + + """ + + + + ### TODO il y a toujours un bug qui fait que si on a pas lancé de variation + ### avant sur HFSS directement et bah epr_hfss n'a pas de fields + + def __init__(self, project_info, target_freqs, target_Qs, target_kers, + target_chis, var_name, + weight_freqs = None, weight_Qs = None, weight_kers = None, + weight_chis = None, cost_fn = None, cost_fn_args = None ): + ''' + + Args : + ------------------------------------------------------------ + ##### project_info : class from pyEPR.project_info ##### + project_info = ProjectInfo("D:/Jules/HFSS_Jules/jules_project", + project_name='jules_project', + design_name='autotune', + setup_name='Setup1') + + Then define the junctions, resonators and ports : + transmon, memory, purcell and readout in this case + + project_info.junctions['transmon'] = {'rect' : 'ind_JJind_rect', + 'line' : 'ind_JJ_junction_line', + 'Lj_variable' : 'Lj'} + + pinfo.resonators['memory'] = {'line' : 'mem_line'} + pinfo.resonators['readout'] = {'line' : 'ro_line'} + pinfo.resonators['purcell'] = {'line' : 'pur_line'} + + pinfo.ports['port50ohm'] = {'rect' :'port_50_purcell_track', + 'line': 'port_50_purcell_line', + 'R': 50} + + ProjectInfo should be well initialized before calling Autotune() + + + ##### target_freqs, target_Qs, target_kers, target_chis : dict ##### + target_xxx are 4 dicts containg the values that you want to obtain. + The keys must be the same strings as those used in pinfo.junctions + and pinfo.resonators. The keys of target_chis must be tuples of str. + If you don't want to tune any chis for example, please set target_chis + to an empty dict. + target_freqs = {'transmon' : 5500, + 'readout' : 6500, + 'purcell' : 6500, + 'memory' : 8000} + + target_Qs = {'readout' : 30000, + 'purcell' : 300} + + target_kers = {'transmon' : 200} + + target_chis = {('transmon', 'memory') : 2, + ('transmon', 'readout') : 0.2} + + ##### var_name : list of str ##### + It is the list of stringsof HFSS variable names that you wish to tune. + Make sure that the strings are written the same as in the HFSS design + + var_name = ['Lj', 'trm_length', 'capa_mem_length', 'capa_ro_length', + 'shift_capa_mem_trm_Y', 'coupling_dist', 'connector_coupling', + 'tune_ro'] + + ##### weight_freqs, weight_Qs, weight_kers, weight_chis : dict ##### + Optional, see target_xxx for details. + Weights are used when calculated the cost + weight_xxx must have the same keys as target_xxx. + weight_freqs = {'readout' : 1, 'purcell' : 1, 'memory' : 3} + weight_Qs = target_Qs = {'readout' : 2, 'purcell' : 2} + weight_kers = {} + weight_chis = {} + + ##### cost_fn and cost_fn_args : function and whatever you want ##### + Optional + If you want your own cost function, it must take 3 arguments : + sorted_FQK (return of sort_modes()), var_list (list of strings such + as '0', '1', ...) and args which can contain anything. + If they are None, the method used is the mean_square. + + + + ''' + ### TODO : isinstance !!!!!! + assert(isinstance(target_freqs, dict)) + + self.project_info = project_info + + + Target = namedtuple('Target', ['freqs', 'Qs', 'kers', 'chis']) + self.target = Target(freqs = target_freqs, + Qs = target_Qs, + kers = target_kers, + chis = target_chis) + + + Weight = namedtuple('Weight', ['freqs', 'Qs', 'kers', 'chis']) + self.weight = Weight(freqs = weight_freqs, + Qs = weight_Qs, + kers = weight_kers, + chis = weight_chis) + + + self.var_name = var_name + # Get all HFSS variable (usefull to have the units) + self._all_var = self.project_info.design.get_variables() + # Cool way to add the matching units to the HFSS variables + self._units = [re.match('.*[0-9]([a-zA-Z]*)$', + self._all_var[key]).group(1) for key in self.var_name] + + + # Create a directory path to save the parametrics.txt + design_name = self.project_info.design_name + project_path = self.project_info.project_path + self._dir_path = project_path +'/parametrics_'+ design_name + os.makedirs(self._dir_path, exist_ok = True) + + + self.cost_fn = cost_fn if cost_fn else mean_square + self.cost_fn_args = (self.target, + self.weight, cost_fn_args) if cost_fn_args else (self.target, + self.weight) + + # self._unsorted_Qs = pd.DataFrame() + # self._unsorted_freqs = pd.DataFrame() + # self._unsorted_chis = + + + + + + def run_HFSS(self, x) : + ''' + This function runs the HFSS variations that correspond to the x + array. It uses the ansys.Optimetrics package that requires a + licence (I think). + + Args : + x (ndarray) of size (n_variations, n_variables) containing + the values of the HFSS variables for each variation + + Returns : + Nothing + ''' + if len(x.shape) == 1 : + x = np.array([x]) + # Number of variations sent to HFSS + self._nvar = x.shape[0] + + # Create a unique partametric_name + parametric_name = _timestamp_name('parametric') + print(parametric_name) + + # Create a file .txt with the correct shape to import to HFSS + # This file contains the var_names, the values of x and their units. + # It is stored in self._dir_path + f = open(self._dir_path + '/' + parametric_name +'.txt', 'wt') + for i in range(len(self.var_name)) : + f.write(self.var_name[i] + '; ') + f.write('\n') + for i in range(x.shape[0]): + for j in range(x.shape[1]): + f.write('{}{}; '.format(str(x[i,j]), self._units[j])) + f.write('\n') + f.close() + + # Load the Optimetrics module from the ansys package + opti=Optimetrics(self.project_info.design) + + # Import the parametric setup with the list of variations + # This function is not pushed on the master branch yet (by manu) + opti.import_setup(parametric_name, self._dir_path+"\%s.txt"%parametric_name, + calc_enable= False) + + # Solve the variation, will do 'analyze setup' + opti.solve_setup(parametric_name) + print('#####') + print('HFSS passes are done') + print('#####') + + + def getFQK(self): + ''' + This function is post processing the HFSS data and it gets the + frequencies, Qs and chi matrix for all variations. + + Returns : + A namedtuple tuple of 3 panda.DataFrame results with the + frequencies, the Qs and the chi matrix. Each DataFrame contains + the results of every variation. + To Use it : + FQK = a.getFQK + a.FQK.freqs[var][mode] + + where mode is the number of the mode + where var is a string such as '0', '1', .. + (var are the HFSS variation string, you will find them in + self.var_list) + + you can also get the Qs and the chi matrix + a.FQK.Qs[var][mode] + a.FQK.chis[var][mode] + + Remark : + The chis are returned in an OrderedDict whose values are + pandas DataFrames + + ''' + ### Load the results of the HFSS simulation + self.epr_hfss = DistributedAnalysis(self.project_info) + nvar = self._nvar + #Get only the last variations + self.var_list = self.epr_hfss.variations[-nvar:] + + if self.target.chis or self.target.kers: # There is at least 1 junction + print('There should be at least one junction') + self.epr_hfss.do_EPR_analysis(self.var_list) + self.epr = QuantumAnalysis(self.epr_hfss.data_filename,self.var_list) + self.epr.analyze_all_variations(self.var_list) + + ###Get the Chis + unsorted_chis = self.epr.results.get_chi_O1() + ### Get the frequencies after the quantum analysis + unsorted_freqs = self.epr.get_frequencies() + ### Get the Qs, if there are no losses, Q = inf + unsorted_Qs = self.epr.Qs + + + else : # There is no junction + print('There must be no junction') + self.project_info.options.calc_U_E = False + self.epr_hfss.do_EPR_analysis(self.var_list) + ### Get the freqs and Qs without the quantum analysis (from HFSS) + data = self.epr_hfss.get_ansys_frequencies_all().T[self.var_list].T + + unsorted_freqs = data['Freq. (GHz)']*1000 + unsorted_freqs.name = 'Freq. (MHz)' + unsorted_Qs = data ['Quality Factor'] + unsorted_chis = {} + + + Unsorted = namedtuple('Unsorted', ['freqs', 'Qs', 'chis']) + self.unsorted_FQK = Unsorted(freqs = unsorted_freqs, + Qs = unsorted_Qs, + chis = unsorted_chis) + + print('#####') + print('The modes frequencies, Qs, Kers, and Chis are calculated but') + print('unsorted yet. You will find them in the namedtuple : self.unsorted_FQK') + print('Units : MHz for everything') + return self.unsorted_FQK + ### TODO : verifier que si des trucs sont vides ca sort quand meme bien !!! + + def sort_modes(self, freqs = None, Qs = None, chis = None) : + ''' + This function is sorting the modes from HFSS. It uses the fact that + each resonators should have a line to claculate their participation + matrixes. + Args : + freqs panda.DataFrame : should be the result returned by + QuantumAnalysis.get_frequencies() + + Qs panda.DataFrame : should be the result returned by + QuantumAnalysis.Qs + + chis collections.OrderedDict : should be the result returned by + QuantumAnalysis.get_chi_01() + + + Returns : + A namedtuple tuple of 3 panda.DataFrame results with the + the sorted results of every variation. + To Use it : + sorted_FQK = a.sort_modes + + a.sorted_FQK.freqs[var][mode] + + where mode is the number of the mode + where var is a string such as '0', '1', .. + (var are the HFSS variation string, you will find them in + self.var_list) + + You can also get the sorted Qs, kers and chis + a.sorted_FQK.Qs[var][mode] + a.sorted_FQK.kers[var][mode] + a.sorted_FQK.chis[var][mode] + + ''' + if freqs is None : + freqs = self.unsorted_FQK.freqs + if Qs is None : + Qs = self.unsorted_FQK.Qs + if chis is None : + chis = self.unsorted_FQK.chis + + + self._sorted_freqs = {} + self._sorted_Qs = {} + self._sorted_kers = {} + self._sorted_chis = {} + + # For each variation, sorting the modes = matching the index of the mode + # with the name of the mode given in self.target_freqs, sel.target_Qs... + for var in self.var_list : + # Initialization of the dicts + self._sorted_freqs[var] = {} + self._sorted_Qs[var] = {} + self._sorted_kers[var] = {} + self._sorted_chis[var] = {} + + ### Participation matrix of the resonators + Pm_res = self.epr_hfss.results[var]['Pr'] + + ### TODO Ces if/else pourraient être évités si on avait récupéré les matrices + # de participation dans 'getFQK' + if self.target.chis or self.target.kers: # There is at least 1 junction + ### Participation matrix of the transmons + Pm_trm = self.epr_hfss.results[var]['Pm'] + # Sorting transmon and resonators modes : finding the highest + # participation matrix coefficient to have the index of the mode. + if not Pm_res.empty : + mode_idxs = Pm_res.idxmax(axis=0).append(Pm_trm.idxmax(axis=0)) + else : + # There is no resonator (only transmons) + mode_idxs = Pm_trm.idxmax(axis=0) + + else : + assert not Pm_res.empty, 'you have no transmon neither resonator!' + mode_idxs = Pm_res.idxmax(axis=0) + + # Assign the sorted modes in the sorted dicts + for mode_name in self.target.freqs : + idx = mode_idxs[mode_name] + self._sorted_freqs[var][mode_name] = freqs[var][idx] + + for mode_name in self.target.Qs : + idx = mode_idxs[mode_name] + self._sorted_Qs[var][mode_name] = Qs[var][idx] + + for mode_name in self.target.kers : + idx = mode_idxs[mode_name] + self._sorted_kers[var][mode_name] = chis[var][idx][idx] + + for mode_name in self.target.chis : + idx1 = mode_idxs[mode_name[0]] + idx2 = mode_idxs[mode_name[1]] + self._sorted_chis[var][(mode_name[0], + mode_name[1])] = chis[var][idx1][idx2] + + Sorted = namedtuple('Sorted', ['freqs', 'Qs', 'kers', 'chis']) + self.sorted_FQK = Sorted(freqs = self._sorted_freqs, + Qs = self._sorted_Qs, + kers = self._sorted_kers, + chis = self._sorted_chis) + print('#####') + print('The modes frequencies, Qs, Kers, and Chis are now sorted!') + print('You will find them in the namedtuple : self.sorted_FQK') + print('Units : MHz for everything') + return self.sorted_FQK + + def get_sorted_modes(self, x) : + ''' + This function just runs 3 functions : run_HFSS, getFQK, sort_modes + Args : + see getFQK + Returns : + see sort_modes + ''' + self.run_HFSS(x) + self.getFQK() + return self.sort_modes() + + + def cost_function(self, x) : + """ This is the cost_function that you want to optimize + Args : + x : see run_HFSS + + Returns : + the cost that should be 0 when your chip is optimimized. + (when the FQK from HFSS are the same as the FQK from + target) + + + """ + self.get_sorted_modes(x) + + cost = self.cost_fn(self.sorted_FQK, self.var_list, self.cost_fn_args) + return(cost) + + + + + + + def optimize(self, bounds, cost_function = None, niter = 20, + method = 'Bayesian'): + """ + This function should optimize your chip + + Parameters + ---------- + bounds : dict + The keys must be the same strings as the HFSS variables contained + in var_name. The values are tuples (x,y) where x is the min bound + associated to the key and y is the max bound. + + cost_function : function, optional + The default is a mean square method. You should better change + cost_fn instead of cost_function (see __init__) + + niter : int, optional + Number of passes. The default is 20. I should probably find another + criteria such as the tolerance. + + method : string, optional + Optimization method, for the moment, there is only the + Bayesian Optimization. + + Returns + ------- + A dictionnary containing as keys : + target : the best cost + params : a dict containing the parameters that gave this cost + """ + + + if method == 'Bayesian' : + def bayesian_cost(**kwargs): + x = np.array([kwargs[name] for name in self.var_name ]) + return -self.cost_function(x) + + + bounds_transformer = SequentialDomainReductionTransformer() + self.optimizer = BayesianOptimization(f = bayesian_cost, pbounds=bounds) + + + self.optimizer.maximize(int(niter/3), int(2*niter/3)) + + self.x0 = self.optimizer.max + + return self.x0 + + # Work in progress for parrallelizing the optimization + + # guess = {} + # for key, val in bounds.items() : + # guess[key] = (val[1]-val[0])/3 + + # self.suggested_points = [] + # self.targets = [] + # next_point = [] + + + # for i in range(niter) : + # utility = UtilityFunction(kind="ucb", kappa = 2.576, xi=0.0) + + # next_point = optimizer.suggest(utility) + # self.suggested_points.append(next_point) + # self.targets.append(bayesian_cost(**next_point)) + + # optimizer.register(next_point, self.targets[-1]) + # if i == 0 : + # optimizer.register(guess, bayesian_cost(**guess)) + + # next_point = [] + + # return(targets, suggested_points) + + + +def mean_square(sorted_FQK, var_list, args): + ''' + This functioun calculates the mean square betwenn the targets and the + result of sort_modes. It can be weighted. + Args : + ##### sorted_FQK : namedtuple ##### + See sort_modes for description + + ##### var_list ##### + list of strings + + ##### args ##### + tuple of argumenents. This function only supports 2-tuples containg + target an weight (2 namedtuples) + + Returns : + The cost (mean square) should be 0 when the x parameters are tuned so + that the target matches the sorted_FQK/ + + + ''' + target = args[0] + weight = args[1] + costs = [] + ### TODO C'est degeu ce if else + if weight.freqs == weight.Qs == weight.kers == weight.chis == None : + for var in var_list : + cost = 0 + for i in range(4) : + for key in target[i].keys() : + cost += ((target[i][key] - sorted_FQK[i][var][key])/target[i][key])**2 + costs.append(cost) + else : + for var in var_list : + cost = 0 + for i in range(4) : + for key in target[i].keys() : + cost += weight[i][key]*((target[i][key] - sorted_FQK[i][var][key])/target[i][key])**2 + costs.append(cost) + + if len(costs) == 1 : + return costs[0] + else : + return costs + + + + + + + + + + + + \ No newline at end of file diff --git a/Autotune/autotune_test_design.py b/Autotune/autotune_test_design.py new file mode 100644 index 0000000..3a7515a --- /dev/null +++ b/Autotune/autotune_test_design.py @@ -0,0 +1,138 @@ +""" +Created on Tue Sep 1 11:19:53 2020 + +@author: jules + +""" + +from HFSSdrawpy.core.modeler import Modeler +from HFSSdrawpy.core.body import Body +from drawpylib.parameters import TRACK, GAP, RLC, MESH, DEFAULT +import drawpylib.cpw_elements as elt + + + +pm = Modeler('gds') +relative = pm.set_variable('1mm') +chip1 = Body(pm, 'chip1') + +chip_width= pm.set_variable("3500um") +chip_length=pm.set_variable("3500um") +chip_thickness= pm.set_variable("280um") +pcb_thickness= pm.set_variable("320um") +vaccuum_thickness= pm.set_variable('700um') +fillet = pm.set_variable('0mm') + +track = pm.set_variable('25um') +gap = pm.set_variable('15um') + +delta = pm.set_variable('250um') +cpl_ro_trm = pm.set_variable('150um') +Xmon_gnd = pm.set_variable('5um') +Xmon_width = 2*Xmon_gnd + 4*gap + 3*track +width_J = pm.set_variable('5um') +gnd_cpl_50ohm = pm.set_variable('5um') + +lines_lenght = pm.set_variable('30um') +port_len = pm.set_variable('450um') + + +# Tunable parameters in the example +tune_mem = pm.set_variable('1000um') +tune_ro = pm.set_variable('2000um') +Lj = pm.set_variable('10nH') +cpl_ro_50ohm = pm.set_variable('200um') +trm_length = pm.set_variable('500um') +cpl_mem_trm = pm.set_variable('200um') + + + + + + +#Memory +with chip1([chip_width/10, tune_mem], [0,-1]): + first_port1, = elt.draw_end_cable(chip1, track, gap, typeEnd='open', + name = 'first_port1') +with chip1([2*chip_width/10, chip_length-delta], [-1,0]): + first_cst_port1, = elt.create_port(chip1, widths=None, name = 'first_cst_port1') + +with chip1([4*chip_width/10, delta], [-1,0]): + second_cst_port1, = elt.create_port(chip1, widths=None, name = 'second_cst_port1') + +with chip1([5*chip_width/10, chip_length/3], [0,1]): + Xmon_end_mem, = elt.draw_Xmon_end_cable(chip1, track, gap, Xmon_width, cpl_mem_trm , + name = 'Xmon_end_mem') + # memory line + with chip1([-track ,0],[-1,0]): + points =[('0um', '0um'), (lines_lenght, '0um')] + mem_line = chip1.polyline(points, closed=False, + name='mem_line', layer = DEFAULT) + # Transmon + with chip1([3*gap + track + Xmon_gnd , 0], [-1,0]): + port_trm1, = elt.draw_end_cable(chip1, track, gap, typeEnd='open', + name = 'port_trm1') + with chip1([-trm_length/2, track/2 + gap/2], [0,1]): + ind1 = elt.draw_ind_inline(chip1, track, gap, gap, width_J, 2*Lj, + name= 'ind_JJ1')#, premesh=True): + with chip1([-trm_length/2, -track/2 - gap/2], [0,1]): + ind2 = elt.draw_ind_inline(chip1, track, gap, gap, width_J,2*Lj, + name= 'ind_JJ2')#, premesh=True): + with chip1([-trm_length, 0], [-1,0]): + port_trm2, = elt.draw_end_cable(chip1, track, gap, typeEnd='open', + name = 'port_trm2') + + with chip1([-(trm_length + 3*gap + track + Xmon_gnd), 0], [1,0]): + Xmon_end_ro, = elt.draw_Xmon_end_cable(chip1, track, gap, Xmon_width, + cpl_ro_trm ,name = 'Xmon_end_ro') + # readout line + with chip1([-track ,0],[-1,0]): + points =[('0um', '0um'), (lines_lenght, '0um')] + ro_line = chip1.polyline(points, closed=False, + name='ro_line', layer = DEFAULT) +# Readout +with chip1([6*chip_width/10, chip_length-delta], [1,0]): + second_cst_port2, = elt.create_port(chip1, widths=None, + name = 'second_cst_port2') +with chip1([8*chip_width/10, delta], [1,0]): + first_cst_port2, = elt.create_port(chip1, widths=None, + name = 'first_cst_port2') +with chip1([9*chip_width/10, tune_ro], [0,1]): + capa_cpl, = elt.draw_Xmon_end_cable(chip1, track, gap, Xmon_width, cpl_ro_50ohm , + name = 'capa_cpl') + + # 50 ohm port + with chip1([3*gap + track + gnd_cpl_50ohm, 0], [-1,0]): + first_50port, = elt.draw_end_cable(chip1, track, gap, typeEnd='open', + name = 'first_50port') + with chip1([port_len, 0], [1,0]): + Rport, = elt.draw_end_cable(chip1, track, gap, typeEnd='RLC', R='50ohm', + name = 'Rport') + + +chip1.draw_cable(first_port1, first_cst_port1, second_cst_port1, Xmon_end_mem, + name = 'mem', is_bond = False) +chip1.draw_cable(port_trm1, port_trm2, name = 'trm', is_bond = False) +chip1.draw_cable(capa_cpl, first_cst_port2, second_cst_port2, Xmon_end_ro, + name = 'ro', is_bond = False) +chip1.draw_cable(first_50port, Rport, name = 'res_port', is_bond = False) + + + +ground_plane = chip1.rect([0, 0], [chip_width, chip_length], layer=TRACK) +ground_plane.subtract(chip1.entities[GAP]) +ground_plane.unite(chip1.entities[TRACK]) +ground_plane.assign_perfect_E() + +#chip substrate +chip1.box([0,0,-chip_thickness],[chip_width, chip_length, chip_thickness], + material='silicon', name='substrate') +chip1.box([0,0,-chip_thickness],[chip_width, chip_length, -pcb_thickness], + material='Rogers TMM 10i (tm)', name='pcb') +chip1.box([0,0,0],[chip_width, chip_length, vaccuum_thickness], name='vaccuum') + + +path = r'D:/Jules/Spice project/klayout' +pm.generate_gds(path, 'mem_trm_ro_50') + + diff --git a/Autotune/autotune_test_optimization.py b/Autotune/autotune_test_optimization.py new file mode 100644 index 0000000..25355a9 --- /dev/null +++ b/Autotune/autotune_test_optimization.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Oct 5 15:24:30 2020 + +@author: Jules +""" + +from autotune import Autotune +from pyEPR.project_info import ProjectInfo +import numpy as np + + + + +##### 1. Inquire the project info +pinfo = ProjectInfo("D:/Jules/HFSS_Jules/Spice_project", + project_name='Spice_project', + design_name='autotune_test_design', + setup_name='Setup1') + +# The names used here ('transmon',..) must be exactly the same as thoses in +# targ_freqs etc.... +pinfo.junctions['transmon'] = {'rect' : 'ind_JJ1ind_rect', + 'line' : 'ind_JJ1_junction_line', + 'Lj_variable' : 'Lj'} + +# The names used here ('memory',..) must be exactly the same as thoses in +# target_freqs etc... +pinfo.resonators['memory'] = {'line' : 'mem_line'} +pinfo.resonators['readout'] = {'line' : 'ro_line'} + + + +##### 2. Inquire the targets you wish to obtain (units : MHZ) + +# Must be the same names as in the pinfo.resonators and pinfo.junctions + +target_freqs = {'transmon' : 5000, 'memory' : 8000, 'readout' : 7500, } +target_Qs = {'readout' : 6000} +target_kers = {'transmon' : 200} +target_chis = {('transmon', 'memory') : 2} + +# Eventually inquire the weights : + # weight_freqs = {'transmon' : 2, 'memory' : 1, 'readout' : 3} + # weight_Qs = target_Qs = {'readout' : 2, 'purcell' : 2} + # weight_kers = {'transmon' : 2} + # weight_chis = {('transmon', 'memory') : 1} + + +##### 3. Inquire the name of the HFSS variable that will be changed +var_name = ['tune_mem', 'tune_ro', 'Lj', + 'cpl_ro_50ohm', 'trm_length', 'cpl_mem_trm'] + +# If you want to test the functions, you can set an x array to change +# manually the value of the variables : +x = np.array([[950, 2000, 11, 200, 450, 220]]) + +##### 4. Launch Autotune ! +auto = Autotune(pinfo, target_freqs, target_Qs, + target_kers, target_chis, var_name) + + +##### 5. Try to get sorted modes : +# auto.get_sorted_modes(x) + +##### 6. Optimize ! +# Correct syntax of bounds for a bayesian optimization +# The name must be the same as the var_name +pbounds = {'tune_mem' : (300, 2700), 'tune_ro' : (650, 2800), 'Lj' : (5,20), + 'cpl_ro_50ohm' : (100, 400), 'trm_length' : (300, 600), + 'cpl_mem_trm' : (100,400)} +# auto.optimize(pbounds) + + + + diff --git a/pyEPR/ansys.py b/pyEPR/ansys.py index 8ded32b..5c35bd1 100644 --- a/pyEPR/ansys.py +++ b/pyEPR/ansys.py @@ -1691,6 +1691,78 @@ def solve_setup(self, setup_name: str): """ return self._optimetrics.SolveSetup(setup_name) + def import_setup(self,parametric_name,array_path, savefields=True, CopyMesh=False,calc_enable=True): + self._optimetrics.ImportSetup("OptiParametric", + [ + "NAME:"+parametric_name, + array_path + ]) + + self._optimetrics.EditSetup(parametric_name, + [ + "NAME:"+ parametric_name, + "IsEnabled:=" , True, + [ + "NAME:ProdOptiSetupDataV2", + "SaveFields:=" , savefields, + "CopyMesh:=" , CopyMesh + ] + ]) + + self._optimetrics.EditSetup(parametric_name, + [ + "NAME:"+ parametric_name, + "IsEnabled:=" , True, + [ + "NAME:ProdOptiSetupDataV2", + "SaveFields:=" , savefields, + "CopyMesh:=" , CopyMesh + ] + ]) + + if calc_enable: + + self._optimetrics.EditSetup(parametric_name, + [ + "NAME:"+ parametric_name, + "IsEnabled:=" , True, + + [ + "NAME:Goals", + [ + "NAME:Goal", + "ReportType:=" , "Fields", + "Solution:=" , "Setup1 : LastAdaptive", + [ + "NAME:SimValueContext" + ], + "Calculation:=" , "calc_energy_electric", + "Name:=" , "calc_energy_electric", + [ + "NAME:Ranges", + "Range:=" , [ "Var:=" , "Phase", "Type:=" , "d", "DiscreteValues:=" , "0deg"] + ] + ], + [ + "NAME:Goal", + "ReportType:=" , "Fields", + "Solution:=" , "Setup1 : LastAdaptive", + [ + "NAME:SimValueContext" + ], + "Calculation:=" , "calc_energy_magnetic", + "Name:=" , "calc_energy_magnetic", + [ + "NAME:Ranges", + "Range:=" , [ "Var:=" , "Phase", "Type:=" , "d", "DiscreteValues:=" , "0deg"] + ] + ] + ] + ]) + + + + def create_setup(self, variable, swp_params, name="ParametricSetup1", swp_type='linear_step', setup_name=None, save_fields=True, copy_mesh=True, solve_with_copied_mesh_only=True,