Add support for SPRKKR potential.

This is a first version for this option. Some work has still to be
done...
This commit is contained in:
Sylvain Tricot 2020-04-10 17:36:25 +02:00
parent 81459cfcfc
commit 8ebfd624e1
13 changed files with 1278 additions and 1259 deletions

View File

@ -1,302 +0,0 @@
INFO:msspec:Getting the TMatrix...
_________________________________________________________________
PHAGEN
_________________________________________________________________
parameters for this xpd calculation:
-----------------------------------------------------------------
edge= k
potype= hedin norman= stdcrm absorber= 1
coor= angs emin= 13.52 Ry emax= 13.52 Ry
delta= 0.300 Ry gamma= 0.03 Ry eftri= 0.000 Ry
ionization state : neutral
final state potential generated internally
Computes the T-matrix and radial matrix elements
coordinates in angstroms Radii
-----------------------------------------------------------------
Cu 29 0.0000 0.0000 -3.6000 0.0000 0.0000
Cu 29 -1.8000 -1.8000 0.0000 0.0000 0.0000
Cu 29 -1.8000 0.0000 -1.8000 0.0000 0.0000
Cu 29 -3.6000 0.0000 0.0000 0.0000 0.0000
Cu 29 -1.8000 1.8000 0.0000 0.0000 0.0000
Cu 29 0.0000 -1.8000 -1.8000 0.0000 0.0000
Cu 29 0.0000 -3.6000 0.0000 0.0000 0.0000
Cu 29 1.8000 -1.8000 0.0000 0.0000 0.0000
Cu 29 0.0000 1.8000 -1.8000 0.0000 0.0000
Cu 29 1.8000 0.0000 -1.8000 0.0000 0.0000
Cu 29 0.0000 0.0000 0.0000 0.0000 0.0000
Cu 29 1.8000 1.8000 0.0000 0.0000 0.0000
Cu 29 0.0000 3.6000 0.0000 0.0000 0.0000
Cu 29 3.6000 0.0000 0.0000 0.0000 0.0000
-----------------------------------------------------------------
** enter calphas **
---
total energy for atom in ground state
total energy for atom with a hole in k edge
calculated ionization energy for edge k = 8994.86914 eV
---
calculated ionization potential (ryd) = 661.387451
---
symmetrizing coordinates...
symmetrized atomic coordinates of cluster
position
atom no. x y z eq
1 osph 0 0.0000 0.0000 0.0000 0
2 Cu 29 0.0000 0.0000 -5.3452 0
3 Cu 29 -3.4015 -3.4015 1.4578 0
4 Cu 29 -3.4015 0.0000 -1.9437 0
5 Cu 29 -6.8030 0.0000 1.4578 0
6 Cu 29 0.0000 0.0000 1.4578 0
7 Cu 29 -3.4015 3.4015 1.4578 3
8 Cu 29 3.4015 -3.4015 1.4578 3
9 Cu 29 3.4015 3.4015 1.4578 3
10 Cu 29 0.0000 -3.4015 -1.9437 4
11 Cu 29 0.0000 3.4015 -1.9437 4
12 Cu 29 3.4015 0.0000 -1.9437 4
13 Cu 29 0.0000 -6.8030 1.4578 5
14 Cu 29 0.0000 6.8030 1.4578 5
15 Cu 29 6.8030 0.0000 1.4578 5
computing muffin tin potential and phase shifts
generating core state wavefunction
generating final potential (complex hedin-lundqvist exchange)
MT radii for Hydrogen atoms determined by stdcrm unless other options are specified
-----------------------------------------------------------------
i rs(i) i=1,natoms
1 9.28 2 2.50 3 2.46 4 2.35 5 2.48 6 2.33 7 2.46 8 2.46
9 2.46 10 2.35 11 2.35 12 2.35 13 2.48 14 2.48 15 2.48
N.B.: Order of atoms as reshuffled by symmetry routines
-----------------------------------------------------------------
input value for coulomb interst. potential = -0.699999988
and interstitial rs = 3.00000000
lower bound for coulomb interst. potential = -0.290025890
and for interst. rs = 2.54297900
lmax assignment based on l_max = r_mt * k_e + 2
where e is the running energy
optimal lmax chosen according to the running energy e for each atom
number of centers= 14
starting potentials and/or charge densities written to file 13
symmetry information generated internally
symmetry information written to file 14
core initial state of type: 1s1/2
fermi level = -0.17690
generating t_l (for030) and atomic cross section (for050)
gamma = 0.030000 rsint = 3.944844
check in subroutine cont
order of neighb. -- symb. -- dist. from absorber
3 Cu 4.81045771
5 Cu 6.80301476
2 Cu 8.33195782
4 Cu 9.62091541
-----------------------------------------------------------------
1 Cu 0.000000
3 Cu 4.810458
5 Cu 6.803015
2 Cu 8.331958
4 Cu 9.620915
1 Cu 0.000000
3 Cu 4.810458
5 Cu 6.803015
2 Cu 8.331958
4 Cu 9.620915
irho = 2 entering vxc to calculate energy dependent exchange
energy dependent vcon = (-0.181322575,0.183172509) at energy 13.5235996
calculating atomic t-matrix elements atm(n)
check orthogonality between core and continuum state
scalar product between core and continuum state = (0.215178907,-7.336383685E-03)
sqrt(xe/pi) = (1.08555424,-3.627028549E-03)
check ionization potential: 661.387451
value of the mean free path:
-----------------------------------------------------------------
average mean free path due to finite gamma: mfp = 8.81667 angstrom at energy 13.52360
average mean free path in the cluster : mfp = 8.71420 angstrom at energy 13.52360
total mean free path due to Im V and gamma: mfp = 4.38257 angstrom at energy 13.52360
-----------------------------------------------------------------
** phagen terminated normally **
INFO:msspec:Getting the TMatrix...
_________________________________________________________________
PHAGEN
_________________________________________________________________
parameters for this xpd calculation:
-----------------------------------------------------------------
edge= k
potype= hedin norman= stdcrm absorber= 1
coor= angs emin= 13.52 Ry emax= 13.52 Ry
delta= 0.300 Ry gamma= 0.03 Ry eftri= 0.000 Ry
ionization state : neutral
final state potential generated internally
Computes the T-matrix and radial matrix elements
coordinates in angstroms Radii
-----------------------------------------------------------------
Cu 29 0.0000 0.0000 -3.8000 0.0000 0.0000
Cu 29 -1.9000 -1.9000 0.0000 0.0000 0.0000
Cu 29 -1.9000 0.0000 -1.9000 0.0000 0.0000
Cu 29 -3.8000 0.0000 0.0000 0.0000 0.0000
Cu 29 -1.9000 1.9000 0.0000 0.0000 0.0000
Cu 29 0.0000 -1.9000 -1.9000 0.0000 0.0000
Cu 29 0.0000 -3.8000 0.0000 0.0000 0.0000
Cu 29 1.9000 -1.9000 0.0000 0.0000 0.0000
Cu 29 0.0000 1.9000 -1.9000 0.0000 0.0000
Cu 29 1.9000 0.0000 -1.9000 0.0000 0.0000
Cu 29 0.0000 0.0000 0.0000 0.0000 0.0000
Cu 29 1.9000 1.9000 0.0000 0.0000 0.0000
Cu 29 0.0000 3.8000 0.0000 0.0000 0.0000
Cu 29 3.8000 0.0000 0.0000 0.0000 0.0000
-----------------------------------------------------------------
** enter calphas **
---
total energy for atom in ground state
total energy for atom with a hole in k edge
calculated ionization energy for edge k = 8994.86914 eV
---
calculated ionization potential (ryd) = 661.387451
---
symmetrizing coordinates...
symmetrized atomic coordinates of cluster
position
atom no. x y z eq
1 osph 0 0.0000 0.0000 0.0000 0
2 Cu 29 0.0000 0.0000 -5.6422 0
3 Cu 29 -3.5905 -3.5905 1.5388 0
4 Cu 29 -3.5905 0.0000 -2.0517 0
5 Cu 29 -7.1810 0.0000 1.5388 0
6 Cu 29 0.0000 0.0000 1.5388 0
7 Cu 29 -3.5905 3.5905 1.5388 3
8 Cu 29 3.5905 -3.5905 1.5388 3
9 Cu 29 3.5905 3.5905 1.5388 3
10 Cu 29 0.0000 -3.5905 -2.0517 4
11 Cu 29 0.0000 3.5905 -2.0517 4
12 Cu 29 3.5905 0.0000 -2.0517 4
13 Cu 29 0.0000 -7.1810 1.5388 5
14 Cu 29 0.0000 7.1810 1.5388 5
15 Cu 29 7.1810 0.0000 1.5388 5
computing muffin tin potential and phase shifts
generating core state wavefunction
generating final potential (complex hedin-lundqvist exchange)
MT radii for Hydrogen atoms determined by stdcrm unless other options are specified
-----------------------------------------------------------------
i rs(i) i=1,natoms
1 9.80 2 2.64 3 2.60 4 2.44 5 2.61 6 2.46 7 2.60 8 2.60
9 2.60 10 2.44 11 2.44 12 2.44 13 2.61 14 2.61 15 2.61
N.B.: Order of atoms as reshuffled by symmetry routines
-----------------------------------------------------------------
input value for coulomb interst. potential = -0.699999988
and interstitial rs = 3.00000000
lower bound for coulomb interst. potential = -0.232031077
and for interst. rs = 2.76609278
lmax assignment based on l_max = r_mt * k_e + 2
where e is the running energy
optimal lmax chosen according to the running energy e for each atom
number of centers= 14
starting potentials and/or charge densities written to file 13
symmetry information generated internally
symmetry information written to file 14
core initial state of type: 1s1/2
fermi level = -0.16924
generating t_l (for030) and atomic cross section (for050)
gamma = 0.030000 rsint = 4.292365
check in subroutine cont
order of neighb. -- symb. -- dist. from absorber
3 Cu 5.07770586
5 Cu 7.18096018
2 Cu 8.79484367
4 Cu 10.1554117
-----------------------------------------------------------------
1 Cu 0.000000
3 Cu 5.077706
5 Cu 7.180960
2 Cu 8.794844
4 Cu 10.155412
1 Cu 0.000000
3 Cu 5.077706
5 Cu 7.180960
2 Cu 8.794844
4 Cu 10.155412
irho = 2 entering vxc to calculate energy dependent exchange
energy dependent vcon = (-0.151319593,0.168841615) at energy 13.5235996
calculating atomic t-matrix elements atm(n)
check orthogonality between core and continuum state
scalar product between core and continuum state = (0.217915282,-9.193832986E-03)
sqrt(xe/pi) = (1.08495688,-3.348779166E-03)
check ionization potential: 661.387451
value of the mean free path:
-----------------------------------------------------------------
average mean free path due to finite gamma: mfp = 8.81667 angstrom at energy 13.52360
average mean free path in the cluster : mfp = 9.13179 angstrom at energy 13.52360
total mean free path due to Im V and gamma: mfp = 4.48573 angstrom at energy 13.52360
-----------------------------------------------------------------
** phagen terminated normally **

View File

@ -1,8 +1,30 @@
import ase #!/usr/bin/env python
from .version import __version__ #
from .misc import LOGGER # Copyright © 2016-2020 - Rennes Physics Institute
#
# This file is part of msspec.
#
# msspec is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# msspec is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this msspec. If not, see <http://www.gnu.org/licenses/>.
#
# Source file : src/msspec/__init__.py
# Last modified: ven. 10 avril 2020 17:22:12
# Committed by : "Sylvain Tricot <sylvain.tricot@univ-rennes1.fr>"
import ase
from msspec.misc import LOGGER
from msspec.version import __version__
__sha__ = "$Id$"
def init_msspec(): def init_msspec():
LOGGER.debug('Initialization of the msspec module') LOGGER.debug('Initialization of the msspec module')

View File

