Exemple #1
0
    def __init__(self, left_site, right_site=None, left_block=None, right_block=None):
        """Creates the system with the specified sites.

	Exactly that. If you don't provide all the arguments, the missing
	blocks or sites are created from the `left_site` single site
	argument.

	Parameters
	----------
	left_site : a Site object.
	    The site you want to use as a single site at the left.
	right_site : a Site object (optional).
	    The site you want to use as a single site at the right.
	left_block : a Block object (optional).
	    The block you want to use as a single block at the left.
	right_block : a Block object (optional).
	    The block you want to use as a single block at the right.
	"""
    	super(System, self).__init__()
	self.left_site = left_site

	if right_site is not None:
	    self.right_site = right_site
	else:
	    self.right_site = left_site

	if left_block is not None:
	    self.left_block = left_block
	else:
	    self.left_block = make_block_from_site(left_site)

	if right_block is not None:
	    self.right_block = right_block
	else:
	    self.right_block = make_block_from_site(left_site)

	self.h = CompositeOperator(self.get_left_dim(), self.get_right_dim())
	self.operators_to_add_to_block = {}
	self.old_left_blocks = []
	self.old_right_blocks = []
	# 
	# start growing on the left, which may look as random as start
	# growing on the right, but however the latter will ruin the
	# *whole* thing.
	#
	self.set_growing_side('left')
	self.number_of_sites = None
	self.model = None
