source: trunk/GSASIIscriptable.py @ 3136

Last change on this file since 3136 was 3136, checked in by vondreele, 4 years ago

make GSAS-II python 3.6 compliant & preserve python 2.7 use;changes:
do from future import division, print_function for all GSAS-II py sources
all menu items revised to be py 2.7/3.6 compliant
all wx.OPEN --> wx.FD_OPEN in file dialogs
all integer divides (typically for image pixel math) made explicit with ; ambiguous ones made floats as appropriate
all print "stuff" --> print (stuff)
all print >> pFile,'stuff' --> pFile.writeCIFtemplate('stuff')
all read file opens made explicit 'r' or 'rb'
all cPickle imports made for py2.7 or 3.6 as cPickle or _pickle; test for '2' platform.version_tuple[0] for py 2.7
define cPickleload to select load(fp) or load(fp,encoding='latin-1') for loading gpx files; provides cross compatibility between py 2.7/3.6 gpx files
make dict.keys() as explicit list(dict.keys()) as needed (NB: possible source of remaining py3.6 bugs)
make zip(a,b) as explicit list(zip(a,b)) as needed (NB: possible source of remaining py3.6 bugs)
select unichr/chr according test for '2' platform.version_tuple[0] for py 2.7 (G2pwdGUI * G2plot) for special characters
select wg.EVT_GRID_CELL_CHANGE (classic) or wg.EVT_GRID_CELL_CHANGED (phoenix) in grid Bind
maxint --> maxsize; used in random number stuff
raise Exception,"stuff" --> raise Exception("stuff")
wx 'classic' sizer.DeleteWindows?() or 'phoenix' sizer.Clear(True)
wx 'classic' SetToolTipString?(text) or 'phoenix' SetToolTip?(wx.ToolTip?(text)); define SetToolTipString?(self,text) to handle the choice in plots
status.SetFields? --> status.SetStatusText?
'classic' AddSimpleTool? or 'phoenix' self.AddTool? for plot toolbar; Bind different as well
define GetItemPydata? as it doesn't exist in wx 'phoenix'
allow python versions 2.7 & 3.6 to run GSAS-II
Bind override commented out - no logging capability (NB: remove all logging code?)
all import ContentsValidator? open filename & test if valid then close; filepointer removed from Reader
binary importers (mostly images) test for 'byte' type & convert as needed to satisfy py 3.6 str/byte rules

