update public distribution

based on internal repository c9a2ac8 2019-01-03 16:04:57 +0100
tagged rev-master-2.0.0
This commit is contained in:
2019-01-31 15:45:02 +01:00
parent bbd16d0f94
commit acea809e4e
92 changed files with 165828 additions and 143181 deletions

View File

View File

@ -0,0 +1,102 @@
"""
@package pmsco.calculators.calculator
abstract scattering program interface.
this module declares the basic interface to scattering programs.
for each scattering program (EDAC, MSC, SSC, ...) a specific interface must be derived from CalcInterface.
the derived interface must implement the run() method.
the run() method and the scattering code may use only the parameters declared in the interface.
TestCalcInterface is provided for testing the PMSCO code quickly without calling an external program.
@author Matthias Muntwiler
@copyright (c) 2015-18 by Paul Scherrer Institut @n
Licensed under the Apache License, Version 2.0 (the "License"); @n
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import time
import numpy as np
import pmsco.data as md
__author__ = 'matthias muntwiler'
class Calculator(object):
"""
Interface class to the calculation program.
"""
def run(self, params, cluster, scan, output_file):
"""
run a calculation with the given parameters and cluster.
the result is returned as the method result and in a file named <code>output_file + '.etpi'</code>,
or <code>output_file + '.etpai'</code> depending on scan mode.
all other intermediate files are deleted unless keep_temp_files is True.
@param params: a pmsco.project.Params object with all necessary values except cluster and output files set.
@param cluster: a pmsco.cluster.Cluster(format=FMT_EDAC) object with all atom positions set.
@param scan: a pmsco.project.Scan() object describing the experimental scanning scheme.
@param output_file: base name for all intermediate and output files
@return: (str, dict) result_file, and dictionary of created files {filename: category}
@return: (str, dict) result_file, and dictionary of created files.
@arg the first element is the name of the main ETPI or ETPAI result file to be further processed.
@arg the second element is a dictionary that lists the names of all created data files with their category.
the dictionary key is the file name,
the value is the file category (cluster, phase, etc.).
"""
return None, None
class TestCalculator(Calculator):
"""
interface class producing random data for testing the MSCO code without calling an external program.
"""
def run(self, params, cluster, scan, output_file):
"""
produce a random test data set.
the scan scheme is generated from the given parameters.
the intensities are random values.
@return: result_file, files_cats
the result file contains an ETPI or ETPAI array with random intensity data.
"""
# set up scan
params.fixed_cluster = 'a' in scan.mode
# generate file names
base_filename = output_file
clu_filename = base_filename + ".clu"
if params.fixed_cluster:
etpi_filename = base_filename + ".etpai"
else:
etpi_filename = base_filename + ".etpi"
cluster.save_to_file(clu_filename)
# generate data and save in ETPI or ETPAI format
result_etpi = scan.raw_data.copy()
result_etpi['i'] = np.random.random_sample(result_etpi.shape)
# slow down the test for debugging
time.sleep(5)
md.save_data(etpi_filename, result_etpi)
files = {clu_filename: 'cluster', etpi_filename: 'region'}
return etpi_filename, files

249
pmsco/calculators/edac.py Normal file
View File

@ -0,0 +1,249 @@
"""
@package pmsco.calculators.edac
Garcia de Abajo EDAC program interface.
@author Matthias Muntwiler
@copyright (c) 2015-18 by Paul Scherrer Institut @n
Licensed under the Apache License, Version 2.0 (the "License"); @n
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import logging
import numpy as np
import os
import pmsco.calculators.calculator as calculator
from pmsco.compat import open
import pmsco.data as md
import pmsco.cluster as mc
import pmsco.edac.edac as edac
from pmsco.helpers import BraceMessage as BMsg
logger = logging.getLogger(__name__)
class EdacCalculator(calculator.Calculator):
def write_input_file(self, params, scan, filepath):
"""
write parameters to an EDAC input file
EDAC will calculate results on a rectangular grid.
the grid is constructed from the limits of the scan coordinates
and the number of positions in each respective dimension.
to avoid any confusion, the input scan should be rectangular with equidistant steps.
the following scans are supported:
(energy), (energy, theta), (energy, phi), (energy, alpha), (theta, phi) holo.
except for the holo scan, each scan dimension must be linear.
the holo scan is translated to a rectangular (theta, phi) scan
where theta is copied
and phi is replaced by a linear scan from the minimum to the maximum phi at 1 degree steps.
the scan type is detected from the scan file.
if alpha is defined, theta is implicitly set to normal emission! (to be generalized)
@param params: a pmsco.project.Params object with all necessary values except cluster and output files set.
@param scan: a pmsco.project.Scan() object describing the experimental scanning scheme.
@param filepath: (str) name and path of the file to be created.
"""
with open(filepath, "w") as f:
f.write("verbose off\n")
f.write("cluster input {0}\n".format(params.cluster_file))
f.write("emitters {0:d} l(A)\n".format(len(params.emitters)))
for em in params.emitters:
f.write("{0:f} {1:f} {2:f} {3:d}\n".format(*em))
en = scan.energies + params.work_function
en_min = en.min()
en_max = en.max()
if en.shape[0] <= 1:
en_num = 1
else:
de = np.diff(en)
de = de[de >= 0.01]
de = de.min()
en_num = int(round((en_max - en_min) / de)) + 1
if en_num != en.shape[0]:
logger.warning("energy scan length mismatch: EDAC {0}, scan {1}".format(en_num, en.shape[0]))
assert en_num < en.shape[0] * 10, \
"linearization of energy scan causes excessive oversampling {0}/{1}".format(en_num, en.shape[0])
f.write("emission energy E(eV) {en0:f} {en1:f} {nen:d}\n".format(en0=en_min, en1=en_max, nen=en_num))
if params.fixed_cluster:
th = scan.alphas
ph = np.remainder(scan.phis + 90.0, 360.0)
f.write("fixed cluster\n")
if np.abs(scan.thetas).max() > 0.0:
logger.warning("theta angle implicitly set to zero due to alpha scan.")
else:
th = np.unique(scan.thetas)
ph = scan.phis
f.write("movable cluster\n")
th_min = th.min()
th_max = th.max()
if th.shape[0] <= 1:
th_num = 1
else:
dt = np.diff(th)
dt = dt[dt >= 0.1]
dt = dt.min()
if ph.shape[0] > 1:
# hemispherical scan
if th_min < 0:
th_min = max(th_min - dt, -90.0)
else:
th_min = max(th_min - dt, 0.0)
if th_max > 0:
th_max = min(th_max + dt, 90.0)
else:
th_max = min(th_max + dt, 0.0)
th_num = int(round((th_max - th_min) / dt)) + 1
assert th_num < th.shape[0] * 10, \
"linearization of theta scan causes excessive oversampling {0}/{1}".format(th_num, th.shape[0])
f.write("beta {0}\n".format(params.polar_incidence_angle))
f.write("incidence {0} {1}\n".format(params.polar_incidence_angle, params.azimuthal_incidence_angle))
f.write("emission angle theta {th0:f} {th1:f} {nth:d}\n".format(th0=th_min, th1=th_max, nth=th_num))
ph_min = ph.min()
ph_max = ph.max()
if th.shape[0] <= 1:
# azimuthal scan
ph_num = ph.shape[0]
elif ph.shape[0] <= 1:
# polar scan
ph_num = 1
else:
# hemispherical scan
dp = np.diff(ph)
dp = dp[dp >= 0.1]
dp = dp.min()
ph_min = max(ph_min - dp, 0.0)
ph_max = min(ph_max + dp, 360.0)
dt = (th_max - th_min) / (th_num - 1)
dp = min(dp, dt)
ph_num = int(round((ph_max - ph_min) / dp)) + 1
assert ph_num < ph.shape[0] * 10, \
"linearization of phi scan causes excessive oversampling {0}/{1}".format(ph_num, ph.shape[0])
f.write("emission angle phi {ph0:f} {ph1:f} {nph:d}\n".format(ph0=ph_min, ph1=ph_max, nph=ph_num))
f.write("initial state {0}\n".format(params.initial_state))
polarizations = {'H': 'LPx', 'V': 'LPy', 'L': 'LCP', 'R': 'RCP'}
f.write("polarization {0}\n".format(polarizations[params.polarization]))
scatterers = ["scatterer {at} {fi}\n".format(at=at, fi=fi)
for (at, fi) in params.phase_files.items()
if os.path.isfile(fi)]
if scatterers:
for scat in scatterers:
f.write(scat)
else:
f.write("muffin-tin\n")
f.write("V0 E(eV) {0:f}\n".format(params.inner_potential))
f.write("cluster surface l(A) {0:f}\n".format(params.z_surface))
f.write("imfp SD-UC\n")
f.write("temperature {0:f} {1:f}\n".format(params.experiment_temperature, params.debye_temperature))
f.write("iteration recursion\n")
f.write("dmax l(A) {0:f}\n".format(params.dmax))
f.write("lmax {0:d}\n".format(params.lmax))
f.write("orders {0:d} ".format(len(params.orders)))
f.write(" ".join(format(order, "d") for order in params.orders) + "\n")
f.write("emission angle window {0:F}\n".format(params.angular_resolution / 2.0))
# f.write("cluster output l(A) out.clu")
# problems:
# - muffin-tin relabels atoms
# - there can be multiple atom types for the same chemical element
# - we have to compare coordinates to find the mapping between input and output cluster
# f.write("scan scatterer i phase-shifts i.pha")
# f.write("scan scatterer i potential i.pot")
f.write("scan pd {0}\n".format(params.output_file))
f.write("end\n")
def run(self, params, cluster, scan, output_file):
"""
run EDAC with the given parameters and cluster.
@param params: a pmsco.project.Params object with all necessary values except cluster and output files set.
@param cluster: a pmsco.cluster.Cluster(format=FMT_EDAC) object with all atom positions set.
@param scan: a pmsco.project.Scan() object describing the experimental scanning scheme.
@param output_file: base name for all intermediate and output files
@return: (str, dict) result_file, and dictionary of created files {filename: category}
"""
# set up scan
params.fixed_cluster = 'a' in scan.mode
# generate file names
base_filename = output_file
clu_filename = base_filename + ".clu"
out_filename = base_filename + ".out"
par_filename = base_filename + ".par"
dat_filename = out_filename
if params.fixed_cluster:
etpi_filename = base_filename + ".etpai"
else:
etpi_filename = base_filename + ".etpi"
# fix EDAC particularities
params.cluster_file = clu_filename
params.output_file = out_filename
params.data_file = dat_filename
params.emitters = cluster.get_emitters()
# save parameter files
logger.debug("writing cluster file %s", clu_filename)
cluster.save_to_file(clu_filename, fmt=mc.FMT_EDAC)
logger.debug("writing input file %s", par_filename)
self.write_input_file(params, scan, par_filename)
# run EDAC
logger.info("calling EDAC with input file %s", par_filename)
edac.run_script(par_filename)
# load results and save in ETPI or ETPAI format
logger.debug("importing data from output file %s", dat_filename)
result_etpi = md.load_edac_pd(dat_filename, energy=scan.energies[0] + params.work_function,
theta=scan.thetas[0], phi=scan.phis[0],
fixed_cluster=params.fixed_cluster)
result_etpi['e'] -= params.work_function
if 't' in scan.mode and 'p' in scan.mode:
hemi_tpi = scan.raw_data.copy()
hemi_tpi['i'] = 0.0
try:
hemi_tpi['s'] = 0.0
except ValueError:
pass
result_etpi = md.interpolate_hemi_scan(result_etpi, hemi_tpi)
if params.fixed_cluster:
expected_shape = max(scan.energies.shape[0], 1) * max(scan.alphas.shape[0], 1)
else:
expected_shape = max(scan.energies.shape[0], 1) * max(scan.phis.shape[0], scan.thetas.shape[0], 1)
if result_etpi.shape[0] != expected_shape:
logger.warning(BMsg("possible scan length mismatch: EDAC result: {result}, expected: {expected}",
result=result_etpi.shape[0], expected=expected_shape))
logger.debug("save result to file %s", etpi_filename)
md.save_data(etpi_filename, result_etpi)
files = {clu_filename: 'input', par_filename: 'input', dat_filename: 'output',
etpi_filename: 'region'}
return etpi_filename, files

