def _fetchResult(token, taskId): """ Fetch the result files from the taskId """ params = {"token": token, "taskId": taskId} ret = invokeBackend("task/getTaskInfo", params) result = ret["result"] originUrl = result["originUrl"] # originSize = result["originSize"] try: originFile, downSize = _downloadToFile(originUrl, f"./Output/{taskId}.origin.json") except Exception: # TODO split the disk write error raise Error.NetworkError(traceback.format_exc()) if outputInfo: print(f"Download result success {originFile} size = {downSize}") measureUrl = result["measureUrl"] # measureSize = result["measureSize"] try: measureFile, downSize = _downloadToFile(measureUrl, f"./Output/{taskId}.measure.json") except Exception: # TODO split the disk write error raise Error.NetworkError(traceback.format_exc()) if outputInfo: print(f"Download result success {measureFile} size = {downSize}") # pprint(result) return originFile, measureFile
def _contract1_2(matrix_parking, matrix_floating, up_or_down, left_or_right): """ Matrix_parking is like 1010. The first half of the indicators are outputs, and the second half are inputs. """ if matrix_floating.shape != (2, 2): raise Error.ParamError("Floating gate not a 1-qubit gate") if matrix_parking.shape == (4, 4): matrix_parking = numpy.reshape(matrix_parking, [2, 2, 2, 2]) elif matrix_parking.shape != (2, 2, 2, 2): raise Error.ParamError("Parking gate not a 2-qubit gate") if left_or_right == 0: if up_or_down == 0: new_array = numpy.einsum("abcd,de->abce", matrix_parking, matrix_floating) elif up_or_down == 1: new_array = numpy.einsum("abcd,ce->abed", matrix_parking, matrix_floating) elif left_or_right == 1: if up_or_down == 0: new_array = numpy.einsum("eb,abcd->aecd", matrix_floating, matrix_parking) elif up_or_down == 1: new_array = numpy.einsum("ea,abcd->ebcd", matrix_floating, matrix_parking) return numpy.reshape(new_array, [4, 4])
def _contract1_1(matrix_parking, matrix_floating): """ The first indicator of the gate is the output, and the latter indicator is the input, just like writing matrix vector multiplication, U{ket0} """ if matrix_floating.shape != (2, 2): raise Error.ParamError("Floating gate not a 1-qubit gate") if matrix_parking.shape != (2, 2): raise Error.ParamError("Parking gate not a 1-qubit gate") new_array = numpy.einsum( 'ab,bc->ac', matrix_parking, matrix_floating ) # v is a vector, A B is a matrix, we must count ABv, here we count AB return new_array
def convertToProcedure(self, name, env): """ Convert to sub-procedure Self env will be destroyed Example: procedure0 = env.makeProcedure('procedure0') :param name: name of sub procedure(not allow duplication) :param env: env of sub procedure :return: QuantumProcedure """ procedure = QuantumProcedure(name, self.params, self.Q, self.circuit) # insert it into quantum programming environment if env.procedureMap.get(name) != None: raise Error.ParamError(f'same procedure name "{name}"') env.procedureMap[name] = procedure # self destroy self.__dict__.clear() return procedure
def initState_1_0(matrixType, n): """ Generate an n-qubit state """ if matrixType == MatrixType.Dense: return initStateDense_1_0(n) else: raise Error.RuntimeError('Not implemented')
def invokeBackend(target, params): """ Invoke the Backend Functions """ try: ret = requests.post( f"{quantumHubAddr}/{target}", json=params).json() except Exception: raise Error.NetworkError(traceback.format_exc()) if ret["error"] > 0: errCode = ret["error"] errMsg = ret["message"] if errCode == 401: raise Error.TokenError(errMsg) else: raise Error.LogicError(errMsg) return ret["data"]
def _waitTask(token, taskId, fetchMeasure=False, downloadResult=True): """ Wait for a task from the taskId """ task = { "token": token, "taskId": taskId } stepStatus = "waiting" while True: try: time.sleep(pollInterval) ret = invokeBackend("task/checkTask", task) if ret["status"] in ("success", "failed", "manual_term"): if outputInfo: print(f"status changed {stepStatus} => {ret['status']}") stepStatus = ret["status"] result = {"status": ret["status"]} if ret["status"] == "success" and "originUrl" in ret.get("result", {}): if downloadResult: originFile, measureFile = _fetchResult(token, taskId) result["origin"] = originFile if fetchMeasure: result["counts"] = _fetchMeasureResult(taskId) else: result["measure"] = measureFile break elif ret["status"] == "failed": result = ret["reason"] break elif ret["status"] == "manual_term": break else: # go on loop # pprint(ret) pass else: if ret["status"] == stepStatus: continue if outputInfo: print(f"status changed {stepStatus} => {ret['status']}") stepStatus = ret["status"] except Error.Error as err: raise err except Exception: raise Error.RuntimeError(traceback.format_exc()) return result
def gate_from_circuitLine(circuitLine): # do not support sparse if circuitLine.HasField('rotationGate'): if circuitLine.rotationGate != RotationGateEnum.U: raise Error.ParamError( f'unsupported operation {RotationGateEnum.Name(circuitLine.rotationGate)}') uGate = U(*circuitLine.paramValues) matrix = uGate.matrix return matrix if circuitLine.HasField('fixedGate'): operationDict = {} operationDict[FixedGateEnum.X] = X.matrix operationDict[FixedGateEnum.Y] = Y.matrix operationDict[FixedGateEnum.Z] = Z.matrix operationDict[FixedGateEnum.H] = H.matrix operationDict[FixedGateEnum.CX] = CX.matrix.reshape(2, 2, 2, 2) matrix = operationDict.get(circuitLine.fixedGate) if matrix is None: raise Error.ParamError(f'unsupported operation {FixedGateEnum.Name(circuitLine.fixedGate)}') return matrix if circuitLine.HasField('customizedGate'): return _protobufMatrixToNumpyMatrix(circuitLine.customizedGate.matrix)
def loadGates(matrixType, operationDict): """ Load the matrix of the gate """ if matrixType == MatrixType.Dense: operationDict[FixedGateEnum.X] = X.matrix operationDict[FixedGateEnum.Y] = Y.matrix operationDict[FixedGateEnum.Z] = Z.matrix operationDict[FixedGateEnum.H] = H.matrix operationDict[FixedGateEnum.CX] = CX.matrix.reshape(2, 2, 2, 2) else: raise Error.RuntimeError('Not implemented')
def __init__(self, matrixType, algorithm, measureMethod): """ To choose the algorithms by the parameters. """ if measureMethod == MeasureMethod.Probability: if matrixType == MatrixType.Dense: self.proc = self.measureDenseByProbability else: raise Error.RuntimeError('Not implemented') else: self.Transfer = TransferProcessor(matrixType, algorithm) self.proc = self.measureBySingleAccumulation
def runSimulator(args, program): """ Initialization process """ parser = argparse.ArgumentParser() parser.add_argument('-mt', default='dense', type=str) parser.add_argument('-a', default='matmul', type=str) parser.add_argument('-mm', default='probability', type=str) parser.add_argument('-s', default=None, type=int) parser.add_argument('-shots', default=None, type=int) parser.add_argument('-inputFile', default=None, type=str) args = parser.parse_args(args=args) matrixType = args.mt.lower() algorithm = args.a.lower() measureMethod = args.mm.lower() seed = args.s shots = args.shots inputFile = args.inputFile if matrixType == 'dense': matrixType = MatrixType.Dense else: raise Error.ParamError(f'Invalid MatrixType {matrixType}') if algorithm == 'matmul': algorithm = Algorithm.Matmul elif algorithm == 'einsum': algorithm = Algorithm.Einsum else: raise Error.ParamError(f'Invalid Algorithm {algorithm}') if measureMethod == 'probability': measureMethod = MeasureMethod.Probability elif measureMethod == 'accumulation': measureMethod = MeasureMethod.Accumulation else: raise Error.ParamError(f'Invalid MeasureMethod {measureMethod}') if seed is not None: if isinstance(seed, int): if seed < 0 or seed > 2147483647: raise Error.ParamError(f'Invalid Seed {seed}') else: raise Error.ParamError(f'Invalid Seed {seed}') if shots <= 0: raise Error.ParamError(f'invalid shots {shots}') if inputFile is not None: with open(inputFile, "rt") as fObj: jsonStr = fObj.read() program = Parse(jsonStr, Program()) return core(program, matrixType, algorithm, measureMethod, shots, seed)
def commit(self, shots, **kwargs): """ Switch local/cloud commitment by prefix of backend name Example: env.commit(1024) env.commit(1024, fetchMeasure=True) env.commit(1024, downloadResult=False) :param shots: experiment counts :param fetchMeasure: named param, default is False, means 'Extract data from measurement results', downloadResult must be True :param downloadResult: named param, default is True, means 'Download experiment results from the server' :return: local or cloud commit result Successful: {status: 'success', origin: resultFilePath, measure: measureDict} # fetchMeasure=True {status: 'success', origin: resultFilePath, counts: 1024} # fetchMeasure=False {status: 'success'} # downloadResult=False failed: {status: 'error', reason: ''} {status: 'failed', reason: ''} """ if self.backendName.startswith('local_'): return self._localCommit(shots, **kwargs) elif self.backendName.startswith('cloud_'): return self._cloudCommit(shots, **kwargs) elif self.backendName.startswith('agent_'): return self._agentCommit(shots, **kwargs) else: raise Error.ParamError( f"invalid backendName => {self.backendName}")
def _getSTSToken(): """ Get the token to upload the file :return: """ if not Define.hubToken: raise Error.ParamError("please provide a valid token") config = invokeBackend("circuit/genSTS", {"token": Define.hubToken}) bosClient = BosClient( BceClientConfiguration( credentials=BceCredentials( str( config['accessKeyId']), str( config['secretAccessKey'])), endpoint='http://bd.bcebos.com', security_token=str( config['sessionToken']))) return [Define.hubToken, bosClient, config['dest']]
def _unrollProcedure(self, circuit, qRegsMap, paramValues): # fill in the circuit for circuitLine in circuit: if circuitLine.HasField('fixedGate'): # fixed gate fixedGateClass = getattr( FixedGate, FixedGateEnum.Name( circuitLine.fixedGate)) # get gate class qRegList = [] for qReg in circuitLine.qRegs: # quantum register lists qRegList.append(qRegsMap[qReg]) self._circuitOut.append(fixedGateClass._toPB(*qRegList)) elif circuitLine.HasField('rotationGate'): # rotation gate rotationGateClass = getattr( RotationGate, RotationGateEnum.Name( circuitLine.rotationGate)) # get gate class qRegList = [] for qReg in circuitLine.qRegs: # quantum register lists qRegList.append(qRegsMap[qReg]) params = [] if len(circuitLine.paramIds) > 0: for index, paramId in enumerate( circuitLine.paramIds): # check procedure params if paramId == -1: params.append( circuitLine.paramValues[index]) # from angles else: params.append( paramValues[paramId]) # from procedure params else: params = circuitLine.paramValues self._circuitOut.append( rotationGateClass(*params)._toPB(*qRegList)) elif circuitLine.HasField('customizedGate'): # customized gate raise Error.ParamError('unsupported operation customizedGate') # todo it is not implemented elif circuitLine.HasField('procedureName'): # procedure qProcedureRegsMap = { index: qRegsMap[qReg] for index, qReg in enumerate(circuitLine.qRegs) } paramIdsLen = len(circuitLine.paramIds) paramValuesLen = len(circuitLine.paramValues) procedureParamLen = paramIdsLen if paramIdsLen > paramValuesLen else paramValuesLen procedureParamValues = [None] * procedureParamLen for i in range(procedureParamLen): if i < paramIdsLen: paramId = circuitLine.paramIds[i] if paramId != -1: procedureParamValues[i] = paramValues[paramId] continue procedureParamValues[i] = circuitLine.paramValues[i] procedure = self._procedureMap[circuitLine.procedureName] self._unrollProcedure(procedure.circuit, qProcedureRegsMap, procedureParamValues) elif circuitLine.HasField('measure'): # measure if circuitLine.measure.type == PBMeasure.Type.Z: # only Z measure is supported pass else: # unsupported measure types raise Error.ParamError( f'unsupported operation measure {PBMeasure.Type.Name(circuitLine.measure.type)}' ) qRegList = [] for qReg in circuitLine.qRegs: # quantum register list qRegList.append(qRegsMap[qReg]) self._circuitOut.append( MeasureZ._toPB(qRegList, circuitLine.measure.cRegs)) elif circuitLine.HasField('barrier'): # barrier qRegList = [] for qReg in circuitLine.qRegs: # quantum register list qRegList.append(qRegsMap[qReg]) self._circuitOut.append(Barrier()._toPB(*qRegList)) else: # unsupported operation raise Error.ParamError('unsupported operation')
def _agentCommit(self, shots, **kwargs): """ Agent commitment :return: task result """ if noLocalTask is not None: raise Error.RuntimeError( 'Agent tasks are not allowed in the online environment') try: self.publish() # circuit in Protobuf format # import the agent plugin according to the backend name module = _loadPythonModule(f'QCompute.Agent.{self.backendName}') if module is None: raise Error.ParamError( f"invalid agent backend => {self.backendName}") simulatorClass = getattr(module, 'Backend') # configure the parameters agent = simulatorClass() agent.program = self.program agent.shots = shots agent.backendParam = self.backendParam # execution agent.commit() # wrap taskResult output = agent.result.output if agent.result.code != 0: return {"status": "error", "reason": output} if agent.result.counts: cRegCount = max(self.program.head.usingCRegs) + 1 agent.result.counts = _formatMeasure(agent.result.counts, cRegCount) originFd, originFn = tempfile.mkstemp(suffix=".json", prefix="local.", dir="./Output") with os.fdopen(originFd, "wt") as fObj: fObj.write(output) taskResult = {"status": "success", "origin": originFn} if kwargs.get("fetchMeasure", False): taskResult["counts"] = agent.result.counts else: measureFd, measureFn = tempfile.mkstemp(suffix=".json", prefix="local.", dir="./Output") with os.fdopen(measureFd, "wt") as fObj: fObj.write(json.dumps(agent.result.counts)) taskResult["measure"] = measureFn return taskResult return {"status": "failed", "reason": output} except Exception: raise Error.RuntimeError(traceback.format_exc())
def core(program, matrixType, algorithm, measureMethod, shots, seed): """ Simulaton process Check if the argument is available. The accepted ones are: 1)DENSE-EINSUM-SINGLE 2)DENSE-EINSUM-PROB 3)DENSE-MATMUL-SINGLE 4)DENSE-MATMUL-PROB """ compositeGate = CompositeGate() program = compositeGate(program) unrollProcedure = UnrollProcedure() program = unrollProcedure(program) unrollCircuit = UnrollCircuit() program = unrollCircuit( program ) # must unrollProcedure before, because of paramIds to paramValues if doCompressGate: compressGate = CompressGate() program = compressGate(program) qRegsMap = { qReg: index for index, qReg in enumerate(program.head.usingQRegs) } qRegCount = len(qRegsMap) operationDict = {} loadGates(matrixType, operationDict) if seed is None: seed = numpy.random.randint(0, 2147483647 + 1) numpy.random.seed(seed) state = initState_1_0(matrixType, qRegCount) transfer = TransferProcessor(matrixType, algorithm) measurer = Measurer(matrixType, algorithm, measureMethod) # collect the result to simulator for the subsequent invoking result = QuantumResult() result.startTimeUtc = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f') measured = False counts = {} measuredQRegsToCRegsBidict = bidict() for circuitLine in program.body.circuit: # Traverse the circuit if measured and not circuitLine.HasField('measure'): raise Error.ParamError('measure must be the last operation') qRegs = [] for qReg in circuitLine.qRegs: qRegs.append(qRegsMap[qReg]) if circuitLine.HasField('fixedGate'): # fixed gate matrix = operationDict.get(circuitLine.fixedGate) if matrix is None: raise Error.ParamError( f'unsupported operation {FixedGateEnum.Name(circuitLine.fixedGate)}' ) state = transfer(state, matrix, qRegs) elif circuitLine.HasField('rotationGate'): # rotation gate if circuitLine.rotationGate != RotationGateEnum.U: raise Error.ParamError( f'unsupported operation {RotationGateEnum.Name(circuitLine.rotationGate)}' ) uGate = U(*circuitLine.paramValues) if matrixType == MatrixType.Dense: matrix = uGate.matrix else: raise Error.RuntimeError('Not implemented') state = transfer(state, matrix, qRegs) elif circuitLine.HasField('customizedGate'): # customized gate if matrixType == MatrixType.Dense: matrix = _protobufMatrixToNumpyMatrix( circuitLine.customizedGate.matrix) else: raise Error.RuntimeError('Not implemented') state = transfer(state, matrix, qRegs) elif circuitLine.HasField('procedureName'): # procedure Error.ParamError( 'unsupported operation procedure, please flatten by UnrollProcedureModule' ) # it is not implemented, flattened by UnrollProcedureModule elif circuitLine.HasField('measure'): # measure if circuitLine.measure.type != PBMeasure.Type.Z: # only Z measure is supported raise Error.ParamError( f'unsupported operation measure {PBMeasure.Type.Name(circuitLine.measure.type)}' ) if not measured: counts = measurer(state, shots) measured = True cRegs = [] for cReg in circuitLine.measure.cRegs: cRegs.append(cReg) for i in range(len(cRegs)): if measuredQRegsToCRegsBidict.get(qRegs[i]) is not None: raise Error.ParamError('measure must be once on a QReg') if measuredQRegsToCRegsBidict.inverse.get( cRegs[i]) is not None: raise Error.ParamError('measure must be once on a CReg') measuredQRegsToCRegsBidict[qRegs[i]] = cRegs[i] elif circuitLine.HasField('barrier'): # barrier pass # unimplemented operation else: # unsupported operation raise Error.ParamError('unsupported operation') measuredCRegsList = list(measuredQRegsToCRegsBidict.keys()) result.endTimeUtc = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f') result.shots = shots result.counts = _filterMeasure(counts, measuredCRegsList) result.seed = int(seed) return result
def _compress(self, circuitOut, circuitIn, QRegs): """ Compress the circuit. Contract all one-qubit gates into two-qubit gates, and output a circuit with only two-qubit gates. :param circuitOut: Output circuit. :param circuitIn: Input circuit. :param circuitMap: A 'note' of circuitlines, classify the circuitlines by qRegs. A dict with lists as values. In the list, different circuitlines are stored according to the sequence, and they are marked with different types. str: 'Start' or 'End' mark. int: the index of a one-qubit gate dict: marking a two-qubit gate list: inrelavent operations, and will output directly into circuitOut. :param circuitIn_copy: A list constructed by copying circuitlines from circuitIn. When a one-qubit gate is contracted into another gate, its position in circuitIn_copy will be None. The other position will be a CustomizedGate. """ QRegs = list(QRegs) n = len(QRegs) circuitMap = dict(zip(QRegs, n*[['Start']])) for num, circuitLine in enumerate(circuitIn): # separate operations in circuitIn and note them in circuitMap if circuitLine.HasField('rotationGate') or circuitLine.HasField('fixedGate'): if len(circuitLine.qRegs) == 2: qregs = circuitLine.qRegs for i in range(2): twobit_gate = {} twobit_gate['num'] = num twobit_gate['up_or_down'] = i list_tmp = list(circuitMap[qregs[i]]) list_tmp.append(twobit_gate) circuitMap[qregs[i]] = list_tmp elif len(circuitLine.qRegs) == 1: qregs = circuitLine.qRegs[0] list_tmp = list(circuitMap[qregs]) list_tmp.append(num) circuitMap[qregs] = list_tmp elif circuitLine.HasField('measure'): qregs = circuitLine.qRegs for qreg in qregs: list_tmp = list(circuitMap[qreg]) list_tmp.append([num]) circuitMap[qreg] = list_tmp elif circuitLine.HasField('barrier'): pass else: raise Error.ParamError('unsupported operation at compress') for key, value in circuitMap.items(): value.append("End") circuitIn_copy = [] # Storage of circuitLines. Copy from circuitIn, revise and then output. for circuitLine in circuitIn: circuitIn_copy.append(circuitLine) for key, value in circuitMap.items(): # separate operations in circuitMap and deal with them for tag_num, tag in enumerate(value): if type(tag) is int: # This is a one-qubit gate. Check the next one. If str occurs, check backward. tag_next = value[tag_num+1] if type(tag_next) is int: # The next one is a one-qubit gate. floating = gate_from_circuitLine(circuitIn_copy[tag]) parking = gate_from_circuitLine(circuitIn_copy[tag_next]) new_gate_matrix = _contract1_1(matrix_floating=floating, matrix_parking=parking) new_circuitline = CustomizedGate(new_gate_matrix)._toPB(*circuitIn[tag_next].qRegs) circuitIn_copy[tag_next] = new_circuitline circuitIn_copy[tag] = None circuitMap[key][tag_num] = None pass elif type(tag_next) is dict: # The next one is a two-qubit gate. floating = gate_from_circuitLine( circuitIn_copy[tag]) # Floating describes a gate to be absorbed. parking = gate_from_circuitLine( circuitIn_copy[tag_next['num']]) # Parking describes a gate that will stay there. new_gate_matrix = _contract1_2(matrix_floating=floating, matrix_parking=parking, left_or_right=0, up_or_down=tag_next['up_or_down']) new_circuitline = CustomizedGate(new_gate_matrix)._toPB(*circuitIn_copy[tag_next['num']].qRegs) circuitIn_copy[tag_next['num']] = new_circuitline circuitIn_copy[tag] = None circuitMap[key][tag_num] = None pass elif type(tag_next) is str or type(tag_next) is list: # Blocked. Check backward. tag_bef_num = tag_num - 1 while tag_bef_num >= 0: # Check backward as checking forward, until the very beginning # if not interrupted. ('Start', when tag_bef_num==0). tag_bef = value[tag_bef_num] if tag_bef is None: # No gate here pass elif type(tag_bef) is dict: floating = gate_from_circuitLine(circuitIn_copy[tag]) parking = gate_from_circuitLine(circuitIn_copy[tag_bef['num']]) new_gate_matrix = _contract1_2(matrix_floating=floating, matrix_parking=parking, left_or_right=1, up_or_down=tag_bef['up_or_down']) new_circuitline = CustomizedGate(new_gate_matrix)._toPB(*circuitIn_copy[tag_bef['num']].qRegs) circuitIn_copy[tag_bef['num']] = new_circuitline circuitIn_copy[tag] = None circuitMap[key][tag_num] = None break elif type(tag_bef) is str or list: break else: raise Error.NetworkError('Wrong compression of gate') tag_bef_num -= 1 else: raise Error.NetworkError('Wrong construction of circuitMap') elif type(tag) is dict or str or list: pass else: raise Error.NetworkError('Wrong construction of circuitMap') for circuitLine in circuitIn_copy: # get the compressed gates and other circuitLines if circuitLine is not None: circuitOut.append(circuitLine) return circuitOut