File size: 100.5 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3########### SVN repository information ###################
4# $Date: 2017-04-12 15:12:45 -0500 (Wed, 12 Apr 2017) $
5# $Author: vondreele $
6# $Revision: 2777 $
7# $URL: https://subversion.xray.aps.anl.gov/pyGSAS/trunk/GSASIIscriptable.py $
8# $Id: GSASIIIO.py 2777 2017-04-12 20:12:45Z vondreele $
9########### SVN repository information ###################
10"""
11*GSASIIscriptable: Scripting Tools*
12======================================
13
14Routines for reading, writing, modifying and creating GSAS-II project (.gpx) files.
15This file specifies several wrapper classes around GSAS-II data representations.
16They all inherit from :class:`G2ObjectWrapper`. The chief class is :class:`G2Project`,
17which represents an entire GSAS-II project and provides several methods to access
18phases, powder histograms, and execute Rietveld refinements.
19These routines can be accessed either by directly calling these routines
20or via a command line interface. Run::
21
22   python GSASIIscriptable.py --help
23
24to show the available subcommands, and inspect each subcommand with
25`python GSASIIscriptable.py <subcommand> --help`.
26
27
28.. _Refinement_parameters_kinds:
29
30=====================
31Refinement parameters
32=====================
33
34Note that parameters and refinement flags used in GSAS-II fall into three classes:
35
36    * **Histogram**: There will be a set of these for each dataset loaded into a
37      project file. The parameters available depend on the type of histogram
38      (Bragg-Brentano, Single-Crystal, TOF,...). Typical Histogram parameters
39      include the overall scale factor, background, instrument and sample parameters;
40      see the :ref:`Histogram_parameters_table` table for a list of the histogram
41      parameters where access has been provided.
42     
43    * **Phase**: There will be a set of these for each phase loaded into a
44      project file. While some parameters are found in all types of phases,
45      others are only found in certain types (modulated, magnetic, protein...).
46      Typical phase parameters include unit cell lengths and atomic positions; see the
47      :ref:`Phase_parameters_table` table for a list of the phase     
48      parameters where access has been provided.
49     
50    * **Histogram-and-phase** (HAP): There is a set of these for every histogram
51      that is associated with each phase, so that if there are ``N`` phases and ``M``
52      histograms, there can be ``N*M`` total sets of "HAP" parameters sets (fewer if all
53      histograms are not linked to all phases.) Typical HAP parameters include the
54      phase fractions, sample microstrain and crystallite size broadening terms,
55      hydrostatic strain pertibations of the unit cell and preferred orientation
56      values.
57      See the :ref:`HAP_parameters_table` table for the HAP parameters where access has
58      been provided.
59
60There are several ways to set parameters using different objects, as described below,
61
62------------------------
63Histogram/Phase objects
64------------------------
65Each phase and powder histogram in a :class:`G2Project` object has an associated
66object. Parameters within each individual object can be turned on and off by calling
67:meth:`G2PwdrData.set_refinements` or :meth:`G2PwdrData.clear_refinements`
68for histogram parameters;
69:meth:`G2Phase.set_refinements` or :meth:`G2Phase.clear_refinements`
70for phase parameters; and :meth:`G2Phase.set_HAP_refinements` or
71:meth:`G2Phase.clear_HAP_refinements`. As an example, if some_histogram is a histogram object (of type :class:`G2PwdrData`), use this to set parameters in that histogram:
72
73.. code-block::  python
74
75    params = { 'Limits': [0.8, 12.0],
76               'Sample Parameters': ['Absorption', 'Contrast', 'DisplaceX'],
77               'Background': {'type': 'chebyschev', 'refine': True}}
78    some_histogram.set_refinements(params)
79
80Likewise to turn refinement flags on, use code such as this:
81
82.. code-block::  python
83
84    params = { 'Instrument Parameters': ['U', 'V', 'W']}
85    some_histogram.set_refinements(params)
86
87and to turn these refinement flags, off use this (Note that the
88``.clear_refinements()`` methods will usually will turn off refinement even
89if a refinement parameter is set in the dict to True.):
90
91.. code-block::  python
92
93    params = { 'Instrument Parameters': ['U', 'V', 'W']}
94    some_histogram.clear_refinements(params)
95
96For phase parameters, use code such as this:
97   
98.. code-block::  python
99
100    params = { 'LeBail': True, 'Cell': True,
101               'Atoms': { 'Mn1': 'X',
102                          'O3': 'XU',
103                          'V4': 'FXU'}}
104    some_histogram.set_refinements(params)
105
106
107and here is an example for HAP parameters:
108
109.. code-block::  python
110
111    params = { 'Babinet': 'BabA',
112               'Extinction': True,
113               'Mustrain': { 'type': 'uniaxial',
114                             'direction': [0, 0, 1],
115                             'refine': True}}
116    some_phase.set_HAP_refinements(params)
117
118Note that the parameters must match the object type and method (phase vs. histogram vs. HAP).
119
120------------------------
121Project objects
122------------------------
123It is also possible to create a composite dictionary containing parameters of any type.
124In this case dictionaries are nested with keys at the outer level of "set" and "clear"
125to specify which function is used with function :meth:`G2Project.set_refinement`. Note
126that optionally a list of histograms and/or phases can be supplied to
127:meth:`G2Project.set_refinement`, where the default is to use all phases and histograms.
128As an example:
129
130.. code-block::  python
131
132    pardict = {'set': { 'Limits': [0.8, 12.0],
133                       'Sample Parameters': ['Absorption', 'Contrast', 'DisplaceX'],
134                       'Background': {'type': 'chebyschev', 'refine': True}},
135              'clear': {'Instrument Parameters': ['U', 'V', 'W']}}
136    my_project.set_refinement(pardict)
137
138------------------------
139Refinement recipe
140------------------------
141Finally, it is possible to specify a sequence of refinement actions as a list of dicts.
142Note in the following example, the list contains a set of dicts, each defined as before.
143It is not possible to specify different actions for differing phases or histograms
144with this method. Note that a
145separate refinement step will be performed for each element in the list.
146An example follows:
147
148.. code-block::  python
149
150    reflist = [
151            {"set": { "Limits": { "low": 0.7 },
152                      "Background": { "no. coeffs": 3,
153                                      "refine": True }}},
154            {"set": { "LeBail": True,
155                      "Cell": True }},
156            {"set": { "Sample Parameters": ["DisplaceX"]}},
157            {"set": { "Instrument Parameters": ["U", "V", "W", "X", "Y"]}},
158            {"set": { "Mustrain": { "type": "uniaxial",
159                                    "refine": "equatorial",
160                                    "direction": [0, 0, 1]}}},
161            {"set": { "Mustrain": { "type": "uniaxial",
162                                    "refine": "axial"}}},
163            {"clear": { "LeBail": True},
164             "set": { "Atoms": { "Mn": "X" }}},
165            {"set": { "Atoms": { "O1": "X", "O2": "X" }}},]
166    my_project.do_refinements(reflist)
167   
168Note that in the second from last refinement step (``reflist[6]``), parameters are both set and cleared. To perform a single refinement without changing any parameters, use this
169call:
170
171.. code-block::  python
172
173    my_project.do_refinements([])
174
175
176
177============================
178Refinement specifiers format
179============================
180
181Refinement parameters are specified as dictionaries, supplied to any of the functions
182named in :ref:`Refinement_parameters_kinds`. Each method accepts a different set
183of keys, described below for each of the three parameter classes.
184
185.. _Histogram_parameters_table:
186
187--------------------
188Histogram parameters
189--------------------
190
191This table describes the dictionaries supplied to :func:`G2PwdrData.set_refinements`
192and :func:`G2PwdrData.clear_refinements`.
193
194.. tabularcolumns:: |l|l|p{3.5in}|
195
196===================== ====================  =================================================
197key                   subkey                explanation
198===================== ====================  =================================================
199Limits                                      The 2-theta range of values to consider. Can
200                                            be either a dictionary of 'low' and/or 'high',
201                                            or a list of 2 items [low, high]
202\                     low                   Sets the low limit
203\                     high                  Sets the high limit
204Sample Parameters                           Should be provided as a **list** of subkeys
205                                            to set or clear, e.g. ['DisplaceX', 'Scale']
206\                     Absorption
207\                     Contrast
208\                     DisplaceX             Sample displacement along the X direction
209\                     DisplaceY             Sample displacement along the Y direction
210\                     Scale                 Histogram Scale factor
211Background                                  Sample background. If value is a boolean,
212                                            the background's 'refine' parameter is set
213                                            to the given boolean. Usually should be a
214                                            dictionary with any of the following keys:
215\                     type                  The background model, e.g. 'chebyschev'
216\                     refine                The value of the refine flag, boolean
217\                     no. coeffs            Number of coefficients to use, integer
218\                     coeffs                List of floats, literal values for background
219\                     FixedPoints           List of (2-theta, intensity) values for fixed points
220\                     fit fixed points      If True, triggers a fit to the fixed points to be calculated. It is calculated when this key is detected, regardless of calls to refine.
221Instrument Parameters                       As in Sample Paramters, Should be provided as a **list** of subkeys to
222                                            set or clear, e.g. ['X', 'Y', 'Zero', 'SH/L']
223\                     U, V, W               All separate keys. Gaussian peak profile terms
224\                     X, Y                  Separate keys. Lorentzian peak profile terms
225\                     Zero                  Zero shift
226\                     SH/L
227\                     Polariz.              Polarization parameter
228\                     Lam                   Lambda, the incident wavelength
229===================== ====================  =================================================
230
231.. _Phase_parameters_table:
232
233----------------
234Phase parameters
235----------------
236
237This table describes the dictionaries supplied to :func:`G2Phase.set_refinements`
238and :func:`G2Phase.clear_refinements`.
239
240.. tabularcolumns:: |l|p{4.5in}|
241
242===================== ============================================
243key                   explanation
244===================== ============================================
245Cell                  Whether or not to refine the unit cell.
246Atoms                 Dictionary of atoms and refinement flags.
247                      Each key should be an atom label, e.g.
248                      'O3', 'Mn5', and each value should be
249                      a string defining what values to refine.
250                      Values can be any combination of 'F'
251                      for fractional occupancy, 'X' for position,
252                      and 'U' for Debye-Waller factor
253LeBail                Enables LeBail intensity extraction.
254===================== ============================================
255
256.. _HAP_parameters_table:
257
258------------------------------
259Histogram-and-phase parameters
260------------------------------
261
262This table describes the dictionaries supplied to :func:`G2Phase.set_HAP_refinements`
263and :func:`G2Phase.clear_HAP_refinements`.
264
265.. tabularcolumns:: |l|l|p{3.5in}|
266
267===================== ====================  =================================================
268key                   subkey                 explanation
269===================== ====================  =================================================
270Babinet                                      Should be a **list** of the following
271                                             subkeys. If not, assumes both
272                                             BabA and BabU
273\                     BabA
274\                     BabU
275Extinction                                   Boolean, True to refine.
276HStrain                                      Boolean, True to refine all appropriate
277                                             $D_ij$ terms.
278Mustrain
279\                     type                   Mustrain model. One of 'isotropic',
280                                             'uniaxial', or 'generalized'. Should always
281                                             be specified.
282\                     direction              For uniaxial only. A list of three
283                                             integers,
284                                             the [hkl] direction of the axis.
285\                     refine                 Usually boolean, set to True to refine.
286                                             When in doubt, set it to true.
287                                             For uniaxial model, can specify list
288                                             of 'axial' or 'equatorial' or a single
289                                             boolean sets both axial and equatorial.
290Size                                         Not implemented
291\                     type                   Size broadening model. One of 'isotropic',
292                                             'uniaxial', or 'ellipsoid'. Should always
293                                             be specified.
294\                     direction              For uniaxial only. A list of three
295                                             integers,
296                                             the [hkl] direction of the axis.
297\                     refine                 A boolean, True to refine.
298Pref.Ori.                                    Boolean, True to refine
299Show                                         Boolean, True to refine
300Use                                          Boolean, True to refine
301Scale                                        Phase fraction; Boolean, True to refine
302===================== ====================  =================================================
303
304
305============================
306Scriptable API
307============================
308"""
309from __future__ import division, print_function # needed?
310import argparse
311import os.path as ospath
312import datetime as dt
313import sys
314import cPickle
315import imp
316import copy
317import os
318import random as ran
319
320import numpy.ma as ma
321import scipy.interpolate as si
322import numpy as np
323import scipy as sp
324
325import GSASIIpath
326GSASIIpath.SetBinaryPath(True)  # for now, this is needed before some of these modules can be imported
327import GSASIIobj as G2obj
328import GSASIIpwd as G2pwd
329import GSASIIstrMain as G2strMain
330import GSASIIspc as G2spc
331import GSASIIElem as G2elem
332
333# Delay imports to not slow down small scripts
334G2fil = None
335PwdrDataReaders = []
336PhaseReaders = []
337
338def LoadG2fil():
339    """Delay importing this module, it is slow"""
340    global G2fil
341    global PwdrDataReaders
342    global PhaseReaders
343    if G2fil is None:
344        import GSASIIfiles
345        G2fil = GSASIIfiles
346        PwdrDataReaders = G2fil.LoadImportRoutines("pwd", "Powder_Data")
347        PhaseReaders = G2fil.LoadImportRoutines("phase", "Phase")
348
349
350def LoadDictFromProjFile(ProjFile):
351    '''Read a GSAS-II project file and load items to dictionary
352   
353    :param str ProjFile: GSAS-II project (name.gpx) full file name
354    :returns: Project,nameList, where
355
356      * Project (dict) is a representation of gpx file following the GSAS-II tree structure
357        for each item: key = tree name (e.g. 'Controls','Restraints',etc.), data is dict
358        data dict = {'data':item data whch may be list, dict or None,'subitems':subdata (if any)}
359      * nameList (list) has names of main tree entries & subentries used to reconstruct project file
360
361    Example for fap.gpx::
362
363      Project = {                 #NB:dict order is not tree order
364        u'Phases':{'data':None,'fap':{phase dict}},
365        u'PWDR FAP.XRA Bank 1':{'data':[histogram data list],'Comments':comments,'Limits':limits, etc},
366        u'Rigid bodies':{'data': {rigid body dict}},
367        u'Covariance':{'data':{covariance data dict}},
368        u'Controls':{'data':{controls data dict}},
369        u'Notebook':{'data':[notebook list]},
370        u'Restraints':{'data':{restraint data dict}},
371        u'Constraints':{'data':{constraint data dict}}]
372        }
373      nameList = [                #NB: reproduces tree order
374        [u'Notebook',],
375        [u'Controls',],
376        [u'Covariance',],
377        [u'Constraints',],
378        [u'Restraints',],
379        [u'Rigid bodies',],
380        [u'PWDR FAP.XRA Bank 1',
381             u'Comments',
382             u'Limits',
383             u'Background',
384             u'Instrument Parameters',
385             u'Sample Parameters',
386             u'Peak List',
387             u'Index Peak List',
388             u'Unit Cells List',
389             u'Reflection Lists'],
390        [u'Phases', u'fap']
391        ]
392    '''
393    # Let IOError be thrown if file does not exist
394    # if not ospath.exists(ProjFile):
395    #     print ('\n*** Error attempt to open project file that does not exist:\n   '+
396    #         str(ProjFile))
397    #     return
398    file = open(ProjFile,'rb')
399    # print('loading from file: {}'.format(ProjFile))
400    Project = {}
401    nameList = []
402    try:
403        while True:
404            try:
405                data = cPickle.load(file)
406            except EOFError:
407                break
408            datum = data[0]
409            Project[datum[0]] = {'data':datum[1]}
410            nameList.append([datum[0],])
411            for datus in data[1:]:
412                Project[datum[0]][datus[0]] = datus[1]
413                nameList[-1].append(datus[0])
414        file.close()
415        # print('project load successful')
416    except:
417        raise IOError("Error reading file "+str(ProjFile)+". This is not a GSAS-II .gpx file")
418    finally:
419        file.close()
420    return Project,nameList
421
422def SaveDictToProjFile(Project,nameList,ProjFile):
423    '''Save a GSAS-II project file from dictionary/nameList created by LoadDictFromProjFile
424
425    :param dict Project: representation of gpx file following the GSAS-II
426        tree structure as described for LoadDictFromProjFile
427    :param list nameList: names of main tree entries & subentries used to reconstruct project file
428    :param str ProjFile: full file name for output project.gpx file (including extension)
429    '''
430    file = open(ProjFile,'wb')
431    try:
432        for name in nameList:
433            data = []
434            item = Project[name[0]]
435            data.append([name[0],item['data']])
436            for item2 in name[1:]:
437                data.append([item2,item[item2]])
438            cPickle.dump(data,file,1)
439    finally:
440        file.close()
441
442def ImportPowder(reader,filename):
443    '''Use a reader to import a powder diffraction data file
444
445    :param str reader: a scriptable reader
446    :param str filename: full name of powder data file; can be "multi-Bank" data
447
448    :returns: list rdlist: list of reader objects containing powder data, one for each
449        "Bank" of data encountered in file. Items in reader object of interest are:
450
451          * rd.comments: list of str: comments found on powder file
452          * rd.dnames: list of str: data nammes suitable for use in GSASII data tree NB: duplicated in all rd entries in rdlist
453          * rd.powderdata: list of numpy arrays: pos,int,wt,zeros,zeros,zeros as needed for a PWDR entry in  GSASII data tree.
454    '''
455    rdfile,rdpath,descr = imp.find_module(reader)
456    rdclass = imp.load_module(reader,rdfile,rdpath,descr)
457    rd = rdclass.GSAS_ReaderClass()
458    if not rd.scriptable:
459        print(u'**** ERROR: '+reader+u' is not a scriptable reader')
460        return None
461    rdlist = []
462    if rd.ContentsValidator(filename):
463        repeat = True
464        rdbuffer = {} # create temporary storage for file reader
465        block = 0
466        while repeat: # loop if the reader asks for another pass on the file
467            block += 1
468            repeat = False
469            rd.objname = ospath.basename(filename)
470            flag = rd.Reader(filename,None,buffer=rdbuffer,blocknum=block,)
471            if flag:
472                rdlist.append(copy.deepcopy(rd)) # save the result before it is written over
473                if rd.repeat:
474                    repeat = True
475        return rdlist
476    print(rd.errors)
477    return None
478
479def SetDefaultDData(dType,histoName,NShkl=0,NDij=0):
480    '''Create an initial Histogram dictionary
481
482    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
483    '''
484    if dType in ['SXC','SNC']:
485        return {'Histogram':histoName,'Show':False,'Scale':[1.0,True],
486            'Babinet':{'BabA':[0.0,False],'BabU':[0.0,False]},
487            'Extinction':['Lorentzian','None', {'Tbar':0.1,'Cos2TM':0.955,
488            'Eg':[1.e-10,False],'Es':[1.e-10,False],'Ep':[1.e-10,False]}],
489            'Flack':[0.0,False]}
490    elif dType == 'SNT':
491        return {'Histogram':histoName,'Show':False,'Scale':[1.0,True],
492            'Babinet':{'BabA':[0.0,False],'BabU':[0.0,False]},
493            'Extinction':['Lorentzian','None', {
494            'Eg':[1.e-10,False],'Es':[1.e-10,False],'Ep':[1.e-10,False]}]}
495    elif 'P' in dType:
496        return {'Histogram':histoName,'Show':False,'Scale':[1.0,False],
497            'Pref.Ori.':['MD',1.0,False,[0,0,1],0,{},[],0.1],
498            'Size':['isotropic',[1.,1.,1.],[False,False,False],[0,0,1],
499                [1.,1.,1.,0.,0.,0.],6*[False,]],
500            'Mustrain':['isotropic',[1000.0,1000.0,1.0],[False,False,False],[0,0,1],
501                NShkl*[0.01,],NShkl*[False,]],
502            'HStrain':[NDij*[0.0,],NDij*[False,]],
503            'Extinction':[0.0,False],'Babinet':{'BabA':[0.0,False],'BabU':[0.0,False]}}
504
505
506def PreSetup(data):
507    '''Create part of an initial (empty) phase dictionary
508
509    from GSASIIphsGUI.py, near end of UpdatePhaseData
510
511    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
512    '''
513    if 'RBModels' not in data:
514        data['RBModels'] = {}
515    if 'MCSA' not in data:
516        data['MCSA'] = {'Models':[{'Type':'MD','Coef':[1.0,False,[.8,1.2],],'axis':[0,0,1]}],'Results':[],'AtInfo':{}}
517    if 'dict' in str(type(data['MCSA']['Results'])):
518        data['MCSA']['Results'] = []
519    if 'Modulated' not in data['General']:
520        data['General']['Modulated'] = False
521    if 'modulated' in data['General']['Type']:
522        data['General']['Modulated'] = True
523        data['General']['Type'] = 'nuclear'
524
525
526def SetupGeneral(data, dirname):
527    """Helps initialize phase data.
528
529    From GSASIIphsGui.py, function of the same name. Minor changes for imports etc.
530
531    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
532    """
533    mapDefault = {'MapType':'','RefList':'','Resolution':0.5,'Show bonds':True,
534                'rho':[],'rhoMax':0.,'mapSize':10.0,'cutOff':50.,'Flip':False}
535    generalData = data['General']
536    atomData = data['Atoms']
537    generalData['AtomTypes'] = []
538    generalData['Isotopes'] = {}
539
540    if 'Isotope' not in generalData:
541        generalData['Isotope'] = {}
542    if 'Data plot type' not in generalData:
543        generalData['Data plot type'] = 'Mustrain'
544    if 'POhkl' not in generalData:
545        generalData['POhkl'] = [0,0,1]
546    if 'Map' not in generalData:
547        generalData['Map'] = mapDefault.copy()
548    if 'Flip' not in generalData:
549        generalData['Flip'] = {'RefList':'','Resolution':0.5,'Norm element':'None',
550            'k-factor':0.1,'k-Max':20.,}
551    if 'testHKL' not in generalData['Flip']:
552        generalData['Flip']['testHKL'] = [[0,0,2],[2,0,0],[1,1,1],[0,2,0],[1,2,3]]
553    if 'doPawley' not in generalData:
554        generalData['doPawley'] = False     #ToDo: change to ''
555    if 'Pawley dmin' not in generalData:
556        generalData['Pawley dmin'] = 1.0
557    if 'Pawley neg wt' not in generalData:
558        generalData['Pawley neg wt'] = 0.0
559    if 'Algolrithm' in generalData.get('MCSA controls',{}) or \
560        'MCSA controls' not in generalData:
561        generalData['MCSA controls'] = {'Data source':'','Annealing':[50.,0.001,50],
562        'dmin':2.0,'Algorithm':'log','Jump coeff':[0.95,0.5],'boltzmann':1.0,
563        'fast parms':[1.0,1.0,1.0],'log slope':0.9,'Cycles':1,'Results':[],'newDmin':True}
564    if 'AtomPtrs' not in generalData:
565        generalData['AtomPtrs'] = [3,1,7,9]
566        if generalData['Type'] == 'macromolecular':
567            generalData['AtomPtrs'] = [6,4,10,12]
568        elif generalData['Type'] == 'magnetic':
569            generalData['AtomPtrs'] = [3,1,10,12]
570    if generalData['Type'] in ['modulated',]:
571        generalData['Modulated'] = True
572        generalData['Type'] = 'nuclear'
573        if 'Super' not in generalData:
574            generalData['Super'] = 1
575            generalData['SuperVec'] = [[0,0,.1],False,4]
576            generalData['SSGData'] = {}
577        if '4DmapData' not in generalData:
578            generalData['4DmapData'] = mapDefault.copy()
579            generalData['4DmapData'].update({'MapType':'Fobs'})
580    if 'Modulated' not in generalData:
581        generalData['Modulated'] = False
582    if 'HydIds' not in generalData:
583        generalData['HydIds'] = {}
584    cx,ct,cs,cia = generalData['AtomPtrs']
585    generalData['NoAtoms'] = {}
586    generalData['BondRadii'] = []
587    generalData['AngleRadii'] = []
588    generalData['vdWRadii'] = []
589    generalData['AtomMass'] = []
590    generalData['Color'] = []
591    if generalData['Type'] == 'magnetic':
592        generalData['MagDmin'] = generalData.get('MagDmin',1.0)
593        landeg = generalData.get('Lande g',[])
594    generalData['Mydir'] = dirname
595    badList = {}
596    for iat,atom in enumerate(atomData):
597        atom[ct] = atom[ct].lower().capitalize()              #force to standard form
598        if generalData['AtomTypes'].count(atom[ct]):
599            generalData['NoAtoms'][atom[ct]] += atom[cx+3]*float(atom[cs+1])
600        elif atom[ct] != 'UNK':
601            Info = G2elem.GetAtomInfo(atom[ct])
602            if not Info:
603                if atom[ct] not in badList:
604                    badList[atom[ct]] = 0
605                badList[atom[ct]] += 1
606                atom[ct] = 'UNK'
607                continue
608            atom[ct] = Info['Symbol'] # N.B. symbol might be changed by GetAtomInfo
609            generalData['AtomTypes'].append(atom[ct])
610            generalData['Z'] = Info['Z']
611            generalData['Isotopes'][atom[ct]] = Info['Isotopes']
612            generalData['BondRadii'].append(Info['Drad'])
613            generalData['AngleRadii'].append(Info['Arad'])
614            generalData['vdWRadii'].append(Info['Vdrad'])
615            if atom[ct] in generalData['Isotope']:
616                if generalData['Isotope'][atom[ct]] not in generalData['Isotopes'][atom[ct]]:
617                    isotope = list(generalData['Isotopes'][atom[ct]].keys())[-1]
618                    generalData['Isotope'][atom[ct]] = isotope
619                generalData['AtomMass'].append(Info['Isotopes'][generalData['Isotope'][atom[ct]]]['Mass'])
620            else:
621                generalData['Isotope'][atom[ct]] = 'Nat. Abund.'
622                if 'Nat. Abund.' not in generalData['Isotopes'][atom[ct]]:
623                    isotope = list(generalData['Isotopes'][atom[ct]].keys())[-1]
624                    generalData['Isotope'][atom[ct]] = isotope
625                generalData['AtomMass'].append(Info['Mass'])
626            generalData['NoAtoms'][atom[ct]] = atom[cx+3]*float(atom[cs+1])
627            generalData['Color'].append(Info['Color'])
628            if generalData['Type'] == 'magnetic':
629                if len(landeg) < len(generalData['AtomTypes']):
630                    landeg.append(2.0)
631    if generalData['Type'] == 'magnetic':
632        generalData['Lande g'] = landeg[:len(generalData['AtomTypes'])]
633
634    if badList:
635        msg = 'Warning: element symbol(s) not found:'
636        for key in badList:
637            msg += '\n\t' + key
638            if badList[key] > 1:
639                msg += ' (' + str(badList[key]) + ' times)'
640        raise Exception("Phase error:\n" + msg)
641        # wx.MessageBox(msg,caption='Element symbol error')
642    F000X = 0.
643    F000N = 0.
644    for i,elem in enumerate(generalData['AtomTypes']):
645        F000X += generalData['NoAtoms'][elem]*generalData['Z']
646        isotope = generalData['Isotope'][elem]
647        F000N += generalData['NoAtoms'][elem]*generalData['Isotopes'][elem][isotope]['SL'][0]
648    generalData['F000X'] = F000X
649    generalData['F000N'] = F000N
650    import GSASIImath as G2mth
651    generalData['Mass'] = G2mth.getMass(generalData)
652
653
654def make_empty_project(author=None, filename=None):
655    """Creates an dictionary in the style of GSASIIscriptable, for an empty
656    project.
657
658    If no author name or filename is supplied, 'no name' and
659    <current dir>/test_output.gpx are used , respectively.
660
661    Returns: project dictionary, name list
662
663    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
664    """
665    if not filename:
666        filename = os.path.join(os.getcwd(), 'test_output.gpx')
667    else:
668        filename = os.path.abspath(filename)
669    gsasii_version = str(GSASIIpath.GetVersionNumber())
670    LoadG2fil()
671    import matplotlib as mpl
672    python_library_versions = G2fil.get_python_versions([mpl, np, sp])
673
674    controls_data = dict(G2obj.DefaultControls)
675    controls_data['LastSavedAs'] = unicode(filename)
676    controls_data['LastSavedUsing'] = gsasii_version
677    controls_data['PythonVersions'] = python_library_versions
678    if author:
679        controls_data['Author'] = author
680
681    output = {'Constraints': {'data': {'HAP': [], 'Hist': [], 'Phase': [],
682                                       'Global': []}},
683              'Controls': {'data': controls_data},
684              u'Covariance': {'data': {}},
685              u'Notebook': {'data': ['']},
686              u'Restraints': {'data': {}},
687              u'Rigid bodies': {'data': {'RBIds': {'Residue': [], 'Vector': []},
688                                'Residue': {'AtInfo': {}},
689                                'Vector':  {'AtInfo': {}}}}}
690
691    names = [[u'Notebook'], [u'Controls'], [u'Covariance'],
692             [u'Constraints'], [u'Restraints'], [u'Rigid bodies']]
693
694    return output, names
695
696
697class G2ImportException(Exception):
698    pass
699
700
701def import_generic(filename, readerlist):
702    """Attempt to import a filename, using a list of reader objects.
703
704    Returns the first reader object which worked."""
705    # Translated from OnImportGeneric method in GSASII.py
706    primaryReaders, secondaryReaders = [], []
707    for reader in readerlist:
708        flag = reader.ExtensionValidator(filename)
709        if flag is None:
710            secondaryReaders.append(reader)
711        elif flag:
712            primaryReaders.append(reader)
713    if not secondaryReaders and not primaryReaders:
714        raise G2ImportException("Could not read file: ", filename)
715
716    with open(filename, 'Ur') as fp:
717        rd_list = []
718
719        for rd in primaryReaders + secondaryReaders:
720            # Initialize reader
721            rd.selections = []
722            rd.dnames = []
723            rd.ReInitialize()
724            # Rewind file
725            rd.errors = ""
726            if not rd.ContentsValidator(filename):
727                # Report error
728                pass
729            if len(rd.selections) > 1:
730                # Select data?
731                # GSASII.py:543
732                raise G2ImportException("Not sure what data to select")
733
734            block = 0
735            rdbuffer = {}
736            repeat = True
737            while repeat:
738                repeat = False
739                block += 1
740                rd.objname = os.path.basename(filename)
741                try:
742                    flag = rd.Reader(filename,buffer=rdbuffer, blocknum=block)
743                except:
744                    flag = False
745                if flag:
746                    # Omitting image loading special cases
747                    rd.readfilename = filename
748                    rd_list.append(copy.deepcopy(rd))
749                    repeat = rd.repeat
750                else:
751                    if GSASIIpath.GetConfigValue('debug'): print("{} Reader failed to read {}".format(rd.formatName,filename))
752            if rd_list:
753                if rd.warnings:
754                    print("Read warning by", rd.formatName, "reader:",
755                          rd.warnings, file=sys.stderr)
756                else:
757                    print("{} read by Reader {}\n".format(filename,rd.formatName))                   
758                return rd_list
759    raise G2ImportException("No reader could read file: " + filename)
760
761
762def load_iprms(instfile, reader):
763    """Loads instrument parameters from a file, and edits the
764    given reader.
765
766    Returns a 2-tuple of (Iparm1, Iparm2) parameters
767    """
768    LoadG2fil()
769    ext = os.path.splitext(instfile)[1]
770
771    if ext.lower() == '.instprm':
772        # New GSAS File, load appropriately
773        with open(instfile) as f:
774            lines = f.readlines()
775        bank = reader.powderentry[2]
776        numbanks = reader.numbanks
777        iparms = G2fil.ReadPowderInstprm(lines, bank, numbanks, reader)
778
779        reader.instfile = instfile
780        reader.instmsg = 'GSAS-II file' + instfile
781        return iparms
782    elif ext.lower() not in ('.prm', '.inst', '.ins'):
783        raise ValueError('Expected .prm file, found: ', instfile)
784
785    # It's an old GSAS file, load appropriately
786    Iparm = {}
787    with open(instfile, 'Ur') as fp:
788        for line in fp:
789            if '#' in line:
790                continue
791            Iparm[line[:12]] = line[12:-1]
792    ibanks = int(Iparm.get('INS   BANK  ', '1').strip())
793    if ibanks == 1:
794        reader.instbank = 1
795        reader.powderentry[2] = 1
796        reader.instfile = instfile
797        reader.instmsg = instfile + ' bank ' + str(reader.instbank)
798        return G2fil.SetPowderInstParms(Iparm, reader)
799    # TODO handle >1 banks
800    raise NotImplementedError("Check GSASIIfiles.py: ReadPowderInstprm")
801
802def load_pwd_from_reader(reader, instprm, existingnames=[]):
803    """Loads powder data from a reader object, and assembles it into a GSASII data tree.
804
805    :returns: (name, tree) - 2-tuple of the histogram name (str), and data
806
807    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
808    """
809    HistName = 'PWDR ' + G2obj.StripUnicode(reader.idstring, '_')
810    HistName = unicode(G2obj.MakeUniqueLabel(HistName, existingnames))
811
812    try:
813        Iparm1, Iparm2 = instprm
814    except ValueError:
815        Iparm1, Iparm2 = load_iprms(instprm, reader)
816
817    Ymin = np.min(reader.powderdata[1])
818    Ymax = np.max(reader.powderdata[1])
819    valuesdict = {'wtFactor': 1.0,
820                  'Dummy': False,
821                  'ranId': ran.randint(0, sys.maxsize),
822                  'Offset': [0.0, 0.0], 'delOffset': 0.02*Ymax,
823                  'refOffset': -0.1*Ymax, 'refDelt': 0.1*Ymax,
824                  'qPlot': False, 'dPlot': False, 'sqrtPlot': False,
825                  'Yminmax': [Ymin, Ymax]}
826    reader.Sample['ranId'] = valuesdict['ranId']
827
828    # Ending keys:
829    # [u'Reflection Lists',
830    #  u'Limits',
831    #  'data',
832    #  u'Index Peak List',
833    #  u'Comments',
834    #  u'Unit Cells List',
835    #  u'Sample Parameters',
836    #  u'Peak List',
837    #  u'Background',
838    #  u'Instrument Parameters']
839    Tmin = np.min(reader.powderdata[0])
840    Tmax = np.max(reader.powderdata[0])
841
842    default_background = [['chebyschev', False, 3, 1.0, 0.0, 0.0],
843                          {'nDebye': 0, 'debyeTerms': [], 'nPeaks': 0, 'peaksList': []}]
844
845    output_dict = {u'Reflection Lists': {},
846                   u'Limits': reader.pwdparms.get('Limits', [(Tmin, Tmax), [Tmin, Tmax]]),
847                   u'data': [valuesdict, reader.powderdata, HistName],
848                   u'Index Peak List': [[], []],
849                   u'Comments': reader.comments,
850                   u'Unit Cells List': [],
851                   u'Sample Parameters': reader.Sample,
852                   u'Peak List': {'peaks': [], 'sigDict': {}},
853                   u'Background': reader.pwdparms.get('Background', default_background),
854                   u'Instrument Parameters': [Iparm1, Iparm2],
855                   }
856
857    names = [u'Comments',
858             u'Limits',
859             u'Background',
860             u'Instrument Parameters',
861             u'Sample Parameters',
862             u'Peak List',
863             u'Index Peak List',
864             u'Unit Cells List',
865             u'Reflection Lists']
866
867    # TODO controls?? GSASII.py:1664-7
868
869    return HistName, [HistName] + names, output_dict
870
871
872def _deep_copy_into(from_, into):
873    """Helper function for reloading .gpx file. See G2Project.reload()
874
875    :author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
876    """
877    if isinstance(from_, dict) and isinstance(into, dict):
878        combined_keys = set(from_.keys()).union(into.keys())
879        for key in combined_keys:
880            if key in from_ and key in into:
881                both_dicts = (isinstance(from_[key], dict)
882                              and isinstance(into[key], dict))
883                both_lists = (isinstance(from_[key], list)
884                              and isinstance(into[key], list))
885                if both_dicts or both_lists:
886                    _deep_copy_into(from_[key], into[key])
887                else:
888                    into[key] = from_[key]
889            elif key in from_:
890                into[key] = from_[key]
891            else:  # key in into
892                del into[key]
893    elif isinstance(from_, list) and isinstance(into, list):
894        if len(from_) == len(into):
895            for i in range(len(from_)):
896                both_dicts = (isinstance(from_[i], dict)
897                              and isinstance(into[i], dict))
898                both_lists = (isinstance(from_[i], list)
899                              and isinstance(into[i], list))
900                if both_dicts or both_lists:
901                    _deep_copy_into(from_[i], into[i])
902                else:
903                    into[i] = from_[i]
904        else:
905            into[:] = from_
906
907
908class G2ObjectWrapper(object):
909    """Base class for all GSAS-II object wrappers.
910
911    The underlying GSAS-II format can be accessed as `wrapper.data`. A number
912    of overrides are implemented so that the wrapper behaves like a dictionary.
913
914    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
915    """
916    def __init__(self, datadict):
917        self.data = datadict
918
919    def __getitem__(self, key):
920        return self.data[key]
921
922    def __setitem__(self, key, value):
923        self.data[key] = value
924
925    def __contains__(self, key):
926        return key in self.data
927
928    def get(self, k, d=None):
929        return self.data.get(k, d)
930
931    def keys(self):
932        return self.data.keys()
933
934    def values(self):
935        return self.data.values()
936
937    def items(self):
938        return self.data.items()
939
940
941class G2Project(G2ObjectWrapper):
942    """
943    Represents an entire GSAS-II project.
944
945    There are two ways to initialize it:
946
947    >>> # Load an existing project file
948    >>> proj = G2Project('filename.gpx')
949    >>> # Create a new project
950    >>> proj = G2Project(filename='new_file.gpx')
951    >>> # Specify an author
952    >>> proj = G2Project(author='Dr. So-And-So', filename='my_data.gpx')
953
954    Histograms can be accessed easily.
955
956    >>> # By name
957    >>> hist = proj.histogram('PWDR my-histogram-name')
958    >>> # Or by index
959    >>> hist = proj.histogram(0)
960    >>> assert hist.id == 0
961    >>> # Or by random id
962    >>> assert hist == proj.histogram(hist.ranId)
963
964    Phases can be accessed the same way.
965
966    >>> phase = proj.phase('name of phase')
967
968    New data can also be loaded via :meth:`~G2Project.add_phase` and
969    :meth:`~G2Project.add_powder_histogram`.
970
971    >>> hist = proj.add_powder_histogram('some_data_file.chi',
972                                         'instrument_parameters.prm')
973    >>> phase = proj.add_phase('my_phase.cif', histograms=[hist])
974
975    Parameters for Rietveld refinement can be turned on and off as well.
976    See :meth:`~G2Project.set_refinement`, :meth:`~G2Project.refine`,
977    :meth:`~G2Project.iter_refinements`, :meth:`~G2Project.do_refinements`.
978    """
979    def __init__(self, gpxfile=None, author=None, filename=None):
980        """Loads a GSAS-II project from a specified filename.
981
982        :param str gpxfile: Existing .gpx file to be loaded. If nonexistent,
983            creates an empty project.
984        :param str author: Author's name. Optional.
985        :param str filename: The filename the project should be saved to in
986            the future. If both filename and gpxfile are present, the project is
987            loaded from the gpxfile, then set to  save to filename in the future"""
988        if gpxfile is None:
989            filename = os.path.abspath(os.path.expanduser(filename))
990            self.filename = filename
991            self.data, self.names = make_empty_project(author=author, filename=filename)
992        elif isinstance(gpxfile, str):
993            # TODO set author, filename
994            self.filename = os.path.abspath(os.path.expanduser(gpxfile))
995            self.data, self.names = LoadDictFromProjFile(gpxfile)
996            self.update_ids()
997        else:
998            raise ValueError("Not sure what to do with gpxfile")
999
1000    @classmethod
1001    def from_dict_and_names(cls, gpxdict, names, filename=None):
1002        """Creates a :class:`G2Project` directly from
1003        a dictionary and a list of names. If in doubt, do not use this.
1004
1005        :returns: a :class:`G2Project`
1006        """
1007        out = cls()
1008        if filename:
1009            filename = os.path.abspath(os.path.expanduser(filename))
1010            out.filename = filename
1011            gpxdict['Controls']['data']['LastSavedAs'] = filename
1012        else:
1013            try:
1014                out.filename = gpxdict['Controls']['data']['LastSavedAs']
1015            except KeyError:
1016                out.filename = None
1017        out.data = gpxdict
1018        out.names = names
1019
1020    def save(self, filename=None):
1021        """Saves the project, either to the current filename, or to a new file.
1022
1023        Updates self.filename if a new filename provided"""
1024        # TODO update LastSavedUsing ?
1025        if filename:
1026            filename = os.path.abspath(os.path.expanduser(filename))
1027            self.data['Controls']['data']['LastSavedAs'] = filename
1028            self.filename = filename
1029        elif not self.filename:
1030            raise AttributeError("No file name to save to")
1031        SaveDictToProjFile(self.data, self.names, self.filename)
1032
1033    def add_powder_histogram(self, datafile, iparams, phases=[]):
1034        """Loads a powder data histogram into the project.
1035
1036        Automatically checks for an instrument parameter file, or one can be
1037        provided. Note that in unix fashion, "~" can be used to indicate the
1038        home directory (e.g. ~/G2data/data.fxye).
1039
1040        :param str datafile: The powder data file to read, a filename.
1041        :param str iparams: The instrument parameters file, a filename.
1042        :param list phases: Phases to link to the new histogram
1043
1044        :returns: A :class:`G2PwdrData` object representing
1045            the histogram
1046        """
1047        LoadG2fil()
1048        datafile = os.path.abspath(os.path.expanduser(datafile))
1049        iparams = os.path.abspath(os.path.expanduser(iparams))
1050        pwdrreaders = import_generic(datafile, PwdrDataReaders)
1051        histname, new_names, pwdrdata = load_pwd_from_reader(
1052                                          pwdrreaders[0], iparams,
1053                                          [h.name for h in self.histograms()])
1054        if histname in self.data:
1055            print("Warning - redefining histogram", histname)
1056        elif self.names[-1][0] == 'Phases':
1057            self.names.insert(-1, new_names)
1058        else:
1059            self.names.append(new_names)
1060        self.data[histname] = pwdrdata
1061        self.update_ids()
1062
1063        for phase in phases:
1064            phase = self.phase(phase)
1065            self.link_histogram_phase(histname, phase)
1066
1067        return self.histogram(histname)
1068
1069    def add_phase(self, phasefile, phasename=None, histograms=[]):
1070        """Loads a phase into the project from a .cif file
1071
1072        :param str phasefile: The CIF file from which to import the phase.
1073        :param str phasename: The name of the new phase, or None for the default
1074        :param list histograms: The names of the histograms to associate with
1075            this phase
1076
1077        :returns: A :class:`G2Phase` object representing the
1078            new phase.
1079        """
1080        LoadG2fil()
1081        histograms = [self.histogram(h).name for h in histograms]
1082        phasefile = os.path.abspath(os.path.expanduser(phasefile))
1083
1084        # TODO handle multiple phases in a file
1085        phasereaders = import_generic(phasefile, PhaseReaders)
1086        phasereader = phasereaders[0]
1087       
1088        phasename = phasename or phasereader.Phase['General']['Name']
1089        phaseNameList = [p.name for p in self.phases()]
1090        phasename = G2obj.MakeUniqueLabel(phasename, phaseNameList)
1091        phasereader.Phase['General']['Name'] = phasename
1092
1093        if 'Phases' not in self.data:
1094            self.data[u'Phases'] = { 'data': None }
1095        assert phasename not in self.data['Phases'], "phase names should be unique"
1096        self.data['Phases'][phasename] = phasereader.Phase
1097
1098        if phasereader.Constraints:
1099            Constraints = self.data['Constraints']
1100            for i in phasereader.Constraints:
1101                if isinstance(i, dict):
1102                    if '_Explain' not in Constraints:
1103                        Constraints['_Explain'] = {}
1104                    Constraints['_Explain'].update(i)
1105                else:
1106                    Constraints['Phase'].append(i)
1107
1108        data = self.data['Phases'][phasename]
1109        generalData = data['General']
1110        SGData = generalData['SGData']
1111        NShkl = len(G2spc.MustrainNames(SGData))
1112        NDij = len(G2spc.HStrainNames(SGData))
1113        Super = generalData.get('Super', 0)
1114        if Super:
1115            SuperVec = np.array(generalData['SuperVec'][0])
1116        else:
1117            SuperVec = []
1118        UseList = data['Histograms']
1119
1120        for hist in histograms:
1121            self.link_histogram_phase(hist, phasename)
1122
1123        for obj in self.names:
1124            if obj[0] == 'Phases':
1125                phasenames = obj
1126                break
1127        else:
1128            phasenames = [u'Phases']
1129            self.names.append(phasenames)
1130        phasenames.append(unicode(phasename))
1131
1132        # TODO should it be self.filename, not phasefile?
1133        SetupGeneral(data, os.path.dirname(phasefile))
1134        self.index_ids()
1135
1136        self.update_ids()
1137        return self.phase(phasename)
1138
1139    def link_histogram_phase(self, histogram, phase):
1140        """Associates a given histogram and phase.
1141
1142        .. seealso::
1143
1144            :meth:`G2Project.histogram`
1145            :meth:`G2Project.phase`"""
1146        hist = self.histogram(histogram)
1147        phase = self.phase(phase)
1148
1149        generalData = phase['General']
1150
1151        if hist.name.startswith('HKLF '):
1152            raise NotImplementedError("HKLF not yet supported")
1153        elif hist.name.startswith('PWDR '):
1154            hist['Reflection Lists'][generalData['Name']] = {}
1155            UseList = phase['Histograms']
1156            SGData = generalData['SGData']
1157            NShkl = len(G2spc.MustrainNames(SGData))
1158            NDij = len(G2spc.HStrainNames(SGData))
1159            UseList[hist.name] = SetDefaultDData('PWDR', hist.name, NShkl=NShkl, NDij=NDij)
1160            UseList[hist.name]['hId'] = hist.id
1161            for key, val in [('Use', True), ('LeBail', False),
1162                             ('newLeBail', True),
1163                             ('Babinet', {'BabA': [0.0, False],
1164                                          'BabU': [0.0, False]})]:
1165                if key not in UseList[hist.name]:
1166                    UseList[hist.name][key] = val
1167        else:
1168            raise RuntimeError("Unexpected histogram" + hist.name)
1169
1170
1171    def reload(self):
1172        """Reload self from self.filename"""
1173        data, names = LoadDictFromProjFile(self.filename)
1174        self.names = names
1175        # Need to deep copy the new data file data into the current tree,
1176        # so that any existing G2Phase, or G2PwdrData objects will still be
1177        # valid
1178        _deep_copy_into(from_=data, into=self.data)
1179
1180    def refine(self, newfile=None, printFile=None, makeBack=False):
1181        # TODO migrate to RefineCore
1182        # G2strMain.RefineCore(Controls,Histograms,Phases,restraintDict,rigidbodyDict,parmDict,varyList,
1183        #      calcControls,pawleyLookup,ifPrint,printFile,dlg)
1184        # index_ids will automatically save the project
1185        self.index_ids()
1186        # TODO G2strMain does not properly use printFile
1187        G2strMain.Refine(self.filename, makeBack=makeBack)
1188        # Reload yourself
1189        self.reload()
1190
1191    def histogram(self, histname):
1192        """Returns the histogram named histname, or None if it does not exist.
1193
1194        :param histname: The name of the histogram (str), or ranId or index.
1195        :returns: A :class:`G2PwdrData` object, or None if
1196            the histogram does not exist
1197
1198        .. seealso::
1199            :meth:`G2Project.histograms`
1200            :meth:`G2Project.phase`
1201            :meth:`G2Project.phases`
1202            """
1203        if isinstance(histname, G2PwdrData):
1204            if histname.proj == self:
1205                return histname
1206        if histname in self.data:
1207            return G2PwdrData(self.data[histname], self)
1208        try:
1209            # see if histname is an id or ranId
1210            histname = int(histname)
1211        except ValueError:
1212            return
1213
1214        for histogram in self.histograms():
1215            if histogram.id == histname or histogram.ranId == histname:
1216                return histogram
1217
1218    def histograms(self):
1219        """Return a list of all histograms, as
1220        :class:`G2PwdrData` objects
1221
1222        .. seealso::
1223            :meth:`G2Project.histograms`
1224            :meth:`G2Project.phase`
1225            :meth:`G2Project.phases`
1226            """
1227        output = []
1228        for obj in self.names:
1229            if len(obj) > 1 and obj[0] != u'Phases':
1230                output.append(self.histogram(obj[0]))
1231        return output
1232
1233    def phase(self, phasename):
1234        """
1235        Gives an object representing the specified phase in this project.
1236
1237        :param str phasename: The name of the desired phase. Either the name
1238            (str), the phase's ranId, or the phase's index
1239        :returns: A :class:`G2Phase` object
1240        :raises: KeyError
1241
1242        .. seealso::
1243            :meth:`G2Project.histograms`
1244            :meth:`G2Project.phase`
1245            :meth:`G2Project.phases`
1246            """
1247        phases = self.data['Phases']
1248        if phasename in phases:
1249            return G2Phase(phases[phasename], phasename, self)
1250
1251        try:
1252            # phasename should be phase index or ranId
1253            phasename = int(phasename)
1254        except ValueError:
1255            return
1256
1257        for phase in self.phases():
1258            if phase.id == phasename or phase.ranId == phasename:
1259                return phase
1260
1261    def phases(self):
1262        """
1263        Returns a list of all the phases in the project.
1264
1265        :returns: A list of :class:`G2Phase` objects
1266
1267        .. seealso::
1268            :meth:`G2Project.histogram`
1269            :meth:`G2Project.histograms`
1270            :meth:`G2Project.phase`
1271            """
1272        for obj in self.names:
1273            if obj[0] == 'Phases':
1274                return [self.phase(p) for p in obj[1:]]
1275        return []
1276
1277    def update_ids(self):
1278        """Makes sure all phases and histograms have proper hId and pId"""
1279        # Translated from GetUsedHistogramsAndPhasesfromTree,
1280        #   GSASIIdataGUI.py:4107
1281        for i, h in enumerate(self.histograms()):
1282            h.id = i
1283        for i, p in enumerate(self.phases()):
1284            p.id = i
1285
1286    def do_refinements(self, refinements, histogram='all', phase='all',
1287                       outputnames=None, makeBack=False):
1288        """Conducts a series of refinements. Wrapper around iter_refinements
1289
1290        :param list refinements: A list of dictionaries defining refinements
1291        :param str histogram: Name of histogram for refinements to be applied
1292            to, or 'all'
1293        :param str phase: Name of phase for refinements to be applied to, or
1294            'all'
1295        """
1296        for proj in self.iter_refinements(refinements, histogram, phase,
1297                                          outputnames, makeBack):
1298            pass
1299        return self
1300
1301    def iter_refinements(self, refinements, histogram='all', phase='all',
1302                         outputnames=None, makeBack=False):
1303        """Conducts a series of refinements, iteratively. Stops after every
1304        refinement and yields this project, to allow error checking or
1305        logging of intermediate results.
1306
1307        :param list refinements: A list of dictionaries defining refinements
1308        :param str histogram: Name of histogram for refinements to be applied
1309            to, a list of histograms or 'all'.
1310            See :func:`set_refinement` for more details
1311        :param str phase: Name of phase for refinements to be applied to, a
1312            list of phases or 'all'.
1313            See :func:`set_refinement` for more details
1314           
1315        >>> def checked_refinements(proj):
1316        ...     for p in proj.iter_refinements(refs):
1317        ...         # Track intermediate results
1318        ...         log(p.histogram('0').residuals)
1319        ...         log(p.phase('0').get_cell())
1320        ...         # Check if parameter diverged, nonsense answer, or whatever
1321        ...         if is_something_wrong(p):
1322        ...             raise Exception("I need a human!")
1323
1324           
1325        """
1326        if outputnames:
1327            if len(refinements) != len(outputnames):
1328                raise ValueError("Should have same number of outuputs to"
1329                                 "refinements")
1330        else:
1331            outputnames = [None for r in refinements]
1332
1333        for output, refinement in zip(outputnames, refinements):
1334            self.set_refinement(refinement, histogram)
1335            # Handle 'once' args - refinements that are disabled after this
1336            # refinement
1337            if 'once' in refinement:
1338                temp = {'set': refinement['once']}
1339                self.set_refinement(temp, histogram, phase)
1340
1341            if output:
1342                self.save(output)
1343
1344            self.refine(makeBack=makeBack)
1345            yield self
1346
1347            # Handle 'once' args - refinements that are disabled after this
1348            # refinement
1349            if 'once' in refinement:
1350                temp = {'clear': refinement['once']}
1351                self.set_refinement(temp, histogram, phase)
1352
1353    def set_refinement(self, refinement, histogram='all', phase='all'):
1354        """Apply specified refinements to a given histogram(s) or phase(s).
1355
1356        :param dict refinement: The refinements to be conducted
1357        :param histogram: Specifies either 'all' (default), a single histogram or
1358          a list of histograms. Histograms may be specified as histogram objects
1359          (see :class:`G2PwdrData`), the histogram name (str) or the index number (int)
1360          of the histogram in the project, numbered starting from 0.
1361          Omitting the parameter or the string 'all' indicates that parameters in
1362          all histograms should be set.
1363        :param phase: Specifies either 'all' (default), a single phase or
1364          a list of phases. Phases may be specified as phase objects
1365          (see :class:`G2Phase`), the phase name (str) or the index number (int)
1366          of the phase in the project, numbered starting from 0.
1367          Omitting the parameter or the string 'all' indicates that parameters in
1368          all phases should be set.
1369
1370        Note that refinement parameters are categorized as one of three types:
1371
1372        1. Histogram parameters
1373        2. Phase parameters
1374        3. Histogram-and-Phase (HAP) parameters
1375       
1376        .. seealso::
1377            :meth:`G2PwdrData.set_refinements`
1378            :meth:`G2PwdrData.clear_refinements`
1379            :meth:`G2Phase.set_refinements`
1380            :meth:`G2Phase.clear_refinements`
1381            :meth:`G2Phase.set_HAP_refinements`
1382            :meth:`G2Phase.clear_HAP_refinements`"""
1383
1384        if histogram == 'all':
1385            hists = self.histograms()
1386        elif isinstance(histogram, list) or isinstance(histogram, tuple):
1387            hists = []
1388            for h in histogram:
1389                if isinstance(h, str) or isinstance(h, int):
1390                    hists.append(self.histogram(h))
1391                else:
1392                    hists.append(h)
1393        elif isinstance(histogram, str) or isinstance(histogram, int):
1394            hists = [self.histogram(histogram)]
1395        else:
1396            hists = [histogram]
1397
1398        if phase == 'all':
1399            phases = self.phases()
1400        elif isinstance(phase, list) or isinstance(phase, tuple):
1401            phases = []
1402            for ph in phase:
1403                if isinstance(ph, str) or isinstance(ph, int):
1404                    phases.append(self.phase(ph))
1405                else:
1406                    phases.append(ph)
1407        elif isinstance(phase, str) or isinstance(phase, int):
1408            phases = [self.phase(phase)]
1409        else:
1410            phases = [phase]
1411
1412        # TODO: HAP parameters:
1413        #   Babinet
1414        #   Extinction
1415        #   HStrain
1416        #   Mustrain
1417        #   Pref. Ori
1418        #   Size
1419
1420        pwdr_set = {}
1421        phase_set = {}
1422        hap_set = {}
1423        for key, val in refinement.get('set', {}).items():
1424            # Apply refinement options
1425            if G2PwdrData.is_valid_refinement_key(key):
1426                pwdr_set[key] = val
1427            elif G2Phase.is_valid_refinement_key(key):
1428                phase_set[key] = val
1429            elif G2Phase.is_valid_HAP_refinement_key(key):
1430                hap_set[key] = val
1431            else:
1432                raise ValueError("Unknown refinement key", key)
1433
1434        for hist in hists:
1435            hist.set_refinements(pwdr_set)
1436        for phase in phases:
1437            phase.set_refinements(phase_set)
1438        for phase in phases:
1439            phase.set_HAP_refinements(hap_set, hists)
1440
1441        pwdr_clear = {}
1442        phase_clear = {}
1443        hap_clear = {}
1444        for key, val in refinement.get('clear', {}).items():
1445            # Clear refinement options
1446            if G2PwdrData.is_valid_refinement_key(key):
1447                pwdr_clear[key] = val
1448            elif G2Phase.is_valid_refinement_key(key):
1449                phase_clear[key] = val
1450            elif G2Phase.is_valid_HAP_refinement_key(key):
1451                hap_set[key] = val
1452            else:
1453                raise ValueError("Unknown refinement key", key)
1454
1455        for hist in hists:
1456            hist.clear_refinements(pwdr_clear)
1457        for phase in phases:
1458            phase.clear_refinements(phase_clear)
1459        for phase in phases:
1460            phase.clear_HAP_refinements(hap_clear, hists)
1461
1462    def index_ids(self):
1463        import GSASIIstrIO as G2strIO
1464        self.save()
1465        return G2strIO.GetUsedHistogramsAndPhases(self.filename)
1466
1467    def add_constraint_raw(self, cons_scope, constr):
1468        """Adds a constraint of type consType to the project.
1469        cons_scope should be one of "Hist", "Phase", "HAP", or "Global".
1470
1471        WARNING it does not check the constraint is well-constructed"""
1472        constrs = self.data['Constraints']['data']
1473        if 'Global' not in constrs:
1474            constrs['Global'] = []
1475        constrs[cons_scope].append(constr)
1476
1477    def hold_many(self, vars, type):
1478        """Apply holds for all the variables in vars, for constraint of a given type.
1479
1480        type is passed directly to add_constraint_raw as consType
1481
1482        :param list vars: A list of variables to hold. Either :class:`GSASIIobj.G2VarObj` objects,
1483            string variable specifiers, or arguments for :meth:`make_var_obj`
1484        :param str type: A string constraint type specifier. See
1485            :class:`G2Project.add_constraint_raw`
1486
1487        """
1488        for var in vars:
1489            if isinstance(var, str):
1490                var = self.make_var_obj(var)
1491            elif not isinstance(var, G2obj.G2VarObj):
1492                var = self.make_var_obj(*var)
1493            self.add_constraint_raw(type, [[1.0, var], None, None, 'h'])
1494
1495    def make_var_obj(self, phase=None, hist=None, varname=None, atomId=None,
1496                     reloadIdx=True):
1497        """Wrapper to create a G2VarObj. Takes either a string representaiton ("p:h:name:a")
1498        or individual names of phase, histogram, varname, and atomId.
1499
1500        Automatically converts string phase, hist, or atom names into the ID required
1501        by G2VarObj."""
1502
1503        if reloadIdx:
1504            self.index_ids()
1505
1506        # If string representation, short circuit
1507        if hist is None and varname is None and atomId is None:
1508            if isinstance(phase, str) and ':' in phase:
1509                return G2obj.G2VarObj(phase)
1510
1511        # Get phase index
1512        phaseObj = None
1513        if isinstance(phase, G2Phase):
1514            phaseObj = phase
1515            phase = G2obj.PhaseRanIdLookup[phase.ranId]
1516        elif phase in self.data['Phases']:
1517            phaseObj = self.phase(phase)
1518            phaseRanId = phaseObj.ranId
1519            phase = G2obj.PhaseRanIdLookup[phaseRanId]
1520
1521        # Get histogram index
1522        if isinstance(hist, G2PwdrData):
1523            hist = G2obj.HistRanIdLookup[hist.ranId]
1524        elif hist in self.data:
1525            histRanId = self.histogram(hist).ranId
1526            hist = G2obj.HistRanIdLookup[histRanId]
1527
1528        # Get atom index (if any)
1529        if isinstance(atomId, G2AtomRecord):
1530            atomId = G2obj.AtomRanIdLookup[phase][atomId.ranId]
1531        elif phaseObj:
1532            atomObj = phaseObj.atom(atomId)
1533            if atomObj:
1534                atomRanId = atomObj.ranId
1535                atomId = G2obj.AtomRanIdLookup[phase][atomRanId]
1536
1537        return G2obj.G2VarObj(phase, hist, varname, atomId)
1538
1539
1540class G2AtomRecord(G2ObjectWrapper):
1541    """Wrapper for an atom record. Has convenient accessors via @property.
1542
1543
1544    Available accessors: label, type, refinement_flags, coordinates,
1545        occupancy, ranId, id, adp_flag, uiso
1546
1547    Example:
1548
1549    >>> atom = some_phase.atom("O3")
1550    >>> # We can access the underlying data format
1551    >>> atom.data
1552    ['O3', 'O-2', '', ... ]
1553    >>> # We can also use wrapper accessors
1554    >>> atom.coordinates
1555    (0.33, 0.15, 0.5)
1556    >>> atom.refinement_flags
1557    u'FX'
1558    >>> atom.ranId
1559    4615973324315876477
1560    >>> atom.occupancy
1561    1.0
1562    """
1563    def __init__(self, data, indices, proj):
1564        self.data = data
1565        self.cx, self.ct, self.cs, self.cia = indices
1566        self.proj = proj
1567
1568    @property
1569    def label(self):
1570        return self.data[self.ct-1]
1571
1572    @property
1573    def type(self):
1574        return self.data[self.ct]
1575
1576    @property
1577    def refinement_flags(self):
1578        return self.data[self.ct+1]
1579
1580    @refinement_flags.setter
1581    def refinement_flags(self, other):
1582        # Automatically check it is a valid refinement
1583        for c in other:
1584            if c not in ' FXU':
1585                raise ValueError("Invalid atom refinement: ", other)
1586        self.data[self.ct+1] = unicode(other)
1587
1588    @property
1589    def coordinates(self):
1590        return tuple(self.data[self.cx:self.cx+3])
1591
1592    @property
1593    def occupancy(self):
1594        return self.data[self.cx+3]
1595
1596    @occupancy.setter
1597    def occupancy(self, val):
1598        self.data[self.cx+3] = float(val)
1599
1600    @property
1601    def ranId(self):
1602        return self.data[self.cia+8]
1603
1604    @property
1605    def adp_flag(self):
1606        # Either 'I' or 'A'
1607        return self.data[self.cia]
1608
1609    @property
1610    def uiso(self):
1611        if self.adp_flag == 'I':
1612            return self.data[self.cia+1]
1613        else:
1614            return self.data[self.cia+2:self.cia+8]
1615
1616    @uiso.setter
1617    def uiso(self, value):
1618        if self.adp_flag == 'I':
1619            self.data[self.cia+1] = float(value)
1620        else:
1621            assert len(value) == 6
1622            self.data[self.cia+2:self.cia+8] = [float(v) for v in value]
1623
1624
1625class G2PwdrData(G2ObjectWrapper):
1626    """Wraps a Powder Data Histogram."""
1627    def __init__(self, data, proj):
1628        self.data = data
1629        self.proj = proj
1630
1631    @staticmethod
1632    def is_valid_refinement_key(key):
1633        valid_keys = ['Limits', 'Sample Parameters', 'Background',
1634                      'Instrument Parameters']
1635        return key in valid_keys
1636
1637    @property
1638    def name(self):
1639        return self.data['data'][-1]
1640
1641    @property
1642    def ranId(self):
1643        return self.data['data'][0]['ranId']
1644
1645    @property
1646    def residuals(self):
1647        data = self.data['data'][0]
1648        return {key: data[key]
1649                for key in ['R', 'Rb', 'wR', 'wRb', 'wRmin']}
1650
1651    @property
1652    def id(self):
1653        self.proj.update_ids()
1654        return self.data['data'][0]['hId']
1655
1656    @id.setter
1657    def id(self, val):
1658        self.data['data'][0]['hId'] = val
1659
1660    def fit_fixed_points(self):
1661        """Attempts to apply a background fit to the fixed points currently specified."""
1662        def SetInstParms(Inst):
1663            dataType = Inst['Type'][0]
1664            insVary = []
1665            insNames = []
1666            insVals = []
1667            for parm in Inst:
1668                insNames.append(parm)
1669                insVals.append(Inst[parm][1])
1670                if parm in ['U','V','W','X','Y','SH/L','I(L2)/I(L1)','alpha',
1671                    'beta-0','beta-1','beta-q','sig-0','sig-1','sig-2','sig-q',] and Inst[parm][2]:
1672                        Inst[parm][2] = False
1673            instDict = dict(zip(insNames, insVals))
1674            instDict['X'] = max(instDict['X'], 0.01)
1675            instDict['Y'] = max(instDict['Y'], 0.01)
1676            if 'SH/L' in instDict:
1677                instDict['SH/L'] = max(instDict['SH/L'], 0.002)
1678            return dataType, instDict, insVary
1679
1680        bgrnd = self.data['Background']
1681
1682        # Need our fixed points in order
1683        bgrnd[1]['FixedPoints'].sort(key=lambda pair: pair[0])
1684        X = [x for x, y in bgrnd[1]['FixedPoints']]
1685        Y = [y for x, y in bgrnd[1]['FixedPoints']]
1686
1687        limits = self.data['Limits'][1]
1688        if X[0] > limits[0]:
1689            X = [limits[0]] + X
1690            Y = [Y[0]] + Y
1691        if X[-1] < limits[1]:
1692            X += [limits[1]]
1693            Y += [Y[-1]]
1694
1695        # Some simple lookups
1696        controls = self.proj['Controls']['data']
1697        inst, inst2 = self.data['Instrument Parameters']
1698        pwddata = self.data['data'][1]
1699
1700        # Construct the data for background fitting
1701        xBeg = np.searchsorted(pwddata[0], limits[0])
1702        xFin = np.searchsorted(pwddata[0], limits[1])
1703        xdata = pwddata[0][xBeg:xFin]
1704        ydata = si.interp1d(X,Y)(ma.getdata(xdata))
1705
1706        W = [1]*len(xdata)
1707        Z = [0]*len(xdata)
1708
1709        dataType, insDict, insVary = SetInstParms(inst)
1710        bakType, bakDict, bakVary = G2pwd.SetBackgroundParms(bgrnd)
1711
1712        # Do the fit
1713        data = np.array([xdata, ydata, W, Z, Z, Z])
1714        G2pwd.DoPeakFit('LSQ', [], bgrnd, limits, inst, inst2, data,
1715                        prevVaryList=bakVary, controls=controls)
1716
1717        # Post-fit
1718        parmDict = {}
1719        bakType, bakDict, bakVary = G2pwd.SetBackgroundParms(bgrnd)
1720        parmDict.update(bakDict)
1721        parmDict.update(insDict)
1722        pwddata[3][xBeg:xFin] *= 0
1723        pwddata[5][xBeg:xFin] *= 0
1724        pwddata[4][xBeg:xFin] = G2pwd.getBackground('', parmDict, bakType, dataType, xdata)[0]
1725
1726        # TODO adjust pwddata? GSASIIpwdGUI.py:1041
1727        # TODO update background
1728        self.proj.save()
1729
1730    def y_calc(self):
1731        return self.data['data'][1][3]
1732
1733    def plot(self, Yobs=True, Ycalc=True, Background=True, Residual=True):
1734        try:
1735            import matplotlib.pyplot as plt
1736            data = self.data['data'][1]
1737            if Yobs:
1738                plt.plot(data[0], data[1], label='Yobs')
1739            if Ycalc:
1740                plt.plot(data[0], data[3], label='Ycalc')
1741            if Background:
1742                plt.plot(data[0], data[4], label='Background')
1743            if Residual:
1744                plt.plot(data[0], data[5], label="Residual")
1745        except ImportError:
1746            pass
1747
1748    def get_wR(self):
1749        """returns the overall weighted profile R factor for a histogram
1750       
1751        :returns: a wR value as a percentage or None if not defined
1752        """
1753        return self['data'][0].get('wR')
1754
1755    def set_refinements(self, refs):
1756        """Sets the refinement parameter 'key' to the specification 'value'
1757
1758        :param dict refs: A dictionary of the parameters to be set. See
1759                          :ref:`Histogram_parameters_table` for a description of
1760                          what these dictionaries should be.
1761
1762        :returns: None
1763
1764        """
1765        do_fit_fixed_points = False
1766        for key, value in refs.items():
1767            if key == 'Limits':
1768                old_limits = self.data['Limits'][1]
1769                new_limits = value
1770                if isinstance(new_limits, dict):
1771                    if 'low' in new_limits:
1772                        old_limits[0] = new_limits['low']
1773                    if 'high' in new_limits:
1774                        old_limits[1] = new_limits['high']
1775                else:
1776                    old_limits[0], old_limits[1] = new_limits
1777            elif key == 'Sample Parameters':
1778                sample = self.data['Sample Parameters']
1779                for sparam in value:
1780                    if sparam not in sample:
1781                        raise ValueError("Unknown refinement parameter, "
1782                                         + str(sparam))
1783                    sample[sparam][1] = True
1784            elif key == 'Background':
1785                bkg, peaks = self.data['Background']
1786
1787                # If True or False, just set the refine parameter
1788                if value in (True, False):
1789                    bkg[1] = value
1790                    return
1791
1792                if 'type' in value:
1793                    bkg[0] = value['type']
1794                if 'refine' in value:
1795                    bkg[1] = value['refine']
1796                if 'no. coeffs' in value:
1797                    cur_coeffs = bkg[2]
1798                    n_coeffs = value['no. coeffs']
1799                    if n_coeffs > cur_coeffs:
1800                        for x in range(n_coeffs - cur_coeffs):
1801                            bkg.append(0.0)
1802                    else:
1803                        for _ in range(cur_coeffs - n_coeffs):
1804                            bkg.pop()
1805                    bkg[2] = n_coeffs
1806                if 'coeffs' in value:
1807                    bkg[3:] = value['coeffs']
1808                if 'FixedPoints' in value:
1809                    peaks['FixedPoints'] = [(float(a), float(b))
1810                                            for a, b in value['FixedPoints']]
1811                if value.get('fit fixed points', False):
1812                    do_fit_fixed_points = True
1813
1814            elif key == 'Instrument Parameters':
1815                instrument, secondary = self.data['Instrument Parameters']
1816                for iparam in value:
1817                    try:
1818                        instrument[iparam][2] = True
1819                    except IndexError:
1820                        raise ValueError("Invalid key:", iparam)
1821            else:
1822                raise ValueError("Unknown key:", key)
1823        # Fit fixed points after the fact - ensure they are after fixed points
1824        # are added
1825        if do_fit_fixed_points:
1826            # Background won't be fit if refinement flag not set
1827            orig = self.data['Background'][0][1]
1828            self.data['Background'][0][1] = True
1829            self.fit_fixed_points()
1830            # Restore the previous value
1831            self.data['Background'][0][1] = orig
1832
1833    def clear_refinements(self, refs):
1834        """Clears the refinement parameter 'key' and its associated value.
1835
1836        :param dict refs: A dictionary of parameters to clear."""
1837        for key, value in refs.items():
1838            if key == 'Limits':
1839                old_limits, cur_limits = self.data['Limits']
1840                cur_limits[0], cur_limits[1] = old_limits
1841            elif key == 'Sample Parameters':
1842                sample = self.data['Sample Parameters']
1843                for sparam in value:
1844                    sample[sparam][1] = False
1845            elif key == 'Background':
1846                bkg, peaks = self.data['Background']
1847
1848                # If True or False, just set the refine parameter
1849                if value in (True, False):
1850                    bkg[1] = False
1851                    return
1852
1853                bkg[1] = False
1854                if 'FixedPoints' in value:
1855                    if 'FixedPoints' in peaks:
1856                        del peaks['FixedPoints']
1857            elif key == 'Instrument Parameters':
1858                instrument, secondary = self.data['Instrument Parameters']
1859                for iparam in value:
1860                    instrument[iparam][2] = False
1861            else:
1862                raise ValueError("Unknown key:", key)
1863
1864
1865class G2Phase(G2ObjectWrapper):
1866    """A wrapper object around a given phase.
1867
1868    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
1869    """
1870    def __init__(self, data, name, proj):
1871        self.data = data
1872        self.name = name
1873        self.proj = proj
1874
1875    @staticmethod
1876    def is_valid_refinement_key(key):
1877        valid_keys = ["Cell", "Atoms", "LeBail"]
1878        return key in valid_keys
1879
1880    @staticmethod
1881    def is_valid_HAP_refinement_key(key):
1882        valid_keys = ["Babinet", "Extinction", "HStrain", "Mustrain",
1883                      "Pref.Ori.", "Show", "Size", "Use", "Scale"]
1884        return key in valid_keys
1885
1886    def atom(self, atomlabel):
1887        """Returns the atom specified by atomlabel, or None if it does not
1888        exist.
1889
1890        :param str atomlabel: The name of the atom (e.g. "O2")
1891        :returns: A :class:`G2AtomRecord` object
1892            representing the atom.
1893        """
1894        # Consult GSASIIobj.py for the meaning of this
1895        cx, ct, cs, cia = self.data['General']['AtomPtrs']
1896        ptrs = [cx, ct, cs, cia]
1897        atoms = self.data['Atoms']
1898        for atom in atoms:
1899            if atom[ct-1] == atomlabel:
1900                return G2AtomRecord(atom, ptrs, self.proj)
1901
1902    def atoms(self):
1903        """Returns a list of atoms present in the phase.
1904
1905        :returns: A list of :class:`G2AtomRecord` objects.
1906
1907        .. seealso::
1908            :meth:`G2Phase.atom`
1909            :class:`G2AtomRecord`
1910        """
1911        ptrs = self.data['General']['AtomPtrs']
1912        output = []
1913        atoms = self.data['Atoms']
1914        for atom in atoms:
1915            output.append(G2AtomRecord(atom, ptrs, self.proj))
1916        return output
1917
1918    def histograms(self):
1919        output = []
1920        for hname in self.data.get('Histograms', {}).keys():
1921            output.append(self.proj.histogram(hname))
1922        return output
1923
1924    @property
1925    def ranId(self):
1926        return self.data['ranId']
1927
1928    @property
1929    def id(self):
1930        return self.data['pId']
1931
1932    @id.setter
1933    def id(self, val):
1934        self.data['pId'] = val
1935
1936    def get_cell(self):
1937        """Returns a dictionary of the cell parameters, with keys:
1938            'length_a', 'length_b', 'length_c', 'angle_alpha', 'angle_beta', 'angle_gamma', 'volume'
1939
1940        :returns: a dict
1941
1942        .. seealso::
1943           :meth:`G2Phase.get_cell_and_esd`
1944
1945        """
1946        cell = self.data['General']['Cell']
1947        return {'length_a': cell[1], 'length_b': cell[2], 'length_c': cell[3],
1948                'angle_alpha': cell[4], 'angle_beta': cell[5], 'angle_gamma': cell[6],
1949                'volume': cell[7]}
1950
1951    def get_cell_and_esd(self):
1952        """
1953        Returns a pair of dictionaries, the first representing the unit cell, the second
1954        representing the estimated standard deviations of the unit cell.
1955
1956        :returns: a tuple of two dictionaries
1957
1958        .. seealso::
1959           :meth:`G2Phase.get_cell`
1960
1961        """
1962        # translated from GSASIIstrIO.ExportBaseclass.GetCell
1963        import GSASIIstrIO as G2stIO
1964        import GSASIIlattice as G2lat
1965        import GSASIImapvars as G2mv
1966        try:
1967            pfx = str(self.id) + '::'
1968            sgdata = self['General']['SGData']
1969            covDict = self.proj['Covariance']['data']
1970
1971            parmDict = dict(zip(covDict.get('varyList',[]),
1972                                covDict.get('variables',[])))
1973            sigDict = dict(zip(covDict.get('varyList',[]),
1974                               covDict.get('sig',[])))
1975
1976            if covDict.get('covMatrix') is not None:
1977                sigDict.update(G2mv.ComputeDepESD(covDict['covMatrix'],
1978                                                  covDict['varyList'],
1979                                                  parmDict))
1980
1981            A, sigA = G2stIO.cellFill(pfx, sgdata, parmDict, sigDict)
1982            cellSig = G2stIO.getCellEsd(pfx, sgdata, A, self.proj['Covariance']['data'])
1983            cellList = G2lat.A2cell(A) + (G2lat.calc_V(A),)
1984            cellDict, cellSigDict = {}, {}
1985            for i, key in enumerate(['length_a', 'length_b', 'length_c',
1986                                     'angle_alpha', 'angle_beta', 'angle_gamma',
1987                                     'volume']):
1988                cellDict[key] = cellList[i]
1989                cellSigDict[key] = cellSig[i]
1990            return cellDict, cellSigDict
1991        except KeyError:
1992            cell = self.get_cell()
1993            return cell, {key: 0.0 for key in cell}
1994
1995    def export_CIF(self, outputname, quickmode=True):
1996        """Write this phase to a .cif file named outputname
1997
1998        :param str outputname: The name of the .cif file to write to
1999        :param bool quickmode: Currently ignored. Carryover from exports.G2export_CIF"""
2000        # This code is all taken from exports/G2export_CIF.py
2001        # Functions copied have the same names
2002        import GSASIImath as G2mth
2003        import GSASIImapvars as G2mv
2004        from exports import G2export_CIF as cif
2005
2006        CIFdate = dt.datetime.strftime(dt.datetime.now(),"%Y-%m-%dT%H:%M")
2007        CIFname = os.path.splitext(self.proj.filename)[0]
2008        CIFname = os.path.split(CIFname)[1]
2009        CIFname = ''.join([c if ord(c) < 128 else ''
2010                           for c in CIFname.replace(' ', '_')])
2011        try:
2012            author = self.proj['Controls']['data'].get('Author','').strip()
2013        except KeyError:
2014            pass
2015        oneblock = True
2016
2017        covDict = self.proj['Covariance']['data']
2018        parmDict = dict(zip(covDict.get('varyList',[]),
2019                            covDict.get('variables',[])))
2020        sigDict = dict(zip(covDict.get('varyList',[]),
2021                           covDict.get('sig',[])))
2022
2023        if covDict.get('covMatrix') is not None:
2024            sigDict.update(G2mv.ComputeDepESD(covDict['covMatrix'],
2025                                              covDict['varyList'],
2026                                              parmDict))
2027
2028        with open(outputname, 'w') as fp:
2029            fp.write(' \n' + 70*'#' + '\n')
2030            cif.WriteCIFitem(fp, 'data_' + CIFname)
2031            # from exports.G2export_CIF.WritePhaseInfo
2032            cif.WriteCIFitem(fp, '\n# phase info for '+str(self.name) + ' follows')
2033            cif.WriteCIFitem(fp, '_pd_phase_name', self.name)
2034            # TODO get esds
2035            cellDict = self.get_cell()
2036            defsigL = 3*[-0.00001] + 3*[-0.001] + [-0.01] # significance to use when no sigma
2037            names = ['length_a','length_b','length_c',
2038                     'angle_alpha','angle_beta ','angle_gamma',
2039                     'volume']
2040            for key, val in cellDict.items():
2041                cif.WriteCIFitem(fp, '_cell_' + key, G2mth.ValEsd(val))
2042
2043            cif.WriteCIFitem(fp, '_symmetry_cell_setting',
2044                         self.data['General']['SGData']['SGSys'])
2045
2046            spacegroup = self.data['General']['SGData']['SpGrp'].strip()
2047            # regularize capitalization and remove trailing H/R
2048            spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
2049            cif.WriteCIFitem(fp, '_symmetry_space_group_name_H-M', spacegroup)
2050
2051            # generate symmetry operations including centering and center of symmetry
2052            SymOpList, offsetList, symOpList, G2oprList, G2opcodes = G2spc.AllOps(
2053                self.data['General']['SGData'])
2054            cif.WriteCIFitem(fp, 'loop_\n    _space_group_symop_id\n    _space_group_symop_operation_xyz')
2055            for i, op in enumerate(SymOpList,start=1):
2056                cif.WriteCIFitem(fp, '   {:3d}  {:}'.format(i,op.lower()))
2057
2058            # TODO skipped histograms, exports/G2export_CIF.py:880
2059
2060            # report atom params
2061            if self.data['General']['Type'] in ['nuclear','macromolecular']:        #this needs macromolecular variant, etc!
2062                cif.WriteAtomsNuclear(fp, self.data, self.name, parmDict, sigDict, [])
2063                # self._WriteAtomsNuclear(fp, parmDict, sigDict)
2064            else:
2065                raise Exception("no export for "+str(self.data['General']['Type'])+" coordinates implemented")
2066            # report cell contents
2067            cif.WriteComposition(fp, self.data, self.name, parmDict)
2068            if not quickmode and self.data['General']['Type'] == 'nuclear':      # report distances and angles
2069                # WriteDistances(fp,self.name,SymOpList,offsetList,symOpList,G2oprList)
2070                raise NotImplementedError("only quickmode currently supported")
2071            if 'Map' in self.data['General'] and 'minmax' in self.data['General']['Map']:
2072                cif.WriteCIFitem(fp,'\n# Difference density results')
2073                MinMax = self.data['General']['Map']['minmax']
2074                cif.WriteCIFitem(fp,'_refine_diff_density_max',G2mth.ValEsd(MinMax[0],-0.009))
2075                cif.WriteCIFitem(fp,'_refine_diff_density_min',G2mth.ValEsd(MinMax[1],-0.009))
2076
2077
2078    def set_refinements(self, refs):
2079        """Sets the refinement parameter 'key' to the specification 'value'
2080
2081        :param dict refs: A dictionary of the parameters to be set. See
2082                          :ref:`Phase_parameters_table` for a description of
2083                          this dictionary.
2084
2085        :returns: None"""
2086        for key, value in refs.items():
2087            if key == "Cell":
2088                self.data['General']['Cell'][0] = value
2089
2090            elif key == "Atoms":
2091                for atomlabel, atomrefinement in value.items():
2092                    if atomlabel == 'all':
2093                        for atom in self.atoms():
2094                            atom.refinement_flags = atomrefinement
2095                    else:
2096                        atom = self.atom(atomlabel)
2097                        if atom is None:
2098                            raise ValueError("No such atom: " + atomlabel)
2099                        atom.refinement_flags = atomrefinement
2100
2101            elif key == "LeBail":
2102                hists = self.data['Histograms']
2103                for hname, hoptions in hists.items():
2104                    if 'LeBail' not in hoptions:
2105                        hoptions['newLeBail'] = bool(True)
2106                    hoptions['LeBail'] = bool(value)
2107            else:
2108                raise ValueError("Unknown key:", key)
2109
2110    def clear_refinements(self, refs):
2111        """Clears a given set of parameters.
2112
2113        :param dict refs: The parameters to clear"""
2114        for key, value in refs.items():
2115            if key == "Cell":
2116                self.data['General']['Cell'][0] = False
2117            elif key == "Atoms":
2118                cx, ct, cs, cia = self.data['General']['AtomPtrs']
2119
2120                for atomlabel in value:
2121                    atom = self.atom(atomlabel)
2122                    # Set refinement to none
2123                    atom.refinement_flags = ' '
2124            elif key == "LeBail":
2125                hists = self.data['Histograms']
2126                for hname, hoptions in hists.items():
2127                    if 'LeBail' not in hoptions:
2128                        hoptions['newLeBail'] = True
2129                    hoptions['LeBail'] = False
2130            else:
2131                raise ValueError("Unknown key:", key)
2132
2133    def set_HAP_refinements(self, refs, histograms='all'):
2134        """Sets the given HAP refinement parameters between this phase and
2135        the given histograms
2136
2137        :param dict refs: A dictionary of the parameters to be set. See
2138                          :ref:`HAP_parameters_table` for a description of this
2139                          dictionary.
2140        :param histograms: Either 'all' (default) or a list of the histograms
2141            whose HAP parameters will be set with this phase. Histogram and phase
2142            must already be associated
2143
2144        :returns: None
2145        """
2146        if histograms == 'all':
2147            histograms = self.data['Histograms'].values()
2148        else:
2149            histograms = [self.data['Histograms'][h.name] for h in histograms]
2150
2151        for key, val in refs.items():
2152            for h in histograms:
2153                if key == 'Babinet':
2154                    try:
2155                        sets = list(val)
2156                    except ValueError:
2157                        sets = ['BabA', 'BabU']
2158                    for param in sets:
2159                        if param not in ['BabA', 'BabU']:
2160                            raise ValueError("Not sure what to do with" + param)
2161                        for hist in histograms:
2162                            hist['Babinet'][param][1] = True
2163                elif key == 'Extinction':
2164                    for h in histograms:
2165                        h['Extinction'][1] = bool(val)
2166                elif key == 'HStrain':
2167                    for h in histograms:
2168                        h['HStrain'][1] = [bool(val) for p in h['HStrain'][1]]
2169                elif key == 'Mustrain':
2170                    for h in histograms:
2171                        mustrain = h['Mustrain']
2172                        newType = None
2173                        direction = None
2174                        if isinstance(val, (unicode, str)):
2175                            if val in ['isotropic', 'uniaxial', 'generalized']:
2176                                newType = val
2177                            else:
2178                                raise ValueError("Not a Mustrain type: " + val)
2179                        elif isinstance(val, dict):
2180                            newType = val.get('type', None)
2181                            direction = val.get('direction', None)
2182
2183                        if newType:
2184                            mustrain[0] = newType
2185                            if newType == 'isotropic':
2186                                mustrain[2][0] = True
2187                                mustrain[5] = [False for p in mustrain[4]]
2188                            elif newType == 'uniaxial':
2189                                if 'refine' in val:
2190                                    types = val['refine']
2191                                    if isinstance(types, (unicode, str)):
2192                                        types = [types]
2193                                    elif isinstance(types, bool):
2194                                        mustrain[2][0] = types
2195                                        mustrain[2][1] = types
2196                                        types = []
2197                                    else:
2198                                        raise ValueError("Not sure what to do with: "
2199                                                         + str(types))
2200                                else:
2201                                    types = []
2202
2203                                for unitype in types:
2204                                    if unitype == 'equatorial':
2205                                        mustrain[2][0] = True
2206                                    elif unitype == 'axial':
2207                                        mustrain[2][1] = True
2208                                    else:
2209                                        msg = 'Invalid uniaxial mustrain type'
2210                                        raise ValueError(msg + ': ' + unitype)
2211                            else:  # newtype == 'generalized'
2212                                mustrain[2] = [False for p in mustrain[1]]
2213
2214                        if direction:
2215                            if len(direction) != 3:
2216                                raise ValueError("Expected hkl, found", direction)
2217                            direction = [int(n) for n in direction]
2218                            mustrain[3] = direction
2219                elif key == 'Size':
2220                    for h in histograms:
2221                        size = h['Size']
2222                        newType = None
2223                        direction = None
2224                        if isinstance(val, (unicode, str)):
2225                            if val in ['isotropic', 'uniaxial', 'ellipsoidal']:
2226                                newType = val
2227                            else:
2228                                raise ValueError("Not a valid Size type: " + val)
2229                        elif isinstance(val, dict):
2230                            newType = val.get('type', None)
2231                            direction = val.get('direction', None)
2232
2233                        if newType:
2234                            size[0] = newType
2235                            refine = val.get('refine')
2236                            if newType == 'isotropic' and refine is not None:
2237                                size[2][0] = bool(refine)
2238                            elif newType == 'uniaxial' and refine is not None:
2239                                size[2][1] = bool(refine)
2240                                size[2][2] = bool(refine)
2241                            elif newType == 'ellipsoidal' and refine is not None:
2242                                size[5] = [bool(refine) for p in size[5]]
2243
2244                        if direction:
2245                            if len(direction) != 3:
2246                                raise ValueError("Expected hkl, found", direction)
2247                            direction = [int(n) for n in direction]
2248                            size[3] = direction
2249                elif key == 'Pref.Ori.':
2250                    for h in histograms:
2251                        h['Pref.Ori.'][2] = bool(val)
2252                elif key == 'Show':
2253                    for h in histograms:
2254                        h['Show'] = bool(val)
2255                elif key == 'Use':
2256                    for h in histograms:
2257                        h['Use'] = bool(val)
2258                elif key == 'Scale':
2259                    for h in histograms:
2260                        h['Scale'][1] = bool(val)
2261                else:
2262                    print(u'Unknown HAP key: '+key)
2263
2264    def clear_HAP_refinements(self, refs, histograms='all'):
2265        """Clears the given HAP refinement parameters between this phase and
2266        the given histograms
2267
2268        :param dict refs: A dictionary of the parameters to be cleared.
2269        :param histograms: Either 'all' (default) or a list of the histograms
2270            whose HAP parameters will be cleared with this phase. Histogram and
2271            phase must already be associated
2272
2273        :returns: None
2274        """
2275        if histograms == 'all':
2276            histograms = self.data['Histograms'].values()
2277        else:
2278            histograms = [self.data['Histograms'][h.name] for h in histograms]
2279
2280        for key, val in refs.items():
2281            for h in histograms:
2282                if key == 'Babinet':
2283                    try:
2284                        sets = list(val)
2285                    except ValueError:
2286                        sets = ['BabA', 'BabU']
2287                    for param in sets:
2288                        if param not in ['BabA', 'BabU']:
2289                            raise ValueError("Not sure what to do with" + param)
2290                        for hist in histograms:
2291                            hist['Babinet'][param][1] = False
2292                elif key == 'Extinction':
2293                    for h in histograms:
2294                        h['Extinction'][1] = False
2295                elif key == 'HStrain':
2296                    for h in histograms:
2297                        h['HStrain'][1] = [False for p in h['HStrain'][1]]
2298                elif key == 'Mustrain':
2299                    for h in histograms:
2300                        mustrain = h['Mustrain']
2301                        mustrain[2] = [False for p in mustrain[2]]
2302                        mustrain[5] = [False for p in mustrain[4]]
2303                elif key == 'Pref.Ori.':
2304                    for h in histograms:
2305                        h['Pref.Ori.'][2] = False
2306                elif key == 'Show':
2307                    for h in histograms:
2308                        h['Show'] = False
2309                elif key == 'Size':
2310                    for h in histograms:
2311                        size = h['Size']
2312                        size[2] = [False for p in size[2]]
2313                        size[5] = [False for p in size[5]]
2314                elif key == 'Use':
2315                    for h in histograms:
2316                        h['Use'] = False
2317                elif key == 'Scale':
2318                    for h in histograms:
2319                        h['Scale'][1] = False
2320                else:
2321                    print(u'Unknown HAP key: '+key)
2322
2323
2324##########################
2325# Command Line Interface #
2326##########################
2327# Each of these takes an argparse.Namespace object as their argument,
2328# representing the parsed command-line arguments for the relevant subcommand.
2329# The argument specification for each is in the subcommands dictionary (see
2330# below)
2331
2332
2333def create(args):
2334    """The create subcommand."""
2335    proj = G2Project(filename=args.filename)
2336
2337    hist_objs = []
2338    for h in args.histograms:
2339        hist_objs.append(proj.add_powder_histogram(h, args.iparams))
2340
2341    for p in args.phases:
2342        proj.add_phase(p, histograms=hist_objs)
2343
2344    proj.save()
2345
2346
2347def dump(args):
2348    """The dump subcommand. This is intended to be called by the :func:`main` routine.
2349    This is typically called by invoking this script with a subcommand::
2350
2351       python GSASIIscriptable.py dump <file.gpx>
2352    """
2353    if not args.histograms and not args.phases:
2354        args.raw = True
2355    if args.raw:
2356        import IPython.lib.pretty as pretty
2357
2358    for fname in args.files:
2359        if args.raw:
2360            proj, nameList = LoadDictFromProjFile(fname)
2361            print("file:", fname)
2362            print("names:", nameList)
2363            for key, val in proj.items():
2364                print(key, ":")
2365                pretty.pprint(val)
2366        else:
2367            proj = G2Project(fname)
2368            if args.histograms:
2369                hists = proj.histograms()
2370                for h in hists:
2371                    print(fname, "hist", h.id, h.name)
2372            if args.phases:
2373                phase_list = proj.phases()
2374                for p in phase_list:
2375                    print(fname, "phase", p.id, p.name)
2376
2377
2378def IPyBrowse(args):
2379    """Load a .gpx file and then open a IPython shell to browse it
2380    """
2381    for fname in args.files:
2382        proj, nameList = LoadDictFromProjFile(fname)
2383        msg = "\nfname {} loaded into proj (dict) with names in nameList".format(fname)
2384        GSASIIpath.IPyBreak_base(msg)
2385        break
2386
2387
2388def refine(args):
2389    """The refine subcommand"""
2390    proj = G2Project(args.gpxfile)
2391    if args.refinements is None:
2392        proj.refine()
2393    else:
2394        import json
2395        with open(args.refinements) as refs:
2396            refs = json.load(refs)
2397        proj.do_refinements(refs['refinements'])
2398
2399
2400def seqrefine(args):
2401    """The seqrefine subcommand"""
2402    raise NotImplementedError("seqrefine is not yet implemented")
2403
2404
2405def export(args):
2406    """The export subcommand"""
2407    # Export phase as CIF to args.exportfile
2408    proj = G2Project(args.gpxfile)
2409    phase = proj.phase(args.phase)
2410    phase.export_CIF(args.exportfile)
2411
2412
2413def _args_kwargs(*args, **kwargs):
2414    return args, kwargs
2415
2416# A dictionary of the name of each subcommand, and a tuple
2417# of its associated function and a list of its arguments
2418# The arguments are passed directly to the add_argument() method
2419# of an argparse.ArgumentParser
2420subcommands = {"create":
2421               (create, [_args_kwargs('filename',
2422                                      help='the project file to create. should end in .gpx'),
2423
2424                         _args_kwargs('-i', '--iparams',
2425                                      help='instrument parameter file'),
2426
2427                         _args_kwargs('-d', '--histograms',
2428                                      nargs='+',
2429                                      help='list of datafiles to add as histograms'),
2430
2431                         _args_kwargs('-p', '--phases',
2432                                      nargs='+',
2433                                      help='list of phases to add. phases are '
2434                                           'automatically associated with all '
2435                                           'histograms given.')]),
2436
2437               "dump": (dump, [_args_kwargs('-d', '--histograms',
2438                                     action='store_true',
2439                                     help='list histograms in files, overrides --raw'),
2440
2441                               _args_kwargs('-p', '--phases',
2442                                            action='store_true',
2443                                            help='list phases in files, overrides --raw'),
2444
2445                               _args_kwargs('-r', '--raw',
2446                                      action='store_true', help='dump raw file contents, default'),
2447
2448                               _args_kwargs('files', nargs='+')]),
2449
2450               "refine":
2451               (refine, [_args_kwargs('gpxfile', help='the project file to refine'),
2452                         _args_kwargs('refinements',
2453                                      help='json file of refinements to apply. if not present'
2454                                           ' refines file as-is',
2455                                      default=None,
2456                                      nargs='?')]),
2457
2458               "seqrefine": (seqrefine, []),
2459               "export": (export, [_args_kwargs('gpxfile',
2460                                                help='the project file from which to export'),
2461                                   _args_kwargs('phase', help='identifier of phase to export'),
2462                                   _args_kwargs('exportfile', help='the .cif file to export to')]),
2463               "browse": (IPyBrowse, [_args_kwargs('files', nargs='+',
2464                                                   help='list of files to browse')])}
2465
2466
2467def main():
2468    '''The command line interface for GSASIIscriptable.
2469
2470    Executes one of the following subcommands:
2471
2472        * :func:`create`
2473        * :func:`dump`
2474        * :func:`refine`
2475        * :func:`seqrefine`
2476        * :func:`export`
2477        * browse (:func:`IPyBrowse`)
2478
2479    These commands are typically called by invoking this script with a subcommand,
2480    for example::
2481
2482       python GSASIIscriptable.py dump <file.gpx>
2483       
2484
2485    .. seealso::
2486        :func:`create`
2487        :func:`dump`
2488        :func:`refine`
2489        :func:`seqrefine`
2490        :func:`export`
2491        :func:`IPyBrowse`
2492    '''
2493    parser = argparse.ArgumentParser()
2494    subs = parser.add_subparsers()
2495
2496    # Create all of the specified subparsers
2497    for name, (func, args) in subcommands.items():
2498        new_parser = subs.add_parser(name)
2499        for listargs, kwds in args:
2500            new_parser.add_argument(*listargs, **kwds)
2501        new_parser.set_defaults(func=func)
2502
2503    # Parse and trigger subcommand
2504    result = parser.parse_args()
2505    result.func(result)
2506
2507if __name__ == '__main__':
2508    main()
Note: See TracBrowser for help on using the repository browser.