Exemple #2
0
class System(object):
    """The system class for the DMRG algorithm.

    The system has two blocks, and two single sites, and is the basic
    structure you run the algorithm on. Its main function is to put all
    these things together to avoid having to pass to every time details
    about the underlying chain structure.

    You use this class as a convenience class.

    Examples
    --------
    >>> from dmrg101.core.sites import SpinOneHalfSite
    >>> from dmrg101.core.system import System
    >>> # build a system with four spins one-half.
    >>> spin_one_half_site = SpinOneHalfSite()
    >>> ising_fm_in_field = System(spin_one_half_site)
    >>> # use four strings to name each operator in term
    >>> ising_fm_in_field.add_to_hamiltonian('s_z', 's_z', 'id', 'id')
    >>> ising_fm_in_field.add_to_hamiltonian('id', 's_z', 's_z', 'id')
    >>> ising_fm_in_field.add_to_hamiltonian('id', 'id', 's_z', 's_z')
    >>> # use argument names to save extra typing for some terms
    >>> h = 0.1
    >>> ising_fm_in_field.add_to_hamiltonian(left_block_op='s_z', param=-h)
    >>> ising_fm_in_field.add_to_hamiltonian(left_site_op='s_z', param=-h)
    >>> ising_fm_in_field.add_to_hamiltonian(right_site_op='s_z', param=-h)
    >>> ising_fm_in_field.add_to_hamiltonian(right_block_op='s_z', param=-h)
    >>> gs_energy, gs_wf = ising_fm_in_field.calculate_ground_state()
    >>> print gs_energy
    -0.35
    >>> print gs_wf.as_matrix
    [[ 0.  0.]
    [[ 0.  1.]
    """
    def __init__(self, left_site, right_site=None, left_block=None, right_block=None):
        """Creates the system with the specified sites.

	Exactly that. If you don't provide all the arguments, the missing
	blocks or sites are created from the `left_site` single site
	argument.

	Parameters
	----------
	left_site : a Site object.
	    The site you want to use as a single site at the left.
	right_site : a Site object (optional).
	    The site you want to use as a single site at the right.
	left_block : a Block object (optional).
	    The block you want to use as a single block at the left.
	right_block : a Block object (optional).
	    The block you want to use as a single block at the right.
	"""
    	super(System, self).__init__()
	self.left_site = left_site

	if right_site is not None:
	    self.right_site = right_site
	else:
	    self.right_site = left_site

	if left_block is not None:
	    self.left_block = left_block
	else:
	    self.left_block = make_block_from_site(left_site)

	if right_block is not None:
	    self.right_block = right_block
	else:
	    self.right_block = make_block_from_site(left_site)

	self.h = CompositeOperator(self.get_left_dim(), self.get_right_dim())
	self.operators_to_add_to_block = {}
	self.old_left_blocks = []
	self.old_right_blocks = []
	# 
	# start growing on the left, which may look as random as start
	# growing on the right, but however the latter will ruin the
	# *whole* thing.
	#
	self.set_growing_side('left')
	self.number_of_sites = None
	self.model = None

    def clear_hamiltonian(self):
        """Makes a brand new hamiltonian.
	"""
	self.h = CompositeOperator(self.get_left_dim(), self.get_right_dim())

    def get_left_dim(self):
	"""Gets the dimension of the Hilbert space of the left block
	"""
	return self.left_block.dim * self.left_site.dim
    
    def get_right_dim(self):
	"""Gets the dimension of the Hilbert space of the right block
	"""
	return self.right_block.dim * self.right_site.dim

    def get_shriking_block_next_step_size(self, left_block_size):
	"""Gets the size of the shrinking block in the next DMRG step.

	Gets the size of the shrinking block, i.e. the number of sites
	(not including the single site), in the next step of the finite
	DMRG algorithm.

	Parameters
	----------
	left_block_size : an int.
	    The *current*, i.e. at the current DMRG step, number of sites
	    of the left block (despite the sweep is to the left or the
	    right.) Again this does not include the single site.

	Returns
	-------
	result : a int.
	   The number of sites of the shrinking block, without including
	   the single site.
	"""
	result = None
	if self.growing_side == 'left':
	    result = self.number_of_sites - (left_block_size + 3)
        else:
	    result = left_block_size - 1
	if result < 0:
	   raise DMRGException("Block shrank too much")
	return result

    def set_growing_side(self, growing_side):
	"""Sets which side, left or right, is growing.

	You use this function to change the side which is growing. You
	should set the growing side every time you want to change the
	direction of the sweeps.

	Parameters
	----------
	growing_side : a string.
	    Which side, left or right, is growing.

	Raises
	------
	DMRGException
	    if the `growing_side` is not 'left' or 'right'.
	"""
	if growing_side not in ('left', 'right'):
	    raise DMRGException("Bad growing side")

	self.growing_side = growing_side
	if self.growing_side == 'left':
	    self.growing_site = self.left_site
	    self.growing_block = self.left_block
	    self.shrinking_site = self.right_site
	    self.shrinking_block = self.right_block
	    self.shrinking_side = 'right'
	else:
	    self.growing_site = self.right_site
	    self.growing_block = self.right_block
	    self.shrinking_site = self.left_site
	    self.shrinking_block = self.left_block
	    self.shrinking_side = 'left'

    def add_to_hamiltonian(self, left_block_op='id', left_site_op='id', 
    		           right_site_op='id', right_block_op='id',
			   param=1.0):
	"""Adds a term to the hamiltonian.

	You use this function to add a term to the Hamiltonian of the
	system. This is just a convenience function. 

	Parameters
	----------
	left_block_op : a string (optional).
	    The name of an operator in the left block of the system.
	left_site_op : a string (optional).
	    The name of an operator in the left site of the system.
	right_site_op : a string (optional).
	    The name of an operator in the right site of the system.
	right_block_op : a string (optional).
	    The name of an operator in the right block of the system.
	param : a double/complex (optional).
	    A parameter which multiplies the term.

	Raises
	------
	DMRGException 
	    if any of the operators are not in the corresponding
	    site/block.

	Examples
	--------
        >>> from dmrg101.core.sites import SpinOneHalfSite
        >>> from dmrg101.core.system import System
        >>> # build a system with four spins one-half.
        >>> spin_one_half_site = SpinOneHalfSite()
        >>> ising_fm_in_field = System(spin_one_half_site)
        >>> # use four strings to name each operator in term
        >>> ising_fm_in_field.add_to_hamiltonian('s_z', 's_z', 'id', 'id')
        >>> ising_fm_in_field.add_to_hamiltonian('id', 's_z', 's_z', 'id')
        >>> ising_fm_in_field.add_to_hamiltonian('id', 'id', 's_z', 's_z')
        >>> # use argument names to save extra typing for some terms
        >>> h = 0.1
        >>> ising_fm_in_field.add_to_hamiltonian(left_block_op='s_z', param=-h)
        >>> ising_fm_in_field.add_to_hamiltonian(left_site_op='s_z', param=-h)
        >>> ising_fm_in_field.add_to_hamiltonian(right_site_op='s_z', param=-h)
        >>> ising_fm_in_field.add_to_hamiltonian(right_block_op='s_z', param=-h)
	"""
	left_side_op = make_tensor(self.left_block.operators[left_block_op],
		                   self.left_site.operators[left_site_op])
	right_side_op = make_tensor(self.right_block.operators[right_block_op],
		                    self.right_site.operators[right_site_op])
	self.h.add(left_side_op, right_side_op, param)
	
    def add_to_operators_to_update(self, name, block_op='id', site_op='id'):
	"""Adds a term to the hamiltonian.

	You use this function to add an operator to the list of operators
	that you need to update. You need to update an operator if it is
	going to be part of a term in the Hamiltonian in any later step in
	the current sweep.

	Parameters
	----------
	name : a string.
	    The name of the operator you are including in the list to
	    update.
	left_block_op : a string (optional).
	    The name of an operator in the left block of the system.
	left_site_op : a string (optional).
	    The name of an operator in the left site of the system.

	Raises
	------
	DMRGException 
	    if any of the operators are not in the corresponding
	    site/block.

	Examples
	--------
        >>> from dmrg101.core.sites import SpinOneHalfSite
        >>> from dmrg101.core.system import System
        >>> # build a system with four spins one-half.
        >>> spin_one_half_site = SpinOneHalfSite()
        >>> ising_fm_in_field = System(spin_one_half_site)
        >>> # some stuff here..., but the only operator that you need to
	>>> # update is 's_z' for the last site of the block.
        >>> ising_fm_in_field.add_to_operators_to_update(site_op='s_z')
	>>> print ising_fm_in_field.operators_to_add_to_block.keys()
	('s_z')
	"""
	tmp = make_tensor(self.growing_block.operators[block_op],
		          self.growing_site.operators[site_op])
	self.operators_to_add_to_block[name] = tmp

    def add_to_block_hamiltonian(self, tmp_matrix_for_bh, block_op='id', 
		                 site_op='id', param=1.0):
	"""Adds a term to the hamiltonian.

	You use this function to add a term to the Hamiltonian of the
	system. This is just a convenience function. 

	Parameters
	----------
	tmp_matrix_for_bh : a numpy array of ndim = 2.
	    An auxiliary matrix to keep track of the result.
	left_block_op : a string (optional).
	    The name of an operator in the left block of the system.
	left_site_op : a string (optional).
	    The name of an operator in the left site of the system.
	param : a double/complex (optional).
	    A parameter which multiplies the term.

	Raises
	------
	DMRGException 
	    if any of the operators are not in the corresponding
	    site/block.

	Examples
	--------
        >>> from dmrg101.core.sites import SpinOneHalfSite
        >>> from dmrg101.core.system import System
        >>> # build a system with four spins one-half.
        >>> spin_one_half_site = SpinOneHalfSite()
        >>> ising_fm_in_field = System(spin_one_half_site)
        >>> # add the previous block Hamiltonian...
        >>> ising_fm_in_field.add_to_block_hamiltonian(block_op = 'bh')
	>>> # ... and then add the term coming from eating the current site.
        >>> ising_fm_in_field.add_to_block_hamiltonian('s_z', 's_z')
	"""
	tmp = make_tensor(self.growing_block.operators[block_op],
		          self.growing_site.operators[site_op])
	tmp_matrix_for_bh += param * tmp

    def update_all_operators(self, transformation_matrix):
	"""Updates the operators and puts them in the block.

	You use this function to actually create the operators that are
	going to make the block after a DMRG iteration. 

	Parameters
	----------
	transformation_matrix : a numpy array of ndim = 2.

	Returns
	-------
	result : a Block.
	   A new block
	"""
	if self.growing_side == 'left':
	    self.old_left_blocks.append(deepcopy(self.left_block))
	    self.left_block = make_updated_block_for_site(
		    transformation_matrix, self.operators_to_add_to_block)
	else:
	    self.old_right_blocks.append(deepcopy(self.right_block))
	    self.right_block = make_updated_block_for_site(
		    transformation_matrix, self.operators_to_add_to_block)
 
    def set_block_to_old_version(self, shrinking_size):
	"""Sets the block for the shriking block to an old version.

	Sets the block for the shriking block to an old version. in
	preparation for the next DMRG step. You use this function in the
	finite version of the DMRG algorithm to set an shriking block to
	an old version.

	Parameters
	----------
	shrinking_size : an int.
	    The size (not including the single site) of the shrinking side
	    in the *next* step of the finite algorithm.
	"""
	if shrinking_size == 0:
	    raise DMRGException("You need to turn around")
	if self.shrinking_side == 'left':
	    self.left_block = self.old_left_blocks[shrinking_size-1]
	else:
	    self.right_block = self.old_right_blocks[shrinking_size-1]

    def calculate_ground_state(self, initial_wf=None, min_lanczos_iterations=3, 
		               too_many_iterations=1000, precision=0.000001):
	"""Calculates the ground state of the system Hamiltonian.

	You use this function to calculate the ground state energy and
	wavefunction for the Hamiltonian of the system. The ground state
	is calculated using the Lanczos algorithm. This is again a
	convenience function.
	
        Parameters
        ----------
        initial_wf : a Wavefunction, optional
            The wavefunction that will be used as seed. If None, a random one
    	    if used.
        min_lanczos_iterations : an int, optional.
            The number of iterations before starting the diagonalizations.
        too_many_iterations : a int, optional.
            The maximum number of iterations allowed.
        precision : a double, optional.
            The accepted precision to which the ground state energy is
            considered not improving.
        
        Returns 
        -------
        gs_energy : a double.
            The ground state energy.
        gs_wf : a Wavefunction.
            The ground state wavefunction (normalized.)
	"""
	return lanczos.calculate_ground_state(self.h, initial_wf, 
			                      min_lanczos_iterations, 
		                              too_many_iterations, precision)

    def get_truncation_matrix(self, ground_state_wf, number_of_states_kept):
        """Grows one side of the system by one site.
    
        Calculates the truncation matrix by calculating the reduced density
        matrix for `ground_state_wf` by tracing out the degrees of freedom of
        the shrinking side.  	
        
        Parameters
        ----------
        ground_state_wf : a Wavefunction.
            The ground state wavefunction of your system.
        number_of_states_kept : an int.
            The number of states you want to keep in each block after the
    	    truncation. If the `number_of_states_kept` is smaller than the
    	    dimension of the current Hilbert space block, all states are kept.
     
        Returns
        -------
	truncation_matrix : a numpy array with ndim = 2.
	    The truncation matrix from the reduced density matrix
	    diagonalization.
        entropy : a double.
            The Von Neumann entropy for the cut that splits the chain into two
    	    equal halves.
        truncation_error : a double.
            The truncation error, i.e. the sum of the discarded eigenvalues of
    	    the reduced density matrix.
        """
        rho = ground_state_wf.build_reduced_density_matrix(self.shrinking_side)
        evals, evecs = diagonalize(rho)
        truncated_evals, truncation_matrix = truncate(evals, evecs,
    		                                      number_of_states_kept)
        entropy = calculate_entropy(truncated_evals)
        truncation_error = calculate_truncation_error(truncated_evals)
        return truncation_matrix, entropy, truncation_error

    def grow_block_by_one_site(self, truncation_matrix):
        """Grows one side of the system by one site.
    
	Uses the truncation matrix to update the operators you need in the
	next steps, effectively growing the size of the block by one site. 	

	Parameters
	----------
	truncation_matrix : a numpy array with ndim = 2.
	    The truncation matrix from the reduced density matrix
	    diagonalization.
	"""
        self.set_block_hamiltonian()
        self.set_operators_to_update()
        self.update_all_operators(truncation_matrix)

    def set_hamiltonian(self):
        """Sets a system Hamiltonian to the model Hamiltonian.

	Just a wrapper around the corresponding `Model` method.
	"""
	self.model.set_hamiltonian(self)
    
    def set_block_hamiltonian(self):
        """Sets the block Hamiltonian to model block Hamiltonian.
	
	Just a wrapper around the corresponding `Model` method.
	"""
	tmp_matrix_size = None
	if self.growing_side == 'left':
	    tmp_matrix_size = self.get_left_dim()
        else: 
	    tmp_matrix_size = self.get_right_dim()
	tmp_matrix_for_bh = np.zeros((tmp_matrix_size, tmp_matrix_size))
	self.model.set_block_hamiltonian(tmp_matrix_for_bh, self)
	self.operators_to_add_to_block['bh'] = tmp_matrix_for_bh
    
    def set_operators_to_update(self):
        """Sets the operators to update to be what you need to AF Heisenberg.
	
	Just a wrapper around the corresponding `Model` method.
	"""
	self.model.set_operators_to_update(self)

    def infinite_dmrg_step(self, left_block_size, number_of_states_kept):
        """Performs one step of the (asymmetric) infinite DMRG algorithm.
    
        Calculates the ground state of a system with a given size, then
        performs the DMRG transformation on the operators of *one* block,
        therefore increasing by one site the number of sites encoded in the
        Hilbert space of this block, and reset the block in the system to be
        the new, enlarged, truncated ones. The other block is kept one-site
        long.
    
        Parameters
        ----------
	left_block_size : an int.
	    The number of sites of the left block, not including the single site.
        number_of_states_kept : an int.
            The number of states you want to keep in each block after the
    	    truncation. If the `number_of_states_kept` is smaller than the
    	    dimension of the current Hilbert space block, all states are kept.
     
        Returns
        -------
        energy : a double.
            The energy for the `current_size`.
        entropy : a double.
            The Von Neumann entropy for the cut that splits the chain into two
    	    equal halves.
        truncation_error : a double.
            The truncation error, i.e. the sum of the discarded eigenvalues of
    	    the reduced density matrix.
    
        Notes
        -----
        This asymmetric version of the algorithm when you just grow one of the
        block while keeping the other one-site long, is obviously less precise
        than the symmetric version when you grow both sides. However as we are
        going to sweep next using the finite algorithm we don't care much
        about precision at this stage.
        """
        self.set_growing_side('left')
        self.set_hamiltonian()
        ground_state_energy, ground_state_wf = self.calculate_ground_state()
        truncation_matrix, entropy, truncation_error = (
	    self.get_truncation_matrix(ground_state_wf,
		                       number_of_states_kept) )
	if left_block_size == self.number_of_sites - 3:
	    self.turn_around('right')
	else:
            self.grow_block_by_one_site(truncation_matrix)
        return ground_state_energy, entropy, truncation_error
    
    def finite_dmrg_step(self, growing_side, left_block_size, 
		         number_of_states_kept):
        """Performs one step of the finite DMRG algorithm.
    
        Calculates the ground state of a system with a given size, then
        performs the DMRG transformation on the operators of *one* block,
        therefore increasing by one site the number of sites encoded in the
        Hilbert space of this block, and reset the block in the system to be
        the new, enlarged, truncated ones. The other block is read out from
        the previous sweep.
    
        Parameters
        ----------
	growing_side : a string.
	    Which side, left or right, is growing.
        left_block_size : an int.
            The number of sites in the left block in the *current* step, not
    	    including the single site.     
        number_of_states_kept : an int.
            The number of states you want to keep in each block after the
    	    truncation. If the `number_of_states_kept` is smaller than the
    	    dimension of the current Hilbert space block, all states are kept.
     
        Returns
        -------
        energy : a double.
            The energy at this step.
        entropy : a double.
            The Von Neumann entropy for the cut at this step.
        truncation_error : a double.
            The truncation error, i.e. the sum of the discarded eigenvalues of
    	    the reduced density matrix.
    
        Raises
        ------
        DMRGException
            if `growing_side` is not 'left' or 'right'.
    
        Notes
        -----
        This asymmetric version of the algorithm when you just grow one of the
        block while keeping the other one-site long, is obviously less precise
        than the symmetric version when you grow both sides. However as we are
        going to sweep next using the finite algorithm we don't care much
        about precision at this stage.
        """
        if growing_side not in ('left', 'right'):
    	    raise DMRGException('Growing side must be left or right.')
    
        self.set_growing_side(growing_side)
        self.set_hamiltonian()
        ground_state_energy, ground_state_wf = self.calculate_ground_state()
        truncation_matrix, entropy, truncation_error = (
	    self.get_truncation_matrix(ground_state_wf,
		                       number_of_states_kept) )
	shrinking_size = self.get_shriking_block_next_step_size(left_block_size)
	if shrinking_size == 0:
	    self.turn_around(self.shrinking_side)
	else:
            self.grow_block_by_one_site(truncation_matrix)
            self.set_block_to_old_version(shrinking_size)
        return ground_state_energy, entropy, truncation_error

    def turn_around(self, new_growing_side):
	"""Turns around in the finite algorithm.

	When you reach the smallest possible size for your shriking block,
	you must turn around and start sweeping in the other direction.
	This is just done by setting the to be growing side, which
	currently is skrinking, to be made up of a single site, and by
	setting the to be shrinking side, which now is growing to be the
	current growing block.

	Parameters
	----------
	new_growing_side : a string.
	    The side that will start growing.
	"""
	if new_growing_side == 'left':
	    self.left_block = make_block_from_site(self.left_site)
	    self.old_left_blocks = []
	else:
	    self.right_block = make_block_from_site(self.right_site)
	    self.old_right_blocks = []
Exemple #3
0
    def clear_hamiltonian(self):
        """Makes a brand new hamiltonian.
	"""
	self.h = CompositeOperator(self.get_left_dim(), self.get_right_dim())