source: trunk/GSASIIscriptable.py @ 3202

Last change on this file since 3202 was 3202, checked in by toby, 4 years ago

make scriptable Py3 ready and add new features; minor fix for new G2 installation

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