Source code for colder.core.physics



import numpy as np
import sympy
import inspect

from typing import Tuple, List, Union

import colder.core.pauli_algebra as palg
import colder.simulation.numerical as cnum
from colder.core.subroutines import *





[docs] class hamiltonian:
[docs] def __init__(self, operator : str, targets : list, coeff : str, coeff_function : callable = None, target_coeffs : Union[np.ndarray,None,int,float] = None ) -> None: """ Hamiltonian object. Args: operator (str): String defining the Pauli operators to apply. targets (list): List of spin targets to apply the operators. The length of the tuples has to match the length of `operator` string. coeff (str): Coefficient symbol. coeff_function (callable, optional): Function to associate to coefficient. To use COLD simulation, this argument has to be provided. Defaults to None. target_coeffs (Union[np.ndarray,None,int,float], optional): Coefficients to associate to the target spins. Defaults to None. Raises: Exception: First argument of `coeff_function`, if provided, must be `t` (time dependence). """ self.targets : list = targets # list of tuples if not isinstance(self.targets[0], tuple): self.targets = [ tuple([e]) for e in self.targets ] self.operator : str = operator # string of operators to apply for each tuple in target site list self.n_terms : int = len(self.targets) # number of targets self.operator_order : int = len(self.operator) # how many operators are multiplied in a single tensor product # inject coefficient name and callable functions self.coeff : str = coeff self.coeff_function : callable = coeff_function if self.coeff_function is not None: arg = self.get_coeff_function_arguments() if arg[0] != 't': # TODO: is this really necessary? raise Exception('first argument must be t') # process target coefficient if target_coeffs is not None: if hasattr(target_coeffs, '__iter__'): # if it is iterable, check for len assert len(target_coeffs) == self.n_terms else: # if it is not iterable, broadcast to np.ndarray target_coeffs = np.ones(self.n_terms)*target_coeffs self.target_coeffs : Union[np.array,None] = target_coeffs
[docs] def expression(self, sum_symbol : str = 'i', weight_symbol : str = 'w', operator_symbol : str = 'sigma_'): """ Print a dummy expression for current hamiltonian. Args: sum_symbol (str, optional): Symbol to use in the sum. Defaults to `i`. weight_symbol (str, optional): Placeholder letter for symbols. Defaults to `w`. operator_symbol (str, optional): Spin operator symbol. Defaults to `sigma_`. """ sum_expression = operator_symbol + self.operator + '^i' global_coeff = None # add weights, if any if self.target_coeffs is not None: if np.all(self.target_coeffs == self.target_coeffs[0]): # all elements are equal global_coeff : float = self.target_coeffs[0] else: sum_expression = weight_symbol + '_' + sum_symbol + '*' + sum_expression if global_coeff is not None: return sympy.Symbol(self.coeff) *global_coeff* sympy.Sum(sum_expression, (sympy.Symbol(sum_symbol), 1, len(self.targets)) ) else: return sympy.Symbol(self.coeff) * sympy.Sum(sum_expression, (sympy.Symbol(sum_symbol), 1, len(self.targets)) )
[docs] def get_strings(self, total_sites : int) -> dict[str, complex]: """ Returns full strings with coefficients from current hamiltonian. Args: total_sites (int): Number of total spin sites. Returns: dict[str, complex]: Dictionary of expressions and coefficients. """ if self.target_coeffs is None: w = np.ones(self.n_terms) else: w = self.target_coeffs return { palg.build_string_from_operator_and_target(strlen=total_sites, target=tt, operator_sequence=self.operator) : cc for tt, cc in zip(self.targets, w) }
def __repr__(self): return ( f"{self.__class__.__name__}" f"(operator={self.operator}, coeff={self.coeff})" ) def __add__(self, otherH): if otherH is None: return self return hamiltonian_collection(self, otherH) def __iadd__(self, otherH): if otherH is None: return self return hamiltonian_collection(self, otherH)
[docs] def get_coeff(self) -> str: """ Get the coefficient of this hamiltonian. Returns: str: Coefficient string. """ return self.coeff
[docs] def get_coeff_function_arguments(self, exception_if_none : bool = True) -> List[str]: """ Get the argument name for the coefficient function. Args: exception_if_none (bool, optional): If True, raises an exception if there exist no function coefficient. Else, returns an empty list. Defaults to True. Raises: Exception: If `exception_if_none`, the exception is raised if no coefficient function is provided. Returns: List[str]: List of arguments for the coefficient function. """ if self.coeff_function is None: if exception_if_none: raise Exception('coeff function not provided in __init__') else: return [] return inspect.getfullargspec(self.coeff_function)[0]
[docs] def make_coefficient_interpolation(self, trange : tuple, fargs : dict, n_interp_points : int = 50, **kwargs) -> cnum.interpolator1D: """ Interpolates the coefficient function in time range `trange`. Args: trange (tuple): Range of time to interpolate. fargs (dict): Arguments to be passed to the coefficient function. n_interp_points (int, optional): Number of points to interpolate. Defaults to 50. Raises: Exception: Coefficient function must be provided to use this feature. Returns: cnum.interpolator1D: Numerical spline interpolator for coefficient function. """ if self.coeff_function is None: raise Exception(f'coeff function {self.coeff} not provided in __init__') return cnum.interpolator1D.from_function(self.coeff_function, trange, n_interp_points = n_interp_points, fargs=fargs, **kwargs)
[docs] class hamiltonian_collection: """ This class creates collection of hamiltonian object that have to be summed. Example: .. highlight:: python .. code-block:: python import colder.core.physics as cphys H_Z = cphys.hamiltonian('Z', [(0,) , (1,)], coeff = 'Z') H_X = cphys.hamiltonian('X', [(0,) , (1,)], coeff = 'X') H = H_Z + H_X # this is a collection (type: cphys.hamiltonian_collection) """
[docs] def __init__(self, *args): """ Creates an hamiltonian collection "summing" instances of hamiltonians. """ self.terms = [] for tt in locals()['args']: if isinstance(tt, hamiltonian_collection): # hamiltonian collections are unwrapped self.terms += tt.terms else: self.terms.append(tt)
[docs] def get_coeffs(self) -> List[str]: return [ term.coeff for term in self.terms ]
[docs] def get_unique_coeffs(self) -> List[str]: return list( set( self.get_coeffs() ) )
[docs] def get_strings(self, total_sites : int) -> List[ dict[str, complex] ]: return [ term.get_strings(total_sites) for term in self.terms ]
[docs] def expression(self, group_coefficients : bool = True): """ Print a dummy expression for current hamiltonian. Args: group_coefficients (bool, optional): If True, coefficients are grouped, if needed. Defaults to True. """ expr = sum( ee.expression() for ee in self.terms ) if group_coefficients: expr = sympy.collect(expr, [ sympy.Symbol(ee) for ee in self.get_unique_coeffs() ] ) return expr
[docs] def make_symbolic_expression(self, total_sites : int): """ Returns the symbolic expression for this hamiltonian collection. Args: total_sites (int): Total number of spin sites. """ return sum( sympy.Symbol(cc)*make_sum_expression(st) for cc, st in zip(self.get_coeffs(), self.get_strings(total_sites)) )
def __add__(self, otherH): return hamiltonian_collection(self, otherH) def __repr__(self): return ( f"{self.__class__.__name__}" f"(terms={self.terms}, coeff={self.get_unique_coeffs()})" )
[docs] def make_coefficients_interpolation(self, trange : tuple, fargs : dict, make_unique : bool = True, **kwargs) -> dict[str, cnum.interpolator1D]: """ Interpolates the coefficient function in time range `trange` for each unique coefficient of hamiltonian collection. Args: trange (tuple): Range of time to interpolate. fargs (dict): Arguments to be passed to the coefficient function. make_unique (bool, optional): If True, uses unique coefficients to avoid multiple computation of the same interpolation. Defaults to True. Returns: dict[str, cnum.interpolator1D]: Dictionary of interpolated functions. """ if make_unique: # strongly suggested, to avoid interpolating many times the same parameter unique_coeff_objects : List[str] = { hh.coeff : hh for hh in self.terms } return { kk : hh.make_coefficient_interpolation(trange=trange, fargs=fargs, **kwargs) for kk, hh in unique_coeff_objects.items() } return { hh.coeff : hh.make_coefficient_interpolation(trange=trange, fargs=fargs, **kwargs) for hh in self.terms }
[docs] class empty_hamiltonian: """ Empty hamiltonian class. The purpuse of this dummy class is to provide a custom empty object to be summed with hamiltonians. Example: .. highlight:: python .. code-block:: python import colder.core.physics as cphys H = cphys.empty_hamiltonian() # just a terrible example: for i in range(3): H += cphys.hamiltonian('XX', [(i,i+1)], coeff = 'J', coeff_function=Jf, target_coeffs= 1/(i+1) ) """ def __init__(self) -> None: pass def __repr__(self): return ( f"{self.__class__.__name__}" f" use + or += operator to add a hamiltonian obj" ) def __add__(self, other): return other def __iadd__(self, other): return other