def prod(v1, v2, base):
    #find tensor product of v1,v2. For use with eg gen_clock_H
    #must be used with full base^N basis,can project after
    dim1 = np.size(v1)
    dim2 = np.size(v2)
    N1 = int(np.log2(dim1))
    N2 = int(np.log2(dim2))
    dim = np.power(base, N1 + N2)

    M = np.outer(v1, v2)
    out = np.zeros(dim, dtype=complex)
    for n in range(0, np.size(M, axis=0)):
        for m in range(0, np.size(M, axis=1)):
            state1 = int_to_bin_base_m(n, base, N1)
            state2 = int_to_bin_base_m(m, base, N2)
            state = np.append(state1, state2)
            ref = int(bin_to_int_base_m(state, base))
            out[ref] = out[ref] + M[n, m]
    return out
for n in range(0, np.size(to_remove_refs, axis=0)):
    print(pxp.basis[pxp.keys[to_remove_refs[n]]])

#redo basis
pxp.basis_refs_new = np.zeros(
    np.size(pxp.basis_refs) - np.size(to_remove_refs))
c = 0
for n in range(0, np.size(pxp.basis_refs, axis=0)):
    if pxp.basis_refs[n] not in to_remove_refs:
        pxp.basis_refs_new[c] = pxp.basis_refs[n]
        c = c + 1
pxp.basis_refs = pxp.basis_refs_new

pxp.basis = np.zeros((np.size(pxp.basis_refs), pxp.N))
for n in range(0, np.size(pxp.basis_refs)):
    pxp.basis[n] = int_to_bin_base_m(pxp.basis_refs[n], pxp.base, pxp.N)
pxp.keys = dict()
for n in range(0, np.size(pxp.basis_refs)):
    pxp.keys[int(pxp.basis_refs[n])] = n

# pxp_syms = model_sym_data(pxp,[translational(pxp),parity(pxp),])
# pxp_syms = model_sym_data(pxp,[translational(pxp)])
# # pxp_syms = model_sym_data(pxp,[parity(pxp)])

H = spin_Hamiltonian(pxp, "x")
H.gen()
H.sector.find_eig()
e, u = H.sector.eigvalues(), H.sector.eigvectors()
# H=H.sector.matrix()

# e,u = np.linalg.eigh(H)
 def __init__(self,ref,system):
     self.system = system
     self.ref = ref
     self.bits = int_to_bin_base_m(self.ref,self.system.base,self.system.N)
     self.key = system.keys[self.ref]
