Source code for qiskit_experiments.library.driven_freq_tuning.coefficients
# This code is part of Qiskit.## (C) Copyright IBM 2024.## This code is licensed under the Apache License, Version 2.0. You may# obtain a copy of this license in the LICENSE.txt file in the root directory# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.## Any modifications or derivative works of this code must retain this# copyright notice, and modified files need to carry a notice indicating# that they have been altered from the originals."""Coefficients characterizing Stark shift."""from__future__importannotationsfromtypingimportAnyimportnumpyasnpfromqiskit.providers.backendimportBackendfromqiskit_ibm_experiment.serviceimportIBMExperimentServicefromqiskit_ibm_experiment.exceptionsimportIBMApiErrorfromqiskit_experiments.framework.jsonimportExperimentDecoderfromqiskit_experiments.framework.backend_dataimportBackendDatafromqiskit_experiments.framework.experiment_dataimportExperimentData
[docs]classStarkCoefficients:"""A collection of coefficients characterizing Stark shift."""def__init__(self,pos_coef_o1:float,pos_coef_o2:float,pos_coef_o3:float,neg_coef_o1:float,neg_coef_o2:float,neg_coef_o3:float,offset:float,):"""Create new coefficients object. Args: pos_coef_o1: The first order shift coefficient on positive amplitude. pos_coef_o2: The second order shift coefficient on positive amplitude. pos_coef_o3: The third order shift coefficient on positive amplitude. neg_coef_o1: The first order shift coefficient on negative amplitude. neg_coef_o2: The second order shift coefficient on negative amplitude. neg_coef_o3: The third order shift coefficient on negative amplitude. offset: Offset frequency. """self.pos_coef_o1=pos_coef_o1self.pos_coef_o2=pos_coef_o2self.pos_coef_o3=pos_coef_o3self.neg_coef_o1=neg_coef_o1self.neg_coef_o2=neg_coef_o2self.neg_coef_o3=neg_coef_o3self.offset=offset
[docs]defconvert_freq_to_amp(self,freqs:np.ndarray,)->np.ndarray:"""A helper function to convert Stark frequency to amplitude. Args: freqs: Target frequency shifts to compute required Stark amplitude. Returns: Estimated Stark amplitudes to induce input frequency shifts. Raises: ValueError: When amplitude value cannot be solved. """amplitudes=np.zeros_like(freqs)foridx,freqinenumerate(freqs):shift=freq-self.offsetifnp.isclose(shift,0.0):amplitudes[idx]=0.0continueifshift>0:fit=[*self.positive_coeffs(),-shift]else:fit=[*self.negative_coeffs(),-shift]amp_candidates=np.roots(fit)# Because the fit function is third order, we get three solutions here.criteria=np.all([# Frequency shift and tone have the same sign by definitionnp.sign(amp_candidates.real)==np.sign(shift),# Tone amplitude is a real valuenp.isclose(amp_candidates.imag,0.0),# The absolute value of tone amplitude must be less than 1.0 + 10 mpnp.abs(amp_candidates.real)<1.0+10*np.finfo(float).eps,],axis=0,)valid_amps=amp_candidates[criteria]iflen(valid_amps)==0:raiseValueError(f"Stark shift at frequency value of {freq} Hz is not available.")iflen(valid_amps)>1:# We assume a monotonic trend but sometimes a large third-order term causes# inflection point and inverts the trend in larger amplitudes.# In this case we would have more than one solution, but we can# take the smallest amplitude before reaching to the inflection point.before_inflection=np.argmin(np.abs(valid_amps.real))valid_amp=float(valid_amps[before_inflection].real)else:valid_amp=float(valid_amps[0].real)amplitudes[idx]=min(valid_amp,1.0)returnamplitudes
[docs]defconvert_amp_to_freq(self,amps:np.ndarray,)->np.ndarray:"""A helper function to convert Stark amplitude to frequency shift. Args: amps: Amplitude values to convert into frequency shift. Returns: Calculated frequency shift at given Stark amplitude. """pos_fit=np.poly1d([*self.positive_coeffs(),self.offset])neg_fit=np.poly1d([*self.negative_coeffs(),self.offset])returnnp.where(amps>0,pos_fit(amps),neg_fit(amps))
[docs]deffind_min_max_frequency(self,min_amp:float,max_amp:float,)->tuple[float,float]:"""A helper function to estimate maximum frequency shift within given amplitude budget. Args: min_amp: Minimum Stark amplitude. max_amp: Maximum Stark amplitude. Returns: Minimum and maximum frequency shift available within the amplitude range. """trim_amps=[]forampin[min_amp,max_amp]:ifamp>0:fit=self.positive_coeffs()else:fit=self.negative_coeffs()# Solve for inflection points by computing the point where derivative becomes zero.solutions=np.roots([deriv*coeffforderiv,coeffinzip((3,2,1),fit)])inflection_points=solutions[(solutions.imag==0)&(np.sign(solutions)==np.sign(amp))]iflen(inflection_points)>0:# When multiple inflection points are found, use the most outer one.# There could be a small inflection point around amp=0,# when the first order term is significant.amp=min([amp,max(inflection_points,key=abs)],key=abs)trim_amps.append(amp)returntuple(self.convert_amp_to_freq(np.asarray(trim_amps)))
def__str__(self):# Short representation for dataframereturn"StarkCoefficients(...)"def__eq__(self,other):returnall([self.pos_coef_o1==other.pos_coef_o1,self.pos_coef_o2==other.pos_coef_o2,self.pos_coef_o3==other.pos_coef_o3,self.neg_coef_o1==other.neg_coef_o1,self.neg_coef_o2==other.neg_coef_o2,self.neg_coef_o3==other.neg_coef_o3,self.offset==other.offset,])def__json_encode__(self)->dict[str,Any]:return{"class":"StarkCoefficients","data":{"pos_coef_o1":self.pos_coef_o1,"pos_coef_o2":self.pos_coef_o2,"pos_coef_o3":self.pos_coef_o3,"neg_coef_o1":self.neg_coef_o1,"neg_coef_o2":self.neg_coef_o2,"neg_coef_o3":self.neg_coef_o3,"offset":self.offset,},}@classmethoddef__json_decode__(cls,value:dict[str,Any])->"StarkCoefficients":ifnotvalue.get("class",None)=="StarkCoefficients":raiseValueError("JSON decoded value for StarkCoefficients is not valid class type.")returnStarkCoefficients(**value.get("data",{}))
[docs]defretrieve_coefficients_from_service(service:IBMExperimentService,backend_name:str,qubit:int,)->StarkCoefficients:"""Retrieve StarkCoefficients object from experiment service. Args: service: IBM Experiment service instance interfacing with result database. backend_name: Name of target backend. qubit: Index of qubit. Returns: StarkCoefficients object. Raises: RuntimeError: When stark_coefficients entry doesn't exist in the service. """try:retrieved=service.analysis_results(device_components=[f"Q{qubit}"],result_type="stark_coefficients",backend_name=backend_name,sort_by=["creation_datetime:desc"],json_decoder=ExperimentDecoder,# Returns the latest value only. IBM service returns 10 entries by default.# This could contain old data from previous version, which might not be deserialized.limit=1,)except(IBMApiError,ValueError)asex:raiseRuntimeError(f"Failed to retrieve the result of stark_coefficients: {ex.message}")fromexiflen(retrieved)==0:raiseRuntimeError("Analysis result of stark_coefficients is not found in the ""experiment service. Run and save the result of StarkRamseyXYAmpScan.")result_data_dict=retrieved[0].result_dataif"_value"inresult_data_dict:# In IBM Experiment service, the result_data["value"] returns# a display value for the experiment service webpage.# Original data is stored in "_value".# TODO: this must be handled by experiment service.returnresult_data_dict["_value"]returnresult_data_dict["value"]
[docs]defretrieve_coefficients_from_backend(backend:Backend,qubit:int,)->StarkCoefficients:"""Retrieve StarkCoefficients object from the Qiskit backend. Args: backend: Qiskit backend object. qubit: Index of qubit. Returns: StarkCoefficients object. Raises: RuntimeError: When experiment service cannot be loaded from backend. """name=BackendData(backend).nameservice=ExperimentData.get_service_from_backend(backend)ifserviceisNone:raiseRuntimeError(f"Valid experiment service is not found for the backend {name}.")returnretrieve_coefficients_from_service(service,name,qubit)