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:
0
pmsco/calculators/__init__.py
Normal file
0
pmsco/calculators/__init__.py
Normal file
102
pmsco/calculators/calculator.py
Normal file
102
pmsco/calculators/calculator.py
Normal 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
249
pmsco/calculators/edac.py
Normal 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
115
pmsco/calculators/msc.py
Normal 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
|
Reference in New Issue
Block a user