### # 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 . ### 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