Files
dev/script/__Lib/diffcalc-2.1/diffcalc/hkl/you/constraints.py
2019-03-20 13:52:00 +01:00

377 lines
14 KiB
Python
Executable File

###
# Copyright 2008-2011 Diamond Light Source Ltd.
# This file is part of Diffcalc.
#
# Diffcalc 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.
#
# Diffcalc 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 Diffcalc. If not, see <http://www.gnu.org/licenses/>.
###
from math import pi
try:
from numpy import matrix
except ImportError:
from numjy import matrix
from diffcalc.util import DiffcalcException, bold
TODEG = 180 / pi
TORAD = pi / 180
NUNAME = 'gam'
def filter_dict(d, keys):
"""Return a copy of d containing only keys that are in keys"""
##return {k: d[k] for k in keys} # requires Python 2.6
return dict((k, d[k]) for k in keys if k in d.keys())
det_constraints = ('delta', NUNAME, 'qaz', 'naz')
ref_constraints = ('a_eq_b', 'alpha', 'beta', 'psi')
samp_constraints = ('mu', 'eta', 'chi', 'phi', 'mu_is_' + NUNAME)
valueless_constraints = ('a_eq_b', 'mu_is_' + NUNAME)
all_constraints = det_constraints + ref_constraints + samp_constraints
number_single_sample = (len(det_constraints) * len(ref_constraints) *
len(samp_constraints))
class YouConstraintManager(object):
def __init__(self, hardware, fixed_constraints = {}):
self._hardware = hardware
self._constrained = {}
# self._tracking = []
self.n_phi = matrix([[0], [0], [1]])
self._hide_detector_constraint = False # default
self._fixed_samp_constraints = ()
self._fix_constraints(fixed_constraints)
def __str__(self):
lines = []
# TODO: Put somewhere with access to UB matrix!
# WIDTH = 13
# n_phi = self.n_phi
# fmt = "% 9.5f % 9.5f % 9.5f"
# lines.append(" n_phi:".ljust(WIDTH) +
# fmt % (n_phi[0, 0], n_phi[1, 0], n_phi[2, 0]))
# if self._getUBMatrix():
# n_cryst = self._getUMatrix().I * self.n_phi
# lines.append(" n_cryst:".ljust(WIDTH) +
# fmt % (n_cryst[0, 0], n_cryst[1, 0], n_cryst[2, 0]))
# n_recip = self._getUBMatrix().I * self.n_phi
# lines.append(" n_recip:".ljust(WIDTH) +
# fmt % (n_recip[0, 0], n_recip[1, 0], n_recip[2, 0]))
# else:
# lines.append(
# " n_cryst:".ljust(WIDTH) + ' "<<< No U matrix >>>"')
# lines.append(
# " n_recip:".ljust(WIDTH) + ' "<<< No UB matrix >>>"')
lines.extend(self.build_display_table_lines())
lines.append("")
lines.extend(self.report_constraints_lines())
lines.append("")
if (self.is_fully_constrained() and
not self.is_current_mode_implemented()):
lines.append(
" Sorry, this constraint combination is not implemented")
lines.append(" Type 'help con' for available combinations")
else:
lines.append(" Type 'help con' for instructions") # okay
return '\n'.join(lines)
@property
def available_constraint_names(self):
"""list of all available constraints"""
return all_constraints
@property
def settable_constraint_names(self):
"""list of all available constraints that have settable values"""
all_copy = list(all_constraints)
for valueless in valueless_constraints:
all_copy.remove(valueless)
return all_copy
@property
def all(self): # @ReservedAssignment
"""dictionary of all constrained values"""
return self._constrained.copy()
@property
def detector(self):
"""dictionary of constrained detector circles"""
return filter_dict(self.all, det_constraints[:-1])
@property
def reference(self):
"""dictionary of constrained reference circles"""
return filter_dict(self.all, ref_constraints)
@property
def sample(self):
"""dictionary of constrained sample circles"""
return filter_dict(self.all, samp_constraints)
@property
def naz(self):
"""dictionary with naz and value if constrained"""
return filter_dict(self.all, ('naz',))
@property
def constrained_names(self):
"""ordered tuple of constained circles"""
names = self.all.keys()
names.sort(key=lambda name: list(all_constraints).index(name))
return tuple(names)
def _fix_constraints(self, fixed_constraints):
for name in fixed_constraints:
self.constrain(name)
self.set_constraint(name, fixed_constraints[name])
if self.detector or self.naz:
self._hide_detector_constraint = True
fixed_samp_constraints = list(self.sample.keys())
if 'mu' in self.sample or NUNAME in self.detector:
fixed_samp_constraints.append('mu_is_' + NUNAME)
self._fixed_samp_constraints = tuple(fixed_samp_constraints)
def is_constrained(self, name):
return name in self._constrained
def get_value(self, name):
return self._constrained[name]
def build_display_table_lines(self):
unfixed_samp_constraints = list(samp_constraints)
for name in self._fixed_samp_constraints:
unfixed_samp_constraints.remove(name)
if self._hide_detector_constraint:
constraint_types = (ref_constraints, unfixed_samp_constraints)
else:
constraint_types = (det_constraints, ref_constraints,
unfixed_samp_constraints)
num_rows = max([len(col) for col in constraint_types])
max_name_width = max(
[len(name) for name in sum(constraint_types[:-1], ())])
cells = []
header_cells = []
if not self._hide_detector_constraint:
header_cells.append(bold(' ' + 'DET'.ljust(max_name_width)))
header_cells.append(bold(' ' + 'REF'.ljust(max_name_width)))
header_cells.append(bold(' ' + 'SAMP'))
cells.append(header_cells)
underline_cells = [' ' + '-' * max_name_width] * len(constraint_types)
cells.append(underline_cells)
for n_row in range(num_rows):
row_cells = []
for col in constraint_types:
name = col[n_row] if n_row < len(col) else ''
row_cells.append(self._label_constraint(name))
row_cells.append(('%-' + str(max_name_width) + 's') % name)
cells.append(row_cells)
lines = [' '.join(row_cells).rstrip() for row_cells in cells]
return lines
def _report_constraint(self, name):
val = self.get_constraint(name)
if name in valueless_constraints:
return " %s" % name
else:
if val is None:
return "! %-5s: ---" % name
else:
return " %-5s: %.4f" % (name, val)
def report_constraints_lines(self):
lines = []
required = 3 - len(self.all)
if required == 0:
pass
elif required == 1:
lines.append('! 1 more constraint required')
else:
lines.append('! %d more constraints required' % required)
constraints = []
constraints.extend(self.detector.keys())
constraints.extend(self.naz.keys())
constraints.extend(self.reference.keys())
constraints.extend(sorted(self.sample.keys()))
for name in constraints:
lines.append(self._report_constraint(name))
return lines
def is_fully_constrained(self):
return len(self.all) == 3
def is_current_mode_implemented(self):
if not self.is_fully_constrained():
raise ValueError("Three constraints required")
if len(self.sample) == 3:
if set(self.sample.keys()) == set(['chi', 'phi', 'eta']):
return True
return False
if len(self.sample) == 1:
return True
if len(self.reference) == 1:
return (set(self.sample.keys()) == set(['chi', 'phi']) or
set(self.sample.keys()) == set(['chi', 'eta']) or
set(self.sample.keys()) == set(['chi', 'mu']) or
set(self.sample.keys()) == set(['mu', 'eta']))
if len(self.detector) == 1:
return (set(self.sample.keys()) == set(['chi', 'phi']) or
set(self.sample.keys()) == set(['mu', 'eta']) or
set(self.sample.keys()) == set(['mu', 'phi'])
)
return False
def _label_constraint(self, name):
if name == '':
label = ' '
# elif self.is_tracking(name): # implies constrained
# label = '~~> '
elif (self.is_constrained(name) and (self.get_value(name) is None) and
name not in valueless_constraints):
label = 'o->'
elif self.is_constrained(name):
label = '-->'
else:
label = ' '
return label
def constrain(self, name):
if self.is_constraint_fixed(name):
raise DiffcalcException('%s is not a valid constraint name' % name)
if name in self.all:
return "%s is already constrained." % name.capitalize()
elif name in det_constraints:
return self._constrain_detector(name)
elif name in ref_constraints:
return self._constrain_reference(name)
elif name in samp_constraints:
return self._constrain_sample(name)
else:
raise DiffcalcException("%s is not a valid constraint name. Type 'con' for a table of constraint name" % name)
def is_constraint_fixed(self, name):
return ((name in det_constraints and self._hide_detector_constraint) or
(name in samp_constraints and name in self._fixed_samp_constraints))
def _constrain_detector(self, name):
if self.naz:
del self._constrained['naz']
self._constrained[name] = None
return 'Naz constraint replaced.'
elif self.detector:
constrained_name = self.detector.keys()[0]
del self._constrained[constrained_name]
self._constrained[name] = None
return'%s constraint replaced.' % constrained_name.capitalize()
elif len(self.all) == 3: # and no detector
raise self._could_not_constrain_exception(name)
else:
self._constrained[name] = None
def _could_not_constrain_exception(self, name):
return DiffcalcException(
"%s could not be constrained. First un-constrain one of the\n"
"angles %s, %s or %s (with 'uncon')" %
((name.capitalize(),) + self.constrained_names))
def _constrain_reference(self, name):
if self.reference:
constrained_name = self.reference.keys()[0]
del self._constrained[constrained_name]
self._constrained[name] = None
return '%s constraint replaced.' % constrained_name.capitalize()
elif len(self.all) == 3: # and no reference
raise self._could_not_constrain_exception(name)
else:
self._constrained[name] = None
def _constrain_sample(self, name):
if len(self._constrained) < 3:
# okay, more to add
self._constrained[name] = None
# else: three constraints are set
elif len(self.sample) == 1:
# (detector and reference constraints set)
# it is clear which sample constraint to remove
constrained_name = self.sample.keys()[0]
del self._constrained[constrained_name]
self._constrained[name] = None
return '%s constraint replaced.' % constrained_name.capitalize()
else:
raise self._could_not_constrain_exception(name)
def unconstrain(self, name):
if self.is_constraint_fixed(name):
raise DiffcalcException('%s is not a valid constraint name')
if name in self._constrained:
del self._constrained[name]
else:
return "%s was not already constrained." % name.capitalize()
def _check_constraint_settable(self, name):
if name not in all_constraints:
raise DiffcalcException(
'Could not set %(name)s. This is not an available '
'constraint.' % locals())
elif name not in self.all.keys():
raise DiffcalcException(
'Could not set %(name)s. This is not currently '
'constrained.' % locals())
elif name in valueless_constraints:
raise DiffcalcException(
'Could not set %(name)s. This constraint takes no '
'value.' % locals())
def clear_constraints(self):
self._constrained = {}
def set_constraint(self, name, value): # @ReservedAssignment
if self.is_constraint_fixed(name):
raise DiffcalcException('%s is not a valid constraint name')
self._check_constraint_settable(name)
# if name in self._tracking:
# raise DiffcalcException(
# "Could not set %s as this constraint is configured to track "
# "its associated\nphysical angle. First remove this tracking "
# "(use 'untrack %s').""" % (name, name))
old_value = self.get_constraint(name)
old = str(old_value) if old_value is not None else '---'
self._constrained[name] = float(value) * TORAD
new = str(value)
return "%(name)s : %(old)s --> %(new)s" % locals()
def get_constraint(self, name):
value = self.all[name]
return None if value is None else value * TODEG