def getNode(data): newNode = Node(data) newNode.data = data newNode.left = None newNode.right = None return newNode
def fit(self, dfForDTree, verbose): #INPUT: Training dataset #OUTPUT: Trained Decision Tree # inizio algo per nodo radice returnList = self.findBestSplit(dfForDTree, False) indexChosenAttribute = returnList[0] attributeValue = returnList[1] Dleft = returnList[2] Dright = returnList[3] self.attributeList.append(int(indexChosenAttribute)) root = Node(int(indexChosenAttribute)) numPattern = len(dfForDTree) entropy = self.computeEntropy(dfForDTree) # memorizzo nel nodo l'attributo, il valore e altre info ottenute dallo split nodeInfo = list() nodeInfo.append(attributeValue) nodeInfo.append(numPattern) nodeInfo.append(entropy) root.data = nodeInfo root.left = Node(int(indexChosenAttribute)) root.right = Node(int(indexChosenAttribute)) if (verbose): print('DLEFT & DRIGHT INIZIALMENTE') print(Dleft) print(Dright) if (self.useClustering): # APPLY K-MEDOIDS FOR DLEFT------------------------------------------------------ TsIndexLeft = Dleft['TsIndex'] # TsIndex contenute in Dleft # Retrieve medoids among all candidates CandidatesListLeft = self.OriginalCandidatesListTrain['IdTs'].isin( TsIndexLeft ) # setta a True gli indici dei candidati che sono stati generati dalle Ts contenute in Dleft CandidateToCluster = self.OriginalCandidatesListTrain[ CandidatesListLeft] # estraggo i candidati da OriginalCandidatesListTrain, che sono generati dalle Ts in Dleft CandidateToCluster = CandidateToCluster.reset_index(drop=True) indexChoosenMedoids = reduceNumberCandidates( self, CandidateToCluster, returnOnlyIndex=True ) # indici di OriginalCandidatesListTrain conteneti candidati da mantenere CandidateToCluster = CandidateToCluster.iloc[indexChoosenMedoids] if (verbose): print('CANDIDATI RIMASTI IN FIT') print(CandidateToCluster) # Compute distances btw Ts and chosen medoids Dleft = computeSubSeqDistance(self, TsIndexLeft, CandidateToCluster, self.window_size) # APPLY K-MEDOIDS FOR DRIGHT------------------------------------------------------ TsIndexRight = Dright['TsIndex'] # TsIndex contenute in Dleft # Retrieve medoids among all candidates CandidatesListRight = self.OriginalCandidatesListTrain['IdTs'].isin( TsIndexRight ) # setta a True gli indici dei candidati che sono stati generati dalle Ts contenute in Dright CandidateToCluster = self.OriginalCandidatesListTrain[ CandidatesListRight] # estraggo i candidati da OriginalCandidatesListTrain, che sono generati dalle Ts in Dleft CandidateToCluster = CandidateToCluster.reset_index(drop=True) indexChoosenMedoids = reduceNumberCandidates( self, CandidateToCluster, returnOnlyIndex=True ) # indici di OriginalCandidatesListTrain conteneti candidati da mantenere CandidateToCluster = CandidateToCluster.iloc[indexChoosenMedoids] if (verbose): print('CANDIDATI RIMASTI IN FIT') print(CandidateToCluster) # Compute distances btw Ts and chosen medoids Dright = computeSubSeqDistance(self, TsIndexRight, CandidateToCluster, self.window_size) if (verbose): print('DLEFT & DRIGHT DOPO IL CLUSTERING') print(Dleft) print(Dright) # Recursive call if (len(Dleft) > 0): self.buildTree(root.left, Dleft, 1, verbose) if (len(Dright) > 0): self.buildTree(root.right, Dright, 1, verbose) Tree.Root = root
def sample(self, n_shots: int, seed: Optional[int] = None) -> np.ndarray: """Sample from the final classical distribution. For each sample, will pick a branch for each internal measurement and traverse the simulation tree until the end-of-circuit measurements are reached. The tree caches the state of the simulation for each branch to reuse for later shots. :param n_shots: The number of samples to take :type n_shots: int :param seed: Seed for the random sampling, defaults to None :type seed: Optional[int], optional :return: Shot table with each row corresponding to a shot. Columns correspond to Bits in decreasing lexicographical order, i.e. [0, 1] corresponds to {Bit(0) : 1, Bit(1) : 0} :rtype: np.ndarray """ if seed is not None: np.random.seed(seed) table = np.zeros((n_shots, len(self.bits)), dtype=int) for s in range(n_shots): # Uniformly select a random point from the measurement distribution point = np.random.uniform(0.0, 1.0) # Traverse the tree until we reach the end-of-circuit measurements current_node = self.tree # The range of values `point` could take to end up in the current node current_lower = 0.0 current_upper = 1.0 while not isinstance(current_node.data, CompleteNode): if isinstance(current_node.data, IncompleteNode): # When at an IncompleteNode, there are no future branches already considered # but we still have computation to simulate on this branch # Simulate new gates until either a mid-circuit measurement # or we reach the end of the internal gates while True: try: next_gate = next(current_node.data.gate_iter) except StopIteration: # There are no more internal gates, so cumulative probabilities for # the final measurements of every qubit cum_probs = ( current_node.data.qstate * current_node.data.qstate.conjugate() ).cumsum() current_node.data = CompleteNode( cum_probs, current_node.data.cstate ) break # Skip the gate if the classical condition is not met if next_gate.condition: condition_met = True for b, v in next_gate.condition.items(): bi = self.bits.index(b) if current_node.data.cstate[bi] != v: condition_met = False break if not condition_met: continue if isinstance(next_gate, Rotation): # Apply the rotation to the quantum state and continue to the next gate pauli_tensor = next_gate.qps.to_sparse_matrix(self.qubits) exponent = -0.5 * next_gate.angle current_node.data.qstate = np.cos( exponent ) * current_node.data.qstate + 1j * np.sin( exponent ) * pauli_tensor.dot( current_node.data.qstate ) else: # Otherwise, we have a measurement # Compute the states after a 0-outcome and a 1-outcome and make a branch # Project into measurement subspaces identity = QubitPauliString().to_sparse_matrix(self.qubits) z_op = QubitPauliString( next_gate.qubit, Pauli.Z ).to_sparse_matrix(self.qubits) zero_proj = 0.5 * (identity + z_op) one_proj = 0.5 * (identity - z_op) zero_state = zero_proj.dot(current_node.data.qstate) one_state = one_proj.dot(current_node.data.qstate) # Find probability of measurement and normalise zero_prob = np.vdot(zero_state, zero_state) if zero_prob >= 1e-10: # Prevent divide-by-zero errors zero_state *= 1 / np.sqrt(zero_prob) if 1 - zero_prob >= 1e-10: one_state *= 1 / np.sqrt(1 - zero_prob) # Update the classical state for each outcome bit_index = self.bits.index(next_gate.bit) zero_cstate = copy(current_node.data.cstate) zero_cstate[bit_index] = 0 one_cstate = current_node.data.cstate one_cstate[bit_index] = 1 # Replace current node in the tree by a branch, with each outcome as children zero_node = Node(0) zero_node.data = IncompleteNode( zero_state, zero_cstate, copy(current_node.data.gate_iter), ) one_node = Node(0) one_node.data = IncompleteNode( one_state, one_cstate, current_node.data.gate_iter ) current_node.data = InternalNode(zero_prob) current_node.left = zero_node current_node.right = one_node break else: # Reached an internal measurement, so randomly pick a branch to traverse current_decision = ( current_lower + (current_upper - current_lower) * current_node.data.zero_prob ) if point < current_decision: current_node = current_node.left current_upper = current_decision else: current_node = current_node.right current_lower = current_decision # Finally reached the end of the circuit # Randomly sample from the final measurements index = np.searchsorted( current_node.data.cum_probs, point / (current_upper - current_lower) ) bitstring = bin(index)[2:].zfill(len(self.qubits)) # Update the classical state with final measurement outcomes table[s] = current_node.data.cstate for g in self.end_measures: if g.condition: # If final measurements are conditioned, the classical state may not be updated condition_met = True for b, v in g.condition.items(): bi = self.bits.index(b) if current_node.data.cstate[bi] != v: condition_met = False break if not condition_met: continue qi = self.qubits.index(g.qubit) bi = self.bits.index(g.bit) table[s, bi] = int(bitstring[qi]) return table