source: trunk/GSASIIscriptable.py @ 3022

Last change on this file since 3022 was 3022, checked in by odonnell, 6 years ago

new pId/hId setters, factor out hist/phase linking code

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