set_common_charges

  • full name: tenpy.networks.site.set_common_charges

  • parent module: tenpy.networks.site

  • type: function

tenpy.networks.site.set_common_charges(sites, new_charges='same', new_names=None, new_mod=None, sort_charge=True)[source]

Adjust the charges of the given sites in place such that they can be used together.

Before we can contract operators (and tensors) corresponding to different Site instances, we first need to define the overall conserved charges, i.e., we need to merge the ChargeInfo of them to a single, global chinfo and adjust the charges of the physical legs. That’s what this function does.

A typical place to do this would be in tenpy.models.model.CouplingMPOModel.init_sites().

(This function replaces the now deprecated multi_sites_combine_charges().)

Parameters:
  • sites (list of Site) – The sites to be combined. The sites are modified in place.

  • new_charges ('same' | 'drop' | 'independent' | list of list of tuple) –

    Defines the new, common charges in terms of the old ones.

    list of lists of tuple

    If a list is given, each entry new_charge of the list defines one new charge, i.e. the new number of charges is qnumber=len(new_charges). Each entry new_charge of the outer list is itself a list of 3-tuples, new_charge = [(factor, site_index, old_charge_index), ...]. where the value of the new charge is the sum of factor times the value of the old charge, (specified by the site_index and the old_charge_index within that site), and the sum runs over all entries in that list new_charge. old_charge_index can be an integer (=the index) or a string (=the name) of the charge in the corresponding sites[site_index].leg.chinfo.

    'same'

    defaults to charges with the same name to match, and charges with different names to be independently conserved (see example below); None-set names are considered different.

    'drop'

    Drop/remove all charges, equivalent to new_charges=[].

    'independent'

    For the case that the charges of the different sites are independent and individually conserved, even if they have the same name.

  • new_names (list of str) – Names for each of the new charges. Defaults to name of the first old charge specified.

  • new_mod (list of int) – mod for the new charges, one entry for each list in new_charges. Defaults to the mod of the old charges, if not specified otherwise.

  • sort_charge (bool) – Whether to sort the physical legs by charges.

Returns:

perms – For each site the permutation performed on the physical leg to sort by charges. Only returned if sort_charge is True.

Return type:

list of ndarray

Examples

When we just initialize some sites, they will in general have different charges. For example, we could have a SpinHalfFermionSite a spin-1 SpinSite. For reference, let’s also print the names and values of the charges.

>>> from tenpy.networks.site import *
>>> ferm = SpinHalfFermionSite(cons_N='N', cons_Sz='Sz')
>>> ferm.leg.chinfo.names
['N', '2*Sz']
>>> print(ferm.leg.to_qflat())
[[ 1 -1]
 [ 0  0]
 [ 2  0]
 [ 1  1]]
>>> spin = SpinSite(1.0, conserve='Sz')
>>> spin.leg.chinfo.names
['2*Sz']
>>> print(spin.leg.to_qflat())
[[-2]
 [ 0]
 [ 2]]

With the default new_charges='same', this function will combine charges with the same name, and hence we will have two conserved quantities, namely the fermion particle number 'N' = N_{up_fermions} + N_{down-fermions}, and the total Sz spin '2*Sz' = N_{up-fermions} + N_{up-spins} - N_{down-fermions} - N_{down-spins}. In this case, there will only appear an extra column of zeros for the charges of the spin leg.

>>> set_common_charges([ferm, spin], new_charges='same')
[array([0, 1, 2, 3]), array([0, 1, 2])]
>>> ferm.leg.chinfo.names
['N', '2*Sz']
>>> print(ferm.leg.to_qflat())  # didn't change (except making a copy)
[[ 1 -1]
 [ 0  0]
 [ 2  0]
 [ 1  1]]
>>> spin.leg.chinfo.names   # additional 'N' chargename
['N', '2*Sz']
>>> print(spin.leg.to_qflat())  # additional column of zeros for the 'N' charge
[[ 0 -2]
 [ 0  0]
 [ 0  2]]