@ -1,4 +1,5 @@
# coding: utf-8 # coding: utf-8
# vim: set et ts=4 sw=4 fdm=indent mouse=a cc=+1 tw=80:
""" """
Module calcio Module calcio
@ -6,13 +7,16 @@ Module calcio
""" """
import numpy as np
from datetime import datetime from datetime import datetime
import ase
import re import re
import numpy as np
import ase
from ase import Atom, Atoms
from ase.data import chemical_symbols
from msspec.misc import UREG, LOGGER from msspec.misc import UREG, LOGGER
class PhagenIO(object): class PhagenIO(object):
def __init__(self, phagen_parameters, malloc_parameters): def __init__(self, phagen_parameters, malloc_parameters):
self.parameters = phagen_parameters self.parameters = phagen_parameters
@ -27,8 +31,9 @@ class PhagenIO(object):
self.nlmax = None self.nlmax = None
self.energies = None self.energies = None
def write_input_file(self, filename='input.ms'): def write_input_file(self, filename='input.ms'): # noqa: C901
# in input folder # in input folder
LOGGER.debug("Writing Phagen input file")
atoms = self.parameters.atoms atoms = self.parameters.atoms
if not atoms.has('mt_radii'): if not atoms.has('mt_radii'):
for a in atoms: for a in atoms:
@ -58,7 +63,7 @@ class PhagenIO(object):
# other sections # other sections
continue continue
value = parameter.value value = parameter.value
if isinstance(value, str) and not re.match('\..*\.', value): if isinstance(value, str) and not re.match(r'\..*\.', value):
s = ' {}=\'{:s}\''.format(name, str(parameter)) s = ' {}=\'{:s}\''.format(name, str(parameter))
else: else:
s = ' {}={:s}'.format(name, str(parameter)) s = ' {}={:s}'.format(name, str(parameter))
@ -167,7 +172,8 @@ class PhagenIO(object):
lines = pattern.split(content) lines = pattern.split(content)
# get the number of atoms (nat) and the number of energy points (ne) # get the number of atoms (nat) and the number of energy points (ne)
nat, ne, _, ipot, lmax_mode = list(map(int, content.split('\n')[0].split())) nat, ne, _, ipot, lmax_mode = list(map(int,
content.split('\n')[0].split()))
self.nat = nat self.nat = nat
self.ne = ne self.ne = ne
self.ipot = ipot self.ipot = ipot
@ -181,7 +187,7 @@ class PhagenIO(object):
if not re.match(r'^\d+$', n): if not re.match(r'^\d+$', n):
numbers.append(float(n)) numbers.append(float(n))
if len(numbers) > 0: if len(numbers) > 0:
array = np.array(numbers).reshape((-1, 4)) # pylint: disable=no-member array = np.array(numbers).reshape((-1, 4))
atom_data.append(array) atom_data.append(array)
# construct the data array # construct the data array
@ -265,8 +271,27 @@ class PhagenIO(object):
proto_index = int(data[i, -1]) proto_index = int(data[i, -1])
proto_indices.append(proto_index) proto_indices.append(proto_index)
atoms.set_array('proto_indices', np.array(proto_indices)) atoms.set_array('proto_indices', np.array(proto_indices))
self.nateqm = int(np.max([len(np.where(data[:,-1]==i)[0]) for i in range( self.nateqm = int(np.max([
1, self.nat + 1)])) len(np.where(data[:, -1] == i)[0]) for i in range(1, self.nat + 1)
]))
def load_prototypical_atoms(self, filename='molinpot3.out'):
with open(filename, 'r') as fd:
content = fd.readlines()
content = content[-len(self.parameters.atoms):]
cluster = Atoms()
for line in content:
datatxt = np.array(re.split(r'\s+', line.strip(' \n')))
neq = int(datatxt[-1])
if neq == 0:
Z = int(datatxt[1])
xyz = (datatxt[[2, 3, 4]].astype(float) * UREG.a0).to(
'angstrom').magnitude
sym = chemical_symbols[Z]
cluster += Atom(sym, position=xyz)
self.prototypical_atoms = cluster
return cluster
def load_potential_file(self, filename='plot_vc.dat'): def load_potential_file(self, filename='plot_vc.dat'):
a_index = 0 a_index = 0
@ -279,11 +304,13 @@ class PhagenIO(object):
a_index += 1 a_index += 1
d = d.split() d = d.split()
a = {'Symbol': d[1], 'distance': float(d[4]), a = {'Symbol': d[1], 'distance': float(d[4]),
'coord': np.array([float(d[7]), float(d[8]), float(d[9])]), 'coord': np.array([float(d[7]), float(d[8]),
float(d[9])]),
'index': int(a_index), 'values': []} 'index': int(a_index), 'values': []}
pot_data.append(a) pot_data.append(a)
else: else:
pot_data[a_index - 1]['values'].append(tuple(float(_) for _ in d.split())) pot_data[a_index - 1]['values'].append(
tuple(float(_) for _ in d.split()))
# convert the values list to a numpy array # convert the values list to a numpy array
for _pot_data in pot_data: for _pot_data in pot_data:
@ -292,27 +319,30 @@ class PhagenIO(object):
return pot_data return pot_data
class SpecIO(object): class SpecIO(object):
def __init__(self, parameters, malloc_parameters, phagenio): def __init__(self, parameters, malloc_parameters, phagenio):
self.parameters = parameters self.parameters = parameters
self.malloc_parameters = malloc_parameters self.malloc_parameters = malloc_parameters
self.phagenio = phagenio self.phagenio = phagenio
def write_input_file(self, filename='spec.dat'): def write_input_file(self, filename='spec.dat'): # noqa: C901
def title(t, shift=4, width=79, center=True): def title(t, shift=4, width=79, center=True):
if center: if center:
s = ('{}*{:^%ds}*\n' % (width - shift - 2)).format(' ' * shift, t) s = ('{}*{:^%ds}*\n' % (width - shift - 2)
).format(' ' * shift, t)
else: else:
s = ('{}*{:%ds}*\n' % (width - shift - 2)).format(' ' * shift, t) s = ('{}*{:%ds}*\n' % (width - shift - 2)
).format(' ' * shift, t)
return s return s
def rule(tabs=(5, 10, 10, 10, 10), symbol='=', shift=4, width=79): def rule(tabs=(5, 10, 10, 10, 10), symbol='=', shift=4, width=79):
s = ' ' * shift + '*' + symbol * (width - shift - 2) + '*\n' s = ' ' * shift + '*' + symbol * (width - shift - 2) + '*\n'
t = np.cumsum(tabs) + shift t = np.cumsum(tabs) + shift
l = list(s) sep = list(s)
for i in t: for i in t:
l[i] = '+' sep[i] = '+'
return ''.join(l) return ''.join(sep)
def fillstr(a, b, index, justify='left'): def fillstr(a, b, index, justify='left'):
alist = list(a) alist = list(a)
@ -426,7 +456,8 @@ class SpecIO(object):
content += rule() content += rule()
line = create_line("SPECTRO,ISPIN,IDICHR,IPOL") line = create_line("SPECTRO,ISPIN,IDICHR,IPOL")
line = fillstr(line, str(p.calctype_spectro), 9, 'left') line = fillstr(line, str(p.calctype_spectro), 9, 'left')
line = fillstr(line, str(p.get_parameter('calctype_ispin')), 19, 'left') line = fillstr(line,
str(p.get_parameter('calctype_ispin')), 19, 'left')
line = fillstr(line, str(p.calctype_idichr), 29, 'left') line = fillstr(line, str(p.calctype_idichr), 29, 'left')
line = fillstr(line, str(p.calctype_ipol), 39, 'left') line = fillstr(line, str(p.calctype_ipol), 39, 'left')
content += line content += line
@ -476,11 +507,13 @@ class SpecIO(object):
line = fillstr(line, str(p.get_parameter('ped_imod')), 9, 'decimal') line = fillstr(line, str(p.get_parameter('ped_imod')), 9, 'decimal')
line = fillstr(line, str(p.get_parameter('ped_imoy')), 19, 'decimal') line = fillstr(line, str(p.get_parameter('ped_imoy')), 19, 'decimal')
line = fillstr(line, str(p.get_parameter('ped_accept')), 29, 'decimal') line = fillstr(line, str(p.get_parameter('ped_accept')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('ped_ichkdir')), 39, 'decimal') line = fillstr(line,
str(p.get_parameter('ped_ichkdir')), 39, 'decimal')
content += line content += line
content += rule() content += rule()
content += title(' ' * 22 + 'LEED EXPERIMENTAL PARAMETERS :', center=False) content += title(' ' * 22 + 'LEED EXPERIMENTAL PARAMETERS :',
center=False)
content += rule() content += rule()
line = create_line("IPHI,ITHETA,IE,IFTHET") line = create_line("IPHI,ITHETA,IE,IFTHET")
line = fillstr(line, str(p.get_parameter('leed_iphi')), 9, 'left') line = fillstr(line, str(p.get_parameter('leed_iphi')), 9, 'left')
@ -496,48 +529,57 @@ class SpecIO(object):
content += line content += line
line = create_line("PHI0,THETA0,E0,R0") line = create_line("PHI0,THETA0,E0,R0")
line = fillstr(line, str(p.get_parameter('leed_phi0')), 9, 'decimal') line = fillstr(line, str(p.get_parameter('leed_phi0')), 9, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_theta0')), 19, 'decimal') line = fillstr(line,
str(p.get_parameter('leed_theta0')), 19, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_e0')), 29, 'decimal') line = fillstr(line, str(p.get_parameter('leed_e0')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_r0')), 39, 'decimal') line = fillstr(line, str(p.get_parameter('leed_r0')), 39, 'decimal')
content += line content += line
line = create_line("PHI1,THETA1,E1,R1") line = create_line("PHI1,THETA1,E1,R1")
line = fillstr(line, str(p.get_parameter('leed_phi1')), 9, 'decimal') line = fillstr(line, str(p.get_parameter('leed_phi1')), 9, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_theta1')), 19, 'decimal') line = fillstr(line,
str(p.get_parameter('leed_theta1')), 19, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_e1')), 29, 'decimal') line = fillstr(line, str(p.get_parameter('leed_e1')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_r1')), 39, 'decimal') line = fillstr(line, str(p.get_parameter('leed_r1')), 39, 'decimal')
content += line content += line
line = create_line("TH_INI,PHI_INI") line = create_line("TH_INI,PHI_INI")
line = fillstr(line, str(p.get_parameter('leed_thini')), 9, 'decimal') line = fillstr(line, str(p.get_parameter('leed_thini')), 9, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_phiini')), 19, 'decimal') line = fillstr(line,
str(p.get_parameter('leed_phiini')), 19, 'decimal')
content += line content += line
line = create_line("IMOD,IMOY,ACCEPT,ICHKDIR") line = create_line("IMOD,IMOY,ACCEPT,ICHKDIR")
line = fillstr(line, str(p.get_parameter('leed_imod')), 9, 'decimal') line = fillstr(line, str(p.get_parameter('leed_imod')), 9, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_imoy')), 19, 'decimal') line = fillstr(line, str(p.get_parameter('leed_imoy')), 19, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_accept')), 29, 'decimal') line = fillstr(line,
str(p.get_parameter('leed_accept')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('leed_ichkdir')), 39, line = fillstr(line, str(p.get_parameter('leed_ichkdir')), 39,
'decimal') 'decimal')
content += line content += line
content += rule() content += rule()
content += title(' ' * 21 + 'EXAFS EXPERIMENTAL PARAMETERS :', center=False) content += title(' ' * 21 + 'EXAFS EXPERIMENTAL PARAMETERS :',
center=False)
content += rule() content += rule()
line = create_line("EDGE,INITL,THLUM,PHILUM") line = create_line("EDGE,INITL,THLUM,PHILUM")
line = fillstr(line, str(p.get_parameter('exafs_edge')), 9, 'left') line = fillstr(line, str(p.get_parameter('exafs_edge')), 9, 'left')
line = fillstr(line, str(p.get_parameter('exafs_initl')), 19, 'left') line = fillstr(line, str(p.get_parameter('exafs_initl')), 19, 'left')
line = fillstr(line, str(p.get_parameter('exafs_thlum')), 29, 'decimal') line = fillstr(line,
str(p.get_parameter('exafs_thlum')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('exafs_philum')), 39, line = fillstr(line, str(p.get_parameter('exafs_philum')), 39,
'decimal') 'decimal')
content += line content += line
line = create_line("NE,EK_INI,EK_FIN,EPH_INI") line = create_line("NE,EK_INI,EK_FIN,EPH_INI")
line = fillstr(line, str(p.get_parameter('exafs_ne')), 9, 'left') line = fillstr(line, str(p.get_parameter('exafs_ne')), 9, 'left')
line = fillstr(line, str(p.get_parameter('exafs_ekini')), 19, 'decimal') line = fillstr(line,
line = fillstr(line, str(p.get_parameter('exafs_ekfin')), 29, 'decimal') str(p.get_parameter('exafs_ekini')), 19, 'decimal')
line = fillstr(line,
str(p.get_parameter('exafs_ekfin')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('exafs_ephini')), 39, line = fillstr(line, str(p.get_parameter('exafs_ephini')), 39,
'decimal') 'decimal')
content += line content += line
content += rule() content += rule()
content += title(' ' * 22 + 'AED EXPERIMENTAL PARAMETERS :', center=False) content += title(' ' * 22 + 'AED EXPERIMENTAL PARAMETERS :',
center=False)
content += rule() content += rule()
line = create_line("EDGE_C,EDGE_I,EDGE_A") line = create_line("EDGE_C,EDGE_I,EDGE_A")
line = fillstr(line, str(p.get_parameter('aed_edgec')), 9, 'left') line = fillstr(line, str(p.get_parameter('aed_edgec')), 9, 'left')
@ -605,7 +647,8 @@ class SpecIO(object):
line = fillstr(line, str(p.get_parameter('eigval_ipwm')), 9, 'left') line = fillstr(line, str(p.get_parameter('eigval_ipwm')), 9, 'left')
line = fillstr(line, str(p.get_parameter('eigval_method')), 19, 'left') line = fillstr(line, str(p.get_parameter('eigval_method')), 19, 'left')
line = fillstr(line, str(p.get_parameter('eigval_acc')), 29, 'decimal') line = fillstr(line, str(p.get_parameter('eigval_acc')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('eigval_expo')), 39, 'decimal') line = fillstr(line,
str(p.get_parameter('eigval_expo')), 39, 'decimal')
content += line content += line
line = create_line("N_MAX,N_ITER,N_TABLE,SHIFT") line = create_line("N_MAX,N_ITER,N_TABLE,SHIFT")
line = fillstr(line, str(p.get_parameter('eigval_nmax')), 9, 'left') line = fillstr(line, str(p.get_parameter('eigval_nmax')), 9, 'left')
@ -673,7 +716,8 @@ class SpecIO(object):
thfwd_arr = np.ones((nat)) thfwd_arr = np.ones((nat))
path_filtering = p.extra_parameters['calculation'].get_parameter( path_filtering = p.extra_parameters['calculation'].get_parameter(
'path_filtering').value 'path_filtering').value
if path_filtering != None and 'backward_scattering' in path_filtering: if (path_filtering is not None and
'backward_scattering' in path_filtering):
ibwd_arr = np.ones((nat), dtype=np.int) ibwd_arr = np.ones((nat), dtype=np.int)
else: else:
ibwd_arr = np.zeros((nat), dtype=np.int) ibwd_arr = np.zeros((nat), dtype=np.int)
@ -694,7 +738,8 @@ class SpecIO(object):
line = create_line("IPW,NCUT,PCTINT,IPP") line = create_line("IPW,NCUT,PCTINT,IPP")
line = fillstr(line, str(p.get_parameter('calc_ipw')), 9, 'left') line = fillstr(line, str(p.get_parameter('calc_ipw')), 9, 'left')
line = fillstr(line, str(p.get_parameter('calc_ncut')), 19, 'left') line = fillstr(line, str(p.get_parameter('calc_ncut')), 19, 'left')
line = fillstr(line, str(p.get_parameter('calc_pctint')), 29, 'decimal') line = fillstr(line,
str(p.get_parameter('calc_pctint')), 29, 'decimal')
line = fillstr(line, str(p.get_parameter('calc_ipp')), 39, 'left') line = fillstr(line, str(p.get_parameter('calc_ipp')), 39, 'left')
content += line content += line
line = create_line("ILENGTH,RLENGTH,UNLENGTH") line = create_line("ILENGTH,RLENGTH,UNLENGTH")
@ -713,7 +758,8 @@ class SpecIO(object):
idcm = format(2, 'd') idcm = format(2, 'd')
temp = format(0., '.2f') temp = format(0., '.2f')
td = format(500., '.2f') td = format(500., '.2f')
LOGGER.warning('Vibrational damping is disabled for this calculation.') LOGGER.warning('Vibrational damping is disabled for this '
'calculation.')
else: else:
idwsph = str(p.get_parameter('calc_idwsph')) idwsph = str(p.get_parameter('calc_idwsph'))
idcm = str(p.get_parameter('calc_idcm')) idcm = str(p.get_parameter('calc_idcm'))
@ -757,8 +803,9 @@ class SpecIO(object):
content += title(' ' * 17 + 'INPUT FILES (PHD, EXAFS, LEED, AED, ' content += title(' ' * 17 + 'INPUT FILES (PHD, EXAFS, LEED, AED, '
'APECS) :', center=False) 'APECS) :', center=False)
content += rule(tabs=(), symbol='-') content += rule(tabs=(), symbol='-')
content += title(' ' * 8 + 'NAME' + ' ' * 20 + 'UNIT' + ' ' * 16 + 'TYPE', content += title(
center=False) ' ' * 8 + 'NAME' + ' ' * 20 + 'UNIT' + ' ' * 16 + 'TYPE',
center=False)
content += rule(tabs=(5, 23, 7, 10)) content += rule(tabs=(5, 23, 7, 10))
line = create_line("DATA FILE,UNIT") line = create_line("DATA FILE,UNIT")
line = fillstr(line, str(p.get_parameter('input_data')), 9, 'right') line = fillstr(line, str(p.get_parameter('input_data')), 9, 'right')
@ -791,8 +838,9 @@ class SpecIO(object):
center=False) center=False)
content += title(' ' * 28 + '(AUGER ELECTRON)', center=False) content += title(' ' * 28 + '(AUGER ELECTRON)', center=False)
content += rule(tabs=(), symbol='-') content += rule(tabs=(), symbol='-')
content += title(' ' * 8 + 'NAME' + ' ' * 20 + 'UNIT' + ' ' * 16 + 'TYPE', content += title(
center=False) ' ' * 8 + 'NAME' + ' ' * 20 + 'UNIT' + ' ' * 16 + 'TYPE',
center=False)
content += rule(tabs=(5, 23, 7, 10)) content += rule(tabs=(5, 23, 7, 10))
line = create_line("PHASE SHIFTS/TL FILE,UNIT") line = create_line("PHASE SHIFTS/TL FILE,UNIT")
line = fillstr(line, str(p.get_parameter('input2_tl')), 9, 'right') line = fillstr(line, str(p.get_parameter('input2_tl')), 9, 'right')
@ -810,8 +858,9 @@ class SpecIO(object):
content += title(' ' * 29 + 'OUTPUT FILES :', center=False) content += title(' ' * 29 + 'OUTPUT FILES :', center=False)
content += rule(tabs=(), symbol='-') content += rule(tabs=(), symbol='-')
content += title(' ' * 8 + 'NAME' + ' ' * 20 + 'UNIT' + ' ' * 16 + 'TYPE', content += title(
center=False) ' ' * 8 + 'NAME' + ' ' * 20 + 'UNIT' + ' ' * 16 + 'TYPE',
center=False)
content += rule(tabs=(5, 23, 7, 10)) content += rule(tabs=(5, 23, 7, 10))
line = create_line("CONTROL FILE,UNIT") line = create_line("CONTROL FILE,UNIT")
line = fillstr(line, str(p.get_parameter('output_log')), 9, 'right') line = fillstr(line, str(p.get_parameter('output_log')), 9, 'right')
@ -826,7 +875,8 @@ class SpecIO(object):
line = fillstr(line, str(p.get_parameter('output_unit11')), 39, 'left') line = fillstr(line, str(p.get_parameter('output_unit11')), 39, 'left')
content += line content += line
line = create_line("AUGMENTED CLUSTER FILE,UNIT") line = create_line("AUGMENTED CLUSTER FILE,UNIT")
line = fillstr(line, str(p.get_parameter('output_augclus')), 9, 'right') line = fillstr(line,
str(p.get_parameter('output_augclus')), 9, 'right')
line = fillstr(line, str(p.get_parameter('output_unit12')), 39, 'left') line = fillstr(line, str(p.get_parameter('output_unit12')), 39, 'left')
content += line content += line
content += rule(tabs=(5, 23, 7, 10)) content += rule(tabs=(5, 23, 7, 10))
@ -874,15 +924,13 @@ class SpecIO(object):
# backup the content in memory # backup the content in memory
old_content = content old_content = content
""" # for key in ('NATP_M', 'NATCLU_M', 'NE_M', 'NEMET_M', 'LI_M', 'NL_M',
for key in ('NATP_M', 'NATCLU_M', 'NE_M', 'NEMET_M', 'LI_M', 'NL_M', # 'NO_ST_M'):
'NO_ST_M'): # required = requirements[key]
required = requirements[key] # limit = self.malloc_parameters.get_parameter(key).value
limit = self.malloc_parameters.get_parameter(key).value # value = required if required > limit else limit
value = required if required > limit else limit # content = re.sub(r'({:s}\s*=\s*)\d+'.format(key),
content = re.sub(r'({:s}\s*=\s*)\d+'.format(key), # r'\g<1>{:d}'.format(value), content)
r'\g<1>{:d}'.format(value), content)
"""
for key in ('NAT_EQ_M', 'N_CL_N_M', 'NDIF_M', 'NSO_M', 'NTEMP_M', for key in ('NAT_EQ_M', 'N_CL_N_M', 'NDIF_M', 'NSO_M', 'NTEMP_M',
'NODES_EX_M', 'NSPIN_M', 'NTH_M', 'NPH_M', 'NDIM_M', 'NODES_EX_M', 'NSPIN_M', 'NTH_M', 'NPH_M', 'NDIM_M',
@ -895,7 +943,6 @@ class SpecIO(object):
content = re.sub(r'({:s}\s*=\s*)\d+'.format(key), content = re.sub(r'({:s}\s*=\s*)\d+'.format(key),
r'\g<1>{:d}'.format(value), content) r'\g<1>{:d}'.format(value), content)
modified = False modified = False
if content != old_content: if content != old_content:
with open(filename, 'w') as fd: with open(filename, 'w') as fd:
@ -945,7 +992,7 @@ class SpecIO(object):
data = np.loadtxt(filename, skiprows=skip, unpack=True) data = np.loadtxt(filename, skiprows=skip, unpack=True)
if len(data.shape) <= 1: if len(data.shape) <= 1:
data = data.reshape((2,-1)) data = data.reshape((2, -1))
return data return data
def load_facdif(self, filename='facdif1.dat'): def load_facdif(self, filename='facdif1.dat'):
@ -956,8 +1003,4 @@ class SpecIO(object):
pat = re.compile(r'ORDER\s+(\d+)\s+TOTAL NUMBER OF PATHS\s+:\s+(\d+)') pat = re.compile(r'ORDER\s+(\d+)\s+TOTAL NUMBER OF PATHS\s+:\s+(\d+)')
with open(filename, 'r') as fd: with open(filename, 'r') as fd:
content = fd.read() content = fd.read()
#return pat.findall(content.replace('\n', '__cr__'))
return pat.findall(content) return pat.findall(content)

View File

@ -1,5 +1,25 @@
# coding: utf-8 #!/usr/bin/env python
# vim: set et sw=4 ts=4 sts=4 nu ai cc=+0 fdm=indent mouse=a tw=80: #
# Copyright © 2016-2020 - Rennes Physics Institute
#
# This file is part of msspec.
#
# msspec is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# msspec is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this msspec. If not, see <http://www.gnu.org/licenses/>.
#
# Source file : src/msspec/calculator.py
# Last modified: ven. 10 avril 2020 17:19:24
# Committed by : "Sylvain Tricot <sylvain.tricot@univ-rennes1.fr>"
""" """
Module calculator Module calculator
================= =================
@ -21,54 +41,58 @@ For more information on MsSpec, follow this
""" """
import inspect
import os import os
import re
import shutil import shutil
import sys import sys
import re
import inspect
from subprocess import Popen, PIPE
from shutil import copyfile, rmtree
from datetime import datetime
import time import time
from io import StringIO
from collections import OrderedDict from collections import OrderedDict
from datetime import datetime
from io import StringIO
from shutil import copyfile
from shutil import rmtree
from subprocess import PIPE
from subprocess import Popen
from ase.calculators.calculator import Calculator
import ase.data
import ase.atom import ase.atom
import ase.atoms import ase.atoms
import ase.data
import numpy as np import numpy as np
from ase.calculators.calculator import Calculator
from terminaltables.ascii_table import AsciiTable
from msspec import iodata from msspec import iodata
from msspec.data import electron_be from msspec.calcio import PhagenIO
from msspec.calcio import SpecIO
from msspec.config import Config from msspec.config import Config
from msspec.misc import (UREG, LOGGER, get_call_info, from msspec.data import electron_be
get_level_from_electron_configuration, from msspec.misc import get_call_info
XRaySource, set_log_output, log_process_output) from msspec.misc import get_level_from_electron_configuration
from msspec.utils import get_atom_index from msspec.misc import log_process_output
from msspec.misc import LOGGER
from msspec.parameters import (PhagenParameters, PhagenMallocParameters, from msspec.misc import set_log_output
SpecParameters, SpecMallocParameters, from msspec.misc import UREG
GlobalParameters, MuffintinParameters, from msspec.misc import XRaySource
TMatrixParameters, SourceParameters, from msspec.parameters import CalculationParameters
DetectorParameters, ScanParameters, from msspec.parameters import DetectorParameters
CalculationParameters, from msspec.parameters import EIGParameters
PEDParameters, EIGParameters) from msspec.parameters import GlobalParameters
from msspec.calcio import PhagenIO, SpecIO from msspec.parameters import MuffintinParameters
from msspec.parameters import PEDParameters
from msspec.parameters import PhagenMallocParameters
from msspec.parameters import PhagenParameters
from msspec.parameters import ScanParameters
from msspec.parameters import SourceParameters
from msspec.parameters import SpecMallocParameters
from msspec.parameters import SpecParameters
from msspec.parameters import TMatrixParameters
from msspec.phagen.fortran.libphagen import main as do_phagen from msspec.phagen.fortran.libphagen import main as do_phagen
from msspec.spec.fortran import _phd_se_noso_nosp_nosym, _phd_mi_noso_nosp_nosym
from msspec.spec.fortran import _eig_mi from msspec.spec.fortran import _eig_mi
from msspec.spec.fortran import _eig_pw from msspec.spec.fortran import _eig_pw
from msspec.spec.fortran import _phd_mi_noso_nosp_nosym
from msspec.spec.fortran import _phd_se_noso_nosp_nosym
from terminaltables.ascii_table import AsciiTable from msspec.utils import get_atom_index
class _MSCALCULATOR(Calculator): class _MSCALCULATOR(Calculator):
@ -78,9 +102,9 @@ class _MSCALCULATOR(Calculator):
""" """
implemented_properties = ['', ] implemented_properties = ['', ]
__data = {} __data = {}
def __init__(self, spectroscopy='PED', algorithm='expansion', def __init__(self, spectroscopy='PED', algorithm='expansion',
polarization=None, dichroism=None, spinpol=False, polarization=None, dichroism=None, spinpol=False,
folder='./calc', txt='-', **kwargs): folder='./calc', txt='-', **kwargs):
stdout = sys.stdout stdout = sys.stdout
if isinstance(txt, str) and txt != '-': if isinstance(txt, str) and txt != '-':
@ -96,7 +120,7 @@ class _MSCALCULATOR(Calculator):
######################################################################## ########################################################################
# Init the upper class # Init the upper class
Calculator.__init__(self, **kwargs) Calculator.__init__(self, **kwargs)
######################################################################## ########################################################################
LOGGER.debug(' create low level parameters') LOGGER.debug(' create low level parameters')
######################################################################## ########################################################################
@ -104,15 +128,15 @@ class _MSCALCULATOR(Calculator):
self.phagen_malloc_parameters = PhagenMallocParameters() self.phagen_malloc_parameters = PhagenMallocParameters()
self.spec_parameters = SpecParameters() self.spec_parameters = SpecParameters()
self.spec_malloc_parameters = SpecMallocParameters() self.spec_malloc_parameters = SpecMallocParameters()
######################################################################## ########################################################################
LOGGER.debug(' create higher level parameters') LOGGER.debug(' create higher level parameters')
######################################################################## ########################################################################
self.tmatrix_parameters = TMatrixParameters(self.phagen_parameters) self.tmatrix_parameters = TMatrixParameters(self.phagen_parameters)
self.muffintin_parameters = MuffintinParameters(self.phagen_parameters, self.muffintin_parameters = MuffintinParameters(self.phagen_parameters,
self.spec_parameters) self.spec_parameters)
self.global_parameters = GlobalParameters(self.phagen_parameters, self.global_parameters = GlobalParameters(self.phagen_parameters,
self.spec_parameters) self.spec_parameters)
@ -124,25 +148,25 @@ class _MSCALCULATOR(Calculator):
self.spec_parameters) self.spec_parameters)
else: else:
raise NameError('No such spectroscopy') raise NameError('No such spectroscopy')
self.source_parameters = SourceParameters(self.global_parameters, self.source_parameters = SourceParameters(self.global_parameters,
self.phagen_parameters, self.phagen_parameters,
self.spec_parameters) self.spec_parameters)
self.detector_parameters = DetectorParameters(self.global_parameters, self.detector_parameters = DetectorParameters(self.global_parameters,
self.phagen_parameters, self.phagen_parameters,
self.spec_parameters) self.spec_parameters)
self.scan_parameters = ScanParameters(self.global_parameters, self.scan_parameters = ScanParameters(self.global_parameters,
self.phagen_parameters, self.phagen_parameters,
self.spec_parameters) self.spec_parameters)
self.calculation_parameters = CalculationParameters( self.calculation_parameters = CalculationParameters(
self.global_parameters, self.phagen_parameters, self.spec_parameters) self.global_parameters, self.phagen_parameters, self.spec_parameters)
# initialize all parameters with defaults values # initialize all parameters with defaults values
LOGGER.info("Set default values =========================================") LOGGER.info("Set default values =========================================")
for p in (list(self.global_parameters) + for p in (list(self.global_parameters) +
list(self.muffintin_parameters) + list(self.muffintin_parameters) +
list(self.tmatrix_parameters) + list(self.tmatrix_parameters) +
list(self.source_parameters) + list(self.source_parameters) +
@ -160,15 +184,15 @@ class _MSCALCULATOR(Calculator):
self.global_parameters.dichroism = dichroism self.global_parameters.dichroism = dichroism
self.global_parameters.spinpol = spinpol self.global_parameters.spinpol = spinpol
self.global_parameters.folder = folder self.global_parameters.folder = folder
self.phagenio = PhagenIO(self.phagen_parameters, self.phagenio = PhagenIO(self.phagen_parameters,
self.phagen_malloc_parameters) self.phagen_malloc_parameters)
self.specio = SpecIO(self.spec_parameters, self.specio = SpecIO(self.spec_parameters,
self.spec_malloc_parameters, self.spec_malloc_parameters,
self.phagenio) self.phagenio)
######################################################################## ########################################################################
LOGGER.debug(' create a space dedicated to the calculation') LOGGER.debug(' create a space dedicated to the calculation')
######################################################################## ########################################################################
@ -180,7 +204,7 @@ class _MSCALCULATOR(Calculator):
os.makedirs(self.tmp_folder) os.makedirs(self.tmp_folder)
os.makedirs(os.path.join(self.tmp_folder, 'input')) os.makedirs(os.path.join(self.tmp_folder, 'input'))
os.makedirs(os.path.join(self.tmp_folder, 'output')) os.makedirs(os.path.join(self.tmp_folder, 'output'))
#copyfile(os.path.join(self.msspec_folder, 'ase', 'Makefile'), #copyfile(os.path.join(self.msspec_folder, 'ase', 'Makefile'),
# os.path.join(self.tmp_folder, 'Makefile')) # os.path.join(self.tmp_folder, 'Makefile'))
#os.chdir(self.tmp_folder) #os.chdir(self.tmp_folder)
@ -202,7 +226,7 @@ class _MSCALCULATOR(Calculator):
dichro_spinpol = True dichro_spinpol = True
#spin = 'NO' #spin = 'NO'
#if spinpol or dichro_spinpol: #if spinpol or dichro_spinpol:
# spin = 'YES' # spin = 'YES'
#if spin == 'YES': #if spin == 'YES':
@ -225,7 +249,7 @@ class _MSCALCULATOR(Calculator):
######################################################################## ########################################################################
LOGGER.debug(' initialization done.\n') LOGGER.debug(' initialization done.\n')
######################################################################## ########################################################################
def _make(self, target): def _make(self, target):
LOGGER.debug(get_call_info(inspect.currentframe())) LOGGER.debug(get_call_info(inspect.currentframe()))
os.chdir(self.tmp_folder) os.chdir(self.tmp_folder)
@ -252,11 +276,11 @@ class _MSCALCULATOR(Calculator):
LOGGER.error("Unable to successfully run the target: {}".format(target)) LOGGER.error("Unable to successfully run the target: {}".format(target))
sys.exit(1) sys.exit(1)
def _guess_ke(self, level): def _guess_ke(self, level):
""" Try to guess the kinetic energy based on the level and """ Try to guess the kinetic energy based on the level and
the source energy. If the kinetic energy cannot be infered the source energy. If the kinetic energy cannot be infered
because the level is not reported in the database, the returned because the level is not reported in the database, the returned
value is None. value is None.
""" """
try: try:
@ -274,39 +298,38 @@ class _MSCALCULATOR(Calculator):
ke = source_energy - binding_energy - wf ke = source_energy - binding_energy - wf
#return np.array(ke, dtype=np.float).flatten() #return np.array(ke, dtype=np.float).flatten()
return ke return ke
def run_phagen(self): def run_phagen(self):
#include_fname = os.path.join(self.tmp_folder, 'src/msxas3.inc')
input_fname = os.path.join(self.tmp_folder, 'input/input.ms') input_fname = os.path.join(self.tmp_folder, 'input/input.ms')
modified = self.phagenio.write_input_file(filename=input_fname)
#mod0 = self.phagenio.write_include_file(filename=include_fname)
mod1 = self.phagenio.write_input_file(filename=input_fname) # Write the external potential file
pot = self.tmatrix_parameters.potential
self.modified = self.modified or mod1 #or mod0 or mod1 if pot != 'muffin_tin':
inpot_fname = os.path.join(self.tmp_folder, 'output/fort.2')
#if self.modified: mflag = pot.write(inpot_fname, self.phagenio.prototypical_atoms)
if mod1: modified = modified or mflag
# run phagen # Make a copy of the input potential file
#self._make('tmatrix') dest = os.path.join(self.tmp_folder, 'input/extpot.dat')
shutil.copy(inpot_fname, dest)
# Run if input files changed
if modified:
# Run phagen
os.chdir(os.path.join(self.tmp_folder, 'output')) os.chdir(os.path.join(self.tmp_folder, 'output'))
# copy external potential file to the workspace
if self.tmatrix_parameters.potential in ('lmto', 'spkkr', 'msf'):
fname = os.path.join(self.init_folder,
self.tmatrix_parameters.potential_file)
shutil.copy(fname, 'fort.2')
do_phagen() do_phagen()
# rename some output files to be more explicit # Copy some output files to be more explicit
#os.rename('fort.10', 'cluster.clu')
#os.rename('fort.35', 'tmatrix.tl')
#os.rename('fort.55', 'tmatrix.rad')
#shutil.copy('fort.10', 'cluster.clu')
shutil.copy('clus/clus.out', 'cluster.clu') shutil.copy('clus/clus.out', 'cluster.clu')
shutil.copy('fort.35', 'tmatrix.tl') shutil.copy('fort.35', 'tmatrix.tl')
shutil.copy('fort.55', 'tmatrix.rad') shutil.copy('fort.55', 'tmatrix.rad')
# Load data
self.phagenio.load_tl_file('tmatrix.tl')
self.phagenio.load_cluster_file('cluster.clu')
self.phagenio.load_prototypical_atoms('div/molinpot3.out')
# Change back to the init_folder
os.chdir(self.init_folder) os.chdir(self.init_folder)
def run_spec(self, malloc={}): def run_spec(self, malloc={}):
def get_li(level): def get_li(level):
@ -317,14 +340,14 @@ class _MSCALCULATOR(Calculator):
#include_fname = os.path.join(self.tmp_folder, 'src/spec.inc') #include_fname = os.path.join(self.tmp_folder, 'src/spec.inc')
input_fname = os.path.join(self.tmp_folder, 'input/spec.dat') input_fname = os.path.join(self.tmp_folder, 'input/spec.dat')
kdirs_fname = os.path.join(self.tmp_folder, 'input/kdirs.dat') kdirs_fname = os.path.join(self.tmp_folder, 'input/kdirs.dat')
mod0 = self.specio.write_input_file(filename=input_fname) mod0 = self.specio.write_input_file(filename=input_fname)
#mod1 = self.specio.write_include_file(filename=include_fname) #mod1 = self.specio.write_include_file(filename=include_fname)
mod2 = self.specio.write_kdirs_file(filename=kdirs_fname) mod2 = self.specio.write_kdirs_file(filename=kdirs_fname)
#self.modified = self.modified or mod0 or mod1 or mod2 #self.modified = self.modified or mod0 or mod1 or mod2
self.modified = self.modified or mod0 or mod2 self.modified = self.modified or mod0 or mod2
#self._make('tmatrix') #self._make('tmatrix')
#self._make('bin/spec') #self._make('bin/spec')
#t0 = time.time() #t0 = time.time()
@ -424,25 +447,29 @@ class _MSCALCULATOR(Calculator):
def get_tmatrix(self): def get_tmatrix(self):
LOGGER.info("Getting the TMatrix...") LOGGER.info("Getting the TMatrix...")
LOGGER.debug(get_call_info(inspect.currentframe())) LOGGER.debug(get_call_info(inspect.currentframe()))
# If using an external potential, Phagen should be run twice,
# the first time with the internal MT potential.
pot = self.tmatrix_parameters.potential
if pot != 'muffin_tin':
LOGGER.info("Phagen is running first with MuffinTin potential...")
self.tmatrix_parameters.potential = "muffin_tin"
self.tmatrix_parameters.exchange_correlation = "hedin_lundqvist_complex"
self.run_phagen()
self.tmatrix_parameters.potential = pot
self.run_phagen() self.run_phagen()
filename = os.path.join(self.tmp_folder, 'output/tmatrix.tl') tl = self.phagenio.tl
tl = self.phagenio.load_tl_file(filename)
filename = os.path.join(self.tmp_folder, 'output/cluster.clu')
self.phagenio.load_cluster_file(filename)
tl_threshold = self.tmatrix_parameters.get_parameter('tl_threshold') tl_threshold = self.tmatrix_parameters.get_parameter('tl_threshold')
if tl_threshold.value != None: if tl_threshold.value != None:
LOGGER.debug(" applying tl_threshold to %s...", LOGGER.debug(" applying tl_threshold to %s...",
tl_threshold.value) tl_threshold.value)
go_on = True go_on = True
while go_on: while go_on:
go_on = False go_on = False
for ia in range(self.phagenio.nat): for ia in range(self.phagenio.nat):
for ie in range(self.phagenio.ne): for ie in range(self.phagenio.ne):
last_tl = tl[ia][ie][-1, -2:] last_tl = tl[ia][ie][-1, -2:]
# convert to complex # convert to complex
last_tl = last_tl[0] + last_tl[1] * 1j last_tl = last_tl[0] + last_tl[1] * 1j
@ -450,36 +477,36 @@ class _MSCALCULATOR(Calculator):
# remove last line of tl # remove last line of tl
tl[ia][ie] = tl[ia][ie][:-1, :] tl[ia][ie] = tl[ia][ie][:-1, :]
go_on = True go_on = True
max_tl = self.tmatrix_parameters.get_parameter('max_tl').value max_tl = self.tmatrix_parameters.get_parameter('max_tl').value
cluster = self.phagen_parameters.get_parameter('atoms').value cluster = self.phagen_parameters.get_parameter('atoms').value
proto_indices = cluster.get_array('proto_indices') proto_indices = cluster.get_array('proto_indices')
if max_tl != None: if max_tl != None:
LOGGER.debug(" applying max_tl: %s", max_tl) LOGGER.debug(" applying max_tl: %s", max_tl)
for ia in range(self.phagenio.nat): for ia in range(self.phagenio.nat):
for ie in range(self.phagenio.ne): for ie in range(self.phagenio.ne):
try: try:
# for each set of tl: # for each set of tl:
# 1. get the symbol of the prototipical atom # 1. get the symbol of the prototipical atom
j = np.where(proto_indices == ia+1)[0] j = np.where(proto_indices == ia+1)[0]
symbol = cluster[j][0].symbol symbol = cluster[j][0].symbol
# 2. get the number of max tl allowed # 2. get the number of max tl allowed
ntl = max_tl[symbol] ntl = max_tl[symbol]
# 3. reshape the tl set accordingly # 3. reshape the tl set accordingly
tl[ia][ie] = tl[ia][ie][:ntl, :] tl[ia][ie] = tl[ia][ie][:ntl, :]
except KeyError: except KeyError:
pass pass
self.phagenio.write_tl_file( self.phagenio.write_tl_file(
os.path.join(self.tmp_folder, 'output/tmatrix.tl')) os.path.join(self.tmp_folder, 'output/tmatrix.tl'))
# update spec extra parameters here # update spec extra parameters here
self.spec_parameters.set_parameter('extra_nat', self.phagenio.nat) self.spec_parameters.set_parameter('extra_nat', self.phagenio.nat)
self.spec_parameters.set_parameter('extra_nlmax', self.phagenio.nlmax) self.spec_parameters.set_parameter('extra_nlmax', self.phagenio.nlmax)
def set_atoms(self, atoms): def set_atoms(self, atoms):
"""Defines the cluster on which the calculator will work. """Defines the cluster on which the calculator will work.
@ -502,7 +529,7 @@ class _MSCALCULATOR(Calculator):
""" """
_ = [] _ = []
for section in ('global', 'muffintin', 'tmatrix', 'spectroscopy', for section in ('global', 'muffintin', 'tmatrix', 'spectroscopy',
'source', 'detector', 'scan', 'calculation'): 'source', 'detector', 'scan', 'calculation'):
parameters = getattr(self, section + '_parameters') parameters = getattr(self, section + '_parameters')
for p in parameters: for p in parameters:
@ -514,7 +541,7 @@ class _MSCALCULATOR(Calculator):
self.atoms.info['absorber'] = self.atoms.absorber self.atoms.info['absorber'] = self.atoms.absorber
self.atoms.write(clusbuf, format='xyz') self.atoms.write(clusbuf, format='xyz')
dset.add_parameter(group='Cluster', name='cluster', value=clusbuf.getvalue(), hidden="True") dset.add_parameter(group='Cluster', name='cluster', value=clusbuf.getvalue(), hidden="True")
def shutdown(self): def shutdown(self):
"""Removes the temporary folder and all its content. """Removes the temporary folder and all its content.
@ -539,7 +566,7 @@ class _PED(_MSCALCULATOR):
"""This class creates a calculator object for PhotoElectron DIffraction """This class creates a calculator object for PhotoElectron DIffraction
spectroscopy. spectroscopy.
:param algorithm: The algorithm to use for the computation. See :param algorithm: The algorithm to use for the computation. See
:ref:`globalparameters-algorithm` for more details about the allowed :ref:`globalparameters-algorithm` for more details about the allowed
values and the type. values and the type.
@ -573,24 +600,24 @@ class _PED(_MSCALCULATOR):
spinpol=spinpol, folder=folder, txt=txt) spinpol=spinpol, folder=folder, txt=txt)
self.iodata = iodata.Data('PED Simulation') self.iodata = iodata.Data('PED Simulation')
def _get_scan(self, scan_type='theta', phi=0, def _get_scan(self, scan_type='theta', phi=0,
theta=np.linspace(-70, 70, 141), level=None, theta=np.linspace(-70, 70, 141), level=None,
kinetic_energy=None, data=None, kinetic_energy=None, data=None,
malloc={}): malloc={}):
LOGGER.info("Computting the %s scan...", scan_type) LOGGER.info("Computting the %s scan...", scan_type)
if data: if data:
self.iodata = data self.iodata = data
if kinetic_energy is None: if kinetic_energy is None:
# try to guess the kinetic energy # try to guess the kinetic energy
kinetic_energy = self._guess_ke(level) kinetic_energy = self._guess_ke(level)
# if still None... # if still None...
if kinetic_energy is None: if kinetic_energy is None:
LOGGER.error('Unable to guess the kinetic energy!') LOGGER.error('Unable to guess the kinetic energy!')
raise ValueError('You must define a kinetic_energy value.') raise ValueError('You must define a kinetic_energy value.')
# update the parameters # update the parameters
self.scan_parameters.set_parameter('kinetic_energy', kinetic_energy) self.scan_parameters.set_parameter('kinetic_energy', kinetic_energy)
all_ke = self.scan_parameters.get_parameter('ke_array') all_ke = self.scan_parameters.get_parameter('ke_array')
@ -599,29 +626,29 @@ class _PED(_MSCALCULATOR):
raise ValueError('Kinetic energy is < 0! ({})'.format( raise ValueError('Kinetic energy is < 0! ({})'.format(
kinetic_energy)) kinetic_energy))
self.scan_parameters.set_parameter('type', scan_type) self.scan_parameters.set_parameter('type', scan_type)
# make sure there is only one energy point in scatf scan # make sure there is only one energy point in scatf scan
if scan_type == 'scatf': if scan_type == 'scatf':
assert len(all_ke) == 1, ('kinetic_energy should not be an array ' assert len(all_ke) == 1, ('kinetic_energy should not be an array '
'in scatf scan') 'in scatf scan')
if scan_type != 'scatf': if scan_type != 'scatf':
self.scan_parameters.set_parameter('phi', phi) self.scan_parameters.set_parameter('phi', phi)
self.scan_parameters.set_parameter('theta', theta) self.scan_parameters.set_parameter('theta', theta)
self.spectroscopy_parameters.set_parameter('level', level) self.spectroscopy_parameters.set_parameter('level', level)
self.get_tmatrix() self.get_tmatrix()
self.run_spec(malloc) self.run_spec(malloc)
# Now load the data # Now load the data
ndset = len(self.iodata) ndset = len(self.iodata)
dset = self.iodata.add_dset('{} scan [{:d}]'.format(scan_type, ndset)) dset = self.iodata.add_dset('{} scan [{:d}]'.format(scan_type, ndset))
for p in self.get_parameters(): for p in self.get_parameters():
bundle = {'group': str(p.group), bundle = {'group': str(p.group),
'name': str(p.name), 'name': str(p.name),
'value': str(p.value), 'value': str(p.value),
'unit': '' if p.unit is None else str(p.unit)} 'unit': '' if p.unit is None else str(p.unit)}
dset.add_parameter(**bundle) dset.add_parameter(**bundle)
if scan_type in ('theta', 'phi', 'energy'): if scan_type in ('theta', 'phi', 'energy'):
@ -636,7 +663,7 @@ class _PED(_MSCALCULATOR):
data = data[:, [1, 4, 5, 6, 8]].T data = data[:, [1, 4, 5, 6, 8]].T
_proto, _sf_real, _sf_imag, _theta, _energy = data _proto, _sf_real, _sf_imag, _theta, _energy = data
_sf = _sf_real + _sf_imag * 1j _sf = _sf_real + _sf_imag * 1j
dset.add_columns(proto_index=_proto, sf_real=np.real(_sf), dset.add_columns(proto_index=_proto, sf_real=np.real(_sf),
sf_imag=np.imag(_sf), sf_module=np.abs(_sf), sf_imag=np.imag(_sf), sf_module=np.abs(_sf),
theta=_theta, energy=_energy) theta=_theta, energy=_energy)
elif scan_type in ('theta_phi',): elif scan_type in ('theta_phi',):
@ -645,7 +672,7 @@ class _PED(_MSCALCULATOR):
#theta_c, phi_c = data[[2, 3], :] #theta_c, phi_c = data[[2, 3], :]
#xsec_c = data[-1, :] #xsec_c = data[-1, :]
#dirsig_c = data[-2, :] #dirsig_c = data[-2, :]
#dset.add_columns(theta=theta_c) #dset.add_columns(theta=theta_c)
#dset.add_columns(phi=phi_c) #dset.add_columns(phi=phi_c)
#dset.add_columns(cross_section=xsec_c) #dset.add_columns(cross_section=xsec_c)
@ -665,14 +692,14 @@ class _PED(_MSCALCULATOR):
xlabel = r'Angle $\theta$($\degree$)' xlabel = r'Angle $\theta$($\degree$)'
ylabel = r'Signal (a. u.)' ylabel = r'Signal (a. u.)'
view = dset.add_view("E = {:.2f} eV".format(ke), title=title, view = dset.add_view("E = {:.2f} eV".format(ke), title=title,
xlabel=xlabel, ylabel=ylabel) xlabel=xlabel, ylabel=ylabel)
for angle_phi in self.scan_parameters.get_parameter( for angle_phi in self.scan_parameters.get_parameter(
'phi').value: 'phi').value:
where = ("energy=={:.2f} and phi=={:.2f}" where = ("energy=={:.2f} and phi=={:.2f}"
"").format(ke, angle_phi) "").format(ke, angle_phi)
legend = r'$\phi$ = {:.1f} $\degree$'.format(angle_phi) legend = r'$\phi$ = {:.1f} $\degree$'.format(angle_phi)
view.select('theta', 'cross_section', where=where, view.select('theta', 'cross_section', where=where,
legend=legend) legend=legend)
if scan_type == 'phi': if scan_type == 'phi':
absorber_symbol = self.atoms[self.atoms.absorber].symbol absorber_symbol = self.atoms[self.atoms.absorber].symbol
@ -681,16 +708,16 @@ class _PED(_MSCALCULATOR):
xlabel = r'Angle $\phi$($\degree$)' xlabel = r'Angle $\phi$($\degree$)'
ylabel = r'Signal (a. u.)' ylabel = r'Signal (a. u.)'
view = dset.add_view("E = {:.2f} eV".format(ke), title=title, view = dset.add_view("E = {:.2f} eV".format(ke), title=title,
xlabel=xlabel, ylabel=ylabel) xlabel=xlabel, ylabel=ylabel)
for angle_theta in self.scan_parameters.get_parameter( for angle_theta in self.scan_parameters.get_parameter(
'theta').value: 'theta').value:
where = ("energy=={:.2f} and theta=={:.2f}" where = ("energy=={:.2f} and theta=={:.2f}"
"").format(ke, angle_theta) "").format(ke, angle_theta)
legend = r'$\theta$ = {:.1f} $\degree$'.format(angle_theta) legend = r'$\theta$ = {:.1f} $\degree$'.format(angle_theta)
view.select('phi', 'cross_section', where=where, view.select('phi', 'cross_section', where=where,
legend=legend) legend=legend)
if scan_type == 'theta_phi': if scan_type == 'theta_phi':
absorber_symbol = self.atoms[self.atoms.absorber].symbol absorber_symbol = self.atoms[self.atoms.absorber].symbol
title = ('Stereographic projection of {}({}) at {:.2f} eV' title = ('Stereographic projection of {}({}) at {:.2f} eV'
@ -698,20 +725,20 @@ class _PED(_MSCALCULATOR):
xlabel = r'Angle $\phi$($\degree$)' xlabel = r'Angle $\phi$($\degree$)'
ylabel = r'Signal (a. u.)' ylabel = r'Signal (a. u.)'
view = dset.add_view("E = {:.2f} eV".format(ke), title=title, view = dset.add_view("E = {:.2f} eV".format(ke), title=title,
xlabel=xlabel, ylabel=ylabel, xlabel=xlabel, ylabel=ylabel,
projection='stereo', colorbar=True, autoscale=True) projection='stereo', colorbar=True, autoscale=True)
view.select('theta', 'phi', 'cross_section') view.select('theta', 'phi', 'cross_section')
if scan_type == 'scatf': if scan_type == 'scatf':
for i in range(self.phagenio.nat): for i in range(self.phagenio.nat):
proto_index = i+1 proto_index = i+1
title = 'Scattering factor at {:.3f} eV'.format(kinetic_energy) title = 'Scattering factor at {:.3f} eV'.format(kinetic_energy)
view = dset.add_view("Proto. atom #{:d}".format(proto_index), view = dset.add_view("Proto. atom #{:d}".format(proto_index),
title=title, projection='polar') title=title, projection='polar')
where = "proto_index=={:d}".format(proto_index) where = "proto_index=={:d}".format(proto_index)
view.select('theta', 'sf_module', where=where, view.select('theta', 'sf_module', where=where,
legend=r'$|f(\theta)|$') legend=r'$|f(\theta)|$')
view.select('theta', 'sf_real', where=where, view.select('theta', 'sf_real', where=where,
@ -795,13 +822,13 @@ class _PED(_MSCALCULATOR):
return self.iodata return self.iodata
def get_scattering_factors(self, level='1s', kinetic_energy=None, def get_scattering_factors(self, level='1s', kinetic_energy=None,
data=None): data=None):
"""Computes the scattering factors of all prototypical atoms in the """Computes the scattering factors of all prototypical atoms in the
cluster. cluster.
This function computes the real and imaginery parts of the scattering This function computes the real and imaginery parts of the scattering
factor as well as its modulus for each non symetrically equivalent atom factor as well as its modulus for each non symetrically equivalent atom
in the cluster. The results are stored in the *data* object if provided in the cluster. The results are stored in the *data* object if provided
as a parameter. as a parameter.
@ -814,11 +841,11 @@ class _PED(_MSCALCULATOR):
argument or a new :py:class:`iodata.Data` object. argument or a new :py:class:`iodata.Data` object.
""" """
data = self._get_scan(scan_type='scatf', level=level, data=data, data = self._get_scan(scan_type='scatf', level=level, data=data,
kinetic_energy=kinetic_energy) kinetic_energy=kinetic_energy)
return data return data
def get_theta_scan(self, phi=0, theta=np.linspace(-70, 70, 141), def get_theta_scan(self, phi=0, theta=np.linspace(-70, 70, 141),
level=None, kinetic_energy=None, data=None): level=None, kinetic_energy=None, data=None):
"""Computes a polar scan of the emitted photoelectrons. """Computes a polar scan of the emitted photoelectrons.
@ -838,7 +865,7 @@ class _PED(_MSCALCULATOR):
data = self._get_scan(scan_type='theta', level=level, theta=theta, data = self._get_scan(scan_type='theta', level=level, theta=theta,
phi=phi, kinetic_energy=kinetic_energy, data=data) phi=phi, kinetic_energy=kinetic_energy, data=data)
return data return data
def get_phi_scan(self, phi=np.linspace(0, 359, 359), theta=0, def get_phi_scan(self, phi=np.linspace(0, 359, 359), theta=0,
level=None, kinetic_energy=None, data=None): level=None, kinetic_energy=None, data=None):
"""Computes an azimuthal scan of the emitted photoelectrons. """Computes an azimuthal scan of the emitted photoelectrons.
@ -858,13 +885,13 @@ class _PED(_MSCALCULATOR):
""" """
data = self._get_scan(scan_type='phi', level=level, theta=theta, data = self._get_scan(scan_type='phi', level=level, theta=theta,
phi=phi, kinetic_energy=kinetic_energy, data=data) phi=phi, kinetic_energy=kinetic_energy, data=data)
return data return data
def get_theta_phi_scan(self, phi=np.linspace(0, 360), def get_theta_phi_scan(self, phi=np.linspace(0, 360),
theta=np.linspace(0, 90, 45), level=None, theta=np.linspace(0, 90, 45), level=None,
kinetic_energy=None, data=None): kinetic_energy=None, data=None):
"""Computes a stereographic scan of the emitted photoelectrons. """Computes a stereographic scan of the emitted photoelectrons.
The azimuth ranges from 0 to 360° and the polar angle ranges from 0 to The azimuth ranges from 0 to 360° and the polar angle ranges from 0 to
90°. 90°.
@ -880,7 +907,7 @@ class _PED(_MSCALCULATOR):
data = self._get_scan(scan_type='theta_phi', level=level, theta=theta, data = self._get_scan(scan_type='theta_phi', level=level, theta=theta,
phi=phi, kinetic_energy=kinetic_energy, data=data, phi=phi, kinetic_energy=kinetic_energy, data=data,
malloc={'NPH_M': 8000}) malloc={'NPH_M': 8000})
return data return data
class _EIG(_MSCALCULATOR): class _EIG(_MSCALCULATOR):
@ -946,7 +973,7 @@ class _EIG(_MSCALCULATOR):
results_fname = os.path.join(self.tmp_folder, 'output/results.dat') results_fname = os.path.join(self.tmp_folder, 'output/results.dat')
data = self.specio.load_results(results_fname) data = self.specio.load_results(results_fname)
energy, spec_rad = data energy, spec_rad = data
dset.add_columns(energy=energy, spectral_radius=spec_rad) dset.add_columns(energy=energy, spectral_radius=spec_rad)
@ -960,7 +987,7 @@ class _EIG(_MSCALCULATOR):
# add the cluster to the dataset # add the cluster to the dataset
self.add_cluster_to_dset(dset) self.add_cluster_to_dset(dset)
return self.iodata return self.iodata
@ -979,7 +1006,7 @@ def MSSPEC(spectroscopy='PED', **kwargs):
cls = getattr(module, '_' + spectroscopy) cls = getattr(module, '_' + spectroscopy)
return cls(**kwargs) return cls(**kwargs)
if __name__ == "__main__": if __name__ == "__main__":
pass pass

View File

@ -1,118 +0,0 @@
# coding: utf-8
"""
Module config
=============
"""
from configparser import NoSectionError
from configparser import SafeConfigParser as ConfigParser
import os
import sys
import fnmatch
from msspec.misc import LOGGER
class NoConfigFile(Exception): pass
class Config(object):
def __init__(self, **kwargs):
self.fname = os.path.join(os.environ['HOME'], '.config/msspec/pymsspec.cfg')
self.version = sys.modules['msspec'].__version__
self.config = ConfigParser()
self.defaults = {'path': 'None'}
self.defaults.update(kwargs)
# try to load the config file, create one with defaults if none is found
try:
self.options = self.read()
self.options.update(kwargs)
self.set(**self.options)
self.write()
except NoConfigFile:
# write a file with default options
self.write(defaults=True)
except NoSectionError:
# the file exists but has no options for this version of pymsspec
self.config.add_section(self.version)
self.set(**self.defaults)
self.write()
def read(self):
fp = self.config.read(self.fname)
if len(fp) == 0:
raise NoConfigFile
self.version = self.config.get('general', 'version')
return dict(self.config.items(self.version))
def set(self, **kwargs):
if not(self.config.has_section(self.version)):
self.config.add_section(self.version)
for k, v in list(kwargs.items()):
self.config.set(self.version, k, v)
def get(self, key):
return self.config.get(self.version, key)
def choose_msspec_folder(self, *folders):
print("Several folders containing the appropriate version of MsSpec were found.")
print("Please choose one tu use:")
s = ""
for i, f in enumerate(folders):
s += '{:d}) {:s}\n'.format(i, f)
print(s)
return(folders[int(input('Your choice: '))])
def find_msspec_folders(self):
version = sys.modules['msspec'].__version__
folders = []
i = 0
prompt = 'Please wait while scanning the filesystem (%3d found) '
sys.stdout.write(prompt % len(folders))
sys.stdout.write('\033[s')
for root, dirnames, filenames in os.walk('/home/stricot'):
sys.stdout.write('\033[u\033[k')
sys.stdout.write('%d folders scanned' % i)
i += 1
for fn in fnmatch.filter(filenames, 'VERSION'):
with open(os.path.join(root, fn), 'r') as fd:
try:
line = fd.readline()
if line.strip() == version:
folders.append(root)
sys.stdout.write('\033[2000D\033[k')
sys.stdout.write(prompt % len(folders))
except:
pass
sys.stdout.write('\033[u\033[k\n')
print('Done.')
return(folders)
def write(self, defaults=False):
if defaults:
self.set(**self.defaults)
with open(self.fname, 'w') as fd:
self.config.write(fd)
LOGGER.info("{} file written".format(self.fname))
def set_mode(self, mode="pymsspec"):
if not(self.config.has_section("general")):
self.config.add_section("general")
self.config.set("general", "mode", str(mode))
def get_mode(self):
return self.config.get("general", "mode")
def set_version(self, version=""):
if not(self.config.has_section("general")):
self.config.add_section("general")
self.config.set("general", "version", version)
def remove_version(self, version):
pass

View File

@ -1,2 +1,28 @@
#!/usr/bin/env python
#
# Copyright © 2016-2020 - Rennes Physics Institute
#
# This file is part of msspec.
#
# msspec is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# msspec is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this msspec. If not, see <http://www.gnu.org/licenses/>.
#
# Source file : src/msspec/create_tests_results.py
# Last modified: ven. 10 avril 2020 17:29:16
# Committed by : "Sylvain Tricot <sylvain.tricot@univ-rennes1.fr>"
from msspec.tests import create_tests_results from msspec.tests import create_tests_results
create_tests_results() create_tests_results()

View File

@ -1,4 +1,25 @@
# coding: utf-8 #!/usr/bin/env python
#
# Copyright © 2016-2020 - Rennes Physics Institute
#
# This file is part of msspec.
#
# msspec is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# msspec is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this msspec. If not, see <http://www.gnu.org/licenses/>.
#
# Source file : src/msspec/iodata.py
# Last modified: ven. 10 avril 2020 17:23:11
# Committed by : "Sylvain Tricot <sylvain.tricot@univ-rennes1.fr>"
""" """
Module iodata Module iodata
============= =============
@ -24,7 +45,7 @@ Here is an example of how to store values in a Data object:
data = Data('all my data') data = Data('all my data')
# and append a new DataSet with its title # and append a new DataSet with its title
dset = data.add_dset('Dataset 0') dset = data.add_dset('Dataset 0')
# To feed the DataSet with columns, use the add_columns method # To feed the DataSet with columns, use the add_columns method
# and provide as many keywords as you like. Each key being the # and provide as many keywords as you like. Each key being the
# column name and each value being an array holding the column # column name and each value being an array holding the column
@ -47,38 +68,34 @@ Here is an example of how to store values in a Data object:
import os import os
import numpy as np import sys
import h5py from distutils.version import LooseVersion
from lxml import etree from distutils.version import StrictVersion
import msspec
from msspec.misc import LOGGER
import ase.io
from io import StringIO from io import StringIO
import wx import ase.io
import h5py
import numpy as np
import wx.grid import wx.grid
from lxml import etree
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg from matplotlib.backends.backend_wxagg import NavigationToolbar2WxAgg
#from matplotlib.backends.backend_wxcairo import FigureCanvasWxCairo as FigureCanvas
#from matplotlib.backends.backend_wxcairo import NavigationToolbar2WxCairo
from matplotlib.figure import Figure from matplotlib.figure import Figure
from terminaltables import AsciiTable from terminaltables import AsciiTable
from distutils.version import StrictVersion, LooseVersion
import sys import msspec
#sys.path.append('../../MsSpecGui/msspecgui/msspec/gui')
from msspec.msspecgui.msspec.gui.clusterviewer import ClusterViewer from msspec.msspecgui.msspec.gui.clusterviewer import ClusterViewer
from msspec.misc import LOGGER
def cols2matrix(x, y, z, nx=88*1+1, ny=360*1+1): def cols2matrix(x, y, z, nx=88*1+1, ny=360*1+1):
# mix the values of existing theta and new theta and return the # mix the values of existing theta and new theta and return the
# unique values # unique values
newx = np.linspace(np.min(x), np.max(x), nx) newx = np.linspace(np.min(x), np.max(x), nx)
newy = np.linspace(np.min(y), np.max(y), ny) newy = np.linspace(np.min(y), np.max(y), ny)
ux = np.unique(np.append(x, newx)) ux = np.unique(np.append(x, newx))
uy = np.unique(np.append(y, newy)) uy = np.unique(np.append(y, newy))
# create an empty matrix to hold the results # create an empty matrix to hold the results
zz = np.empty((len(ux), len(uy))) zz = np.empty((len(ux), len(uy)))
zz[:] = np.nan zz[:] = np.nan
@ -87,46 +104,46 @@ def cols2matrix(x, y, z, nx=88*1+1, ny=360*1+1):
i = np.argwhere(ux == p[0]) i = np.argwhere(ux == p[0])
j = np.argwhere(uy == p[1]) j = np.argwhere(uy == p[1])
zz[i, j] = p[2] zz[i, j] = p[2]
for i in range(len(ux)): for i in range(len(ux)):
#ok, = np.where(-np.isnan(zz[i,:])) #ok, = np.where(-np.isnan(zz[i,:]))
ok, = np.where(~np.isnan(zz[i, :])) ok, = np.where(~np.isnan(zz[i, :]))
if len(ok) > 0: if len(ok) > 0:
xp = uy[ok] xp = uy[ok]
fp = zz[i, ok] fp = zz[i, ok]
zz[i,:] = np.interp(uy, xp, fp) zz[i,:] = np.interp(uy, xp, fp)
for i in range(len(uy)): for i in range(len(uy)):
#ok, = np.where(-np.isnan(zz[:,i])) #ok, = np.where(-np.isnan(zz[:,i]))
ok, = np.where(~np.isnan(zz[:, i])) ok, = np.where(~np.isnan(zz[:, i]))
if len(ok) > 0: if len(ok) > 0:
xp = ux[ok] xp = ux[ok]
fp = zz[ok, i] fp = zz[ok, i]
zz[:,i] = np.interp(ux, xp, fp) zz[:,i] = np.interp(ux, xp, fp)
return ux, uy, zz return ux, uy, zz
class _DataPoint(dict): class _DataPoint(dict):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs) dict.__init__(self, *args, **kwargs)
def __getattr__(self, name): def __getattr__(self, name):
if name in list(self.keys()): if name in list(self.keys()):
return self[name] return self[name]
else: else:
raise AttributeError("'{}' object has no attribute '{}'".format( raise AttributeError("'{}' object has no attribute '{}'".format(
self.__class__.__name__, name)) self.__class__.__name__, name))
class DataSet(object): class DataSet(object):
""" """
This class can create an object to hold column-oriented data. This class can create an object to hold column-oriented data.
:param title: The text used to entitled the dataset :param title: The text used to entitled the dataset
:type title: str :type title: str
:param notes: Some comments to add to the data :param notes: Some comments to add to the data
:type notes: str :type notes: str
""" """
def __init__(self, title, notes=""): def __init__(self, title, notes=""):
self.title = title self.title = title
@ -134,23 +151,23 @@ class DataSet(object):
self._views = [] self._views = []
self._parameters = [] self._parameters = []
self.attributes = {} self.attributes = {}
self._col_names = [] self._col_names = []
self._col_arrays = [] self._col_arrays = []
self._defaults = {'bool': False, 'str': '', 'int': 0, 'float': 0., self._defaults = {'bool': False, 'str': '', 'int': 0, 'float': 0.,
'complex': complex(0)} 'complex': complex(0)}
self._formats = {bool: '{:s}', str: '{:s}', int: '{:<20d}', self._formats = {bool: '{:s}', str: '{:s}', int: '{:<20d}',
float: '{:<20.10e}', complex: 's'} float: '{:<20.10e}', complex: 's'}
def _empty_array(self, val): def _empty_array(self, val):
if isinstance(val, str): if isinstance(val, str):
t = 'S256' t = 'S256'
else: else:
t = np.dtype(type(val)) t = np.dtype(type(val))
if isinstance(val, bool): if isinstance(val, bool):
default = self._defaults['bool'] default = self._defaults['bool']
elif isinstance(val, str): elif isinstance(val, str):
default = self._defaults['str'] default = self._defaults['str']
elif isinstance(val, int): elif isinstance(val, int):
@ -161,9 +178,9 @@ class DataSet(object):
default = self._defaults['complex'] default = self._defaults['complex']
else: else:
raise TypeError('Not a supported type') raise TypeError('Not a supported type')
return np.array([default]*len(self), dtype=t) return np.array([default]*len(self), dtype=t)
def add_row(self, **kwargs): def add_row(self, **kwargs):
"""Add a row of data into the dataset. """Add a row of data into the dataset.
@ -180,17 +197,17 @@ class DataSet(object):
arr = self._col_arrays[i] arr = self._col_arrays[i]
arr = np.append(arr, v) arr = np.append(arr, v)
self._col_arrays[i] = arr self._col_arrays[i] = arr
def add_columns(self, **kwargs): def add_columns(self, **kwargs):
""" """
Add columns to the dataset. Add columns to the dataset.
You can provide as many columns as you want to this function. This You can provide as many columns as you want to this function. This
function can be called several times on the same dataset but each time function can be called several times on the same dataset but each time
with different column names. Column names are given as keywords. with different column names. Column names are given as keywords.
:Example: :Example:
>>> from iodata import DataSet >>> from iodata import DataSet
>>> dset = DataSet('My Dataset', notes="Just an example") >>> dset = DataSet('My Dataset', notes="Just an example")
>>> xdata = range(10) >>> xdata = range(10)
@ -211,7 +228,7 @@ class DataSet(object):
>>> | 8 64 | >>> | 8 64 |
>>> | 9 81 | >>> | 9 81 |
>>> +-------+ >>> +-------+
""" """
for k, vv in list(kwargs.items()): for k, vv in list(kwargs.items()):
assert k not in self._col_names, ("'{}' column already exists" assert k not in self._col_names, ("'{}' column already exists"
@ -228,11 +245,11 @@ class DataSet(object):
def delete_rows(self, itemspec): def delete_rows(self, itemspec):
""" """
Delete the rows specified with itemspec. Delete the rows specified with itemspec.
""" """
for i in range(len(self._col_names)): for i in range(len(self._col_names)):
self._col_arrays[i] = np.delete(self._col_arrays[i], itemspec) self._col_arrays[i] = np.delete(self._col_arrays[i], itemspec)
def delete_columns(self, *tags): def delete_columns(self, *tags):
""" """
Removes all columns name passed as arguments Removes all columns name passed as arguments
@ -245,7 +262,7 @@ class DataSet(object):
i = self._col_names.index(tag) i = self._col_names.index(tag)
self._col_names.pop(i) self._col_names.pop(i)
self._col_arrays.pop(i) self._col_arrays.pop(i)
def columns(self): def columns(self):
""" """
Get all the column names. Get all the column names.
@ -255,7 +272,7 @@ class DataSet(object):
""" """
return self._col_names return self._col_names
def add_view(self, name, **plotopts): def add_view(self, name, **plotopts):
""" """
Creates a new view named *name* with specied plot options. Creates a new view named *name* with specied plot options.
@ -268,12 +285,12 @@ class DataSet(object):
""" """
if isinstance(name, str): if isinstance(name, str):
v = _DataSetView(self, name, **plotopts) v = _DataSetView(self, name, **plotopts)
else: else:
v = name v = name
v.dataset = self v.dataset = self
self._views.append(v) self._views.append(v)
return v return v
def views(self): def views(self):
"""Returns all the defined views in the dataset. """Returns all the defined views in the dataset.
@ -281,7 +298,7 @@ class DataSet(object):
:rtype: List of :py:class:`iodata._DataSetView` :rtype: List of :py:class:`iodata._DataSetView`
""" """
return self._views return self._views
def add_parameter(self, **kwargs): def add_parameter(self, **kwargs):
"""Add a parameter to store with the dataset. """Add a parameter to store with the dataset.
@ -379,20 +396,20 @@ class DataSet(object):
#fd.write(' ') #fd.write(' ')
fd.write(fmt.format(value)) fd.write(fmt.format(value))
#fd.write(str(value) + ', ') #fd.write(str(value) + ', ')
fd.write('\n') fd.write('\n')
def __getitem__(self, itemspec): def __getitem__(self, itemspec):
if isinstance(itemspec, str): if isinstance(itemspec, str):
return getattr(self, itemspec) return getattr(self, itemspec)
title = 'untitled' title = 'untitled'
new = DataSet(title) new = DataSet(title)
new._col_names = self.columns() new._col_names = self.columns()
for arr in self._col_arrays: for arr in self._col_arrays:
new._col_arrays.append(np.array(arr[itemspec]).flatten()) new._col_arrays.append(np.array(arr[itemspec]).flatten())
return new return new
def __setstate__(self, state): def __setstate__(self, state):
self.__dict__ = state self.__dict__ = state
@ -409,7 +426,7 @@ class DataSet(object):
def __iter__(self): def __iter__(self):
for i in range(len(self)): for i in range(len(self)):
_ = {k: arr[i] for k, arr in zip(self._col_names, _ = {k: arr[i] for k, arr in zip(self._col_names,
self._col_arrays)} self._col_arrays)}
point = _DataPoint(_) point = _DataPoint(_)
yield point yield point
@ -427,17 +444,17 @@ class DataSet(object):
ncols = min(max_col, len(self._col_arrays)) ncols = min(max_col, len(self._col_arrays))
table_data = [self._col_names[:ncols]] table_data = [self._col_names[:ncols]]
table_data[0].insert(0, "") table_data[0].insert(0, "")
all_indices = np.arange(0, len(self)) all_indices = np.arange(0, len(self))
indices = all_indices indices = all_indices
if len(self) > max_len: if len(self) > max_len:
indices = list(range(int(max_len/2))) + list(range(int(-max_len/2), 0)) indices = list(range(int(max_len/2))) + list(range(int(-max_len/2), 0))
_i = 0 _i = 0
for i in indices: for i in indices:
if i < _i: if i < _i:
row = ['...' for _ in range(ncols + 1)] row = ['...' for _ in range(ncols + 1)]
table_data.append(row) table_data.append(row)
row = [str(all_indices[i]),] row = [str(all_indices[i]),]
for j in range(ncols): for j in range(ncols):
arr = self._col_arrays[j] arr = self._col_arrays[j]
@ -446,13 +463,13 @@ class DataSet(object):
row.append('...') row.append('...')
table_data.append(row) table_data.append(row)
_i = i _i = i
table = AsciiTable(table_data) table = AsciiTable(table_data)
table.outer_border = True table.outer_border = True
table.title = self.title table.title = self.title
table.inner_column_border = False table.inner_column_border = False
return table.table return table.table
def __repr__(self): def __repr__(self):
s = "<{}('{}')>".format(self.__class__.__name__, self.title) s = "<{}('{}')>".format(self.__class__.__name__, self.title)
return s return s
@ -497,7 +514,7 @@ class Data(object):
i = titles.index(title) i = titles.index(title)
self._datasets.pop(i) self._datasets.pop(i)
self._dirty = True self._dirty = True
def get_last_dset(self): def get_last_dset(self):
"""Get the last DataSet of the Data object. """Get the last DataSet of the Data object.
@ -505,7 +522,7 @@ class Data(object):
:rtype: :py:class:`iodata.DataSet` :rtype: :py:class:`iodata.DataSet`
""" """
return self._datasets[-1] return self._datasets[-1]
def is_dirty(self): def is_dirty(self):
"""Wether the Data object needs to be saved. """Wether the Data object needs to be saved.
@ -513,7 +530,7 @@ class Data(object):
:rtype: bool :rtype: bool
""" """
return self._dirty return self._dirty
def save(self, filename, append=False): def save(self, filename, append=False):
"""Saves the current Data to the hard drive. """Saves the current Data to the hard drive.
@ -549,13 +566,13 @@ class Data(object):
dset.title, os.path.abspath(filename))) dset.title, os.path.abspath(filename)))
continue continue
grp = data_grp.create_group(dset.title) grp = data_grp.create_group(dset.title)
grp.attrs['notes'] = dset.notes grp.attrs['notes'] = dset.notes
for col_name in dset.columns(): for col_name in dset.columns():
data = dset[col_name] data = dset[col_name]
grp.create_dataset(col_name, data=data) grp.create_dataset(col_name, data=data)
meta_grp.attrs['version'] = msspec.__version__ meta_grp.attrs['version'] = msspec.__version__
root = etree.Element('metainfo') root = etree.Element('metainfo')
# xmlize views # xmlize views
for dset in self._datasets: for dset in self._datasets:
@ -563,7 +580,7 @@ class Data(object):
for view in dset.views(): for view in dset.views():
view_el = etree.fromstring(view.to_xml()) view_el = etree.fromstring(view.to_xml())
views_node.append(view_el) views_node.append(view_el)
# xmlize parameters # xmlize parameters
for dset in self._datasets: for dset in self._datasets:
param_node = etree.SubElement(root, 'parameters', dataset=dset.title) param_node = etree.SubElement(root, 'parameters', dataset=dset.title)
@ -580,7 +597,7 @@ class Data(object):
meta_grp.create_dataset('info', data=np.array((xml_str,)).view('S1')) meta_grp.create_dataset('info', data=np.array((xml_str,)).view('S1'))
self._dirty = False self._dirty = False
LOGGER.info('Data saved in {}'.format(os.path.abspath(filename))) LOGGER.info('Data saved in {}'.format(os.path.abspath(filename)))
@staticmethod @staticmethod
def load(filename): def load(filename):
"""Loads an HDF5 file from the disc. """Loads an HDF5 file from the disc.
@ -594,7 +611,7 @@ class Data(object):
with h5py.File(filename, 'r') as fd: with h5py.File(filename, 'r') as fd:
parameters = {} parameters = {}
views = {} views = {}
output.title = fd['DATA'].attrs['title'] output.title = fd['DATA'].attrs['title']
for dset_name in fd['DATA'] : for dset_name in fd['DATA'] :
parameters[dset_name] = [] parameters[dset_name] = []
@ -603,7 +620,7 @@ class Data(object):
dset.notes = fd['DATA'][dset_name].attrs['notes'] dset.notes = fd['DATA'][dset_name].attrs['notes']
for h5dset in fd['DATA'][dset_name]: for h5dset in fd['DATA'][dset_name]:
dset.add_columns(**{h5dset: fd['DATA'][dset_name][h5dset].value}) dset.add_columns(**{h5dset: fd['DATA'][dset_name][h5dset].value})
try: try:
vfile = LooseVersion(fd['MsSpec viewer metainfo'].attrs['version']) vfile = LooseVersion(fd['MsSpec viewer metainfo'].attrs['version'])
if vfile > LooseVersion(msspec.__version__): if vfile > LooseVersion(msspec.__version__):
@ -614,31 +631,31 @@ class Data(object):
dset_name = elt0.attrib['dataset'] dset_name = elt0.attrib['dataset']
for elt1 in elt0.iter('parameter'): for elt1 in elt0.iter('parameter'):
parameters[dset_name].append(elt1.attrib) parameters[dset_name].append(elt1.attrib)
for elt0 in root.iter('views'): for elt0 in root.iter('views'):
dset_name = elt0.attrib['dataset'] dset_name = elt0.attrib['dataset']
for elt1 in elt0.iter('view'): for elt1 in elt0.iter('view'):
view = _DataSetView(None, "") view = _DataSetView(None, "")
view.from_xml(etree.tostring(elt1)) view.from_xml(etree.tostring(elt1))
views[dset_name].append(view) views[dset_name].append(view)
except Exception as err: except Exception as err:
print(err) print(err)
for dset in output: for dset in output:
for v in views[dset.title]: for v in views[dset.title]:
dset.add_view(v) dset.add_view(v)
for p in parameters[dset.title]: for p in parameters[dset.title]:
dset.add_parameter(**p) dset.add_parameter(**p)
output._dirty = False output._dirty = False
return output return output
def __iter__(self): def __iter__(self):
for dset in self._datasets: for dset in self._datasets:
yield dset yield dset
def __getitem__(self, key): def __getitem__(self, key):
try: try:
titles = [d.title for d in self._datasets] titles = [d.title for d in self._datasets]
@ -653,11 +670,11 @@ class Data(object):
def __str__(self): def __str__(self):
s = str([dset.title for dset in self._datasets]) s = str([dset.title for dset in self._datasets])
return s return s
def __repr__(self): def __repr__(self):
s = "<Data('{}')>".format(self.title) s = "<Data('{}')>".format(self.title)
return s return s
def view(self): def view(self):
"""Pops up a grphical window to show all the defined views of the Data object. """Pops up a grphical window to show all the defined views of the Data object.
@ -691,8 +708,8 @@ class _DataSetView(object):
legend = kwargs.get('legend', '') legend = kwargs.get('legend', '')
self._selection_conditions.append(condition) self._selection_conditions.append(condition)
self._selection_tags.append(args) self._selection_tags.append(args)
self._plotopts['legend'].append(legend) self._plotopts['legend'].append(legend)
def tags(self): def tags(self):
return self._selection_tags return self._selection_tags
@ -704,11 +721,11 @@ class _DataSetView(object):
# replace all occurence of tags # replace all occurence of tags
for tag in self.dataset.columns(): for tag in self.dataset.columns():
condition = condition.replace(tag, "p['{}']".format(tag)) condition = condition.replace(tag, "p['{}']".format(tag))
for i, p in enumerate(self.dataset): for i, p in enumerate(self.dataset):
if eval(condition): if eval(condition):
indices.append(i) indices.append(i)
values = [] values = []
for tag in tags: for tag in tags:
values.append(getattr(self.dataset[indices], tag)) values.append(getattr(self.dataset[indices], tag))
@ -724,20 +741,20 @@ class _DataSetView(object):
'plotopts': self._plotopts 'plotopts': self._plotopts
} }
root = etree.Element('root') root = etree.Element('root')
return data return data
def to_xml(self): def to_xml(self):
plotopts = self._plotopts.copy() plotopts = self._plotopts.copy()
legends = plotopts.pop('legend') legends = plotopts.pop('legend')
root = etree.Element('view', name=self.title) root = etree.Element('view', name=self.title)
for key, value in list(plotopts.items()): for key, value in list(plotopts.items()):
root.attrib[key] = str(value) root.attrib[key] = str(value)
#root.attrib['dataset_name'] = self.dataset.title #root.attrib['dataset_name'] = self.dataset.title
for tags, cond, legend in zip(self._selection_tags, for tags, cond, legend in zip(self._selection_tags,
self._selection_conditions, self._selection_conditions,
legends): legends):
curve = etree.SubElement(root, 'curve') curve = etree.SubElement(root, 'curve')
curve.attrib['legend'] = legend curve.attrib['legend'] = legend
@ -745,10 +762,10 @@ class _DataSetView(object):
axes = etree.SubElement(curve, 'axes') axes = etree.SubElement(curve, 'axes')
for tag in tags: for tag in tags:
variable = etree.SubElement(axes, 'axis', name=tag) variable = etree.SubElement(axes, 'axis', name=tag)
return etree.tostring(root, pretty_print=False) return etree.tostring(root, pretty_print=False)
def from_xml(self, xmlstr): def from_xml(self, xmlstr):
root = etree.fromstring(xmlstr) root = etree.fromstring(xmlstr)
self.title = root.attrib['name'] self.title = root.attrib['name']
@ -777,15 +794,15 @@ class _DataSetView(object):
for var in curve.iter('axis'): for var in curve.iter('axis'):
variables.append(var.attrib['name']) variables.append(var.attrib['name'])
tags.append(tuple(variables)) tags.append(tuple(variables))
self._selection_conditions = conditions self._selection_conditions = conditions
self._selection_tags = tags self._selection_tags = tags
self._plotopts['legend'] = legends self._plotopts['legend'] = legends
def __repr__(self): def __repr__(self):
s = "<{}('{}')>".format(self.__class__.__name__, self.title) s = "<{}('{}')>".format(self.__class__.__name__, self.title)
return s return s
def __str__(self): def __str__(self):
try: try:
dset_title = self.dataset.title dset_title = self.dataset.title
@ -803,7 +820,7 @@ class _GridWindow(wx.Frame):
title = 'Data: ' + dset.title title = 'Data: ' + dset.title
wx.Frame.__init__(self, parent, title=title, size=(640, 480)) wx.Frame.__init__(self, parent, title=title, size=(640, 480))
self.create_grid(dset) self.create_grid(dset)
def create_grid(self, dset): def create_grid(self, dset):
grid = wx.grid.Grid(self, -1) grid = wx.grid.Grid(self, -1)
grid.CreateGrid(len(dset), len(dset.columns())) grid.CreateGrid(len(dset), len(dset.columns()))
@ -811,13 +828,13 @@ class _GridWindow(wx.Frame):
grid.SetColLabelValue(ic, c) grid.SetColLabelValue(ic, c)
for iv, v in enumerate(dset[c]): for iv, v in enumerate(dset[c]):
grid.SetCellValue(iv, ic, str(v)) grid.SetCellValue(iv, ic, str(v))
class _ParametersWindow(wx.Frame): class _ParametersWindow(wx.Frame):
def __init__(self, dset, parent=None): def __init__(self, dset, parent=None):
title = 'Parameters: ' + dset.title title = 'Parameters: ' + dset.title
wx.Frame.__init__(self, parent, title=title, size=(400, 480)) wx.Frame.__init__(self, parent, title=title, size=(400, 480))
self.create_tree(dset) self.create_tree(dset)
def create_tree(self, dset): def create_tree(self, dset):
datatree = {} datatree = {}
for p in dset.parameters(): for p in dset.parameters():
@ -829,10 +846,10 @@ class _ParametersWindow(wx.Frame):
#group.append("{:s} = {:s}".format(p['name'], strval)) #group.append("{:s} = {:s}".format(p['name'], strval))
group.append("{} = {} {}".format(p['name'], p['value'], p['unit'])) group.append("{} = {} {}".format(p['name'], p['value'], p['unit']))
datatree[p['group']] = group datatree[p['group']] = group
tree = wx.TreeCtrl(self, -1) tree = wx.TreeCtrl(self, -1)
root = tree.AddRoot('Parameters') root = tree.AddRoot('Parameters')
for key in list(datatree.keys()): for key in list(datatree.keys()):
item0 = tree.AppendItem(root, key) item0 = tree.AppendItem(root, key)
for item in datatree[key]: for item in datatree[key]:
@ -843,7 +860,7 @@ class _ParametersWindow(wx.Frame):
class _DataWindow(wx.Frame): class _DataWindow(wx.Frame):
def __init__(self, data): def __init__(self, data):
assert isinstance(data, (Data, DataSet)) assert isinstance(data, (Data, DataSet))
if isinstance(data, DataSet): if isinstance(data, DataSet):
dset = data dset = data
data = Data() data = Data()
@ -851,9 +868,9 @@ class _DataWindow(wx.Frame):
self.data = data self.data = data
self._filename = None self._filename = None
self._current_dset = None self._current_dset = None
wx.Frame.__init__(self, None, title="", size=(640, 480)) wx.Frame.__init__(self, None, title="", size=(640, 480))
self.Bind(wx.EVT_CLOSE, self.on_close) self.Bind(wx.EVT_CLOSE, self.on_close)
# Populate the menu bar # Populate the menu bar
@ -870,18 +887,18 @@ class _DataWindow(wx.Frame):
sizer = wx.BoxSizer(wx.VERTICAL) sizer = wx.BoxSizer(wx.VERTICAL)
#sizer.Add(self.notebook) #sizer.Add(self.notebook)
self.SetSizer(sizer) self.SetSizer(sizer)
self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_page_changed) self.Bind(wx.EVT_NOTEBOOK_PAGE_CHANGED, self.on_page_changed)
self.create_notebooks() self.create_notebooks()
self.update_title() self.update_title()
def create_notebooks(self): def create_notebooks(self):
for key in list(self.notebooks.keys()): for key in list(self.notebooks.keys()):
nb = self.notebooks.pop(key) nb = self.notebooks.pop(key)
nb.Destroy() nb.Destroy()
for dset in self.data: for dset in self.data:
nb = wx.Notebook(self, -1) nb = wx.Notebook(self, -1)
self.notebooks[dset.title] = nb self.notebooks[dset.title] = nb
@ -890,9 +907,9 @@ class _DataWindow(wx.Frame):
self.create_page(nb, view) self.create_page(nb, view)
self.create_menu() self.create_menu()
self.show_dataset(self.data[0].title) self.show_dataset(self.data[0].title)
def create_menu(self): def create_menu(self):
menubar = wx.MenuBar() menubar = wx.MenuBar()
@ -903,24 +920,24 @@ class _DataWindow(wx.Frame):
menu1.Append(140, "Export\tCtrl+E") menu1.Append(140, "Export\tCtrl+E")
menu1.AppendSeparator() menu1.AppendSeparator()
menu1.Append(199, "Close\tCtrl+Q") menu1.Append(199, "Close\tCtrl+Q")
menu2 = wx.Menu() menu2 = wx.Menu()
for i, dset in enumerate(self.data): for i, dset in enumerate(self.data):
menu_id = 201 + i menu_id = 201 + i
menu2.AppendRadioItem(menu_id, dset.title) menu2.AppendRadioItem(menu_id, dset.title)
self.Bind(wx.EVT_MENU, self.on_menu_dataset, id=menu_id) self.Bind(wx.EVT_MENU, self.on_menu_dataset, id=menu_id)
self.Bind(wx.EVT_MENU, self.on_open, id=110) self.Bind(wx.EVT_MENU, self.on_open, id=110)
self.Bind(wx.EVT_MENU, self.on_save, id=120) self.Bind(wx.EVT_MENU, self.on_save, id=120)
self.Bind(wx.EVT_MENU, self.on_saveas, id=130) self.Bind(wx.EVT_MENU, self.on_saveas, id=130)
self.Bind(wx.EVT_MENU, self.on_close, id=199) self.Bind(wx.EVT_MENU, self.on_close, id=199)
menu3 = wx.Menu() menu3 = wx.Menu()
menu3.Append(301, "Data") menu3.Append(301, "Data")
menu3.Append(302, "Cluster") menu3.Append(302, "Cluster")
menu3.Append(303, "Parameters") menu3.Append(303, "Parameters")
self.Bind(wx.EVT_MENU, self.on_viewdata, id=301) self.Bind(wx.EVT_MENU, self.on_viewdata, id=301)
self.Bind(wx.EVT_MENU, self.on_viewcluster, id=302) self.Bind(wx.EVT_MENU, self.on_viewcluster, id=302)
self.Bind(wx.EVT_MENU, self.on_viewparameters, id=303) self.Bind(wx.EVT_MENU, self.on_viewparameters, id=303)
@ -929,24 +946,24 @@ class _DataWindow(wx.Frame):
menubar.Append(menu2, "&Datasets") menubar.Append(menu2, "&Datasets")
menubar.Append(menu3, "&View") menubar.Append(menu3, "&View")
self.SetMenuBar(menubar) self.SetMenuBar(menubar)
def on_open(self, event): def on_open(self, event):
if self.data.is_dirty(): if self.data.is_dirty():
mbx = wx.MessageDialog(self, ('Displayed data is unsaved. Do ' mbx = wx.MessageDialog(self, ('Displayed data is unsaved. Do '
'you wish to save before opening' 'you wish to save before opening'
'another file ?'), 'another file ?'),
'Warning: Unsaved data', 'Warning: Unsaved data',
wx.YES_NO | wx.ICON_WARNING) wx.YES_NO | wx.ICON_WARNING)
if mbx.ShowModal() == wx.ID_YES: if mbx.ShowModal() == wx.ID_YES:
self.on_saveas(wx.Event()) self.on_saveas(wx.Event())
mbx.Destroy() mbx.Destroy()
wildcard = "HDF5 files (*.hdf5)|*.hdf5" wildcard = "HDF5 files (*.hdf5)|*.hdf5"
dlg = wx.FileDialog( dlg = wx.FileDialog(
self, message="Open a file...", defaultDir=os.getcwd(), self, message="Open a file...", defaultDir=os.getcwd(),
defaultFile="", wildcard=wildcard, style=wx.FD_OPEN defaultFile="", wildcard=wildcard, style=wx.FD_OPEN
) )
if dlg.ShowModal() == wx.ID_OK: if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath() path = dlg.GetPath()
self._filename = path self._filename = path
@ -954,41 +971,41 @@ class _DataWindow(wx.Frame):
self.create_notebooks() self.create_notebooks()
dlg.Destroy() dlg.Destroy()
self.update_title() self.update_title()
def on_save(self, event): def on_save(self, event):
if self._filename: if self._filename:
if self.data.is_dirty(): if self.data.is_dirty():
self.data.save(self._filename) self.data.save(self._filename)
else: else:
self.on_saveas(event) self.on_saveas(event)
def on_saveas(self, event): def on_saveas(self, event):
overwrite = True overwrite = True
wildcard = "HDF5 files (*.hdf5)|*.hdf5|All files (*.*)|*.*" wildcard = "HDF5 files (*.hdf5)|*.hdf5|All files (*.*)|*.*"
dlg = wx.FileDialog( dlg = wx.FileDialog(
self, message="Save file as ...", defaultDir=os.getcwd(), self, message="Save file as ...", defaultDir=os.getcwd(),
defaultFile='{}.hdf5'.format(self.data.title.replace(' ','_')), defaultFile='{}.hdf5'.format(self.data.title.replace(' ','_')),
wildcard=wildcard, style=wx.FD_SAVE) wildcard=wildcard, style=wx.FD_SAVE)
dlg.SetFilterIndex(0) dlg.SetFilterIndex(0)
if dlg.ShowModal() == wx.ID_OK: if dlg.ShowModal() == wx.ID_OK:
path = dlg.GetPath() path = dlg.GetPath()
if os.path.exists(path): if os.path.exists(path):
mbx = wx.MessageDialog(self, ('This file already exists. ' mbx = wx.MessageDialog(self, ('This file already exists. '
'Do you wish to overwrite it ?'), 'Do you wish to overwrite it ?'),
'Warning: File exists', 'Warning: File exists',
wx.YES_NO | wx.ICON_WARNING) wx.YES_NO | wx.ICON_WARNING)
if mbx.ShowModal() == wx.ID_NO: if mbx.ShowModal() == wx.ID_NO:
overwrite = False overwrite = False
mbx.Destroy() mbx.Destroy()
if overwrite: if overwrite:
self.data.save(path) self.data.save(path)
self._filename = path self._filename = path
dlg.Destroy() dlg.Destroy()
self.update_title() self.update_title()
def on_viewdata(self, event): def on_viewdata(self, event):
dset = self.data[self._current_dset] dset = self.data[self._current_dset]
frame = _GridWindow(dset, parent=self) frame = _GridWindow(dset, parent=self)
frame.Show() frame.Show()
@ -1004,12 +1021,12 @@ class _DataWindow(wx.Frame):
cluster_viewer.rotate_atoms(45., 45.) cluster_viewer.rotate_atoms(45., 45.)
#cluster_viewer.show_emitter(True) #cluster_viewer.show_emitter(True)
win.Show() win.Show()
def on_viewparameters(self, event): def on_viewparameters(self, event):
dset = self.data[self._current_dset] dset = self.data[self._current_dset]
frame = _ParametersWindow(dset, parent=self) frame = _ParametersWindow(dset, parent=self)
frame.Show() frame.Show()
def on_close(self, event): def on_close(self, event):
if self.data.is_dirty(): if self.data.is_dirty():
mbx = wx.MessageDialog(self, ('Displayed data is unsaved. Do you ' mbx = wx.MessageDialog(self, ('Displayed data is unsaved. Do you '
@ -1020,15 +1037,15 @@ class _DataWindow(wx.Frame):
mbx.Destroy() mbx.Destroy()
return return
self.Destroy() self.Destroy()
def on_menu_dataset(self, event): def on_menu_dataset(self, event):
menu_id = event.GetId() menu_id = event.GetId()
dset_name = self.GetMenuBar().FindItemById(menu_id).GetText() dset_name = self.GetMenuBar().FindItemById(menu_id).GetText()
self.show_dataset(dset_name) self.show_dataset(dset_name)
def show_dataset(self, name): def show_dataset(self, name):
for nb in list(self.notebooks.values()): for nb in list(self.notebooks.values()):
nb.Hide() nb.Hide()
self.notebooks[name].Show() self.notebooks[name].Show()
@ -1037,7 +1054,7 @@ class _DataWindow(wx.Frame):
self._current_dset = name self._current_dset = name
def create_page(self, nb, view): def create_page(self, nb, view):
opts = view._plotopts opts = view._plotopts
p = wx.Panel(nb, -1) p = wx.Panel(nb, -1)
figure = Figure() figure = Figure()
@ -1060,12 +1077,12 @@ class _DataWindow(wx.Frame):
toolbar.update() toolbar.update()
sizer.Add(canvas, 5, wx.ALL|wx.EXPAND) sizer.Add(canvas, 5, wx.ALL|wx.EXPAND)
p.SetSizer(sizer) p.SetSizer(sizer)
p.Fit() p.Fit()
p.Show() p.Show()
for values, label in zip(view.get_data(), opts['legend']): for values, label in zip(view.get_data(), opts['legend']):
if proj in ('ortho', 'stereo'): if proj in ('ortho', 'stereo'):
theta, phi, Xsec = cols2matrix(*values) theta, phi, Xsec = cols2matrix(*values)
@ -1081,9 +1098,9 @@ class _DataWindow(wx.Frame):
im = axes.pcolormesh(X, Y, Xsec) im = axes.pcolormesh(X, Y, Xsec)
axes.set_yticks(R_ticks) axes.set_yticks(R_ticks)
axes.set_yticklabels(theta_ticks) axes.set_yticklabels(theta_ticks)
figure.colorbar(im) figure.colorbar(im)
elif proj == 'polar': elif proj == 'polar':
values[0] = np.radians(values[0]) values[0] = np.radians(values[0])
axes.plot(*values, label=label, picker=5, marker=opts['marker']) axes.plot(*values, label=label, picker=5, marker=opts['marker'])
@ -1107,13 +1124,13 @@ class _DataWindow(wx.Frame):
axes.legend() axes.legend()
axes.autoscale(enable=opts['autoscale']) axes.autoscale(enable=opts['autoscale'])
# MPL events # MPL events
figure.canvas.mpl_connect('motion_notify_event', self.on_mpl_motion) figure.canvas.mpl_connect('motion_notify_event', self.on_mpl_motion)
figure.canvas.mpl_connect('pick_event', self.on_mpl_pick) figure.canvas.mpl_connect('pick_event', self.on_mpl_pick)
nb.AddPage(p, view.title) nb.AddPage(p, view.title)
def update_statusbar(self): def update_statusbar(self):
sb = self.GetStatusBar() sb = self.GetStatusBar()
@ -1123,7 +1140,7 @@ class _DataWindow(wx.Frame):
if item.IsChecked(): if item.IsChecked():
sb.SetStatusText("%s" % item.GetText(), 1) sb.SetStatusText("%s" % item.GetText(), 1)
break break
def update_title(self): def update_title(self):
title = "MsSpec Data Viewer" title = "MsSpec Data Viewer"
if self.data.title: if self.data.title:
@ -1131,7 +1148,7 @@ class _DataWindow(wx.Frame):
if self._filename: if self._filename:
title += " [" + os.path.basename(self._filename) + "]" title += " [" + os.path.basename(self._filename) + "]"
self.SetTitle(title) self.SetTitle(title)
def on_mpl_motion(self, event): def on_mpl_motion(self, event):
sb = self.GetStatusBar() sb = self.GetStatusBar()
try: try:
@ -1139,7 +1156,7 @@ class _DataWindow(wx.Frame):
sb.SetStatusText(txt, 2) sb.SetStatusText(txt, 2)
except Exception: except Exception:
pass pass
def on_mpl_pick(self, event): def on_mpl_pick(self, event):
print(event.artist) print(event.artist)
@ -1186,7 +1203,3 @@ if __name__ == "__main__":
import sys import sys
data = Data.load(sys.argv[1]) data = Data.load(sys.argv[1])
data.view() data.view()

View File

@ -1,10 +1,34 @@
# coding: utf-8 #!/usr/bin/env python
# vim: set fdm=indent ts=4 sw=4 sts=4 et tw=80 ai cc=+0 mouse=a nu : # #
# Copyright © 2016-2020 - Rennes Physics Institute
#
# This file is part of msspec.
#
# msspec is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# msspec is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this msspec. If not, see <http://www.gnu.org/licenses/>.
#
# Source file : src/msspec/misc.py
# Last modified: ven. 10 avril 2020 17:30:42
# Committed by : "Sylvain Tricot <sylvain.tricot@univ-rennes1.fr>"
""" """
Module misc Module misc
=========== ===========
""" """
import logging import logging
from pint import UnitRegistry from pint import UnitRegistry
import numpy as np import numpy as np
@ -12,6 +36,7 @@ import inspect
import re import re
import os import os
class XRaySource(object): class XRaySource(object):
MG_KALPHA = 1253.6 MG_KALPHA = 1253.6
AL_KALPHA = 1486.6 AL_KALPHA = 1486.6
@ -19,10 +44,10 @@ class XRaySource(object):
pass pass
UREG = UnitRegistry() UREG = UnitRegistry()
UREG.define('rydberg = c * h * rydberg_constant = Ry') #UREG.define('rydberg = c * h * rydberg_constant = Ry')
UREG.define('bohr_radius = 4 * pi * epsilon_0 * hbar**2 / electron_mass / e**2 = a0') #UREG.define('bohr_radius = 4 * pi * epsilon_0 * hbar**2 / electron_mass / e**2 = a0')
logging.basicConfig(level=logging.INFO) #logging.basicConfig(level=logging.INFO)
LOGGER = logging.getLogger('msspec') LOGGER = logging.getLogger('msspec')
np.set_printoptions(formatter={'float': lambda x:'%.2f' % x}, threshold=5) np.set_printoptions(formatter={'float': lambda x:'%.2f' % x}, threshold=5)

View File

@ -1,16 +1,47 @@
# coding: utf-8 #!/usr/bin/env python
# vim: set et ts=4 sw=4 nu fdm=indent: #
# Copyright © 2016-2020 - Rennes Physics Institute
#
# This file is part of msspec.
#
# msspec is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# msspec is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this msspec. If not, see <http://www.gnu.org/licenses/>.
#
# Source file : src/msspec/parameters.py
# Last modified: ven. 10 avril 2020 17:31:50
# Committed by : "Sylvain Tricot <sylvain.tricot@univ-rennes1.fr>"
""" """
Module parameters Module parameters
================= =================
""" """
import textwrap
import numpy as np
import re import re
from terminaltables import AsciiTable import textwrap
from msspec.misc import LOGGER, UREG, XRaySource, get_level_from_electron_configuration
import ase import ase
import numpy as np
from terminaltables import AsciiTable
from msspec.misc import get_level_from_electron_configuration
from msspec.misc import LOGGER
from msspec.misc import UREG
from msspec.misc import XRaySource
from msspec.utils import ForeignPotential
from msspec.utils import SPRKKRPotential
class Parameter(object): class Parameter(object):
def __init__(self, name, types=None, limits=(None, None), def __init__(self, name, types=None, limits=(None, None),
@ -31,7 +62,7 @@ class Parameter(object):
self.fmt = fmt self.fmt = fmt
self.binding = binding self.binding = binding
self.private = private self.private = private
self.docstring = doc self.docstring = textwrap.dedent(doc)
self._value = None self._value = None
self.value = default self.value = default
@ -59,41 +90,41 @@ class Parameter(object):
return value.to(self.unit).magnitude return value.to(self.unit).magnitude
return value return value
def assert_message(self, msg, *args):
s = '\'{}\': {}'.format(self.name, msg)
s = s.format(*args)
try:
allowed_types = '\n'.join(
['- ' + str(_) for _ in self.allowed_types])
except TypeError:
allowed_types = self.allowed_types
try:
allowed_values = '\n'.join(
['- ' + str(_) for _ in self.allowed_values])
except TypeError:
allowed_values = self.allowed_values
data = [
['PROPERTY', 'VALUE'],
['Name', '\'{}\''.format(self.name)],
['Allowed types', '{}'.format(allowed_types)],
['Limits', '{} <= value <= {}'.format(self.low_limit,
self.high_limit)],
['Unit', '{}'.format(self.unit)],
['Allowed values', '{}'.format(allowed_values)],
['Default', '{}'.format(self.default)]
]
t = AsciiTable(data)
table = '\n'.join(['\t' * 2 + _ for _ in t.table.split('\n')])
s = "{}\n\n{}\n{}".format(s, table, self.docstring)
return s
def check(self, value): def check(self, value):
def assert_message(msg, *args):
s = '\'{}\': {}'.format(self.name, msg)
s = s.format(*args)
try:
allowed_types = '\n'.join(
['- ' + str(_) for _ in self.allowed_types])
except TypeError:
allowed_types = self.allowed_types
try:
allowed_values = '\n'.join(
['- ' + str(_) for _ in self.allowed_values])
except TypeError:
allowed_values = self.allowed_values
data = [
['PROPERTY', 'VALUE'],
['Name', '\'{}\''.format(self.name)],
['Allowed types', '{}'.format(allowed_types)],
['Limits', '{} <= value <= {}'.format(self.low_limit,
self.high_limit)],
['Unit', '{}'.format(self.unit)],
['Allowed values', '{}'.format(allowed_values)],
['Default', '{}'.format(self.default)]
]
t = AsciiTable(data)
table = '\n'.join(['\t' * 2 + _ for _ in t.table.split('\n')])
s = "{}\n\n{}\n{}".format(s, table, self.docstring)
return s
# convert if a unit was given # convert if a unit was given
_value = self.convert(value) _value = self.convert(value)
@ -106,7 +137,7 @@ class Parameter(object):
assert bool in self.allowed_types assert bool in self.allowed_types
assert isinstance(_value, self.allowed_types) assert isinstance(_value, self.allowed_types)
except AssertionError: except AssertionError:
raise AssertionError(assert_message( raise AssertionError(self.assert_message(
'Type {} is not an allowed type for this option ' 'Type {} is not an allowed type for this option '
'({} allowed)', '({} allowed)',
str(type(_value)), str(self.allowed_types))) str(type(_value)), str(self.allowed_types)))
@ -120,22 +151,22 @@ class Parameter(object):
# for i, val in enumerate(_values): # for i, val in enumerate(_values):
for val in np.array(_value).flatten(): for val in np.array(_value).flatten():
if self.low_limit != None: if self.low_limit != None:
assert val >= self.low_limit, assert_message( assert val >= self.low_limit, self.assert_message(
'Value must be >= {:s}', 'Value must be >= {:s}',
str(self.low_limit)) str(self.low_limit))
if self.high_limit != None: if self.high_limit != None:
assert val <= self.high_limit, assert_message( assert val <= self.high_limit, self.assert_message(
'Value must be <= {:s}', 'Value must be <= {:s}',
str(self.high_limit)) str(self.high_limit))
if self.allowed_values != None and isinstance(val, tuple(type(x) for x in self.allowed_values)): if self.allowed_values != None: # and isinstance(val, tuple(type(x) for x in self.allowed_values)):
assert val in self.allowed_values, assert_message( assert val in self.allowed_values, self.assert_message(
'This value is not allowed. Please choose between ' 'This value is not allowed. Please choose between '
'one of {:s}', 'one of {:s}',
str(self.allowed_values)) str(self.allowed_values))
if self.pattern != None: if self.pattern != None:
p = re.compile(self.pattern) p = re.compile(self.pattern)
m = p.match(val) m = p.match(val)
assert m != None, assert_message( assert m != None, self.assert_message(
'Wrong string format') 'Wrong string format')
# _value[i] = val # _value[i] = val
@ -157,8 +188,8 @@ class Parameter(object):
new_value = None new_value = None
try: try:
new_value = self.binding(self) new_value = self.binding(self)
except AttributeError: except AttributeError as err:
pass LOGGER.warning(err)
if new_value is not None: if new_value is not None:
self._value = new_value self._value = new_value
# LOGGER.debug('{:s} = {:s}'.format(self.name, str(self.value))) # LOGGER.debug('{:s} = {:s}'.format(self.name, str(self.value)))
@ -716,72 +747,72 @@ class GlobalParameters(BaseParameters):
parameters = ( parameters = (
Parameter('spectroscopy', types=str, allowed_values=( Parameter('spectroscopy', types=str, allowed_values=(
'PED', 'AED', 'LEED', 'EXAFS', 'EIG'), default='PED', 'PED', 'AED', 'LEED', 'EXAFS', 'EIG'), default='PED',
doc=textwrap.dedent(""" doc="""
There are 4 choices for the spectroscopy option: There are 4 choices for the spectroscopy option:
- '**PED**', for Photo Electron Diffraction - '**PED**', for Photo Electron Diffraction
- '**AED**', for Auger Electron Diffraction - '**AED**', for Auger Electron Diffraction
- '**LEED**', for Low Energy Electron Diffraction - '**LEED**', for Low Energy Electron Diffraction
- '**EXAFS**', for the Extended X-ray Absorption Fine Structure - '**EXAFS**', for the Extended X-ray Absorption Fine Structure
Additionally, a 5th keyword **EIG** is used to get deeper information Additionally, a 5th keyword **EIG** is used to get deeper information
about the convergence of the eigen values of multiple scattering about the convergence of the eigen values of multiple scattering
matrix. matrix.
The value is case insensitive. The value is case insensitive.
""")), """),
Parameter('algorithm', types=str, allowed_values=('expansion', Parameter('algorithm', types=str, allowed_values=('expansion',
'inversion', 'inversion',
'correlation', 'correlation',
'power'), 'power'),
default='expansion', doc=textwrap.dedent(""" default='expansion', doc="""
You can choose the algorithm used for the computation of the scattering path operator. You can choose the algorithm used for the computation of the scattering path operator.
- '**inversion**', for the classical matrix inversion method - '**inversion**', for the classical matrix inversion method
- '**expansion**', for the Rehr-Albers series expansion - '**expansion**', for the Rehr-Albers series expansion
- '**correlation**', for the correlation-expansion algorithm - '**correlation**', for the correlation-expansion algorithm
- '**power**', for the power method approximation scheme (only for spectroscopy='EIG') - '**power**', for the power method approximation scheme (only for spectroscopy='EIG')
The series expansion algorithm is well suited for high energy since the number of terms The series expansion algorithm is well suited for high energy since the number of terms
required decreases as the energy increases. The exact solution is obtained by the matrix inversion required decreases as the energy increases. The exact solution is obtained by the matrix inversion
method but should be preferably used for lower energy. method but should be preferably used for lower energy.
""")), """),
Parameter('polarization', types=(type(None), str), Parameter('polarization', types=(type(None), str),
allowed_values=(None, 'linear_qOz', 'linear_xOy', allowed_values=(None, 'linear_qOz', 'linear_xOy',
'circular'), default=None, doc=textwrap.dedent(""" 'circular'), default=None, doc="""
Specify the polarization of the incident light. Specify the polarization of the incident light.
- **None**, for unpolarized light - **None**, for unpolarized light
- '**linear_qOz**' for a polarization vector in the :math:`(\\vec{q}0z)` plane - '**linear_qOz**' for a polarization vector in the :math:`(\\vec{q}0z)` plane
- '**linear_xOy**' for a polarization vector in the :math:`(x0y)` plane - '**linear_xOy**' for a polarization vector in the :math:`(x0y)` plane
- '**circular**' for circular dichroism - '**circular**' for circular dichroism
""")), """),
Parameter('dichroism', types=(type(None), str), Parameter('dichroism', types=(type(None), str),
allowed_values=(None, 'natural', 'sum_over_spin', allowed_values=(None, 'natural', 'sum_over_spin',
'spin_resolved'), default=None, doc=textwrap.dedent(""" 'spin_resolved'), default=None, doc="""
Used to perform dichroic calculations. The default (None) is to disable this. Used to perform dichroic calculations. The default (None) is to disable this.
""")), """),
Parameter('spinpol', types=bool, default=False, doc=textwrap.dedent(""" Parameter('spinpol', types=bool, default=False, doc="""
Enable or disbale spin-resolved calculations. Enable or disbale spin-resolved calculations.
""")), """),
Parameter('folder', types=str, default='./calc', doc=textwrap.dedent(""" Parameter('folder', types=str, default='./calc', doc="""
This parameter is the path to the temporary folder used for the calculations. If you do not change this This parameter is the path to the temporary folder used for the calculations. If you do not change this
parameter between calculations, all the content will be overridden. This is usually not a problem, since the parameter between calculations, all the content will be overridden. This is usually not a problem, since the
whole bunch of data created during a computation is not meant to be saved. But you may want to anyway by whole bunch of data created during a computation is not meant to be saved. But you may want to anyway by
changing it to another path. changing it to another path.
This folder is not automatically removed after a computation. It is removed when calling the :meth:`shutdown` This folder is not automatically removed after a computation. It is removed when calling the :meth:`shutdown`
calculator method: calculator method:
.. code-block:: python .. code-block:: python
calc = MSSPEC() # the './calc' folder is created calc = MSSPEC() # the './calc' folder is created
# do your calculation here # do your calculation here
calc.shutdown() # the folder is removed calc.shutdown() # the folder is removed
""")) """)
) )
BaseParameters.__init__(self) BaseParameters.__init__(self)
self.add_parameters(*parameters) self.add_parameters(*parameters)
@ -801,7 +832,7 @@ class GlobalParameters(BaseParameters):
phagen_calctype, spec_calctype = mapping[p.value] phagen_calctype, spec_calctype = mapping[p.value]
self.phagen_parameters.calctype = phagen_calctype self.phagen_parameters.calctype = phagen_calctype
self.spec_parameters.calctype_spectro = spec_calctype self.spec_parameters.calctype_spectro = spec_calctype
def bind_spinpol(self, p): def bind_spinpol(self, p):
if p.value == True: if p.value == True:
LOGGER.error('Spin polarization is not yet enabled in the Python version.') LOGGER.error('Spin polarization is not yet enabled in the Python version.')
@ -811,48 +842,48 @@ class GlobalParameters(BaseParameters):
if p.value is not None: if p.value is not None:
LOGGER.error('Dichroism is not yet enabled in the Python version.') LOGGER.error('Dichroism is not yet enabled in the Python version.')
raise NotImplemented raise NotImplemented
class MuffintinParameters(BaseParameters): class MuffintinParameters(BaseParameters):
def __init__(self, phagen_parameters, spec_parameters): def __init__(self, phagen_parameters, spec_parameters):
parameters = ( parameters = (
Parameter('charge_relaxation', types=bool, default=True, doc=textwrap.dedent(""" Parameter('charge_relaxation', types=bool, default=True, doc="""
Used to specify wether the charge density of the photoabsorbing atom, which is used Used to specify wether the charge density of the photoabsorbing atom, which is used
to construct the potential, is allowaed to relax around the core hole or not. to construct the potential, is allowaed to relax around the core hole or not.
""")), """),
Parameter('ionicity', types=dict, default={}, doc=textwrap.dedent(""" Parameter('ionicity', types=dict, default={}, doc="""
A dictionary to specify the ionicity of each kind of atoms. If empty, the atoms are considered to be A dictionary to specify the ionicity of each kind of atoms. If empty, the atoms are considered to be
neutral. Otherwise, each key must be a chemical symbol and the corresponding value should be the fraction neutral. Otherwise, each key must be a chemical symbol and the corresponding value should be the fraction
of electrons added (negative) or substracted (positive) with respect to neutrality. of electrons added (negative) or substracted (positive) with respect to neutrality.
As an example for a LaFeO\ :sub:`3` cluster:: As an example for a LaFeO\ :sub:`3` cluster::
>>> calc.muffintin_parameters.ionicity = {'La': 0.15, 'Fe': 0.15, 'O': -0.1} >>> calc.muffintin_parameters.ionicity = {'La': 0.15, 'Fe': 0.15, 'O': -0.1}
means that 0.15 electrons have been substracted from La, likewise from Fe. Neutrality implies that 0.3 means that 0.15 electrons have been substracted from La, likewise from Fe. Neutrality implies that 0.3
electrons have to be added to oxygen atoms. electrons have to be added to oxygen atoms.
""")), """),
Parameter('relativistic_mode', types=str, Parameter('relativistic_mode', types=str,
allowed_values=('non_relativistic', 'scalar_relativistic', allowed_values=('non_relativistic', 'scalar_relativistic',
'spin_orbit_resolved'), 'spin_orbit_resolved'),
default='non_relativistic', doc=textwrap.dedent(""" default='non_relativistic', doc="""
To tell whether to use the scalar relativistic approximation or not. To tell whether to use the scalar relativistic approximation or not.
""")), """),
Parameter('radius_overlapping', types=float, limits=(0., 1.), Parameter('radius_overlapping', types=float, limits=(0., 1.),
default=0., doc=textwrap.dedent(""" default=0., doc="""
to allow atomic spheres to overlapp with each other. The value is a percentage, 1. means 100%. to allow atomic spheres to overlapp with each other. The value is a percentage, 1. means 100%.
""")), """),
Parameter('interstitial_potential', types=(int, float), Parameter('interstitial_potential', types=(int, float),
unit=UREG.eV, default=0., doc=textwrap.dedent(""" unit=UREG.eV, default=0., doc="""
The average interstitial potential (or inner potential) expressed in eV. It is used to compute The average interstitial potential (or inner potential) expressed in eV. It is used to compute
the refraction at the surface. the refraction at the surface.
""")), """),
Parameter('hydrogen_radius', types=(int, float), default=0.9, Parameter('hydrogen_radius', types=(int, float), default=0.9,
limits=(0., None), unit=UREG.angstroms, doc=textwrap.dedent(""" limits=(0., None), unit=UREG.angstroms, doc="""
The program can have difficulties to find the radius of the hydrogen atom (small atom). You can The program can have difficulties to find the radius of the hydrogen atom (small atom). You can
specify here a value for the radius. If you set it to 'None', the calculation of the muffin-tin specify here a value for the radius. If you set it to 'None', the calculation of the muffin-tin
radius of H atoms will be left to the program. radius of H atoms will be left to the program.
""")) """)
) )
BaseParameters.__init__(self) BaseParameters.__init__(self)
self.add_parameters(*parameters) self.add_parameters(*parameters)
@ -884,71 +915,71 @@ class MuffintinParameters(BaseParameters):
class TMatrixParameters(BaseParameters): class TMatrixParameters(BaseParameters):
def __init__(self, phagen_parameters): def __init__(self, phagen_parameters):
parameters = ( parameters = (
Parameter('potential', types=str, Parameter('potential', types=(str, ForeignPotential),
allowed_values=('muffin_tin', 'lmto', 'spkkr', 'msf'), default='muffin_tin',
default='muffin_tin', doc=textwrap.dedent(""" doc="""
This option allows to choose which kind of potential is used to compute the T-Matrix. For now, This option allows to choose which kind of potential is
only the internal *Muffin-Tin* potential is supported. used to compute the T-Matrix. For now, only the internal
""")), *Muffin-Tin* potential is supported.""",
Parameter('potential_file', types=(str,type(None)), default=None), ),
Parameter('exchange_correlation', types=str, Parameter('exchange_correlation', types=str,
allowed_values=('hedin_lundqvist_real', allowed_values=('hedin_lundqvist_real',
'hedin_lundqvist_complex', 'hedin_lundqvist_complex',
'x_alpha_real', 'x_alpha_real',
'dirac_hara_real', 'dirac_hara_complex'), 'dirac_hara_real', 'dirac_hara_complex'),
default='hedin_lundqvist_complex', doc=textwrap.dedent(""" default='hedin_lundqvist_complex', doc="""
Set the type of exchange and correlation potential that will be used. Set the type of exchange and correlation potential that will be used.
""")), """),
Parameter('imaginery_part', types=(int, float), default=0., doc=textwrap.dedent(""" Parameter('imaginery_part', types=(int, float), default=0., doc="""
This value is added to complex potentials to account for core-hole lifetime and experimental resolution This value is added to complex potentials to account for core-hole lifetime and experimental resolution
broadening effects. broadening effects.
""")), """),
Parameter('lmax_mode', types=str, Parameter('lmax_mode', types=str,
allowed_values=('max_ke', 'true_ke', 'imposed'), allowed_values=('max_ke', 'true_ke', 'imposed'),
default='true_ke', doc=textwrap.dedent(""" default='true_ke', doc="""
This allows to control the number of basis functions used to expand the wave function on each This allows to control the number of basis functions used to expand the wave function on each
atom. It can be: atom. It can be:
- '**imposed**', and will be equal to the *lmaxt* parameter (see below). It is therefore independent - '**imposed**', and will be equal to the *lmaxt* parameter (see below). It is therefore independent
on both the energy and atom type. on both the energy and atom type.
- '**max_ke**', in this case :math:`l_{max}` is computed according to the formula - '**max_ke**', in this case :math:`l_{max}` is computed according to the formula
:math:`l_{max} = kr_{at} + 1` where :math:`k=E^{1/2}_{max}` with :math:`E_{max}` being the :math:`l_{max} = kr_{at} + 1` where :math:`k=E^{1/2}_{max}` with :math:`E_{max}` being the
maximum kinetic energy. In this case :math:`l_{max}` is independent of the energy but maximum kinetic energy. In this case :math:`l_{max}` is independent of the energy but
depends on the atom number. depends on the atom number.
- '**true_ke**', in this case :math:`l_{max}` is computed according to the formula - '**true_ke**', in this case :math:`l_{max}` is computed according to the formula
:math:`l_{max} = kr_{at} + 1` where :math:`k=E^{1/2}_k` with :math:`E_k` being the kinetic :math:`l_{max} = kr_{at} + 1` where :math:`k=E^{1/2}_k` with :math:`E_k` being the kinetic
energy. In this case :math:`l_{max}` depends both on the energy and the atom number. energy. In this case :math:`l_{max}` depends both on the energy and the atom number.
""")), """),
Parameter('lmaxt', types=int, limits=(1, None), default=19, doc=textwrap.dedent(""" Parameter('lmaxt', types=int, limits=(1, None), default=19, doc="""
The value of :math:`l_{max}` if *lmax_mode = 'imposed'* The value of :math:`l_{max}` if *lmax_mode = 'imposed'*
""")), """),
Parameter('tl_threshold', types=(type(None), float), default=None, doc=textwrap.dedent(""" Parameter('tl_threshold', types=(type(None), float), default=None, doc="""
This option allows to control the number of basis function by defining a threshold value for the *tl*. This option allows to control the number of basis function by defining a threshold value for the *tl*.
For example:: For example::
>>> calc.tmatrix_parameters.tl_threshold = 1e-6 >>> calc.tmatrix_parameters.tl_threshold = 1e-6
will remove all *tl* with a value :math:`< 1.10^{-6}` will remove all *tl* with a value :math:`< 1.10^{-6}`
.. note:: .. note::
This option is compatible with any modes of the *lmax_mode* option. This option is compatible with any modes of the *lmax_mode* option.
""")), """),
Parameter('max_tl', types=(type(None), dict), default=None, doc=textwrap.dedent(""" Parameter('max_tl', types=(type(None), dict), default=None, doc="""
*max_tl* is used to sepcify a maximum number of basis functions to use for each kind of atoms. For example, *max_tl* is used to sepcify a maximum number of basis functions to use for each kind of atoms. For example,
in the case of an MgO cluster, you could write:: in the case of an MgO cluster, you could write::
>>> calc.muffintin_parameters.max_tl = {'Mg': 20, 'O', 15} >>> calc.muffintin_parameters.max_tl = {'Mg': 20, 'O', 15}
to tell the program to use at most 20 *tl* for Mg and 15 for O. It acts like a filter, meaning that if you to tell the program to use at most 20 *tl* for Mg and 15 for O. It acts like a filter, meaning that if you
use this option, you are not required to sepcif a value for each kind of atoms in your cluster. You can use this option, you are not required to sepcif a value for each kind of atoms in your cluster. You can
restrict the number of *tl* only for one type of atom for example. restrict the number of *tl* only for one type of atom for example.
.. note:: .. note::
This option is compatible with any modes of the *lmax_mode* option. This option is compatible with any modes of the *lmax_mode* option.
""")) """)
) )
BaseParameters.__init__(self) BaseParameters.__init__(self)
self.add_parameters(*parameters) self.add_parameters(*parameters)
@ -956,12 +987,17 @@ class TMatrixParameters(BaseParameters):
self.freeze() self.freeze()
def bind_potential(self, p): def bind_potential(self, p):
mapping = {'muffin_tin': 'in', 'lmto': 'ex', 'spkkr': 'ex', 'msf': 'ex'} mapping = {SPRKKRPotential: 'spkkr'}
if p.value.lower() in ('muffin_tin'): if isinstance(p.value, str):
assert p.value.lower() in ('muffin_tin'), p.assert_message(
"\"{}\" is not an known potential type", p.value)
self.phagen_parameters.potgen = 'in' self.phagen_parameters.potgen = 'in'
elif p.value.lower() in ('spkkr', 'lmto', 'msf'): elif isinstance(p.value, ForeignPotential):
self.phagen_parameters.potgen = 'ex' self.phagen_parameters.potgen = 'ex'
self.phagen_parameters.potype = p.value.lower() potype = mapping[p.value.__class__]
self.phagen_parameters.potype = potype
def bind_exchange_correlation(self, p): def bind_exchange_correlation(self, p):
potential = self.get_parameter('potential').value potential = self.get_parameter('potential').value
@ -975,10 +1011,11 @@ class TMatrixParameters(BaseParameters):
} }
self.phagen_parameters.potype = mapping[p.value] self.phagen_parameters.potype = mapping[p.value]
else: else:
self.phagen_parameters.potype = potential mapping = {SPRKKRPotential: 'spkkr'}
self.phagen_parameters.potype = mapping[potential.value.__class__]
def bind_potential_file(self, p): def bind_potential_file(self, p):
pass pass
def bind_imaginery_part(self, p): def bind_imaginery_part(self, p):
self.phagen_parameters.gamma = p.value self.phagen_parameters.gamma = p.value
@ -1009,37 +1046,37 @@ class SourceParameters(BaseParameters):
def __init__(self, global_parameters=None, phagen_parameters=None, spec_parameters=None): def __init__(self, global_parameters=None, phagen_parameters=None, spec_parameters=None):
parameters = ( parameters = (
Parameter('energy', types=(list, tuple, int, float), Parameter('energy', types=(list, tuple, int, float),
limits=(0, None), unit=UREG.eV, doc=textwrap.dedent(""" limits=(0, None), unit=UREG.eV, doc="""
The photon energy in eV. The photon energy in eV.
Common laboratories X-ray source Mg |kalpha| and Al |kalpha| lines are Common laboratories X-ray source Mg |kalpha| and Al |kalpha| lines are
defined in the :py:class:`msspec.misc.XRaySource` class. For example: defined in the :py:class:`msspec.misc.XRaySource` class. For example:
.. highlight:: python .. highlight:: python
:: ::
>>> from msspec.calculator import MSSPEC >>> from msspec.calculator import MSSPEC
>>> calc = MSSPEC() >>> calc = MSSPEC()
>>> calc.source_parameters.energy = XRaySource.MG_KALPHA >>> calc.source_parameters.energy = XRaySource.MG_KALPHA
>>> print calc.source_parameters.energy >>> print calc.source_parameters.energy
1253.6 1253.6
"""), """,
default=XRaySource.MG_KALPHA), default=XRaySource.MG_KALPHA),
Parameter('theta', types=(int, float), limits=(-180., 180.), Parameter('theta', types=(int, float), limits=(-180., 180.),
unit=UREG.degree, default=-55., doc=textwrap.dedent(""" unit=UREG.degree, default=-55., doc="""
The polar angle of the photon incidence (in degrees). Please refer to The polar angle of the photon incidence (in degrees). Please refer to
:ref:`this figure <ped_full_picture>` for questions regarding the proper :ref:`this figure <ped_full_picture>` for questions regarding the proper
orientation. orientation.
""")), """),
Parameter('phi', types=(int, float), limits=(-180., 180.), Parameter('phi', types=(int, float), limits=(-180., 180.),
unit=UREG.degree, default=0., doc=textwrap.dedent(""" unit=UREG.degree, default=0., doc="""
The azimuthal angle of the photon incidence (in degrees). Please refer to The azimuthal angle of the photon incidence (in degrees). Please refer to
:ref:`this figure <ped_full_picture>` for questions regarding the proper :ref:`this figure <ped_full_picture>` for questions regarding the proper
orientation. orientation.
""")), """),
) )
BaseParameters.__init__(self) BaseParameters.__init__(self)
self.add_parameters(*parameters) self.add_parameters(*parameters)
@ -1091,13 +1128,13 @@ class DetectorParameters(BaseParameters):
parameters = ( parameters = (
Parameter('angular_acceptance', types=(int, float), Parameter('angular_acceptance', types=(int, float),
unit=UREG.degree, limits=(0., None), default=0., unit=UREG.degree, limits=(0., None), default=0.,
doc=textwrap.dedent(""" doc="""
The angular acceptance of the detector in degrees used The angular acceptance of the detector in degrees used
when the *average_sampling* option is enabled below. when the *average_sampling* option is enabled below.
""")), """),
Parameter('average_sampling', types=(type(None), str), Parameter('average_sampling', types=(type(None), str),
allowed_values=(None, 'low', 'medium', 'high'), allowed_values=(None, 'low', 'medium', 'high'),
default=None, doc=textwrap.dedent(""" default=None, doc="""
Used to averaged the signal over directions lying in the Used to averaged the signal over directions lying in the
cone of half-angle *angular_acceptance*. The number of cone of half-angle *angular_acceptance*. The number of
directions to take into account depends on the choosen directions to take into account depends on the choosen
@ -1107,11 +1144,11 @@ class DetectorParameters(BaseParameters):
- '**low**', to average over 5 directions - '**low**', to average over 5 directions
- '**medium**', to average over 13 directions - '**medium**', to average over 13 directions
- '**high**', to average over 49 directions - '**high**', to average over 49 directions
""")), """),
Parameter('rotate', types=bool, default=False, doc=textwrap.dedent(""" Parameter('rotate', types=bool, default=False, doc="""
When False, the sample is rotated when doing a scan (the When False, the sample is rotated when doing a scan (the
usual way). Otherwise, the detector is rotated. usual way). Otherwise, the detector is rotated.
""")) """)
) )
BaseParameters.__init__(self) BaseParameters.__init__(self)
self.add_parameters(*parameters) self.add_parameters(*parameters)
@ -1172,18 +1209,18 @@ class ScanParameters(BaseParameters):
default=np.array([0.])), default=np.array([0.])),
Parameter('kinetic_energy', types=(list, tuple, int, float), Parameter('kinetic_energy', types=(list, tuple, int, float),
unit=UREG.eV, limits=(0., None), unit=UREG.eV, limits=(0., None),
default=200., doc=textwrap.dedent(""" default=200., doc="""
if given as a list or tuple: if given as a list or tuple:
* with 2 elements, 10 points of energy will be generated * with 2 elements, 10 points of energy will be generated
with the first element as the starting energy and the with the first element as the starting energy and the
second element as the stopping energy. second element as the stopping energy.
* with 3 elements, the first element is the starting energy * with 3 elements, the first element is the starting energy
the second one is the stopping energy and the last one is the second one is the stopping energy and the last one is
the number of points. the number of points.
if given as a float or integer, there will be one point if given as a float or integer, there will be one point
for the kinetic energy. for the kinetic energy.
""")), """),
Parameter('ke_array', types=np.ndarray, unit=UREG.eV, Parameter('ke_array', types=np.ndarray, unit=UREG.eV,
default=np.array([200., ]), private=True) default=np.array([200., ]), private=True)
) )
@ -1380,58 +1417,58 @@ class CalculationParameters(BaseParameters):
def __init__(self, global_parameters, phagen_parameters, spec_parameters): def __init__(self, global_parameters, phagen_parameters, spec_parameters):
parameters = ( parameters = (
Parameter('RA_cutoff', types=int, limits=(0, 8), default=1, Parameter('RA_cutoff', types=int, limits=(0, 8), default=1,
doc=textwrap.dedent(""" doc="""
The Rehr-Albers cut-off parameter which controls the degree of The Rehr-Albers cut-off parameter which controls the degree of
sphericity introduced in the description of the basis functions sphericity introduced in the description of the basis functions
used to expand the wave function around each atomic center. used to expand the wave function around each atomic center.
It is only meaningful for the series expansion algorithm. It is only meaningful for the series expansion algorithm.
Its value is limited to 8 but it is rarely necessary to go beyond Its value is limited to 8 but it is rarely necessary to go beyond
2 or 3.""")), 2 or 3."""),
Parameter('scattering_order', types=int, limits=(1, 10), default=3, Parameter('scattering_order', types=int, limits=(1, 10), default=3,
doc=textwrap.dedent(""" doc="""
The scattering order. Only meaningful for the 'expansion' algorithm. The scattering order. Only meaningful for the 'expansion' algorithm.
Its value is limited to 10.""")), Its value is limited to 10."""),
Parameter('renormalization_mode', allowed_values=(None, 'G_n', 'Sigma_n', Parameter('renormalization_mode', allowed_values=(None, 'G_n', 'Sigma_n',
'Z_n', 'Pi_1', 'Lowdin'), 'Z_n', 'Pi_1', 'Lowdin'),
types=(type(None), str), default=None, types=(type(None), str), default=None,
doc=textwrap.dedent(""" doc="""
Enable the calculation of the coefficients for the renormalization of Enable the calculation of the coefficients for the renormalization of
the multiple scattering series. the multiple scattering series.
You can choose to renormalize in terms of the :math:`G_n`, the You can choose to renormalize in terms of the :math:`G_n`, the
:math:`\\Sigma_n`, the :math:`Z_n`, the :math:`\\Pi_1` or the Lowdin :math:`\\Sigma_n`, the :math:`Z_n`, the :math:`\\Pi_1` or the Lowdin
:math:`K^2` matrices""")), :math:`K^2` matrices"""),
Parameter('renormalization_omega', types=(int,float,complex), Parameter('renormalization_omega', types=(int,float,complex),
default=1.+0j, default=1.+0j,
doc=textwrap.dedent(""" doc="""
The :math:`\\omega` coefficient used to initialize the The :math:`\\omega` coefficient used to initialize the
renormalization alogorithm.""")), renormalization alogorithm."""),
Parameter('RA_cutoff_damping', types=int, limits=(0, None), Parameter('RA_cutoff_damping', types=int, limits=(0, None),
default=0, doc=textwrap.dedent(""" default=0, doc="""
The Rehr-Albers truncation order. If > 0, the *RA_cutoff* is The Rehr-Albers truncation order. If > 0, the *RA_cutoff* is
decreased by 1 every *i*\ :sup:`th` scatterer until 0, where decreased by 1 every *i*\ :sup:`th` scatterer until 0, where
*i* = *RA_cutoff_damping*.""")), *i* = *RA_cutoff_damping*."""),
Parameter('spin_flip', types=bool, default=False, Parameter('spin_flip', types=bool, default=False,
doc=textwrap.dedent(""" doc="""
This parameter tells if spin-flip is authorized or not in the This parameter tells if spin-flip is authorized or not in the
scattering process. scattering process.
:Note: :Note:
This option works only if the spin polarization is This option works only if the spin polarization is
enabled in your calculator object (see spinpol_).""")), enabled in your calculator object (see spinpol_)."""),
Parameter('integrals', types=str, allowed_values=('all', Parameter('integrals', types=str, allowed_values=('all',
'diagonal'), 'diagonal'),
default='all', doc=textwrap.dedent(""" default='all', doc="""
This option allows to take into account all four radial integrals This option allows to take into account all four radial integrals
(R++, R+-, R-+ and R--) in the calculation or only the diagonal (R++, R+-, R-+ and R--) in the calculation or only the diagonal
radial integrals (R++ and R--) which are generally much larger. radial integrals (R++ and R--) which are generally much larger.
.. note:: .. note::
This option works only if the spin polarization is This option works only if the spin polarization is
enabled in your calculator object. enabled in your calculator object.
""")), """),
Parameter('path_filtering', types=(type(None), str, tuple, list), Parameter('path_filtering', types=(type(None), str, tuple, list),
allowed_values=(None, allowed_values=(None,
'forward_scattering', 'forward_scattering',
@ -1439,78 +1476,78 @@ class CalculationParameters(BaseParameters):
'distance_cutoff', 'distance_cutoff',
'plane_wave_normal', 'plane_wave_normal',
'plane_wave_spin_averaged'), 'plane_wave_spin_averaged'),
default=None, doc=textwrap.dedent(""" default=None, doc="""
Used to activate some filters. It is possible to specify several Used to activate some filters. It is possible to specify several
of them by grouping them in a tuple or a list. For example:: of them by grouping them in a tuple or a list. For example::
>>> my_filters = ('forward_scattering', 'backward_scattering') >>> my_filters = ('forward_scattering', 'backward_scattering')
>>> calc.calculation_parameters.path_filtering = my_filters >>> calc.calculation_parameters.path_filtering = my_filters
""")), """),
Parameter('off_cone_events', types=int, limits=(0, None), default=1, Parameter('off_cone_events', types=int, limits=(0, None), default=1,
doc=textwrap.dedent(""" doc="""
Used in conjunction with the '*forward_scattering*' filter. Used in conjunction with the '*forward_scattering*' filter.
If the number of scattering processes outside the forward (or If the number of scattering processes outside the forward (or
backward) scattering cone is greater than this number, then the backward) scattering cone is greater than this number, then the
path is rejected and its contribution to the scattering path path is rejected and its contribution to the scattering path
operator wont be computed. operator wont be computed.
""")), """),
Parameter('scattering_order_cutoff', types=int, limits=(0, 10), Parameter('scattering_order_cutoff', types=int, limits=(0, 10),
default=2, doc=textwrap.dedent(""" default=2, doc="""
Used in conjunction with the *plane_wave_normal* filter. It states Used in conjunction with the *plane_wave_normal* filter. It states
to activate the plane wave approximation (which is fast but to activate the plane wave approximation (which is fast but
less accurate) to compute the contribution when the scattering order less accurate) to compute the contribution when the scattering order
is greater than this value.""")), is greater than this value."""),
Parameter('distance', types=(int, float), limits=(0, None), Parameter('distance', types=(int, float), limits=(0, None),
unit=UREG.angstroms, default=10., doc=textwrap.dedent(""" unit=UREG.angstroms, default=10., doc="""
Used with the '*distance_cut_off*' filter. Paths whose length is Used with the '*distance_cut_off*' filter. Paths whose length is
larger than this value are simply rejected.""")), larger than this value are simply rejected."""),
Parameter('vibrational_damping', types=(type(None), str), Parameter('vibrational_damping', types=(type(None), str),
allowed_values=(None, 'debye_waller', 'averaged_tl'), allowed_values=(None, 'debye_waller', 'averaged_tl'),
default='debye_waller', doc=textwrap.dedent(""" default='debye_waller', doc="""
Tells how to compute the effect of atomic vibrations. It can be: Tells how to compute the effect of atomic vibrations. It can be:
- '**debye_waller**' for using the Debye Waller model. - '**debye_waller**' for using the Debye Waller model.
- '**averaged_tl**' to use the more correct averaging over T-matrix elements.""")), - '**averaged_tl**' to use the more correct averaging over T-matrix elements."""),
Parameter('temperature', types=(int, float), limits=(0, None), Parameter('temperature', types=(int, float), limits=(0, None),
unit=UREG.degK, default=293., doc=textwrap.dedent(""" unit=UREG.degK, default=293., doc="""
The temperature of the cluster. Used when *use_debye_model* = True The temperature of the cluster. Used when *use_debye_model* = True
""")), """),
Parameter('debye_temperature', types=(int, float), limits=(0, None), Parameter('debye_temperature', types=(int, float), limits=(0, None),
unit=UREG.degK, default=420., doc=textwrap.dedent(""" unit=UREG.degK, default=420., doc="""
The Debye temperature used for the calculation of the mean square The Debye temperature used for the calculation of the mean square
displacements if *use_debye_model* = True""")), displacements if *use_debye_model* = True"""),
Parameter('use_debye_model', types=bool, default=False, Parameter('use_debye_model', types=bool, default=False,
doc=textwrap.dedent(""" doc="""
No matter the way you compute the effect of atomic vibrations, No matter the way you compute the effect of atomic vibrations,
you need the mean square displacements of atoms. It can be computed you need the mean square displacements of atoms. It can be computed
internally following the Debye model if you set this option to True. internally following the Debye model if you set this option to True.
""")), """),
Parameter('vibration_scaling', types=(int, float), Parameter('vibration_scaling', types=(int, float),
limits=(0., None), default=1.2, doc=textwrap.dedent(""" limits=(0., None), default=1.2, doc="""
Used to simulate the fact that surface atoms vibrate more than Used to simulate the fact that surface atoms vibrate more than
bulk ones. It is a factor applied to the mean square displacements. bulk ones. It is a factor applied to the mean square displacements.
""")), """),
Parameter('basis_functions', types=str, allowed_values=( Parameter('basis_functions', types=str, allowed_values=(
'plane_wave', 'spherical'), default='spherical', private=True), 'plane_wave', 'spherical'), default='spherical', private=True),
Parameter('cutoff_factor', types=(int, float), Parameter('cutoff_factor', types=(int, float),
limits=(1e-4, 999.9999), default=0.01, private=True), limits=(1e-4, 999.9999), default=0.01, private=True),
Parameter('mean_free_path', types=(int, float, str), Parameter('mean_free_path', types=(int, float, str),
default='SeahDench', allowed_values=('mono', 'SeahDench'), default='SeahDench', allowed_values=('mono', 'SeahDench'),
doc=textwrap.dedent(""" doc="""
The electron mean free path value. You can either: The electron mean free path value. You can either:
- Enter a value (in Angströms), in this case any value <=0 will disable the damping - Enter a value (in Angströms), in this case any value <=0 will disable the damping
- Enter the keyword 'mono' to use a formula valid only for monoelemental samples - Enter the keyword 'mono' to use a formula valid only for monoelemental samples
- Enter the keyword 'SeahDench' to use the Seah and Dench formula. - Enter the keyword 'SeahDench' to use the Seah and Dench formula.
.. note:: .. note::
The mean free path is only taken into account when the input T-matrix corresponds The mean free path is only taken into account when the input T-matrix corresponds
to a real potential as, when the potential is complex, this damping is taken care to a real potential as, when the potential is complex, this damping is taken care
of by the imaginery part othe potential. of by the imaginery part othe potential.
""")), """),
) )
BaseParameters.__init__(self) BaseParameters.__init__(self)
@ -1537,11 +1574,11 @@ class CalculationParameters(BaseParameters):
'Pi_1' : 4, 'Pi_1' : 4,
'Lowdin' : 5} 'Lowdin' : 5}
# Check that the method is neither 'Z_n' nor 'K^2' for other # Check that the method is neither 'Z_n' nor 'K^2' for other
# 'spetroscopy' than EIG # 'spetroscopy' than EIG
try: try:
if (self.global_parameters.spectroscopy == 'PED' if (self.global_parameters.spectroscopy == 'PED'
and self.global_parameters.algorithm == 'expansion'): and self.global_parameters.algorithm == 'expansion'):
#assert( p.value in (None, 'Sigma_n', 'G_n') ) #assert( p.value in (None, 'Sigma_n', 'G_n') )
assert( p.value in p.allowed_values) assert( p.value in p.allowed_values)
elif (self.global_parameters.spectroscopy == 'EIG' elif (self.global_parameters.spectroscopy == 'EIG'
and self.global_parameters.algorithm == 'inversion'): and self.global_parameters.algorithm == 'inversion'):
@ -1561,7 +1598,7 @@ class CalculationParameters(BaseParameters):
self.spec_parameters.calc_renr = omega.real self.spec_parameters.calc_renr = omega.real
self.spec_parameters.calc_reni = omega.imag self.spec_parameters.calc_reni = omega.imag
LOGGER.info("Renormalization omega set to \'{}\'".format(p.value)) LOGGER.info("Renormalization omega set to \'{}\'".format(p.value))
def bind_RA_cutoff_damping(self, p): def bind_RA_cutoff_damping(self, p):
self.spec_parameters.calc_ino = p.value self.spec_parameters.calc_ino = p.value
LOGGER.info('Rehr-Albers cutoff damping set to %s', p.value) LOGGER.info('Rehr-Albers cutoff damping set to %s', p.value)
@ -1728,8 +1765,8 @@ class PEDParameters(BaseParameters):
def __init__(self, phagen_parameters, spec_parameters): def __init__(self, phagen_parameters, spec_parameters):
parameters = ( parameters = (
Parameter('level', types=str, pattern=r'\d+[spdfgSPDFG](\d/2)?$', Parameter('level', types=str, pattern=r'\d+[spdfgSPDFG](\d/2)?$',
default='1s', doc=textwrap.dedent(""" default='1s', doc="""
The level is the electronic level where the electron comes from. The level is the electronic level where the electron comes from.
It is written: *nlJ* It is written: *nlJ*
where: where:
@ -1742,7 +1779,7 @@ class PEDParameters(BaseParameters):
>>> calc.spectroscopy_parameters.level = '2p3/2' >>> calc.spectroscopy_parameters.level = '2p3/2'
>>> calc.spectroscopy_parameters.level = '2p' # is equivalent to '2p1/2' >>> calc.spectroscopy_parameters.level = '2p' # is equivalent to '2p1/2'
""")), """),
Parameter('final_state', types=int, limits=(-1, 2), default=2), Parameter('final_state', types=int, limits=(-1, 2), default=2),
Parameter('spin_orbit', types=(type(None), str), Parameter('spin_orbit', types=(type(None), str),
allowed_values=(None, 'single', 'both'), default=None), allowed_values=(None, 'single', 'both'), default=None),
@ -1779,8 +1816,8 @@ class EIGParameters(BaseParameters):
def __init__(self, phagen_parameters, spec_parameters): def __init__(self, phagen_parameters, spec_parameters):
parameters = ( parameters = (
Parameter('level', types=str, pattern=r'\d+[spdfgSPDFG](\d/2)?$', Parameter('level', types=str, pattern=r'\d+[spdfgSPDFG](\d/2)?$',
default='1s', doc=textwrap.dedent(""" default='1s', doc="""
The level is the electronic level where the electron comes from. The level is the electronic level where the electron comes from.
It is written: *nlJ* It is written: *nlJ*
where: where:
@ -1793,21 +1830,21 @@ class EIGParameters(BaseParameters):
>>> calc.spectroscopy_parameters.level = '2p3/2' >>> calc.spectroscopy_parameters.level = '2p3/2'
>>> calc.spectroscopy_parameters.level = '2p' # is equivalent to '2p1/2' >>> calc.spectroscopy_parameters.level = '2p' # is equivalent to '2p1/2'
""")), """),
Parameter('final_state', types=int, limits=(-1, 2), default=2), Parameter('final_state', types=int, limits=(-1, 2), default=2),
Parameter('spin_orbit', types=(type(None), str), Parameter('spin_orbit', types=(type(None), str),
allowed_values=(None, 'single', 'both'), default=None), allowed_values=(None, 'single', 'both'), default=None),
Parameter('kernel_matrix_spectrum', types=(bool,), default=False, doc=textwrap.dedent(""" Parameter('kernel_matrix_spectrum', types=(bool,), default=False, doc="""
Whether to output the kernel matrix spectrum for each energy point. Whether to output the kernel matrix spectrum for each energy point.
""")), """),
Parameter('method', types=(str,), default='EPSI', Parameter('method', types=(str,), default='EPSI',
allowed_values=['AITK', 'RICH', 'SALZ', 'EPSI', 'EPSG', allowed_values=['AITK', 'RICH', 'SALZ', 'EPSI', 'EPSG',
'RHOA', 'THET', 'LEGE', 'CHEB', 'OVER', 'RHOA', 'THET', 'LEGE', 'CHEB', 'OVER',
'DURB', 'DLEV', 'TLEV', 'ULEV', 'VLEV', 'DURB', 'DLEV', 'TLEV', 'ULEV', 'VLEV',
'ELEV', 'EULE', 'GBWT', 'VARI', 'ITHE', 'ELEV', 'EULE', 'GBWT', 'VARI', 'ITHE',
'EALG'], 'EALG'],
doc=textwrap.dedent("""The convergence acceleration scheme to be used. doc="""The convergence acceleration scheme to be used.
""")), """),
) )
BaseParameters.__init__(self) BaseParameters.__init__(self)
self.add_parameters(*parameters) self.add_parameters(*parameters)
@ -1832,5 +1869,3 @@ class EIGParameters(BaseParameters):
def bind_kernel_matrix_spectrum(self, p): def bind_kernel_matrix_spectrum(self, p):
value = int(p.value) value = int(p.value)
self.spec_parameters.eigval_ispectrum_ne = value self.spec_parameters.eigval_ispectrum_ne = value

View File

@ -1,21 +1,41 @@
# coding: utf-8 #!/usr/bin/env python
#
# Copyright © 2016-2020 - Rennes Physics Institute
#
# This file is part of msspec.
#
# msspec is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# msspec is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this msspec. If not, see <http://www.gnu.org/licenses/>.
#
# Source file : src/msspec/tests.py
# Last modified: ven. 10 avril 2020 17:33:28
# Committed by : "Sylvain Tricot <sylvain.tricot@univ-rennes1.fr>"
from msspec.calculator import MSSPEC
from msspec.utils import *
from msspec.misc import set_log_level
from ase.build import bulk
import os
import sys
import unittest
from hashlib import md5 from hashlib import md5
from pickle import dumps from pickle import dumps
import unittest from ase.build import bulk
import sys
import os from msspec.calculator import MSSPEC
from msspec.misc import set_log_level
from msspec.utils import *
set_log_level('error') set_log_level('error')
RESULTS_FILENAME = os.path.join(os.path.dirname(__file__), 'results.txt') RESULTS_FILENAME = os.path.join(os.path.dirname(__file__), 'results.txt')
def perform_test(obj, funcname, filename=RESULTS_FILENAME): def perform_test(obj, funcname, filename=RESULTS_FILENAME):
f = getattr(obj, '_' + funcname) f = getattr(obj, '_' + funcname)
output = md5(dumps(f())).hexdigest() output = md5(dumps(f())).hexdigest()

View File

@ -1,5 +1,26 @@
# -*- encoding: utf-8 -*- #!/usr/bin/env python
# vim: set fdm=indent ts=4 sw=4 sts=4 et tw=80 ai cc=+0 mouse=a nu : # #
# Copyright © 2016-2020 - Rennes Physics Institute
#
# This file is part of msspec.
#
# msspec is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# msspec is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this msspec. If not, see <http://www.gnu.org/licenses/>.
#
# Source file : src/msspec/utils.py
# Last modified: ven. 10 avril 2020 15:49:35
# Committed by : "Sylvain Tricot <sylvain.tricot@univ-rennes1.fr>"
""" """
Module utils Module utils
@ -8,20 +29,159 @@ Module utils
""" """
import re
import numpy as np import numpy as np
from ase import Atoms, Atom from ase import Atom
from ase.visualize import view from ase import Atoms
class MsSpecAtoms(Atoms): from msspec.iodata import Data
def __init__(self, *args, **kwargs): from msspec.misc import LOGGER
Atoms.__init__(self, *args, **kwargs)
self.__absorber_index = None
def set_absorber(self, index):
self.__absorber_index = index
def get_absorber(self): class ForeignPotential(object):
return self.__absorber_index def __init__(self):
self.data = Data(title='Foreign Potential')
# Load exported potentials
# phagen_data is a dictionary with:
# self.phagen_data = {
# 'VintTotal' : <float>,
# 'VintCoulomb': <float>,
# 'RHOint' : <float>,
# 'types': [
# {
# 'Z' : <int>,
# 'RWS' : <float>,
# 'data': <np.array(..., 4, dtype=float)>
# },
# ...
# ...
# ...
# ]
# }
self.phagen_data = {'types': []}
def write(self, filename, prototypical_atoms):
LOGGER.debug(f"Writing Phagen input potential file: {filename}")
def append_atom_potential(atom):
Z = atom.number
# Find the right type (Z) in the phagen_data
itypes = []
for i, t in enumerate(self.phagen_data['types']):
if t['Z'] == Z:
itypes.append(i)
# Check now that we have only one type in the list
# otherwise we do not know yet how to deal with this.
assert len(itypes) > 0, f"Cannot find the data for atom with Z={Z}"
assert len(itypes) == 1, f"Too many datasets for atom with Z={Z}"
# So far so good, let's write the block
t = self.phagen_data['types'][itypes[0]]
s = "{:<7d}{:<10d}{:1.4f}\n".format(
t['Z'], len(t['data']), t['RWS'])
line_fmt = "{:+1.8e} " * 4 + "\n"
for row in t['data']:
s += line_fmt.format(*row)
return s
content = ""
# Start by writing the header line
content += "{:.2f} {:.4f} {:.2f}\n".format(
self.phagen_data['VintCoulomb'],
self.phagen_data['RHOint'],
self.phagen_data['VintTotal'])
# Then for each atom in the given prototypical cluster,
# write the data block
for atom in prototypical_atoms:
content += append_atom_potential(atom)
# Write the content to filename
try:
with open(filename, 'r') as fd:
old_content = fd.read()
except IOError:
old_content = ''
modified = False
if content != old_content:
with open(filename, 'w') as fd:
fd.write(content)
modified = True
return modified
class SPRKKRPotential(ForeignPotential):
def __init__(self, atoms, potfile, *exported_files):
super().__init__()
def read(content, pattern, types):
# compile the pattern for regex matching
pat = re.compile(pattern, re.MULTILINE)
# get the keys and data as list of strings
keys = re.split(r'\s+',
pat.search(content).group('KEYS').strip(' \n'))
txt = pat.search(content).group('DATA').strip('\n').split('\n')
data = []
for line in txt:
# Unpacking values
values_txt = re.split(r'\s+', line.strip())
data_dict = {}
for i, _ in enumerate(values_txt):
# Type casting
value = types[i].__call__(_)
data_dict[keys[i]] = value
# push to the list
data.append(data_dict)
return data
# load info in *.pot file
with open(potfile, 'r') as fd:
content = fd.read()
self.sites_data = read(content,
(r'^\s*SITES\s*\n((.*\n)+?\s*(?P<KEYS>IQ.*)\n'
r'(?P<DATA>(.*\n)+?))\*+'),
[int] + [float] * 3)
self.types_data = read(content,
(r'^\s*TYPES\s*\n(\s*(?P<KEYS>IT.*)\n'
r'(?P<DATA>(.*\n)+?))\*+'),
[int, str] + [int] * 4)
self.occ_data = read(content,
(r'^\s*OCCUPATION\s*\n(\s*(?P<KEYS>IQ.*)\n'
r'(?P<DATA>(.*\n)+?))\*+'),
[int] * 5 + [float])
for f in exported_files:
# get the IT from the filename
# m=re.match('SPRKKR-IT_(?P<IT>\d+)-PHAGEN.*', os.path.basename(f))
# it = int(m.group('IT'))
# load the content of the header (2 lines)
with open(f, 'r') as fd:
first_line = fd.readline().strip('\n')
second_line = fd.readline().strip('\n')
# load Coulomb and Total interstitial potential
pattern = (r'#\s*VMTZ_TOTAL\s*=\s*(?P<TOTAL>.*?)\s+'
r'VMTZ_Coulomb\s*=\s*(?P<COULOMB>.*?)\s+.*$')
m = re.match(pattern, first_line)
self.phagen_data.update(VintCoulomb=float(m.group('COULOMB')),
VintTotal=float(m.group('TOTAL')),
RHOint=0.)
# load Z, Wigner-Seitz radius from 2nd line of header
type_data = {}
_ = re.split(r'\s+', second_line.strip("# "))
type_data.update(Z=int(_[0]), RWS=float(_[3]))
# load the data
data = np.loadtxt(f, comments='#')
type_data.update(data=data)
self.phagen_data['types'].append(type_data)
class EmptySphere(Atom): class EmptySphere(Atom):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -30,137 +190,119 @@ class EmptySphere(Atom):
def get_atom_index(atoms, x, y, z): def get_atom_index(atoms, x, y, z):
""" Return the index of the atom that is the closest to the coordiantes """ Return the index of the atom that is the closest to the coordiantes
given as parameters. given as parameters.
:param ase.Atoms atoms: an ASE Atoms object :param ase.Atoms atoms: an ASE Atoms object
:param float x: the x position in angstroms :param float x: the x position in angstroms
:param float y: the y position in angstroms :param float y: the y position in angstroms
:param float z: the z position in angstroms :param float z: the z position in angstroms
:return: the index of the atom as an integer :return: the index of the atom as an integer
:rtype: int :rtype: int
""" """
# get all distances # get all distances
d = np.linalg.norm(atoms.get_positions() - np.array([x, y, z]), axis = 1) d = np.linalg.norm(atoms.get_positions() - np.array([x, y, z]), axis=1)
# get the index of the min distance # get the index of the min distance
i = np.argmin(d) i = np.argmin(d)
# return the index and the corresponding distance # return the index and the corresponding distance
return i return i
def center_cluster(atoms, invert=False): def center_cluster(atoms, invert=False):
""" Centers an Atoms object by translating it so the origin is roughly """ Centers an Atoms object by translating it so the origin is roughly
at the center of the cluster. at the center of the cluster.
The function supposes that the cluster is wrapped inside the unit cell, The function supposes that the cluster is wrapped inside the unit cell,
with the origin being at the corner of the cell. with the origin being at the corner of the cell.
It is used in combination with the cut functions, which work only if the It is used in combination with the cut functions, which work only if the
origin is at the center of the cluster origin is at the center of the cluster
:param ase.Atoms atoms: an ASE Atoms object
:param ase.Atoms atoms: an ASE Atoms object :param bool invert: if True, performs the opposite translation
:param bool invert: if True, performs the opposite translation (uncentering the cluster) (uncentering the cluster)
"""
""" for i, cell_vector in enumerate(atoms.get_cell()):
for i, cell_vector in enumerate(atoms.get_cell()): if invert:
if invert: atoms.translate(0.5*cell_vector)
atoms.translate(0.5*cell_vector) else:
else: atoms.translate(-0.5*cell_vector)
atoms.translate(-0.5*cell_vector)
def cut_sphere(atoms, radius, center=(0, 0, 0)): def cut_sphere(atoms, radius, center=(0, 0, 0)):
""" Removes all the atoms of an Atoms object outside a sphere with a
given radius
:param ase.Atoms atoms: an ASE Atoms object
:param float radius: the radius of the sphere
:return: The modified atom cluster
:rtype: ase.Atoms
"""
assert radius >= 0, "Please give a positive radius value" assert radius >= 0, "Please give a positive radius value"
radii = np.linalg.norm(atoms.positions - center, axis=1) radii = np.linalg.norm(atoms.positions - center, axis=1)
indices = np.where(radii <= radius)[0] indices = np.where(radii <= radius)[0]
return atoms[indices] return atoms[indices]
def _cut_sphere(atoms, radius=None):
""" Removes all the atoms of an Atoms object outside a sphere with a
given radius
:param ase.Atoms atoms: an ASE Atoms object
:param float radius: the radius of the sphere
:return: The modified atom cluster
:rtype: ase.Atoms
"""
if radius is None:
raise ValueError("radius not set")
new_atoms = atoms.copy()
del_list = []
for index, position in enumerate(new_atoms.positions):
if np.linalg.norm(position) > radius:
del_list.append(index)
del_list.reverse()
for index in del_list:
del new_atoms[index]
return new_atoms
def cut_cylinder(atoms, axis="z", radius=None): def cut_cylinder(atoms, axis="z", radius=None):
""" Removes all the atoms of an Atoms object outside a cylinder with a """ Removes all the atoms of an Atoms object outside a cylinder with a
given axis and radius given axis and radius
:param ase.Atoms atoms: an ASE Atoms object :param ase.Atoms atoms: an ASE Atoms object
:param str axis: string "x", "y", or "z". The axis of the cylinder, "z" by default :param str axis: string "x", "y", or "z". The axis of the cylinder,
:param float radius: the radius of the cylinder "z" by default
:param float radius: the radius of the cylinder
:return: The modified atom cluster :return: The modified atom cluster
:rtype: ase.Atoms :rtype: ase.Atoms
""" """
if radius is None: if radius is None:
raise ValueError("radius not set") raise ValueError("radius not set")
new_atoms = atoms.copy() new_atoms = atoms.copy()
dims = {"x": 0, "y": 1, "z": 2} dims = {"x": 0, "y": 1, "z": 2}
if axis in dims: if axis in dims:
axis = dims[axis] axis = dims[axis]
else: else:
raise ValueError("axis not valid, must be 'x','y', or 'z'") raise ValueError("axis not valid, must be 'x','y', or 'z'")
del_list = [] del_list = []
for index, position in enumerate(new_atoms.positions): for index, position in enumerate(new_atoms.positions):
# calculating the distance of the atom to the given axis # calculating the distance of the atom to the given axis
r = 0 r = 0
for dim in range(3): for dim in range(3):
if dim != axis: if dim != axis:
r = r + position[dim]**2 r = r + position[dim]**2
r = np.sqrt(r) r = np.sqrt(r)
if r > radius: if r > radius:
del_list.append(index) del_list.append(index)
del_list.reverse() del_list.reverse()
for index in del_list: for index in del_list:
del new_atoms[index] del new_atoms[index]
return new_atoms return new_atoms
def cut_cone(atoms, radius, z = 0):
def cut_cone(atoms, radius, z=0):
"""Shapes the cluster as a cone. """Shapes the cluster as a cone.
Keeps all the atoms of the input Atoms object inside a cone of based radius *radius* and of height *z*. Keeps all the atoms of the input Atoms object inside a cone of based
radius *radius* and of height *z*.
:param atoms: The cluster to modify. :param atoms: The cluster to modify.
:type atoms: :py:class:`ase.Atoms` :type atoms: :py:class:`ase.Atoms`
:param radius: The base cone radius in :math:`\mathring{A}`. :param radius: The base cone radius in :math:`\mathring{A}`. # noqa: W605
:type radius: float :type radius: float
:param z: The height of the cone in :math:`\mathring{A}`. :param z: The height of the cone in :math:`\mathring{A}`. # noqa: W605
:type z: float :type z: float
:return: A new cluster. :return: A new cluster.
:rtype: :py:class:`ase.Atoms` :rtype: :py:class:`ase.Atoms`
""" """
new_atoms = atoms.copy() new_atoms = atoms.copy()
origin = np.array((0, 0, 0))
max_theta = np.arctan(radius/(-z)) max_theta = np.arctan(radius/(-z))
u = np.array((0, 0, -z)) u = np.array((0, 0, -z))
normu = np.linalg.norm(u) normu = np.linalg.norm(u)
new_atoms.translate(u) new_atoms.translate(u)
@ -168,20 +310,20 @@ def cut_cone(atoms, radius, z = 0):
for i in range(len(new_atoms)): for i in range(len(new_atoms)):
v = new_atoms[i].position v = new_atoms[i].position
normv = np.linalg.norm(v) normv = np.linalg.norm(v)
_ = np.dot(u, v)/normu/normv _ = np.dot(u, v)/normu/normv
if _ == 0: if _ == 0:
print(v) print(v)
theta = np.arccos(_) theta = np.arccos(_)
if theta <= max_theta: if theta <= max_theta:
indices.append(i) indices.append(i)
new_atoms = new_atoms[indices] new_atoms = new_atoms[indices]
new_atoms.translate(-u) # pylint: disable=invalid-unary-operand-type new_atoms.translate(-u)
return new_atoms return new_atoms
def cut_plane(atoms, x=None, y=None, z=None): def cut_plane(atoms, x=None, y=None, z=None):
""" Removes the atoms whose coordinates are higher (or lower, for a """ Removes the atoms whose coordinates are higher (or lower, for a
negative cutoff value) than the coordinates given for every dimension. negative cutoff value) than the coordinates given for every dimension.
@ -191,9 +333,9 @@ def cut_plane(atoms, x=None, y=None, z=None):
.. code-block:: python .. code-block:: python
cut_plane(atoms, x=[-5,5], y=3.6, z=0) cut_plane(atoms, x=[-5,5], y=3.6, z=0)
#every atom whose x-coordinate is higher than 5 or lower than -5, and/or # every atom whose x-coordinate is higher than 5 or lower than -5,
#y-coordinate is higher than 3.6, and/or z-coordinate is higher than 0 # and/or y-coordinate is higher than 3.6, and/or z-coordinate is higher
#is deleted. # than 0 is deleted.
:param ase.Atoms atoms: an ASE Atoms object :param ase.Atoms atoms: an ASE Atoms object
:param int x: x cutoff value :param int x: x cutoff value
@ -206,9 +348,11 @@ def cut_plane(atoms, x=None, y=None, z=None):
dim_names = ('x', 'y', 'z') dim_names = ('x', 'y', 'z')
dim_values = [x, y, z] dim_values = [x, y, z]
for i, (name, value) in enumerate(zip(dim_names, dim_values)): for i, (name, value) in enumerate(zip(dim_names, dim_values)):
assert isinstance(value, (int, float, list, tuple, type(None))), "Wrong type" assert isinstance(value, (int, float, list, tuple, type(None))), \
"Wrong type"
if isinstance(value, (tuple, list)): if isinstance(value, (tuple, list)):
assert len(value) == 2 and np.all([isinstance(el, (int, float, type(None))) for el in value]), \ assert len(value) == 2 and np.all(
[isinstance(el, (int, float, type(None))) for el in value]), \
"Wrong length" "Wrong length"
else: else:
try: try:
@ -216,7 +360,7 @@ def cut_plane(atoms, x=None, y=None, z=None):
dim_values[i] = [-np.inf, value] dim_values[i] = [-np.inf, value]
else: else:
dim_values[i] = [value, np.inf] dim_values[i] = [value, np.inf]
except: except Exception:
dim_values[i] = [value, value] dim_values[i] = [value, value]
if dim_values[i][0] is None: if dim_values[i][0] is None:
@ -227,15 +371,18 @@ def cut_plane(atoms, x=None, y=None, z=None):
dim_values = np.array(dim_values) dim_values = np.array(dim_values)
def constraint(coordinates): def constraint(coordinates):
return np.all(np.logical_and(coordinates >= dim_values[:,0], coordinates <= dim_values[:,1])) return np.all(np.logical_and(coordinates >= dim_values[:, 0],
coordinates <= dim_values[:, 1]))
indices = np.where(list(map(constraint, atoms.positions)))[0] indices = np.where(list(map(constraint, atoms.positions)))[0]
return atoms[indices] return atoms[indices]
def hemispherical_cluster(cluster, emitter_tag=0, emitter_plane=0, diameter=0,
def hemispherical_cluster(cluster, emitter_tag=0, emitter_plane=0, diameter=0,
planes=0, shape='spherical'): planes=0, shape='spherical'):
"""Creates and returns a cluster based on an Atoms object and some parameters. """Creates and returns a cluster based on an Atoms object and some
parameters.
:param cluster: the Atoms object used to create the cluster :param cluster: the Atoms object used to create the cluster
:type cluster: Atoms object :type cluster: Atoms object
@ -245,7 +392,8 @@ def hemispherical_cluster(cluster, emitter_tag=0, emitter_plane=0, diameter=0,
:type diameter: float :type diameter: float
:param planes: the number of planes of your cluster :param planes: the number of planes of your cluster
:type planes: integer :type planes: integer
:param emitter_plane: the plane where your emitter will be starting by 0 for the first plane :param emitter_plane: the plane where your emitter will be starting by 0
for the first plane
:type emitter_plane: integer :type emitter_plane: integer
See :ref:`hemispherical_cluster_faq` for more informations. See :ref:`hemispherical_cluster_faq` for more informations.
@ -255,7 +403,8 @@ def hemispherical_cluster(cluster, emitter_tag=0, emitter_plane=0, diameter=0,
nmin = np.inf nmin = np.inf
for atom in cluster: for atom in cluster:
if ze - eps < atom.z < ze + eps and (atom.symbol == symbol or symbol == None): if ze - eps < atom.z < ze + eps and (atom.symbol == symbol or
symbol is None):
n = np.sqrt(atom.x**2 + atom.y**2) n = np.sqrt(atom.x**2 + atom.y**2)
if (n < nmin): if (n < nmin):
nmin = n nmin = n
@ -270,68 +419,115 @@ def hemispherical_cluster(cluster, emitter_tag=0, emitter_plane=0, diameter=0,
eps = 0.01 # a useful small value eps = 0.01 # a useful small value
c = cell[:, 2].max() # a lattice parameter c = cell[:, 2].max() # a lattice parameter
a = cell[:, 0].max() # a lattice parameter a = cell[:, 0].max() # a lattice parameter
p = np.alen(np.unique(np.round(cluster.get_positions()[:, 2], 4))) # the number of planes in the cluster
symbol = cluster[np.where(cluster.get_tags() == emitter_tag)[0][0]].symbol # the symbol of your emitter
assert (diameter != 0 or planes != 0), "At least one of diameter or planes parameter must be use." # the number of planes in the cluster
p = np.alen(np.unique(np.round(cluster.get_positions()[:, 2], 4)))
# the symbol of your emitter
symbol = cluster[np.where(cluster.get_tags() == emitter_tag)[0][0]].symbol
assert (diameter != 0 or planes != 0), \
"At least one of diameter or planes parameter must be use."
if diameter == 0: if diameter == 0:
l = 1+2*(planes*c/p+1) # calculate the minimal diameter according to the number of planes # calculate the minimal diameter according to the number of planes
min_diameter = 1+2*(planes*c/p+1)
else: else:
l = diameter min_diameter = diameter
rep = int(2*l/min(a,c)) # number of repetition in each direction # number of repetition in each direction
cluster = cluster.repeat((rep, rep, rep)) # repeat the cluster rep = int(3*min_diameter/min(a, c))
center_cluster(cluster) # center the cluster # repeat the cluster
cluster.set_cell(cell) # reset the cell cluster = cluster.repeat((rep, rep, rep))
cluster = cut_plane(cluster, z=eps) # cut the cluster so that we have a centered surface
i = np.where(cluster.get_tags() == emitter_tag) # positions where atoms have the tag of the emitter_tag # center the cluster
all_ze = np.sort(np.unique(np.round(cluster.get_positions()[:, 2][i], 4))) # an array of all unique z corresponding to where we have the right atom's tag center_cluster(cluster)
all_z = np.sort(np.unique(np.round(cluster.get_positions()[:, 2], 4))) # an array of all unique z
n = np.where(all_z == all_z.max())[0][0] - np.where(all_z == all_ze.max())[0][0] # calculate the number of planes above the emitter's plane # reset the cell
ze = all_ze.max() # the height of the emitter's plane cluster.set_cell(cell)
# cut the cluster so that we have a centered surface
cluster = cut_plane(cluster, z=eps)
# if the number of planes above the emitter's plane is smaller than it must be, recalculate n and ze # positions where atoms have the tag of the emitter_tag
i = np.where(cluster.get_tags() == emitter_tag)
# an array of all unique z corresponding to where we have the right
# atom's tag
all_ze = np.sort(np.unique(np.round(cluster.get_positions()[:, 2][i], 4)))
# an array of all unique z
all_z = np.sort(np.unique(np.round(cluster.get_positions()[:, 2], 4)))
# calculate the number of planes above the emitter's plane
n = np.where(all_z == all_z.max())[0][0] - np.where(
all_z == all_ze.max())[0][0]
# the height of the emitter's plane
ze = all_ze.max()
# if the number of planes above the emitter's plane is smaller than it must
# be, recalculate n and ze
while n < emitter_plane: while n < emitter_plane:
all_ze = all_ze[:-1] all_ze = all_ze[:-1]
n = np.where(all_z == all_z.max())[0][0] - np.where(all_z == all_ze.max())[0][0] n = np.where(all_z == all_z.max())[0][0] - np.where(
all_z == all_ze.max())[0][0]
ze = all_ze.max() ze = all_ze.max()
# values of x and y of the emitter
tx, ty = get_xypos(cluster, ze, symbol)
tx, ty = get_xypos(cluster, ze, symbol) # values of x and y of the emitter # center the cluster on the emitter
Atoms.translate(cluster, [-tx, -ty, 0]) # center the cluster on the emitter Atoms.translate(cluster, [-tx, -ty, 0])
z_cut = all_z[np.where(all_z == all_ze.max())[0][0] + emitter_plane] # calculate where to cut to get the right number of planes above the emitter # calculate where to cut to get the right number of planes above the
Atoms.translate(cluster, [0, 0, -z_cut]) # translate the surface at z=0 # emitter
cluster = cut_plane(cluster, z=eps) # cut the planes above those we want to keep z_cut = all_z[np.where(all_z == all_ze.max())[0][0] + emitter_plane]
# translate the surface at z=0
Atoms.translate(cluster, [0, 0, -z_cut])
# cut the planes above those we want to keep
cluster = cut_plane(cluster, z=eps)
radius = diameter/2 radius = diameter/2
if planes!=0: if planes != 0:
all_z = np.sort(np.unique(np.round(cluster.get_positions()[:, 2], 4))) # an array of all unique remaining z # an array of all unique remaining z
all_z = np.sort(np.unique(np.round(cluster.get_positions()[:, 2], 4)))
zplan = all_z[-planes] zplan = all_z[-planes]
xplan, yplan = get_xypos(cluster, zplan) xplan, yplan = get_xypos(cluster, zplan)
radius = np.sqrt(xplan**2 + yplan**2 + zplan**2) radius = np.sqrt(xplan**2 + yplan**2 + zplan**2)
if diameter!=0:
assert (radius <= diameter/2), "The number of planes is too high compared to the diameter." if diameter != 0:
assert (radius <= diameter/2), ("The number of planes is too high "
"compared to the diameter.")
radius = max(radius, diameter/2) radius = max(radius, diameter/2)
if shape in ('spherical'): if shape in ('spherical'):
cluster = cut_sphere(cluster, radius=radius + eps) # cut a sphere in our cluster with the diameter which is indicate in the parameters # cut a sphere in our cluster with the diameter which is indicate in
# the parameters
cluster = cut_sphere(cluster, radius=radius + eps)
elif shape in ('cylindrical'): elif shape in ('cylindrical'):
cluster = cut_cylinder(cluster, radius=radius + eps) # cut a sphere in our cluster with the diameter which is indicate in the parameters # cut a sphere in our cluster with the diameter which is indicate in
# the parameters
cluster = cut_cylinder(cluster, radius=radius + eps)
else: else:
raise NameError('Unkknown shape specifier: \"{}\"'.format(shape)) raise NameError('Unkknown shape specifier: \"{}\"'.format(shape))
if planes!=0: if planes != 0:
zcut = np.sort(np.unique(np.round(cluster.get_positions()[:, 2], 4)))[::-1][planes-1] - eps # calculate where to cut to get the right number of planes # calculate where to cut to get the right number of planes
cluster = cut_plane(cluster, z=zcut) # cut the right number of planes positions = np.unique(np.round(cluster.get_positions()[:, 2], 4))
zcut = np.sort(positions)[::-1][planes-1] - eps
all_z = np.sort(np.unique(np.round(cluster.get_positions()[:, 2], 4))) # an array of all unique remaining z # cut the right number of planes
assert emitter_plane < np.alen(all_z), "There are not enough existing plans." cluster = cut_plane(cluster, z=zcut)
# an array of all unique remaining z
all_z = np.sort(np.unique(np.round(cluster.get_positions()[:, 2], 4)))
assert emitter_plane < np.alen(all_z), ("There are not enough existing "
"plans.")
ze = all_z[- emitter_plane - 1] # the z-coordinate of the emitter ze = all_z[- emitter_plane - 1] # the z-coordinate of the emitter
Atoms.translate(cluster, [0, 0, -ze]) # put the emitter in (0,0,0) Atoms.translate(cluster, [0, 0, -ze]) # put the emitter in (0,0,0)

View File

@ -1,10 +1,33 @@
# coding: utf-8 #!/usr/bin/env python
# vim: set et sw=4 ts=4 sts=4 nu ai cc=+0 fdm=indent mouse=a: #
# Copyright © 2016-2020 - Rennes Physics Institute
#
# This file is part of msspec.
#
# msspec is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# msspec is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this msspec. If not, see <http://www.gnu.org/licenses/>.
#
# Source file : src/msspec/version.py
# Last modified: ven. 10 avril 2020 17:34:38
# Committed by : "Sylvain Tricot <sylvain.tricot@univ-rennes1.fr>"
from setuptools_scm import get_version
from pkg_resources import parse_version, DistributionNotFound, get_distribution
import os import os
from pkg_resources import DistributionNotFound
from pkg_resources import get_distribution
from pkg_resources import parse_version
from setuptools_scm import get_version
# find the version number # find the version number
# 1- Try to read it from the git info # 1- Try to read it from the git info
# 2- If it fails, try to read it from the distribution file # 2- If it fails, try to read it from the distribution file

View File

@ -1,22 +1,31 @@
# coding: utf-8 #!/usr/bin/env python
# vim: set fdm=indent ts=4 sw=4 sts=4 et tw=80 ai cc=+0 mouse=a nu : #
# Copyright © 2016-2020 - Rennes Physics Institute
#
# This file is part of msspec.
#
# msspec is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
from setuptools import setup, Extension, find_packages # msspec is distributed in the hope that it will be useful,
from distutils.file_util import copy_file # but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
from distutils.command.build import build as _build # You should have received a copy of the GNU General Public License
from setuptools.command.build_ext import build_ext as _build_ext # along with this msspec. If not, see <http://www.gnu.org/licenses/>.
from setuptools.command.install import install as _install #
from distutils.command.clean import clean as _clean # Source file : src/setup.py
# Last modified: mar. 07 avril 2020 17:01:42
# Committed by : "Sylvain Tricot <sylvain.tricot@univ-rennes1.fr>"
import subprocess from setuptools import setup, find_packages
import traceback from version import __version__
import os
import sys import sys
import glob
sys.path.insert(0, "msspec") sys.path.insert(0, "msspec")
from version import __version__
with open('setup_requirements.txt', 'r') as fd: with open('setup_requirements.txt', 'r') as fd:
SETUP_REQUIREMENTS = fd.read().strip().split('\n') SETUP_REQUIREMENTS = fd.read().strip().split('\n')
@ -26,46 +35,46 @@ with open('requirements.txt', 'r') as fd:
if __name__ == "__main__": if __name__ == "__main__":
setup(name='msspec', setup(name='msspec',
version=__version__, version=__version__,
include_package_data=True, include_package_data=True,
packages=find_packages(include='msspec.*'), packages=find_packages(include='msspec.*'),
setup_requires=SETUP_REQUIREMENTS, setup_requires=SETUP_REQUIREMENTS,
install_requires=REQUIREMENTS, install_requires=REQUIREMENTS,
author='Didier Sébilleau, Sylvain Tricot', author='Didier Sébilleau, Sylvain Tricot',
author_email='sylvain.tricot@univ-rennes1.fr', author_email='sylvain.tricot@univ-rennes1.fr',
maintainer='Sylvain Tricot', maintainer='Sylvain Tricot',
maintainer_email='sylvain.tricot@univ-rennes1.fr', maintainer_email='sylvain.tricot@univ-rennes1.fr',
url='https://msspec.cnrs.fr', url='https://msspec.cnrs.fr',
description=('A multiple scattering package for sepectroscopies using ' description=('A multiple scattering package for sepectroscopies '
'electrons to probe materials'), 'using electrons to probe materials'),
long_description="""MsSpec is a Fortran package to compute the long_description="""MsSpec is a Fortran package to compute the
cross-section of several spectroscopies involving one (or more) cross-section of several spectroscopies involving one (or more)
electron(s) as the probe. This package provides a python interface to electron(s) as the probe. This package provides a python interface to
control all the steps of the calculation. control all the steps of the calculation.
Available spectroscopies: Available spectroscopies:
* Photoelectron diffraction * Photoelectron diffraction
* Auger electron diffraction * Auger electron diffraction
* Low energy electron diffraction * Low energy electron diffraction
* X-Ray absorption spectroscopy * X-Ray absorption spectroscopy
* Auger Photoelectron coincidence spectroscopy * Auger Photoelectron coincidence spectroscopy
* Computation of the spectral radius""", * Computation of the spectral radius""",
download_url='https://msspec.cnrs.fr/downloads.html', download_url='https://msspec.cnrs.fr/downloads.html',
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers # See https://pypi.python.org/pypi?%3Aaction=list_classifiers
classifiers=[ classifiers=[
'Development Status :: 3 - Alpha', 'Development Status :: 3 - Alpha',
'Environment :: Console', 'Environment :: Console',
'Intended Audience :: Science/Research', 'Intended Audience :: Science/Research',
'License :: OSI Approved :: GNU General Public License (GPL)', 'License :: OSI Approved :: GNU General Public License (GPL)',
'Natural Language :: English', 'Natural Language :: English',
'Operating System :: Microsoft :: Windows :: Windows 10', 'Operating System :: Microsoft :: Windows :: Windows 10',
'Operating System :: POSIX :: Linux', 'Operating System :: POSIX :: Linux',
'Operating System :: MacOS :: MacOS X', 'Operating System :: MacOS :: MacOS X',
'Programming Language :: Fortran', 'Programming Language :: Fortran',
'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3 :: Only',
'Topic :: Scientific/Engineering :: Physics', 'Topic :: Scientific/Engineering :: Physics',
], ],
keywords='spectroscopy atom electron photon multiple scattering', keywords='spectroscopy atom electron photon multiple scattering',
license='GPL', license='GPL',
) )