source: trunk/GSASIImapvars.py @ 3711

Last change on this file since 3711 was 3711, checked in by toby, 5 years ago

major constraint update: move conflicting equivs to constraints; allow a formula for multiplier; update docs extensively. New routine EvaluateMultipliers? needed to evaluate formulae

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 77.5 KB
Line 
1# -*- coding: utf-8 -*-
2########### SVN repository information ###################
3# $Date: 2018-10-29 21:10:08 +0000 (Mon, 29 Oct 2018) $
4# $Author: toby $
5# $Revision: 3711 $
6# $URL: trunk/GSASIImapvars.py $
7# $Id: GSASIImapvars.py 3711 2018-10-29 21:10:08Z toby $
8########### SVN repository information ###################
9"""
10*GSASIImapvars: Parameter constraints*
11======================================
12
13Module to implements algebraic contraints, parameter redefinition
14and parameter simplification contraints.
15
16Types of constraints
17--------------------
18
19There are four ways to specify constraints, as listed below.
20Note that parameters are initially stored in the
21main section of the GSAS-II data tree under heading ``Constraints``.
22This dict has four keys, 'Hist', 'HAP', 'Global', and 'Phase',
23each containing a list of constraints. An additional set of constraints
24are generated for each phase based on symmetry considerations by calling
25:func:`GSASIIstrIO.GetPhaseData`.
26
27Note that in the constraints, as stored in the GSAS-II data tree, parameters
28are stored as :class:`GSASIIobj.G2VarObj` objects, as these objects allow for
29changes in numbering of phases, histograms and atoms.
30When they are interpreted (in :func:`GSASIIstrIO.ProcessConstraints`),
31references
32to numbered objects are resolved using the appropriate random ids and the
33variable object is expressed as a string of form ``ph:hst:VARNAM:at``.
34
35Alternate parameters (New Var)
36^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
37
38Parameter redefinition ("New Var" constraints)
39is done by creating an expression that relates several
40parameters:
41
42::
43
44   Mx1 * Px + My1 * Py +...
45   Mx2 * Px + Mz2 * Pz + ...
46
47where Pj is a GSAS-II parameter name and Mjk is a constant (float) multiplier.
48Alternately, multipliers Mjk can contain a formula (str) that will be evaluated prior
49to the start of the refinement. In a formula, GSAS-II parameters will be replaced by the
50value of the parameter before the formula is evaluated, so ``'np.cos(0::Ax:2)'`` is a valid
51multiplier.
52
53This type of constraint describes an alternate
54degree of freedom where parameter Px and Py, etc. are varied to keep
55their ratio
56fixed according the expression. A new variable is assigned to each degree of
57freedom when refined. An example where this can be valuable is when
58two parameters, P1 and P2, have similar values and are highly correlated. It is often better to create a new variable, Ps = P1 + P2, and refine Ps.
59In the later stages of refinement, a second
60variable, Pd = P1 - P2, can be defined and it can be seen if refining Pd is
61supported by the data. Another use will be to define parameters that
62express "irrep modes" in terms of the fundamental structural parameters.
63
64These "New Var" constraints are stored as described for type "f" in the
65:ref:`constraint definitions table <Constraint_definitions_table>`.
66
67Constrained parameters (Const)
68^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
69
70A constraint on a set of variables can be supplied in the form of a
71linear algebraic equation: ::
72
73  Nx1 * Px + Ny1 * Py +... = C1
74
75where Cn is a constant (float), where Pj is a GSAS-II parameter name,
76and where Njk is a constant multiplier (float) or a formula (str) that will be evaluated prior
77to the start of the refinement. In a formula, GSAS-II parameters will be replaced by the
78value of the parameter before the formula is evaluated, so ``'np.cos(0::Ax:2)'`` is a valid
79multiplier.
80
81These equations set an interdependence between variables.
82Common uses of parameter constraints are to set rules that decrease the number of parameters,
83such as restricting the sum of fractional occupancies for atoms that share
84a site to sum to unity, thus reducing the effective number of variables by one.
85Likewise, the Uiso value for a H atom "riding" on a C, N or O atom
86can be related by a fixed offset (the so called B+1 "rule").
87
88A "Const" constraint is stored as described for type "c" in the
89:ref:`constraint definitions table <Constraint_definitions_table>`.
90
91Equivalenced parameters (Equiv)
92^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
93
94A simplifed way to set up a constraint equation is to define an equivalence,
95which can be of form: ::
96
97  C1 * P1 = C2 * Py
98
99or::
100
101  C1 * P1 = C2 * P2 = C3 * P3 = ...
102
103where Cn is a constant (float), where Pj is a GSAS-II parameter name. This
104means that parameters Py (or P2 and P3) are determined from (or "slaved" to)
105parameter P1. Alternately, equivalences can be created with :func:`StoreEquivalence`
106where the multipliers can be a formula (str) that will be evaluated prior to the start of
107the refinement. In a formula, GSAS-II parameters will be replaced by the value of the
108parameter before the formula is evaluate, so ``'np.cos(0::Ax:2)'`` is a valid multiplier.
109
110Note that
111the latter constraint expression is conceptually identical to
112defining constraint equations. In practice, however,
113equivalenced parameters are processed in a
114different and more direct manner than constraint equations. The previous
115set of equalities could also be written in this way as a set of constraint
116equations: ::
117
118  C1 * P1 - C2 * P2 = 0
119  C1 * P1 - C3 * P3 = 0
120  ...
121
122
123The first parameter (P1 above)
124is considered the independent variable
125and the remaining parameters are dependent variables. The dependent variables
126are set from the independent variable.
127An example of how this may be used woul be if, for example,
128a material has a number of O atoms, all in fairly similar bonding environments
129and the diffraction data are sparse, one my reduce the complexity of the model
130by defining Uiso for the first O atoms to be identical to the remaining atoms.
131The results of this refinement will be simpler to understand than if a set of
132constraint equations is used because the refined parameter will be the independent variable, which will be as ph::Uiso:n, corresponding to the first O atom.
133
134A parameter can be used in multiple
135equivalences as independent variable,
136but if parameter is used as both a dependent and independent variable or
137a parameter is used in equivalences and in "New Var" or "Const" constraints,
138this create conflicts that cannot be resolved within the equivalences implementation
139but can be handled as constraint equations.
140The equivalences that violate this are discovered in :func:`CheckEquivalences`
141and then :func:`MoveConfEquiv` is used to change these equivalences to "Const" equations.
142
143Equivalenced parameters ("EQUIV" constraints), when defined by users,
144are stored as described for type "e" in the
145:ref:`constraint definitions table <Constraint_definitions_table>`.
146Other equvalences are generated by symmetry prior
147to display or refinement in :func:`GSASIIstrIO.GetPhaseData`.
148These are not stored.
149
150Fixed parameters (Hold)
151^^^^^^^^^^^^^^^^^^^^^^^^
152
153When variables are refined where a single refinement flag determines that several variables
154are refined at the same time (examples are: cell parameters, atom positions, anisotropic
155displacement parameters, magnetic moments,...) it can be useful to specify that a
156specific parameter should not be varied. These will most commonly be generated due to symmetry,
157but under specific conditions, there may be other good reasons to constrain a variable.
158
159A "Hold" constraint is stored as described for type "h" in the
160:ref:`constraint definitions table <Constraint_definitions_table>`.
161
162Constraint Processing
163---------------------
164
165When constraints will be used or edited, they are processed using a series of
166calls:
167
168* First all of the stored constraints are appended into a single list. They are
169  initially stored in separate lists only to improve their creation and display
170  in the GUI.
171
172* Then :func:`InitVars` is used to initialize the global variables in
173  this module (:mod:`GSASIImapvars`).
174
175* Then :func:`GSASIIstrIO.ProcessConstraints` is used to initially process the
176  constraints, as described below.
177
178* Symmetry-generated equivalences are then created in
179  :func:`GSASIIstrIO.GetPhaseData`, which also calls
180  :func:`GSASIIstrIO.cellVary` and for Pawley refinements
181  :func:`GSASIIstrIO.GetPawleyConstr`. These are entered directly into this
182  module's globals using :func:`StoreEquivalence`.
183* Constraints/equivalences are then checked for possible conflicts with
184  :func:`CheckConstraints`, this requires grouping the constraints,
185  as described below.
186* In refinements, :func:`GenerateConstraints` is then called to
187  create the constraints that will be used, see below for
188* For debugging constraints, :func:`VarRemapShow` can be called after
189  :func:`GenerateConstraints` to display the generated constraints.
190
191Constraint Reorganization (:func:`~GSASIIstrIO.ProcessConstraints`)
192^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
193
194:func:`GSASIIstrIO.ProcessConstraints` is used to initially process the
195constraints. This does these things:
196
1971. The "Hold", "Const" and "New Var" expressions are split between two paired lists,
198   :data:`constDictList` and :data:`fixedList` which are set:
199 
200   * For "Hold" entries a dict with a single entry is placed in constDictList where
201     the key is the parameter name (associated value is 0.0) and fixedList gets a
202     value of 0.0.
203   * For "Const" entries, a dict with multiple entries is placed in constDictList where
204     the key is the parameter name and the value is the multiplier for the parameter,
205     while fixedList gets a string value corresponding to the constant value for
206     the expression.
207   * For "New Var" entries, a dict with multiple entries is placed in constDictList
208     where the key is the parameter name and the value is the multiplier
209     for the parameter; an additional key "_vary" is given the value of True or False
210     depending on the refinement flag setting. The corresponding entry in
211     fixedList is None
212
213   The output from this will have this form where the first entry is a "Const", the
214   second is a "New Var" and the final is a "Hold".
215
216  .. code-block:: python
217
218    constrDict = [
219         {'0:12:Scale': 2.0, '0:14:Scale': 4.0, '0:13:Scale': 3.0, '0:0:Scale': 0.5},
220         {'2::C(10,6,1)': 1.0, '1::C(10,6,1)': 1.0, '_vary':True},
221         {'0::A0': 0.0}]
222    fixedList = ['5.0', None, '0']
223
224
2252. Equivalences are stored using :func:`StoreEquivalence`
226   into this module's globals (:data:`~GSASIImapvars.arrayList`,
227   :data:`~GSASIImapvars.invarrayList`,    :data:`~GSASIImapvars.indParmList`,
228   :data:`~GSASIImapvars.dependentParmList` and  :data:`~GSASIImapvars.symGenList`)
229
230
231Parameter Grouping (:func:`GenerateConstraints`)
232^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
233
234Functions :func:`CheckConstraints` and :func:`GenerateConstraints` are used to
235process the parameter equivalences and constraint lists created in
236:func:`~GSASIIstrIO.ProcessConstraints`. The former is used to generate
237error messages and the latter to generate the internal information used to
238apply the constraints.
239
240Initially, in both a list of parameters that are fixed and those used in
241constraint relations are tabulated in :func:`CheckEquivalences`. The equivalence
242relations are the scanned for the following potential problems:
243
2441. a parameter is used as a dependent variable in more than one
245   equivalence relation
2462. a parameter is fixed and used in in an equivalence relation either
247   as a dependent or independent variable
2483. a parameter is used as a dependent variable in one equivalence relation and
249   as a independent variable in another
2504. a parameter is used in in an equivalence relation (either
251   as a dependent or independent variable) and is used in a constraint
252   expression
2535. a parameter is not defined in a particular refinement, but is used in an
254   equivalence relation
2556. a parameter uses a wildcard for the histogram number (sequential refinements)
256
257Cases 1 & 2 above cannot be corrected, and result in errors. Cases 3 & 4 are
258potentially corrected with :func:`MoveConfEquiv`, as described below.
259Case 5 causes the equivalence to
260be dropped. Case 6 causes the current histogram number to be substituted for
261the wildcard.
262
263For cases 3 & 4, :func:`MoveConfEquiv` is used to change these equivalences
264into "Const" equations. This can potentially mean that additional
265equivalences will be problematic, so if there are changes made by
266:func:`MoveConfEquiv`, :func:`CheckEquivalences` is repeated. If any problem
267cases are noted, the refinement cannot be performed.
268
269Constraint expressions ("Const" and "New Var") are sorted into
270groups so that each group contains the minimum number of entries that
271ensures each parameter is referenced in only one group in
272:func:`GroupConstraints`. This is done by scanning the
273list of dicts in :data:`constDictList` one by one and making a list
274of parameters used in that constraint expression. Any expression that contains
275a parameter in is in that list is added to the current group and those
276parameters are added to this list of parameters. The list of ungrouped
277expressions is then scanned again until no more expressions are added to the
278current group. This process is repeated until every expression has been
279placed in a group. Function :func:`GroupConstraints` returns two lists of lists.
280The first has, for each group, a list of the indices in :data:`constDictList`
281that comprise the group (there can be only one). The second list contains,
282for each group, the unique parameter names in that group.
283
284Each constraint group is then processed. First, wildcard parameters are
285renamed (in a sequential refinement). Any fixed parameters that are used
286in constraints are noted as errors. The number of refined parameters and
287the number of parameters that are not defined in the current refinement are
288also noted. It is fine if all parameters in a group are not defined or all are
289not varied, but if some are defined and others not or some are varied and
290others not, this constitutes an error.
291
292The contents of each group is then examined. Groups with a single
293parameter (holds) are ignored. Then for each group, the number
294of parameters in the group (Np) and the number of expressions in the
295group (Nc) are counted and for each expression. If Nc > Np, then the constraint
296is overdetermined, which also constitutes an error.
297
298The parameter multipliers for each expression are then assembled:
299
300::
301
302   M1a * P1 + M2a * P2 +... Mka * Pk
303   M1b * P1 + M2b * P2 +... Mkb * Pk
304   ...
305   M1j * P1 + M2j * P2 +... Mkj * Pk
306
307
308From this it becomes possible to create a Nc x Np matrix, which is
309called the constraint matrix:
310
311 .. math::
312
313   \\left( \\begin{matrix}
314   M_{1a}  & M_{2a} &... & M_{ka} \\\\
315   M_{1b}  & M_{2b} &... & M_{kb} \\\\
316   ...  \\\\
317   M_{1j}  & M_{2j}  &... & M_{kj}
318   \\end{matrix}\\right)
319
320When Nc<Np, then additional rows need to be added to the matrix and to
321the vector that contains the value for each row (:data:`fixedList`) where
322values are ``None`` for New Vars and a constant for fixed values.
323This then can describe a system of Np simultaneous equations:
324
325 .. math::
326
327   \\left( \\begin{matrix}
328   M_{1a}  & M_{2a} &... & M_{ka} \\\\
329   M_{1b}  & M_{2b} &... & M_{kb} \\\\
330   ...  \\\\
331   M_{1j}  & M_{2j}  &... & M_{kj}
332   \\end{matrix}\\right)
333   \\left( \\begin{matrix}
334   P_{1} \\\\
335   P_{2} \\\\
336   ...  \\\\
337   P_{k}
338   \\end{matrix}\\right)
339   =
340   \\left( \\begin{matrix}
341   C_{1} & C_{2} &  ... & C_{k}
342   \\end{matrix}\\right)
343
344This is done by creating a square matrix from the group using
345:func:`_FillArray` with parameter ``FillDiagonals=False`` (the default). Any
346unspecified rows are left as all zero. The first Nc rows in the
347array are then coverted to row-echelon form using :func:`_RowEchelon`. This
348will create an Exception if any two rows are linearly dependent (which means
349that no matter what values are used for the remaining rows, that the matrix
350will be singular). :func:`_FillArray` is then called with parameter
351``FillDiagonals=True``, which again creates a square matrix but where
352unspecified rows are zero except for the diagonal elements. The 
353`Gram-Schmidt process <http://en.wikipedia.org/wiki/Gram-Schmidt>`_,
354implemented  in :func:`GramSchmidtOrtho`, is used to find orthonormal unit
355vectors for the remaining Np-Nc rows of the matrix. This will fail with
356a ``ConstraintException`` if this is not possible (singular matrix) or
357the result is placed in :data:`constrArr` as a numpy array.
358
359Rows in the matrix corresponding to "New Var" constraints and those that
360were generated by the Gram-Schmidt process are provided with variable names
361(this can be specified if a "New Var" entry by using a ``"_name"`` element
362in the constraint dict, but at present this is not implemented.) Names are
363generated using :data:`paramPrefix` which is set to ``"::constr"``, plus a
364number to make the new parameter name unique. Global dict :data:`genVarLookup`
365provides a lookup table, where the names of the parameters related to this new
366parameter can be looked up easily.
367
368Finally the parameters used as input to the constraint are placed in
369this module's globals
370:data:`~GSASIImapvars.dependentParmList` and the constraint matrix is
371placed in into  :data:`~GSASIImapvars.arrayList`. This can be used to compute
372the initial values for "New Var" parameters. The inverse of the
373constraint matrix is placed in :data:`~GSASIImapvars.invarrayList` and a list
374of the "New Var" parameters and a list of the fixed values (as str's)
375is placed in :data:`~GSASIImapvars.indParmList`. A lookup table for
376fixed values as floats is placed in :data:`~GSASIImapvars.fixedDict`.
377Finally the appropriate entry in :data:`~GSASIImapvars.symGenList` is set to
378False to indicate that this is not a symmetry generated constraint.
379
380
381*Externally-Accessible Routines*
382---------------------------------
383
384To define a set of constrained and unconstrained relations, one
385defines a list of dictionary defining constraint parameters and their
386values, a list of fixed values for each constraint and a list of
387parameters to be varied. In addition, one uses
388:func:`StoreEquivalence` to define parameters that are equivalent.
389Use :func:`EvaluateMultipliers` to convert formula-based constraint/equivalence
390multipliers to numbers and then
391use :func:`CheckConstraints` to check that the input is
392internally consistent and finally :func:`GroupConstraints` and
393:func:`GenerateConstraints` to generate the internally used
394tables. Routine :func:`Map2Dict` is used to initialize the parameter
395dictionary and routine :func:`Dict2Map`, :func:`Dict2Deriv`, and
396:func:`ComputeDepESD` are used to apply constraints. Routine
397:func:`VarRemapShow` is used to print out the constraint information,
398as stored by :func:`GenerateConstraints`. Further information on each routine
399is below:
400
401:func:`InitVars`
402  This is optionally used to clear out all defined previously defined constraint information
403 
404:func:`StoreEquivalence`
405  To implement parameter redefinition, one calls StoreEquivalence. This should be called for every set of
406  equivalence relationships. There is no harm in using StoreEquivalence with the same independent variable:
407
408  .. code-block:: python
409
410       StoreEquivalence('x',('y',))
411       StoreEquivalence('x',('z',))
412
413  or equivalently
414
415  .. code-block:: python
416
417       StoreEquivalence('x',('y','z'))
418
419  The latter will run more efficiently. Note that mixing independent
420  and dependent variables would require assignments, such as
421
422  .. code-block:: python
423
424        StoreEquivalence('x',('y',))
425        StoreEquivalence('y',('z',))
426
427  would require that equivalences be applied in a particular order and
428  thus is implemented as a constraint equation rather than an equivalence.
429       
430  Use StoreEquivalence before calling GenerateConstraints or CheckConstraints
431
432:func:`CheckConstraints`
433   check that input in internally consistent
434
435:func:`GenerateConstraints`
436   generate the internally used tables from constraints and equivalences
437
438:func:`EvaluateMultipliers`
439   Convert any string-specified multipliers to numbers. Call this before
440   using :func:`CheckConstraints` or :func:`GenerateConstraints`.
441
442:func:`Map2Dict`
443   To determine values for the parameters created in this module, one
444   calls Map2Dict. This will not apply contraints.
445
446:func:`Dict2Map`
447   To take values from the new independent parameters and constraints,
448   one calls Dict2Map and set the parameter array, thus appling contraints.
449
450:func:`Dict2Deriv`
451   Use Dict2Deriv to determine derivatives on independent parameters
452   from those on dependent ones.
453
454:func:`ComputeDepESD`     
455   Use ComputeDepESD to compute uncertainties on dependent variables.
456
457:func:`VarRemapShow`
458   To show a summary of the parameter remapping, one calls VarRemapShow.
459
460*Global Variables*
461------------------
462
463dependentParmList:
464   a list containing group of lists of
465   parameters used in the group. Note that parameters listed in
466   dependentParmList should not be refined as they will not affect
467   the model
468
469indParmList:
470     a list containing groups of Independent parameters defined in
471     each group. This contains both parameters used in parameter
472     redefinitions as well as names of generated new parameters.
473
474arrayList:
475     a list containing group of relationship matrices to relate
476     parameters in dependentParmList to those in indParmList. Unlikely
477     to be used externally.
478
479invarrayList:
480     a list containing group of relationship matrices to relate
481     parameters in indParmList to those in dependentParmList. Unlikely
482     to be used externally.
483
484fixedVarList:
485     a list of variables that have been 'fixed'
486     by defining them as equal to a constant (::var: = 0). Note that
487     the constant value is ignored at present. These variables are
488     later removed from varyList which prevents them from being refined.
489     Unlikely to be used externally.
490
491fixedDict:
492     a dictionary containing the fixed values corresponding
493     to parameter equations.  The dict key is an ascii string, but the
494     dict value is a float.  Unlikely to be used externally.
495
496symGenList:
497     a list of boolean values that will be True to indicate that a constraint
498     (only equivalences) is generated by symmetry (or Pawley overlap)
499     
500problemVars:
501     a list containing variables that show up in constraints producing errors
502
503
504
505*Routines/variables*
506---------------------
507
508Note that parameter names in GSAS-II are strings of form ``<ph#>:<hst#>:<nam>`` or ``<ph#>::<nam>:<at#>``.
509"""
510
511from __future__ import division, print_function
512import numpy as np
513import sys
514import GSASIIpath
515GSASIIpath.SetVersionNumber("$Revision: 3711 $")
516# data used for constraints;
517debug = False # turns on printing as constraint input is processed
518
519# note that constraints are stored listed by contraint groups,
520# where each constraint
521# group contains those parameters that must be handled together
522dependentParmList = []
523'''a list of lists where each item contains a list of parameters in each constraint group.
524note that parameters listed in dependentParmList should not be refined directly.'''
525indParmList = [] # a list of names for the new parameters
526'''a list of lists where each item contains a list for each constraint group with
527fixed values for constraint equations and names of generated (New Var) parameters.
528'''
529arrayList = []
530'''a list of of relationship matrices that map model parameters in each
531constraint group (in :data:`dependentParmList`) to
532generated (New Var) parameters.
533'''
534invarrayList = []
535'''a list of of inverse-relationship matrices that map constrained values and
536generated (New Var) parameters (in :data:`indParmList`) to model parameters
537(in :data:`dependentParmList`).
538'''
539fixedDict = {}
540'''A dict lookup-table containing the fixed values corresponding
541to defined parameter equations. Note the key is the original ascii string
542and the value in the dict is a float.
543'''
544fixedVarList = []
545'''List of variables that should not be refined.
546'''
547symGenList = []
548'''A list of flags that if True indicates a constraint was generated by symmetry
549'''
550problemVars = []
551'''a list of variables causing errors
552'''
553dependentVars = []
554'A list of dependent variables, taken from (:data:`dependentParmList`).'
555independentVars = []
556'A list of dependent variables, taken from (:data:`indParmList`).'
557genVarLookup = {}
558'provides a list of variables that are related to each generated variable'
559paramPrefix = "::constr"
560'A prefix for generated parameter names'
561consNum = 0
562'The number to be assigned to the next constraint to be created'
563
564class ConstraintException(Exception):
565    '''Defines an Exception that is used when an exception is raised processing constraints
566    '''
567    pass
568
569def InitVars():
570    '''Initializes all constraint information'''
571    global dependentParmList,arrayList,invarrayList,indParmList,fixedDict,consNum,symGenList
572    dependentParmList = [] # contains a list of parameters in each group
573    arrayList = [] # a list of of relationship matrices
574    invarrayList = [] # a list of inverse relationship matrices
575    indParmList = [] # a list of names for the new parameters
576    fixedDict = {} # a dictionary containing the fixed values corresponding to defined parameter equations
577    symGenList = [] # Flag if constraint is generated by symmetry
578    consNum = 0 # number of the next constraint to be created
579    global genVarLookup
580    genVarLookup = {}
581
582def VarKeys(constr):
583    """Finds the keys in a constraint that represent variables
584    e.g. eliminates any that start with '_'
585
586    :param dict constr: a single constraint entry of form::
587
588        {'var1': mult1, 'var2': mult2,... '_notVar': val,...}
589
590        (see :func:`GroupConstraints`)
591    :returns: a list of keys where any keys beginning with '_' are
592      removed.
593    """
594    return [i for i in constr.keys() if not i.startswith('_')]
595
596
597def GroupConstraints(constrDict):
598    """divide the constraints into groups that share no parameters.
599
600    :param dict constrDict: a list of dicts defining relationships/constraints
601
602    ::
603   
604       constrDict = [{<constr1>}, {<constr2>}, ...]
605
606    where {<constr1>} is {'var1': mult1, 'var2': mult2,... }
607
608    :returns: two lists of lists:
609   
610      * a list of grouped contraints where each constraint grouped containts a list
611        of indices for constraint constrDict entries
612      * a list containing lists of parameter names contained in each group
613     
614      """
615    assignedlist = [] # relationships that have been used
616    groups = [] # contains a list of grouplists
617    ParmList = []
618    for i,consi in enumerate(constrDict):
619        if i in assignedlist: continue # already in a group, skip
620        # starting a new group
621        grouplist = [i,]
622        assignedlist.append(i)
623        groupset = set(VarKeys(consi))
624        changes = True # always loop at least once
625        while(changes): # loop until we can't find anything to add to the current group
626            changes = False # but don't loop again unless we find something
627            for j,consj in enumerate(constrDict):
628                if j in assignedlist: continue # already in a group, skip
629                if len(set(VarKeys(consj)) & groupset) > 0: # true if this needs to be added
630                    changes = True
631                    grouplist.append(j)
632                    assignedlist.append(j)
633                    groupset = groupset | set(VarKeys(consj))
634        group = sorted(grouplist)
635        varlist = sorted(list(groupset))
636        groups.append(group)
637        ParmList.append(varlist)
638    return groups,ParmList
639
640def CheckConstraints(varyList,constrDict,fixedList):
641    '''Takes a list of relationship entries comprising a group of
642    constraints and checks for inconsistencies such as conflicts in
643    parameter/variable definitions and or inconsistently varied parameters.
644
645    :param list varyList: a list of parameters names that will be varied
646
647    :param dict constrDict: a list of dicts defining relationships/constraints
648      (as created in :func:`GSASIIstrIO.ProcessConstraints` and
649      documented in :func:`GroupConstraints`)
650
651    :param list fixedList: a list of values specifying a fixed value for each
652      dict in constrDict. Values are either strings that can be converted to
653      floats or ``None`` if the constraint defines a new parameter rather
654      than a constant.
655
656    :returns: two strings:
657
658      * the first lists conflicts internal to the specified constraints
659      * the second lists conflicts where the varyList specifies some
660        parameters in a constraint, but not all
661       
662      If there are no errors, both strings will be empty
663    '''
664    import re
665    global dependentParmList,arrayList,invarrayList,indParmList,consNum
666    global problemVars
667    # Process the equivalences
668    #    If there are conflicting parameters, move them into constraints. This
669    #    may create new conflicts, requiring movement of other parameters, so
670    #    loop until there are no more changes to make.
671    parmsChanged = True
672    while parmsChanged:
673        parmsChanged = 0
674        errmsg,warnmsg,fixVlist,dropVarList,translateTable = CheckEquivalences(
675            constrDict,varyList)
676        #print('debug: using MoveConfEquiv to address =',errmsg)
677        if problemVars: parmsChanged = MoveConfEquiv(constrDict,fixedList)
678#    GSASIIpath.IPyBreak()
679
680    groups,parmlist = GroupConstraints(constrDict)
681    # scan through parameters in each relationship. Are all varied? If only some are
682    # varied, create a warning message.
683    for group,varlist in zip(groups,parmlist):
684        if len(varlist) == 1: continue
685        for rel in group:
686            varied = 0
687            notvaried = ''
688            for var in constrDict[rel]:
689                if var.startswith('_'): continue
690                if not re.match('[0-9]*:[0-9\*]*:',var):
691                    warnmsg += "\nVariable "+str(var)+" does not begin with a ':'"
692                if var in varyList:
693                    varied += 1
694                else:
695                    if notvaried: notvaried += ', '
696                    notvaried += var
697                if var in fixVlist:
698                    errmsg += '\nParameter '+var+" is Fixed and used in a constraint:\n\t"
699                    if var not in problemVars: problemVars.append(var)
700                    errmsg += _FormatConstraint(constrDict[rel],fixedList[rel])+"\n"
701            if varied > 0 and varied != len(VarKeys(constrDict[rel])):
702                warnmsg += "\nNot all variables refined in constraint:\n\t"
703                warnmsg += _FormatConstraint(constrDict[rel],fixedList[rel])
704                warnmsg += '\nNot refined: ' + notvaried + '\n'
705    if errmsg or warnmsg:
706        return errmsg,warnmsg
707
708    # now look for process each group and create the relations that are needed to form
709    # non-singular square matrix
710    for group,varlist in zip(groups,parmlist):
711        if len(varlist) == 1: continue # a constraint group with a single variable can be ignored
712        if len(varlist) < len(group): # too many relationships -- no can do
713            errmsg += "\nOver-constrained input. "
714            errmsg += "There are more constraints " + str(len(group))
715            errmsg += "\n\tthan variables " + str(len(varlist)) + "\n"
716            for rel in group:
717                errmsg += _FormatConstraint(constrDict[rel],fixedList[rel])
718                errmsg += "\n"
719                continue
720        try:
721            multarr = _FillArray(group,constrDict,varlist)
722            _RowEchelon(len(group),multarr,varlist)
723        except:
724            errmsg += "\nSingular input. "
725            errmsg += "There are internal inconsistencies in these constraints\n"
726            for rel in group:
727                errmsg += _FormatConstraint(constrDict[rel],fixedList[rel])
728                errmsg += "\n"
729            continue
730        try:
731            multarr = _FillArray(group,constrDict,varlist,FillDiagonals=True)
732            GramSchmidtOrtho(multarr,len(group))
733        except:
734            errmsg += "\nUnexpected singularity with constraints (in Gram-Schmidt)\n"
735            for rel in group:
736                errmsg += _FormatConstraint(constrDict[rel],fixedList[rel])
737                errmsg += "\n"
738            continue
739        mapvar = []
740        group = group[:]
741        # scan through all generated and input variables
742        # Check again for inconsistent variable use
743        # for new variables -- where varied and unvaried parameters get grouped
744        # together. I don't think this can happen when not flagged before, but
745        # it does not hurt to check again.
746        for i in range(len(varlist)):
747            varied = 0
748            notvaried = ''
749            if len(group) > 0:
750                rel = group.pop(0)
751                fixedval = fixedList[rel]
752                for var in VarKeys(constrDict[rel]):
753                    if var in varyList:
754                        varied += 1
755                    else:
756                        if notvaried: notvaried += ', '
757                        notvaried += var
758            else:
759                fixedval = None
760            if fixedval is None:
761                varname = paramPrefix + str(consNum) # assign a name to a variable
762                mapvar.append(varname)
763                consNum += 1
764            else:
765                mapvar.append(fixedval)
766            if varied > 0 and notvaried != '':
767                warnmsg += "\nNot all variables refined in generated constraint"
768                warnmsg += '\nPlease report this unexpected error\n'
769                for rel in group:
770                    warnmsg += _FormatConstraint(constrDict[rel],fixedList[rel])
771                    warnmsg += "\n"
772                warnmsg += '\n\tNot refined: ' + notvaried + '\n'
773        try:
774            np.linalg.inv(multarr)           
775        except:
776            errmsg += "\nSingular input. "
777            errmsg += "The following constraints are not "
778            errmsg += "linearly independent\n\tor do not "
779            errmsg += "allow for generation of a non-singular set\n"
780            errmsg += 'Please report this unexpected error\n'
781            for rel in group:
782                errmsg += _FormatConstraint(constrDict[rel],fixedList[rel])
783                errmsg += "\n"
784    _setVarLists([])
785    return errmsg,warnmsg
786
787def GenerateConstraints(varyList,constrDict,fixedList,parmDict=None,SeqHist=None):
788    '''Takes a list of relationship entries comprising a group of
789    constraints and builds the relationship lists and their inverse
790    and stores them in global variables Also checks for internal
791    conflicts or inconsistencies in parameter/variable definitions.
792
793    :param list varyList: a list of parameters names (strings of form
794      ``<ph>:<hst>:<nam>``) that will be varied. Note that this is changed here.
795   
796    :param dict constrDict: a list of dicts defining relationships/constraints
797      (as defined in :func:`GroupConstraints`)
798
799    :param list fixedList: a list of values specifying a fixed value for each
800      dict in constrDict. Values are either strings that can be converted to
801      floats, float values or None if the constraint defines a new parameter.
802     
803    :param dict parmDict: a dict containing all parameters defined in current
804      refinement.
805
806    :param int SeqHist: number of current histogram, when used in a sequential
807      refinement. None (default) otherwise. Wildcard variable names are
808      set to the current histogram, when found if not None.
809    '''
810    global dependentParmList,arrayList,invarrayList,indParmList,consNum
811    global genVarLookup
812    msg = ''
813    shortmsg = ''
814    changed = False
815    # Process the equivalences
816    #    If there are conflicting parameters, move them into constraints. This
817    #    may create new conflicts, requiring movement of other parameters, so
818    #    loop until there are no more changes to make.
819    parmsChanged = True
820    while parmsChanged:
821        parmsChanged = 0
822        errmsg,warnmsg,fixVlist,dropVarList,translateTable = CheckEquivalences(
823            constrDict,varyList,parmDict,SeqHist)
824        if problemVars:
825            parmsChanged = MoveConfEquiv(constrDict,fixedList)
826            changed = True
827    if errmsg:
828        msg = errmsg
829    if warnmsg:
830        if msg: msg += '\n'
831        msg += warnmsg
832
833    # scan through parameters in each relationship. Are all varied? If only some are
834    # varied, create an error message.
835    groups,parmlist = GroupConstraints(constrDict)
836    for group,varlist in zip(groups,parmlist):
837        if len(varlist) == 1: continue
838        for rel in group:
839            varied = 0
840            notvaried = ''
841            unused = 0
842            notused = ''
843            for var in constrDict[rel]:
844                if var.startswith('_'): continue
845                if var.split(':')[1] == '*' and SeqHist is not None:
846                    # convert wildcard var to reference current histogram; save translation in table
847                    sv = var.split(':')
848                    sv[1] = str(SeqHist)
849                    translateTable[var] = ':'.join(sv)
850                    var = translateTable[var]
851                if parmDict is not None and var not in parmDict:
852                    unused += 1
853                    if notvaried: notused += ', '
854                    notused += var
855                if var in varyList:
856                    varied += 1
857                else:
858                    if notvaried: notvaried += ', '
859                    notvaried += var
860                if var in fixedVarList:
861                    msg += '\nError: parameter '+var+" is Fixed and used in a constraint:\n\t"
862                    msg += _FormatConstraint(constrDict[rel],fixedList[rel])+"\n"
863            #if unused > 0:# and unused != len(VarKeys(constrDict[rel])):
864            if unused > 0 and unused != len(VarKeys(constrDict[rel])):
865                #msg += "\nSome (but not all) variables in constraint are not defined:\n\t"
866                #msg += _FormatConstraint(constrDict[rel],fixedList[rel])
867                #msg += '\nNot used: ' + notused + '\n'
868                shortmsg += notused+" not used in constraint "+_FormatConstraint(constrDict[rel],fixedList[rel])
869            elif varied > 0 and varied != len(VarKeys(constrDict[rel])):
870                #msg += "\nNot all variables refined in constraint:\n\t"
871                #msg += _FormatConstraint(constrDict[rel],fixedList[rel])
872                #msg += '\nNot refined: ' + notvaried + '\n'
873                shortmsg += notvaried+" not varied in constraint "+_FormatConstraint(constrDict[rel],fixedList[rel])
874    # if there were errors found, go no farther
875    if shortmsg and SeqHist is not None:
876        if msg:
877            print (' *** ERROR in constraint definitions! ***')
878            print (msg)
879            raise ConstraintException
880        print ('*** Sequential refinement: ignoring constraint definition(s): ***')
881        print (shortmsg)
882        msg = ''
883    elif shortmsg:
884        msg += shortmsg
885    if msg:
886        print (' *** ERROR in constraint definitions! ***')
887        print (msg)
888        raise ConstraintException
889               
890    # now process each group and create the relations that are needed to form
891    # a non-singular square matrix
892    # If all are varied and this is a constraint equation, then set VaryFree flag
893    # so that the newly created relationships will be varied
894    for group,varlist in zip(groups,parmlist):
895        if len(varlist) == 1: continue
896        # for constraints, if all included variables are refined,
897        # set the VaryFree flag, and remaining degrees of freedom will be
898        # varied (since consistency was checked, if any one variable is
899        # refined, then assume that all are)
900        varsList = [] # make a list of all the referenced variables as well
901        VaryFree = False
902        for rel in group:
903            varied = 0
904            unused = 0
905            for var in VarKeys(constrDict[rel]):
906                var = translateTable.get(var,var) # replace wildcards
907                if parmDict is not None and var not in parmDict:
908                    unused += 1                   
909                if var not in varsList: varsList.append(var)
910                if var in varyList: varied += 1
911            if fixedList[rel] is not None and varied > 0:
912                VaryFree = True
913        if len(varlist) < len(group): # too many relationships -- no can do
914            msg = 'too many relationships'
915            break
916        # Since we checked before, if any variables are unused, then all must be.
917        # If so, this set of relationships can be ignored
918        if unused:
919            if debug: print('Constraint ignored (all variables undefined)')
920            if debug: print ('    '+_FormatConstraint(constrDict[rel],fixedList[rel]))
921            continue
922        # fill in additional degrees of freedom
923        try:
924            arr = _FillArray(group,constrDict,varlist)
925            _RowEchelon(len(group),arr,varlist)
926            constrArr = _FillArray(group,constrDict,varlist,FillDiagonals=True)
927            GramSchmidtOrtho(constrArr,len(group))
928        except:
929            msg = 'Singular relationships'
930            break
931        mapvar = []
932        group = group[:]
933        # scan through all generated and input relationships, we need to add to the varied list
934        # all the new parameters where VaryFree has been set or where a New Var is varied.
935        #
936        # If a group does not contain any fixed values (constraint equations)
937        # and nothing in the group is varied, drop this group, so that the
938        # dependent parameters can be refined individually.
939        unused = True
940        for i in range(len(varlist)):
941            if len(group) > 0: # get the original equation reference
942                rel = group.pop(0)
943                fixedval = fixedList[rel]
944                varyflag = constrDict[rel].get('_vary',False)
945                varname = constrDict[rel].get('_name','')
946            else: # this relationship has been generated
947                varyflag = False
948                varname = ''
949                fixedval = None
950            if fixedval is None: # this is a new variable, not a constraint
951                if not varname:
952                    varname = paramPrefix + str(consNum) # no assigned name, create one
953                    consNum += 1
954                mapvar.append(varname)
955                genVarLookup[varname] = varlist # save list of variables related to this new var
956                # vary the new relationship if it is a degree of freedom in
957                # a set of contraint equations or if a New Var is flagged to be varied.
958                if VaryFree or varyflag: 
959                    unused = False
960                    varyList.append(varname)
961                    # fix (prevent varying) of all the variables inside the constraint group
962                    # (dependent vars)
963                    for var in varsList:
964                        if var in varyList: varyList.remove(var)
965            else:
966                unused = False
967                mapvar.append(fixedval)
968        if unused: # skip over constraints that don't matter (w/o fixed value or any refined variables)
969            if debug: print('Constraint ignored (all variables unrefined)')
970            if debug: print ('   '+_FormatConstraint(constrDict[rel],fixedList[rel]))
971            continue 
972        dependentParmList.append([translateTable.get(var,var) for var in varlist])
973        arrayList.append(constrArr)
974        invarrayList.append(np.linalg.inv(constrArr))
975        indParmList.append(mapvar)
976        symGenList.append(False)
977    if msg:
978        print (' *** ERROR in constraint definitions! ***')
979        print (msg)
980        print (VarRemapShow(varyList))
981        raise ConstraintException
982    # setup dictionary containing the fixed values
983    global fixedDict
984    # key is original ascii string, value is float
985    for fixedval in fixedList:
986        if fixedval:
987            fixedDict[fixedval] = float(fixedval)
988    _setVarLists(dropVarList)
989    if changed:
990        print(60*'=')
991        print('Constraints were reclassified to avoid conflicts, as below:')
992        print (VarRemapShow(varyList,True))
993        print(60*'=')
994   
995def _setVarLists(dropVarList):
996    '''Make list of dependent and independent variables (after dropping unused vars in dropVarList)
997    '''
998    global dependentParmList,indParmList
999    global dependentVars
1000    global independentVars
1001    dependentVars = []
1002    independentVars = []
1003    for varlist,mapvars in zip(dependentParmList,indParmList):  # process all constraints
1004        for mv in mapvars:
1005            if mv in dropVarList: continue
1006            if mv not in independentVars: independentVars.append(mv)
1007        for mv in varlist:
1008            if mv in dropVarList: continue
1009            if mv not in dependentVars: dependentVars.append(mv)
1010    if debug: # on debug, show what is parsed & generated, semi-readable
1011        print (50*'-')
1012        #print (VarRemapShow(varyList))
1013        #print ('Varied: ',varyList)
1014        print ('Not Varied: ',fixedVarList)
1015
1016# def CheckEquivalences(constrDict,varyList):
1017#     global dependentParmList,arrayList,invarrayList,indParmList,consNum
1018#     global problemVars
1019#     warnmsg = ''
1020#     errmsg = ''
1021#     problemVars = []
1022#     # process fixed variables (holds)
1023#     fixVlist = [] # list of Fixed vars
1024#     constrVars = [] # list of vars in constraint expressions
1025#     for cdict in constrDict:
1026#         # N.B. No "_" names in holds
1027#         if len(cdict) == 1:
1028#             fixVlist.append(list(cdict.keys())[0])
1029#         else:
1030#             constrVars += cdict.keys() # this will include _vary (not a problem)
1031#     # process equivalences: make a list of dependent and independent vars
1032#     #    and check for repeated uses (repetition of a parameter as an
1033#     #    independent var is OK)
1034#     indepVarList = []
1035#     depVarList = []
1036#     multdepVarList = []
1037#     for varlist,mapvars,multarr,invmultarr in zip(
1038#         dependentParmList,indParmList,arrayList,invarrayList):
1039#         if multarr is None: # an equivalence
1040#             zeromult = False
1041#             for mv in mapvars:
1042#                 varied = 0
1043#                 notvaried = ''
1044#                 if mv in varyList:
1045#                     varied += 1
1046#                 else:
1047#                     if notvaried: notvaried += ', '
1048#                     notvaried += mv
1049#                 if mv not in indepVarList: indepVarList.append(mv)
1050#                 for v,m in zip(varlist,invmultarr):
1051#                     if v in indepVarList:
1052#                         errmsg += '\nVariable '+v+' is used to set values in a constraint before its value is set in another constraint\n'
1053#                         if v not in problemVars: problemVars.append(v)
1054#                     if m == 0: zeromult = True
1055#                     if v in varyList:
1056#                         varied += 1
1057#                     else:
1058#                         if notvaried: notvaried += ', '
1059#                         notvaried += v
1060#                     if v in depVarList:
1061#                         multdepVarList.append(v)
1062#                     else:
1063#                         depVarList.append(v)
1064#             if varied > 0 and varied != len(varlist)+1:
1065#                 warnmsg += "\nNot all variables refined in equivalence:\n\t"
1066#                 s = ""
1067#                 for v in varlist:
1068#                     if s != "": s+= " & "
1069#                     s += str(v)           
1070#                 warnmsg += str(mv) + " => " + s
1071#                 warnmsg += '\nNot refined: ' + notvaried + '\n'
1072#             if zeromult:
1073#                 errmsg += "\nZero multiplier is invalid in equivalence:\n\t"
1074#                 s = ""
1075#                 for v in varlist:
1076#                     if s != "": s+= " & "
1077#                     s += str(v)           
1078#                 errmsg += str(mv) + " => " + s + '\n'
1079#     # check for errors:
1080#     if len(multdepVarList) > 0:
1081#         errmsg += "\nThe following parameters(s) are used in conflicting Equivalence relations as dependent variables:\n"
1082#         s = ''
1083#         for var in sorted(set(multdepVarList)):
1084#             if v not in problemVars: problemVars.append(v)
1085#             if s != "": s+= ", "
1086#             s += str(var)           
1087#         errmsg += '\t'+ s + '\n'
1088#     equivVarList = list(set(indepVarList).union(set(depVarList)))
1089#     if debug: print ('equivVarList',equivVarList)
1090#     # check for parameters that are both fixed and in an equivalence (not likely)
1091#     inboth = set(fixVlist).intersection(set(equivVarList))
1092#     if len(inboth) > 0:
1093#         errmsg += "\nThe following parameter(s) are used in both Equivalence and Fixed constraints:\n"
1094#         s = ''
1095#         for var in sorted(inboth):
1096#             if var not in problemVars: problemVars.append(var)
1097#             if s != "": s+= ", "
1098#             s += str(var)
1099#         errmsg += '\t'+ s + '\n'
1100#     # check for parameters that in both an equivalence and a constraint expression
1101#     inboth = set(constrVars).intersection(set(equivVarList))
1102#     if len(inboth) > 0:
1103#         errmsg += "\nThe following parameter(s) are used in both Equivalence and Equiv or new var constraints:\n"
1104#         s = ''
1105#         for var in sorted(inboth):
1106#             if var not in problemVars: problemVars.append(var)
1107#             if s != "": s+= ", "
1108#             s += str(var)
1109#         errmsg += '\t'+ s + '\n'
1110#     return errmsg,warnmsg,fixVlist
1111
1112def CheckEquivalences(constrDict,varyList,parmDict=None,SeqHist=None):
1113    '''Process equivalence constraints, looking for conflicts such as
1114    where a parameter is used in both an equivalence and a constraint expression
1115    or where chaining is done (A->B and B->C).
1116    When called during refinements, parmDict is defined, and for sequential refinement
1117    SeqHist ia also defined.
1118
1119      * parmDict is used to remove equivalences where a parameter is not present
1120        in a refinement
1121      * SeqHist is used to rename wild-card parameter names in sequential
1122        refinements to use the current histogram.
1123    '''
1124    global dependentParmList,arrayList,invarrayList,indParmList,consNum
1125    global problemVars
1126    warnmsg = ''
1127    errmsg = ''
1128    problemVars = []
1129    # process fixed variables (holds)
1130    fixVlist = [] # list of Fixed vars
1131    constrVars = [] # list of vars in constraint expressions
1132    for cdict in constrDict:
1133        # N.B. No "_" names in holds
1134        if len(cdict) == 1:
1135            fixVlist.append(list(cdict.keys())[0])
1136        else:
1137            constrVars += cdict.keys() # this will include _vary (not a problem)
1138    # process equivalences: make a list of dependent and independent vars
1139    #    and check for repeated uses (repetition of a parameter as an
1140    #    independent var is OK)
1141    indepVarList = []
1142    depVarList = []
1143    multdepVarList = []
1144    dropVarList = []
1145    translateTable = {} # lookup table for wildcard referenced variables
1146    for varlist,mapvars,multarr,invmultarr in zip(
1147        dependentParmList,indParmList,arrayList,invarrayList):
1148        if multarr is None: # an equivalence
1149            zeromult = False
1150            for i,mv in enumerate(mapvars):
1151                if mv.split(':')[1] == '*' and SeqHist is not None:
1152                    # convert wildcard var to reference current histogram; save translation in table
1153                    sv = mv.split(':')
1154                    sv[1] = str(SeqHist)
1155                    mv = translateTable[mv] = ':'.join(sv)
1156                    mapvars[i] = mv
1157                varied = 0
1158                notvaried = ''
1159                if mv in varyList:
1160                    varied += 1
1161                else:
1162                    if notvaried: notvaried += ', '
1163                    notvaried += mv
1164                if parmDict is not None and mv not in parmDict:
1165                    print ("Dropping equivalence for variable "+str(mv)+". Not defined in this refinement")
1166                    if mv not in dropVarList: dropVarList.append(mv)
1167                if mv not in indepVarList: indepVarList.append(mv)
1168            for i,(v,m) in enumerate(zip(varlist,invmultarr)):
1169                if v.split(':')[1] == '*' and SeqHist is not None:
1170                    # convert wildcard var to reference current histogram; save translation in table
1171                    sv = v.split(':')
1172                    sv[1] = str(SeqHist)
1173                    varlist[i] = v = translateTable[v] = ':'.join(sv)
1174                if parmDict is not None and v not in parmDict:
1175                    print ("Dropping equivalence for dep. variable "+str(v)+". Not defined in this refinement")
1176                    if v not in dropVarList: dropVarList.append(v)
1177                    continue
1178                if m == 0: zeromult = True
1179                if v in varyList:
1180                    varied += 1
1181                else:
1182                    if notvaried: notvaried += ', '
1183                    notvaried += v
1184                if v in indepVarList:
1185                    errmsg += '\nVariable '+v+' is used to set values in a constraint before its value is set in another constraint\n'
1186                    if v not in problemVars: problemVars.append(v)
1187                if v in depVarList:
1188                    multdepVarList.append(v)
1189                else:
1190                    depVarList.append(v)
1191            if varied > 0 and varied != len(varlist)+1:
1192                warnmsg += "\nNot all variables refined in equivalence:\n\t"
1193                s = ""
1194                for v in varlist:
1195                    if s != "": s+= " & "
1196                    s += str(v)           
1197                warnmsg += str(mv) + " => " + s
1198                warnmsg += '\nNot refined: ' + notvaried + '\n'
1199            if zeromult:
1200                errmsg += "\nZero multiplier is invalid in equivalence:\n\t"
1201                s = ""
1202                for v in varlist:
1203                    if s != "": s+= " & "
1204                    s += str(v)           
1205                errmsg += str(mv) + " => " + s + '\n'
1206    # check for errors:
1207    if len(multdepVarList) > 0:
1208        errmsg += "\nThe following parameters(s) are used in conflicting Equivalence relations as dependent variables:\n"
1209        s = ''
1210        for var in sorted(set(multdepVarList)):
1211            if v not in problemVars: problemVars.append(v)
1212            if s != "": s+= ", "
1213            s += str(var)           
1214        errmsg += '\t'+ s + '\n'
1215    equivVarList = list(set(indepVarList).union(set(depVarList)))
1216    if debug: print ('equivVarList',equivVarList)
1217    # check for parameters that are both fixed and in an equivalence (not likely)
1218    inboth = set(fixVlist).intersection(set(equivVarList))
1219    if len(inboth) > 0:
1220        errmsg += "\nThe following parameter(s) are used in both Equivalence and Fixed constraints:\n"
1221        s = ''
1222        for var in sorted(inboth):
1223            if var not in problemVars: problemVars.append(var)
1224            if s != "": s+= ", "
1225            s += str(var)
1226        errmsg += '\t'+ s + '\n'
1227    # check for parameters that in both an equivalence and a constraint expression
1228    inboth = set(constrVars).intersection(set(equivVarList))
1229    if len(inboth) > 0:
1230        errmsg += "\nThe following parameter(s) are used in both Equivalence and Equiv or new var constraints:\n"
1231        s = ''
1232        for var in sorted(inboth):
1233            if var not in problemVars: problemVars.append(var)
1234            if s != "": s+= ", "
1235            s += str(var)
1236        errmsg += '\t'+ s + '\n'
1237    return errmsg,warnmsg,fixVlist,dropVarList,translateTable
1238
1239def MoveConfEquiv(constrDict,fixedList):
1240    '''Address conflicts in Equivalence constraints by creating an constraint equation
1241    that has the same action as the equivalence and removing the Equivalence
1242    '''
1243    global dependentParmList,arrayList,invarrayList,indParmList,consNum
1244    global problemVars
1245    parmsChanged = 0
1246    for i,(varlist,mapvars) in enumerate(zip(dependentParmList,indParmList)):
1247        conf = False
1248        for mv in mapvars:
1249            if mv in problemVars:
1250                conf = True
1251                break
1252        for v in varlist:
1253            if v in problemVars:
1254                conf = True
1255                break
1256        if conf:
1257            parmsChanged += 1
1258            indvar = indParmList[i][0]
1259            for dep,mult in zip(dependentParmList[i],invarrayList[i]):
1260                #print('debug replacing equiv with constraint equation 0=',{indvar:-1.,dep:mult[0]})
1261                constrDict += [{indvar:-1.,dep:mult[0]}]
1262                fixedList += ['0.0']
1263            dependentParmList[i] = None
1264    if parmsChanged:
1265        for i in range(len(dependentParmList)-1,-1,-1):
1266            if dependentParmList[i] is None:
1267                del dependentParmList[i],indParmList[i],arrayList[i],invarrayList[i]
1268    return parmsChanged
1269
1270def StoreEquivalence(independentVar,dependentList,symGen=True):
1271    '''Takes a list of dependent parameter(s) and stores their
1272    relationship to a single independent parameter (independentVar)
1273
1274    :param str independentVar: name of master parameter that will be used to determine the value
1275      to set the dependent variables
1276
1277    :param list dependentList: a list of parameters that will set from
1278         independentVar. Each item in the list can be a string with the parameter
1279         name or a tuple containing a name and multiplier:
1280         ``['::parm1',('::parm2',.5),]``
1281
1282    '''
1283   
1284    global dependentParmList,arrayList,invarrayList,indParmList,symGenList
1285    mapList = []
1286    multlist = []
1287    allfloat = True
1288    for var in dependentList:
1289        if isinstance(var, str):
1290            mult = 1.0
1291        elif len(var) == 2:
1292            var,mult = var
1293        else:
1294            raise Exception("Cannot parse "+repr(var) + " as var or (var,multiplier)")
1295        mapList.append(var)
1296        try:           
1297            multlist.append(tuple((float(mult),)))
1298        except:
1299            allfloat = False
1300            multlist.append(tuple((mult,)))
1301    # added relationships to stored values
1302    arrayList.append(None)
1303    if allfloat:
1304        invarrayList.append(np.array(multlist))
1305    else:
1306        invarrayList.append(multlist)
1307    indParmList.append(list((independentVar,)))
1308    dependentParmList.append(mapList)
1309    symGenList.append(symGen)
1310    return
1311
1312def EvaluateMultipliers(constList,*dicts):
1313    '''Convert multipliers for constraints and equivalences that are specified
1314    as strings into values. The strings can specify values in the parameter dicts as
1315    well as normal Python functions, such as "2*np.cos(0::Ax:2/2.)"
1316   
1317    :param list constList: a list of dicts containing constraint expressions
1318    :param \*dict1: one or more dicts containing GSAS-II parameters and their values
1319       can be specified
1320    :returns: an empty string if there were no errors, or an error message listing
1321       the strings that could not be converted.
1322    '''
1323    def SubfromParmDict(s,prmDict):
1324        for key in prmDict:
1325            if key in s:
1326                s = s.replace(key,str(prmDict[key]))
1327        return eval(s)
1328    prmDict = {}
1329    for d in dicts: prmDict.update(d) # combine all passed parameter dicts
1330    problemList = ""
1331    # loop through multipliers in contraint expressions
1332    for const in constList:
1333        for key in const:
1334            try:
1335                1+const[key]
1336                continue
1337            except:
1338                pass
1339            try:
1340                newval = SubfromParmDict(const[key][:],prmDict)
1341                if GSASIIpath.GetConfigValue('debug'):
1342                    print('Changing ',const[key],'to',newval)
1343                const[key] = newval               
1344            except:
1345                if problemList: problemList += ", "
1346                problemList += const[key]
1347    # loop through multipliers in equivalences
1348    global arrayList,invarrayList
1349    for i,(a,valList) in enumerate(zip(arrayList,invarrayList)):
1350        if a is not None: continue # ignore if not equiv
1351        try:
1352            valList.shape
1353            continue # ignore if already a numpy array
1354        except:
1355            pass
1356        repList = []
1357        for v in valList:
1358            try:
1359                1+v[0]
1360                repList.append(tuple((v[0],)))
1361                continue
1362            except:
1363                pass
1364            try:
1365                newval = SubfromParmDict(v[0][:],prmDict)
1366                if GSASIIpath.GetConfigValue('debug'):
1367                    print('Changing ',v[0],'to',newval)
1368                repList.append(tuple((newval,)))
1369            except:
1370                if problemList: problemList += ", "
1371                problemList += v[0]
1372                repList.append(tuple(('error',)))
1373        invarrayList[i] = np.array(repList)
1374    return problemList
1375
1376def GetDependentVars():
1377    '''Return a list of dependent variables: e.g. variables that are
1378    constrained in terms of other variables
1379
1380    :returns: a list of variable names
1381
1382    '''
1383    global dependentVars
1384    return dependentVars
1385
1386def GetIndependentVars():
1387    '''Return a list of independent variables: e.g. variables that are
1388    created by constraints of other variables
1389
1390    :returns: a list of variable names
1391
1392    '''
1393    global independentVars
1394    return independentVars
1395
1396def PrintIndependentVars(parmDict,varyList,sigDict,PrintAll=False,pFile=None):
1397    '''Print the values and uncertainties on the independent variables'''
1398    global dependentParmList,arrayList,invarrayList,indParmList,fixedDict
1399    printlist = []
1400    mapvars = GetIndependentVars()
1401    for i,name in enumerate(mapvars):
1402        if name in fixedDict: continue
1403        if PrintAll or name in varyList:
1404            sig = sigDict.get(name)
1405            printlist.append([name,parmDict[name],sig])
1406    if len(printlist) == 0: return
1407    s1 = ''
1408    s2 = ''
1409    s3 = ''
1410    pFile.write(130*'-'+'\n')
1411    pFile.write("Variables generated by constraints\n")
1412    printlist.append(3*[None])
1413    for name,val,esd in printlist:
1414        if len(s1) > 120 or name is None:
1415            pFile.write(''+'\n')
1416            pFile.write(s1+'\n')
1417            pFile.write(s2+'\n')
1418            pFile.write(s3+'\n')
1419            s1 = ''
1420            if name is None: break
1421        if s1 == "":
1422            s1 = ' name  :'
1423            s2 = ' value :'
1424            s3 = ' sig   :'
1425        s1 += '%15s' % (name)
1426        s2 += '%15.4f' % (val)
1427        if esd is None:
1428            s3 += '%15s' % ('n/a')
1429        else:   
1430            s3 += '%15.4f' % (esd)
1431
1432def ComputeDepESD(covMatrix,varyList,parmDict):
1433    '''Compute uncertainties for dependent parameters from independent ones
1434    returns a dictionary containing the esd values for dependent parameters
1435    '''
1436    sigmaDict = {}
1437    for varlist,mapvars,invmultarr in zip(dependentParmList,indParmList,invarrayList):
1438        #if invmultarr is None: continue # probably not needed
1439#        try:
1440#            valuelist = [parmDict[var] for var in mapvars]
1441#        except KeyError:
1442#            continue
1443        # get the v-covar matrix for independent parameters
1444        vcov = np.zeros((len(mapvars),len(mapvars)))
1445        for i1,name1 in enumerate(mapvars):
1446            if name1 not in varyList: continue
1447            iv1 = varyList.index(name1)
1448            for i2,name2 in enumerate(mapvars):
1449                if name2 not in varyList: continue
1450                iv2 = varyList.index(name2)
1451                vcov[i1][i2] = covMatrix[iv1][iv2]
1452        # vec is the vector that multiplies each of the independent values
1453        for v,vec in zip(varlist,invmultarr):
1454            sigmaDict[v] = np.sqrt(np.inner(vec.T,np.inner(vcov,vec)))
1455    return sigmaDict
1456
1457def _FormatConstraint(RelDict,RelVal):
1458    '''Formats a Constraint or Function for use in a convenient way'''
1459    linelen = 45
1460    s = [""]
1461    for var,val in RelDict.items():
1462        if var.startswith('_'): continue
1463        if len(s[-1]) > linelen: s.append(' ')
1464        m = val
1465        if s[-1] != "" and m >= 0:
1466            s[-1] += ' + '
1467        elif s[-1] != "":
1468            s[-1] += ' - '
1469            m = abs(m)
1470        s[-1] += '%.3f*%s '%(m,var)
1471    if len(s[-1]) > linelen: s.append(' ')
1472    if RelVal is None:
1473        s[-1] += ' = New variable'
1474    else:
1475        s[-1] += ' = ' + RelVal
1476    s1 = ''
1477    for s2 in s:
1478        if s1 != '': s1 += '\n\t'
1479        s1 += s2
1480    return s1
1481
1482def VarRemapShow(varyList,inputOnly=False):
1483    '''List out the saved relationships. This should be done after the constraints have been
1484    defined using :func:`StoreEquivalence`, :func:`GroupConstraints` and :func:`GenerateConstraints`.
1485
1486    :returns: a string containing the details of the contraint relationships
1487    '''
1488    s = ''
1489    if len(fixedVarList) > 0:
1490        s += 'Fixed Variables:\n'
1491        for v in fixedVarList:
1492            s += '    ' + v + '\n'
1493    if not inputOnly:
1494        s += 'User-supplied variable mapping relations:\n'
1495    symout = ''
1496    global dependentParmList,arrayList,invarrayList,indParmList,fixedDict,symGenList
1497
1498    for varlist,mapvars,multarr,invmultarr,symFlag in zip(
1499        dependentParmList,indParmList,arrayList,invarrayList,symGenList):
1500        for i,mv in enumerate(mapvars):
1501            if multarr is None:
1502#                s1 = '  ' + str(mv) + ' is equivalent to parameter(s): '
1503                if len(varlist) == 1:
1504                    s1 = '   ' + str(mv) + ' is equivalent to '
1505                else:
1506                    s1 = '   ' + str(mv) + ' is equivalent to parameters: '
1507                j = 0
1508                for v,m in zip(varlist,invmultarr):
1509                    if debug: print ('v,m[0]: ',v,m[0])
1510                    if len(s1.split('\n')[-1]) > 75: s1 += '\n        '
1511                    if j > 0: s1 += ' & '
1512                    j += 1
1513                    s1 += str(v)
1514                    if m != 1:
1515                        s1 += " / " + str(m[0])
1516                if symFlag:
1517                    symout += s1 + '\n'
1518                else:
1519                    s += s1 + '\n'
1520                continue
1521            s += %s = ' % mv
1522            j = 0
1523            for m,v in zip(multarr[i,:],varlist):
1524                if m == 0: continue
1525                if j > 0: s += ' + '
1526                j += 1
1527                s += '(%s * %s)' % (m,v)
1528            if mv in varyList: s += ' VARY'
1529            s += '\n'
1530    if symout:
1531        s += 'Symmetry-generated relations:\n' + symout
1532    if inputOnly: return s
1533    s += 'Inverse variable mapping relations:\n'
1534    for varlist,mapvars,invmultarr in zip(dependentParmList,indParmList,invarrayList):
1535        for i,mv in enumerate(varlist):
1536            s += %s = ' % mv
1537            j = 0
1538            for m,v in zip(invmultarr[i,:],mapvars):
1539                if m == 0: continue
1540                if j > 0: s += ' + '
1541                j += 1
1542                s += '(%s * %s)' % (m,v)
1543            s += '\n'
1544    return s
1545
1546def GetSymEquiv():
1547    '''Return the automatically generated (equivalence) relationships.
1548
1549    :returns: a list of strings containing the details of the contraint relationships
1550    '''
1551    symout = []
1552    global dependentParmList,arrayList,invarrayList,indParmList,fixedDict,symGenList
1553
1554    for varlist,mapvars,multarr,invmultarr,symFlag in zip(
1555        dependentParmList,indParmList,arrayList,invarrayList,symGenList):
1556        for i,mv in enumerate(mapvars):
1557            if not symFlag: continue
1558            if multarr is None:
1559                #s1 = str(mv) + ' = '
1560                s1 = ''
1561                s2 = ' = ' + str(mv)
1562                j = 0
1563                for v,m in zip(varlist,invmultarr):
1564                    if debug: print ('v,m[0]: ',v,m[0])
1565                    if len(s1.split('\n')[-1]) > 75: s1 += '\n        '
1566                    if j > 0: s1 += ' =  '
1567                    j += 1
1568                    s1 += str(v)
1569                    if m != 1:
1570                        s1 += " / " + str(m[0])
1571                    symout.append(s1+s2)
1572                continue
1573            else:
1574                s = %s = ' % mv
1575                j = 0
1576                for m,v in zip(multarr[i,:],varlist):
1577                    if m == 0: continue
1578                    if j > 0: s += ' + '
1579                    j += 1
1580                    s += '(%s * %s)' % (m,v)
1581                print ('unexpected sym op='+s)
1582    return symout
1583
1584def Dict2Deriv(varyList,derivDict,dMdv):
1585    '''Compute derivatives for Independent Parameters from the
1586    derivatives for the original parameters
1587
1588    :param list varyList: a list of parameters names that will be varied
1589
1590    :param dict derivDict: a dict containing derivatives for parameter values keyed by the
1591      parameter names.
1592
1593    :param list dMdv: a Jacobian, as a list of np.array containing derivatives for dependent
1594      parameter computed from derivDict
1595
1596    '''
1597    global dependentParmList,arrayList,invarrayList,indParmList,invarrayList
1598    for varlist,mapvars,multarr,invmultarr in zip(dependentParmList,indParmList,arrayList,invarrayList):
1599        for i,name in enumerate(mapvars):
1600            # grouped variables: need to add in the derv. w/r
1601            # dependent variables to the independent ones
1602            if name not in varyList: continue # skip if independent var not varied
1603            if multarr is None:
1604                for v,m in zip(varlist,invmultarr):
1605                    if debug: print ('start dMdv',dMdv[varyList.index(name)])
1606                    if debug: print ('add derv',v,'/',m[0],'to derv',name,'add=',derivDict[v] / m[0])
1607                    if m == 0: continue
1608                    dMdv[varyList.index(name)] += derivDict[v] / m[0]
1609            else:
1610                for v,m in zip(varlist,multarr[i,:]):
1611                    if debug: print ('start dMdv',dMdv[varyList.index(name)])
1612                    if debug: print ('add derv',v,'*',m,'to derv',name,'add=',m * derivDict[v])
1613                    if m == 0: continue
1614                    dMdv[varyList.index(name)] += m * derivDict[v]
1615
1616def Map2Dict(parmDict,varyList):
1617    '''Create (or update) the Independent Parameters from the original
1618    set of Parameters
1619
1620    Removes dependent variables from the varyList
1621
1622    This should be done once, after the constraints have been
1623    defined using :func:`StoreEquivalence`,
1624    :func:`GroupConstraints` and :func:`GenerateConstraints` and
1625    before any variable refinement is done. This completes the parameter
1626    dictionary by defining independent parameters and it satisfies the
1627    constraint equations in the initial parameters
1628
1629    :param dict parmDict: a dict containing parameter values keyed by the
1630      parameter names.
1631      This will contain updated values for both dependent and independent
1632      parameters after Dict2Map is called. It will also contain some
1633      unexpected entries of every constant value {'0':0.0} & {'1.0':1.0},
1634      which do not cause any problems.
1635
1636    :param list varyList: a list of parameters names that will be varied
1637   
1638
1639    '''
1640    # process the Independent vars: remove dependent ones from varylist
1641    # and then compute values for the Independent ones from their dependents
1642    global dependentParmList,arrayList,invarrayList,indParmList,fixedDict
1643    for varlist,mapvars,multarr in zip(dependentParmList,indParmList,arrayList):
1644        for item in varlist:
1645            try:
1646                varyList.remove(item)
1647            except ValueError:
1648                pass
1649        if multarr is None: continue
1650        valuelist = [parmDict[var] for var in varlist]
1651        parmDict.update(zip(mapvars,
1652                            np.dot(multarr,np.array(valuelist)))
1653                        )
1654    # now remove fixed variables from the varyList
1655    global fixedVarList
1656    for item in fixedVarList:
1657        try:
1658            varyList.remove(item)
1659        except ValueError:
1660            pass
1661    # Set constrained parameters to their fixed values
1662    parmDict.update(fixedDict)
1663
1664def Dict2Map(parmDict,varyList):
1665    '''Applies the constraints defined using :func:`StoreEquivalence`,
1666    :func:`GroupConstraints` and :func:`GenerateConstraints` by changing
1667    values in a dict containing the parameters. This should be
1668    done before the parameters are used for any computations
1669
1670    :param dict parmDict: a dict containing parameter values keyed by the
1671      parameter names.
1672      This will contain updated values for both dependent and independent
1673      parameters after Dict2Map is called. It will also contain some
1674      unexpected entries of every constant value {'0':0.0} & {'1.0':1.0},
1675      which do not cause any problems.
1676
1677    :param list varyList: a list of parameters names that will be varied
1678   
1679    '''
1680    global dependentParmList,arrayList,invarrayList,indParmList,fixedDict
1681    # reset fixed values (should not be needed, but very quick)
1682    # - this seems to update parmDict with {'0':0.0} & {'1.0':1.0} - probably not what was intended
1683    # not needed, but also not a problem - BHT
1684    parmDict.update(fixedDict)
1685    for varlist,mapvars,invmultarr in zip(dependentParmList,indParmList,invarrayList):
1686        #if invmultarr is None: continue
1687        try: 
1688            valuelist = [parmDict[var] for var in mapvars]
1689        except KeyError:
1690            continue
1691        parmDict.update(zip(varlist,np.dot(invmultarr,np.array(valuelist))))
1692
1693#======================================================================
1694# internal routines follow (these routines are unlikely to be called
1695# from outside the module)
1696
1697def GramSchmidtOrtho(a,nkeep=0):
1698    '''Use the Gram-Schmidt process (http://en.wikipedia.org/wiki/Gram-Schmidt) to
1699    find orthonormal unit vectors relative to first row.
1700
1701    If nkeep is non-zero, the first nkeep rows in the array are not changed
1702   
1703    input:
1704       arrayin: a 2-D non-singular square array
1705    returns:
1706       a orthonormal set of unit vectors as a square array
1707    '''
1708    def proj(a,b):
1709        'Projection operator'
1710        return a*(np.dot(a,b)/np.dot(a,a))
1711    for j in range(nkeep,len(a)):
1712        for i in range(j):
1713            a[j] -= proj(a[i],a[j])
1714        if np.allclose(np.linalg.norm(a[j]),0.0):
1715            raise ConstraintException("Singular input to GramSchmidtOrtho")
1716        a[j] /= np.linalg.norm(a[j])
1717    return a
1718
1719def _FillArray(sel,dict,collist,FillDiagonals=False):
1720    '''Construct a n by n matrix (n = len(collist)
1721    filling in the rows using the relationships defined
1722    in the dictionaries selected by sel
1723
1724    If FillDiagonals is True, diagonal elements in the
1725    array are set to 1.0
1726    '''
1727    n = len(collist)
1728    if FillDiagonals:
1729        arr = np.eye(n)
1730    else:
1731        arr = np.zeros(2*[n,])
1732    # fill the top rows
1733    for i,cnum in enumerate(sel):
1734        for j,var in enumerate(collist):
1735            arr[i,j] = dict[cnum].get(var,0)
1736    return arr
1737
1738def _SwapColumns(i,m,v):
1739    '''Swap columns in matrix m as well as the labels in v
1740    so that element (i,i) is replaced by the first non-zero element in row i after that element
1741
1742    Throws an exception if there are no non-zero elements in that row
1743    '''
1744    for j in range(i+1,len(v)):
1745        if not np.allclose(m[i,j],0):
1746            m[:,(i,j)] = m[:,(j,i)]
1747            v[i],v[j] = v[j],v[i]
1748            return
1749    else:
1750        raise ConstraintException('Singular input')
1751
1752def _RowEchelon(m,arr,collist):
1753    '''Convert the first m rows in Matrix arr to row-echelon form
1754    exchanging columns in the matrix and collist as needed.
1755
1756    throws an exception if the matrix is singular because
1757    the first m rows are not linearly independent
1758    '''
1759    for i in range(m):
1760        if np.allclose(arr[i,i],0):
1761            _SwapColumns(i,arr,collist)
1762        arr[i,:] /= arr[i,i] # normalize row
1763        # subtract current row from subsequent rows to set values to left of diagonal to 0
1764        for j in range(i+1,m):
1765            arr[j,:] -= arr[i,:] * arr[j,i]
1766
1767if __name__ == "__main__":
1768    parmdict = {}
1769    constrDict = [
1770        {'0:12:Scale': 2.0, '0:11:Scale': 1.0, '0:14:Scale': 4.0, '0:13:Scale': 3.0, '0:0:Scale': 0.5},
1771        {'0:0:eA': 0.0},
1772        {'2::C(10,6,1)': 1.0, '1::C(10,6,1)': 1.0},
1773        {'1::C(10,0,1)': 1.0, '2::C(10,0,1)': 1.0},
1774        {'1::AUiso:0': 1.0, '0::AUiso:0': 1.0},
1775        {'0::A0': 0.0}
1776        ]
1777    fixedList = ['5.0', '0', None, None, '1.0', '0']
1778    StoreEquivalence('2::atomx:3',('2::atomy:3', ('2::atomz:3',2,), ))
1779    #StoreEquivalence('1::atomx:3',('2::atomx:3', ('2::atomz:3',2,), )) # error: dependent & independent vars mixed
1780    #StoreEquivalence('1::atomx:3',('2::atomy:3', ('2::atomz:3',2,), )) # error: dependent vars repeated
1781    #StoreEquivalence('0:1:eA',('0:0:eA',)) # error: equiv & fixed
1782    #StoreEquivalence('0:99:Scale',('0:12:Scale',)) # error: equiv & constrained
1783    #StoreEquivalence('0:12:Scale',('0:99:Scale',)) # error: equiv & constrained
1784    varylist = ['2::atomx:3',
1785                '2::C(10,6,1)', '1::C(10,6,1)',
1786                '2::atomy:3', '2::atomz:3',
1787                '0:12:Scale', '0:11:Scale', '0:14:Scale', '0:13:Scale', '0:0:Scale']
1788#    e,w = CheckConstraints([,
1789#                     [{'2:0:Scale': 1.0, '5:0:Scale': 1.0, '10:0:Scale': 1.0, '6:0:Scale': 1.0, '9:0:Scale': 1.0, '8:0:Scale': 1.0,# '3:0:Scale': 1.0, '4:0:Scale': 1.0, '7:0:Scale': 1.0, '1:0:Scale': 1.0, '0:0:Scale': 1.0}],
1790#                     ['1.0'])
1791#    if e: print 'error=',e
1792#    if w: print 'error=',w
1793#    varyList = ['0::A0', '0::AUiso:0', '0::Afrac:1', '0::Afrac:2', '0::Afrac:3', '0::Afrac:4', '0::dAx:5', '0::dAy:5', '0::dAz:5', '0::AUiso:5', ':0:Back;0', ':0:Back;1', ':0:Back;2', ':0:Back;3', ':0:Back;4', ':0:Back;5', ':0:Back;6', ':0:Back;7', ':0:Back;8', ':0:Back;9', ':0:Back;10', ':0:Back;11', ':0:U', ':0:V', ':0:W', ':0:X', ':0:Y', ':0:Scale', ':0:DisplaceX', ':0:DisplaceY']
1794#    constrDict = [
1795#        {'0::Afrac:4': 24.0, '0::Afrac:1': 16.0, '0::Afrac:3': 24.0, '0::Afrac:2': 16.0},
1796#        {'0::Afrac:1': 1.0, '0::Afrac:2': 1.0},
1797#        {'0::Afrac:4': 1.0, '0::Afrac:3': 1.0}]
1798#    fixedList = ['40.0', '1.0', '1.0']
1799
1800    errmsg, warnmsg = CheckConstraints(varylist,constrDict,fixedList)
1801    if errmsg:
1802        print ("*** Error ********************")
1803        print (errmsg)
1804    if warnmsg:
1805        print ("*** Warning ********************")
1806        print (warnmsg)
1807    if errmsg or warnmsg:
1808        sys.exit()
1809    groups,parmlist = GroupConstraints(constrDict)
1810    GenerateConstraints(groups,parmlist,varylist,constrDict,fixedList)
1811    print (VarRemapShow(varylist))
1812    parmdict.update( {
1813        '0:12:Scale': 1.0, '0:11:Scale': 1.0, '0:14:Scale': 1.0, '0:13:Scale': 1.0, '0:0:Scale': 2.0,
1814        '0:0:eA': 0.0,
1815        '2::C(10,6,1)': 0.2, '1::C(10,6,1)': 0.3,
1816        '1::C(10,0,1)': 0.2, '2::C(10,0,1)': 0.3,
1817        '1::AUiso:0': 0.02, '0::AUiso:0': 0.03,
1818        '0::A0': 0.0,
1819        '2::atomx:3':0.23,'2::atomy:3':-.23, '2::atomz:3':-0.11,
1820        })
1821    print ('parmdict start',parmdict)
1822    print ('varylist start',varylist)
1823    before = parmdict.copy()
1824    Map2Dict(parmdict,varylist)
1825    print ('parmdict before and after Map2Dict')
1826    print ('  key / before / after')
1827    for key in sorted(list(parmdict.keys())):
1828        print ('  '+key,'\t',before.get(key),'\t',parmdict[key])
1829    print ('varylist after',varylist)
1830    before = parmdict.copy()
1831    Dict2Map(parmdict,varylist)
1832    print ('after Dict2Map')
1833    print ('  key / before / after')
1834    for key in sorted(list(parmdict.keys())):
1835        print ('  '+key,'\t',before.get(key),'\t',parmdict[key])
1836#    dMdv = len(varylist)*[0]
1837#    deriv = {}
1838#    for i,v in enumerate(parmdict.keys()): deriv[v]=i
1839#    Dict2Deriv(varylist,deriv,dMdv)
Note: See TracBrowser for help on using the repository browser.