With new_charges='independent', we preserve the charges of the old sites individually. In this example, we get 3 conserved quantities, namely the fermion particle number 'N_ferm' = N_{up_fermions} + N_{down-fermions}, and the fermionic Sz spin '2*Sz_ferm' = N_{up-fermions} - N_{down-fermions} and the Sz spin of the spin sites, '2*Sz_spin' = N_{up-spins} - N_{down-spins}. (We give the charges new names for clearer distinction.) Corresponding zero columns are added to the LegCharges.

>>> ferm = SpinHalfFermionSite(cons_N='N', cons_Sz='Sz')
>>> spin = SpinSite(1.0, conserve='Sz')
>>> set_common_charges([ferm, spin], new_charges='independent',
...                    new_names=['N_ferm', '2*Sz_ferm', '2*Sz_spin'])
[array([0, 1, 2, 3]), array([0, 1, 2])]
>>> print(ferm.leg.to_qflat())  # didn't change (except making a copy)
[[ 1 -1  0]
 [ 0  0  0]
 [ 2  0  0]
 [ 1  1  0]]
>>> print(spin.leg.to_qflat())  # additional column of zeros for the 'N' charge
[[ 0  0 -2]
 [ 0  0  0]
 [ 0  0  2]]

With the full specification of the new_charges through a list of list of tuples, you can create new charges as linear combinations of the charges of the individual sites. For example, the SpinHalfFermionSite is essentially the product of two FermionSite, one for the up electrons, and one for the down electrons. The '2*Sz' charge of the SpinHalfFermionSite is then equivalent to the difference of individual particle numbers, '2*Sz' = N_{up} - N_{down}.

>>> f_up = FermionSite(conserve='N')
>>> f_down = FermionSite(conserve='N')
>>> print(f_up.leg.to_qflat())
[[0]
 [1]]
>>> print(f_down.leg.to_qflat())
[[0]
 [1]]
>>> f_down.state_labels
{'empty': 0, 'full': 1}
>>> set_common_charges([f_up, f_down],
...                    new_charges=[[(1, 0, 'N'), ( 1, 1, 'N')],
...                                 [(1, 0, 'N'), (-1, 1, 'N')]],
...                    new_names=['N_tot', '2*Sz=(N_up-N_down)'])
[array([0, 1]), array([1, 0])]
>>> f_down.state_labels  # sorting charges caused permutation of local states
{'empty': 1, 'full': 0}
>>> print(f_up.leg.to_qflat())
[[0 0]
 [1 1]]
>>> print(f_down.leg.to_qflat()) # top row = full, bottom row=empty
[[ 1 -1]
 [ 0  0]]

Another example could be that you have both fermions and bosons, and that you have terms \(c_i c_j b^\dagger_k + c^\dagger_i c^\dagger_j b_k\), where two fermions can merge into a pair forming a boson. In this case, neither the fermion number nor the boson number is preserved individually, but the combination N_{fermions} + 2 * N_{bosons} is preserved.

>>> ferm = FermionSite(conserve='N')
>>> bos = BosonSite(Nmax=3, conserve='N')
>>> set_common_charges([ferm, bos], [[(1, 0, 'N'), (2, 1, 'N')]], ['N_f + 2 N_b'])
[array([0, 1]), array([0, 1, 2, 3])]

The new_charges='drop' or new_charges=[] option is a quick way to remove any charges.

>>> ferm = SpinHalfFermionSite(cons_N='N', cons_Sz='Sz')
>>> spin = SpinSite(1.0, conserve='Sz')
>>> set_common_charges([ferm, spin], new_charges='drop')
[array([0, 1, 2, 3]), array([0, 1, 2])]
>>> assert ferm.leg.chinfo.qnumber == spin.leg.chinfo.qnumber == 0  # trivial: no charges