mirror of https://github.com/QMCPACK/qmcpack.git
732 lines
23 KiB
Python
732 lines
23 KiB
Python
##################################################################
|
|
## (c) Copyright 2015- by Jaron T. Krogel ##
|
|
##################################################################
|
|
|
|
|
|
#====================================================================#
|
|
# physical_system.py #
|
|
# Representations of matter, particles, and particles collected #
|
|
# together in complete systems. #
|
|
# #
|
|
# Content summary: #
|
|
# PhysicalSystem #
|
|
# Class representing electrons+ions for a simulation. #
|
|
# #
|
|
# generate_physical_system #
|
|
# User function to create arbitrary physical systems. #
|
|
# #
|
|
# Matter #
|
|
# Base class for all forms of matter. #
|
|
# Class contains a list of all matter known to Nexus. #
|
|
# #
|
|
# Particle #
|
|
# Class representing a particular particle species. #
|
|
# #
|
|
# Ion, PseudoIon #
|
|
# Specialized Particle classes for ion species and ions #
|
|
# represented by pseudopotentials. #
|
|
# #
|
|
# Particles #
|
|
# A collection of particles. #
|
|
# PhysicalSystem objects contain a Particles instance. #
|
|
# #
|
|
#====================================================================#
|
|
|
|
|
|
from numpy import dot,array
|
|
from numpy.linalg import inv
|
|
from generic import obj
|
|
from developer import DevBase
|
|
from unit_converter import convert
|
|
from periodic_table import is_element
|
|
from structure import Structure
|
|
from debug import *
|
|
|
|
|
|
class Matter(DevBase):
|
|
particle_collection = None
|
|
|
|
@classmethod
|
|
def set_elements(cls,elements):
|
|
cls.elements = set(elements)
|
|
#end def set_elements
|
|
|
|
@classmethod
|
|
def set_particle_collection(cls,pc):
|
|
cls.particle_collection = pc
|
|
#end def set_particle_collection
|
|
|
|
@classmethod
|
|
def new_particles(cls,*particles,**named_particles):
|
|
cls.particle_collection.add_particles(*particles,**named_particles)
|
|
#end def new_particles
|
|
|
|
def is_element(self,name,symbol=False):
|
|
return is_element(name,symbol=symbol)
|
|
#end def is_element
|
|
#end class Matter
|
|
|
|
|
|
class Particle(Matter):
|
|
def __init__(self,name=None,mass=None,charge=None,spin=None):
|
|
self.name = name
|
|
self.mass = mass
|
|
self.charge = charge
|
|
self.spin = spin
|
|
#end def __init__
|
|
|
|
def set_count(self,count):
|
|
self.count = count
|
|
#end def set_count
|
|
#end class Particle
|
|
|
|
|
|
class Ion(Particle):
|
|
def __init__(self,name=None,mass=None,charge=None,spin=None,
|
|
protons=None,neutrons=None):
|
|
Particle.__init__(self,name,mass,charge,spin)
|
|
self.protons = protons
|
|
self.neutrons = neutrons
|
|
#end def __init__
|
|
|
|
def pseudize(self,valence):
|
|
ps = PseudoIon()
|
|
ps.transfer_from(self)
|
|
ps.charge = valence
|
|
ps.core_electrons = ps.protons - valence
|
|
return ps
|
|
#end def pseudize
|
|
#end class
|
|
|
|
|
|
class PseudoIon(Ion):
|
|
def __init__(self,name=None,mass=None,charge=None,spin=None,
|
|
protons=None,neutrons=None,core_electrons=None):
|
|
Ion.__init__(self,name,mass,charge,spin,protons,neutrons)
|
|
self.core_electrons = core_electrons
|
|
#end def __init__
|
|
#end class PseudoIon
|
|
|
|
|
|
class Particles(Matter):
|
|
def __init__(self,*particles,**named_particles):
|
|
self.add_particles(*particles,**named_particles)
|
|
#end def __init__
|
|
|
|
def add_particles(self,*particles,**named_particles):
|
|
if len(particles)==1 and isinstance(particles[0],list):
|
|
particles = particles[0]
|
|
#end if
|
|
for particle in particles:
|
|
self[particle.name] = particle
|
|
#end for
|
|
for name,particle in named_particles.iteritems():
|
|
self[name] = particle
|
|
#end for
|
|
#end def add_particles
|
|
|
|
def get_particle(self,name):
|
|
p = None
|
|
if name in self:
|
|
p = self[name]
|
|
else:
|
|
iselem,symbol = self.is_element(name,symbol=True)
|
|
if iselem and symbol in self:
|
|
p = self[symbol].copy()
|
|
p.name = name
|
|
self[name] = p
|
|
#end if
|
|
#end if
|
|
return p
|
|
#end def get_particle
|
|
|
|
def get(self,quantity):
|
|
q = obj()
|
|
for name,particle in self.iteritems():
|
|
q[name] = particle[quantity]
|
|
#end for
|
|
return q
|
|
#end def get
|
|
|
|
def rename(self,**name_pairs):
|
|
for old,new in name_pairs.iteritems():
|
|
if old in self:
|
|
o = self[old]
|
|
o.name = new
|
|
del self[old]
|
|
if new in self:
|
|
self[new].count += o.count
|
|
else:
|
|
self[new] = o
|
|
#end if
|
|
#end if
|
|
#end for
|
|
#end def rename
|
|
|
|
def get_ions(self):
|
|
ions = obj()
|
|
for name,particle in self.iteritems():
|
|
if self.is_element(name):
|
|
ions[name] = particle
|
|
#end if
|
|
#end for
|
|
return ions
|
|
#end def get_ions
|
|
|
|
def count_ions(self,species=False):
|
|
nions = 0
|
|
nspecies = 0
|
|
for name,particle in self.iteritems():
|
|
if self.is_element(name):
|
|
nspecies += 1
|
|
nions += particle.count
|
|
#end if
|
|
#end for
|
|
if species:
|
|
return nions,nspecies
|
|
else:
|
|
return nions
|
|
#end if
|
|
#end def count_ions
|
|
|
|
def get_electrons(self):
|
|
electrons = obj()
|
|
for electron in ('up_electron','down_electron'):
|
|
if electron in self:
|
|
electrons[electron] = self[electron]
|
|
#end if
|
|
#end for
|
|
return electrons
|
|
#end def get_electrons
|
|
|
|
def count_electrons(self):
|
|
nelectrons = 0
|
|
for electron in ('up_electron','down_electron'):
|
|
if electron in self:
|
|
nelectrons += self[electron].count
|
|
#end if
|
|
#end for
|
|
return nelectrons
|
|
#end def count_electrons
|
|
#end class Particles
|
|
|
|
me_amu = convert(1.,'me','amu')
|
|
|
|
plist = [
|
|
Particle('up_electron' ,1.0,-1, 1),
|
|
Particle('down_electron',1.0,-1,-1),
|
|
]
|
|
from periodic_table import ptable
|
|
for name,a in ptable.elements.iteritems():
|
|
spin = 0 # don't have this data
|
|
protons = a.atomic_number
|
|
neutrons = int(round(a.atomic_weight['amu']-a.atomic_number))
|
|
p = Ion(a.symbol,a.atomic_weight['me'],a.atomic_number,spin,protons,neutrons)
|
|
plist.append(p)
|
|
#end for
|
|
for name,iso in ptable.isotopes.iteritems():
|
|
for mass_number,a in iso.iteritems():
|
|
spin = 0 # don't have this data
|
|
protons = a.atomic_number
|
|
neutrons = int(round(a.atomic_weight['amu']-a.atomic_number))
|
|
p = Ion(a.symbol+'_'+str(mass_number),a.atomic_weight['me'],a.atomic_number,spin,protons,neutrons)
|
|
plist.append(p)
|
|
#end for
|
|
#end for
|
|
|
|
Matter.set_elements(ptable.elements.keys())
|
|
Matter.set_particle_collection(Particles(plist))
|
|
|
|
del plist
|
|
|
|
|
|
|
|
class PhysicalSystem(Matter):
|
|
|
|
def __init__(self,structure=None,net_charge=0,net_spin=0,particles=None,**valency):
|
|
self.pseudized = False
|
|
|
|
if structure is None:
|
|
self.structure = Structure()
|
|
else:
|
|
self.structure = structure
|
|
#end if
|
|
if particles is None:
|
|
self.particles = Particles()
|
|
else:
|
|
self.particles = particles.copy()
|
|
#end if
|
|
|
|
self.folded_system = None
|
|
if self.structure.folded_structure!=None:
|
|
vratio = structure.volume()/structure.folded_structure.volume()
|
|
ncells = int(round(vratio))
|
|
if abs(vratio-ncells)>1e-4:
|
|
self.error('volume of system does not divide evenly into folded system')
|
|
#end if
|
|
if net_charge%ncells!=0:
|
|
self.error('net charge of system does not divide evenly into folded system')
|
|
#end if
|
|
if net_spin%ncells!=0:
|
|
self.error('net_spin of system does not divide evenly into folded system')
|
|
#end if
|
|
self.folded_system = PhysicalSystem(
|
|
structure = structure.folded_structure,
|
|
net_charge = net_charge/ncells,
|
|
net_spin = net_spin/ncells,
|
|
particles = particles,
|
|
**valency
|
|
)
|
|
#end if
|
|
|
|
self.valency_in = obj(**valency)
|
|
self.net_charge_in = net_charge
|
|
self.net_spin_in = net_spin
|
|
|
|
self.update_particles(clear=False)
|
|
|
|
self.check_folded_system()
|
|
#end def __init__
|
|
|
|
|
|
def update_particles(self,clear=True):
|
|
#add ions
|
|
pc = dict()
|
|
elem = list(self.structure.elem)
|
|
for ion in set(elem):
|
|
pc[ion] = elem.count(ion)
|
|
#end for
|
|
missing = set(pc.keys())-set(self.particles.keys())
|
|
if len(missing)>0 or len(elem)==0:
|
|
if clear:
|
|
self.particles.clear()
|
|
#end if
|
|
self.add_particles(**pc)
|
|
|
|
#pseudize
|
|
if len(self.valency_in)>0:
|
|
self.pseudize(**self.valency_in)
|
|
#end if
|
|
|
|
#add electrons
|
|
self.generate_electrons(self.net_charge_in,self.net_spin_in)
|
|
#end if
|
|
#end def update_particles
|
|
|
|
|
|
def update(self):
|
|
self.net_charge = self.structure.background_charge
|
|
self.net_spin = 0
|
|
for p in self.particles:
|
|
self.net_charge += p.count*p.charge
|
|
self.net_spin += p.count*p.spin
|
|
#end for
|
|
self.net_charge = int(round(float(self.net_charge)))
|
|
self.net_spin = int(round(float(self.net_spin)))
|
|
#end def update
|
|
|
|
|
|
def add_particles(self,**particle_counts):
|
|
pc = self.particle_collection # all known particles
|
|
plist = []
|
|
for name,count in particle_counts.iteritems():
|
|
particle = pc.get_particle(name)
|
|
if particle is None:
|
|
self.error('particle {0} is unknown'.format(name))
|
|
else:
|
|
particle = particle.copy()
|
|
#end if
|
|
particle.set_count(count)
|
|
plist.append(particle)
|
|
#end for
|
|
self.particles.add_particles(plist)
|
|
self.update()
|
|
#end def add_particles
|
|
|
|
|
|
def generate_electrons(self,net_charge=0,net_spin=0):
|
|
nelectrons = -net_charge + self.net_charge
|
|
if net_spin=='low':
|
|
net_spin = nelectrons%2
|
|
#end if
|
|
nup = float(nelectrons + net_spin - self.net_spin)/2
|
|
ndown = float(nelectrons - net_spin + self.net_spin)/2
|
|
if abs(nup-int(nup))>1e-3:
|
|
self.error('requested spin state {0} incompatible with {1} electrons'.format(net_spin,nelectrons))
|
|
#end if
|
|
nup = int(nup)
|
|
ndown = int(ndown)
|
|
self.add_particles(up_electron=nup,down_electron=ndown)
|
|
#end def generate_electrons
|
|
|
|
|
|
def pseudize(self,**valency):
|
|
errors = False
|
|
for ion,valence_charge in valency.iteritems():
|
|
if ion in self.particles:
|
|
ionp = self.particles[ion]
|
|
if isinstance(ionp,Ion):
|
|
self.particles[ion] = ionp.pseudize(valence_charge)
|
|
self.pseudized = True
|
|
else:
|
|
self.error(ion+' cannot be pseudized',exit=False)
|
|
#end if
|
|
else:
|
|
self.error(ion+' is not in the physical system',exit=False)
|
|
errors = True
|
|
#end if
|
|
#end for
|
|
if errors:
|
|
self.error('system cannot be generated')
|
|
#end if
|
|
self.valency = obj(**valency)
|
|
self.update()
|
|
#end def pseudize
|
|
|
|
|
|
def check_folded_system(self):
|
|
sys_folded = self.folded_system!=None
|
|
struct_folded = self.structure.folded_structure!=None
|
|
if sys_folded!=struct_folded:
|
|
self.error('folding of physical system and structure is not consistent\n system folded: {0}\n structure folded: {1}'.format(sys_folded,struct_folded))
|
|
#end if
|
|
if sys_folded and id(self.structure.folded_structure)!=id(self.folded_system.structure):
|
|
self.error('structure of folded system and folded structure are distinct\n this is not allowed and may be a developer error')
|
|
#end if
|
|
#end def check_folded_system
|
|
|
|
|
|
def change_units(self,units):
|
|
self.structure.change_units(units,folded=False)
|
|
if self.folded_system!=None:
|
|
self.folded_system.change_units(units)
|
|
#end if
|
|
#end def change_units
|
|
|
|
|
|
def group_atoms(self):
|
|
self.structure.group_atoms(folded=False)
|
|
if self.folded_system!=None:
|
|
self.folded_system.group_atoms()
|
|
#end if
|
|
#end def group_atoms
|
|
|
|
|
|
def rename(self,folded=True,**name_pairs):
|
|
self.particles.rename(**name_pairs)
|
|
self.structure.rename(folded=False,**name_pairs)
|
|
if self.pseudized:
|
|
for old,new in name_pairs.iteritems():
|
|
if old in self.valency:
|
|
if new not in self.valency:
|
|
self.valency[new] = self.valency[old]
|
|
#end if
|
|
del self.valency[old]
|
|
#end if
|
|
#end for
|
|
self.valency_in = self.valency
|
|
#end if
|
|
if self.folded_system!=None and folded:
|
|
self.folded_system.rename(folded=folded,**name_pairs)
|
|
#end if
|
|
#end def rename
|
|
|
|
|
|
def copy(self):
|
|
cp = DevBase.copy(self)
|
|
if self.folded_system!=None and self.structure.folded_structure!=None:
|
|
del cp.folded_system.structure
|
|
cp.folded_system.structure = cp.structure.folded_structure
|
|
#end if
|
|
return cp
|
|
#end def copy
|
|
|
|
|
|
def load(self,filepath):
|
|
DevBase.load(self,filepath)
|
|
if self.folded_system!=None and self.structure.folded_structure!=None:
|
|
del self.folded_system.structure
|
|
self.folded_system.structure = self.structure.folded_structure
|
|
#end if
|
|
#end def load
|
|
|
|
|
|
def tile(self,*td,**kwargs):
|
|
extensive = True
|
|
net_spin = None
|
|
if 'extensive' in kwargs:
|
|
extensive = kwargs['extensive']
|
|
#end if
|
|
if 'net_spin' in kwargs:
|
|
net_spin = kwargs['net_spin']
|
|
#end if
|
|
supercell = self.structure.tile(*td)
|
|
supercell.remove_folded()
|
|
if extensive:
|
|
ncells = int(round(supercell.volume()/self.structure.volume()))
|
|
net_charge = ncells*self.net_charge
|
|
if net_spin is None:
|
|
net_spin = ncells*self.net_spin
|
|
#end if
|
|
else:
|
|
net_charge = self.net_charge
|
|
if net_spin is None:
|
|
net_spin = self.net_spin
|
|
#end if
|
|
#end if
|
|
system = self.copy()
|
|
supersystem = PhysicalSystem(
|
|
structure = supercell,
|
|
net_charge = net_charge,
|
|
net_spin = net_spin,
|
|
**self.valency
|
|
)
|
|
supersystem.folded_system = system
|
|
supersystem.structure.set_folded(system.structure)
|
|
return supersystem
|
|
#end def tile
|
|
|
|
|
|
def remove_folded_system(self):
|
|
self.folded_system = None
|
|
self.structure.remove_folded_structure()
|
|
#end def remove_folded_system
|
|
|
|
|
|
def remove_folded(self):
|
|
self.remove_folded_system()
|
|
#end def remove_folded
|
|
|
|
|
|
def get_primitive(self):
|
|
if self.folded_system is None:
|
|
fs = self
|
|
else:
|
|
fs = self.folded_system
|
|
while fs.folded_system!=None:
|
|
fs = fs.folded_system
|
|
#end while
|
|
#end if
|
|
return fs
|
|
#end def get_primitive
|
|
|
|
|
|
def folded_representation(self,arg0,arg1=None):
|
|
self.error('folded_representation needs a developers attention to make it equivalent with tile')
|
|
if isinstance(arg0,PhysicalSystem):
|
|
folded_system = arg0
|
|
elif isinstance(arg0,str):
|
|
shape = arg0
|
|
tiling = arg1
|
|
if tiling is None:
|
|
tiling = (1,1,1)
|
|
#end if
|
|
if not 'generation_info' in self:
|
|
self.error('system was not formed with generate_physical_system, cannot form folded representation')
|
|
#end if
|
|
structure,element,scale,units,net_charge,net_spin,particles,valency = \
|
|
self.generation_info.tuple('structure','element','scale','units', \
|
|
'net_charge','net_spin','particles','valency')
|
|
folded_system = generate_physical_system(
|
|
structure = structure,
|
|
shape = shape,
|
|
element = element,
|
|
tiling = tiling,
|
|
scale = scale,
|
|
units = units,
|
|
net_charge = net_charge,
|
|
net_spin = net_spin,
|
|
particles = particles,
|
|
**valency
|
|
)
|
|
else:
|
|
self.error('unrecognized inputs in folded_representation')
|
|
#end if
|
|
tilematrix,kmap = self.structure.fold(folded_system.structure,'tilematrix','kmap')
|
|
self.set(
|
|
folded_system = folded_system,
|
|
tilematrix = tilematrix,
|
|
kmap = kmap
|
|
)
|
|
return folded_system
|
|
#end def folded_representation
|
|
|
|
|
|
def large_Zeff_elem(self,Zmin):
|
|
elem = []
|
|
for atom,Zeff in self.valency.iteritems():
|
|
if Zeff>Zmin:
|
|
elem.append(atom)
|
|
#end if
|
|
#end for
|
|
return elem
|
|
#end def large_Zeff_elem
|
|
|
|
|
|
def ae_pp_species(self):
|
|
species = set(self.structure.elem)
|
|
if self.pseudized:
|
|
pp_species = set(self.valency.keys())
|
|
ae_species = species-pp_species
|
|
else:
|
|
pp_species = set()
|
|
ae_species = species
|
|
#end if
|
|
return ae_species,pp_species
|
|
#end def ae_pp_species
|
|
#end class PhysicalSystem
|
|
|
|
|
|
import os
|
|
from structure import generate_structure,read_structure
|
|
from copy import deepcopy
|
|
ps_defaults = dict(
|
|
type='crystal',
|
|
kshift = (0,0,0),
|
|
net_charge=0,
|
|
net_spin=0,
|
|
pretile=None,
|
|
tiling=None,
|
|
tiled_spin=None,
|
|
extensive=True
|
|
)
|
|
def generate_physical_system(**kwargs):
|
|
for var,val in ps_defaults.iteritems():
|
|
if not var in kwargs:
|
|
kwargs[var] = val
|
|
#end if
|
|
#end for
|
|
type = kwargs['type']
|
|
if type=='atom' or type=='dimer' or type=='trimer':
|
|
del kwargs['kshift']
|
|
del kwargs['tiling']
|
|
#if not 'units' in kwargs:
|
|
# kwargs['units'] = 'B'
|
|
##end if
|
|
tiling = None
|
|
else:
|
|
tiling = kwargs['tiling']
|
|
#end if
|
|
|
|
if 'structure' in kwargs:
|
|
s = kwargs['structure']
|
|
is_str = isinstance(s,str)
|
|
if is_str and os.path.exists(s):# and '.' in os.path.split(s)[1]:
|
|
if 'elem' in kwargs:
|
|
print 'system using elem'
|
|
kwargs['structure'] = read_structure(s,elem=kwargs['elem'])
|
|
else:
|
|
kwargs['structure'] = read_structure(s)
|
|
#end if
|
|
elif is_str and '/' in s:
|
|
PhysicalSystem.class_error('path provided for structure file does not exist: '+s,'generate_physical_system')
|
|
#end if
|
|
#end if
|
|
|
|
generation_info = obj()
|
|
generation_info.transfer_from(deepcopy(kwargs))
|
|
|
|
net_charge = kwargs['net_charge']
|
|
net_spin = kwargs['net_spin']
|
|
tiled_spin = kwargs['tiled_spin']
|
|
extensive = kwargs['extensive']
|
|
del kwargs['net_spin']
|
|
del kwargs['net_charge']
|
|
del kwargs['tiled_spin']
|
|
del kwargs['extensive']
|
|
if 'particles' in kwargs:
|
|
particles = kwargs['particles']
|
|
del kwargs['particles']
|
|
else:
|
|
generation_info.particles = None
|
|
#end if
|
|
pretile = kwargs['pretile']
|
|
del kwargs['pretile']
|
|
valency = dict()
|
|
remove = []
|
|
for var in kwargs:
|
|
#if var in Matter.elements:
|
|
if is_element(var):
|
|
valency[var] = kwargs[var]
|
|
remove.append(var)
|
|
#end if
|
|
#end if
|
|
generation_info.valency = deepcopy(valency)
|
|
for var in remove:
|
|
del kwargs[var]
|
|
#end for
|
|
|
|
if pretile is None:
|
|
structure = generate_structure(**kwargs)
|
|
else:
|
|
for d in range(len(pretile)):
|
|
if tiling[d]%pretile[d]!=0:
|
|
PhysicalSystem.class_error('pretile does not divide evenly into tiling\n tiling provided: {0}\n pretile provided: {1}'.format(tiling,pretile),'generate_physical_system')
|
|
#end if
|
|
#end for
|
|
tiling = tuple(array(tiling)/array(pretile))
|
|
kwargs['tiling'] = pretile
|
|
pre = generate_structure(**kwargs)
|
|
pre.remove_folded_structure()
|
|
structure = pre.tile(tiling)
|
|
#end if
|
|
|
|
if tiling!=None and tiling!=(1,1,1) and structure.has_folded():
|
|
fps = PhysicalSystem(
|
|
structure = structure.folded_structure,
|
|
net_charge = net_charge,
|
|
net_spin = net_spin,
|
|
**valency
|
|
)
|
|
structure.remove_folded()
|
|
folded_structure = fps.structure
|
|
if extensive:
|
|
ncells = int(round(structure.volume()/folded_structure.volume()))
|
|
net_charge = ncells*net_charge
|
|
net_spin = ncells*net_spin
|
|
#end if
|
|
if tiled_spin!=None:
|
|
net_spin = tiled_spin
|
|
#end if
|
|
ps = PhysicalSystem(
|
|
structure = structure,
|
|
net_charge = net_charge,
|
|
net_spin = net_spin,
|
|
**valency
|
|
)
|
|
structure.set_folded(folded_structure)
|
|
ps.folded_system = fps
|
|
else:
|
|
ps = PhysicalSystem(
|
|
structure = structure,
|
|
net_charge = net_charge,
|
|
net_spin = net_spin,
|
|
**valency
|
|
)
|
|
#end if
|
|
|
|
ps.generation_info = generation_info
|
|
|
|
return ps
|
|
#end def generate_physical_system
|
|
|
|
|
|
|
|
|
|
if __name__=='__main__':
|
|
|
|
from structure import generate_structure
|
|
|
|
ps = PhysicalSystem(
|
|
structure = generate_structure('diamond','sc','Ge',(2,2,2),scale=5.639,units='A'),
|
|
net_charge = 1,
|
|
net_spin = 1,
|
|
Ge = 4
|
|
)
|
|
|
|
print 'net_charge',ps.net_charge
|
|
print 'net_spin ',ps.net_spin
|
|
print ps.particles
|
|
|
|
#end if
|