source: trunk/GSASIIscriptable.py @ 3356

Last change on this file since 3356 was 3294, checked in by toby, 7 years ago

more doc fixes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Author Revision URL Id
File size: 127.9 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3########### SVN repository information ###################
4# $Date: 2018-02-20 20:16:21 +0000 (Tue, 20 Feb 2018) $
5# $Author: vondreele $
6# $Revision: 3294 $
7# $URL: trunk/GSASIIscriptable.py $
8# $Id: GSASIIscriptable.py 3294 2018-02-20 20:16:21Z vondreele $
9########### SVN repository information ###################
10#
11# TODO: integrate 2d data based on a model
12#
13"""
14*GSASIIscriptable: Scripting Interface*
15=======================================
16
17Routines for reading, writing, modifying and creating GSAS-II project (.gpx) files.
18This file specifies several wrapper classes around GSAS-II data representations.
19They all inherit from :class:`G2ObjectWrapper`. The chief class is :class:`G2Project`,
20which represents an entire GSAS-II project and provides several methods to access
21phases, powder histograms, and execute Rietveld refinements. These routines can be
22accessed in two ways:
23the :ref:`CommandlineInterface` provides access a number of features without writing
24Python scripts of from the :ref:`API`, which allows much more versatile access from
25within Python.
26
27
28=====================
29Overview
30=====================
31The most commonly used routines are:
32
33:class:`G2Project`
34   Always needed: used to create a new project (.gpx) file or read in an existing one.
35
36:meth:`G2Project.add_powder_histogram`
37   Used to read in powder diffraction data to a project file.
38
39:meth:`G2Project.add_simulated_powder_histogram`
40   Defines a "dummy" powder diffraction data that will be simulated after a refinement step.
41
42:meth:`G2Project.save`
43   Writes the current project to disk.
44
45:meth:`G2Project.do_refinements`
46   This is passed a list of dictionaries, where each dict defines a refinement step.
47   Passing a list with a single empty dict initiates a refinement with the current
48   parameters and flags.
49   
50:meth:`G2Project.set_refinement`
51   This is passed a single dict which is used to set parameters and flags.
52   These actions can be performed also in :meth:`G2Project.do_refinements`.
53
54Refinement dicts
55   A refinement dict sets up a single refinement step
56   (as used in :meth:`G2Project.do_refinements` and described in
57   :ref:`Project_dicts`).
58   It specifies parameter & refinement flag changes, which are usually followed by a refinement and
59   optionally by calls to locally-defined Python functions. The keys used in these dicts are
60   defined in the :ref:`Refinement_recipe` table, below.
61
62There are several ways to set parameters project using different level objects, as is
63are described in sections below, but the simplest way to access parameters and flags
64in a project is to use the above routines.
65
66=====================
67Refinement parameters
68=====================
69The most complex part of scripting GSAS-II comes in setting up the input to control refinements, which is
70described here.
71
72.. _Project_dicts:
73
74-----------------------------
75Project-level Parameter Dict
76-----------------------------
77
78As noted below (:ref:`Refinement_parameters_kinds`), there are three types of refinement parameters,
79which can be accessed individually by the objects that encapsulate individual phases and histograms
80but it will be usually simplest to create a composite dictionary
81that is used at the project-level. A dict is created with keys
82"set" and "clear" that can be supplied to :meth:`G2Project.set_refinement`
83(or :meth:`G2Project.do_refinements`, see :ref:`Refinement_recipe` below) that will
84determine parameter values and will determine which parameters will be refined.
85
86The specific keys and subkeys that can be used are defined in tables
87:ref:`Histogram_parameters_table`, :ref:`Phase_parameters_table` and :ref:`HAP_parameters_table`.
88
89Note that optionally a list of histograms and/or phases can be supplied in the call to
90:meth:`G2Project.set_refinement`, but if not specified, the default is to use all defined
91phases and histograms.
92
93As an example:
94
95.. code-block::  python
96
97    pardict = {'set': { 'Limits': [0.8, 12.0],
98                       'Sample Parameters': ['Absorption', 'Contrast', 'DisplaceX'],
99                       'Background': {'type': 'chebyschev', 'refine': True}},
100              'clear': {'Instrument Parameters': ['U', 'V', 'W']}}
101    my_project.set_refinement(pardict)
102   
103.. _Refinement_recipe:
104   
105------------------------
106Refinement recipe
107------------------------
108Building on the :ref:`Project_dicts`,
109it is possible to specify a sequence of refinement actions as a list of
110these dicts and supplying this list
111as an argument to :meth:`G2Project.do_refinements`.
112
113As an example, this code performs the same actions as in the example in the section above:
114
115.. code-block::  python
116   
117    pardict = {'set': { 'Limits': [0.8, 12.0],
118                       'Sample Parameters': ['Absorption', 'Contrast', 'DisplaceX'],
119                       'Background': {'type': 'chebyschev', 'refine': True}},
120              'clear': {'Instrument Parameters': ['U', 'V', 'W']}}
121    my_project.do_refinements([pardict])
122
123However, in addition to setting a number of parameters, this example will perform a refinement as well.
124If more than one dict is specified in the list, as is done in this example,
125two refinement steps will be performed:
126
127.. code-block::  python
128
129    my_project.do_refinements([pardict,pardict1])
130
131
132The keys defined in the following table
133may be used in a dict supplied to :meth:`G2Project.do_refinements`. Note that now keys ``histograms``
134and ``phases`` are used to limit actions to specific sets of parameters within the project.
135
136========== ============================================================================
137key         explanation
138========== ============================================================================
139set                    Specifies a dict with keys and subkeys as described in the
140                       :ref:`Refinement_parameters_fmt` section. Items listed here
141                       will be set to be refined.
142clear                  Specifies a dict, as above for set, except that parameters are
143                       cleared and thus will not be refined.
144once                   Specifies a dict as above for set, except that parameters are
145                       set for the next cycle of refinement and are cleared once the
146                       refinement step is completed.
147skip                   Normally, once parameters are processed with a set/clear/once
148                       action(s), a refinement is started. If skip is defined as True
149                       (or any other value) the refinement step is not performed.
150output                 If a file name is specified for output is will be used to save
151                       the current refinement.
152histograms             Should contain a list of histogram(s) to be used for the
153                       set/clear/once action(s) on :ref:`Histogram_parameters_table` or
154                       :ref:`HAP_parameters_table`. Note that this will be
155                       ignored for :ref:`Phase_parameters_table`. Histograms may be
156                       specified as a list of strings [('PWDR ...'),...], indices
157                       [0,1,2] or as list of objects [hist1, hist2].
158phases                 Should contain a list of phase(s) to be used for the
159                       set/clear/once action(s) on :ref:`Phase_parameters_table` or
160                       :ref:`HAP_parameters_table`. Note that this will be
161                       ignored for :ref:`Histogram_parameters_table`.
162                       Phases may be specified as a list of strings
163                       [('Phase name'),...], indices [0,1,2] or as list of objects
164                       [phase0, phase2].
165call                   Specifies a function to call after a refinement is completed.
166                       The value supplied can be the object (typically a function)
167                       that will be called or a string that will evaluate (in the
168                       namespace inside :meth:`G2Project.iter_refinements` where
169                       ``self`` references the project.)
170                       Nothing is called if this is not specified.
171callargs               Provides a list of arguments that will be passed to the function
172                       in call (if any). If call is defined and callargs is not, the
173                       current <tt>G2Project</tt> is passed as a single argument.
174========== ============================================================================
175
176An example that performs a series of refinement steps follows:
177
178.. code-block::  python
179
180    reflist = [
181            {"set": { "Limits": { "low": 0.7 },
182                      "Background": { "no. coeffs": 3,
183                                      "refine": True }}},
184            {"set": { "LeBail": True,
185                      "Cell": True }},
186            {"set": { "Sample Parameters": ["DisplaceX"]}},
187            {"set": { "Instrument Parameters": ["U", "V", "W", "X", "Y"]}},
188            {"set": { "Mustrain": { "type": "uniaxial",
189                                    "refine": "equatorial",
190                                    "direction": [0, 0, 1]}}},
191            {"set": { "Mustrain": { "type": "uniaxial",
192                                    "refine": "axial"}}},
193            {"clear": { "LeBail": True},
194             "set": { "Atoms": { "Mn": "X" }}},
195            {"set": { "Atoms": { "O1": "X", "O2": "X" }}},]
196    my_project.do_refinements(reflist)
197   
198
199In this example, a separate refinement step will be performed for each dict in the list (since
200"skip" is not included).
201Note that in the second from last refinement step, parameters are both set and cleared.
202   
203.. _Refinement_parameters_kinds:
204
205----------------------------
206Refinement parameter types
207----------------------------
208
209Note that parameters and refinement flags used in GSAS-II fall into three classes:
210
211    * **Histogram**: There will be a set of these for each dataset loaded into a
212      project file. The parameters available depend on the type of histogram
213      (Bragg-Brentano, Single-Crystal, TOF,...). Typical Histogram parameters
214      include the overall scale factor, background, instrument and sample parameters;
215      see the :ref:`Histogram_parameters_table` table for a list of the histogram
216      parameters where access has been provided.
217     
218    * **Phase**: There will be a set of these for each phase loaded into a
219      project file. While some parameters are found in all types of phases,
220      others are only found in certain types (modulated, magnetic, protein...).
221      Typical phase parameters include unit cell lengths and atomic positions; see the
222      :ref:`Phase_parameters_table` table for a list of the phase     
223      parameters where access has been provided.
224     
225    * **Histogram-and-phase** (HAP): There is a set of these for every histogram
226      that is associated with each phase, so that if there are ``N`` phases and ``M``
227      histograms, there can be ``N*M`` total sets of "HAP" parameters sets (fewer if all
228      histograms are not linked to all phases.) Typical HAP parameters include the
229      phase fractions, sample microstrain and crystallite size broadening terms,
230      hydrostatic strain perturbations of the unit cell and preferred orientation
231      values.
232      See the :ref:`HAP_parameters_table` table for the HAP parameters where access has
233      been provided.
234
235.. _Refinement_parameters_fmt:
236
237=================================
238Specifying Refinement Parameters
239=================================
240
241Refinement parameter values and flags to turn refinement on and off are specified within dictionaries,
242where the details of these dicts are organized depends on the
243type of parameter (see :ref:`Refinement_parameters_kinds`), with a different set
244of keys (as described below) for each of the three types of parameters.
245
246.. _Histogram_parameters_table:
247
248--------------------
249Histogram parameters
250--------------------
251
252This table describes the dictionaries supplied to :func:`G2PwdrData.set_refinements`
253and :func:`G2PwdrData.clear_refinements`. As an example,
254
255.. code-block::  python
256
257   hist.set_refinements({"Background": {"no.coeffs": 3, "refine": True},
258                         "Sample Parameters": ["Scale"],
259                         "Limits": [10000, 40000]})
260
261With :meth:`G2Project.do_refinements`, these parameters should be placed inside a dict with a key
262``set``, ``clear``, or ``once``. Values will be set for all histograms, unless the ``histograms``
263key is used to define specific histograms. As an example:
264
265.. code-block::  python
266
267  gsas_proj.do_refinements([
268      {'set': {
269          'Background': {'no.coeffs': 3, 'refine': True},
270          'Sample Parameters': ['Scale'],
271          'Limits': [10000, 40000]},
272      'histograms': [1,2]}
273                            ])
274
275Note that below in the Instrument Parameters section,
276related profile parameters (such as U and V) are grouped together but
277separated by commas to save space in the table.
278
279.. tabularcolumns:: |l|l|p{3.5in}|
280
281===================== ====================  =================================================
282key                   subkey                explanation
283===================== ====================  =================================================
284Limits                                      The range of 2-theta (degrees) or TOF (in
285                                            microsec) range of values to use. Can
286                                            be either a dictionary of 'low' and/or 'high',
287                                            or a list of 2 items [low, high]
288\                     low                   Sets the low limit
289\                     high                  Sets the high limit
290
291Sample Parameters                           Should be provided as a **list** of subkeys
292                                            to set or clear, e.g. ['DisplaceX', 'Scale']
293\                     Absorption
294\                     Contrast
295\                     DisplaceX             Sample displacement along the X direction
296\                     DisplaceY             Sample displacement along the Y direction
297\                     Scale                 Histogram Scale factor
298
299Background                                  Sample background. If value is a boolean,
300                                            the background's 'refine' parameter is set
301                                            to the given boolean. Usually should be a
302                                            dictionary with any of the following keys:
303\                     type                  The background model, e.g. 'chebyschev'
304\                     refine                The value of the refine flag, boolean
305\                     no. coeffs            Number of coefficients to use, integer
306\                     coeffs                List of floats, literal values for background
307\                     FixedPoints           List of (2-theta, intensity) values for fixed points
308\                     fit fixed points      If True, triggers a fit to the fixed points to
309                                            be calculated. It is calculated when this key is
310                                            detected, regardless of calls to refine.
311
312Instrument Parameters                       As in Sample Paramters, provide as a **list** of
313                                            subkeys to
314                                            set or clear, e.g. ['X', 'Y', 'Zero', 'SH/L']
315\                     U, V, W               Gaussian peak profile terms
316\                     X, Y, Z               Lorentzian peak profile terms
317\                     alpha, beta-0,        TOF profile terms
318                      beta-1, beta-q,
319\                     sig-0, sig-1,         TOF profile terms
320                      sig-2, sig-q
321\                     difA, difB, difC      TOF Calibration constants
322\                     Zero                  Zero shift
323\                     SH/L                  Finger-Cox-Jephcoat low-angle peak asymmetry
324\                     Polariz.              Polarization parameter
325\                     Lam                   Lambda, the incident wavelength
326===================== ====================  =================================================
327
328.. _Phase_parameters_table:
329
330----------------
331Phase parameters
332----------------
333
334This table describes the dictionaries supplied to :func:`G2Phase.set_refinements`
335and :func:`G2Phase.clear_refinements`. With :meth:`G2Project.do_refinements`,
336these parameters should be placed inside a dict with a key
337``set``, ``clear``, or ``once``. Values will be set for all phases, unless the ``phases``
338key is used to define specific phase(s).
339
340
341.. tabularcolumns:: |l|p{4.5in}|
342
343======= ==========================================================
344key                   explanation
345======= ==========================================================
346Cell                  Whether or not to refine the unit cell.
347Atoms                 Dictionary of atoms and refinement flags.
348                      Each key should be an atom label, e.g.
349                      'O3', 'Mn5', and each value should be
350                      a string defining what values to refine.
351                      Values can be any combination of 'F'
352                      for fractional occupancy, 'X' for position,
353                      and 'U' for Debye-Waller factor
354LeBail                Enables LeBail intensity extraction.
355======= ==========================================================
356
357
358.. _HAP_parameters_table:
359
360
361Histogram-and-phase parameters
362------------------------------
363
364This table describes the dictionaries supplied to :func:`G2Phase.set_HAP_refinements`
365and :func:`G2Phase.clear_HAP_refinements`. When supplied to
366:meth:`G2Project.do_refinements`, these parameters should be placed inside a dict with a key
367``set``, ``clear``, or ``once``. Values will be set for all histograms used in each phase,
368unless the ``histograms`` and ``phases`` keys are used to define specific phases and histograms.
369
370.. tabularcolumns:: |l|l|p{3.5in}|
371
372=============  ==========  ============================================================
373key             subkey                 explanation
374=============  ==========  ============================================================
375Babinet                                Should be a **list** of the following
376                                       subkeys. If not, assumes both
377                                       BabA and BabU
378\               BabA
379\               BabU
380Extinction                             Boolean, True to refine.
381HStrain                                Boolean, True to refine all appropriate
382                                       $D_ij$ terms.
383Mustrain
384\               type                   Mustrain model. One of 'isotropic',
385                                       'uniaxial', or 'generalized'. Should always
386                                       be specified.
387\              direction               For uniaxial only. A list of three
388                                       integers,
389                                       the [hkl] direction of the axis.
390\               refine                 Usually boolean, set to True to refine.
391                                       When in doubt, set it to true.
392                                       For uniaxial model, can specify list
393                                       of 'axial' or 'equatorial' or a single
394                                       boolean sets both axial and equatorial.
395Size                                   Not yet implemented
396\               type                   Size broadening model. One of 'isotropic',
397                                       'uniaxial', or 'ellipsoid'. Should always
398                                       be specified.
399\              direction               For uniaxial only. A list of three
400                                       integers,
401                                       the [hkl] direction of the axis.
402\               refine                 Boolean, True to refine.
403Pref.Ori.                              Boolean, True to refine
404Show                                   Boolean, True to refine
405Use                                    Boolean, True to refine
406Scale                                  Phase fraction; Boolean, True to refine
407=============  ==========  ============================================================
408
409------------------------
410Histogram/Phase objects
411------------------------
412Each phase and powder histogram in a :class:`G2Project` object has an associated
413object. Parameters within each individual object can be turned on and off by calling
414:meth:`G2PwdrData.set_refinements` or :meth:`G2PwdrData.clear_refinements`
415for histogram parameters;
416:meth:`G2Phase.set_refinements` or :meth:`G2Phase.clear_refinements`
417for phase parameters; and :meth:`G2Phase.set_HAP_refinements` or
418: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:
419
420.. code-block::  python
421
422    params = { 'Limits': [0.8, 12.0],
423               'Sample Parameters': ['Absorption', 'Contrast', 'DisplaceX'],
424               'Background': {'type': 'chebyschev', 'refine': True}}
425    some_histogram.set_refinements(params)
426
427Likewise to turn refinement flags on, use code such as this:
428
429.. code-block::  python
430
431    params = { 'Instrument Parameters': ['U', 'V', 'W']}
432    some_histogram.set_refinements(params)
433
434and to turn these refinement flags, off use this (Note that the
435``.clear_refinements()`` methods will usually will turn off refinement even
436if a refinement parameter is set in the dict to True.):
437
438.. code-block::  python
439
440    params = { 'Instrument Parameters': ['U', 'V', 'W']}
441    some_histogram.clear_refinements(params)
442
443For phase parameters, use code such as this:
444   
445.. code-block::  python
446
447    params = { 'LeBail': True, 'Cell': True,
448               'Atoms': { 'Mn1': 'X',
449                          'O3': 'XU',
450                          'V4': 'FXU'}}
451    some_histogram.set_refinements(params)
452
453
454and here is an example for HAP parameters:
455
456.. code-block::  python
457
458    params = { 'Babinet': 'BabA',
459               'Extinction': True,
460               'Mustrain': { 'type': 'uniaxial',
461                             'direction': [0, 0, 1],
462                             'refine': True}}
463    some_phase.set_HAP_refinements(params)
464
465Note that the parameters must match the object type and method (phase vs. histogram vs. HAP).
466
467.. _CommandlineInterface:
468
469=======================================
470GSASIIscriptable Command-line Interface
471=======================================
472
473The routines described above are intended to be called from a Python script, but an
474alternate way to access some of the same functionality is to
475invoke the ``GSASIIscriptable.py`` script from
476the command line usually from within a shell script or batch file. This
477will usually be done with a command such as::
478
479       python <path/>GSASIIscriptable.py <subcommand> <file.gpx> <options>
480
481    The following subcommands are defined:
482
483        * create, see :func:`create`
484        * add, see :func:`add`
485        * dump, see :func:`dump`
486        * refine, see :func:`refine`
487        * seqrefine, see :func:`seqrefine`
488        * export, :func:`export`
489        * browse, see :func:`IPyBrowse`
490       
491Run::
492
493   python GSASIIscriptable.py --help
494
495to show the available subcommands, and inspect each subcommand with
496`python GSASIIscriptable.py <subcommand> --help` or see the documentation for each of the above routines.
497
498.. _JsonFormat:
499
500-------------------------
501Parameters in JSON files
502-------------------------
503
504The refine command requires two inputs: an existing GSAS-II project (.gpx) file and
505a JSON format file
506(see `Introducing JSON <http://json.org/>`_) that contains a single dict.
507This dict may have two keys:
508
509refinements:
510  This defines the a set of refinement steps in a JSON representation of a
511  :ref:`Refinement_recipe` list.
512
513code:
514  This optionally defines Python code that will be executed after the project is loaded,
515  but before the refinement is started. This can be used to execute Python code to change
516  parameters that are not accessible via a :ref:`Refinement_recipe` dict (note that the
517  project object is accessed with variable ``proj``) or to define code that will be called
518  later (see key ``call`` in the :ref:`Refinement_recipe` section.)
519   
520JSON website: `Introducing JSON <http://json.org/>`_.
521
522.. _API:
523
524============================================================
525GSASIIscriptable Application Layer (API)
526============================================================
527
528The large number of classes and modules in this module are described below.
529Most commonly a script will create a G2Project object using :class:`G2Project` and then
530perform actions such as
531adding a histogram (method :meth:`G2Project.add_powder_histogram`),
532adding a phase (method :meth:`G2Project.add_phase`),
533or setting parameters and performing a refinement
534(method :meth:`G2Project.do_refinements`).
535
536In some cases, it may be easier to use
537methods inside :class:`G2PwdrData` or :class:`G2Phase` or these objects
538may offer more options.
539
540---------------------------------------------------------------
541Complete Documentation: All classes and functions
542---------------------------------------------------------------
543"""
544from __future__ import division, print_function
545import argparse
546import os.path as ospath
547import datetime as dt
548import sys
549import platform
550if '2' in platform.python_version_tuple()[0]:
551    import cPickle
552    strtypes = (str,unicode)
553else:
554    import _pickle as cPickle
555    strtypes = (str,bytes)
556import imp
557import copy
558import os
559import random as ran
560
561import numpy.ma as ma
562import scipy.interpolate as si
563import numpy as np
564import scipy as sp
565
566import GSASIIpath
567GSASIIpath.SetBinaryPath(True)  # for now, this is needed before some of these modules can be imported
568import GSASIIobj as G2obj
569import GSASIIpwd as G2pwd
570import GSASIIstrMain as G2strMain
571import GSASIIspc as G2spc
572import GSASIIElem as G2elem
573
574
575# Delay imports to not slow down small scripts
576G2fil = None
577PwdrDataReaders = []
578PhaseReaders = []
579exportersByExtension = {}
580'''Specifies the list of extensions that are supported for Powder data export'''
581
582def LoadG2fil():
583    """Delay importing this module, it is slow"""
584    global G2fil
585    if G2fil is None:
586        import GSASIIfiles
587        G2fil = GSASIIfiles
588        global PwdrDataReaders
589        global PhaseReaders
590        PwdrDataReaders = G2fil.LoadImportRoutines("pwd", "Powder_Data")
591        PhaseReaders = G2fil.LoadImportRoutines("phase", "Phase")
592        AllExporters = G2fil.LoadExportRoutines(None)
593        global exportersByExtension
594        exportersByExtension = {}
595        for obj in AllExporters:
596            try:
597                obj.Writer
598            except AttributeError:
599                continue
600            for typ in obj.exporttype:
601                if typ not in exportersByExtension:
602                    exportersByExtension[typ] = {obj.extension:obj}
603                else:
604                    exportersByExtension[typ][obj.extension] = obj
605
606def LoadDictFromProjFile(ProjFile):
607    '''Read a GSAS-II project file and load items to dictionary
608   
609    :param str ProjFile: GSAS-II project (name.gpx) full file name
610    :returns: Project,nameList, where
611
612      * Project (dict) is a representation of gpx file following the GSAS-II tree structure
613        for each item: key = tree name (e.g. 'Controls','Restraints',etc.), data is dict
614        data dict = {'data':item data whch may be list, dict or None,'subitems':subdata (if any)}
615      * nameList (list) has names of main tree entries & subentries used to reconstruct project file
616
617    Example for fap.gpx::
618
619      Project = {                 #NB:dict order is not tree order
620        u'Phases':{'data':None,'fap':{phase dict}},
621        u'PWDR FAP.XRA Bank 1':{'data':[histogram data list],'Comments':comments,'Limits':limits, etc},
622        u'Rigid bodies':{'data': {rigid body dict}},
623        u'Covariance':{'data':{covariance data dict}},
624        u'Controls':{'data':{controls data dict}},
625        u'Notebook':{'data':[notebook list]},
626        u'Restraints':{'data':{restraint data dict}},
627        u'Constraints':{'data':{constraint data dict}}]
628        }
629      nameList = [                #NB: reproduces tree order
630        [u'Notebook',],
631        [u'Controls',],
632        [u'Covariance',],
633        [u'Constraints',],
634        [u'Restraints',],
635        [u'Rigid bodies',],
636        [u'PWDR FAP.XRA Bank 1',
637             u'Comments',
638             u'Limits',
639             u'Background',
640             u'Instrument Parameters',
641             u'Sample Parameters',
642             u'Peak List',
643             u'Index Peak List',
644             u'Unit Cells List',
645             u'Reflection Lists'],
646        [u'Phases', u'fap']
647        ]
648    '''
649    # Let IOError be thrown if file does not exist
650    # if not ospath.exists(ProjFile):
651    #     print ('\n*** Error attempt to open project file that does not exist:\n   '+
652    #         str(ProjFile))
653    #     return
654    file = open(ProjFile,'rb')
655    # print('loading from file: {}'.format(ProjFile))
656    Project = {}
657    nameList = []
658    try:
659        while True:
660            try:
661                data = cPickle.load(file)
662            except EOFError:
663                break
664            datum = data[0]
665            Project[datum[0]] = {'data':datum[1]}
666            nameList.append([datum[0],])
667            for datus in data[1:]:
668                Project[datum[0]][datus[0]] = datus[1]
669                nameList[-1].append(datus[0])
670        file.close()
671        # print('project load successful')
672    except:
673        raise IOError("Error reading file "+str(ProjFile)+". This is not a GSAS-II .gpx file")
674    finally:
675        file.close()
676    return Project,nameList
677
678def SaveDictToProjFile(Project,nameList,ProjFile):
679    '''Save a GSAS-II project file from dictionary/nameList created by LoadDictFromProjFile
680
681    :param dict Project: representation of gpx file following the GSAS-II
682        tree structure as described for LoadDictFromProjFile
683    :param list nameList: names of main tree entries & subentries used to reconstruct project file
684    :param str ProjFile: full file name for output project.gpx file (including extension)
685    '''
686    file = open(ProjFile,'wb')
687    try:
688        for name in nameList:
689            data = []
690            item = Project[name[0]]
691            data.append([name[0],item['data']])
692            for item2 in name[1:]:
693                data.append([item2,item[item2]])
694            cPickle.dump(data,file,1)
695    finally:
696        file.close()
697
698# def ImportPowder(reader,filename):
699#     '''Use a reader to import a powder diffraction data file
700
701#     :param str reader: a scriptable reader
702#     :param str filename: full name of powder data file; can be "multi-Bank" data
703
704#     :returns: list rdlist: list of reader objects containing powder data, one for each
705#         "Bank" of data encountered in file. Items in reader object of interest are:
706
707#           * rd.comments: list of str: comments found on powder file
708#           * rd.dnames: list of str: data nammes suitable for use in GSASII data tree NB: duplicated in all rd entries in rdlist
709#           * rd.powderdata: list of numpy arrays: pos,int,wt,zeros,zeros,zeros as needed for a PWDR entry in  GSASII data tree.
710#     '''
711#     rdfile,rdpath,descr = imp.find_module(reader)
712#     rdclass = imp.load_module(reader,rdfile,rdpath,descr)
713#     rd = rdclass.GSAS_ReaderClass()
714#     if not rd.scriptable:
715#         print(u'**** ERROR: '+reader+u' is not a scriptable reader')
716#         return None
717#     rdlist = []
718#     if rd.ContentsValidator(filename):
719#         repeat = True
720#         rdbuffer = {} # create temporary storage for file reader
721#         block = 0
722#         while repeat: # loop if the reader asks for another pass on the file
723#             block += 1
724#             repeat = False
725#             rd.objname = ospath.basename(filename)
726#             flag = rd.Reader(filename,None,buffer=rdbuffer,blocknum=block,)
727#             if flag:
728#                 rdlist.append(copy.deepcopy(rd)) # save the result before it is written over
729#                 if rd.repeat:
730#                     repeat = True
731#         return rdlist
732#     print(rd.errors)
733#     return None
734
735def SetDefaultDData(dType,histoName,NShkl=0,NDij=0):
736    '''Create an initial Histogram dictionary
737
738    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
739    '''
740    if dType in ['SXC','SNC']:
741        return {'Histogram':histoName,'Show':False,'Scale':[1.0,True],
742            'Babinet':{'BabA':[0.0,False],'BabU':[0.0,False]},
743            'Extinction':['Lorentzian','None', {'Tbar':0.1,'Cos2TM':0.955,
744            'Eg':[1.e-10,False],'Es':[1.e-10,False],'Ep':[1.e-10,False]}],
745            'Flack':[0.0,False]}
746    elif dType == 'SNT':
747        return {'Histogram':histoName,'Show':False,'Scale':[1.0,True],
748            'Babinet':{'BabA':[0.0,False],'BabU':[0.0,False]},
749            'Extinction':['Lorentzian','None', {
750            'Eg':[1.e-10,False],'Es':[1.e-10,False],'Ep':[1.e-10,False]}]}
751    elif 'P' in dType:
752        return {'Histogram':histoName,'Show':False,'Scale':[1.0,False],
753            'Pref.Ori.':['MD',1.0,False,[0,0,1],0,{},[],0.1],
754            'Size':['isotropic',[1.,1.,1.],[False,False,False],[0,0,1],
755                [1.,1.,1.,0.,0.,0.],6*[False,]],
756            'Mustrain':['isotropic',[1000.0,1000.0,1.0],[False,False,False],[0,0,1],
757                NShkl*[0.01,],NShkl*[False,]],
758            'HStrain':[NDij*[0.0,],NDij*[False,]],
759            'Extinction':[0.0,False],'Babinet':{'BabA':[0.0,False],'BabU':[0.0,False]}}
760
761
762def PreSetup(data):
763    '''Create part of an initial (empty) phase dictionary
764
765    from GSASIIphsGUI.py, near end of UpdatePhaseData
766
767    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
768    '''
769    if 'RBModels' not in data:
770        data['RBModels'] = {}
771    if 'MCSA' not in data:
772        data['MCSA'] = {'Models':[{'Type':'MD','Coef':[1.0,False,[.8,1.2],],'axis':[0,0,1]}],'Results':[],'AtInfo':{}}
773    if 'dict' in str(type(data['MCSA']['Results'])):
774        data['MCSA']['Results'] = []
775    if 'Modulated' not in data['General']:
776        data['General']['Modulated'] = False
777#    if 'modulated' in data['General']['Type']:
778#        data['General']['Modulated'] = True
779#        data['General']['Type'] = 'nuclear'
780
781
782def SetupGeneral(data, dirname):
783    """Helps initialize phase data.
784
785    From GSASIIphsGui.py, function of the same name. Minor changes for imports etc.
786
787    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
788    """
789    mapDefault = {'MapType':'','RefList':'','Resolution':0.5,'Show bonds':True,
790                'rho':[],'rhoMax':0.,'mapSize':10.0,'cutOff':50.,'Flip':False}
791    generalData = data['General']
792    atomData = data['Atoms']
793    generalData['AtomTypes'] = []
794    generalData['Isotopes'] = {}
795
796    if 'Isotope' not in generalData:
797        generalData['Isotope'] = {}
798    if 'Data plot type' not in generalData:
799        generalData['Data plot type'] = 'Mustrain'
800    if 'POhkl' not in generalData:
801        generalData['POhkl'] = [0,0,1]
802    if 'Map' not in generalData:
803        generalData['Map'] = mapDefault.copy()
804    if 'Flip' not in generalData:
805        generalData['Flip'] = {'RefList':'','Resolution':0.5,'Norm element':'None',
806            'k-factor':0.1,'k-Max':20.,}
807    if 'testHKL' not in generalData['Flip']:
808        generalData['Flip']['testHKL'] = [[0,0,2],[2,0,0],[1,1,1],[0,2,0],[1,2,3]]
809    if 'doPawley' not in generalData:
810        generalData['doPawley'] = False     #ToDo: change to ''
811    if 'Pawley dmin' not in generalData:
812        generalData['Pawley dmin'] = 1.0
813    if 'Pawley neg wt' not in generalData:
814        generalData['Pawley neg wt'] = 0.0
815    if 'Algolrithm' in generalData.get('MCSA controls',{}) or \
816        'MCSA controls' not in generalData:
817        generalData['MCSA controls'] = {'Data source':'','Annealing':[50.,0.001,50],
818        'dmin':2.0,'Algorithm':'log','Jump coeff':[0.95,0.5],'boltzmann':1.0,
819        'fast parms':[1.0,1.0,1.0],'log slope':0.9,'Cycles':1,'Results':[],'newDmin':True}
820    if 'AtomPtrs' not in generalData:
821        generalData['AtomPtrs'] = [3,1,7,9]
822        if generalData['Type'] == 'macromolecular':
823            generalData['AtomPtrs'] = [6,4,10,12]
824        elif generalData['Type'] == 'magnetic':
825            generalData['AtomPtrs'] = [3,1,10,12]
826    if generalData['Modulated']:
827        generalData['Type'] = 'nuclear'
828        if 'Super' not in generalData:
829            generalData['Super'] = 1
830            generalData['SuperVec'] = [[0,0,.1],False,4]
831            generalData['SSGData'] = {}
832        if '4DmapData' not in generalData:
833            generalData['4DmapData'] = mapDefault.copy()
834            generalData['4DmapData'].update({'MapType':'Fobs'})
835    if 'Modulated' not in generalData:
836        generalData['Modulated'] = False
837    if 'HydIds' not in generalData:
838        generalData['HydIds'] = {}
839    cx,ct,cs,cia = generalData['AtomPtrs']
840    generalData['NoAtoms'] = {}
841    generalData['BondRadii'] = []
842    generalData['AngleRadii'] = []
843    generalData['vdWRadii'] = []
844    generalData['AtomMass'] = []
845    generalData['Color'] = []
846    if generalData['Type'] == 'magnetic':
847        generalData['MagDmin'] = generalData.get('MagDmin',1.0)
848        landeg = generalData.get('Lande g',[])
849    generalData['Mydir'] = dirname
850    badList = {}
851    for iat,atom in enumerate(atomData):
852        atom[ct] = atom[ct].lower().capitalize()              #force to standard form
853        if generalData['AtomTypes'].count(atom[ct]):
854            generalData['NoAtoms'][atom[ct]] += atom[cx+3]*float(atom[cs+1])
855        elif atom[ct] != 'UNK':
856            Info = G2elem.GetAtomInfo(atom[ct])
857            if not Info:
858                if atom[ct] not in badList:
859                    badList[atom[ct]] = 0
860                badList[atom[ct]] += 1
861                atom[ct] = 'UNK'
862                continue
863            atom[ct] = Info['Symbol'] # N.B. symbol might be changed by GetAtomInfo
864            generalData['AtomTypes'].append(atom[ct])
865            generalData['Z'] = Info['Z']
866            generalData['Isotopes'][atom[ct]] = Info['Isotopes']
867            generalData['BondRadii'].append(Info['Drad'])
868            generalData['AngleRadii'].append(Info['Arad'])
869            generalData['vdWRadii'].append(Info['Vdrad'])
870            if atom[ct] in generalData['Isotope']:
871                if generalData['Isotope'][atom[ct]] not in generalData['Isotopes'][atom[ct]]:
872                    isotope = list(generalData['Isotopes'][atom[ct]].keys())[-1]
873                    generalData['Isotope'][atom[ct]] = isotope
874                generalData['AtomMass'].append(Info['Isotopes'][generalData['Isotope'][atom[ct]]]['Mass'])
875            else:
876                generalData['Isotope'][atom[ct]] = 'Nat. Abund.'
877                if 'Nat. Abund.' not in generalData['Isotopes'][atom[ct]]:
878                    isotope = list(generalData['Isotopes'][atom[ct]].keys())[-1]
879                    generalData['Isotope'][atom[ct]] = isotope
880                generalData['AtomMass'].append(Info['Mass'])
881            generalData['NoAtoms'][atom[ct]] = atom[cx+3]*float(atom[cs+1])
882            generalData['Color'].append(Info['Color'])
883            if generalData['Type'] == 'magnetic':
884                if len(landeg) < len(generalData['AtomTypes']):
885                    landeg.append(2.0)
886    if generalData['Type'] == 'magnetic':
887        generalData['Lande g'] = landeg[:len(generalData['AtomTypes'])]
888
889    if badList:
890        msg = 'Warning: element symbol(s) not found:'
891        for key in badList:
892            msg += '\n\t' + key
893            if badList[key] > 1:
894                msg += ' (' + str(badList[key]) + ' times)'
895        raise G2ScriptException("Phase error:\n" + msg)
896        # wx.MessageBox(msg,caption='Element symbol error')
897    F000X = 0.
898    F000N = 0.
899    for i,elem in enumerate(generalData['AtomTypes']):
900        F000X += generalData['NoAtoms'][elem]*generalData['Z']
901        isotope = generalData['Isotope'][elem]
902        F000N += generalData['NoAtoms'][elem]*generalData['Isotopes'][elem][isotope]['SL'][0]
903    generalData['F000X'] = F000X
904    generalData['F000N'] = F000N
905    import GSASIImath as G2mth
906    generalData['Mass'] = G2mth.getMass(generalData)
907
908
909def make_empty_project(author=None, filename=None):
910    """Creates an dictionary in the style of GSASIIscriptable, for an empty
911    project.
912
913    If no author name or filename is supplied, 'no name' and
914    <current dir>/test_output.gpx are used , respectively.
915
916    Returns: project dictionary, name list
917
918    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
919    """
920    if not filename:
921        filename = 'test_output.gpx'
922    filename = os.path.abspath(filename)
923    gsasii_version = str(GSASIIpath.GetVersionNumber())
924    LoadG2fil()
925    import matplotlib as mpl
926    python_library_versions = G2fil.get_python_versions([mpl, np, sp])
927
928    controls_data = dict(G2obj.DefaultControls)
929    controls_data['LastSavedAs'] = filename
930    controls_data['LastSavedUsing'] = gsasii_version
931    controls_data['PythonVersions'] = python_library_versions
932    if author:
933        controls_data['Author'] = author
934
935    output = {'Constraints': {'data': {'HAP': [], 'Hist': [], 'Phase': [],
936                                       'Global': []}},
937              'Controls': {'data': controls_data},
938              u'Covariance': {'data': {}},
939              u'Notebook': {'data': ['']},
940              u'Restraints': {'data': {}},
941              u'Rigid bodies': {'data': {'RBIds': {'Residue': [], 'Vector': []},
942                                'Residue': {'AtInfo': {}},
943                                'Vector':  {'AtInfo': {}}}}}
944
945    names = [[u'Notebook'], [u'Controls'], [u'Covariance'],
946             [u'Constraints'], [u'Restraints'], [u'Rigid bodies']]
947
948    return output, names
949
950
951class G2ImportException(Exception):
952    pass
953
954class G2ScriptException(Exception):
955    pass
956
957def import_generic(filename, readerlist, fmthint=None):
958    """Attempt to import a filename, using a list of reader objects.
959
960    Returns the first reader object which worked."""
961    # Translated from OnImportGeneric method in GSASII.py
962    primaryReaders, secondaryReaders = [], []
963    for reader in readerlist:
964        if fmthint is not None and fmthint not in reader.formatName: continue
965        flag = reader.ExtensionValidator(filename)
966        if flag is None:
967            secondaryReaders.append(reader)
968        elif flag:
969            primaryReaders.append(reader)
970    if not secondaryReaders and not primaryReaders:
971        raise G2ImportException("Could not read file: ", filename)
972
973    with open(filename, 'Ur') as fp:
974        rd_list = []
975
976        for rd in primaryReaders + secondaryReaders:
977            # Initialize reader
978            rd.selections = []
979            rd.dnames = []
980            rd.ReInitialize()
981            # Rewind file
982            rd.errors = ""
983            if not rd.ContentsValidator(filename):
984                # Report error
985                pass
986            if len(rd.selections) > 1:
987                # Select data?
988                # GSASII.py:543
989                raise G2ImportException("Not sure what data to select")
990
991            block = 0
992            rdbuffer = {}
993            repeat = True
994            while repeat:
995                repeat = False
996                block += 1
997                rd.objname = os.path.basename(filename)
998                try:
999                    flag = rd.Reader(filename,buffer=rdbuffer, blocknum=block)
1000                except:
1001                    flag = False
1002                if flag:
1003                    # Omitting image loading special cases
1004                    rd.readfilename = filename
1005                    rd_list.append(copy.deepcopy(rd))
1006                    repeat = rd.repeat
1007                else:
1008                    if GSASIIpath.GetConfigValue('debug'): print("{} Reader failed to read {}".format(rd.formatName,filename))
1009            if rd_list:
1010                if rd.warnings:
1011                    print("Read warning by", rd.formatName, "reader:",
1012                          rd.warnings, file=sys.stderr)
1013                else:
1014                    print("{} read by Reader {}\n".format(filename,rd.formatName))                   
1015                return rd_list
1016    raise G2ImportException("No reader could read file: " + filename)
1017
1018
1019def load_iprms(instfile, reader):
1020    """Loads instrument parameters from a file, and edits the
1021    given reader.
1022
1023    Returns a 2-tuple of (Iparm1, Iparm2) parameters
1024    """
1025    LoadG2fil()
1026    ext = os.path.splitext(instfile)[1]
1027
1028    if ext.lower() == '.instprm':
1029        # New GSAS File, load appropriately
1030        with open(instfile) as f:
1031            lines = f.readlines()
1032        bank = reader.powderentry[2]
1033        numbanks = reader.numbanks
1034        iparms = G2fil.ReadPowderInstprm(lines, bank, numbanks, reader)
1035
1036        reader.instfile = instfile
1037        reader.instmsg = 'GSAS-II file' + instfile
1038        return iparms
1039    elif ext.lower() not in ('.prm', '.inst', '.ins'):
1040        raise ValueError('Expected .prm file, found: ', instfile)
1041
1042    # It's an old GSAS file, load appropriately
1043    Iparm = {}
1044    with open(instfile, 'Ur') as fp:
1045        for line in fp:
1046            if '#' in line:
1047                continue
1048            Iparm[line[:12]] = line[12:-1]
1049    ibanks = int(Iparm.get('INS   BANK  ', '1').strip())
1050    if ibanks == 1:
1051        reader.instbank = 1
1052        reader.powderentry[2] = 1
1053        reader.instfile = instfile
1054        reader.instmsg = instfile + ' bank ' + str(reader.instbank)
1055        return G2fil.SetPowderInstParms(Iparm, reader)
1056    # TODO handle >1 banks
1057    raise NotImplementedError("Check GSASIIfiles.py: ReadPowderInstprm")
1058
1059def load_pwd_from_reader(reader, instprm, existingnames=[]):
1060    """Loads powder data from a reader object, and assembles it into a GSASII data tree.
1061
1062    :returns: (name, tree) - 2-tuple of the histogram name (str), and data
1063
1064    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
1065    """
1066    HistName = 'PWDR ' + G2obj.StripUnicode(reader.idstring, '_')
1067    HistName = G2obj.MakeUniqueLabel(HistName, existingnames)
1068
1069    try:
1070        Iparm1, Iparm2 = instprm
1071    except ValueError:
1072        Iparm1, Iparm2 = load_iprms(instprm, reader)
1073
1074    Ymin = np.min(reader.powderdata[1])
1075    Ymax = np.max(reader.powderdata[1])
1076    valuesdict = {'wtFactor': 1.0,
1077                  'Dummy': False,
1078                  'ranId': ran.randint(0, sys.maxsize),
1079                  'Offset': [0.0, 0.0], 'delOffset': 0.02*Ymax,
1080                  'refOffset': -0.1*Ymax, 'refDelt': 0.1*Ymax,
1081                  'qPlot': False, 'dPlot': False, 'sqrtPlot': False,
1082                  'Yminmax': [Ymin, Ymax]}
1083    reader.Sample['ranId'] = valuesdict['ranId']
1084
1085    # Ending keys:
1086    # [u'Reflection Lists',
1087    #  u'Limits',
1088    #  'data',
1089    #  u'Index Peak List',
1090    #  u'Comments',
1091    #  u'Unit Cells List',
1092    #  u'Sample Parameters',
1093    #  u'Peak List',
1094    #  u'Background',
1095    #  u'Instrument Parameters']
1096    Tmin = np.min(reader.powderdata[0])
1097    Tmax = np.max(reader.powderdata[0])
1098
1099    default_background = [['chebyschev', False, 3, 1.0, 0.0, 0.0],
1100                          {'nDebye': 0, 'debyeTerms': [], 'nPeaks': 0, 'peaksList': []}]
1101
1102    output_dict = {u'Reflection Lists': {},
1103                   u'Limits': reader.pwdparms.get('Limits', [(Tmin, Tmax), [Tmin, Tmax]]),
1104                   u'data': [valuesdict, reader.powderdata, HistName],
1105                   u'Index Peak List': [[], []],
1106                   u'Comments': reader.comments,
1107                   u'Unit Cells List': [],
1108                   u'Sample Parameters': reader.Sample,
1109                   u'Peak List': {'peaks': [], 'sigDict': {}},
1110                   u'Background': reader.pwdparms.get('Background', default_background),
1111                   u'Instrument Parameters': [Iparm1, Iparm2],
1112                   }
1113
1114    names = [u'Comments',
1115             u'Limits',
1116             u'Background',
1117             u'Instrument Parameters',
1118             u'Sample Parameters',
1119             u'Peak List',
1120             u'Index Peak List',
1121             u'Unit Cells List',
1122             u'Reflection Lists']
1123
1124    # TODO controls?? GSASII.py:1664-7
1125
1126    return HistName, [HistName] + names, output_dict
1127
1128
1129def _deep_copy_into(from_, into):
1130    """Helper function for reloading .gpx file. See G2Project.reload()
1131
1132    :author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
1133    """
1134    if isinstance(from_, dict) and isinstance(into, dict):
1135        combined_keys = set(from_.keys()).union(into.keys())
1136        for key in combined_keys:
1137            if key in from_ and key in into:
1138                both_dicts = (isinstance(from_[key], dict)
1139                              and isinstance(into[key], dict))
1140                both_lists = (isinstance(from_[key], list)
1141                              and isinstance(into[key], list))
1142                if both_dicts or both_lists:
1143                    _deep_copy_into(from_[key], into[key])
1144                else:
1145                    into[key] = from_[key]
1146            elif key in from_:
1147                into[key] = from_[key]
1148            else:  # key in into
1149                del into[key]
1150    elif isinstance(from_, list) and isinstance(into, list):
1151        if len(from_) == len(into):
1152            for i in range(len(from_)):
1153                both_dicts = (isinstance(from_[i], dict)
1154                              and isinstance(into[i], dict))
1155                both_lists = (isinstance(from_[i], list)
1156                              and isinstance(into[i], list))
1157                if both_dicts or both_lists:
1158                    _deep_copy_into(from_[i], into[i])
1159                else:
1160                    into[i] = from_[i]
1161        else:
1162            into[:] = from_
1163
1164
1165class G2ObjectWrapper(object):
1166    """Base class for all GSAS-II object wrappers.
1167
1168    The underlying GSAS-II format can be accessed as `wrapper.data`. A number
1169    of overrides are implemented so that the wrapper behaves like a dictionary.
1170
1171    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
1172    """
1173    def __init__(self, datadict):
1174        self.data = datadict
1175
1176    def __getitem__(self, key):
1177        return self.data[key]
1178
1179    def __setitem__(self, key, value):
1180        self.data[key] = value
1181
1182    def __contains__(self, key):
1183        return key in self.data
1184
1185    def get(self, k, d=None):
1186        return self.data.get(k, d)
1187
1188    def keys(self):
1189        return self.data.keys()
1190
1191    def values(self):
1192        return self.data.values()
1193
1194    def items(self):
1195        return self.data.items()
1196
1197
1198class G2Project(G2ObjectWrapper):
1199    """
1200    Represents an entire GSAS-II project.
1201
1202    There are two ways to initialize it:
1203
1204    >>> # Load an existing project file
1205    >>> proj = G2Project('filename.gpx')
1206   
1207    >>> # Create a new project
1208    >>> proj = G2Project(newgpx='new_file.gpx')
1209   
1210    Histograms can be accessed easily.
1211
1212    >>> # By name
1213    >>> hist = proj.histogram('PWDR my-histogram-name')
1214   
1215    >>> # Or by index
1216    >>> hist = proj.histogram(0)
1217    >>> assert hist.id == 0
1218   
1219    >>> # Or by random id
1220    >>> assert hist == proj.histogram(hist.ranId)
1221
1222    Phases can be accessed the same way.
1223
1224    >>> phase = proj.phase('name of phase')
1225
1226    New data can also be loaded via :meth:`~G2Project.add_phase` and
1227    :meth:`~G2Project.add_powder_histogram`.
1228
1229    >>> hist = proj.add_powder_histogram('some_data_file.chi',
1230                                         'instrument_parameters.prm')
1231    >>> phase = proj.add_phase('my_phase.cif', histograms=[hist])
1232
1233    Parameters for Rietveld refinement can be turned on and off as well.
1234    See :meth:`~G2Project.set_refinement`, :meth:`~G2Project.clear_refinements`,
1235    :meth:`~G2Project.iter_refinements`, :meth:`~G2Project.do_refinements`.
1236    """
1237    def __init__(self, gpxfile=None, author=None, filename=None, newgpx=None):
1238        """Loads a GSAS-II project from a specified filename.
1239
1240        :param str gpxfile: Existing .gpx file to be loaded. If nonexistent,
1241            creates an empty project.
1242        :param str author: Author's name (not yet implemented)
1243        :param str newgpx: The filename the project should be saved to in
1244            the future. If both newgpx and gpxfile are present, the project is
1245            loaded from the gpxfile, then when saved will be written to newgpx.
1246        :param str filename: Name to be used to save the project. Has same function as
1247            parameter newgpx (do not use both gpxfile and filename). Use of newgpx
1248            is preferred over filename.
1249        """
1250        if filename is not None and newgpx is not None:
1251            raise G2ScriptException('Do not use filename and newgpx together')
1252        elif newgpx is not None:
1253            filename = newgpx
1254        if gpxfile is None:
1255            filename = os.path.abspath(os.path.expanduser(filename))
1256            self.filename = filename
1257            self.data, self.names = make_empty_project(author=author, filename=filename)
1258        elif isinstance(gpxfile, str): # TODO: create replacement for isinstance that checks if path exists
1259                                       # filename is valid, etc.
1260            if isinstance(filename, str): 
1261                self.filename = os.path.abspath(os.path.expanduser(filename)) # both are defined
1262            else: 
1263                self.filename = os.path.abspath(os.path.expanduser(gpxfile))
1264            # TODO set author
1265            self.data, self.names = LoadDictFromProjFile(gpxfile)
1266            self.update_ids()
1267        else:
1268            raise ValueError("Not sure what to do with gpxfile")
1269
1270    @classmethod
1271    def from_dict_and_names(cls, gpxdict, names, filename=None):
1272        """Creates a :class:`G2Project` directly from
1273        a dictionary and a list of names. If in doubt, do not use this.
1274
1275        :returns: a :class:`G2Project`
1276        """
1277        out = cls()
1278        if filename:
1279            filename = os.path.abspath(os.path.expanduser(filename))
1280            out.filename = filename
1281            gpxdict['Controls']['data']['LastSavedAs'] = filename
1282        else:
1283            try:
1284                out.filename = gpxdict['Controls']['data']['LastSavedAs']
1285            except KeyError:
1286                out.filename = None
1287        out.data = gpxdict
1288        out.names = names
1289
1290    def save(self, filename=None):
1291        """Saves the project, either to the current filename, or to a new file.
1292
1293        Updates self.filename if a new filename provided"""
1294        # TODO update LastSavedUsing ?
1295        if filename:
1296            filename = os.path.abspath(os.path.expanduser(filename))
1297            self.data['Controls']['data']['LastSavedAs'] = filename
1298            self.filename = filename
1299        elif not self.filename:
1300            raise AttributeError("No file name to save to")
1301        SaveDictToProjFile(self.data, self.names, self.filename)
1302
1303    def add_powder_histogram(self, datafile, iparams, phases=[], fmthint=None):
1304        """Loads a powder data histogram into the project.
1305
1306        Automatically checks for an instrument parameter file, or one can be
1307        provided. Note that in unix fashion, "~" can be used to indicate the
1308        home directory (e.g. ~/G2data/data.fxye).
1309
1310        :param str datafile: The powder data file to read, a filename.
1311        :param str iparams: The instrument parameters file, a filename.
1312        :param list phases: Phases to link to the new histogram
1313        :param str fmthint: If specified, only importers where the format name
1314          (reader.formatName, as shown in Import menu) contains the
1315          supplied string will be tried as importers. If not specified, all
1316          importers consistent with the file extension will be tried
1317          (equivalent to "guess format" in menu).
1318
1319        :returns: A :class:`G2PwdrData` object representing
1320            the histogram
1321        """
1322        LoadG2fil()
1323        datafile = os.path.abspath(os.path.expanduser(datafile))
1324        iparams = os.path.abspath(os.path.expanduser(iparams))
1325        pwdrreaders = import_generic(datafile, PwdrDataReaders,fmthint=fmthint)
1326        histname, new_names, pwdrdata = load_pwd_from_reader(
1327                                          pwdrreaders[0], iparams,
1328                                          [h.name for h in self.histograms()])
1329        if histname in self.data:
1330            print("Warning - redefining histogram", histname)
1331        elif self.names[-1][0] == 'Phases':
1332            self.names.insert(-1, new_names)
1333        else:
1334            self.names.append(new_names)
1335        self.data[histname] = pwdrdata
1336        self.update_ids()
1337
1338        for phase in phases:
1339            phase = self.phase(phase)
1340            self.link_histogram_phase(histname, phase)
1341
1342        return self.histogram(histname)
1343
1344    def add_simulated_powder_histogram(self, histname, iparams, Tmin, Tmax, Tstep,
1345                                       wavelength=None, scale=None, phases=[]):
1346        """Loads a powder data histogram into the project.
1347
1348        Requires an instrument parameter file.
1349        Note that in unix fashion, "~" can be used to indicate the
1350        home directory (e.g. ~/G2data/data.prm). The instrument parameter file
1351        will determine if the histogram is x-ray, CW neutron, TOF, etc. as well
1352        as the instrument type.
1353
1354        :param str histname: A name for the histogram to be created.
1355        :param str iparams: The instrument parameters file, a filename.
1356        :param float Tmin: Minimum 2theta or TOF (ms) for dataset to be simulated
1357        :param float Tmax: Maximum 2theta or TOF (ms) for dataset to be simulated
1358        :param float Tstep: Step size in 2theta or TOF (ms) for dataset to be simulated       
1359        :param float wavelength: Wavelength for CW instruments, overriding the value
1360           in the instrument parameters file if specified.
1361        :param float scale: Histogram scale factor which multiplies the pattern. Note that
1362           simulated noise is added to the pattern, so that if the maximum intensity is
1363           small, the noise will mask the computed pattern. The scale
1364           needs to be a large number for CW neutrons.
1365           The default, None, provides a scale of 1 for x-rays and TOF; 10,000 for CW neutrons.
1366        :param list phases: Phases to link to the new histogram. Use proj.phases() to link to
1367           all defined phases.
1368
1369        :returns: A :class:`G2PwdrData` object representing the histogram
1370        """
1371        LoadG2fil()
1372        iparams = os.path.abspath(os.path.expanduser(iparams))
1373        if not os.path.exists(iparams):
1374            raise G2ScriptException("File does not exist:"+iparams)
1375        rd = G2obj.ImportPowderData( # Initialize a base class reader
1376            extensionlist=tuple(),
1377            strictExtension=False,
1378            formatName = 'Simulate dataset',
1379            longFormatName = 'Compute a simulated pattern')
1380        rd.powderentry[0] = '' # no filename
1381        rd.powderentry[2] = 1 # only one bank
1382        rd.comments.append('This is a dummy dataset for powder pattern simulation')
1383        #Iparm1, Iparm2 = load_iprms(iparams, rd)
1384        if Tmax < Tmin:
1385            Tmin,Tmax = Tmax,Tmin
1386        Tstep = abs(Tstep)
1387        if 'TOF' in rd.idstring:
1388                N = (np.log(Tmax)-np.log(Tmin))/Tstep
1389                x = np.exp((np.arange(0,N))*Tstep+np.log(Tmin*1000.))
1390                N = len(x)
1391        else:           
1392                N = int((Tmax-Tmin)/Tstep)+1
1393                x = np.linspace(Tmin,Tmax,N,True)
1394                N = len(x)
1395        if N < 3:
1396            raise G2ScriptException("Error: Range is too small or step is too large, <3 points")
1397        rd.powderdata = [
1398            np.array(x), # x-axis values
1399            np.zeros_like(x), # powder pattern intensities
1400            np.ones_like(x), # 1/sig(intensity)^2 values (weights)
1401            np.zeros_like(x), # calc. intensities (zero)
1402            np.zeros_like(x), # calc. background (zero)
1403            np.zeros_like(x), # obs-calc profiles
1404            ]
1405        Tmin = rd.powderdata[0][0]
1406        Tmax = rd.powderdata[0][-1]
1407        rd.idstring = histname
1408        histname, new_names, pwdrdata = load_pwd_from_reader(rd, iparams,
1409                                                            [h.name for h in self.histograms()])
1410        if histname in self.data:
1411            print("Warning - redefining histogram", histname)
1412        elif self.names[-1][0] == 'Phases':
1413            self.names.insert(-1, new_names)
1414        else:
1415            self.names.append(new_names)
1416        if scale is not None:
1417            pwdrdata['Sample Parameters']['Scale'][0] = scale
1418        elif pwdrdata['Instrument Parameters'][0]['Type'][0].startswith('PNC'):
1419            pwdrdata['Sample Parameters']['Scale'][0] = 10000.
1420        self.data[histname] = pwdrdata
1421        self.update_ids()
1422
1423        for phase in phases:
1424            phase = self.phase(phase)
1425            self.link_histogram_phase(histname, phase)
1426
1427        return self.histogram(histname)
1428   
1429    def add_phase(self, phasefile, phasename=None, histograms=[], fmthint=None):
1430        """Loads a phase into the project from a .cif file
1431
1432        :param str phasefile: The CIF file from which to import the phase.
1433        :param str phasename: The name of the new phase, or None for the default
1434        :param list histograms: The names of the histograms to associate with
1435            this phase. Use proj.Histograms() to add to all histograms.
1436        :param str fmthint: If specified, only importers where the format name
1437          (reader.formatName, as shown in Import menu) contains the
1438          supplied string will be tried as importers. If not specified, all
1439          importers consistent with the file extension will be tried
1440          (equivalent to "guess format" in menu).
1441
1442        :returns: A :class:`G2Phase` object representing the
1443            new phase.
1444        """
1445        LoadG2fil()
1446        histograms = [self.histogram(h).name for h in histograms]
1447        phasefile = os.path.abspath(os.path.expanduser(phasefile))
1448
1449        # TODO handle multiple phases in a file
1450        phasereaders = import_generic(phasefile, PhaseReaders, fmthint=fmthint)
1451        phasereader = phasereaders[0]
1452       
1453        phasename = phasename or phasereader.Phase['General']['Name']
1454        phaseNameList = [p.name for p in self.phases()]
1455        phasename = G2obj.MakeUniqueLabel(phasename, phaseNameList)
1456        phasereader.Phase['General']['Name'] = phasename
1457
1458        if 'Phases' not in self.data:
1459            self.data[u'Phases'] = { 'data': None }
1460        assert phasename not in self.data['Phases'], "phase names should be unique"
1461        self.data['Phases'][phasename] = phasereader.Phase
1462
1463        if phasereader.Constraints:
1464            Constraints = self.data['Constraints']
1465            for i in phasereader.Constraints:
1466                if isinstance(i, dict):
1467                    if '_Explain' not in Constraints:
1468                        Constraints['_Explain'] = {}
1469                    Constraints['_Explain'].update(i)
1470                else:
1471                    Constraints['Phase'].append(i)
1472
1473        data = self.data['Phases'][phasename]
1474        generalData = data['General']
1475        SGData = generalData['SGData']
1476        NShkl = len(G2spc.MustrainNames(SGData))
1477        NDij = len(G2spc.HStrainNames(SGData))
1478        Super = generalData.get('Super', 0)
1479        if Super:
1480            SuperVec = np.array(generalData['SuperVec'][0])
1481        else:
1482            SuperVec = []
1483        UseList = data['Histograms']
1484
1485        for hist in histograms:
1486            self.link_histogram_phase(hist, phasename)
1487
1488        for obj in self.names:
1489            if obj[0] == 'Phases':
1490                phasenames = obj
1491                break
1492        else:
1493            phasenames = [u'Phases']
1494            self.names.append(phasenames)
1495        phasenames.append(phasename)
1496
1497        # TODO should it be self.filename, not phasefile?
1498        SetupGeneral(data, os.path.dirname(phasefile))
1499        self.index_ids()
1500
1501        self.update_ids()
1502        return self.phase(phasename)
1503
1504    def link_histogram_phase(self, histogram, phase):
1505        """Associates a given histogram and phase.
1506
1507        .. seealso::
1508
1509            :meth:`G2Project.histogram`
1510            :meth:`G2Project.phase`"""
1511        hist = self.histogram(histogram)
1512        phase = self.phase(phase)
1513
1514        generalData = phase['General']
1515
1516        if hist.name.startswith('HKLF '):
1517            raise NotImplementedError("HKLF not yet supported")
1518        elif hist.name.startswith('PWDR '):
1519            hist['Reflection Lists'][generalData['Name']] = {}
1520            UseList = phase['Histograms']
1521            SGData = generalData['SGData']
1522            NShkl = len(G2spc.MustrainNames(SGData))
1523            NDij = len(G2spc.HStrainNames(SGData))
1524            UseList[hist.name] = SetDefaultDData('PWDR', hist.name, NShkl=NShkl, NDij=NDij)
1525            UseList[hist.name]['hId'] = hist.id
1526            for key, val in [('Use', True), ('LeBail', False),
1527                             ('newLeBail', True),
1528                             ('Babinet', {'BabA': [0.0, False],
1529                                          'BabU': [0.0, False]})]:
1530                if key not in UseList[hist.name]:
1531                    UseList[hist.name][key] = val
1532        else:
1533            raise RuntimeError("Unexpected histogram" + hist.name)
1534
1535
1536    def reload(self):
1537        """Reload self from self.filename"""
1538        data, names = LoadDictFromProjFile(self.filename)
1539        self.names = names
1540        # Need to deep copy the new data file data into the current tree,
1541        # so that any existing G2Phase, or G2PwdrData objects will still be
1542        # valid
1543        _deep_copy_into(from_=data, into=self.data)
1544
1545    def refine(self, newfile=None, printFile=None, makeBack=False):
1546        # TODO migrate to RefineCore
1547        # G2strMain.RefineCore(Controls,Histograms,Phases,restraintDict,rigidbodyDict,parmDict,varyList,
1548        #      calcControls,pawleyLookup,ifPrint,printFile,dlg)
1549        # index_ids will automatically save the project
1550        self.index_ids()
1551        # TODO G2strMain does not properly use printFile
1552        G2strMain.Refine(self.filename, makeBack=makeBack)
1553        # Reload yourself
1554        self.reload()
1555
1556    def histogram(self, histname):
1557        """Returns the histogram named histname, or None if it does not exist.
1558
1559        :param histname: The name of the histogram (str), or ranId or index.
1560        :returns: A :class:`G2PwdrData` object, or None if
1561            the histogram does not exist
1562
1563        .. seealso::
1564            :meth:`G2Project.histograms`
1565            :meth:`G2Project.phase`
1566            :meth:`G2Project.phases`
1567            """
1568        if isinstance(histname, G2PwdrData):
1569            if histname.proj == self:
1570                return histname
1571        if histname in self.data:
1572            return G2PwdrData(self.data[histname], self)
1573        try:
1574            # see if histname is an id or ranId
1575            histname = int(histname)
1576        except ValueError:
1577            return
1578
1579        for histogram in self.histograms():
1580            if histogram.id == histname or histogram.ranId == histname:
1581                return histogram
1582
1583    def histograms(self):
1584        """Return a list of all histograms, as
1585        :class:`G2PwdrData` objects
1586
1587        .. seealso::
1588            :meth:`G2Project.histograms`
1589            :meth:`G2Project.phase`
1590            :meth:`G2Project.phases`
1591            """
1592        output = []
1593        for obj in self.names:
1594            if len(obj) > 1 and obj[0] != u'Phases':
1595                output.append(self.histogram(obj[0]))
1596        return output
1597
1598    def phase(self, phasename):
1599        """
1600        Gives an object representing the specified phase in this project.
1601
1602        :param str phasename: A reference to the desired phase. Either the phase
1603            name (str), the phase's ranId, the phase's index (both int) or
1604            a phase object (:class:`G2Phase`)
1605        :returns: A :class:`G2Phase` object
1606        :raises: KeyError
1607
1608        .. seealso::
1609            :meth:`G2Project.histograms`
1610            :meth:`G2Project.phase`
1611            :meth:`G2Project.phases`
1612            """
1613        if isinstance(phasename, G2Phase):
1614            if phasename.proj == self:
1615                return phasename
1616        phases = self.data['Phases']
1617        if phasename in phases:
1618            return G2Phase(phases[phasename], phasename, self)
1619
1620        try:
1621            # phasename should be phase index or ranId
1622            phasename = int(phasename)
1623        except ValueError:
1624            return
1625
1626        for phase in self.phases():
1627            if phase.id == phasename or phase.ranId == phasename:
1628                return phase
1629
1630    def phases(self):
1631        """
1632        Returns a list of all the phases in the project.
1633
1634        :returns: A list of :class:`G2Phase` objects
1635
1636        .. seealso::
1637            :meth:`G2Project.histogram`
1638            :meth:`G2Project.histograms`
1639            :meth:`G2Project.phase`
1640            """
1641        for obj in self.names:
1642            if obj[0] == 'Phases':
1643                return [self.phase(p) for p in obj[1:]]
1644        return []
1645
1646    def update_ids(self):
1647        """Makes sure all phases and histograms have proper hId and pId"""
1648        # Translated from GetUsedHistogramsAndPhasesfromTree,
1649        #   GSASIIdataGUI.py:4107
1650        for i, h in enumerate(self.histograms()):
1651            h.id = i
1652        for i, p in enumerate(self.phases()):
1653            p.id = i
1654
1655    def do_refinements(self, refinements, histogram='all', phase='all',
1656                       outputnames=None, makeBack=False):
1657        """Conducts one or a series of refinements according to the
1658           input provided in parameter refinements. This is a wrapper
1659           around :meth:`iter_refinements`
1660
1661        :param list refinements: A list of dictionaries specifiying changes to be made to
1662            parameters before refinements are conducted.
1663            See the :ref:`Refinement_recipe` section for how this is defined.
1664        :param str histogram: Name of histogram for refinements to be applied
1665            to, or 'all'; note that this can be overridden for each refinement
1666            step via a "histograms" entry in the dict.
1667        :param str phase: Name of phase for refinements to be applied to, or
1668            'all'; note that this can be overridden for each refinement
1669            step via a "phases" entry in the dict.
1670        :param list outputnames: Provides a list of project (.gpx) file names
1671            to use for each refinement step (specifying None skips the save step).
1672            See :meth:`save`.
1673            Note that this can be overridden using an "output" entry in the dict.
1674        :param bool makeBack: determines if a backup ).bckX.gpx) file is made
1675            before a refinement is performed. The default is False.
1676           
1677        To perform a single refinement without changing any parameters, use this
1678        call:
1679
1680        .. code-block::  python
1681       
1682            my_project.do_refinements([])
1683        """
1684       
1685        for proj in self.iter_refinements(refinements, histogram, phase,
1686                                          outputnames, makeBack):
1687            pass
1688        return self
1689
1690    def iter_refinements(self, refinements, histogram='all', phase='all',
1691                         outputnames=None, makeBack=False):
1692        """Conducts a series of refinements, iteratively. Stops after every
1693        refinement and yields this project, to allow error checking or
1694        logging of intermediate results. Parameter use is the same as for
1695        :meth:`do_refinements` (which calls this method).
1696
1697        >>> def checked_refinements(proj):
1698        ...     for p in proj.iter_refinements(refs):
1699        ...         # Track intermediate results
1700        ...         log(p.histogram('0').residuals)
1701        ...         log(p.phase('0').get_cell())
1702        ...         # Check if parameter diverged, nonsense answer, or whatever
1703        ...         if is_something_wrong(p):
1704        ...             raise Exception("I need a human!")
1705
1706           
1707        """
1708        if outputnames:
1709            if len(refinements) != len(outputnames):
1710                raise ValueError("Should have same number of outputs to"
1711                                 "refinements")
1712        else:
1713            outputnames = [None for r in refinements]
1714
1715        for output, refinedict in zip(outputnames, refinements):
1716            if 'histograms' in refinedict:
1717                hist = refinedict['histograms']
1718            else:
1719                hist = histogram
1720            if 'phases' in refinedict:
1721                ph = refinedict['phases']
1722            else:
1723                ph = phase
1724            if 'output' in refinedict:
1725                output = refinedict['output']
1726            self.set_refinement(refinedict, hist, ph)
1727            # Handle 'once' args - refinements that are disabled after this
1728            # refinement
1729            if 'once' in refinedict:
1730                temp = {'set': refinedict['once']}
1731                self.set_refinement(temp, hist, ph)
1732
1733            if output:
1734                self.save(output)
1735
1736            if 'skip' not in refinedict:
1737                self.refine(makeBack=makeBack)
1738            yield self
1739
1740            # Handle 'once' args - refinements that are disabled after this
1741            # refinement
1742            if 'once' in refinedict:
1743                temp = {'clear': refinedict['once']}
1744                self.set_refinement(temp, hist, ph)
1745            if 'call' in refinedict:
1746                fxn = refinedict['call']
1747                if callable(fxn):
1748                    fxn(*refinedict.get('callargs',[self]))
1749                elif callable(eval(fxn)):
1750                    eval(fxn)(*refinedict.get('callargs',[self]))
1751                else:
1752                    raise G2ScriptException("Error: call value {} is not callable".format(fxn))
1753
1754    def set_refinement(self, refinement, histogram='all', phase='all'):
1755        """Apply specified refinements to a given histogram(s) or phase(s).
1756
1757        :param dict refinement: The refinements to be conducted
1758        :param histogram: Specifies either 'all' (default), a single histogram or
1759          a list of histograms. Histograms may be specified as histogram objects
1760          (see :class:`G2PwdrData`), the histogram name (str) or the index number (int)
1761          of the histogram in the project, numbered starting from 0.
1762          Omitting the parameter or the string 'all' indicates that parameters in
1763          all histograms should be set.
1764        :param phase: Specifies either 'all' (default), a single phase or
1765          a list of phases. Phases may be specified as phase objects
1766          (see :class:`G2Phase`), the phase name (str) or the index number (int)
1767          of the phase in the project, numbered starting from 0.
1768          Omitting the parameter or the string 'all' indicates that parameters in
1769          all phases should be set.
1770
1771        Note that refinement parameters are categorized as one of three types:
1772
1773        1. Histogram parameters
1774        2. Phase parameters
1775        3. Histogram-and-Phase (HAP) parameters
1776       
1777        .. seealso::
1778            :meth:`G2PwdrData.set_refinements`
1779            :meth:`G2PwdrData.clear_refinements`
1780            :meth:`G2Phase.set_refinements`
1781            :meth:`G2Phase.clear_refinements`
1782            :meth:`G2Phase.set_HAP_refinements`
1783            :meth:`G2Phase.clear_HAP_refinements`"""
1784
1785        if histogram == 'all':
1786            hists = self.histograms()
1787        elif isinstance(histogram, list) or isinstance(histogram, tuple):
1788            hists = []
1789            for h in histogram:
1790                if isinstance(h, str) or isinstance(h, int):
1791                    hists.append(self.histogram(h))
1792                else:
1793                    hists.append(h)
1794        elif isinstance(histogram, str) or isinstance(histogram, int):
1795            hists = [self.histogram(histogram)]
1796        else:
1797            hists = [histogram]
1798
1799        if phase == 'all':
1800            phases = self.phases()
1801        elif isinstance(phase, list) or isinstance(phase, tuple):
1802            phases = []
1803            for ph in phase:
1804                if isinstance(ph, str) or isinstance(ph, int):
1805                    phases.append(self.phase(ph))
1806                else:
1807                    phases.append(ph)
1808        elif isinstance(phase, str) or isinstance(phase, int):
1809            phases = [self.phase(phase)]
1810        else:
1811            phases = [phase]
1812
1813        # TODO: HAP parameters:
1814        #   Babinet
1815        #   Extinction
1816        #   HStrain
1817        #   Mustrain
1818        #   Pref. Ori
1819        #   Size
1820
1821        pwdr_set = {}
1822        phase_set = {}
1823        hap_set = {}
1824        for key, val in refinement.get('set', {}).items():
1825            # Apply refinement options
1826            if G2PwdrData.is_valid_refinement_key(key):
1827                pwdr_set[key] = val
1828            elif G2Phase.is_valid_refinement_key(key):
1829                phase_set[key] = val
1830            elif G2Phase.is_valid_HAP_refinement_key(key):
1831                hap_set[key] = val
1832            else:
1833                raise ValueError("Unknown refinement key", key)
1834
1835        for hist in hists:
1836            hist.set_refinements(pwdr_set)
1837        for phase in phases:
1838            phase.set_refinements(phase_set)
1839        for phase in phases:
1840            phase.set_HAP_refinements(hap_set, hists)
1841
1842        pwdr_clear = {}
1843        phase_clear = {}
1844        hap_clear = {}
1845        for key, val in refinement.get('clear', {}).items():
1846            # Clear refinement options
1847            if G2PwdrData.is_valid_refinement_key(key):
1848                pwdr_clear[key] = val
1849            elif G2Phase.is_valid_refinement_key(key):
1850                phase_clear[key] = val
1851            elif G2Phase.is_valid_HAP_refinement_key(key):
1852                hap_set[key] = val
1853            else:
1854                raise ValueError("Unknown refinement key", key)
1855
1856        for hist in hists:
1857            hist.clear_refinements(pwdr_clear)
1858        for phase in phases:
1859            phase.clear_refinements(phase_clear)
1860        for phase in phases:
1861            phase.clear_HAP_refinements(hap_clear, hists)
1862
1863    def index_ids(self):
1864        import GSASIIstrIO as G2strIO
1865        self.save()
1866        return G2strIO.GetUsedHistogramsAndPhases(self.filename)
1867
1868    def add_constraint_raw(self, cons_scope, constr):
1869        """Adds a constraint of type consType to the project.
1870        cons_scope should be one of "Hist", "Phase", "HAP", or "Global".
1871
1872        WARNING it does not check the constraint is well-constructed"""
1873        constrs = self.data['Constraints']['data']
1874        if 'Global' not in constrs:
1875            constrs['Global'] = []
1876        constrs[cons_scope].append(constr)
1877
1878    def hold_many(self, vars, type):
1879        """Apply holds for all the variables in vars, for constraint of a given type.
1880
1881        type is passed directly to add_constraint_raw as consType
1882
1883        :param list vars: A list of variables to hold. Either :class:`GSASIIobj.G2VarObj` objects,
1884            string variable specifiers, or arguments for :meth:`make_var_obj`
1885        :param str type: A string constraint type specifier. See
1886            :class:`G2Project.add_constraint_raw`
1887
1888        """
1889        for var in vars:
1890            if isinstance(var, str):
1891                var = self.make_var_obj(var)
1892            elif not isinstance(var, G2obj.G2VarObj):
1893                var = self.make_var_obj(*var)
1894            self.add_constraint_raw(type, [[1.0, var], None, None, 'h'])
1895
1896    def make_var_obj(self, phase=None, hist=None, varname=None, atomId=None,
1897                     reloadIdx=True):
1898        """Wrapper to create a G2VarObj. Takes either a string representaiton ("p:h:name:a")
1899        or individual names of phase, histogram, varname, and atomId.
1900
1901        Automatically converts string phase, hist, or atom names into the ID required
1902        by G2VarObj."""
1903
1904        if reloadIdx:
1905            self.index_ids()
1906
1907        # If string representation, short circuit
1908        if hist is None and varname is None and atomId is None:
1909            if isinstance(phase, str) and ':' in phase:
1910                return G2obj.G2VarObj(phase)
1911
1912        # Get phase index
1913        phaseObj = None
1914        if isinstance(phase, G2Phase):
1915            phaseObj = phase
1916            phase = G2obj.PhaseRanIdLookup[phase.ranId]
1917        elif phase in self.data['Phases']:
1918            phaseObj = self.phase(phase)
1919            phaseRanId = phaseObj.ranId
1920            phase = G2obj.PhaseRanIdLookup[phaseRanId]
1921
1922        # Get histogram index
1923        if isinstance(hist, G2PwdrData):
1924            hist = G2obj.HistRanIdLookup[hist.ranId]
1925        elif hist in self.data:
1926            histRanId = self.histogram(hist).ranId
1927            hist = G2obj.HistRanIdLookup[histRanId]
1928
1929        # Get atom index (if any)
1930        if isinstance(atomId, G2AtomRecord):
1931            atomId = G2obj.AtomRanIdLookup[phase][atomId.ranId]
1932        elif phaseObj:
1933            atomObj = phaseObj.atom(atomId)
1934            if atomObj:
1935                atomRanId = atomObj.ranId
1936                atomId = G2obj.AtomRanIdLookup[phase][atomRanId]
1937
1938        return G2obj.G2VarObj(phase, hist, varname, atomId)
1939
1940
1941class G2AtomRecord(G2ObjectWrapper):
1942    """Wrapper for an atom record. Has convenient accessors via @property.
1943
1944
1945    Available accessors: label, type, refinement_flags, coordinates,
1946        occupancy, ranId, id, adp_flag, uiso
1947
1948    Example:
1949
1950    >>> atom = some_phase.atom("O3")
1951    >>> # We can access the underlying data format
1952    >>> atom.data
1953    ['O3', 'O-2', '', ... ]
1954    >>> # We can also use wrapper accessors
1955    >>> atom.coordinates
1956    (0.33, 0.15, 0.5)
1957    >>> atom.refinement_flags
1958    u'FX'
1959    >>> atom.ranId
1960    4615973324315876477
1961    >>> atom.occupancy
1962    1.0
1963    """
1964    def __init__(self, data, indices, proj):
1965        self.data = data
1966        self.cx, self.ct, self.cs, self.cia = indices
1967        self.proj = proj
1968
1969    @property
1970    def label(self):
1971        return self.data[self.ct-1]
1972
1973    @property
1974    def type(self):
1975        return self.data[self.ct]
1976
1977    @property
1978    def refinement_flags(self):
1979        return self.data[self.ct+1]
1980
1981    @refinement_flags.setter
1982    def refinement_flags(self, other):
1983        # Automatically check it is a valid refinement
1984        for c in other:
1985            if c not in ' FXU':
1986                raise ValueError("Invalid atom refinement: ", other)
1987        self.data[self.ct+1] = other
1988
1989    @property
1990    def coordinates(self):
1991        return tuple(self.data[self.cx:self.cx+3])
1992
1993    @property
1994    def occupancy(self):
1995        return self.data[self.cx+3]
1996
1997    @occupancy.setter
1998    def occupancy(self, val):
1999        self.data[self.cx+3] = float(val)
2000
2001    @property
2002    def ranId(self):
2003        return self.data[self.cia+8]
2004
2005    @property
2006    def adp_flag(self):
2007        # Either 'I' or 'A'
2008        return self.data[self.cia]
2009
2010    @property
2011    def uiso(self):
2012        if self.adp_flag == 'I':
2013            return self.data[self.cia+1]
2014        else:
2015            return self.data[self.cia+2:self.cia+8]
2016
2017    @uiso.setter
2018    def uiso(self, value):
2019        if self.adp_flag == 'I':
2020            self.data[self.cia+1] = float(value)
2021        else:
2022            assert len(value) == 6
2023            self.data[self.cia+2:self.cia+8] = [float(v) for v in value]
2024
2025
2026class G2PwdrData(G2ObjectWrapper):
2027    """Wraps a Powder Data Histogram."""
2028    def __init__(self, data, proj):
2029        self.data = data
2030        self.proj = proj
2031
2032    @staticmethod
2033    def is_valid_refinement_key(key):
2034        valid_keys = ['Limits', 'Sample Parameters', 'Background',
2035                      'Instrument Parameters']
2036        return key in valid_keys
2037
2038    @property
2039    def name(self):
2040        return self.data['data'][-1]
2041
2042    @property
2043    def ranId(self):
2044        return self.data['data'][0]['ranId']
2045
2046    @property
2047    def residuals(self):
2048        '''Provides a dictionary with with the R-factors for this histogram.
2049        Includes the weighted and unweighted profile terms (R, Rb, wR, wRb, wRmin)
2050        as well as the Bragg R-values for each phase (ph:H:Rf and ph:H:Rf^2).
2051        '''
2052        data = self.data['data'][0]
2053        return {key: data[key] for key in data
2054                if key in ['R', 'Rb', 'wR', 'wRb', 'wRmin']
2055                   or ':' in key}
2056
2057    @property
2058    def InstrumentParameters(self):
2059        '''Provides a dictionary with with the Instrument Parameters
2060        for this histogram.
2061        '''
2062        return self.data['Instrument Parameters'][0]
2063
2064    @property
2065    def SampleParameters(self):
2066        '''Provides a dictionary with with the Sample Parameters
2067        for this histogram.
2068        '''
2069        return self.data['Sample Parameters']
2070   
2071    @property
2072    def id(self):
2073        self.proj.update_ids()
2074        return self.data['data'][0]['hId']
2075
2076    @id.setter
2077    def id(self, val):
2078        self.data['data'][0]['hId'] = val
2079
2080    def fit_fixed_points(self):
2081        """Attempts to apply a background fit to the fixed points currently specified."""
2082        def SetInstParms(Inst):
2083            dataType = Inst['Type'][0]
2084            insVary = []
2085            insNames = []
2086            insVals = []
2087            for parm in Inst:
2088                insNames.append(parm)
2089                insVals.append(Inst[parm][1])
2090                if parm in ['U','V','W','X','Y','Z','SH/L','I(L2)/I(L1)','alpha',
2091                    'beta-0','beta-1','beta-q','sig-0','sig-1','sig-2','sig-q',] and Inst[parm][2]:
2092                        Inst[parm][2] = False
2093            instDict = dict(zip(insNames, insVals))
2094            instDict['X'] = max(instDict['X'], 0.01)
2095            instDict['Y'] = max(instDict['Y'], 0.01)
2096            if 'SH/L' in instDict:
2097                instDict['SH/L'] = max(instDict['SH/L'], 0.002)
2098            return dataType, instDict, insVary
2099
2100        bgrnd = self.data['Background']
2101
2102        # Need our fixed points in order
2103        bgrnd[1]['FixedPoints'].sort(key=lambda pair: pair[0])
2104        X = [x for x, y in bgrnd[1]['FixedPoints']]
2105        Y = [y for x, y in bgrnd[1]['FixedPoints']]
2106
2107        limits = self.data['Limits'][1]
2108        if X[0] > limits[0]:
2109            X = [limits[0]] + X
2110            Y = [Y[0]] + Y
2111        if X[-1] < limits[1]:
2112            X += [limits[1]]
2113            Y += [Y[-1]]
2114
2115        # Some simple lookups
2116        controls = self.proj['Controls']['data']
2117        inst, inst2 = self.data['Instrument Parameters']
2118        pwddata = self.data['data'][1]
2119
2120        # Construct the data for background fitting
2121        xBeg = np.searchsorted(pwddata[0], limits[0])
2122        xFin = np.searchsorted(pwddata[0], limits[1])
2123        xdata = pwddata[0][xBeg:xFin]
2124        ydata = si.interp1d(X,Y)(ma.getdata(xdata))
2125
2126        W = [1]*len(xdata)
2127        Z = [0]*len(xdata)
2128
2129        dataType, insDict, insVary = SetInstParms(inst)
2130        bakType, bakDict, bakVary = G2pwd.SetBackgroundParms(bgrnd)
2131
2132        # Do the fit
2133        data = np.array([xdata, ydata, W, Z, Z, Z])
2134        G2pwd.DoPeakFit('LSQ', [], bgrnd, limits, inst, inst2, data,
2135                        prevVaryList=bakVary, controls=controls)
2136
2137        # Post-fit
2138        parmDict = {}
2139        bakType, bakDict, bakVary = G2pwd.SetBackgroundParms(bgrnd)
2140        parmDict.update(bakDict)
2141        parmDict.update(insDict)
2142        pwddata[3][xBeg:xFin] *= 0
2143        pwddata[5][xBeg:xFin] *= 0
2144        pwddata[4][xBeg:xFin] = G2pwd.getBackground('', parmDict, bakType, dataType, xdata)[0]
2145
2146        # TODO adjust pwddata? GSASIIpwdGUI.py:1041
2147        # TODO update background
2148        self.proj.save()
2149
2150    def getdata(self,datatype):
2151        '''Provides access to the histogram data of the selected data type
2152
2153        :param str datatype: must be one of the following values (case is ignored)
2154       
2155           * 'X': the 2theta or TOF values for the pattern
2156           * 'Yobs': the observed intensity values
2157           * 'Yweight': the weights for each data point (1/sigma**2)
2158           * 'Ycalc': the computed intensity values
2159           * 'Background': the computed background values
2160           * 'Residual': the difference between Yobs and Ycalc (obs-calc)
2161
2162        :returns: an numpy MaskedArray with data values of the requested type
2163       
2164        '''
2165        enums = ['x', 'yobs', 'yweight', 'ycalc', 'background', 'residual']
2166        if datatype.lower() not in enums:
2167            raise G2ScriptException("Invalid datatype = "+datatype+" must be one of "+str(enums))
2168        return self.data['data'][1][enums.index(datatype.lower())]
2169       
2170    def y_calc(self):
2171        return self.data['data'][1][3]
2172
2173    def Export(self,fileroot,extension):
2174        '''Write the histogram into a file. The path is specified by fileroot and
2175        extension.
2176       
2177        :param str fileroot: name of the file, optionally with a path (extension is
2178           ignored)
2179        :param str extension: includes '.', must match an extension in global
2180           exportersByExtension['powder'] or a Exception is raised.
2181        :returns: name of file that was written
2182        '''
2183        if extension not in exportersByExtension.get('powder',[]):
2184            raise G2ScriptException('No Writer for file type = "'+extension+'"')
2185        fil = os.path.abspath(os.path.splitext(fileroot)[0]+extension)
2186        obj = exportersByExtension['powder'][extension]
2187        obj.SetFromArray(hist=self.data,histname=self.name)
2188        obj.Writer(self.name,fil)
2189           
2190    def plot(self, Yobs=True, Ycalc=True, Background=True, Residual=True):
2191        try:
2192            import matplotlib.pyplot as plt
2193            data = self.data['data'][1]
2194            if Yobs:
2195                plt.plot(data[0], data[1], label='Yobs')
2196            if Ycalc:
2197                plt.plot(data[0], data[3], label='Ycalc')
2198            if Background:
2199                plt.plot(data[0], data[4], label='Background')
2200            if Residual:
2201                plt.plot(data[0], data[5], label="Residual")
2202        except ImportError:
2203            pass
2204
2205    def get_wR(self):
2206        """returns the overall weighted profile R factor for a histogram
2207       
2208        :returns: a wR value as a percentage or None if not defined
2209        """
2210        return self['data'][0].get('wR')
2211
2212    def set_refinements(self, refs):
2213        """Sets the refinement parameter 'key' to the specification 'value'
2214
2215        :param dict refs: A dictionary of the parameters to be set. See
2216                          :ref:`Histogram_parameters_table` for a description of
2217                          what these dictionaries should be.
2218
2219        :returns: None
2220
2221        """
2222        do_fit_fixed_points = False
2223        for key, value in refs.items():
2224            if key == 'Limits':
2225                old_limits = self.data['Limits'][1]
2226                new_limits = value
2227                if isinstance(new_limits, dict):
2228                    if 'low' in new_limits:
2229                        old_limits[0] = new_limits['low']
2230                    if 'high' in new_limits:
2231                        old_limits[1] = new_limits['high']
2232                else:
2233                    old_limits[0], old_limits[1] = new_limits
2234            elif key == 'Sample Parameters':
2235                sample = self.data['Sample Parameters']
2236                for sparam in value:
2237                    if sparam not in sample:
2238                        raise ValueError("Unknown refinement parameter, "
2239                                         + str(sparam))
2240                    sample[sparam][1] = True
2241            elif key == 'Background':
2242                bkg, peaks = self.data['Background']
2243
2244                # If True or False, just set the refine parameter
2245                if value in (True, False):
2246                    bkg[1] = value
2247                    return
2248
2249                if 'type' in value:
2250                    bkg[0] = value['type']
2251                if 'refine' in value:
2252                    bkg[1] = value['refine']
2253                if 'no. coeffs' in value:
2254                    cur_coeffs = bkg[2]
2255                    n_coeffs = value['no. coeffs']
2256                    if n_coeffs > cur_coeffs:
2257                        for x in range(n_coeffs - cur_coeffs):
2258                            bkg.append(0.0)
2259                    else:
2260                        for _ in range(cur_coeffs - n_coeffs):
2261                            bkg.pop()
2262                    bkg[2] = n_coeffs
2263                if 'coeffs' in value:
2264                    bkg[3:] = value['coeffs']
2265                if 'FixedPoints' in value:
2266                    peaks['FixedPoints'] = [(float(a), float(b))
2267                                            for a, b in value['FixedPoints']]
2268                if value.get('fit fixed points', False):
2269                    do_fit_fixed_points = True
2270
2271            elif key == 'Instrument Parameters':
2272                instrument, secondary = self.data['Instrument Parameters']
2273                for iparam in value:
2274                    try:
2275                        instrument[iparam][2] = True
2276                    except IndexError:
2277                        raise ValueError("Invalid key:", iparam)
2278            else:
2279                raise ValueError("Unknown key:", key)
2280        # Fit fixed points after the fact - ensure they are after fixed points
2281        # are added
2282        if do_fit_fixed_points:
2283            # Background won't be fit if refinement flag not set
2284            orig = self.data['Background'][0][1]
2285            self.data['Background'][0][1] = True
2286            self.fit_fixed_points()
2287            # Restore the previous value
2288            self.data['Background'][0][1] = orig
2289
2290    def clear_refinements(self, refs):
2291        """Clears the refinement parameter 'key' and its associated value.
2292
2293        :param dict refs: A dictionary of parameters to clear."""
2294        for key, value in refs.items():
2295            if key == 'Limits':
2296                old_limits, cur_limits = self.data['Limits']
2297                cur_limits[0], cur_limits[1] = old_limits
2298            elif key == 'Sample Parameters':
2299                sample = self.data['Sample Parameters']
2300                for sparam in value:
2301                    sample[sparam][1] = False
2302            elif key == 'Background':
2303                bkg, peaks = self.data['Background']
2304
2305                # If True or False, just set the refine parameter
2306                if value in (True, False):
2307                    bkg[1] = False
2308                    return
2309
2310                bkg[1] = False
2311                if 'FixedPoints' in value:
2312                    if 'FixedPoints' in peaks:
2313                        del peaks['FixedPoints']
2314            elif key == 'Instrument Parameters':
2315                instrument, secondary = self.data['Instrument Parameters']
2316                for iparam in value:
2317                    instrument[iparam][2] = False
2318            else:
2319                raise ValueError("Unknown key:", key)
2320
2321
2322class G2Phase(G2ObjectWrapper):
2323    """A wrapper object around a given phase.
2324
2325    Author: Jackson O'Donnell (jacksonhodonnell .at. gmail.com)
2326    """
2327    def __init__(self, data, name, proj):
2328        self.data = data
2329        self.name = name
2330        self.proj = proj
2331
2332    @staticmethod
2333    def is_valid_refinement_key(key):
2334        valid_keys = ["Cell", "Atoms", "LeBail"]
2335        return key in valid_keys
2336
2337    @staticmethod
2338    def is_valid_HAP_refinement_key(key):
2339        valid_keys = ["Babinet", "Extinction", "HStrain", "Mustrain",
2340                      "Pref.Ori.", "Show", "Size", "Use", "Scale"]
2341        return key in valid_keys
2342
2343    def atom(self, atomlabel):
2344        """Returns the atom specified by atomlabel, or None if it does not
2345        exist.
2346
2347        :param str atomlabel: The name of the atom (e.g. "O2")
2348        :returns: A :class:`G2AtomRecord` object
2349            representing the atom.
2350        """
2351        # Consult GSASIIobj.py for the meaning of this
2352        cx, ct, cs, cia = self.data['General']['AtomPtrs']
2353        ptrs = [cx, ct, cs, cia]
2354        atoms = self.data['Atoms']
2355        for atom in atoms:
2356            if atom[ct-1] == atomlabel:
2357                return G2AtomRecord(atom, ptrs, self.proj)
2358
2359    def atoms(self):
2360        """Returns a list of atoms present in the phase.
2361
2362        :returns: A list of :class:`G2AtomRecord` objects.
2363
2364        .. seealso::
2365            :meth:`G2Phase.atom`
2366            :class:`G2AtomRecord`
2367        """
2368        ptrs = self.data['General']['AtomPtrs']
2369        output = []
2370        atoms = self.data['Atoms']
2371        for atom in atoms:
2372            output.append(G2AtomRecord(atom, ptrs, self.proj))
2373        return output
2374
2375    def histograms(self):
2376        output = []
2377        for hname in self.data.get('Histograms', {}).keys():
2378            output.append(self.proj.histogram(hname))
2379        return output
2380
2381    @property
2382    def ranId(self):
2383        return self.data['ranId']
2384
2385    @property
2386    def id(self):
2387        return self.data['pId']
2388
2389    @id.setter
2390    def id(self, val):
2391        self.data['pId'] = val
2392
2393    def get_cell(self):
2394        """Returns a dictionary of the cell parameters, with keys:
2395            'length_a', 'length_b', 'length_c', 'angle_alpha', 'angle_beta', 'angle_gamma', 'volume'
2396
2397        :returns: a dict
2398
2399        .. seealso::
2400           :meth:`G2Phase.get_cell_and_esd`
2401
2402        """
2403        cell = self.data['General']['Cell']
2404        return {'length_a': cell[1], 'length_b': cell[2], 'length_c': cell[3],
2405                'angle_alpha': cell[4], 'angle_beta': cell[5], 'angle_gamma': cell[6],
2406                'volume': cell[7]}
2407
2408    def get_cell_and_esd(self):
2409        """
2410        Returns a pair of dictionaries, the first representing the unit cell, the second
2411        representing the estimated standard deviations of the unit cell.
2412
2413        :returns: a tuple of two dictionaries
2414
2415        .. seealso::
2416           :meth:`G2Phase.get_cell`
2417
2418        """
2419        # translated from GSASIIstrIO.ExportBaseclass.GetCell
2420        import GSASIIstrIO as G2stIO
2421        import GSASIIlattice as G2lat
2422        import GSASIImapvars as G2mv
2423        try:
2424            pfx = str(self.id) + '::'
2425            sgdata = self['General']['SGData']
2426            covDict = self.proj['Covariance']['data']
2427
2428            parmDict = dict(zip(covDict.get('varyList',[]),
2429                                covDict.get('variables',[])))
2430            sigDict = dict(zip(covDict.get('varyList',[]),
2431                               covDict.get('sig',[])))
2432
2433            if covDict.get('covMatrix') is not None:
2434                sigDict.update(G2mv.ComputeDepESD(covDict['covMatrix'],
2435                                                  covDict['varyList'],
2436                                                  parmDict))
2437
2438            A, sigA = G2stIO.cellFill(pfx, sgdata, parmDict, sigDict)
2439            cellSig = G2stIO.getCellEsd(pfx, sgdata, A, self.proj['Covariance']['data'])
2440            cellList = G2lat.A2cell(A) + (G2lat.calc_V(A),)
2441            cellDict, cellSigDict = {}, {}
2442            for i, key in enumerate(['length_a', 'length_b', 'length_c',
2443                                     'angle_alpha', 'angle_beta', 'angle_gamma',
2444                                     'volume']):
2445                cellDict[key] = cellList[i]
2446                cellSigDict[key] = cellSig[i]
2447            return cellDict, cellSigDict
2448        except KeyError:
2449            cell = self.get_cell()
2450            return cell, {key: 0.0 for key in cell}
2451
2452    def export_CIF(self, outputname, quickmode=True):
2453        """Write this phase to a .cif file named outputname
2454
2455        :param str outputname: The name of the .cif file to write to
2456        :param bool quickmode: Currently ignored. Carryover from exports.G2export_CIF"""
2457        # This code is all taken from exports/G2export_CIF.py
2458        # Functions copied have the same names
2459        import GSASIImath as G2mth
2460        import GSASIImapvars as G2mv
2461        from exports import G2export_CIF as cif
2462
2463        CIFdate = dt.datetime.strftime(dt.datetime.now(),"%Y-%m-%dT%H:%M")
2464        CIFname = os.path.splitext(self.proj.filename)[0]
2465        CIFname = os.path.split(CIFname)[1]
2466        CIFname = ''.join([c if ord(c) < 128 else ''
2467                           for c in CIFname.replace(' ', '_')])
2468        try:
2469            author = self.proj['Controls']['data'].get('Author','').strip()
2470        except KeyError:
2471            pass
2472        oneblock = True
2473
2474        covDict = self.proj['Covariance']['data']
2475        parmDict = dict(zip(covDict.get('varyList',[]),
2476                            covDict.get('variables',[])))
2477        sigDict = dict(zip(covDict.get('varyList',[]),
2478                           covDict.get('sig',[])))
2479
2480        if covDict.get('covMatrix') is not None:
2481            sigDict.update(G2mv.ComputeDepESD(covDict['covMatrix'],
2482                                              covDict['varyList'],
2483                                              parmDict))
2484
2485        with open(outputname, 'w') as fp:
2486            fp.write(' \n' + 70*'#' + '\n')
2487            cif.WriteCIFitem(fp, 'data_' + CIFname)
2488            # from exports.G2export_CIF.WritePhaseInfo
2489            cif.WriteCIFitem(fp, '\n# phase info for '+str(self.name) + ' follows')
2490            cif.WriteCIFitem(fp, '_pd_phase_name', self.name)
2491            # TODO get esds
2492            cellDict = self.get_cell()
2493            defsigL = 3*[-0.00001] + 3*[-0.001] + [-0.01] # significance to use when no sigma
2494            names = ['length_a','length_b','length_c',
2495                     'angle_alpha','angle_beta ','angle_gamma',
2496                     'volume']
2497            for key, val in cellDict.items():
2498                cif.WriteCIFitem(fp, '_cell_' + key, G2mth.ValEsd(val))
2499
2500            cif.WriteCIFitem(fp, '_symmetry_cell_setting',
2501                         self.data['General']['SGData']['SGSys'])
2502
2503            spacegroup = self.data['General']['SGData']['SpGrp'].strip()
2504            # regularize capitalization and remove trailing H/R
2505            spacegroup = spacegroup[0].upper() + spacegroup[1:].lower().rstrip('rh ')
2506            cif.WriteCIFitem(fp, '_symmetry_space_group_name_H-M', spacegroup)
2507
2508            # generate symmetry operations including centering and center of symmetry
2509            SymOpList, offsetList, symOpList, G2oprList, G2opcodes = G2spc.AllOps(
2510                self.data['General']['SGData'])
2511            cif.WriteCIFitem(fp, 'loop_\n    _space_group_symop_id\n    _space_group_symop_operation_xyz')
2512            for i, op in enumerate(SymOpList,start=1):
2513                cif.WriteCIFitem(fp, '   {:3d}  {:}'.format(i,op.lower()))
2514
2515            # TODO skipped histograms, exports/G2export_CIF.py:880
2516
2517            # report atom params
2518            if self.data['General']['Type'] in ['nuclear','macromolecular']:        #this needs macromolecular variant, etc!
2519                cif.WriteAtomsNuclear(fp, self.data, self.name, parmDict, sigDict, [])
2520                # self._WriteAtomsNuclear(fp, parmDict, sigDict)
2521            else:
2522                raise G2ScriptException("no export for "+str(self.data['General']['Type'])+" coordinates implemented")
2523            # report cell contents
2524            cif.WriteComposition(fp, self.data, self.name, parmDict)
2525            if not quickmode and self.data['General']['Type'] == 'nuclear':      # report distances and angles
2526                # WriteDistances(fp,self.name,SymOpList,offsetList,symOpList,G2oprList)
2527                raise NotImplementedError("only quickmode currently supported")
2528            if 'Map' in self.data['General'] and 'minmax' in self.data['General']['Map']:
2529                cif.WriteCIFitem(fp,'\n# Difference density results')
2530                MinMax = self.data['General']['Map']['minmax']
2531                cif.WriteCIFitem(fp,'_refine_diff_density_max',G2mth.ValEsd(MinMax[0],-0.009))
2532                cif.WriteCIFitem(fp,'_refine_diff_density_min',G2mth.ValEsd(MinMax[1],-0.009))
2533
2534
2535    def set_refinements(self, refs):
2536        """Sets the refinement parameter 'key' to the specification 'value'
2537
2538        :param dict refs: A dictionary of the parameters to be set. See
2539                          :ref:`Phase_parameters_table` for a description of
2540                          this dictionary.
2541
2542        :returns: None"""
2543        for key, value in refs.items():
2544            if key == "Cell":
2545                self.data['General']['Cell'][0] = value
2546
2547            elif key == "Atoms":
2548                for atomlabel, atomrefinement in value.items():
2549                    if atomlabel == 'all':
2550                        for atom in self.atoms():
2551                            atom.refinement_flags = atomrefinement
2552                    else:
2553                        atom = self.atom(atomlabel)
2554                        if atom is None:
2555                            raise ValueError("No such atom: " + atomlabel)
2556                        atom.refinement_flags = atomrefinement
2557
2558            elif key == "LeBail":
2559                hists = self.data['Histograms']
2560                for hname, hoptions in hists.items():
2561                    if 'LeBail' not in hoptions:
2562                        hoptions['newLeBail'] = bool(True)
2563                    hoptions['LeBail'] = bool(value)
2564            else:
2565                raise ValueError("Unknown key:", key)
2566
2567    def clear_refinements(self, refs):
2568        """Clears a given set of parameters.
2569
2570        :param dict refs: The parameters to clear"""
2571        for key, value in refs.items():
2572            if key == "Cell":
2573                self.data['General']['Cell'][0] = False
2574            elif key == "Atoms":
2575                cx, ct, cs, cia = self.data['General']['AtomPtrs']
2576
2577                for atomlabel in value:
2578                    atom = self.atom(atomlabel)
2579                    # Set refinement to none
2580                    atom.refinement_flags = ' '
2581            elif key == "LeBail":
2582                hists = self.data['Histograms']
2583                for hname, hoptions in hists.items():
2584                    if 'LeBail' not in hoptions:
2585                        hoptions['newLeBail'] = True
2586                    hoptions['LeBail'] = False
2587            else:
2588                raise ValueError("Unknown key:", key)
2589
2590    def set_HAP_refinements(self, refs, histograms='all'):
2591        """Sets the given HAP refinement parameters between this phase and
2592        the given histograms
2593
2594        :param dict refs: A dictionary of the parameters to be set. See
2595                          :ref:`HAP_parameters_table` for a description of this
2596                          dictionary.
2597        :param histograms: Either 'all' (default) or a list of the histograms
2598            whose HAP parameters will be set with this phase. Histogram and phase
2599            must already be associated
2600
2601        :returns: None
2602        """
2603        if histograms == 'all':
2604            histograms = self.data['Histograms'].values()
2605        else:
2606            histograms = [self.data['Histograms'][h.name] for h in histograms]
2607
2608        for key, val in refs.items():
2609            for h in histograms:
2610                if key == 'Babinet':
2611                    try:
2612                        sets = list(val)
2613                    except ValueError:
2614                        sets = ['BabA', 'BabU']
2615                    for param in sets:
2616                        if param not in ['BabA', 'BabU']:
2617                            raise ValueError("Not sure what to do with" + param)
2618                        for hist in histograms:
2619                            hist['Babinet'][param][1] = True
2620                elif key == 'Extinction':
2621                    for h in histograms:
2622                        h['Extinction'][1] = bool(val)
2623                elif key == 'HStrain':
2624                    for h in histograms:
2625                        h['HStrain'][1] = [bool(val) for p in h['HStrain'][1]]
2626                elif key == 'Mustrain':
2627                    for h in histograms:
2628                        mustrain = h['Mustrain']
2629                        newType = None
2630                        direction = None
2631                        if isinstance(val, strtypes):
2632                            if val in ['isotropic', 'uniaxial', 'generalized']:
2633                                newType = val
2634                            else:
2635                                raise ValueError("Not a Mustrain type: " + val)
2636                        elif isinstance(val, dict):
2637                            newType = val.get('type', None)
2638                            direction = val.get('direction', None)
2639
2640                        if newType:
2641                            mustrain[0] = newType
2642                            if newType == 'isotropic':
2643                                mustrain[2][0] = True
2644                                mustrain[5] = [False for p in mustrain[4]]
2645                            elif newType == 'uniaxial':
2646                                if 'refine' in val:
2647                                    types = val['refine']
2648                                    if isinstance(types, strtypes):
2649                                        types = [types]
2650                                    elif isinstance(types, bool):
2651                                        mustrain[2][0] = types
2652                                        mustrain[2][1] = types
2653                                        types = []
2654                                    else:
2655                                        raise ValueError("Not sure what to do with: "
2656                                                         + str(types))
2657                                else:
2658                                    types = []
2659
2660                                for unitype in types:
2661                                    if unitype == 'equatorial':
2662                                        mustrain[2][0] = True
2663                                    elif unitype == 'axial':
2664                                        mustrain[2][1] = True
2665                                    else:
2666                                        msg = 'Invalid uniaxial mustrain type'
2667                                        raise ValueError(msg + ': ' + unitype)
2668                            else:  # newtype == 'generalized'
2669                                mustrain[2] = [False for p in mustrain[1]]
2670
2671                        if direction:
2672                            if len(direction) != 3:
2673                                raise ValueError("Expected hkl, found", direction)
2674                            direction = [int(n) for n in direction]
2675                            mustrain[3] = direction
2676                elif key == 'Size':
2677                    for h in histograms:
2678                        size = h['Size']
2679                        newType = None
2680                        direction = None
2681                        if isinstance(val, strtypes):
2682                            if val in ['isotropic', 'uniaxial', 'ellipsoidal']:
2683                                newType = val
2684                            else:
2685                                raise ValueError("Not a valid Size type: " + val)
2686                        elif isinstance(val, dict):
2687                            newType = val.get('type', None)
2688                            direction = val.get('direction', None)
2689
2690                        if newType:
2691                            size[0] = newType
2692                            refine = val.get('refine')
2693                            if newType == 'isotropic' and refine is not None:
2694                                size[2][0] = bool(refine)
2695                            elif newType == 'uniaxial' and refine is not None:
2696                                size[2][1] = bool(refine)
2697                                size[2][2] = bool(refine)
2698                            elif newType == 'ellipsoidal' and refine is not None:
2699                                size[5] = [bool(refine) for p in size[5]]
2700
2701                        if direction:
2702                            if len(direction) != 3:
2703                                raise ValueError("Expected hkl, found", direction)
2704                            direction = [int(n) for n in direction]
2705                            size[3] = direction
2706                elif key == 'Pref.Ori.':
2707                    for h in histograms:
2708                        h['Pref.Ori.'][2] = bool(val)
2709                elif key == 'Show':
2710                    for h in histograms:
2711                        h['Show'] = bool(val)
2712                elif key == 'Use':
2713                    for h in histograms:
2714                        h['Use'] = bool(val)
2715                elif key == 'Scale':
2716                    for h in histograms:
2717                        h['Scale'][1] = bool(val)
2718                else:
2719                    print(u'Unknown HAP key: '+key)
2720
2721    def getHAPvalues(self, histname):
2722        """Returns a dict with HAP values for the selected histogram
2723
2724        :param histogram: is a histogram object (:class:`G2PwdrData`) or
2725            a histogram name or the index number of the histogram
2726
2727        :returns: HAP value dict
2728        """
2729        if isinstance(histname, G2PwdrData):
2730            histname = histname.name
2731        elif histname in self.data['Histograms']:
2732            pass
2733        elif type(histname) is int:
2734            histname = self.proj.histograms()[histname].name
2735        else:
2736            raise G2ScriptException("Invalid histogram reference: "+str(histname))
2737        return self.data['Histograms'][histname]
2738                   
2739    def clear_HAP_refinements(self, refs, histograms='all'):
2740        """Clears the given HAP refinement parameters between this phase and
2741        the given histograms
2742
2743        :param dict refs: A dictionary of the parameters to be cleared.
2744        :param histograms: Either 'all' (default) or a list of the histograms
2745            whose HAP parameters will be cleared with this phase. Histogram and
2746            phase must already be associated
2747
2748        :returns: None
2749        """
2750        if histograms == 'all':
2751            histograms = self.data['Histograms'].values()
2752        else:
2753            histograms = [self.data['Histograms'][h.name] for h in histograms]
2754
2755        for key, val in refs.items():
2756            for h in histograms:
2757                if key == 'Babinet':
2758                    try:
2759                        sets = list(val)
2760                    except ValueError:
2761                        sets = ['BabA', 'BabU']
2762                    for param in sets:
2763                        if param not in ['BabA', 'BabU']:
2764                            raise ValueError("Not sure what to do with" + param)
2765                        for hist in histograms:
2766                            hist['Babinet'][param][1] = False
2767                elif key == 'Extinction':
2768                    for h in histograms:
2769                        h['Extinction'][1] = False
2770                elif key == 'HStrain':
2771                    for h in histograms:
2772                        h['HStrain'][1] = [False for p in h['HStrain'][1]]
2773                elif key == 'Mustrain':
2774                    for h in histograms:
2775                        mustrain = h['Mustrain']
2776                        mustrain[2] = [False for p in mustrain[2]]
2777                        mustrain[5] = [False for p in mustrain[4]]
2778                elif key == 'Pref.Ori.':
2779                    for h in histograms:
2780                        h['Pref.Ori.'][2] = False
2781                elif key == 'Show':
2782                    for h in histograms:
2783                        h['Show'] = False
2784                elif key == 'Size':
2785                    for h in histograms:
2786                        size = h['Size']
2787                        size[2] = [False for p in size[2]]
2788                        size[5] = [False for p in size[5]]
2789                elif key == 'Use':
2790                    for h in histograms:
2791                        h['Use'] = False
2792                elif key == 'Scale':
2793                    for h in histograms:
2794                        h['Scale'][1] = False
2795                else:
2796                    print(u'Unknown HAP key: '+key)
2797
2798
2799##########################
2800# Command Line Interface #
2801##########################
2802# Each of these takes an argparse.Namespace object as their argument,
2803# representing the parsed command-line arguments for the relevant subcommand.
2804# The argument specification for each is in the subcommands dictionary (see
2805# below)
2806
2807commandhelp={}
2808commandhelp["create"] = "creates a GSAS-II project, optionally adding histograms and/or phases"
2809def create(args):
2810    """Implements the create command-line subcommand. This creates a GSAS-II project, optionally adding histograms and/or phases::
2811
2812  usage: GSASIIscriptable.py create [-h] [-d HISTOGRAMS [HISTOGRAMS ...]]
2813                                  [-i IPARAMS [IPARAMS ...]]
2814                                  [-p PHASES [PHASES ...]]
2815                                  filename
2816                                 
2817positional arguments::
2818
2819  filename              the project file to create. should end in .gpx
2820
2821optional arguments::
2822
2823  -h, --help            show this help message and exit
2824  -d HISTOGRAMS [HISTOGRAMS ...], --histograms HISTOGRAMS [HISTOGRAMS ...]
2825                        list of datafiles to add as histograms
2826  -i IPARAMS [IPARAMS ...], --iparams IPARAMS [IPARAMS ...]
2827                        instrument parameter file, must be one for every
2828                        histogram
2829  -p PHASES [PHASES ...], --phases PHASES [PHASES ...]
2830                        list of phases to add. phases are automatically
2831                        associated with all histograms given.
2832
2833    """
2834    proj = G2Project(gpxname=args.filename)
2835
2836    hist_objs = []
2837    if args.histograms:
2838        for h,i in zip(args.histograms,args.iparams):
2839            print("Adding histogram from",h,"with instparm ",i)
2840            hist_objs.append(proj.add_powder_histogram(h, i))
2841
2842    if args.phases: 
2843        for p in args.phases:
2844            print("Adding phase from",p)
2845            proj.add_phase(p, histograms=hist_objs)
2846        print('Linking phase(s) to histogram(s):')
2847        for h in hist_objs:
2848            print ('   '+h.name)
2849
2850    proj.save()
2851
2852commandhelp["add"] = "adds histograms and/or phases to GSAS-II project"
2853def add(args):
2854    """Implements the add command-line subcommand. This adds histograms and/or phases to GSAS-II project::
2855
2856  usage: GSASIIscriptable.py add [-h] [-d HISTOGRAMS [HISTOGRAMS ...]]
2857                               [-i IPARAMS [IPARAMS ...]]
2858                               [-hf HISTOGRAMFORMAT] [-p PHASES [PHASES ...]]
2859                               [-pf PHASEFORMAT] [-l HISTLIST [HISTLIST ...]]
2860                               filename
2861
2862
2863positional arguments::
2864
2865  filename              the project file to open. Should end in .gpx
2866
2867optional arguments::
2868
2869  -h, --help            show this help message and exit
2870  -d HISTOGRAMS [HISTOGRAMS ...], --histograms HISTOGRAMS [HISTOGRAMS ...]
2871                        list of datafiles to add as histograms
2872  -i IPARAMS [IPARAMS ...], --iparams IPARAMS [IPARAMS ...]
2873                        instrument parameter file, must be one for every
2874                        histogram
2875  -hf HISTOGRAMFORMAT, --histogramformat HISTOGRAMFORMAT
2876                        format hint for histogram import. Applies to all
2877                        histograms
2878  -p PHASES [PHASES ...], --phases PHASES [PHASES ...]
2879                        list of phases to add. phases are automatically
2880                        associated with all histograms given.
2881  -pf PHASEFORMAT, --phaseformat PHASEFORMAT
2882                        format hint for phase import. Applies to all phases.
2883                        Example: -pf CIF
2884  -l HISTLIST [HISTLIST ...], --histlist HISTLIST [HISTLIST ...]
2885                        list of histgram indices to associate with added
2886                        phases. If not specified, phases are associated with
2887                        all previously loaded histograms. Example: -l 2 3 4
2888   
2889    """
2890    proj = G2Project(args.filename)
2891
2892    if args.histograms:
2893        for h,i in zip(args.histograms,args.iparams):
2894            print("Adding histogram from",h,"with instparm ",i)
2895            proj.add_powder_histogram(h, i, fmthint=args.histogramformat)
2896
2897    if args.phases: 
2898        if not args.histlist:
2899            histlist = proj.histograms()
2900        else:
2901            histlist = [proj.histogram(i) for i in args.histlist]
2902
2903        for p in args.phases:
2904            print("Adding phase from",p)
2905            proj.add_phase(p, histograms=histlist, fmthint=args.phaseformat)
2906           
2907        if not args.histlist:
2908            print('Linking phase(s) to all histogram(s)')
2909        else:
2910            print('Linking phase(s) to histogram(s):')
2911            for h in histlist:
2912                print ('   '+h.name)
2913
2914    proj.save()
2915
2916
2917commandhelp["dump"] = "Shows the contents of a GSAS-II project"
2918def dump(args):
2919    """Implements the dump command-line subcommand, which shows the contents of a GSAS-II project::
2920
2921       usage: GSASIIscriptable.py dump [-h] [-d] [-p] [-r] files [files ...]
2922
2923positional arguments::
2924
2925  files
2926
2927optional arguments::
2928
2929  -h, --help        show this help message and exit
2930  -d, --histograms  list histograms in files, overrides --raw
2931  -p, --phases      list phases in files, overrides --raw
2932  -r, --raw         dump raw file contents, default
2933 
2934    """
2935    if not args.histograms and not args.phases:
2936        args.raw = True
2937    if args.raw:
2938        import IPython.lib.pretty as pretty
2939
2940    for fname in args.files:
2941        if args.raw:
2942            proj, nameList = LoadDictFromProjFile(fname)
2943            print("file:", fname)
2944            print("names:", nameList)
2945            for key, val in proj.items():
2946                print(key, ":")
2947                pretty.pprint(val)
2948        else:
2949            proj = G2Project(fname)
2950            if args.histograms:
2951                hists = proj.histograms()
2952                for h in hists:
2953                    print(fname, "hist", h.id, h.name)
2954            if args.phases:
2955                phase_list = proj.phases()
2956                for p in phase_list:
2957                    print(fname, "phase", p.id, p.name)
2958
2959
2960commandhelp["browse"] = "Load a GSAS-II project and then open a IPython shell to browse it"
2961def IPyBrowse(args):
2962    """Load a .gpx file and then open a IPython shell to browse it::
2963
2964  usage: GSASIIscriptable.py browse [-h] files [files ...]
2965
2966positional arguments::
2967
2968  files       list of files to browse
2969
2970optional arguments::
2971
2972  -h, --help  show this help message and exit
2973
2974    """
2975    for fname in args.files:
2976        proj, nameList = LoadDictFromProjFile(fname)
2977        msg = "\nfname {} loaded into proj (dict) with names in nameList".format(fname)
2978        GSASIIpath.IPyBreak_base(msg)
2979        break
2980
2981
2982commandhelp["refine"] = '''
2983Conducts refinements on GSAS-II projects according to a list of refinement
2984steps in a JSON dict
2985'''
2986def refine(args):
2987    """Implements the refine command-line subcommand:
2988    conducts refinements on GSAS-II projects according to a JSON refinement dict::
2989
2990        usage: GSASIIscriptable.py refine [-h] gpxfile [refinements]
2991
2992positional arguments::
2993
2994  gpxfile      the project file to refine
2995  refinements  json file of refinements to apply. if not present refines file
2996               as-is
2997
2998optional arguments::
2999
3000  -h, --help   show this help message and exit
3001 
3002    """
3003    proj = G2Project(args.gpxfile)
3004    if args.refinements is None:
3005        proj.refine()
3006    else:
3007        import json
3008        with open(args.refinements) as refs:
3009            refs = json.load(refs)
3010        if type(refs) is not dict:
3011            raise G2ScriptException("Error: JSON object must be a dict.")
3012        if "code" in refs:
3013            print("executing code:\n|  ",'\n|  '.join(refs['code']))
3014            exec('\n'.join(refs['code']))
3015        proj.do_refinements(refs['refinements'])
3016
3017
3018commandhelp["seqrefine"] = "Not implemented. Placeholder for eventual sequential refinement implementation"
3019def seqrefine(args):
3020    """Future implementation for the seqrefine command-line subcommand """
3021    raise NotImplementedError("seqrefine is not yet implemented")
3022
3023
3024commandhelp["export"] = "Export phase as CIF"
3025def export(args):
3026    """Implements the export command-line subcommand: Exports phase as CIF::
3027
3028      usage: GSASIIscriptable.py export [-h] gpxfile phase exportfile
3029
3030positional arguments::
3031
3032  gpxfile     the project file from which to export
3033  phase       identifier of phase to export
3034  exportfile  the .cif file to export to
3035
3036optional arguments::
3037
3038  -h, --help  show this help message and exit
3039
3040    """
3041    proj = G2Project(args.gpxfile)
3042    phase = proj.phase(args.phase)
3043    phase.export_CIF(args.exportfile)
3044
3045
3046def _args_kwargs(*args, **kwargs):
3047    return args, kwargs
3048
3049# A dictionary of the name of each subcommand, and a tuple
3050# of its associated function and a list of its arguments
3051# The arguments are passed directly to the add_argument() method
3052# of an argparse.ArgumentParser
3053
3054subcommands = {"create":
3055               (create, [_args_kwargs('filename',
3056                                      help='the project file to create. should end in .gpx'),
3057
3058                         _args_kwargs('-d', '--histograms',
3059                                      nargs='+',
3060                                      help='list of datafiles to add as histograms'),
3061                                     
3062                         _args_kwargs('-i', '--iparams',
3063                                      nargs='+',
3064                                      help='instrument parameter file, must be one'
3065                                           ' for every histogram'
3066                                      ),
3067
3068                         _args_kwargs('-p', '--phases',
3069                                      nargs='+',
3070                                      help='list of phases to add. phases are '
3071                                           'automatically associated with all '
3072                                           'histograms given.')]),
3073               "add": (add, [_args_kwargs('filename',
3074                                      help='the project file to open. Should end in .gpx'),
3075
3076                         _args_kwargs('-d', '--histograms',
3077                                      nargs='+',
3078                                      help='list of datafiles to add as histograms'),
3079                                     
3080                         _args_kwargs('-i', '--iparams',
3081                                      nargs='+',
3082                                      help='instrument parameter file, must be one'
3083                                           ' for every histogram'
3084                                      ),
3085                                     
3086                         _args_kwargs('-hf', '--histogramformat',
3087                                      help='format hint for histogram import. Applies to all'
3088                                           ' histograms'
3089                                      ),
3090
3091                         _args_kwargs('-p', '--phases',
3092                                      nargs='+',
3093                                      help='list of phases to add. phases are '
3094                                           'automatically associated with all '
3095                                           'histograms given.'),
3096
3097                         _args_kwargs('-pf', '--phaseformat',
3098                                      help='format hint for phase import. Applies to all'
3099                                           ' phases. Example: -pf CIF'
3100                                      ),
3101                                     
3102                         _args_kwargs('-l', '--histlist',
3103                                      nargs='+',
3104                                      help='list of histgram indices to associate with added'
3105                                           ' phases. If not specified, phases are'
3106                                           ' associated with all previously loaded'
3107                                           ' histograms. Example: -l 2 3 4')]),
3108                                           
3109               "dump": (dump, [_args_kwargs('-d', '--histograms',
3110                                     action='store_true',
3111                                     help='list histograms in files, overrides --raw'),
3112
3113                               _args_kwargs('-p', '--phases',
3114                                            action='store_true',
3115                                            help='list phases in files, overrides --raw'),
3116
3117                               _args_kwargs('-r', '--raw',
3118                                      action='store_true', help='dump raw file contents, default'),
3119
3120                               _args_kwargs('files', nargs='+')]),
3121
3122               "refine":
3123               (refine, [_args_kwargs('gpxfile', help='the project file to refine'),
3124                         _args_kwargs('refinements',
3125                                      help='JSON file of refinements to apply. if not present'
3126                                           ' refines file as-is',
3127                                      default=None,
3128                                      nargs='?')]),
3129
3130               "seqrefine": (seqrefine, []),
3131               "export": (export, [_args_kwargs('gpxfile',
3132                                                help='the project file from which to export'),
3133                                   _args_kwargs('phase', help='identifier of phase to export'),
3134                                   _args_kwargs('exportfile', help='the .cif file to export to')]),
3135               "browse": (IPyBrowse, [_args_kwargs('files', nargs='+',
3136                                                   help='list of files to browse')])}
3137
3138
3139def main():
3140    '''The command-line interface for calling GSASIIscriptable as a shell command,
3141    where it is expected to be called as::
3142
3143       python GSASIIscriptable.py <subcommand> <file.gpx> <options>
3144
3145    The following subcommands are defined:
3146
3147        * create, see :func:`create`
3148        * add, see :func:`add`
3149        * dump, see :func:`dump`
3150        * refine, see :func:`refine`
3151        * seqrefine, see :func:`seqrefine`
3152        * export, :func:`export`
3153        * browse, see :func:`IPyBrowse`
3154
3155    .. seealso::
3156        :func:`create`
3157        :func:`add`
3158        :func:`dump`
3159        :func:`refine`
3160        :func:`seqrefine`
3161        :func:`export`
3162        :func:`IPyBrowse`
3163    '''
3164    parser = argparse.ArgumentParser(description=
3165        "Use of "+os.path.split(__file__)[1]+" Allows GSAS-II actions from command line."
3166        )
3167    subs = parser.add_subparsers()
3168
3169    # Create all of the specified subparsers
3170    for name, (func, args) in subcommands.items():
3171        new_parser = subs.add_parser(name,help=commandhelp.get(name),
3172                                     description='Command "'+name+'" '+commandhelp.get(name))
3173        for listargs, kwds in args:
3174            new_parser.add_argument(*listargs, **kwds)
3175        new_parser.set_defaults(func=func)
3176
3177    # Parse and trigger subcommand
3178    result = parser.parse_args()
3179    result.func(result)
3180
3181if __name__ == '__main__':
3182    main()
Note: See TracBrowser for help on using the repository browser.