def perm_inv_key(ref):
    bits = int_to_bin_base_m(ref,int(N/2)+1,2)
    return bits
    def update_H_pos_sweep(self,
                           state,
                           i_index,
                           block_references,
                           block_keys,
                           op_sizes,
                           k_vec=None):
        new_refs_coef = dict(
        )  #for storing all refs and there coef from looping positions
        for position in range(0, self.system.N):
            #indices operators act on (ie check pbc and loop around if at edge)

            #periodic site_indices (wrap around chain)
            if self.system.bc == "periodic":
                site_indices = dict()
                for op_index in range(0, np.size(self.model, axis=0)):
                    d = np.size(self.model[op_index])
                    site_indices[op_index] = np.zeros(d)
                    for n in range(0, d):
                        if position + n < self.system.N:
                            site_indices[op_index][n] = position + n
                        else:
                            site_indices[op_index][
                                n] = position + n - self.system.N
            #open (convention: Just throw away all terms that would wrap around chain)
            #eg, nn int x_i x_{i+1}, run to x_{N-2} X_{N-1}
            elif self.system.bc == "open":
                site_indices = dict()
                for op_index in range(0, np.size(self.model, axis=0)):
                    d = np.size(self.model[op_index])
                    if position + d - 1 < self.system.N:
                        site_indices[op_index] = np.zeros(d)
                        for n in range(0, d):
                            site_indices[op_index][n] = position + n
                    else:
                        site_indices[op_index] = None

            #filter these sites for identitys, projectors and operators to act with
            #0 is projector, -1 is identity
            P_indices = dict()
            non_projector_site_indices = dict()
            non_projector_op_indices = dict()

            #throw away keys where site_indices[op_index] = None (OBC)
            op_index_keys = list(site_indices.keys())
            to_del = []
            for n in range(0, np.size(op_index_keys, axis=0)):
                if site_indices[op_index_keys[n]] is None:
                    to_del = np.append(to_del, n)
            for n in range(np.size(to_del, axis=0) - 1, -1, -1):
                op_index_keys = np.delete(op_index_keys, to_del[n], axis=0)

            for op_index in op_index_keys:
                for n in range(0, np.size(self.model[op_index], axis=0)):
                    if self.model[op_index][n] != 0 and self.model[op_index][
                            n] != -1:
                        #init dictionary if empty
                        if op_index not in list(
                                non_projector_site_indices.keys()):
                            non_projector_site_indices[op_index] = np.array(
                                [site_indices[op_index][n]])
                            non_projector_op_indices[op_index] = np.array([n])
                        else:
                            non_projector_site_indices[op_index] = np.append(
                                non_projector_site_indices[op_index],
                                site_indices[op_index][n])
                            non_projector_op_indices[op_index] = np.append(
                                non_projector_op_indices[op_index], n)
                    elif self.model[op_index][n] == 0:
                        if op_index not in list(P_indices.keys()):
                            P_indices[op_index] = np.array(
                                [site_indices[op_index][n]])
                        else:
                            P_indices[op_index] = np.append(
                                P_indices[op_index], site_indices[op_index][n])

            #loop through all ops in sum to get all the product states mapped to + coef (full H space, sym eq state used later)
            for op_index in op_index_keys:
                #check state survives projectors first:
                survives_projectors = 1
                if op_index in list(P_indices.keys()):
                    for n in range(0, np.size(P_indices[op_index], axis=0)):
                        if state[int(P_indices[op_index][n])] != 0:
                            survives_projectors = 0
                            break
                if survives_projectors == 1:
                    site_indices = non_projector_site_indices[op_index].astype(
                        int)

                    #matrix whos rows are the (on site H space) vectors formed when operator acts on site
                    v = np.zeros((np.size(site_indices), self.system.base),
                                 dtype=complex)
                    for n in range(0, np.size(site_indices)):
                        v[n] = self.site_ops[self.model[op_index][int(
                            non_projector_op_indices[op_index][n])]][int(
                                state[int(site_indices[n])])]
                    #seq of tensor products gives all combo of final states due to product of ops Q1 Q2...
                    if np.size(site_indices) > 1:
                        x = prod(v[0], v[1], self.system.base)
                        for n in range(2, np.size(site_indices)):
                            x = prod(x, v[n], self.system.base)
                    else:
                        x = v[0]

                    #put this data in 2d array with rows (coef,[c1,c2,c3])
                    #c1,c2,c3... the new bits after operator acted on state (eg |010>->2*|000>, row=2,0)
                    new_bits = np.zeros(np.size(site_indices), dtype=int)
                    coefficients = []
                    for n in range(0, np.size(x, axis=0)):
                        if np.abs(x[n]) > 0:
                            new_bits = np.vstack(
                                (new_bits,
                                 int_to_bin_base_m(
                                     n, self.system.base,
                                     np.size(site_indices)).astype(int)))
                            coefficients = np.append(coefficients, x[n])
                    new_bits = np.delete(new_bits, 0, axis=0)  #delete init row

                    if np.size(coefficients) != 0:
                        for n in range(0, np.size(new_bits, axis=0)):
                            #form new state bit representation
                            new_state = np.zeros(np.size(state), dtype=int)
                            for m in range(0, np.size(state, axis=0)):
                                if m not in site_indices:
                                    new_state[m] = state[m]
                            if np.size(site_indices) > 1:
                                for m in range(0, np.size(site_indices,
                                                          axis=0)):
                                    new_state[site_indices[m]] = new_bits[n, m]
                            else:
                                new_state[site_indices] = new_bits[n, 0]

                            new_ref = bin_to_int_base_m(
                                new_state, self.system.base)
                            if new_ref not in list(new_refs_coef.keys()):
                                if self.uc_size is False:
                                    new_refs_coef[new_ref] = coefficients[
                                        n] * self.model_coef[op_index]
                                else:
                                    #check position is correct wrt uc size + site
                                    if position % self.uc_size[
                                            op_index] == self.uc_pos[op_index]:
                                        new_refs_coef[new_ref] = coefficients[
                                            n] * self.model_coef[op_index]
                            else:
                                if self.uc_size is False:
                                    new_refs_coef[new_ref] = new_refs_coef[
                                        new_ref] + coefficients[
                                            n] * self.model_coef[op_index]
                                else:
                                    #check position is correct wrt uc size + site
                                    if position % self.uc_size[
                                            op_index] == self.uc_pos[op_index]:
                                        new_refs_coef[new_ref] = new_refs_coef[
                                            new_ref] + coefficients[
                                                n] * self.model_coef[op_index]

        #Use bit maps (refs) mapped from all postions and there coef to update H elements
        all_new_refs = list(new_refs_coef.keys())
        for ref_index in range(0, np.size(all_new_refs, axis=0)):
            if k_vec is None:  #if not using syms, simply find location of new state in basis and update H_ij
                if all_new_refs[ref_index] in self.system.basis_refs:
                    new_ref_index = self.system.keys[all_new_refs[ref_index]]
                    self.sector.update_H_entry(
                        i_index,
                        new_ref_index,
                        new_refs_coef[all_new_refs[ref_index]],
                        dim=np.size(block_references))

            else:  #must lookup locations in symmetry basis, orbit ref states etc if using symmetries
                if all_new_refs[ref_index] in self.system.basis_refs:
                    #location of state in full basis (to extract periodicty, norm data)
                    j_full_index = self.system.keys[all_new_refs[ref_index]]
                    #sym connected ref state
                    new_ref_sym_conn_ref = self.syms.sym_data[j_full_index, 0]

                    if new_ref_sym_conn_ref in block_references:
                        #location of ref state in sym block basis (for index of Hamiltionian)
                        j_ref_index = block_keys[new_ref_sym_conn_ref]

                        i_ref = block_references[i_index]
                        i_ref_full_index = self.system.keys[i_ref]

                        L = self.syms.sym_data[j_full_index, 2:]
                        N_a = self.syms.sym_data[i_ref_full_index, 1]
                        N_b = self.syms.sym_data[j_full_index, 1]

                        element = N_b / N_a * np.exp(
                            1j * 2 * math.pi / self.system.N *
                            np.vdot(k_vec, L))
                        # print(i_index,j_ref_index,np.real(element),new_refs_coef[all_new_refs[ref_index]])
                        self.sector.update_H_entry(
                            i_index,
                            j_ref_index,
                            element * new_refs_coef[all_new_refs[ref_index]],
                            k_vec,
                            dim=np.size(block_references))