In response to the
pull request #10 and to continue with this thread:
I completely agree that it's stupid to define each model multiple times. Using the trick above I got it down to two classes - one for a Chain derived from the NearestNeigborModel and once for generic 2D lattices (which include quasi-1D cases). Yet, I still define the couplings twice, which seems unnecessary and will lead to errors (e.g. when someone updates only one of the two classes).
The two classes are necessary as one is derived from the NearestNeigborModel, but defining the couplings twice isn't.
We should first define the model, allowing for general lattices (including a Chain), and then define the 1D version by deriving from it and the
NearestNeigborModel
.
I still like the idea to allow for a
conserve='best'
paramter, which checks the other model parameters on what can be preserved, as it done e.g. in the SpinChain model. This requires defining the Site instance inside the model, though, which makes everything a bit tricky in terms of where we initialize what. Personally, I also find it very convenient to have a single dictionary of parameters defining the model, including the lattice and what's conserved.
What do you think of changing the classes in tf_ising.py to the following:
Code: Select all
class TFIModel(CouplingModel, MPOModel):
r"""Transverse field Ising model on a general (2D) lattice.
... documentation ..."""
def __init__(self, model_param):
# 0) read out/set default parameters
lat = get_parameter(model_param, 'lattice', "Square", self.__class__)
if isinstance(lat, str):
Lx = get_parameter(model_param, 'Lx', 1, self.__class__)
Ly = get_parameter(model_param, 'Ly', 4, self.__class__)
bc_MPS = get_parameter(model_param, 'bc_MPS', 'infinite', self.__class__)
bc_y = get_parameter(model_param, 'bc_y', 'cylinder', self.__class__)
order = get_parameter(model_param, 'order', 'default', self.__class__)
conserve = get_parameter(model_param, 'conserve', None, self.__class__)
assert conserve != 'Sz' # invalid!
assert bc_y in ['cylinder', 'ladder']
# 1-3)
site = SpinHalfSite(conserve=conserve)
# 4) lattice
bc_x = 'periodic' if bc_MPS == 'infinite' else 'open'
bc_y = 'periodic' if bc_y == 'cylinder' else 'open'
lat = lattice.__dict__.get(lat) # the corresponding class
lat = lat(Lx, Ly, site, order=order, bc=[bc_x, bc_y], bc_MPS=bc_MPS) # instance
J = get_parameter(model_param, 'J', 1., self.__class__)
g = get_parameter(model_param, 'g', 1., self.__class__)
unused_parameters(model_param, self.__class__) # checks for mistyped parameters
# 5) initialize CouplingModel
CouplingModel.__init__(self, lat)
# 6) add terms of the Hamiltonian
# (u is always 0 as we have only one site in the unit cell)
self.add_onsite(-np.asarray(g), 0, 'Sigmaz')
J = np.asarray(J)
if all([site.conserve is None for site in lat.unit_cell]):
for u1, u2, dx in self.lat.nearest_neighbors:
self.add_coupling(-J, u1, 'Sigmax', u2, 'Sigmax', dx)
else:
for op1, op2 in [('Sp', 'Sp'), ('Sp', 'Sm'), ('Sm', 'Sp'), ('Sm', 'Sm')]:
for u1, u2, dx in self.lat.nearest_neighbors:
self.add_coupling(-J, u1, op1, u2, op2, dx)
# 7) initialize MPO
MPOModel.__init__(self, lat, self.calc_H_MPO())
class TFIChain(TFIModel, NearestNeighborModel):
r"""Transverse field Ising model on a chain.
... documentation ... """
def __init__(self, model_param):
# 0) read out/set default parameters
L = get_parameter(model_param, 'L', 2, self.__class__)
bc_MPS = get_parameter(model_param, 'bc_MPS', 'finite', self.__class__)
conserve = get_parameter(model_param, 'conserve', 'parity', self.__class__)
assert conserve != 'Sz'
# 1-3)
site = SpinHalfSite(conserve=conserve)
# 4) lattice
bc = 'periodic' if bc_MPS == 'infinite' else 'open'
lat = Chain(L, site, bc=bc, bc_MPS=bc_MPS)
model_param['lattice'] = lat
super().__init__(model_param)
# 8) initialize bonds
NearestNeighborModel.__init__(self, lat, self.calc_H_bond())
Note that this even keeps backwards compatibility
The general TFIModel is 2D in the sense that generating the lattice with
lat(Lx, Ly, site, order=order, bc=[bc_x, bc_y], bc_MPS=bc_MPS)
is the common call structure for 2D lattices, which doesn't work for a Chain, but it can also be used with any other, user-defined lattice if an instance is directly given with the
lattice
parameter.
All other "...Chain" models can be generalized in a similar way to 2D lattices, most importantly the SpinChain. I guess we should do that when we include this change into the master branch....