115
pmsco/calculators/msc.py Normal file
View File

@ -0,0 +1,115 @@
"""
@package pmsco.msc_calculator
Kaduwela MSC program interface.
This module is currently not maintained.
@author Matthias Muntwiler
@copyright (c) 2015 by Paul Scherrer Institut @n
Licensed under the Apache License, Version 2.0 (the "License"); @n
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
import pmsco.calculators.calculator as calculator
import pmsco.data as md
import pmsco.msc.msc as msc
import logging
logger = logging.getLogger(__name__)
class MscCalculator(calculator.Calculator):
def write_input_file(self, params, filepath):
with open(filepath, "w") as f:
f.write(" %s\n" % (params.title) )
f.write(" %s\n" % (params.comment) )
l_init = "spdf".index(params.initial_state[1])
f.write(" %4u\n" % (l_init) )
f.write(" %4u\n" % (params.spherical_order) )
f.write(" %s\n" % (params.polarization) )
f.write(" %4u\n" % (params.scattering_level) )
f.write(" %7.2f%7.2f\n" % (params.fcut, params.cut) )
f.write(" %12.6f\n" % (params.angular_resolution) )
f.write(" %12.6f\n" % (params.lattice_constant) )
f.write(" %12.6f\n" % (params.z_surface) )
f.write(" %4u\n" % (params.atom_types) )
for iat in range(params.atom_types):
f.write(" %4u %s\n" % (params.atomic_number[iat], "..."))
f.write(" %4u %s\n" % (params.atomic_number[iat], params.phase_file[iat]))
f.write(" %12.6f\n" % (params.msq_displacement[iat]) )
f.write(" %12.6f\n" % (params.planewave_attenuation) )
f.write(" %12.6f\n" % (params.inner_potential) )
f.write(" %12.6f\n" % (params.symmetry_range) )
f.write(" %12.6f\n" % (params.polar_incidence_angle) )
f.write(" %12.6f\n" % (params.azimuthal_incidence_angle) )
f.write(" %s\n" % (params.vibration_model) )
f.write(" %12.6f\n" % (params.substrate_atomic_mass) )
f.write(" %12.6f\n" % (params.experiment_temperature) )
f.write(" %12.6f\n" % (params.debye_temperature) )
f.write(" %12.6f\n" % (params.debye_wavevector) )
f.write(" %12.6f%7.3f\n" % (params.rme_minus_value, params.rme_minus_shift) )
f.write(" %12.6f%7.3f\n" % (params.rme_plus_value, params.rme_plus_shift) )
f.write(" %4u\n" % (1) )
f.write(" %4u %12.6f\n" % (1, 1.0) )
def run(self, params, cluster, scan, output_file):
"""
run the MSC program with the given parameters and cluster.
@param params: a project.Params() object with all necessary values except cluster and output files set.
@param cluster: a cluster.Cluster(format=FMT_MSC) object with all atom positions set.
@param scan: a project.Scan() object with all relevant parameters set.
in particular, a scan file is required.
@param output_file: base name for all intermediate and output files
@return: result_file, files_cats
the scan file must be in ETP(IS) format:
* column 0: kinetic energy in eV
* column 1: polar angle in degrees
* column 2: azimuthal angle in degrees
* further columns are ignored
"""
# generate file names
base_filename = output_file
clu_filename = base_filename + ".clu"
out_filename = base_filename + ".list"
par_filename = base_filename + ".par"
dat_filename = base_filename + ".plt1"
etpi_filename = base_filename + ".etpi"
# fix MSC particularities
# singularity at theta == polar_incidence_angle
if params.polar_incidence_angle == 60.0:
params.polar_incidence_angle += 0.1
# save parameter files
cluster.save_to_file(clu_filename)
self.write_input_file(params, par_filename)
if logger.isEnabledFor(logging.INFO):
options = "11"
else:
options = "00"
revision = ""
# run MSC
msc.mscmain(par_filename, clu_filename, scan.filename, base_filename, revision, options)
# load results
result_etpi = md.load_plt(dat_filename)
md.save_data(etpi_filename, result_etpi)
files = {clu_filename: 'input', par_filename: 'input', dat_filename: 'output', base_filename: 'log',
out_filename: 'log', etpi_filename: 'energy'}
return etpi_filename, files