Guest User

Untitled

a guest
Jul 13th, 2015
470
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 262.69 KB | None | 0 0
  1. """
  2. Top level interface to Phoebe2.
  3.  
  4. The Bundle aims at providing a user-friendly interface to a Body or BodyBag,
  5. such that parameters can easily be queried or changed, data added, results
  6. plotted and observations computed. It does not contain any implementation of
  7. physics; that is the responsibility of the backend and the associated
  8. library.
  9.  
  10. **Initialisation**
  11.  
  12. Simply initialize a Bundle with default parameters via::
  13.  
  14.    >>> mybundle = Bundle()
  15.  
  16. Or use other predefined systems as a starting point::
  17.  
  18.    >>> mybundle = Bundle('binary')
  19.    >>> mybundle = Bundle('overcontact')
  20.    >>> mybundle = Bundle('hierarchical_triple')
  21.    >>> mybundle = Bundle('binary_pulsating_primary')
  22.    >>> mybundle = Bundle('pulsating_star')
  23.    >>> mybundle = Bundle('sun')
  24.    >>> mybundle = Bundle('vega')
  25.    
  26. For a complete list, see the documentation of the :py:mod:`create <phoebe.parameters.create>`
  27. module (section *Generic systems* and *specific targets*).
  28.  
  29. **Phoebe1 compatibility**
  30.    
  31. Phoebe2 can be used in a Phoebe1 compatibility mode ('legacy' mode), which is
  32. most easily when you start immediately from a legacy parameter file:
  33.    
  34.    >>> mybundle = Bundle('legacy.phoebe')
  35.  
  36. When a Bundle is loaded this way, computational options are added automatically
  37. to the Bundle to mimick the physics that is available in Phoebe1. These options
  38. are collected in a ParameterSet of context 'compute' with label ``legacy``.
  39. The most important parameters are listed below (some unimportant ones have been
  40. excluded so if you try this, you'll see more parameters)
  41.  
  42.    >>> print(mybundle['legacy'])
  43.              label legacy        --   label for the compute options              
  44.            heating True          --   Allow irradiators to heat other Bodies      
  45.               refl False         --   Allow irradiated Bodies to reflect light    
  46.           refl_num 1             --   Number of reflections                      
  47.                ltt False         --   Correct for light time travel effects      
  48.         subdiv_num 3             --   Number of subdivisions                      
  49.        eclipse_alg binary        --   Type of eclipse algorithm                  
  50.        beaming_alg none          --   Type of beaming algorithm                  
  51.    irradiation_alg point_source  --   Type of irradiation algorithm
  52.  
  53. The irradiation algorithm and reflection iterations will be adjust to match the
  54. value in the loaded parameter file.
  55.  
  56. Once data is added, you could run Phoebe2 while mimicking the physics in Phoebe1
  57. via::
  58.  
  59. >>> mybundle.run_compute(label='legacy')
  60. >>> print(mybundle.get_logp())
  61.  
  62. >>> mybundle.plot_obs('lightcurve_0', fmt='ko')
  63. >>> mybundle.plot_obs('primaryrv_0', fmt='ko')
  64. >>> mybundle.plot_obs('secondaryrv_0', fmt='ro')
  65.  
  66. >>> mybundle.plot_syn('lightcurve_0', 'k-', lw=2)
  67. >>> mybundle.plot_syn('primaryrv_0', 'k-', lw=2)
  68. >>> mybundle.plot_syn('secondaryrv_0', 'r-', lw=2)
  69.  
  70. Basic class:
  71.  
  72. .. autosummary::
  73.  
  74.    Bundle
  75.    
  76. """
  77. import pickle
  78. import logging
  79. import functools
  80. import inspect
  81. import textwrap
  82. import numpy as np
  83. from collections import OrderedDict
  84. from datetime import datetime
  85. from time import sleep
  86. import matplotlib.pyplot as plt
  87. import copy
  88. import os
  89. import re
  90. import readline
  91. import json
  92. from phoebe.utils import callbacks, utils, plotlib, coordinates, config
  93. from phoebe.parameters import parameters
  94. from phoebe.parameters import definitions
  95. from phoebe.parameters import datasets
  96. from phoebe.parameters import create
  97. from phoebe.parameters import tools
  98. from phoebe.parameters import feedback as mod_feedback
  99. from phoebe.backend import fitting, observatory, plotting
  100. from phoebe.backend import universe
  101. from phoebe.atmospheres import limbdark
  102. from phoebe.io import parsers
  103. from phoebe.dynamics import keplerorbit
  104. from phoebe.frontend.plotting import Axes, Figure
  105. from phoebe.frontend.usersettings import Settings
  106. from phoebe.frontend.common import Container, rebuild_trunk
  107. #~ from phoebe.frontend.figures import Axes
  108. from phoebe.units import conversions
  109. from phoebe.frontend import phcompleter
  110. from phoebe.frontend import stringreps
  111.  
  112. logger = logging.getLogger("BUNDLE")
  113. logger.addHandler(logging.NullHandler())
  114.  
  115. def run_on_server(fctn):
  116.     """
  117.    Parse usersettings to determine whether to run a function locally
  118.    or submit it to a server
  119.    
  120.    [FUTURE]
  121.    """
  122.     @functools.wraps(fctn)
  123.     def parse(bundle,*args,**kwargs):
  124.         """
  125.        Real parser.
  126.        """
  127.         # first we need to reconstruct the arguments passed to the original function
  128.         callargs = inspect.getcallargs(fctn,bundle,*args,**kwargs)
  129.         dump = callargs.pop('self')
  130.         callargstr = ','.join(["%s=%s" % (key, "\'%s\'" % callargs[key]\
  131.             if isinstance(callargs[key],str) else callargs[key]) for key in callargs.keys()])
  132.  
  133.         # determine if the function is supposed to be run on a server
  134.         servername = kwargs['server'] if 'server' in kwargs.keys() else None
  135.  
  136.         # is_server is a kwarg that will be True in the script running on a server
  137.         # itself - this just simply avoids the server resending to itself, without
  138.         # having to remove server from the kwargs
  139.         is_server = kwargs.pop('is_server',False)
  140.  
  141.         # now, if this function is supposed to run on a server, let's prepare
  142.         # the script and files and submit the job.  Otherwise we'll just return
  143.         # with the original function as called.
  144.         if servername is not False and servername is not None and not is_server:
  145.             # first we need to retrieve the requested server using get_server
  146.             server =  bundle.get_server(servername)
  147.            
  148.             # servers can be local (only have mpi settings) - in these cases we
  149.             # don't need to submit a script, and the original function can handle
  150.             # retrieving and applying the mpi settings
  151.             if server.is_external():
  152.                
  153.                 # now we know that we are being asked to run on an external server,
  154.                 # so let's do a quick check to see if the server seems to be online
  155.                 # and responding.  If it isn't we'll provide an error message and abort
  156.                 if server.check_status():
  157.                    
  158.                     # The external server seems to be responding, so now we need to
  159.                     # prepare all files and the script to run on the server
  160.                     mount_dir = server.server_ps.get_value('mount_dir')
  161.                     server_dir = server.server_ps.get_value('server_dir')
  162.                    
  163.                     # Copy the bundle to the mounted directory, without removing
  164.                     # usersettings (so that the server can have access to everything)
  165.                     logger.info('copying bundle to {}'.format(mount_dir))
  166.                     timestr = str(datetime.now()).replace(' ','_')
  167.                     in_f = '%s.bundle.in.phoebe' % timestr
  168.                     out_f = '%s.bundle.out.phoebe' % timestr
  169.                     script_f = '%s.py' % timestr
  170.                     bundle.save(os.path.join(mount_dir,in_f),save_usersettings=True) # might have a problem here if threaded!!
  171.                    
  172.                     # Now we write a script file to the mounted directory which will
  173.                     # load the saved bundle file, call the original function with the
  174.                     # same arguments, and save the bundle to a new file
  175.                     #
  176.                     # we'll also provide some status updates so that we can see when
  177.                     # the script has started/failed/completed
  178.                     logger.info('creating script to run on {}'.format(servername))
  179.                     f = open(os.path.join(mount_dir,script_f),'w')
  180.                     f.write("#!/usr/bin/python\n")
  181.                     f.write("try:\n")
  182.                     f.write("\timport os\n")
  183.                     f.write("\tfrom phoebe.frontend.bundle import load\n")
  184.                     f.write("\tbundle = load('%s',load_usersettings=False)\n" % os.path.join(server_dir,in_f))
  185.                     f.write("\tbundle.%s(%s,is_server=True)\n" % (fctn.func_name, callargstr))
  186.                     f.write("\tbundle.save('%s')\n" % (os.path.join(server_dir,out_f)))
  187.                     f.write("except:\n")
  188.                     f.write("\tos.system(\"echo 'failed' > %s.status\")\n" % (script_f))
  189.                     f.close()
  190.                    
  191.                     # create job and submit
  192.                     logger.info('running script on {}'.format(servername))
  193.                     server.run_script_external(script_f)
  194.                    
  195.                     # lock the bundle and store information about the job
  196.                     # so it can be retrieved later
  197.                     # the bundle returned from the server will not have this lock - so we won't have to manually reset it
  198.                     bundle.lock = {'locked': True, 'server': servername, 'script': script_f, 'command': "bundle.%s(%s)\n" % (fctn.func_name, callargstr), 'files': [in_f, out_f, script_f], 'rfile': out_f}
  199.                                        
  200.                     return
  201.  
  202.                 else:
  203.                     logger.warning('{} server not available'.format(servername))
  204.                     return
  205.  
  206.         # run locally by calling the original function
  207.         return fctn(bundle, *args, **kwargs)
  208.    
  209.     return parse
  210.    
  211.      
  212.    
  213.    
  214.  
  215. class Bundle(Container):
  216.     """
  217.    Class representing a collection of systems and stuff related to it.
  218.    
  219.    Accessing and changing parameters via twigs:
  220.    
  221.    .. autosummary::
  222.    
  223.        phoebe.frontend.common.Container.get_value
  224.        phoebe.frontend.common.Container.get_value_all
  225.        phoebe.frontend.common.Container.get_ps
  226.        phoebe.frontend.common.Container.get_parameter
  227.        phoebe.frontend.common.Container.get_adjust
  228.        phoebe.frontend.common.Container.get_prior
  229.        phoebe.frontend.common.Container.set_value
  230.        phoebe.frontend.common.Container.set_value_all
  231.        phoebe.frontend.common.Container.set_ps
  232.        phoebe.frontend.common.Container.set_adjust
  233.        phoebe.frontend.common.Container.set_prior
  234.        phoebe.frontend.common.Container.attach_ps
  235.        Bundle.add_parameter
  236.        phoebe.frontend.common.Container.twigs
  237.        phoebe.frontend.common.Container.search
  238.    
  239.    Adding and handling data:        
  240.        
  241.    .. autosummary::    
  242.        
  243.        Bundle.set_system
  244.        
  245.        Bundle.run_compute
  246.        
  247.        Bundle.lc_fromfile
  248.        Bundle.lc_fromarrays
  249.        Bundle.lc_fromexisting
  250.        Bundle.rv_fromfile
  251.        Bundle.rv_fromarrays
  252.        Bundle.rv_fromexisting
  253.        Bundle.sed_fromfile
  254.        Bundle.sed_fromarrays
  255.        Bundle.sp_fromfile
  256.        Bundle.sp_fromarrays
  257.        Bundle.if_fromfile
  258.        Bundle.if_fromarrays
  259.        
  260.                
  261.        Bundle.disable_lc
  262.        Bundle.disable_rv
  263.    
  264.    Computations and fitting:
  265.    
  266.    .. autosummary::
  267.    
  268.        Bundle.run_compute
  269.        Bundle.run_fitting
  270.    
  271.    High-level plotting functionality:
  272.    
  273.    .. autosummary::
  274.    
  275.        Bundle.plot_obs
  276.        Bundle.plot_syn
  277.        Bundle.plot_residuals
  278.        Bundle.plot_mesh
  279.  
  280.        
  281.    Convenience functions:
  282.    
  283.    .. autosummary::
  284.        
  285.        Bundle.get_datarefs
  286.        Bundle.get_lc_datarefs
  287.        Bundle.get_rv_datarefs
  288.        Bundle.get_system
  289.        Bundle.get_object
  290.        Bundle.get_orbit
  291.        
  292.    
  293.    
  294.    **Printing information**
  295.    
  296.    An overview of all the Parameters and observations loaded in a Bundle can
  297.    be printed to the screen easily using::
  298.    
  299.        mybundle = phoebe.Bundle()
  300.        print(mybundle)
  301.        
  302.    Extra functions are available that return informational strings
  303.    
  304.    .. autosummary::
  305.    
  306.        Bundle.summary
  307.        Bundle.info
  308.        
  309.        
  310.    **Initialization**
  311.    
  312.    You can initiate a bundle in different ways:
  313.    
  314.      1. Using the default binary parameters::
  315.      
  316.          mybundle = Bundle()
  317.          
  318.      2. Via a PHOEBE 2.0 file in JSON format::
  319.      
  320.          mybundle = Bundle('newbundle.json')
  321.    
  322.      3. Via a Phoebe Legacy ASCII parameter file::
  323.      
  324.          mybundle = Bundle('legacy.phoebe')
  325.    
  326.      4. Via a Body or BodyBag::
  327.      
  328.          mysystem = phoebe.create.from_library('V380_Cyg', create_body=True)
  329.          mybundle = Bundle(mysystem)
  330.      
  331.      5. Via the predefined systems in the library::
  332.      
  333.          mybundle = Bundle('V380_Cyg')            
  334.        
  335.    For more details, see :py:func:`set_system`.
  336.    
  337.    **Interface**
  338.    
  339.    The interaction with a Bundle is much alike interaction with a Python
  340.    dictionary. The following functionality is implemented and behaves as
  341.    expected::
  342.            
  343.            # return the value of the period if it exists, raises error if 'period' does not exist
  344.            period = mybundle['period']
  345.            
  346.            # set the value of the period if it exists, raises error otherwise
  347.            mybundle['period'] = 5.0
  348.            
  349.            # return the value of the period if it exists, else returns None
  350.            period = mybundle.get('period')
  351.            
  352.            # return the value of the period if it exists, else returns default_value (whatever it is)
  353.            period = mybundle.get('period', default_value)
  354.            
  355.            # returns a list of available keys
  356.            keys = mybundle.keys()
  357.            
  358.            # returns a list of values
  359.            values = mybundle.values()
  360.            
  361.            # iterate over the keys in the Bundle
  362.            for key in mybundle:
  363.                print(key, mybundle[key])
  364.    
  365.    .. important::
  366.    
  367.        *keys* are referred to as *twigs* in the context of Bundles. They behave
  368.        much like dictionary keys, but are much more flexible to account for the
  369.        plethora of available parameters (and possible duplicates!) in the
  370.        Bundle. For example, both stars in a binary need atmosphere tables in
  371.        order to compute their bolometric luminosities. This is done via the
  372.        parameter named ``atm``, but since it is present in both components,
  373.        you need to ``@`` operator to be more specific:
  374.        
  375.            >>> mybundle['atm@primary'] = 'kurucz'
  376.            >>> mybundle['atm@secondary'] = 'blackbody'
  377.            
  378.    
  379.    
  380.        
  381.    **Structure of the Bundle**
  382.        
  383.    A Bundle contains:
  384.    
  385.        - a Body (or BodyBag), called :envvar:`system` in this context.
  386.        - a list of compute options which can be stored and used to compute observables.
  387.        
  388.        
  389.    """        
  390.     def __init__(self, system=None, remove_dataref=False):
  391.         """
  392.        Initialize a Bundle.
  393.  
  394.        For all the different possibilities to set a system, see :py:func:`Bundle.set_system`.
  395.        """
  396.        
  397.         # self.sections is an ordered dictionary containing lists of
  398.         # ParameterSets (or ParameterSet-like objects)
  399.  
  400.         super(Bundle, self).__init__()
  401.        
  402.         self.sections['system'] = [None] # only 1
  403.         self.sections['compute'] = []
  404.         self.sections['fitting'] = []
  405.         self.sections['dataset'] = []
  406.         self.sections['feedback'] = []
  407.         #~ self.sections['version'] = []
  408.         self.sections['figure'] = []
  409.         self.sections['axes'] = []
  410.         self.sections['plot'] = []
  411.         #~ self.sections['meshview'] = [parameters.ParameterSet(context='plotting:mesh')] # only 1
  412.         #~ self.sections['orbitview'] = [parameters.ParameterSet(context='plotting:orbit')] # only 1
  413.        
  414.         self.current_axes = None
  415.         self.current_figure = None
  416.         self.currently_plotted = [] # this will hold (axesref, plotref) pairs of items already plotted on-the-fly
  417.        
  418.         # self.select_time controls at which time to draw the meshview and
  419.         # the 'selector' on axes (if enabled)
  420.         self.select_time = None
  421.        
  422.         # we need to keep track of all attached signals so that they can
  423.         # be purged before pickling the bundle, and can be restored
  424.         self.signals = {}
  425.         self.attached_signals = []
  426.         self.attached_signals_system = [] #these will be purged when making copies of the system and can be restored through set_system
  427.        
  428.         # Now we load a copy of the usersettings into self.usersettings
  429.         # Note that by default these will be removed from the bundle before
  430.         # saving, and reimported when the bundle is loaded
  431.         self.set_usersettings()
  432.        
  433.         # self.lock simply keeps track of whether the bundle is waiting
  434.         # on a job to complete on a server.  If a lock is in place, it can
  435.         # manually be removed by self.server_cancel()
  436.         self.lock = {'locked': False, 'server': '', 'script': '', 'command': '', 'files': [], 'rfile': None}
  437.  
  438.         # Let's keep track of the filename whenever saving the bundle -
  439.         # if self.save() is called without a filename but we have one in
  440.         # memory, we'll try to save to that location.
  441.         self.filename = None
  442.        
  443.         # self.settings are bundle-level settings that control defaults
  444.         # for bundle functions.  These do not belong in either individual
  445.         # ParameterSets or the usersettings
  446.         self.settings = {}
  447.         self.settings['add_version_on_compute'] = False
  448.         self.settings['add_feedback_on_fitting'] = False
  449.         self.settings['update_mesh_on_select_time'] = False
  450.        
  451.         # Next we'll set the system, which will parse the string sent
  452.         # to init and will handle attaching all necessary signals
  453.         if system is not None:
  454.             # then for now (even though its hacky), we'll initialize
  455.             # everything by setting the default first
  456.             self.set_system()
  457.         self.set_system(system, remove_dataref=remove_dataref)
  458.        
  459.         # Lastly, make sure all atmosphere tables are registered
  460.         atms = self._get_by_search('atm', kind='Parameter', all=True, ignore_errors=True)
  461.         ldcoeffs = self._get_by_search('ld_coeffs', kind='Parameter', all=True, ignore_errors=True)
  462.         for atm in atms:
  463.             limbdark.register_atm_table(atm.get_value())
  464.         for ldcoeff in ldcoeffs:
  465.             limbdark.register_atm_table(ldcoeff.get_value())
  466.        
  467.         # set tab completer
  468.         readline.set_completer(phcompleter.Completer().complete)
  469.         readline.set_completer_delims(' \t\n`~!#$%^&*)-=+]{}\\|;:,<>/?')
  470.         readline.parse_and_bind("tab: complete")
  471.  
  472.     def _loop_through_container(self):
  473.         """
  474.        [FUTURE]
  475.  
  476.        override container defaults to also loop through the usersettings
  477.        and copy as necessary
  478.  
  479.        This function loops through the container and returns a list of dictionaries
  480.        (called the "trunk").
  481.        
  482.        This function is called by _build_trunk and the rebuild_trunk decorator,
  483.        and shouldn't need to be called elsewhere        
  484.        """
  485.        
  486.         # we need to override the Container._loop_through_container to
  487.         # also search through usersettings and copy any items that do
  488.         # not exist here yet
  489.        
  490.         # first get items from bundle
  491.         return_items = super(Bundle, self)._loop_through_container(do_sectionlevel=False)
  492.         bundle_twigs = [ri['twig'] for ri in return_items]
  493.        
  494.         # then get items from usersettings, checking each twig to see if there is a duplicate
  495.         # with those found from the bundle.  If so - the bundle version trumps.  If not -
  496.         # we need to make a copy to the bundle and return that version
  497.        
  498.         for ri in self.get_usersettings()._loop_through_container():
  499.             if ri['twig'] not in bundle_twigs:
  500.                 # then we need to make the copy
  501.                
  502.                 if ri['section'] in ['compute','fitting']:
  503.                     if ri['kind']=='OrderedDict':
  504.                         # then this is at the section-level, and these
  505.                         # will be rebuilt for the bundle later, so let's
  506.                         # ignore for now
  507.                         ri = None
  508.                     else:
  509.                         item_copy = ri['item'].copy()
  510.                         ri['item'] = item_copy
  511.  
  512.                         ri['container'] = self.__class__.__name__
  513.                         ri['twig_full'] = "{}@{}".format(ri['twig'],ri['container'])
  514.                        
  515.                         # now we need to attach to the correct place in the bundle
  516.                         if isinstance(item_copy, parameters.ParameterSet):
  517.                             # The following check is added to make old(er)
  518.                             # Bundle files still loadable.
  519.                             if ri['section'] in self.sections:
  520.                                 self.sections[ri['section']].append(item_copy)
  521.                             else:
  522.                                 logger.error('Unable to load information from section {}'.format(ri['section']))
  523.                
  524.                 if ri is not None:
  525.                     return_items.append(ri)
  526.                    
  527.         # now that new items have been copied, we need to redo things at the section level
  528.         return_items += super(Bundle, self)._loop_through_container(do_pslevel=False)
  529.        
  530.         return return_items
  531.        
  532.     ## string representation
  533.     def __str__(self):
  534.         return self.to_string()
  535.        
  536.     def to_string(self):
  537.         """
  538.        Returns the string representation of the bundle
  539.        
  540.        :return: string representation
  541.        :rtype: str
  542.        """
  543.         return stringreps.to_str(self)
  544.         # Make sure to not print out all array variables
  545.         old_threshold = np.get_printoptions()['threshold']
  546.         np.set_printoptions(threshold=8)
  547.         # TODO: expand this to be generic across all sections
  548.         txt = ""
  549.         txt += "============ Compute ============\n"
  550.         computes = self._get_dict_of_section('compute').values()
  551.         for icomp in computes:
  552.             mystring = []
  553.             for par in icomp:
  554.                 mystring.append("{}={}".format(par,icomp.get_parameter(par).to_str()))
  555.                 if icomp.get_parameter(par).has_unit():
  556.                     mystring[-1] += ' {}'.format(icomp.get_parameter(par).get_unit())
  557.             mystring = ', '.join(mystring)
  558.             txt += "\n".join(textwrap.wrap(mystring, initial_indent='', subsequent_indent=7*' ', width=79))
  559.             txt += "\n"
  560.         txt += "============ Other ============\n"
  561.         if len(self._get_dict_of_section("fitting")):
  562.             txt += "{} fitting options\n".format(len(self._get_dict_of_section("fitting")))
  563.         if len(self._get_dict_of_section("axes")):
  564.             txt += "{} axes\n".format(len(self._get_dict_of_section("axes")))
  565.         txt += "============ System ============\n"
  566.         txt += self.list(summary='full')
  567.        
  568.         # Default printoption
  569.         np.set_printoptions(threshold=old_threshold)
  570.         return txt
  571.    
  572.     #{ Settings
  573.     def set_usersettings(self,basedir=None):
  574.         """
  575.        Load user settings into the bundle
  576.        
  577.        These settings are not saved with the bundle, but are removed and
  578.        reloaded everytime the bundle is loaded or this function is called.
  579.        
  580.        @param basedir: location of cfg files (or none to load from default location)
  581.        @type basedir: string
  582.        """
  583.         if basedir is None or isinstance(basedir,str):
  584.             settings = Settings()
  585.         else: # if settings are passed instead of basedir - this should be fixed
  586.             settings = basedir
  587.            
  588.         # else we assume settings is already the correct type
  589.         self.usersettings = settings
  590.         self._build_trunk()
  591.        
  592.     def get_usersettings(self):
  593.         """
  594.        Return the user settings class
  595.        
  596.        These settings are not saved with the bundle, but are removed and
  597.        reloaded everytime the bundle is loaded or set_usersettings is called.
  598.        """
  599.         return self.usersettings
  600.        
  601.     def save_usersettings(self, basedir=None):
  602.         """
  603.        save all usersettings in to .cfg files in basedir
  604.        if not provided, basedir will default to ~/.phoebe (recommended)
  605.        
  606.        
  607.        @param basedir: base directory, or None to save to initialized basedir
  608.        @type basedir: str or None
  609.        """
  610.         self.get_usersettings().save()
  611.        
  612.     def restart_logger(self, label='default_logger', **kwargs):
  613.         """
  614.        [FUTURE]
  615.        
  616.        restart the logger
  617.        
  618.        any additional arguments passed will temporarily override the settings
  619.        in the stored logger.  These can include style, clevel, flevel, filename, filemode
  620.        
  621.        @param label: the label of the logger (will default if not provided)
  622.        @type label: str
  623.        """
  624.        
  625.         self.get_usersettings().restart_logger(label, **kwargs)
  626.        
  627.        
  628.            
  629.     def get_server(self, label=None):
  630.         """
  631.        Return a server by name
  632.        
  633.        [FUTURE]
  634.        
  635.        @param servername: name of the server
  636.        @type servername: string
  637.        """
  638.         return self._get_by_section(label,"server")
  639.        
  640.     #}    
  641.     #{ System
  642.     def set_system(self, system=None, remove_dataref=False):
  643.         """
  644.        Change or set the system.
  645.        
  646.        Possibilities:
  647.        
  648.        1. If :envvar:`system` is a Body, then that body will be set as the system
  649.        2. If :envvar:`system` is a string, the following options exist:
  650.            - the string represents a Phoebe pickle file containing a Body; that
  651.              one will be set
  652.            - the string represents a Phoebe Legacy file, then it will be parsed
  653.              to a system
  654.            - the string represents a WD lcin file, then it will be parsed to
  655.              a system
  656.            - the string represents a system from the library (or spectral type),
  657.              then the library will create the system
  658.        
  659.        With :envvar:`remove_dataref`, you can either choose to remove a
  660.        specific dataset (if it is a string with the datareference), remove all
  661.        data (if it is ``True``) or keep them as they are (if ``False``, the
  662.        default).
  663.        
  664.        :param system: the new system
  665.        :type system: Body or str
  666.        :param remove_dataref: remove any/all datasets or keep them
  667.        :type remove_dataref: ``False``, ``None`` or a string
  668.        """
  669.         # Possibly we initialized an empty Bundle
  670.         if system is None:
  671.             #~ self.sections['system'] = [None]
  672.             #~ return None
  673.             library_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)),'../parameters/library/')
  674.             system = os.path.join(library_dir, 'defaults.phoebe')
  675.         elif system is False:
  676.             self.system = None
  677.             return
  678.            
  679.         # Or a real system
  680.         file_type = None
  681.        
  682.         if isinstance(system, universe.Body):
  683.             self.sections['system'] = [system]
  684.         elif isinstance(system, list) or isinstance(system, tuple):
  685.             self.sections['system'] = [create.system(system)]
  686.        
  687.         # Or we could've given a filename
  688.         else:
  689.             if not os.path.isfile(system):
  690.                 # then let's see if the filename exists in the library
  691.                 library_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)),'../parameters/library/')
  692.                 if os.path.isfile(os.path.join(library_dir, system)):
  693.                     system = os.path.join(library_dir, system)
  694.             # Try to guess the file type (if it is a file)
  695.             if os.path.isfile(system):
  696.                 try:
  697.                     f = open(system, 'r')
  698.                     load_dict = json.load(f)
  699.                     f.close()
  700.                
  701.                 except ValueError:
  702.                     file_type, contents = guess_filetype(system)
  703.                     if file_type in ['wd', 'pickle_body']:
  704.                         system = contents
  705.                     elif file_type == 'phoebe_legacy':
  706.                         system = contents[0]
  707.                         if contents[1].get_value('label') in [c.get_value('label') for c in self.sections['compute']]:
  708.                             self.remove_compute(contents[1].get_value('label'))
  709.                         self.sections['compute'].append(contents[1])
  710.                     elif file_type == 'pickle_bundle':
  711.                         system = contents.get_system()
  712.                         self.sections = contents.sections.copy()
  713.                
  714.                 else:
  715.                     self._load_json(system)
  716.                     file_type = 'json'                    
  717.  
  718.             else:
  719.                 try:
  720.                 #~ if True:
  721.                     self._from_dict(system)
  722.                 except:
  723.                     # As a last resort, we pass it on to 'body_from_string' in the
  724.                     # create module:                    
  725.                     system = create.body_from_string(system)
  726.                 else:
  727.                     file_type = 'json'
  728.            
  729.             if file_type is not 'json':
  730.                 # if it was json, then we've already attached the system
  731.                 self.sections['system'] = [system]
  732.          
  733.         system = self.get_system()
  734.         if system is None:
  735.             raise IOError('Initalisation failed: file/system not found')
  736.        
  737.         # Clear references if necessary:
  738.         if remove_dataref is not False:
  739.             if remove_dataref is True:
  740.                 remove_dataref = None
  741.             system.remove_ref(remove_dataref)
  742.            
  743.         # initialize uptodate
  744.         #~ print system.get_label()
  745.         system.uptodate = False
  746.        
  747.         # connect signals
  748.         #self.attach_system_signals()
  749.         self._build_trunk()
  750.        
  751.     def get_system(self):
  752.         """
  753.        Return the system.
  754.        
  755.        :return: the attached system
  756.        :rtype: Body or BodyBag
  757.        """
  758.         # we have to handle system slightly differently since building
  759.         # the trunk requires calling this function
  760.         return self.sections['system'][0]
  761.         #~ return self._get_by_search(section='system', ignore_errors=True)
  762.    
  763.     def summary(self, objref=None):
  764.         """
  765.        Make a summary of the hierarchy of the system (or any object in it).
  766.        
  767.        :param objref: object reference
  768.        :type objref: str or None (defaults to the whole system)
  769.        :return: summary string
  770.        :rtype: str
  771.        """
  772.         bund_str = ""
  773.         computes = self._get_dict_of_section('compute')
  774.         if len(computes):
  775.             bund_str+= "* Compute: " + ", ".join(computes.keys()) + '\n'
  776.         #fittings = self._get_dict_of_section("fitting")
  777.         #if len(fittings):
  778.         #    bund_str+= "Fitting: " + ", ".join(fittings.keys()) + '\n'
  779.         #axes = self._get_dict_of_section("axes")
  780.         #if len(axes):
  781.         #    bund_str+= "Axes: " + ", ".join(axes.keys()) + '\n'
  782.         system_str = self.get_object(objref).list(summary='cursory')
  783.         return system_str + '\n\n' + bund_str
  784.        
  785.     def tree(self):
  786.         """
  787.        Return a summary of the system.
  788.        
  789.        :return: string representation of the system
  790.        :rtype: str
  791.        """
  792.         return self.to_string()
  793.    
  794.     def list(self, summary=None, *args):
  795.         """
  796.        List with indices all the ParameterSets that are available.
  797.        
  798.        Simply a shortcut to :py:func:`bundle.get_system().list(...) <universe.Body.list>`.
  799.        See that function for more info on the arguments.
  800.        """
  801.         return self.get_system().list(summary,*args)
  802.    
  803.    
  804.     def clear_syn(self):
  805.         """
  806.        Clear all synthetic datasets.
  807.        
  808.        Simply a shortcut to :py:func:`bundle.get_system().clear_synthetic() <phoebe.backend.universe.Body.clear_synthetic>`
  809.        """
  810.         self.get_system().clear_synthetic()
  811.         self._build_trunk()
  812.        
  813.     def set_time(self, time, label=None, server=None, **kwargs):
  814.         """
  815.        Set the time of a system, taking compute options into account.
  816.                
  817.        [FUTURE]
  818.        
  819.        @param time: time
  820.        @type time: float
  821.        """
  822.         system = self.get_system()
  823.        
  824.         # clear all previous models and create new model
  825.         system.clear_synthetic()
  826.  
  827.         # <pieterdegroote> Necessary?
  828.         system.set_time(0)
  829.        
  830.         # get compute options, handling 'default' if label==None
  831.         options = self.get_compute(label, create_default=True).copy()
  832.        
  833.         # get server options
  834.         # <kyle> this is dangerous and won't always work (if server is not local)
  835.         if server is not None:
  836.             server = self.get_server(server)
  837.             mpi = server.mpi_ps
  838.         else:
  839.             mpi = kwargs.pop('mpi', None)
  840.        
  841.         options['time'] = [time]
  842.         options['types'] = ['lc']
  843.         options['refs'] = ['all']
  844.         options['samprate'] = [[0]]
  845.         system.compute(mpi=mpi, **options)
  846.                
  847.         #~ system.uptodate = label
  848.         #~ self.attach_system_signals()
  849.        
  850.     def get_uptodate(self):
  851.         """
  852.        Check whether the synthetic model is uptodate
  853.        
  854.        If any parameters in the system have changed since the latest
  855.        run_compute this will return False.
  856.        If not, this will return the label of the latest compute options
  857.        
  858.        [FUTURE]
  859.        
  860.        @return: uptodate
  861.        @rtype: bool or str
  862.        """
  863.         return self.get_system().uptodate
  864.            
  865.     #}
  866.     #{ Parameters/ParameterSets
  867.            
  868.     def get_logp(self, dataset=None):
  869.         """
  870.        Retrieve the log probability of a collection of (or all) datasets.
  871.        
  872.        This is wrapper around :py:func:`Body.get_logp <phoebe.backend.universe.Body.get_logp>`
  873.        that deals with enabled/disabled datasets, possibly on the fly. The
  874.        probability computation include the distributions of the priors on the
  875.        parameters.
  876.        
  877.        To get the :math:`\chi^2`, simply do:
  878.        
  879.        .. math::
  880.        
  881.            \chi^2 = -2\log p
  882.        """
  883.        
  884.         # Q <pieterdegroote>: should we check first for system.uptodate?
  885.         # Perhaps the run_compute didn't work out?
  886.        
  887.         if dataset is not None:
  888.             # First disable/enabled correct datasets
  889.             old_state = []
  890.             location = 0
  891.        
  892.             for obs in self.get_system().walk_type(type='obs'):
  893.                 old_state.append(obs.get_enabled())
  894.                 this_state = False
  895.                 if dataset == obs['ref'] or dataset == obs.get_context():
  896.                     if index is None or index == location:
  897.                         this_state = True
  898.                     location += 1
  899.                 obs.set_enabled(this_state)
  900.        
  901.         # Then compute statistics
  902.         logf, chi2, n_data = self.get_system().get_logp(include_priors=True)
  903.        
  904.         # Then reset the enable property
  905.         if dataset is not None:
  906.             for obs, state in zip(self.get_system().walk_type(type='obs'), old_state):
  907.                 obs.set_enabled(state)
  908.        
  909.         return -0.5*sum(chi2)
  910.    
  911.     #}
  912.     #{ Objects
  913.     def _get_object(self, objref=None):
  914.         """
  915.        Twig-free version of get_object.
  916.        
  917.        This version of get_object that does not use twigs... this must remain
  918.        so that we can call it while building the trunk
  919.  
  920.        :param objref: Body's label or twig
  921.        :type objref: str
  922.        :return: the Body corresponding to the label
  923.        :rtype: Body
  924.        :raises ValueError: when objref is not present
  925.        """
  926.         # handle objref if twig was given instead
  927.         objref = objref.split('@')[0] if objref is not None else None
  928.         # return the Body/BodyBag from the system hierarchy
  929.         system = self.get_system()
  930.         if objref is None or system.get_label() == objref or objref == '__nolabel__':
  931.             this_child = system
  932.         else:
  933.             for child in system.walk_bodies():
  934.                 if child.get_label() == objref:
  935.                     this_child = child
  936.                     break
  937.             else:
  938.                 raise ValueError("Object {} not found".format(objref))
  939.         return this_child
  940.    
  941.     def get_object(self, twig=None):
  942.         """
  943.        Retrieve a Body or BodyBag from the system
  944.        
  945.        if twig is None, will return the system itself
  946.        
  947.        :param twig: the twig/twiglet to use when searching
  948.        :type twig: str
  949.        :return: the object
  950.        :rtype: Body
  951.        """
  952.         if twig is None or twig == '__nolabel__':
  953.             return self.get_system()
  954.         return self._get_by_search(twig, kind='Body')
  955.        
  956.     def get_children(self, twig=None):
  957.         """
  958.        Retrieve the direct children of a Body or BodyBag from the system
  959.        
  960.        :param twig: the twig/twiglet to use when searching
  961.        :type twig: str
  962.        :return: the children
  963.        :rtype: list of bodies
  964.        """
  965.         obj = self.get_object(twig)
  966.         if hasattr(obj, 'bodies'):
  967.             #return [b.bodies[0] if hasattr(b,'bodies') else b for b in obj.bodies]
  968.             return obj.bodies
  969.         else:
  970.             return []
  971.        
  972.        
  973.     def get_parent(self, twig=None):
  974.         """
  975.        Retrieve the direct parent of a Body or BodyBag from the system
  976.        
  977.        :param twig: the twig/twiglet to use when searching
  978.        :type twig: str
  979.        :return: the children
  980.        :rtype: Body
  981.        """
  982.         return self.get_object(twig).get_parent()
  983.        
  984.        
  985.     def get_orbit(self, objref, time=None, phase=None, length_unit='Rsol',
  986.                   velocity_unit='km/s', time_unit='d', observer_position=None):
  987.         """
  988.        Retrieve the position, velocity, barycentric and proper time of an object.
  989.        
  990.        This function returns 3 quantities: the position of the object of the
  991.        orbit (by default in solar radii, change via :envvar:`length_unit`) for
  992.        each time point, the velocity of the object (by default in km/s, change
  993.        via :envvar:`velocity_unit`) and the proper time of the object (by
  994.        default in days, change via :envvar:`time_unit`).
  995.        
  996.        The coordinate frame is chosen such that for each tuple (X, Y, Z), X and
  997.        Y are in the plane of the sky, and Z is the radial direction pointing
  998.        away from the observer. Negative velocities are towards the observer.
  999.        Negative Z coordinates are closer to the observer than positive Z
  1000.        coordinates. When :envvar:`length_unit` is really length (as opposed to
  1001.        angular, see below), then the X, Y and Z coordinates are relative to the
  1002.        center of mass of the system.
  1003.        
  1004.        Length units can also be given in angle (rad, deg, mas), in which case
  1005.        the angular coordinates are returned for the X and Y coordinates and the
  1006.        Z coordinate is absolute (i.e. including the distance to the object). Be
  1007.        sure to set the distance to a realistic value!
  1008.        
  1009.        **Example usage**::
  1010.        
  1011.            mybundle = Bundle()
  1012.            position, velocity, bary_time, proper_time = mybundle.get_orbit('primary')
  1013.        
  1014.            # On-sky orbit:
  1015.            plt.plot(position[0], position[1], 'k-')
  1016.            
  1017.            # Radial velocity:
  1018.            plt.plot(bary_time, velocity[2], 'r-')
  1019.        
  1020.        
  1021.        
  1022.        :param objref: name of the object to retrieve the orbit from
  1023.        :type objref: str
  1024.        :param time: time array to compute the orbit on. If none is given, the
  1025.         time array will cover one orbital period of the outer orbit, with a time
  1026.         resolution of at least 100 points per inner orbital period. Time array
  1027.         has to be in days, but can be converted in the output.
  1028.        :type time: array
  1029.        :return: position (solar radii), velocity (km/s), barycentric times (d), proper times (d)
  1030.        :rtype: 3-tuple, 3-tuple, array, array
  1031.        """
  1032.         # Get the Body to compute the orbit of
  1033.         body = self.get_object(objref)
  1034.        
  1035.         # Get a list of all orbits the component belongs to, and if it's the
  1036.         # primary or secondary in each of them
  1037.         orbits, components = body.get_orbits()
  1038.        
  1039.         # If no times are given, construct a time array that has the length
  1040.         # of the outermost orbit, and a resolution to sample the inner most
  1041.         # orbit with at least 100 points
  1042.         if time is None:
  1043.             period_outer = orbits[-1]['period']
  1044.             t0 = orbits[-1]['t0']
  1045.            
  1046.             if phase is None:
  1047.                 period_inner = orbits[0]['period']
  1048.                 t_step = period_inner / 100.
  1049.                 time = np.arange(t0, t0+period_outer, t_step)
  1050.             else:
  1051.                 time = t0 + phase*period_outer
  1052.        
  1053.         pos, vel, proper_time = keplerorbit.get_barycentric_hierarchical_orbit(time,
  1054.                                                              orbits, components)
  1055.        
  1056.         # Correct for systemic velocity (we didn't reverse the z-axis yet!)
  1057.         globs = self.get_system().get_globals()
  1058.         if globs is not None:
  1059.             vgamma = globs['vgamma']
  1060.             vel[-1] = vel[-1] - conversions.convert('km/s', 'Rsol/d', vgamma)
  1061.        
  1062.         # Convert to correct units. If positional units are angles rather than
  1063.         # length, we need to first convert the true coordinates to spherical
  1064.         # coordinates
  1065.         if not observer_position and conversions.get_type(length_unit) == 'length':
  1066.             pos = [conversions.convert('Rsol', length_unit, i) for i in pos]
  1067.            
  1068.             # Switch direction of Z coords
  1069.             pos[2] = -1*pos[2]
  1070.            
  1071.         # Angular position wrt solar system barycentre
  1072.         elif not observer_position:
  1073.             position = body.get_globals(context='position')
  1074.             distance = position.get_value_with_unit('distance')
  1075.             origin = (position.get_value('ra', 'rad'), position.get_value('dec', 'rad'))
  1076.            
  1077.             # Switch direction of Z coords
  1078.             pos = np.array(pos)
  1079.             pos[2] = -1*pos[2]
  1080.            
  1081.             # Convert true coordinates to spherical ones. Then pos is actually
  1082.             # ra/dec
  1083.             pos = list(keplerorbit.truecoords_to_spherical(np.array(pos).T, distance=distance,
  1084.                                                 origin=origin, units=length_unit))
  1085.            
  1086.             # Take proper motions into account
  1087.             pmdec = position.get_value('pmdec', 'rad/d')
  1088.             pmra = position.get_value('pmra', 'rad/d')
  1089.             pos[1] = pos[1] + pmdec*time
  1090.             pos[0] = pos[0] + pmra*time/np.cos(pos[1])
  1091.        
  1092.         # Angular position wrt observer coordinates
  1093.         else:
  1094.             raise NotImplementedError
  1095.            
  1096.         vel = [conversions.convert('Rsol/d', velocity_unit, i) for i in vel]
  1097.        
  1098.         proper_time = conversions.convert('d', time_unit, proper_time)
  1099.         time = conversions.convert('d', time_unit, time)
  1100.        
  1101.         # Switch direction of radial velocities and Z coords
  1102.         vel[2] = -1*vel[2]
  1103.        
  1104.         return tuple(pos), tuple(vel), time, proper_time
  1105.        
  1106.        
  1107.     def get_orbitps(self, twig=None):
  1108.         """
  1109.        Retrieve the orbit ParameterSet that belongs to a given BodyBag
  1110.        
  1111.        [FUTURE]
  1112.        
  1113.        @param twig: the twig/twiglet to use when searching
  1114.        @type twig: str
  1115.        @return: the orbit PS
  1116.        @rtype: ParameterSet
  1117.        """
  1118.         # TODO: handle default if twig is None
  1119.         return self._get_by_search('orbit@{}'.format(twig), kind='ParameterSet', context='orbit')
  1120.        
  1121.        
  1122.     def get_meshps(self, twig=None):
  1123.         """
  1124.        [FUTURE]
  1125.  
  1126.        retrieve the mesh ParameterSet that belongs to a given component
  1127.        
  1128.        @param twig: the twig/twiglet to use when searching
  1129.        @type twig: str
  1130.        @return: the mesh PS
  1131.        @rtype: ParameterSet
  1132.        """
  1133.         return self._get_by_search('mesh@{}'.format(twig), kind='ParameterSet', context='mesh*')
  1134.    
  1135.    
  1136.     def set_main_period(self, period=None, objref=None):
  1137.         """
  1138.        Set the main period of the system.
  1139.        
  1140.        Any parameter that is used to set the period needs to be of dimensions
  1141.        of time or frequency, such that the conversion to time is
  1142.        straightforward.
  1143.        
  1144.        [FUTURE]
  1145.        
  1146.        :param period: twig of the Parameter that represent the main period
  1147.        :type period: str (twig)
  1148.        :raises ValueError: if period is not of time or frequency dimensions
  1149.        """
  1150.         # Get the object to set the main period of, and get the parameter
  1151.         obj = self.get_object(objref)
  1152.        
  1153.         if period is not None:
  1154.             period = self._get_by_search(period, kind='Parameter')
  1155.        
  1156.             # Check unit type:
  1157.             unit_type = conversions.get_type(period.get_unit())
  1158.             if not unit_type in ['time', 'frequency']:
  1159.                 raise ValueError('Cannot set period of the system to {} which has units of {} (needs to be time or frequency)'.format(period, unit_type))
  1160.             obj.set_period(period=period)
  1161.        
  1162.     #}  
  1163.     #{ Datasets
  1164.     def _attach_datasets(self, output, skip_defaults_from_body=True):
  1165.         """
  1166.        attach datasets and pbdeps from parsing file or creating synthetic datasets
  1167.        
  1168.        output is a dictionary with object names as keys and lists of both
  1169.        datasets and pbdeps as the values {objectname: [[ds1,ds2],[ps1,ps2]]}
  1170.        
  1171.        this is called from bundle.load_data and bundle.data_fromarrays
  1172.        and should not be called on its own  
  1173.        
  1174.        If ``skip_defaults_from_body`` is True, none of the parameters in the
  1175.        pbdep will be changed, and they will be added "as is". Else,
  1176.        ``skip_defaults_from_body`` needs to be a list of parameter names that
  1177.        **cannot** be changed. Any other key in the pbdep that is available in the
  1178.        main body parameterSet (e.g. atm, ld_coeffs...) but not in the
  1179.        ``skip_defaults_from_body`` list, will have the value taken from the main
  1180.        body. This, way, defaults from the body can be easily transferred.
  1181.        
  1182.        [FUTURE]
  1183.        """
  1184.        
  1185.         for objectlabel in output.keys():      
  1186.             # get the correct component (body)
  1187.             comp = self.get_object(objectlabel)
  1188.            
  1189.             # unpack all datasets and parametersets
  1190.             dss = output[objectlabel][0] # obs
  1191.             pss = output[objectlabel][1] # pbdep
  1192.            
  1193.             # attach pbdeps *before* obs (or will throw error and ref will not be correct)
  1194.             # pbdeps need to be attached to bodies, not body bags
  1195.             # so if comp is a body bag, attach it to all its predecessors
  1196.             if hasattr(comp, 'get_bodies'):
  1197.                 for body in comp.get_bodies():
  1198.                    
  1199.                     # get the main parameterSet:
  1200.                     main_parset = body.params.values()[0]
  1201.                    
  1202.                     for ps in pss:
  1203.                        
  1204.                         # Override defaults: those are all the keys that are
  1205.                         # available in both the pbdep and the main parset, and
  1206.                         # are not listed in skip_defaults_from_body
  1207.                         take_defaults = None
  1208.                         if skip_defaults_from_body is not True:
  1209.                             take_defaults = (set(ps.keys()) & set(main_parset.keys())) - set(skip_defaults_from_body)
  1210.                             for key in take_defaults:
  1211.                                 if ps[key] != main_parset[key]:
  1212.                                     ps[key] = main_parset[key]
  1213.                             # and in case of overwriting existing one
  1214.                             take_defaults = set(ps.keys()) - set(skip_defaults_from_body)
  1215.                         body.add_pbdeps(ps.copy(), take_defaults=take_defaults)
  1216.                        
  1217.             else:
  1218.                 # get the main parameterSet:
  1219.                 main_parset = comp.params.values()[0]
  1220.                
  1221.                 for ps in pss:
  1222.                    
  1223.                     # Override defaults: those are all the keys that are
  1224.                     # available in both the pbdep and the main parset, and
  1225.                     # are not listed in skip_defaults_from_body
  1226.                     take_defaults = None
  1227.                     if skip_defaults_from_body is not True:
  1228.                         take_defaults = (set(ps.keys()) & set(main_parset.keys())) - set(skip_defaults_from_body)
  1229.                         for key in take_defaults:
  1230.                             ps[key] = main_parset[key]
  1231.                     comp.add_pbdeps(ps.copy(), take_defaults=take_defaults)
  1232.            
  1233.             # obs get attached to the requested object
  1234.             for ds in dss:
  1235.                 #~ ds.load()
  1236.                 #ds.estimate_sigma(force=False)
  1237.                
  1238.                 # if there is a time column in the dataset, sort according to
  1239.                 # time
  1240.                 if 'time' in ds and not np.all(np.diff(ds['time'])>=0):
  1241.                     logger.warning("The observations are not sorted in time -- sorting now")
  1242.                     sa = np.argsort(ds['time'])
  1243.                     ds = ds[sa]                    
  1244.                
  1245.                 comp.add_obs(ds)
  1246.        
  1247.         # Initialize the mesh after adding stuff (i.e. add columns ld_new_ref...
  1248.         self.get_system().init_mesh()
  1249.         self._build_trunk()
  1250.    
  1251.     #rebuild_trunk done by _attach_datasets
  1252.     def data_fromfile(self, filename, category='lc', objref=None, dataref=None,
  1253.                       columns=None, units=None, **kwargs):
  1254.         """
  1255.        Add data from a file.
  1256.        
  1257.        Create multiple DataSets, load data, and add to corresponding bodies
  1258.        
  1259.        Special case here is "sed", which parses a list of snapshot multicolour
  1260.        photometry to different lcs. They will be grouped by ``filename``.
  1261.        
  1262.        @param category: category (lc, rv, sp, sed, etv)
  1263.        @type category: str
  1264.        @param filename: filename
  1265.        @type filename: str
  1266.        @param passband: passband
  1267.        @type passband: str
  1268.        @param columns: list of columns in file
  1269.        @type columns: list of strings
  1270.        @param objref: component for each column in file
  1271.        @type objref: list of strings (labels of the bodies)
  1272.        @param units: provide any non-default units
  1273.        @type units: dict
  1274.        @param dataref: name for ref for all returned datasets
  1275.        @type dataref: str
  1276.        :return: dataref of added observations (perhaps it was autogenerated!)
  1277.        :rtype: str
  1278.        """
  1279.         if units is None:
  1280.             units = {}
  1281.            
  1282.         # In some cases, we can have subcategories of categories. For example
  1283.         # "sp" can be given as a timeseries or a snapshort. They are treated
  1284.         # the same in the backend, but they need different parse functions
  1285.         if len(category.split(':')) > 1:
  1286.             category, subcategory = category.split(':')
  1287.         else:
  1288.             subcategory = None
  1289.        
  1290.         # We need a reference to link the pb and ds together.
  1291.         if dataref is None:
  1292.             # If the reference is None, suggest one. We'll add it as "lc01"
  1293.             # if no "lc01" exist, otherwise "lc02" etc (unless "lc02" already exists)
  1294.             existing_refs = self.get_system().get_refs(category=category)
  1295.             id_number = len(existing_refs)+1
  1296.             dataref = category + '{:02d}'.format(id_number)
  1297.             while dataref in existing_refs:
  1298.                 id_number += 1
  1299.                 dataref = category + '{:02d}'.format(len(existing_refs)+1)            
  1300.        
  1301.         # Individual cases
  1302.         if category == 'rv':
  1303.             output = datasets.parse_rv(filename, columns=columns,
  1304.                                        components=objref, units=units,
  1305.                                        ref=dataref,
  1306.                                        full_output=True, **kwargs)
  1307.            
  1308.         elif category == 'lc':
  1309.             output = datasets.parse_lc(filename, columns=columns, units=units,
  1310.                                        components=objref, full_output=True,
  1311.                                        ref=dataref, **kwargs)
  1312.            
  1313.             # if no componets (objref) was given, then we assume it's the system!
  1314.             for lbl in output:
  1315.                 if lbl == '__nolabel__':
  1316.                     output[self.get_system().get_label()] = output.pop('__nolabel__')
  1317.                    
  1318.            
  1319.         elif category == 'etv':
  1320.             output = datasets.parse_etv(filename, columns=columns,
  1321.                                         components=objref, units=units,
  1322.                                         full_output=True, **kwargs)
  1323.        
  1324.         elif category == 'sp':
  1325.             if subcategory is None:
  1326.                 try:
  1327.                     output = datasets.parse_spec_timeseries(filename, columns=columns,
  1328.                                        components=objref, units=units,
  1329.                                        full_output=True, ref=dataref,
  1330.                                        **kwargs)
  1331.                 except IOError:
  1332.                     raise IOError("Either the file '{}' does not exist or you've specified a snapshot spectrum as a timeseries (set snapshot=True in sp_fromfile)".format(filename))
  1333.             # Then this is a shapshot
  1334.             else:
  1335.                 output = datasets.parse_spec_as_lprof(filename, columns=columns,
  1336.                                        components=objref, units=units,
  1337.                                        full_output=True, ref=dataref,
  1338.                                        **kwargs)
  1339.        
  1340.         elif category == 'sed':
  1341.             scale, offset = kwargs.pop('adjust_scale', False), kwargs.pop('adjust_offset', False)
  1342.             output = datasets.parse_phot(filename, columns=columns,
  1343.                   units=units, group=dataref,
  1344.                   group_kwargs=dict(scale=scale, offset=offset),
  1345.                   full_output=True, **kwargs)
  1346.            
  1347.         elif category == 'if':
  1348.             if subcategory == 'oifits':
  1349.                 output = datasets.parse_oifits(filename, full_output=True,
  1350.                                                ref=dataref, **kwargs)
  1351.         #elif category == 'pl':
  1352.         #    output = datasets.parse_plprof(filename, columns=columns,
  1353.         #                               components=objref, full_output=True,
  1354.         #                               **{'passband':passband, 'ref': ref})
  1355.         else:
  1356.             output = None
  1357.             print("only lc, rv, etv, sed, and sp currently implemented")
  1358.             raise NotImplementedError
  1359.        
  1360.         if output is not None:
  1361.             self._attach_datasets(output, skip_defaults_from_body=kwargs.keys())
  1362.             return dataref
  1363.                        
  1364.                        
  1365.     #rebuild_trunk done by _attach_datasets
  1366.     def data_fromarrays(self, category='lc', objref=None, dataref=None,
  1367.                         **kwargs):
  1368.         """
  1369.        Create and attach data templates to compute the model.
  1370.  
  1371.        Additional keyword arguments contain information for the actual data
  1372.        template (cf. the "columns" in a data file) as well as for the passband
  1373.        dependable (pbdep) description of the dataset (optional, e.g.
  1374.        ``passband``, ``atm``, ``ld_func``, ``ld_coeffs``, etc). For any
  1375.        parameter that is not explicitly set, the defaults from each component
  1376.        are used, instead of the Phoebe2 defaults. For example when adding a
  1377.        light curve, pbdeps are added to each component, and the ``atm``,
  1378.        ``ld_func`` and ``ld_coeffs`` are taken from the component (i.e. the
  1379.        bolometric parameters) unless explicitly overriden.
  1380.        
  1381.        Unique references are added automatically if none are provided by the
  1382.        user (via :envvar:`dataref`). Instead of the backend-popular UUID
  1383.        system, the bundle implements a more readable system of unique
  1384.        references: the first light curve that is added is named 'lc01', and
  1385.        similarly for other categories. If the dataset with the reference
  1386.        already exists, 'lc02' is tried and so on.
  1387.        
  1388.        **Light curves (default)**
  1389.        
  1390.        Light curves are typically added to the entire system, as the combined
  1391.        light from all components is observed.
  1392.        
  1393.        For a list of available parameters, see :ref:`lcdep <parlabel-phoebe-lcdep>`
  1394.        and :ref:`lcobs <parlabel-phoebe-lcobs>`.
  1395.        
  1396.        >>> time = np.linspace(0, 10.33, 101)
  1397.        >>> bundle.data_fromarrays(time=time, passband='GENEVA.V')
  1398.        
  1399.        or in phase space (phase space will probably not work for anything but
  1400.        light curves and radial velocities):
  1401.        
  1402.        >>> phase = np.linspace(-0.5, 0.5, 101)
  1403.        >>> bundle.data_fromarrays(phase=phase, passband='GENEVA.V')
  1404.        
  1405.        **Radial velocity curves**
  1406.        
  1407.        Radial velocities are typically added to the separate components, since
  1408.        they are determined from disentangled spectra.
  1409.        
  1410.        For a list of available parameters, see :ref:`rvdep <parlabel-phoebe-rvdep>`
  1411.        and :ref:`rvobs <parlabel-phoebe-rvobs>`.
  1412.        
  1413.        >>> time = np.linspace(0, 10.33, 101)
  1414.        >>> bundle.data_fromarrays(category='rv', objref='primary', time=time)
  1415.        >>> bundle.data_fromarrays(category='rv', objref='secondary', time=time)
  1416.        
  1417.        **Spectra**
  1418.        
  1419.        Spectra are typically added to the separate components, although they
  1420.        could as well be added to the entire system.
  1421.        
  1422.        For a list of available parameters, see :ref:`spdep <parlabel-phoebe-spdep>`
  1423.        and :ref:`spobs <parlabel-phoebe-spobs>`.
  1424.        
  1425.        >>> time = np.linspace(-0.5, 0.5, 11)
  1426.        >>> wavelength = np.linspace(454.8, 455.2, 500)
  1427.        >>> bundle.data_fromarrays(category='sp', objref='primary', time=time, wavelength=wavelength)
  1428.        
  1429.        or to add to the entire system:
  1430.        
  1431.        >>> bundle.data_fromarrays(time=time, wavelength=wavelength)
  1432.        
  1433.        **Interferometry**
  1434.        
  1435.        Interferometry is typically added to the entire system.
  1436.        
  1437.        For a list of available parameters, see :ref:`ifdep <parlabel-phoebe-ifdep>`
  1438.        and :ref:`ifobs <parlabel-phoebe-ifobs>`.
  1439.        
  1440.        >>> time = 0.1 * np.ones(101)
  1441.        >>> ucoord = np.linspace(0, 200, 101)
  1442.        >>> vcoord = np.zeros(101)
  1443.        >>> bundle.data_fromarrays(category='if', time=time, ucoord=ucoord, vcoord=vcoord)
  1444.        
  1445.        One extra option for interferometry is to set the keyword :envvar:`images`
  1446.        to a string, e.g.:
  1447.        
  1448.        >>> bundle.data_fromarrays(category='if', images='debug', time=time, ucoord=ucoord, vcoord=vcoord)
  1449.        
  1450.        This will generate plots of the system on the sky with the projected
  1451.        baseline orientation (as well as some other info), but will also
  1452.        write out an image with the summed profile (_prof.png) and the rotated
  1453.        image (to correspond to the baseline orientation, (_rot.png). Lastly,
  1454.        a FITS file is output that contains the image, for use in other programs.
  1455.        This way, you have all the tools available for debugging and to check
  1456.        if things go as expected.
  1457.        
  1458.        **And then what?**
  1459.        
  1460.        After creating synthetic datasets, you'll probably want to move on to
  1461.        functions such as
  1462.        
  1463.        - :py:func:`Bundle.run_compute`
  1464.        - :py:func:`Bundle.plot_syn`
  1465.        
  1466.        :param category: one of 'lc', 'rv', 'sp', 'etv', 'if', 'pl'
  1467.        :type category: str
  1468.        :param objref: component for each column in file
  1469.        :type objref: None, str, list of str or list of bodies
  1470.        :param dataref: name for ref for all returned datasets
  1471.        :type dataref: str
  1472.        :return: dataref of added observations (perhaps it was autogenerated!)
  1473.        :rtype: str
  1474.        :raises ValueError: if :envvar:`category` is not recognised.
  1475.        :raises ValueError: if :envvar:`time` and :envvar:`phase` are both given
  1476.        :raises KeyError: if any keyword argument is not recognised as obs/dep Parameter
  1477.        :raises TypeError: if a keyword is given but the value cannot be cast to the Parameter
  1478.        """
  1479.         # create pbdeps and attach to the necessary object
  1480.         # this function will be used for creating pbdeps without loading an
  1481.         # actual file ie. creating a synthetic model only times will need to be
  1482.         # provided by the compute options (auto will not load times out of a pbdep)
  1483.        
  1484.         # Modified functionality from datasets.parse_header
  1485.        
  1486.         # What DataSet subclass do we need? We can derive it from the category.
  1487.         # This can be LCDataSet, RVDataSet etc.. If the category is not
  1488.         # recognised, we'll add the generic "DataSet".
  1489.         if not category in config.dataset_class:
  1490.             dataset_class = DataSet
  1491.         else:
  1492.             dataset_class = getattr(datasets, config.dataset_class[category])
  1493.    
  1494.         # Suppose the user did not specifiy the object to attach anything to
  1495.         if objref is None:
  1496.             # then attempt to make smart prediction
  1497.             if category in ['lc','if','sp', 'pl']:
  1498.                 # then top-level
  1499.                 components = [self.get_system()]
  1500.                 #logger.warning('components not provided - assuming {}'.format([comp.get_label() for comp in components]))
  1501.             else:
  1502.                 logger.error('data_fromarrays failed: components need to be provided')
  1503.                 return
  1504.         # is component just one string?
  1505.         elif isinstance(objref, str):
  1506.             components = [self.get_object(objref)]
  1507.         # is component a list of strings?
  1508.         elif isinstance(objref[0], str):
  1509.             components = [self.get_object(iobjref) for iobjref in objref]
  1510.         # perhaps component is a list of bodies, that's just fine then
  1511.         else:
  1512.             components = objref
  1513.        
  1514.         # We need a reference to link the pb and ds together.
  1515.         if dataref is None:
  1516.             # If the reference is None, suggest one. We'll add it as "lc01"
  1517.             # if no "lc01" exist, otherwise "lc02" etc (unless "lc02" already exists)
  1518.             existing_refs = self.get_system().get_refs(category=category)
  1519.             id_number = len(existing_refs)+1
  1520.             dataref = category + '{:02d}'.format(id_number)
  1521.             while dataref in existing_refs:
  1522.                 id_number += 1
  1523.                 dataref = category + '{:02d}'.format(len(existing_refs)+1)
  1524.        
  1525.         # Create template pb and ds:
  1526.         ds = dataset_class(context=category+'obs', ref=dataref)
  1527.         pb = parameters.ParameterSet(context=category+'dep', ref=dataref)
  1528.        
  1529.         # Split up the kwargs in extra arguments for the dataset and pbdep. We
  1530.         # are not filling in the pbdep parameters yet, because we're gonna use
  1531.         # smart defaults (see below). If a parameter is in both parameterSets,
  1532.         # the pbdep gets preference (e.g. for pblum, l3).
  1533.         pbkwargs = {}
  1534.         for key in kwargs:
  1535.             if key in pb:
  1536.                 pbkwargs[key] = kwargs[key]
  1537.             elif key in ds:
  1538.                 # Make sure time and phase are not both given, and that either
  1539.                 # time or phase ends up in the defined columns (this is important
  1540.                 # for the code that unphases)
  1541.                 if key == 'time' and 'phase' in kwargs:
  1542.                     raise ValueError("You need to give either 'time' or 'phase', not both")
  1543.                 elif key == 'phase' and 'time' in ds['columns']:
  1544.                     columns = ds['columns']
  1545.                     columns[columns.index('time')] = 'phase'
  1546.                     ds['columns'] = columns
  1547.                 # and fill in    
  1548.                 ds[key] = kwargs[key]
  1549.                
  1550.             else:
  1551.                 raise ValueError("Parameter '{}' not found in obs/dep".format(key))        
  1552.        
  1553.         # Special treatment of oversampling rate and exposure time: if they are
  1554.         # single numbers, we need to convert them in arraya as long as as the
  1555.         # times
  1556.         for expand_key in ['samprate', 'exptime']:
  1557.             if expand_key in ds and not ds[expand_key].shape:
  1558.                 ds[expand_key] = len(ds) * [ds[expand_key]]
  1559.        
  1560.         # check if all columns have the same length as time (or phase). There
  1561.         # a few exceptions: wavelength can be the same for all times for example
  1562.         reference_length = len(ds['time']) if 'time' in ds['columns'] else len(ds['phase'])
  1563.         ignore_columns = set(['wavelength'])
  1564.         for col in (set(ds['columns']) - ignore_columns):
  1565.             if not (len(ds[col])==0 or len(ds[col])==reference_length):
  1566.                 raise ValueError("Length of column {} in dataset {} does not equal length of time/phase column".format(col, dataref))
  1567.        
  1568.         output = {}
  1569.         skip_defaults_from_body = pbkwargs.keys()
  1570.         for component in components:
  1571.             pb = parameters.ParameterSet(context=category+'dep', ref=dataref, **pbkwargs)
  1572.             output[component.get_label()] = [[ds],[pb]]
  1573.         self._attach_datasets(output, skip_defaults_from_body=skip_defaults_from_body)
  1574.         return dataref
  1575.    
  1576.    
  1577.     def data_fromexisting(self, to_dataref, from_dataref=None, category=None,
  1578.                           **kwargs):
  1579.         """
  1580.        Duplicate existing data to a new set with a different dataref.
  1581.        
  1582.        This can be useful if you want to change little things and want to
  1583.        examine the difference between computing options easily, or if you
  1584.        want to compute an existing set onto a higher time resolution etc.
  1585.        
  1586.        Any extra kwargs are copied to any pbdep or obs where the key is
  1587.        present.
  1588.        
  1589.        All *pbdeps* and *obs* with dataref :envvar:`from_dataref` will be
  1590.        duplicated into a *pbdep* with dataref :envvar:`to_dataref`.
  1591.        
  1592.        [FUTURE]
  1593.        
  1594.        :param category: category of the data to look for. If none are given,
  1595.        all types of data will be examined. This only has a real influence on
  1596.        the default value of :envvar:`from_dataref`.
  1597.        :type category: str, one of ``lc``, ``rv``...
  1598.        :raises KeyError: if :envvar:`to_dataref` already exists
  1599.        :raises KeyError: if :envvar:`from_dataref` is not given and there is
  1600.        either no or more than one dataset present.
  1601.        :raises ValueError: if keyword arguments are set that could not be
  1602.        processed.
  1603.        """
  1604.         # we keep track of every kwarg that has been used, to make sure
  1605.         # everything has found a place
  1606.         processed_kwargs = []
  1607.        
  1608.         # if no dataref is given, just take the only reference we have. If there
  1609.         # is more than one, this is ambiguous so we raise a KeyError
  1610.         if from_dataref is None:
  1611.             existing_refs = self.get_system().get_refs(category=category)
  1612.             if len(existing_refs) != 1:
  1613.                 # build custom message depending on what is available
  1614.                 if len(existing_refs) == 0:
  1615.                     msg = "no data present"
  1616.                 else:
  1617.                     msg = ("more than one available. Please specify 'from_data"
  1618.                       "ref' to be any of {}").format(", ".join(existing_refs))
  1619.                 raise KeyError(("Cannot figure out which dataref to copy from. "
  1620.                     "No 'from_dataref' was given and there is {}.").format(msg))
  1621.            
  1622.             from_dataref = existing_refs[0]
  1623.        
  1624.         # Walk through the system looking for deps, syns or obs
  1625.         for path, item in self.get_system().walk_all(path_as_string=False):
  1626.             if item == from_dataref:
  1627.                 # now there's two possibilities: either we're at the root
  1628.                 # parameterSet, or we have a Parameter with value dataref.
  1629.                 # We're only interested in the former
  1630.                 if isinstance(item, str):
  1631.                     # where are we at?
  1632.                     the_ordered_dict = path[-3]
  1633.                     the_context = path[-2]
  1634.                    
  1635.                     # check if the new data ref exists
  1636.                     if to_dataref in the_ordered_dict[the_context]:
  1637.                         raise KeyError(("Cannot add data from existing. "
  1638.                               "There already exists a {} with to_dataref "
  1639.                               "{}").format(the_context, to_dataref))
  1640.                    
  1641.                     # get the existing PS and copy it into a new one
  1642.                     existing = the_ordered_dict[the_context][from_dataref]
  1643.                     new = existing.copy()
  1644.                     the_ordered_dict[the_context][to_dataref] = new
  1645.                    
  1646.                     # update the reference, and any other kwargs that might
  1647.                     # be given. Remember if we added kwarg, in the end we'll
  1648.                     # check if everything found a place
  1649.                     new['ref'] = to_dataref
  1650.                     for key in kwargs:
  1651.                         if key in new:
  1652.                             new[key] = kwargs[key]
  1653.                             processed_kwargs.append(key)
  1654.                    
  1655.                    
  1656.                     # Make sure to clear the synthetic
  1657.                     if the_context[-3:] == 'syn':
  1658.                         new.clear()
  1659.        
  1660.         # check if all kwargs found a place
  1661.         processed_kwargs = set(processed_kwargs)
  1662.         unprocessed_kwargs = []
  1663.         for key in kwargs:
  1664.             if not key in processed_kwargs:
  1665.                 unprocessed_kwargs.append(key)
  1666.         if unprocessed_kwargs:
  1667.             raise ValueError(("Unprocessed arguments to *_fromexisting: "
  1668.                               "{}").format(", ".join(unprocessed_kwargs)))
  1669.        
  1670.         # Initialize the mesh after adding stuff (i.e. add columns ld_new_ref...
  1671.         self.get_system().init_mesh()
  1672.         self._build_trunk()
  1673.            
  1674.                
  1675.        
  1676.    
  1677.     def lc_fromarrays(self, objref=None, dataref=None, time=None, phase=None,
  1678.                       flux=None, sigma=None, flag=None, weight=None,
  1679.                       exptime=None, samprate=None, offset=None, scale=None,
  1680.                       atm=None, ld_func=None, ld_coeffs=None, passband=None,
  1681.                       pblum=None, l3=None, alb=None, beaming=None,
  1682.                       scattering=None, method=None):
  1683.         """
  1684.        Create and attach light curve templates to compute the model.
  1685.        
  1686.        For any parameter that is not explicitly set (i.e. not left equal to
  1687.        ``None``), the defaults from each component in the system are used
  1688.        instead of the Phoebe2 defaults. For example, the :envvar:`atm`,
  1689.        :envvar:`ld_func` and :envvar:`ld_coeffs` arguments are taken from the
  1690.        component (which reflect the bolometric properties) unless explicitly
  1691.        overriden.
  1692.        
  1693.        A unique data reference (:envvar:`dataref`) is added automatically if
  1694.        none is provided by the user. A readable system of unique references is
  1695.        applied: the first light curve that is added is named ``lc01`` unless
  1696.        that reference already exists, in which case ``lc02`` is tried and so
  1697.        on. On the other hand, if a :envvar:`dataref` is given at it already
  1698.        exists, it's settings are overwritten.
  1699.        
  1700.        If no :envvar:`objref` is given, the light curve is added to the total
  1701.        system, and not to a separate component. That is probably fine in almost
  1702.        all cases, since you observe the whole system simultaneously and not the
  1703.        components separately.
  1704.        
  1705.        Note that you cannot add :envvar:`times` and :envvar:`phases` simultaneously.
  1706.        
  1707.        **Example usage**
  1708.        
  1709.        It doesn't make much sense to leave the time array empty, since then
  1710.        nothing will be computed. Thus, the minimal function call that makes
  1711.        sense is something like:
  1712.        
  1713.        >>> bundle = phoebe.Bundle()
  1714.        >>> bundle.lc_fromarrays(time=np.linspace(0, 10.33, 101))
  1715.        
  1716.        or in phase space:
  1717.        
  1718.        >>> phase = np.linspace(-0.5, 0.5, 101)
  1719.        >>> bundle.lc_fromarrays(phase=phase, passband='GENEVA.V')
  1720.        
  1721.        With many more details:
  1722.        
  1723.        >>> bundle.lc_fromarrays(phase=phase, samprate=5, exptime=20.,
  1724.        ...     passband='GENEVA.V', atm='kurucz', ld_func='claret',
  1725.        ...     ld_coeffs='kurucz')
  1726.        
  1727.        .. note:: More information
  1728.        
  1729.            - For a list of acceptable values for each parameter, see
  1730.              :ref:`lcdep <parlabel-phoebe-lcdep>` and
  1731.              :ref:`lcobs <parlabel-phoebe-lcobs>`.
  1732.            - In general, :envvar:`time`, :envvar:`flux`, :envvar:`phase`,
  1733.              :envvar:`sigma`, :envvar:`flag`, :envvar:`weight`, :envvar:`exptime` and
  1734.              :envvar:`samprate` should all be arrays of equal length (unless left to
  1735.              ``None``).
  1736.        
  1737.        :param objref: component for each column in file
  1738.        :type objref: None, str, list of str or list of bodies
  1739.        :param dataref: name for ref for all returned datasets
  1740.        :type dataref: str
  1741.        :return: dataref of added observations (perhaps it was autogenerated!)
  1742.        :rtype: str
  1743.        :raises ValueError: if :envvar:`time` and :envvar:`phase` are both given
  1744.        :raises TypeError: if a keyword is given but the value cannot be cast to the Parameter
  1745.        
  1746.        """
  1747.         # retrieve the arguments with which this function is called
  1748.         set_kwargs, posargs = utils.arguments()
  1749.        
  1750.         # filter the arguments according to not being "None" nor being "self"
  1751.         set_kwargs = {key:set_kwargs[key] for key in set_kwargs \
  1752.                   if set_kwargs[key] is not None and key != 'self'}
  1753.        
  1754.         # We can pass everything now to the main function
  1755.         return self.data_fromarrays(category='lc', **set_kwargs)
  1756.    
  1757.    
  1758.     def lc_fromfile(self, filename, objref=None, dataref=None, columns=None,
  1759.                       units=None, offset=None, scale=None, atm=None,
  1760.                       ld_func=None, ld_coeffs=None, passband=None, pblum=None,
  1761.                       l3=None, alb=None, beaming=None, scattering=None,
  1762.                       method=None):
  1763.         """
  1764.        Add a lightcurve from a file.
  1765.        
  1766.        The data contained in :envvar:`filename` will be loaded to the object
  1767.        with object reference :envvar:`objref` (if ``None``, defaults to the whole
  1768.        system), and will have data reference :envvar:`dataref`. If no
  1769.        :envvar:`dataref` is is given, a unique one is generated: the first
  1770.        light curve that is added is named 'lc01', and if that one already
  1771.        exists, 'lc02' is tried and so on.
  1772.        
  1773.        For any parameter that is not explicitly set (i.e. not left equal to
  1774.        ``None``), the defaults from each component in the system are used
  1775.        instead of the Phoebe2 defaults. For example, the :envvar:`atm`,
  1776.        :envvar:`ld_func` and :envvar:`ld_coeffs` arguments are taken from the
  1777.        component (which reflect the bolometric properties) unless explicitly
  1778.        overriden.
  1779.        
  1780.        **Example usage**
  1781.        
  1782.        A plain file can loaded via::
  1783.        
  1784.        >>> bundle.lc_fromfile('myfile.lc')
  1785.        
  1786.        Note that extra parameters can be given in the file itself, but can
  1787.        also be overriden in the function call:
  1788.        
  1789.        >>> bundle.lc_fromfile('myfile.lc', passband='JOHNSON.V')
  1790.        
  1791.        If you have a non-standard file (non-default column order or non-default
  1792.        units), you have some liberty in specifying the file-format here. You
  1793.        can specify the order of the :envvar:`columns` (a list) and the
  1794.        :envvar:`units` of each column (dict). You can skip columns by giving an
  1795.        empty string ``''``:
  1796.        
  1797.        >>> bundle.lc_fromfile('myfile.lc', columns=['flux', 'time', 'sigma'])
  1798.        >>> bundle.lc_fromfile('myfile.lc', columns=['time', 'mag', 'sigma'])
  1799.        >>> bundle.lc_fromfile('myfile.lc', columns=['sigma', 'phase', 'mag'])
  1800.        >>> bundle.lc_fromfile('myfile.lc', columns=['', 'phase', 'mag'])
  1801.        >>> bundle.lc_fromfile('myfile.lc', units=dict(time='s'))
  1802.        >>> bundle.lc_fromfile('myfile.lc', units=dict(time='s', flux='erg/s/cm2/AA'))
  1803.        >>> bundle.lc_fromfile('myfile.lc', columns=['time', 'flux'], units=dict(time='s', flux='mag'))
  1804.        
  1805.        Note that
  1806.        
  1807.        >>> bundle.lc_fromfile('myfile.lc', columns=['time', 'mag']))
  1808.        
  1809.        is actually a shortcut to
  1810.        
  1811.        >>> bundle.lc_fromfile('myfile.lc', columns=['time', 'flux'], units=dict(flux='mag'))
  1812.        
  1813.        .. note:: More information
  1814.        
  1815.            - For a list of acceptable values for each parameter, see
  1816.              :ref:`lcdep <parlabel-phoebe-lcdep>` and
  1817.              :ref:`lcobs <parlabel-phoebe-lcobs>`.
  1818.            - For more information on file formats, see
  1819.              :py:func:`phoebe.parameters.datasets.parse_lc`.
  1820.        
  1821.        
  1822.        :param objref: component for each column in file
  1823.        :type objref: None, str, list of str or list of bodies
  1824.        :param dataref: name for ref for all returned datasets
  1825.        :type dataref: str
  1826.        :return: dataref of added observations (perhaps it was autogenerated!)
  1827.        :rtype: str
  1828.        :raises TypeError: if a keyword is given but the value cannot be cast
  1829.         to the Parameter
  1830.        """
  1831.         # retrieve the arguments with which this function is called
  1832.         set_kwargs, posargs = utils.arguments()
  1833.        
  1834.         # filter the arguments according to not being "None" nor being "self"
  1835.         set_kwargs = {key:set_kwargs[key] for key in set_kwargs \
  1836.                   if set_kwargs[key] is not None and key != 'self'}
  1837.        
  1838.         # We can pass everything now to the main function
  1839.         return self.data_fromfile(category='lc', **set_kwargs)
  1840.    
  1841.    
  1842.     def lc_fromexisting(self, to_dataref, from_dataref=None, time=None, phase=None,
  1843.                       flux=None, sigma=None, flag=None, weight=None,
  1844.                       exptime=None, samprate=None, offset=None, scale=None,
  1845.                       atm=None, ld_func=None, ld_coeffs=None, passband=None,
  1846.                       pblum=None, l3=None, alb=None, beaming=None,
  1847.                       scattering=None, method=None):
  1848.         """
  1849.        Duplicate an existing light curve to a new one with a different dataref.
  1850.        
  1851.        This can be useful if you want to change little things and want to
  1852.        examine the difference between computing options easily, or if you
  1853.        want to compute an existing light curve onto a higher time resolution
  1854.        etc.
  1855.        
  1856.        Any extra kwargs are copied to any lcdep or lcobs where the key is
  1857.        present.
  1858.        
  1859.        All *lcdeps* and *lcobs* with dataref :envvar:`from_dataref` will be
  1860.        duplicated into an *lcdep* with dataref :envvar:`to_dataref`.
  1861.        
  1862.        For a list of available parameters, see :ref:`lcdep <parlabel-phoebe-lcdep>`
  1863.        and :ref:`lcobs <parlabel-phoebe-lcobs>`.
  1864.        
  1865.        :raises KeyError: if :envvar:`to_dataref` already exists
  1866.        :raises KeyError: if :envvar:`from_dataref` is not given and there is
  1867.         either no or more than one light curve present.
  1868.        :raises ValueError: if keyword arguments are set that could not be
  1869.         processed.
  1870.        """
  1871.         # retrieve the arguments with which this function is called
  1872.         set_kwargs, posargs = utils.arguments()
  1873.        
  1874.         # filter the arguments according to not being "None" nor being "self"
  1875.         set_kwargs = {key:set_kwargs[key] for key in set_kwargs \
  1876.                   if set_kwargs[key] is not None and key not in ['self', 'to_dataref']}
  1877.        
  1878.         self.data_fromexisting(to_dataref, category='lc', **set_kwargs)
  1879.        
  1880.     def rv_fromarrays(self, objref=None, dataref=None, time=None, phase=None,
  1881.                       rv=None, sigma=None, flag=None, weight=None,
  1882.                       exptime=None, samprate=None, offset=None, scale=None,
  1883.                       method=None, atm=None, ld_func=None, ld_coeffs=None,
  1884.                       passband=None, pblum=None, l3=None, alb=None, beaming=None,
  1885.                       scattering=None):
  1886.         """
  1887.        Create and attach radial velocity curve templates to compute the model.
  1888.        
  1889.        In contrast to py:func:`lc_fromarrays <phoebe.frontend.bundle.Bundle.lc_fromarrays`,
  1890.        this function will probably
  1891.        always be called with a specific :envvar:`objref`. While light curves
  1892.        typically encompass the whole system (and are thus added to the whole
  1893.        system by default), the radial velocities curves belong to a given
  1894.        component. Therefore, make sure to always supply :envvar:`objref`.
  1895.        
  1896.        An extra keyword is :envvar:`method`, which can take the values
  1897.        ``flux-weighted`` (default) or ``dynamical``. In the later case, no
  1898.        mesh is computed for the component, but the Kepler-predictions of the
  1899.        (hierarhical) orbit are computed. This is much faster, but includes no
  1900.        Rossiter-McLaughlin effect.
  1901.        
  1902.        For any parameter that is not explicitly set (i.e. not left equal to
  1903.        ``None``), the defaults from each component in the system are used
  1904.        instead of the Phoebe2 defaults. For example, the :envvar:`atm`,
  1905.        :envvar:`ld_func` and :envvar:`ld_coeffs` arguments are taken from the
  1906.        component (which reflect the bolometric properties) unless explicitly
  1907.        overriden.
  1908.        
  1909.        A unique data reference (:envvar:`dataref`) is added automatically if
  1910.        none is provided by the user. A readable system of unique references is
  1911.        applied: the first rv curve that is added is named ``rv01`` unless
  1912.        that reference already exists, in which case ``rv02`` is tried and so
  1913.        on. On the other hand, if a :envvar:`dataref` is given at it already
  1914.        exists, it's settings are overwritten.
  1915.        
  1916.        Note that you cannot add :envvar:`times` and `phases` simultaneously.
  1917.        
  1918.        **Example usage**
  1919.        
  1920.        It doesn't make much sense to leave the time array empty, since then
  1921.        nothing will be computed. Thus, the minimal function call that makes
  1922.        sense is something like:
  1923.        
  1924.        >>> bundle.rv_fromarrays('primary', time=np.linspace(0, 10.33, 101))
  1925.        
  1926.        or in phase space (phase space will probably not work for anything but
  1927.        light curves and radial velocities):
  1928.        
  1929.        >>> phase = np.linspace(-0.5, 0.5, 101)
  1930.        >>> bundle.rv_fromarrays('primary', phase=phase, passband='GENEVA.V')
  1931.        
  1932.        With many more details:
  1933.        
  1934.        >>> bundle.rv_fromarrays('primary', phase=phase, samprate=5, exptime=20.,
  1935.        ...     passband='GENEVA.V', atm='kurucz', ld_func='claret',
  1936.        ...     ld_coeffs='kurucz')
  1937.        
  1938.        For a list of acceptable values for each parameter, see
  1939.        :ref:`rvdep <parlabel-phoebe-rvdep>` and
  1940.        :ref:`rvobs <parlabel-phoebe-rvobs>`.
  1941.        
  1942.        In general, :envvar:`time`, :envvar:`flux`, :envvar:`phase`,
  1943.        :envvar:`sigma`, :envvar:`flag`, :envvar:`weight, :envvar:`exptime` and
  1944.        :envvar:`samprate` should all be arrays of equal length (unless left to
  1945.        ``None``).
  1946.        
  1947.        :param objref: component for each column in file
  1948.        :type objref: None, str, list of str or list of bodies
  1949.        :param dataref: name for ref for all returned datasets
  1950.        :type dataref: str
  1951.        :return: dataref of added observations (perhaps it was autogenerated!)
  1952.        :rtype: str
  1953.        :raises ValueError: if :envvar:`time` and :envvar:`phase` are both given
  1954.        :raises TypeError: if a keyword is given but the value cannot be cast to the Parameter
  1955.        """
  1956.         # we're a little careful when it comes to binaries:
  1957.         if hasattr(self.get_system(), 'bodies') and len(self.get_system())>1:
  1958.             if objref is None:
  1959.                 raise ValueError(("In binary or multiple systems, you are "
  1960.                                   "required to specify the component to which "
  1961.                                   "you want to add rv data (via objref)"))
  1962.             if objref == self.get_system().get_label():
  1963.                 raise ValueError("Cannot add RV to the system, only to the components. Please specify 'objref'.")
  1964.         # But other system configuration can have a smart default
  1965.         elif objref is None:
  1966.             objref = self.get_system().get_label()
  1967.        
  1968.         # retrieve the arguments with which this function is called
  1969.         set_kwargs, posargs = utils.arguments()
  1970.        
  1971.         # filter the arguments according to not being "None" nor being "self"
  1972.         set_kwargs = {key:set_kwargs[key] for key in set_kwargs \
  1973.                   if set_kwargs[key] is not None and key not in ['self','objref']}
  1974.        
  1975.         # We can pass everything now to the main function
  1976.         return self.data_fromarrays(category='rv', objref=objref, **set_kwargs)
  1977.    
  1978.    
  1979.     def rv_fromfile(self, filename, objref=None, dataref=None, columns=None,
  1980.                       units=None, offset=None, scale=None, atm=None,
  1981.                       ld_func=None, ld_coeffs=None, passband=None, pblum=None,
  1982.                       l3=None, alb=None, beaming=None, scattering=None,
  1983.                       method=None):
  1984.         """
  1985.        Add a radial velocity curve from a file.
  1986.        
  1987.        The data contained in :envvar:`filename` will be loaded to the object
  1988.        with object reference :envvar:`objref`, and will have data reference
  1989.        :envvar:`dataref`. If no :envvar:`dataref` is is given, a unique one is
  1990.        generated: the first radial velocity curve that is added is named 'rv01',
  1991.        and if that one already exists, 'rv02' is tried and so on.
  1992.        
  1993.        An extra keyword is :envvar:`method`, which can take the values
  1994.        ``flux-weighted`` (default) or ``dynamical``. In the later case, no
  1995.        mesh is computed for the component, but the Kepler-predictions of the
  1996.        (hierarhical) orbit are computed. This is much faster, but includes no
  1997.        Rossiter-McLaughlin effect.
  1998.        
  1999.        For any parameter that is not explicitly set (i.e. not left equal to
  2000.        ``None``), the defaults from each component in the system are used
  2001.        instead of the Phoebe2 defaults. For example, the :envvar:`atm`,
  2002.        :envvar:`ld_func` and :envvar:`ld_coeffs` arguments are taken from the
  2003.        component (which reflect the bolometric properties) unless explicitly
  2004.        overriden.
  2005.        
  2006.        **Example usage**
  2007.        
  2008.        A plain file can loaded via::
  2009.        
  2010.        >>> bundle.rv_fromfile('myfile.rv', 'primary')
  2011.        
  2012.        Note that extra parameters can be given in the file itself, but can
  2013.        also be overriden in the function call:
  2014.        
  2015.        >>> bundle.rv_fromfile('myfile.rv', 'primary', atm='kurucz')
  2016.        
  2017.        If your radial velocity measurements of several components are in one
  2018.        file (say time, rv of primary, rv of secondary, sigma of primary rv,
  2019.        sigma of secondary rv), you could easily do:
  2020.        
  2021.        >>> bundle.rv_fromfile('myfile.rv', 'primary', columns=['time', 'rv', '', 'sigma'])
  2022.        >>> bundle.rv_fromfile('myfile.rv', 'secondary', columns=['time', '', 'rv', '', 'sigma'])
  2023.        
  2024.        .. note:: More information
  2025.        
  2026.            - For a list of acceptable values for each parameter, see
  2027.              :ref:`rvdep <parlabel-phoebe-rvdep>` and
  2028.              :ref:`rvobs <parlabel-phoebe-rvobs>`.
  2029.            - For more information on file formats, see
  2030.              :py:func:`phoebe.parameters.datasets.parse_rv`.
  2031.        
  2032.        
  2033.        
  2034.        :param objref: component for each column in file
  2035.        :type objref: None, str, list of str or list of bodies
  2036.        :param dataref: name for ref for all returned datasets
  2037.        :type dataref: str
  2038.        :return: dataref of added observations (perhaps it was autogenerated!)
  2039.        :rtype: str
  2040.        :raises TypeError: if a keyword is given but the value cannot be cast to the Parameter
  2041.        """# we're a little careful when it comes to binaries:
  2042.         if hasattr(self.get_system(), 'bodies') and len(self.get_system())>1:
  2043.             if objref is None:
  2044.                 raise ValueError(("In binary or multiple systems, you are "
  2045.                                   "required to specify the component to which "
  2046.                                   "you want to add rv data (via objref)"))
  2047.             if objref == self.get_system().get_label():
  2048.                 raise ValueError("Cannot add RV to the system, only to the components. Please specify 'objref'.")
  2049.            
  2050.         # retrieve the arguments with which this function is called
  2051.         set_kwargs, posargs = utils.arguments()
  2052.        
  2053.         # filter the arguments according to not being "None" nor being "self"
  2054.         set_kwargs = {key:set_kwargs[key] for key in set_kwargs \
  2055.                   if set_kwargs[key] is not None and key != 'self'}
  2056.        
  2057.         # We can pass everything now to the main function
  2058.         return self.data_fromfile(category='rv', **set_kwargs)
  2059.    
  2060.    
  2061.     def rv_fromexisting(to_dataref, from_dataref=None, time=None, phase=None,
  2062.                       rv=None, sigma=None, flag=None, weight=None,
  2063.                       exptime=None, samprate=None, offset=None, scale=None,
  2064.                       atm=None, ld_func=None, ld_coeffs=None, passband=None,
  2065.                       pblum=None, l3=None, alb=None, beaming=None,
  2066.                       scattering=None):
  2067.         """
  2068.        Duplicate an existing radial velocity curve to a new one with a different dataref.
  2069.        
  2070.        This can be useful if you want to change little things and want to
  2071.        examine the difference between computing options easily, or if you
  2072.        want to compute an existing radial velocity curve onto a higher time
  2073.        resolution etc.
  2074.        
  2075.        Any extra kwargs are copied to any rvdep or rvobs where the key is
  2076.        present.
  2077.        
  2078.        All *rvdeps* and *rvobs* with dataref :envvar:`from_dataref` will be
  2079.        duplicated into an *rvdep* with dataref :envvar:`to_dataref`.
  2080.        
  2081.        :raises KeyError: if :envvar:`to_dataref` already exists
  2082.        :raises KeyError: if :envvar:`from_dataref` is not given and there is
  2083.        either no or more than one radial velocity curve present.
  2084.        :raises ValueError: if keyword arguments are set that could not be
  2085.        processed.
  2086.        """
  2087.         # retrieve the arguments with which this function is called
  2088.         set_kwargs, posargs = utils.arguments()
  2089.        
  2090.         # filter the arguments according to not being "None" nor being "self"
  2091.         set_kwargs = {key:set_kwargs[key] for key in set_kwargs \
  2092.                   if set_kwargs[key] is not None and key != 'self'}
  2093.        
  2094.         self.data_fromexisting(to_dataref,  category='rv', **set_kwargs)
  2095.    
  2096.    
  2097.     def sed_fromarrays(self, objref=None, dataref=None, time=None, phase=None,
  2098.                        passband=None, flux=None, sigma=None, unit=None,
  2099.                        scale=None, offset=None, auto_scale=False,
  2100.                        auto_offset=False, **kwargs):
  2101.         """
  2102.        Create and attach SED templates to compute the model.
  2103.        
  2104.        A spectral energy distribution (SED) is nothing more than a collection
  2105.        of absolutely calibrated lcs in different passbands. The given arrays
  2106.        of times, flux etc.. therefore need to be all arrays of the same lenght,
  2107.        similarly as for :py:func:`Bundle.lc_fromarrays`. One extra array needs
  2108.        to be given, i.e. a list of passbands via :envvar:`passband`.
  2109.        Optionally, a list of units can be added (i.e. the supplied fluxes can
  2110.        have different units, e.g. mag, erg/s/cm2/AA, Jy...).
  2111.        
  2112.        Extra keyword arguments are all passed to :py:func:`Bundle.lc_fromarrays`.
  2113.        That means that each lc attached will have the same set of atmosphere
  2114.        tables, limb darkening coefficients etc. If they need to be different
  2115.        for particular lcs, these need to be changed manually afterwards.
  2116.        
  2117.        Each added light curve will be named ``<dataref>_<passband>``, so they
  2118.        can be accessed using that twig.
  2119.        
  2120.        Note that many SED measurements are not recorded in time. They still
  2121.        need to be given in Phoebe2 anyway, e.g. all zeros.
  2122.        
  2123.        **Example usage**
  2124.        
  2125.        Initiate a Bundle:
  2126.        
  2127.        >>> vega = phoebe.Bundle('Vega')
  2128.        
  2129.        Create the required/optional arrays
  2130.        
  2131.        >>> passbands = ['JOHNSON.V', 'JOHNSON.U', '2MASS.J', '2MASS.H', '2MASS.KS']
  2132.        >>> flux = [0.033, 0.026, -0.177, -0.029, 0.129]
  2133.        >>> sigma = [0.012, 0.014, 0.206, 0.146, 0.186]
  2134.        >>> unit = ['mag', 'mag', 'mag', 'mag', 'mag']
  2135.        >>> time = [0.0, 0.0, 0.0, 0.0, 0.0]
  2136.        
  2137.        And add them to the Bundle.
  2138.        
  2139.        >>> x.sed_fromarrays(dataref='mysed', passband=passbands, time=time, flux=flux,
  2140.                 sigma=sigma, unit=unit)
  2141.        
  2142.        [FUTURE]
  2143.        """
  2144.         if passband is None:
  2145.             raise ValueError("Passband is required")
  2146.        
  2147.         # We need a reference to link the pb and ds together.
  2148.         if dataref is None:
  2149.             # If the reference is None, suggest one. We'll add it as "lc01"
  2150.             # if no "sed01" exist, otherwise "sed02" etc (unless "sed02"
  2151.             # already exists)
  2152.             existing_refs = self.get_system().get_refs(category='lc')
  2153.             id_number = len(existing_refs)+1
  2154.             dataref = 'sed{:02d}'.format(id_number)
  2155.             while dataref in existing_refs:
  2156.                 id_number += 1
  2157.                 dataref = 'sed{:02d}'.format(len(existing_refs)+1)            
  2158.        
  2159.         # group data per passband:
  2160.         passbands = np.asarray(passband)
  2161.         unique_passbands = np.unique(passbands)
  2162.    
  2163.         # Convert fluxes to the right units
  2164.         if unit is not None:
  2165.             if sigma is None:
  2166.                 flux = np.array([conversions.convert(iunit, 'W/m3', iflux,\
  2167.                           passband=ipassband) for iunit, iflux, ipassband \
  2168.                               in zip(unit, flux, passbands)])
  2169.             else:
  2170.                 flux, sigma = np.array([conversions.convert(iunit, 'W/m3',
  2171.                           iflux, isigma, passband=ipassband) for iunit, iflux,\
  2172.                               ipassband, isigma in zip(unit, flux, \
  2173.                                   passbands, sigma)]).T
  2174.    
  2175.         # Group per passband
  2176.         split_up = ['time', 'phase', 'flux', 'sigma']
  2177.         added_datarefs = []
  2178.         for unique_passband in unique_passbands:
  2179.             this_group = (passbands == unique_passband)
  2180.             this_kwargs = kwargs.copy()
  2181.             this_dataref = dataref + '_' + unique_passband
  2182.            
  2183.             # Split given arrays per passband
  2184.             for variable in split_up:
  2185.                 if locals()[variable] is not None:
  2186.                     this_kwargs[variable] = np.array(locals()[variable])[this_group]
  2187.            
  2188.             # And add these as a light curve
  2189.             added = self.lc_fromarrays(objref=objref, dataref=this_dataref,
  2190.                                        passband=unique_passband, **this_kwargs)
  2191.            
  2192.             added_datarefs.append(added)
  2193.            
  2194.         # Group the observations, but first collect them all
  2195.         this_object = self.get_object(objref)
  2196.         obs = [this_object.get_obs(category='lc', ref=iref) \
  2197.                      for iref in added_datarefs]
  2198.        
  2199.         tools.group(obs, dataref, scale=auto_scale, offset=auto_offset)
  2200.        
  2201.         return dataref
  2202.    
  2203.     def sed_fromfile(self, filename, objref=None, dataref=None, columns=None,
  2204.                       units=None, offset=None, scale=None, adjust_scale=None,
  2205.                       adjust_offset=None):
  2206.         """
  2207.        Add SED templates from a file.
  2208.        
  2209.        [FUTURE]
  2210.        """
  2211.        
  2212.         # We need a reference to link the pb and ds together.
  2213.         if dataref is None:
  2214.             # If the reference is None, suggest one. We'll add it as "lc01"
  2215.             # if no "sed01" exist, otherwise "sed02" etc (unless "sed02"
  2216.             # already exists)
  2217.             existing_refs = self.get_system().get_refs(category='lc')
  2218.             id_number = len(existing_refs)+1
  2219.             dataref = 'sed{:02d}'.format(id_number)
  2220.             while dataref in existing_refs:
  2221.                 id_number += 1
  2222.                 dataref = 'sed{:02d}'.format(len(existing_refs)+1)            
  2223.            
  2224.         # retrieve the arguments with which this function is called
  2225.         set_kwargs, posargs = utils.arguments()
  2226.        
  2227.         # filter the arguments according to not being "None" nor being "self"
  2228.         set_kwargs = {key:set_kwargs[key] for key in set_kwargs \
  2229.                   if set_kwargs[key] is not None and key != 'self'}
  2230.         # We can pass everything now to the main function
  2231.         return self.data_fromfile(category='sed', **set_kwargs)
  2232.    
  2233.    
  2234.     def sp_fromarrays(self, objref=None, dataref=None, time=None, phase=None,
  2235.                       wavelength=None, flux=None, continuum=None, sigma=None,
  2236.                       flag=None, R_input=None, offset=None, scale=None,
  2237.                       vgamma_offset=None, profile=None, R=None, vmicro=None,
  2238.                       depth=None, atm=None, ld_func=None, ld_coeffs=None,
  2239.                       passband=None, pblum=None, l3=None, alb=None,
  2240.                       beaming=None):
  2241.         """
  2242.        Create and attach spectral templates to compute the model.
  2243.        
  2244.        For any parameter that is not explicitly set (i.e. not left equal to
  2245.        ``None``), the defaults from each component in the system are used
  2246.        instead of the Phoebe2 defaults. For example, the :envvar:`atm`,
  2247.        :envvar:`ld_func` and :envvar:`ld_coeffs` arguments are taken from the
  2248.        component (which reflect the bolometric properties) unless explicitly
  2249.        overriden.
  2250.        
  2251.        A unique data reference (:envvar:`dataref`) is added automatically if
  2252.        none is provided by the user. A readable system of unique references is
  2253.        applied: the first spectrum that is added is named ``sp01`` unless
  2254.        that reference already exists, in which case ``sp02`` is tried and so
  2255.        on. On the other hand, if a :envvar:`dataref` is given at it already
  2256.        exists, it's settings are overwritten.
  2257.        
  2258.        If no :envvar:`objref` is given, the spectrum is added to the total
  2259.        system, and not to a separate component. That is not want you want when
  2260.        you're modeling disentangled spectra.
  2261.        
  2262.        Note that you cannot add :envvar:`times` and :envvar:`phases` simultaneously.
  2263.        
  2264.        **Example usage**
  2265.        
  2266.        It doesn't make much sense to leave the time or wavelength array empty,
  2267.        since then nothing will be computed. Thus, the minimal function call
  2268.        that makes sense is something like:
  2269.        
  2270.        >>> bundle = phoebe.Bundle()
  2271.        >>> bundle.sp_fromarrays(time=np.linspace(0, 10.33, 101),
  2272.        ...                      wavelength=np.linspace(399, 401, 500))
  2273.        
  2274.        or in phase space:
  2275.        
  2276.        >>> wavelength = np.linspace(399, 401, 500)
  2277.        >>> phase = np.linspace(-0.5, 0.5, 101)
  2278.        >>> bundle.sp_fromarrays(wavelength=wavelenth, phase=phase, passband='GENEVA.V')
  2279.        
  2280.        With many more details:
  2281.        
  2282.        >>> bundle.sp_fromarrays(wavelength=wavelenth, phase=phase, samprate=5,
  2283.        ...     exptime=20., passband='GENEVA.V', atm='kurucz',
  2284.        ...     ld_func='claret',  ld_coeffs='kurucz')
  2285.        
  2286.        For a list of acceptable values for each parameter, see
  2287.        :ref:`spdep <parlabel-phoebe-spdep>` and
  2288.        :ref:`spobs <parlabel-phoebe-spobs>`.
  2289.        
  2290.        In general, :envvar:`time`, :envvar:`flux`, :envvar:`phase`,
  2291.        :envvar:`sigma`, :envvar:`flag`, :envvar:`weight, :envvar:`exptime` and
  2292.        :envvar:`samprate` should all be arrays of equal length (unless left to
  2293.        ``None``).
  2294.        
  2295.        [FUTURE]
  2296.        
  2297.        :param objref: component for each column in file
  2298.        :type objref: None, str, list of str or list of bodies
  2299.        :param dataref: name for ref for all returned datasets
  2300.        :type dataref: str
  2301.        :return: dataref of added observations (perhaps it was autogenerated!)
  2302.        :rtype: str
  2303.        :raises ValueError: if :envvar:`time` and :envvar:`phase` are both given
  2304.        :raises TypeError: if a keyword is given but the value cannot be cast to the Parameter
  2305.        """
  2306.         # retrieve the arguments with which this function is called
  2307.         set_kwargs, posargs = utils.arguments()
  2308.        
  2309.         # filter the arguments according to not being "None" nor being "self"
  2310.         set_kwargs = {key:set_kwargs[key] for key in set_kwargs \
  2311.                   if set_kwargs[key] is not None and key != 'self'}
  2312.        
  2313.         # We can pass everything now to the main function
  2314.         return self.data_fromarrays(category='sp', **set_kwargs)
  2315.    
  2316.    
  2317.     def sp_fromfile(self, filename, objref=None, time=None,
  2318.                       clambda=None, wrange=None, vgamma_offset=None,
  2319.                       dataref=None, snapshot=False, columns=None,
  2320.                       units=None, offset=None, scale=None, atm=None,
  2321.                       R_input=None, vmacro=None, vmicro=None, depth=None,
  2322.                       profile=None, alphaT=None,
  2323.                       ld_func=None, ld_coeffs=None, passband=None, pblum=None,
  2324.                       l3=None, alb=None, beaming=None, scattering=None):
  2325.         """
  2326.        Add spectral templates from a file.
  2327.        
  2328.        [FUTURE]
  2329.        
  2330.        :param objref: component for each column in file
  2331.        :type objref: None, str, list of str or list of bodies
  2332.        :param dataref: name for ref for all returned datasets
  2333.        :type dataref: str
  2334.        :param snapshot: filetype of the spectra: is it a timeseries or a snapshot?
  2335.        :type snapshot: bool
  2336.        :return: dataref of added observations (perhaps it was autogenerated!)
  2337.        :rtype: str
  2338.        :raises TypeError: if a keyword is given but the value cannot be cast to the Parameter
  2339.        """
  2340.         # retrieve the arguments with which this function is called
  2341.         set_kwargs, posargs = utils.arguments()
  2342.        
  2343.         # filter the arguments according to not being "None" nor being "self"
  2344.         set_kwargs = {key:set_kwargs[key] for key in set_kwargs \
  2345.                   if set_kwargs[key] is not None and key not in ['self','snapshot']}
  2346.        
  2347.         # Determine whether it is a spectral timeseries of snapshot
  2348.         category = 'sp:ss' if snapshot else 'sp'
  2349.        
  2350.         # We can pass everything now to the main function
  2351.         return self.data_fromfile(category=category, **set_kwargs)
  2352.        
  2353.     def pl_fromarrays(self, objref=None, dataref=None, time=None, phase=None,
  2354.                       wavelength=None, flux=None, continuum=None, sigma=None,
  2355.                       V=None, sigma_V=None, Q=None, sigma_Q=None, U=None,
  2356.                       sigma_U=None,
  2357.                       flag=None, R_input=None, offset=None, scale=None,
  2358.                       vgamma_offset=None, profile=None, R=None, vmicro=None,
  2359.                       depth=None, atm=None, ld_func=None, ld_coeffs=None,
  2360.                       passband=None, pblum=None, l3=None, alb=None,
  2361.                       beaming=None):
  2362.         """
  2363.        Create and attach spectrapolarimetry templates to compute the model.
  2364.        
  2365.        See :py:func:`sp_fromarrays` for more information.
  2366.        """
  2367.         # retrieve the arguments with which this function is called
  2368.         set_kwargs, posargs = utils.arguments()
  2369.        
  2370.         # filter the arguments according to not being "None" nor being "self"
  2371.         set_kwargs = {key:set_kwargs[key] for key in set_kwargs \
  2372.                   if set_kwargs[key] is not None and key != 'self'}
  2373.        
  2374.         # We can pass everything now to the main function
  2375.         return self.data_fromarrays(category='pl', **set_kwargs)
  2376.    
  2377.    
  2378.    
  2379.     def if_fromfile(self, filename, objref=None, dataref=None,
  2380.                     include_closure_phase=False, include_triple_amplitude=False,
  2381.                     include_eff_wave=True,
  2382.                     atm=None, ld_func=None, ld_coeffs=None, passband=None,
  2383.                     pblum=None, l3=None, bandwidth_smearing=None,
  2384.                     bandwidth_subdiv=None,  alb=None,
  2385.                     beaming=None, scattering=None):
  2386.         """
  2387.        Add interferometry data from an OIFITS file.
  2388.        
  2389.        The data contained in :envvar:`filename` will be loaded to the object
  2390.        with object reference :envvar:`objref` (if ``None``, defaults to the
  2391.        whole system), and will have data reference :envvar:`dataref`. If no
  2392.        :envvar:`dataref` is is given, a unique one is generated: the first
  2393.        interferometric dataset that is added is named 'if01', and if that one
  2394.        already exists, 'if02' is tried and so on.
  2395.        
  2396.        By default, only the visibility is loaded from the OIFITS file. if you
  2397.        want to include closure phases and/or triple amplitudes, you need to
  2398.        set :envvar:`include_closure_phase` and/or :envvar:`include_triple_amplitude`
  2399.        explicitly.
  2400.        
  2401.        The effective wavelengths from the OIFITS file are loaded by default,
  2402.        but if you want to use the effective wavelength from the passband to
  2403.        convert baselines to spatial frequencies, you need to exclude them via
  2404.        :envvar:`include_eff_wave=False`. Setting the :envvar:`bandwidth_smearing`
  2405.        to `simple` or `detailed` will make the parameter obsolete since the
  2406.        spatial frequencies will be computed in a different way.
  2407.        
  2408.        For any other parameter that is not explicitly set (i.e. not left equal to
  2409.        ``None``), the defaults from each component in the system are used
  2410.        instead of the Phoebe2 defaults. For example, the :envvar:`atm`,
  2411.        :envvar:`ld_func` and :envvar:`ld_coeffs` arguments are taken from the
  2412.        component (which reflect the bolometric properties) unless explicitly
  2413.        overriden.
  2414.        
  2415.        **Example usage**
  2416.        
  2417.        An OIFITS file can loaded via::
  2418.        
  2419.        >>> bundle.if_fromfile('myfile.fits')
  2420.        
  2421.        Extra parameters can overriden in the function call:
  2422.        
  2423.        >>> bundle.if_fromfile('myfile.fits', atm='kurucz')
  2424.        
  2425.        .. note:: More information
  2426.        
  2427.            - For a list of acceptable values for each parameter, see
  2428.              :ref:`lcdep <parlabel-phoebe-ifdep>` and
  2429.              :ref:`lcobs <parlabel-phoebe-ifobs>`.
  2430.            - For more information on file formats, see
  2431.              :py:func:`phoebe.parameters.datasets.parse_oifits`.
  2432.        
  2433.        
  2434.        :param objref: component to add data to
  2435.        :type objref: None, str, list of str or list of bodies
  2436.        :param dataref: name for ref for all returned datasets
  2437.        :type dataref: str
  2438.        :return: dataref of added observations (perhaps it was autogenerated!)
  2439.        :rtype: str
  2440.        :raises TypeError: if a keyword is given but the value cannot be cast
  2441.         to the Parameter
  2442.        """
  2443.         # retrieve the arguments with which this function is called
  2444.         set_kwargs, posargs = utils.arguments()
  2445.        
  2446.         # filter the arguments according to not being "None" nor being "self"
  2447.         set_kwargs = {key:set_kwargs[key] for key in set_kwargs \
  2448.                   if set_kwargs[key] is not None and key != 'self'}
  2449.        
  2450.         # We can pass everything now to the main function
  2451.         return self.data_fromfile(category='if:oifits', **set_kwargs)
  2452.    
  2453.     def if_fromarrays(self, objref=None, dataref=None, time=None, phase=None,
  2454.                       ucoord=None, vcoord=None, vis2=None, sigma_vis2=None,
  2455.                       vphase=None, sigma_vphase=None,
  2456.                       eff_wave=None, flag=None, weight=None,
  2457.                       exptime=None, samprate=None, offset=None, scale=None,
  2458.                       atm=None, ld_func=None, ld_coeffs=None, passband=None,
  2459.                       pblum=None, l3=None, bandwidth_smearing=None,
  2460.                       bandwidth_subdiv=None,alb=None, beaming=None,
  2461.                       scattering=None):
  2462.         """
  2463.        Create and attach light curve templates to compute the model.
  2464.        
  2465.        For any parameter that is not explicitly set (i.e. not left equal to
  2466.        ``None``), the defaults from each component in the system are used
  2467.        instead of the Phoebe2 defaults. For example, the :envvar:`atm`,
  2468.        :envvar:`ld_func` and :envvar:`ld_coeffs` arguments are taken from the
  2469.        component (which reflect the bolometric properties) unless explicitly
  2470.        overriden.
  2471.        
  2472.        A unique data reference (:envvar:`dataref`) is added automatically if
  2473.        none is provided by the user. A readable system of unique references is
  2474.        applied: the first interferometric dataset that is added is named
  2475.        ``if01`` unless that reference already exists, in which case ``if02``
  2476.        is tried and so on. On the other hand, if a :envvar:`dataref` is given
  2477.        at it already exists, it's settings are overwritten.
  2478.        
  2479.        If no :envvar:`objref` is given, the interferometry is added to the
  2480.        total system, and not to a separate component. That is probably fine in
  2481.        almost all cases, since you observe the whole system simultaneously and
  2482.        not the components separately.
  2483.        
  2484.        Note that you cannot add :envvar:`times` and :envvar:`phases` simultaneously.
  2485.        
  2486.        **Example usage**
  2487.        
  2488.        It doesn't make much sense to leave the time array empty, since then
  2489.        nothing will be computed. You are also required to give U and V
  2490.        coordinates.
  2491.        Thus, the minimal function call that makes
  2492.        sense is something like:
  2493.        
  2494.        >>> bundle = phoebe.Bundle()
  2495.        >>> bundle.if_fromarrays(time=np.linspace(0, 10.33, 101))
  2496.        
  2497.        or in phase space:
  2498.        
  2499.        >>> phase = np.linspace(-0.5, 0.5, 101)
  2500.        >>> bundle.if_fromarrays(phase=phase, passband='GENEVA.V')
  2501.                
  2502.        .. note:: More information
  2503.        
  2504.            - For a list of acceptable values for each parameter, see
  2505.              :ref:`ifdep <parlabel-phoebe-ifdep>` and
  2506.              :ref:`ifobs <parlabel-phoebe-ifobs>`.
  2507.        
  2508.        :param objref: component for each column in file
  2509.        :type objref: None, str, list of str or list of bodies
  2510.        :param dataref: name for ref for all returned datasets
  2511.        :type dataref: str
  2512.        :return: dataref of added observations (perhaps it was autogenerated!)
  2513.        :rtype: str
  2514.        :raises ValueError: if :envvar:`time` and :envvar:`phase` are both given
  2515.        :raises TypeError: if a keyword is given but the value cannot be cast to the Parameter
  2516.        
  2517.        """
  2518.         # retrieve the arguments with which this function is called
  2519.         set_kwargs, posargs = utils.arguments()
  2520.        
  2521.         # filter the arguments according to not being "None" nor being "self"
  2522.         set_kwargs = {key:set_kwargs[key] for key in set_kwargs \
  2523.                   if set_kwargs[key] is not None and key != 'self'}
  2524.        
  2525.         # We can pass everything now to the main function
  2526.         return self.data_fromarrays(category='if', **set_kwargs)
  2527.    
  2528.    
  2529.     def if_fromexisting(self, to_dataref, from_dataref=None,
  2530.                     remove_closure_phase=False, remove_triple_amplitude=False,
  2531.                     remove_eff_wave=False, time=None, phase=None, ucoord=None,
  2532.                     vcoord=None, vis2=None, sigma_vis2=None, eff_wave=None,
  2533.                     atm=None, ld_func=None, ld_coeffs=None, passband=None,
  2534.                     pblum=None, l3=None, bandwidth_smearing=None,
  2535.                     bandwidth_subdiv=None,  alb=None,
  2536.                     beaming=None, scattering=None):
  2537.         """
  2538.        Duplicate an existing interferometry set to a new one with a different dataref.
  2539.                
  2540.        See :py:func:`Bundle.lc_fromexisting` for more info.
  2541.        
  2542.        Additionally, you can easiyl remove closure phases, triple amplitudes
  2543.        and/or effective wavelength, if you ever so wish.
  2544.        
  2545.        :param objref: component to add data to
  2546.        :type objref: None, str, list of str or list of bodies
  2547.        :param dataref: name for ref for all returned datasets
  2548.        :type dataref: str
  2549.        :return: dataref of added observations (perhaps it was autogenerated!)
  2550.        :rtype: str
  2551.        :raises TypeError: if a keyword is given but the value cannot be cast
  2552.         to the Parameter
  2553.        """
  2554.         # retrieve the arguments with which this function is called
  2555.         set_kwargs, posargs = utils.arguments()
  2556.        
  2557.         # filter the arguments according to not being "None" nor being "self"
  2558.         ignore = ['self', 'to_dataref', 'remove_closure_phase',
  2559.                   'remove_triple_amplitude', 'remove_eff_wave']
  2560.         set_kwargs = {key:set_kwargs[key] for key in set_kwargs \
  2561.                   if set_kwargs[key] is not None and key not in ignore}
  2562.        
  2563.         # We can pass everything now to the main function
  2564.         out = self.data_fromexisting(to_dataref, category='if', **set_kwargs)
  2565.        
  2566.         # Remove closure phases, triple amplitudes or effective wavelengths
  2567.         # if necessary
  2568.         obs = self.get_obs(to_dataref)
  2569.         if remove_closure_phase:
  2570.             obs.remove('closure_phase')
  2571.         if remove_triple_amplitude:
  2572.             obs.remove('triple_ampl')
  2573.         if remove_eff_wave:
  2574.             obs.remove('eff_wave')
  2575.        
  2576.         return out
  2577.    
  2578.    
  2579.     def add_parameter(self, twig, replaces=None, value=None):
  2580.         """
  2581.        Add a new parameter to the set of parameters as a combination of others.
  2582.        
  2583.        The value of the new parameter can either be derived from the existing
  2584.        ones, or replaces one of the existing ones. It is thus not possible to
  2585.        simply add an extra parameter; i.e. the number of total free parameters
  2586.        must stay the same.
  2587.        
  2588.        Explanation of the two scenarios:
  2589.        
  2590.        **1. Adding a new parameter without replacing an existing one**
  2591.        
  2592.        For example, you want to add ``asini`` as a parameter but want to keep
  2593.        ``sma`` and ``incl`` as free parameters in the fit:
  2594.        
  2595.        >>> mybundle = phoebe.Bundle()
  2596.        >>> mybundle.add_parameter('asini@orbit')
  2597.        >>> mybundle['asini']
  2598.        9.848077530129958
  2599.        
  2600.        Then, you can still change ``sma`` and ``incl``:
  2601.        
  2602.        >>> mybundle['sma'] = 20.0
  2603.        >>> mybundle['incl'] = 85.0, 'deg'
  2604.        
  2605.        and then ``asini`` is updated automatically:
  2606.        
  2607.        >>> x['asini']
  2608.        19.923893961843316
  2609.        
  2610.        However you are not allowed to change the parameter of ``asini``
  2611.        manually, because the code does not know if it needs to update ``incl``
  2612.        or ``sma`` to keep consistency:
  2613.        
  2614.        >>> x['asini'] = 10.0
  2615.        ValueError: Cannot change value of parameter 'asini', it is derived from other parameters
  2616.  
  2617.        **2. Adding a new parameter to replace an existing one**
  2618.        
  2619.        >>> mybundle = phoebe.Bundle()
  2620.        >>> mybundle.add_parameter('asini@orbit', replaces='sma')
  2621.        >>> mybundle['asini']
  2622.        9.848077530129958
  2623.        
  2624.        Then, you can change ``asini`` and ``incl``:
  2625.        
  2626.        >>> mybundle['asini'] = 10.0
  2627.        >>> mybundle['incl'] = 85.0, 'deg'
  2628.        
  2629.        and ``sma`` is updated automatically:
  2630.        
  2631.        >>> x['sma']
  2632.        10.038198375429241
  2633.        
  2634.        However you are not allowed to change the parameter of ``sma``
  2635.        manually, because the code does not know if it needs to update ``asini``
  2636.        or ``incl`` to keep consistency:
  2637.        
  2638.        >>> x['sma'] = 10.0
  2639.        ValueError: Cannot change value of parameter 'sma', it is derived from other parameters
  2640.  
  2641.        **Non-exhaustive list of possible additions**
  2642.        
  2643.        - ``asini@orbit``: projected system semi-major axis (:py:func:`more info <phoebe.parameters.tools.add_asini>`)
  2644.        - ``ecosw@orbit``: eccentricity times cosine of argument of periastron,
  2645.          automatically adds also ``esinw`` (:py:func:`more info <phoebe.parameters.tools.add_esinw_ecosw>`)
  2646.        - ``theta_eff@orbit``: Effective misalignment parameter (:py:func:`more info <phoebe.parameters.tools.add_theta_eff>`)
  2647.        [FUTURE]
  2648.        """
  2649.         # Does the parameter already exist?
  2650.         param = self._get_by_search(twig, kind='Parameter', ignore_errors=True,
  2651.                                     return_trunk_item=True)
  2652.        
  2653.         # If the parameter does not exist, we need to create a new parameter
  2654.         # and add it in the right place in the tree.
  2655.         twig_split = twig.split('@')
  2656.        
  2657.         # Figure out what the parameter name is
  2658.         qualifier = twig_split[0]
  2659.        
  2660.         # Special cases:
  2661.         if qualifier in ['ecosw', 'esinw']:
  2662.             qualifier = 'esinw_ecosw'
  2663.        
  2664.         # If the parameter does not exist yet, there's some work to do: we need
  2665.         # to figure out where to add it, and we need to create it
  2666.        
  2667.         # There are two ways we can add it: the easy way is if the parameter
  2668.         # can be added via the "tools" module
  2669.         if hasattr(tools, 'add_{}'.format(qualifier)):
  2670.            
  2671.             # Retrieve the parameterSet to add it to
  2672.             twig_rest = '@'.join(twig_split[1:])
  2673.             item = self._get_by_search(twig_rest, kind='ParameterSet',
  2674.                                        return_trunk_item=True)
  2675.            
  2676.             # If the 'replaces' is a twig, make sure it's a valid one
  2677.             if replaces is not None:
  2678.                 replaces_param = self.get_parameter(replaces)
  2679.                
  2680.                 if replaces_param.get_context() != item['item'].get_context():
  2681.                     raise ValueError("The parameter {} cannot replace {} because it is not in the same ParameterSet".format(twig, replaces))
  2682.                    
  2683.                 # Set the replaced parameter to be hidden or replaced
  2684.                 #replaces_param.set_hidden(True)
  2685.                 replaces_param.set_replaced_by(None)
  2686.                 replaces_qualifier = replaces_param.get_qualifier()
  2687.             else:
  2688.                 replaces_qualifier = None
  2689.                    
  2690.             # get the function that is responsible for adding this parameter
  2691.             add_function = getattr(tools, 'add_{}'.format(qualifier))
  2692.             argspecs = inspect.getargspec(add_function)[0]
  2693.             # build the args for this function (only add the value and/or derive
  2694.             # argument if that is allowed by the function)
  2695.             add_args = [item['item']]
  2696.             if qualifier in argspecs:
  2697.                 add_args += [value]
  2698.             elif value is not None:
  2699.                 raise ValueError("The value of parameter '{}' can not be set explicitly, it can only be derived".format(qualifier))
  2700.             if 'derive' in argspecs:
  2701.                 add_args += [replaces_qualifier]
  2702.             elif replaces_qualifier is not None:
  2703.                 raise ValueError("Parameter '{}' can only be derived itself, it cannot be used to derive '{}'".format(qualifier, replaces_qualifier))
  2704.                    
  2705.             params = add_function(*add_args)
  2706.            
  2707.             if replaces is None:
  2708.                 for param in params:
  2709.                     param.set_replaced_by(True)
  2710.             elif replaces is not False:
  2711.                 for param in params:
  2712.                     param.set_replaced_by(None)
  2713.                 replaces_param.set_replaced_by(params)
  2714.            
  2715.             logger.info("Added {} to ParameterSet with context {}".format(qualifier, item['item'].get_context()))
  2716.            
  2717.             self._build_trunk()
  2718.            
  2719.            
  2720.             return None
  2721.        
  2722.         elif param is None:
  2723.            
  2724.             # If this parameter does not replaces any other, it is derived itself
  2725.             if replaces is None:
  2726.                 replaces = qualifier    
  2727.            
  2728.             # Get all the info on this parameter
  2729.             info = definitions.rels['binary'][qualifier].copy()
  2730.            
  2731.             # Figure out which level (i.e. ParameterSet) to add it to
  2732.             in_level_as = info.pop('in_level_as')
  2733.            
  2734.             # If we already have a level here, it's easy-peasy
  2735.             if in_level_as[:2] != '__':
  2736.                 twig_rest = '@'.join([in_level_as] + twig_split[1:])
  2737.                 item = self._get_by_search(twig_rest, kind='Parameter',
  2738.                                          return_trunk_item=True)
  2739.             elif in_level_as == '__system__':
  2740.                 system = self.get_system()
  2741.                 system.attach_ps()
  2742.            
  2743.             # And add it
  2744.             pset = item['path'][-2]
  2745.             pset.add(info)
  2746.            
  2747.             param = pset.get_parameter(qualifier)
  2748.             #param.set_replaces(replaces)
  2749.            
  2750.         # In any case we need to set the 'replaces' attribute and the value    
  2751.         param.set_replaces(replaces)
  2752.         param.set_value(value)
  2753.        
  2754.         # add the preprocessing thing
  2755.         system = self.get_system()
  2756.         system.add_preprocess('binary_custom_variables')
  2757.         self._build_trunk()  
  2758.    
  2759.    
  2760.     def get_datarefs(self, objref=None, category=None, per_category=False):
  2761.         """
  2762.        Return all the datarefs, or only those of a certain category.
  2763.        
  2764.        [FUTURE]
  2765.        """
  2766.         return self.get_object(objref).get_refs(category=category,
  2767.                                                 per_category=per_category)
  2768.    
  2769.     def get_lc_datarefs(self, objref=None):
  2770.         """
  2771.        Return all datarefs of category lc.
  2772.        
  2773.        [FUTURE]
  2774.        """
  2775.         return self.get_datarefs(objref=objref, category='lc')
  2776.    
  2777.     def get_rv_datarefs(self, objref=None):
  2778.         """
  2779.        Return all datarefs of category rv.
  2780.        
  2781.        [FUTURE]
  2782.        """
  2783.         return self.get_datarefs(objref=objref, category='rv')
  2784.    
  2785.     def get_syn(self, twig=None):
  2786.         """
  2787.        Get the synthetic parameterset for an observation
  2788.        
  2789.        @param twig: the twig/twiglet to use when searching
  2790.        @type twig: str
  2791.        @return: the observations DataSet
  2792.        @rtype: DataSet
  2793.        """
  2794.         return self._get_by_search(twig, context='*syn', class_name='*DataSet')
  2795.  
  2796.     def get_dep(self, twig=None):
  2797.         """
  2798.        Get observations dependables
  2799.        
  2800.        @param twig: the twig/twiglet to use when searching
  2801.        @type twig: str
  2802.        @return: the observations dependables ParameterSet
  2803.        @rtype: ParameterSet
  2804.        """
  2805.         return self._get_by_search(twig, context='*dep', class_name='ParameterSet')
  2806.        
  2807.     def get_obs(self, twig=None):
  2808.         """
  2809.        Get observations
  2810.        
  2811.        @param twig: the twig/twiglet to use when searching
  2812.        @type twig: str
  2813.        @return: the observations DataSet
  2814.        @rtype: DataSet
  2815.        """
  2816.         return self._get_by_search(twig, context='*obs', class_name='*DataSet')
  2817.        
  2818.     def enable_data(self, dataref=None, category=None, enabled=True):
  2819.         """
  2820.        Enable a dataset so that it will be considered during run_compute
  2821.        
  2822.        If the category is given and :envvar:`dataref=None`, the dataset to
  2823.        disable must be unique, i.e. there can be only one. Otherwise, a
  2824.        ValueError will be raised.
  2825.        
  2826.        @param dataref: reference of the dataset
  2827.        @type dataref: str
  2828.        @param category: the category of the dataset ('lc', 'rv', etc)
  2829.        @type category: str
  2830.        @param enabled: whether to enable (True) or disable (False)
  2831.        @type enabled: bool
  2832.        :raises ValueError: when the dataref is ambiguous, or is None
  2833.        :raises ValueEror: if dataref is None and no category is given
  2834.        :raises KeyError: when dataref is not available
  2835.        """
  2836.         try:
  2837.             dataref = self._process_dataref(dataref, category)
  2838.         except:
  2839.             dataref = None
  2840.        
  2841.        
  2842.         system = self.get_system()
  2843.         try:
  2844.             iterate_all_my_bodies = system.walk_bodies()
  2845.         except AttributeError:
  2846.             iterate_all_my_bodies = [system]
  2847.        
  2848.         for body in iterate_all_my_bodies:
  2849.             this_objref = body.get_label()
  2850.             #~ if objref is None or this_objref == objref:
  2851.             if True:
  2852.                 for obstype in body.params['obs']:
  2853.                     if dataref is None:
  2854.                         for idataref in body.params['obs'][obstype]:
  2855.                             body.params['obs'][obstype][idataref].set_enabled(enabled)
  2856.                             logger.info("{} {} '{}'".format('Enabled' if enabled else 'Disabled', obstype, idataref))
  2857.                     elif dataref in body.params['obs'][obstype]:
  2858.                         body.params['obs'][obstype][dataref].set_enabled(enabled)
  2859.                         logger.info("{} {} '{}'".format('Enabled' if enabled else 'Disabled', obstype, dataref))
  2860.  
  2861.        
  2862.     def disable_data(self, dataref=None, category=None):
  2863.         """
  2864.        Disable a dataset so that it will not be considered during run_compute
  2865.        
  2866.        See :py:func:`Bundle.enable_data` for more info
  2867.        
  2868.        @param dataref: reference of the dataset
  2869.        @type dataref: str
  2870.        @param category: the category of the dataset ('lc', 'rv', etc)
  2871.        @type category: str
  2872.        :raises ValueError: when the dataref is ambiguous, or is None and no category is given.
  2873.        """
  2874.         self.enable_data(dataref, category, enabled=False)
  2875.        
  2876.     def enable_lc(self, dataref=None):
  2877.         """
  2878.        Enable an LC dataset so that it will be considered during run_compute
  2879.        
  2880.        If no dataref is given and there is only one light curve added, there
  2881.        is no ambiguity and that one will be enabled.
  2882.        
  2883.        @param dataref: reference of the dataset
  2884.        @type dataref: str
  2885.        :raises ValueError: when the dataref is ambiguous
  2886.        :raises KeyError: when dataref does not exist
  2887.        """
  2888.         self.enable_data(dataref, 'lc', True)
  2889.        
  2890.     def disable_lc(self, dataref=None):
  2891.         """
  2892.        Disable an LC dataset so that it will not be considered during run_compute
  2893.        
  2894.        See :py:func:`Bundle.enable_lc` for more info.
  2895.        
  2896.        @param dataref: reference of the dataset
  2897.        @type dataref: str
  2898.        """
  2899.         self.enable_data(dataref, 'lc', False)
  2900.        
  2901.     def enable_rv(self, dataref=None):
  2902.         """
  2903.        Enable an RV dataset so that it will be considered during run_compute
  2904.        
  2905.        See :py:func:`Bundle.enable_lc` for more info
  2906.        
  2907.        @param dataref: reference of the dataset
  2908.        @type dataref: str
  2909.        """
  2910.         self.enable_data(dataref, 'rv', True)
  2911.        
  2912.     def disable_rv(self, dataref=None):
  2913.         """
  2914.        Disable an RV dataset so that it will not be considered during run_compute
  2915.        
  2916.        See :py:func:`Bundle.enable_lc` for more info
  2917.        
  2918.        @param dataref: reference of the dataset
  2919.        @type dataref: str
  2920.        """
  2921.         self.enable_data(dataref, 'rv', False)
  2922.    
  2923.     def enable_sp(self, dataref=None):
  2924.         """
  2925.        Enable an SP dataset so that it will be considered during run_compute
  2926.        
  2927.        See :py:func:`Bundle.enable_lc` for more info
  2928.        
  2929.        @param dataref: reference of the dataset
  2930.        @type dataref: str
  2931.        """
  2932.         self.enable_data(dataref, 'sp', True)
  2933.        
  2934.     def disable_sp(self, dataref=None):
  2935.         """
  2936.        Disable an SP dataset so that it will not be considered during run_compute
  2937.        
  2938.        See :py:func:`Bundle.enable_lc` for more info
  2939.        
  2940.        @param dataref: reference of the dataset
  2941.        @type dataref: str
  2942.        """
  2943.         self.enable_data(dataref, 'sp', False)
  2944.        
  2945.     def enable_sed(self, dataref=None):
  2946.         """
  2947.        Enable LC datasets belonging to an sed so that it will be considered during run_compute
  2948.        
  2949.        See :py:func:`Bundle.enable_lc` for more info
  2950.        
  2951.        @param dataref: reference of the dataset
  2952.        @type dataref: str
  2953.        """
  2954.         system = self.get_system()
  2955.         all_lc_refs = system.get_refs(category='lc')
  2956.         all_lc_refs = [ref for ref in all_lc_refs if dataref in ref]
  2957.         for dataref in all_lc_refs:
  2958.             self.enable_data(dataref, 'lc', True)
  2959.        
  2960.     def disable_sed(self, dataref=None):
  2961.         """
  2962.        Enable LC datasets belonging to an sed so that it will not be considered during run_compute
  2963.        
  2964.        See :py:func:`Bundle.enable_lc` for more info
  2965.        
  2966.        @param dataref: reference of the dataset
  2967.        @type dataref: str
  2968.        """
  2969.         system = self.get_system()
  2970.         all_lc_refs = system.get_refs(category='lc')
  2971.         all_lc_refs = [ref for ref in all_lc_refs if dataref in ref]
  2972.         for dataref in all_lc_refs:
  2973.             self.enable_data(dataref, 'lc', False)
  2974.        
  2975.     def reload_obs(self, twig=None):
  2976.         """
  2977.        [FUTURE]
  2978.  
  2979.        reload a dataset from its source file
  2980.        
  2981.        @param twig: the twig/twiglet to use when searching
  2982.        @type twig: str
  2983.        """
  2984.         self.get_obs(twig).load()
  2985.        
  2986.         #~ dss = self.get_obs(dataref=dataref,all=True).values()
  2987.         #~ for ds in dss:
  2988.             #~ ds.load()
  2989.    
  2990.     def _process_dataref(self, dataref, category=None):
  2991.         """
  2992.        [FUTURE]
  2993.        
  2994.        this function handles checking if a dataref passed to a function
  2995.        (eg. remove_data, enable_data, etc) is unique to a single category
  2996.        
  2997.        this function also handles determining a default if dataref is None
  2998.        """
  2999.         if dataref is None:
  3000.             # then see if there is only one entry with this category
  3001.             # and if so, default to it
  3002.             if category is None:
  3003.                 # Next line doesn't seem to work, so I short-cutted a return value
  3004.                 category = '*'
  3005.                
  3006.             dss = self._get_by_search(dataref,
  3007.                     context = ['{}obs'.format(category),'{}syn'.format(category),'{}dep'.format(category)],
  3008.                     kind = 'ParameterSet', all = True, ignore_errors = True)
  3009.            
  3010.             datarefs = []
  3011.             for ds in dss:
  3012.                 if ds['ref'] not in datarefs:
  3013.                     datarefs.append(ds['ref'])
  3014.             if len(datarefs)==1:
  3015.                 # this is our default!
  3016.                 return datarefs[0]
  3017.             elif len(datarefs)==0:
  3018.                 raise ValueError("no datasets found")
  3019.                 # no default
  3020.                 return None
  3021.             else:
  3022.                 raise ValueError("more than one dataset: must provide dataref")
  3023.                 # no default
  3024.                 return None
  3025.         else:
  3026.             # confirm its (always) the correct category
  3027.             # *technically* if it isn't always correct, we should just remove the correct ones
  3028.             #dss = self._get_by_search(dataref, context=['*obs','*syn','*dep'], kind='ParameterSet', all=True, ignore_errors=True)
  3029.             dss = self._get_by_search(dataref, context='*dep', kind='ParameterSet', all=True, ignore_errors=True)
  3030.             if not len(dss):
  3031.                 raise KeyError("No dataref found matching {}".format(dataref))
  3032.             for ds in dss:
  3033.                 if category is None:
  3034.                     # then instead we're just making sure that all are of the same type
  3035.                     category = ds.context[:-3]
  3036.                 if ds.context[:-3] != category:
  3037.                     raise ValueError("{} not always of category {}".format(dataref, category))
  3038.                     # forbid this dataref
  3039.                     return None
  3040.            
  3041.             # we've survived, this dataref is allowed
  3042.             return dataref
  3043.  
  3044.  
  3045.     def remove_data(self, dataref=None, category=None):
  3046.         """
  3047.        remove a dataset (and all its obs, syn, dep) from the system
  3048.        
  3049.        @param dataref: reference of the dataset
  3050.        @type dataref: str
  3051.        @param category: the category of the dataset ('lc', 'rv', etc)
  3052.        @type category: str
  3053.        """
  3054.        
  3055.         dataref = self._process_dataref(dataref, category)
  3056.        
  3057.         if dataref is not None:
  3058.             # disable any plotoptions that use this dataset
  3059.             #~ for axes in self.get_axes(all=True).values():
  3060.                 #~ for pl in axes.get_plot().values():
  3061.                     #~ if pl.get_value('dataref')==dataref:
  3062.                         #~ pl.set_value('active',False)
  3063.            
  3064.             # remove all obs attached to any object in the system
  3065.             for obj in self.get_system().walk_bodies():
  3066.                 obj.remove_obs(refs=[dataref])
  3067.                 if hasattr(obj, 'remove_pbdeps'): #TODO otherwise: warning 'BinaryRocheStar' has no attribute 'remove_pbdeps'
  3068.                     obj.remove_pbdeps(refs=[dataref])
  3069.            
  3070.             self._build_trunk()
  3071.             return
  3072.    
  3073.     def remove_lc(self, dataref=None):
  3074.         """
  3075.        remove an LC dataset (and all its obs, syn, dep) from the system
  3076.        
  3077.        @param dataref: reference of the dataset
  3078.        @type dataref: str
  3079.        """
  3080.         self.remove_data(dataref, 'lc')
  3081.  
  3082.  
  3083.     def remove_rv(self, dataref=None):
  3084.         """
  3085.        remove an RV dataset (and all its obs, syn, dep) from the system
  3086.        
  3087.        @param dataref: reference of the dataset
  3088.        @type dataref: str
  3089.        """
  3090.         self.remove_data(dataref, 'rv')
  3091.        
  3092.        
  3093.        
  3094.        
  3095.     #}
  3096.     #{ Compute
  3097.    
  3098.     @rebuild_trunk
  3099.     @run_on_server
  3100.     def run_compute(self, label=None, objref=None, animate=False, **kwargs):
  3101.     #~ def run_compute(self,label=None,anim=False,add_version=None,server=None,**kwargs):
  3102.         """
  3103.        Perform calculations to mirror any enabled attached observations.
  3104.        
  3105.        Main arguments: :envvar:`label`, :envvar:`objref`, :envvar:`anim`.
  3106.        
  3107.        Extra keyword arguments are passed to the
  3108.        :ref:`compute <parlabel-phoebe-compute>` ParameterSet.
  3109.        
  3110.        **Example usage**
  3111.        
  3112.        The minimal setup is::
  3113.        
  3114.            >>> mybundle = phoebe.Bundle()
  3115.            >>> dataref = mybundle.lc_fromarrays(phase=[0, 0.5, 1.0])
  3116.            >>> mybundle.run_compute()
  3117.        
  3118.        After which you can plot the results via::
  3119.        
  3120.            >>> mybundle.plot_syn(dataref)
  3121.        
  3122.        **Keyword 'label'**
  3123.        
  3124.        Different compute options can be added via
  3125.        :py:func:`Bundle.add_compute() <phoebe.frontend.common.Container.add_compute>`,
  3126.        where each of these ParameterSets have a
  3127.        :envvar:`label`. If :envvar:`label` is given and that compute option is
  3128.        present, those options will be used. If no :envvar:`label` is given, a
  3129.        default set of compute options is created on the fly. The used set of
  3130.        options is returned but also stored for later reference. You can access
  3131.        it via the ``default`` label in the bundle::
  3132.            
  3133.            >>> mybundle.run_compute()
  3134.        
  3135.        and at any point you can query:
  3136.        
  3137.            >>> options = mybundle['default@compute']
  3138.            
  3139.        If you want to store new options before hand for later usage you can
  3140.        issue:
  3141.        
  3142.            >>> mybundle.add_compute(label='no_heating', heating=False)
  3143.            >>> options = mybundle.run_compute(label='no_heating')
  3144.          
  3145.        **Keyword 'objref'**
  3146.        
  3147.        If :envvar:`objref` is given, the computations are only performed on
  3148.        that object. This is only advised for introspection. Beware that the
  3149.        synthetics will no longer mirror the observations of the entire system,
  3150.        but only those of the specified object.
  3151.        
  3152.        .. warning::
  3153.            
  3154.            1. Even if you only compute the light curve of the secondary in a
  3155.               binary system, the system geometry is still determined by the entire
  3156.               system. Thus, eclipses will occur if the secondary gets eclipsed!
  3157.               If you don't want this behaviour, either turn of eclipse computations
  3158.               entirely (via :envvar:`eclipse_alg='none'`) or create a new
  3159.               binary system with the other component removed.
  3160.            
  3161.            2. This function only computes synthetics of objects that have
  3162.               observations. If observations are added to the system level and
  3163.               :envvar:`run_compute` is run on a component where there are no
  3164.               observations attached to, a :envvar:`ValueError` will be raised.
  3165.        
  3166.        **Keyword 'animate'**
  3167.        
  3168.        It is possible to animate the computations for visual inspection of the
  3169.        system geometry. The different valid options for setting
  3170.        :envvar:`animate` are given in
  3171.        :py:func:`observatory.compute <phoebe.backend.observatory.compute>`,
  3172.        but here are two straightforward examples::
  3173.        
  3174.            >>> mybundle.run_compute(animate=True)
  3175.            >>> mybundle.run_compute(animate='lc')
  3176.        
  3177.        .. warning::
  3178.        
  3179.            1. Animations are only supported on computers/backends that support the
  3180.               animation capabilities of matplotlib (likely excluding Macs).
  3181.            
  3182.            2. Animations will not work in interactive mode in ipython (i.e. when
  3183.               started as ``ipython --pylab``.
  3184.        
  3185.        **Extra keyword arguments**
  3186.        
  3187.        Any extra keyword arguments are passed on to the ``compute``
  3188.        ParameterSet.
  3189.        
  3190.        This frontend function wraps the backend function
  3191.        :py:func:`observatory.compute <phoebe.backend.observatory.compute>`.
  3192.        
  3193.        :param label: name of one of the compute ParameterSets stored in bundle
  3194.        :type label: str
  3195.        :param objref: name of the top-level object used when observing
  3196.        :type objref: str
  3197.        :param anim: whether to animate the computations
  3198.        :type anim: False or str
  3199.        :return: used compute options
  3200.        :rtype: ParameterSet
  3201.        :raises ValueError: if there are no observations (or only empty ones) attached to the object.
  3202.        :raises KeyError: if a label of a compute options is set that was not added before
  3203.        """
  3204.         server = None # server support deferred
  3205.         system = self.get_system()
  3206.         system.fix_mesh()
  3207.        
  3208.         obj = self.get_object(objref) if objref is not None else system
  3209.         #~ if add_version is None:
  3210.             #~ add_version = self.settings['add_version_on_compute']
  3211.                
  3212.         self.purge_signals(self.attached_signals_system)
  3213.            
  3214.         # clear all previous models and create new model
  3215.         system.reset_and_clear()
  3216.         #system.clear_synthetic()
  3217.        
  3218.         # get compute options, handling 'default' if label==None
  3219.         options = self.get_compute(label, create_default=True).copy()
  3220.         mpi = kwargs.pop('mpi', None)
  3221.        
  3222.         # get server options
  3223.         if server is not None:
  3224.             server = self.get_server(server)
  3225.             mpi = server.mpi_ps
  3226.            
  3227.         # now temporarily override with any values passed through kwargs    
  3228.         for k,v in kwargs.items():
  3229.             #if k in options.keys(): # otherwise nonexisting kwargs can be given
  3230.             try:
  3231.                 options.set_value(k,v)
  3232.             except AttributeError:
  3233.                 raise ValueError("run_compute does not accept keyword '{}'".format(k))
  3234.        
  3235.         # Q <pieterdegroote>: should we first set system.uptodate to False and
  3236.         # then try/except the computations? Though we should keep track of
  3237.         # why things don't work out.. how to deal with out-of-grid interpolation
  3238.         # etc...
  3239.         if options['time'] == 'auto':
  3240.             #~ observatory.compute(self.system,mpi=self.mpi if mpi else None,**options)
  3241.             if mpi is not None and animate:
  3242.                 raise ValueError("You cannot animate and use MPI simultaneously")
  3243.             elif mpi is not None:
  3244.                 obj.compute(mpi=mpi, **options)
  3245.             else:
  3246.                 obj.compute(animate=animate, **options)
  3247.            
  3248.         #else:
  3249.             #im_extra_func_kwargs = {key: value for key,value in self.get_meshview().items()}
  3250.             #observatory.observe(obj,options['time'],lc=True,rv=True,sp=True,pl=True,
  3251.                 #extra_func=[observatory.ef_binary_image] if anim!=False else [],
  3252.                 #extra_func_kwargs=[self.get_meshview()] if anim!=False else [],
  3253.                 #mpi=mpi,**options
  3254.                 #)
  3255.        
  3256.         #if anim != False:
  3257.             #for ext in ['.gif','.avi']:
  3258.                 #plotlib.make_movie('ef_binary_image*.png',output='{}{}'.format(anim,ext),cleanup=ext=='.avi')
  3259.            
  3260.         system.uptodate = label
  3261.        
  3262.         #~ if add_version is not False:
  3263.             #~ self.add_version(name=None if add_version==True else add_version)
  3264.  
  3265.         self.attach_system_signals()
  3266.        
  3267.         return options
  3268.        
  3269.     #}
  3270.            
  3271.     #{ Fitting
  3272.     @rebuild_trunk
  3273.     @run_on_server
  3274.     def run_fitting(self, fittinglabel='lmfit', computelabel=None,
  3275.                     add_feedback=True, accept_feedback=True, server=None,
  3276.                     mpi=None, **kwargs):
  3277.         """
  3278.        Run fitting for a given fitting ParameterSet and store the feedback
  3279.        
  3280.        **Prerequisites**
  3281.        
  3282.        First of all, you need to have *observations* added to your system and
  3283.        have at least one of them *enabled*.
  3284.        
  3285.        Before you can run any fitting, you need to define *priors* on the
  3286.        parameters that you want to include in the probability calculations, and
  3287.        set those parameters that you want to include in the normal fitting
  3288.        process to be *adjustable*. A limited number of parameters can be
  3289.        estimated using a direct (linear) fit. You can mark these by setting
  3290.        them to be adjustable, but not define a prior. Thus, there are
  3291.        **3 types of parameters**:
  3292.        
  3293.        - Parameters you want to vary by virtue of the fitting algorithm. You
  3294.          need to define a prior and set them to be adjustable, e.g.::
  3295.          
  3296.          >>> mybundle.set_prior('incl', distribution='uniform', lower=80, upper=100)
  3297.          >>> mybundle.set_adjust('incl')
  3298.          
  3299.        - Parameters you want to estimated using a direct fitting approach. Set
  3300.          them to be adjustable, but do not define a prior::
  3301.          
  3302.          >>> mybundle.set_adjust('scale@lc01@lcobs')
  3303.        
  3304.        - Parameters you want to include in the probability calculations, but
  3305.          not fit directly. Only define a prior, but do not mark them for
  3306.          adjustment For example suppose you have prior information on the mass
  3307.          of the primary component in a binary system::
  3308.          
  3309.          >>> mybundle.add_parameter('mass1@orbit')
  3310.          >>> mybundle.set_prior('mass1@orbit', distribution='normal', mu=1.2, sigma=0.1)
  3311.      
  3312.        .. warning::
  3313.      
  3314.            The fitting algorithms are very strict on priors and extreme limits
  3315.            on parameters. Before a fitting algorithm is run, a :py:func:`check <phoebe.frontend.bundle.Bundle.check>` is performed to check if all
  3316.            values are within the prior limits (attribute :envvar:`prior` of
  3317.            a Parameter) and the extreme limits (attributes :envvar:`llim` and
  3318.            :envvar:`ulim` of the Parameters). Phoebe2 will notify you if any
  3319.            of the checks did not pass. You can adjust any of intervals through
  3320.            :py:func:`Parameter.set_prior <phoebe.parameters.parameters.Parameter.set_prior>`
  3321.            or :py:func:`Parameter.set_limits <phoebe.parameters.parameters.Parameter.set_limits>`.
  3322.            
  3323.      
  3324.        **Setting up fitting and compute options**
  3325.      
  3326.        First you need to decide the fitting *context*, i.e. which fitting
  3327.        scheme or algorithm you want to use. Every fitting algorithm has
  3328.        different options to set, e.g. the number of iterations in an MCMC chain,
  3329.        or details on the algorithm that needs to be used. Because every fitting
  3330.        algorithm needs to iterate the computations of the system (and evaluate
  3331.        it to choose a new set of parameters), it also needs to know the compute
  3332.        options (e.g. take reflection effects into account etc.).
  3333.        Finally you need to supply a *label* to the fitting options, for easy
  3334.        future referencing::
  3335.        
  3336.            >>> mybundle.add_fitting(context='fitting:emcee', computelabel='preview',
  3337.                                     iters=100, walkers=10, label='my_mcmc')
  3338.        
  3339.        You can add more than one fitting option, as long as you don't duplicate
  3340.        the labels::
  3341.        
  3342.            >>> mybundle.add_fitting(context='fitting:lmfit', computelabel='preview',
  3343.                                     method='nelder', label='simplex_method')
  3344.            >>> mybundle.add_fitting(context='fitting:lmfit', computelabel='preview',
  3345.                                     method='leastsq', label='levenberg-marquardt')
  3346.                                
  3347.        
  3348.        You can easily print out all the options via::
  3349.        
  3350.        >>> print(mybundle['my_mcmc@fitting'])
  3351.        
  3352.        **Running the fitter**
  3353.        
  3354.        You can run the fitter simply by issueing
  3355.        
  3356.        >>> feedback = mybundle.run_fitting(fittinglabel='my_mcmc')
  3357.        
  3358.        When run like this, the results from the fitting procedure will
  3359.        automatically be added to system and the best model will be set as the
  3360.        current model. You can change that behaviour via the :envvar:`add_feedback`
  3361.        and :envvar:`accept_feedback` arguments when calling this function.
  3362.        
  3363.        Some fitting algorithms accept an additional :envvar:`mpi` parameterSet.
  3364.        You need to define one, set the options and supply it in the fitting
  3365.        command::
  3366.        
  3367.        >>> mpi = phoebe.ParameterSet('mpi', np=4)
  3368.        >>> feedback = mybundle.run_fitting(fittinglabel='my_mcmc', mpi=mpi)
  3369.        
  3370.        The fitter returns a :py:class:`Feedback <phoebe.parameters.feedback.Feedback>`
  3371.        object, that contains a summary of the fitting results. You can simply
  3372.        print or access the feedback via the Bundle::
  3373.        
  3374.        >>> print(feedback)
  3375.        >>> print(mybundle['my_mcmc@feedback'])
  3376.        
  3377.        **More details on emcee**
  3378.        
  3379.        Probably the most general, but also most slowest fitting method is the
  3380.        Monte Carlo Markov Chain method. Under the hood, Phoebe2 uses the
  3381.        *emcee* Python package to this end. Several options from the *fitting:emcee*
  3382.        context are noteworthy here:
  3383.            
  3384.        - :envvar:`iters`: number of iterations to run. You can set this to an
  3385.          incredibly high number; you can interrupt the chain or assess the
  3386.          current state at any point because the MCMC chain is incrementally
  3387.          saved to a file. It is recommended to pickle your Bundle right before
  3388.          running the fitting algorithm. If you do, you can in a separate script
  3389.          or Python terminal monitor the chain like::
  3390.          
  3391.          >>> mybundle = phoebe.load('mypickle.pck')
  3392.          >>> mybundle.feedback_fromfile('my_mcmc.mcmc_chain.dat')
  3393.          >>> mybundle['my_mcmc@feedback'].plot_logp()
  3394.          
  3395.          You can also at any point restart a previous (perhaps
  3396.          interrupted) chain (see :envvar:`incremental`)
  3397.        - :envvar:`walkers`: number of different MCMC chains to run simultaneously.
  3398.          The emcee package requires at least 2 times the number of free parameters,
  3399.          but recommends much more (as many as feasible).
  3400.        - :envvar:`init_from`: the walkers have to start from some point. Either
  3401.          the starting points are drawn randomly from the priors (:envvar:`init_from='prior'`),
  3402.          from the posteriors (:envvar:`init_from='posterior'`), or from the
  3403.          last state of the previous run (:envvar:`init_from='previous_run'`).
  3404.          When you draw randomly from priors or posteriors, there is some
  3405.          checking performed if all parameters are valid, e.g. if you set the
  3406.          morphology to be detached, it checks whether all walkers start from
  3407.          such a scenario regardless of the actual priors on the potentials.
  3408.        - :envvar:`incremental`: add the results to the previous computation or
  3409.          not. You can continue the previous chain *and* resample from the
  3410.          posteriors or priors if you wish (via :envvar:`init_from`). Suppose you
  3411.          ran a chain like::
  3412.          
  3413.          >>> mybundle.add_fitting(context='fitting:emcee', init_from='prior', label='my_mcmc', computelabel='preview')
  3414.          >>> feedback = mybundle.run_fitting(fittinglabel='my_mcmc')
  3415.          
  3416.          Then, you could just continue these computations via::
  3417.          
  3418.          >>> mybundle['incremental@my_mcmc@fitting'] = True
  3419.          >>> mybundle['init_from@my_mcmc@fitting'] = 'previous_run'
  3420.          >>> feedback = mybundle.run_fitting(fittinglabel='my_mcmc')
  3421.          
  3422.          Alternatively, you can resample multivariate normals from the previous
  3423.          posteriors to continue the chain, e.g. after clipping walkers with
  3424.          low probability and/or after a burn-in period::
  3425.          
  3426.          >>> mybundle['my_mcmc@feedback'].modify_chain(lnproblim=-40, burnin=10)
  3427.          >>> mybundle.accept_feedback('my_mcmc')
  3428.          >>> mybundle['incremental@my_mcmc@fitting'] = True
  3429.          >>> mybundle['init_from@my_mcmc@fitting'] = 'posteriors'
  3430.          >>> feedback = mybundle.run_fitting(fittinglabel='my_mcmc')
  3431.      
  3432.          Quality control and convergence monitoring can be done via::
  3433.        
  3434.          >>> mybundle['my_mcmc@feedback'].plot_logp()
  3435.          >>> mybundle['my_mcmc@feedback'].plot_summary()
  3436.          
  3437.        
  3438.        [FUTURE]
  3439.        
  3440.        @param computelabel: name of compute ParameterSet
  3441.        @type computelabel: str
  3442.        @param fittinglabel: name of fitting ParameterSet
  3443.        @type fittinglabel: str
  3444.        @param add_feedback: flag to store the feedback (retrieve with get_feedback)
  3445.        @type add_feedback: bool
  3446.        @param accept_feedback: whether to automatically accept the feedback into the system
  3447.        @type accept_feedback: bool
  3448.        @param server: name of server to run on, or False to force locally (will override usersettings)
  3449.        @type server: string
  3450.        """
  3451.         if add_feedback is None:
  3452.             add_feedback = self.settings['add_feedback_on_fitting']
  3453.    
  3454.         if server is not None:
  3455.             server = self.get_server(server)
  3456.             mpi = server.mpi_ps
  3457.         else:
  3458.             mpi = mpi
  3459.        
  3460.         # get fitting params
  3461.         fittingoptions = self.get_fitting(fittinglabel).copy()
  3462.        
  3463.         # get compute params
  3464.         if computelabel is None:
  3465.             computelabel = fittingoptions['computelabel']
  3466.        
  3467.         # Make sure that the fittingoptions refer to the correct computelabel
  3468.         computeoptions = self.get_compute(computelabel).copy()
  3469.         fittingoptions['computelabel'] = computelabel
  3470.            
  3471.         # now temporarily override with any values passed through kwargs    
  3472.         for k,v in kwargs.items():
  3473.             if k in fittingoptions.keys():
  3474.                 fittingoptions.set_value(k,v)
  3475.             elif k in computeoptions.keys():
  3476.                 computeoptions.set_value(k,v)        
  3477.        
  3478.         # Remember the initial values of the adjustable parameters, we'll reset
  3479.         # them later:
  3480.         init_values = [par.get_value() for par in self.get_system().get_adjustable_parameters()]
  3481.        
  3482.         # here, we should disable those obs that have no flux/rv/etc.., i.e.
  3483.         # the ones that were added just for exploration purposes. We should
  3484.         # keep track of those and then re-enstate them to their original
  3485.         # value afterwards (the user could also have disabled a dataset)
  3486.         # <some code>
  3487.         logger.warning("Fit options:\n{:s}".format(fittingoptions))
  3488.         logger.warning("Compute options:\n{:s}".format(computeoptions))
  3489.        
  3490.         # Run the fitting for real
  3491.         feedback = fitting.run(self.get_system(), params=computeoptions,
  3492.                                fitparams=fittingoptions, mpi=mpi)
  3493.        
  3494.         # Reset the parameters to their initial values
  3495.         for par, val in zip(self.get_system().get_adjustable_parameters(), init_values):
  3496.             par.set_value(val)
  3497.        
  3498.        
  3499.         if add_feedback:
  3500.             # Create a Feedback class and add it to the feedback section with
  3501.             # the same label as the fittingoptions
  3502.             subcontext = fittingoptions.get_context().split(':')[1]
  3503.             class_name = 'Feedback' + subcontext.title()
  3504.             feedback = getattr(mod_feedback, class_name)(*feedback, init=self,
  3505.                                  fitting=fittingoptions, compute=computeoptions)
  3506.            
  3507.             # Make sure not to duplicate entries
  3508.             existing_fb = [fb.get_label() for fb in self.sections['feedback']]
  3509.             if feedback.get_label() in existing_fb:
  3510.                 self.sections['feedback'][existing_fb.index(feedback.get_label())] = feedback
  3511.             else:
  3512.                 self._add_to_section('feedback', feedback)
  3513.             logger.info(("You can access the feedback from the fitting '{}' ) "
  3514.                          "with the twig '{}@feedback'".format(fittinglabel, fittinglabel)))
  3515.        
  3516.         # Then re-instate the status of the obs without flux/rv/etc..
  3517.         # <some code>
  3518.        
  3519.         # Accept the feedback: set/reset the variables to their fitted values
  3520.         # or their initial values, and in any case recompute the system such
  3521.         # that the synthetics are up-to-date with the parameters
  3522.         self.accept_feedback(fittingoptions['label']+'@feedback',
  3523.                              recompute=True, revert=(not accept_feedback))
  3524.         return feedback
  3525.    
  3526.     def feedback_fromfile(self, feedback_file, fittinglabel=None,
  3527.                           accept_feedback=True, ongoing=False):
  3528.         """
  3529.        Add fitting feedback from a file.
  3530.        
  3531.        [FUTURE]
  3532.        """
  3533.         if fittinglabel is None:
  3534.             fittinglabel = os.path.basename(feedback_file).split('.mcmc_chain.dat')[0]
  3535.            
  3536.         fittingoptions = self.get_fitting(fittinglabel).copy()
  3537.         computeoptions = self.get_compute(fittingoptions['computelabel']).copy()
  3538.        
  3539.         # Remember the initial values of the adjustable parameters, we'll reset
  3540.         # them later:
  3541.         init_values = [par.get_value() for par in self.get_system().get_adjustable_parameters()]
  3542.        
  3543.         # Create a Feedback class and add it to the feedback section with
  3544.         # the same label as the fittingoptions
  3545.         subcontext = fittingoptions.get_context().split(':')[1]
  3546.         class_name = 'Feedback' + subcontext.title()
  3547.         feedback = getattr(mod_feedback, class_name)(feedback_file, init=self,
  3548.                              fitting=fittingoptions, compute=computeoptions,
  3549.                              ongoing=ongoing)
  3550.        
  3551.         # Make sure not to duplicate entries
  3552.         existing_fb = [fb.get_label() for fb in self.sections['feedback']]
  3553.         if feedback.get_label() in existing_fb:
  3554.             self.sections['feedback'][existing_fb.index(feedback.get_label())] = feedback
  3555.         else:
  3556.             self._add_to_section('feedback', feedback)
  3557.         logger.info(("You can access the feedback from the fitting '{}' ) "
  3558.                      "with the twig '{}@feedback'".format(fittinglabel, fittinglabel)))
  3559.        
  3560.         # Accept the feedback: set/reset the variables to their fitted values
  3561.         # or their initial values, and in any case recompute the system such
  3562.         # that the synthetics are up-to-date with the parameters
  3563.         self.accept_feedback(fittingoptions['label']+'@feedback',
  3564.                              recompute=accept_feedback, revert=(not accept_feedback))
  3565.        
  3566.         return feedback
  3567.        
  3568.    
  3569.     def accept_feedback(self, twig, revert=False, recompute=True):
  3570.         """
  3571.        Change parameters with those resulting from fitting.
  3572.        
  3573.        [FUTURE]
  3574.        """
  3575.         # Retrieve the correct feedback
  3576.        
  3577.         feedback = self._get_by_search(twig, kind='Feedback')
  3578.         feedback.apply_to(self.get_system(), revert=revert)
  3579.        
  3580.         # If we need to recompute, recompute with the specified label
  3581.         if recompute:
  3582.             computelabel = feedback.get_computelabel()
  3583.             self.run_compute(label=computelabel)
  3584.             self._build_trunk()
  3585.        
  3586.         return feedback
  3587.    
  3588.     #}
  3589.    
  3590.     def set_syn_as_obs(self, dataref, sigma=0.01):
  3591.         """
  3592.        Set synthetic computations as if they were really observed.
  3593.        
  3594.        This can be handy to experiment with the fitting routines.
  3595.        
  3596.        [FUTURE]
  3597.        """
  3598.         syn = self._get_by_search(dataref, context='*syn', class_name='*DataSet')
  3599.         obs = self._get_by_search(dataref, context='*obs', class_name='*DataSet')
  3600.        
  3601.         if obs.get_context()[:-3] == 'lc':
  3602.             if np.isscalar(sigma):
  3603.                 sigma = sigma*np.median(syn['flux'])*np.ones(len(syn))
  3604.             obs['flux'] = syn['flux'] + np.random.normal(size=len(obs), scale=sigma)
  3605.             obs['sigma'] = sigma
  3606.        
  3607.         elif obs.get_context()[:-3] == 'rv':
  3608.             if np.isscalar(sigma):
  3609.                 sigma = sigma*np.median(syn['rv'])
  3610.             obs['rv'] = syn['rv'] + np.random.normal(size=len(obs), scale=sigma)
  3611.             obs['sigma'] = sigma
  3612.            
  3613.    
  3614.     #{ Figures
  3615.     def show(self, twig=None, attach=None, **kwargs):
  3616.         """
  3617.        [FUTURE]
  3618.        
  3619.        Draw and show a figure, axes, or plot that is attached to the bundle.
  3620.        
  3621.        If :envvar:`twig` is None, then the current figure will be plotted.
  3622.        All kwargs are passed to :py:func`Bundle.draw`
  3623.        
  3624.        :param twig: twig pointing the the figure, axes, or plot options
  3625.        :type twig: str or None
  3626.        :param attach: whether to attach these options to the bundle
  3627.        :type attach: bool
  3628.        """
  3629.         ax_or_fig = self.draw(twig, attach=attach, **kwargs)
  3630.         plt.show()
  3631.        
  3632.     def savefig(self, twig=None, filename=None, attach=None, **kwargs):
  3633.         """
  3634.        [FUTURE]
  3635.  
  3636.        Draw and save a figure, axes, or plot that is attached to the bundle
  3637.        to file.
  3638.        
  3639.        If :envvar:`twig` is None, then the current figure will be plotted.
  3640.        All kwargs are passed to :py:func`Bundle.draw`
  3641.        
  3642.        :param twig: twig pointing the the figure, axes, or plot options
  3643.        :type twig: str or None
  3644.        :param attach: whether to attach these options to the bundle
  3645.        :type attach: bool
  3646.        :param filename: filename for the resulting image
  3647.        :type filename: str
  3648.        """
  3649.         ax_or_fig = self.draw(twig, attach=attach **kwargs)
  3650.         plt.savefig(filename)
  3651.        
  3652.     def draw(self, twig=None, attach=None, **kwargs):
  3653.         """
  3654.        [FUTURE]
  3655.        
  3656.        Draw a figure, axes, or plot that is attached to the bundle.
  3657.        
  3658.        If :envvar:`twig` is None, then the current figure will be plotted.
  3659.        
  3660.        If you do not provide a value for :envvar:`attach`, it will default to True
  3661.        if you provide a value for :envvar:`twig`, or False if you do not.  In this
  3662.        way, simply calling draw() with no arguments will not retain a copy in the
  3663.        bundle - you must either explicitly provide a twig or set attach to True.
  3664.        
  3665.        If you don't provide :envvar:`fig` or :envvar:`ax`, then the the
  3666.        figure/axes will be drawn to plt.gcf() or plt.gca()
  3667.        
  3668.        If you provide :envvar:`fig` but :envvar:`twig` points to a plot or
  3669.        axes instead of a figure, then the axes will be drawn to fig.gca()
  3670.        
  3671.        :param twig: twig pointing the the figure, axes, or plot options
  3672.        :type twig: str or None
  3673.        :param attach: whether to attach these options to the bundle
  3674.        :type attach: bool
  3675.        :param ax: mpl axes used for plotting (optional, overrides :envvar:`fig`)
  3676.        :type ax: mpl.Axes
  3677.        :param fig: mpl figure used for plotting (optional)
  3678.        :type fig: mpl.Figure
  3679.        :param clf: whether to call plt.clf() before plotting
  3680.        :type clf: bool
  3681.        :param cla: whether to call plt.cal() before plotting
  3682.        :type cla: bool
  3683.        """
  3684.         if attach is None:
  3685.             attach = twig is not None
  3686.        
  3687.         if twig is None:
  3688.             ps = self.get_figure()
  3689.         else:
  3690.             ps = self._get_by_search(twig, section=['plot','axes','figure'])
  3691.        
  3692.         ax = kwargs.pop('ax', None)
  3693.         fig = kwargs.pop('fig', None)
  3694.         clf = kwargs.pop('clf', False)
  3695.         cla = kwargs.pop('cla', False)
  3696.        
  3697.         ref = ps.get_value('ref')
  3698.         level = ps.context.split(':')[1].split('_')[0] # will be plot, axes, or figure
  3699.        
  3700.         if level=='figure':
  3701.             if fig is None:
  3702.                 fig = plt.gcf()
  3703.             if clf:
  3704.                 fig.clf()
  3705.            
  3706.             ret = self.draw_figure(ref, fig=fig, **kwargs)
  3707.            
  3708.             if not attach:
  3709.                 self.remove_figure(ref if ref is not None else self.current_figure)
  3710.        
  3711.         elif level=='axes':
  3712.             if ax is None:
  3713.                 if fig is None:
  3714.                     ax = plt.gca()
  3715.                 else:
  3716.                     ax = fig.gca()
  3717.             if cla:
  3718.                 ax.cla()
  3719.        
  3720.             ret = self.draw_axes(ref, ax=ax, **kwargs)
  3721.            
  3722.             if not attach:
  3723.                 self.remove_axes(ref if ref is not None else self.current_axes)
  3724.            
  3725.         else:
  3726.             if ax is None:
  3727.                 if fig is None:
  3728.                     ax = plt.gca()
  3729.                 else:
  3730.                     ax = fig.gca()
  3731.             if cla:
  3732.                 ax.cla()
  3733.            
  3734.             ret = self.draw_plot(ref, ax=ax, **kwargs)
  3735.            
  3736.             if not attach:
  3737.                 self.remove_plot(ref)
  3738.            
  3739.        
  3740.         return ret
  3741.  
  3742.     def get_figure(self, figref=None):
  3743.         """
  3744.        [FUTURE]
  3745.        
  3746.        Get the ParameterSet object for a figure attached to the bundle
  3747.        
  3748.        If :envvar:`figref` is None, then the current figure will be retrieved
  3749.        if one exists.  If one does not exist, an empty will first be created
  3750.        and attached to the bundle.
  3751.        
  3752.        :param figref: ref to the figure
  3753.        :type figref: str or None
  3754.        :return: the figure
  3755.        :rtype: frontend.plotting.Figure (ParameterSet)
  3756.        """
  3757.         # TODO: allow passing kwargs
  3758.        
  3759.         if figref is None:
  3760.             figref = self._handle_current_figure()
  3761.         else:
  3762.             figref = figref.split('@')[0]
  3763.            
  3764.         self.current_figure = figref
  3765.        
  3766.         fig_ps = self._get_by_section(figref, "figure", kind=None)
  3767.        
  3768.         if self.current_axes is None and len(fig_ps['axesrefs']):
  3769.             self.current_axes = fig_ps['axesrefs'][-1]
  3770.        
  3771.         return fig_ps
  3772.    
  3773.     def add_figure(self, figref=None, **kwargs):
  3774.         """
  3775.        [FUTURE]
  3776.        
  3777.        Add a new figure to the bundle, and set it as the current figure
  3778.        for any subsequent plotting calls
  3779.        
  3780.        If :envvar:`figref` is None, a default will be created
  3781.        
  3782.        Any kwargs will get passed to the plotting:figure ParameterSet.
  3783.        
  3784.        :param figref: ref for the figure
  3785.        :type figref: str or None
  3786.        :return: the figref
  3787.        :rtype: str
  3788.        """
  3789.        
  3790.         if figref is None:
  3791.             figref = "fig{:02}".format(len(self.sections['figure'])+1)
  3792.         fig = Figure(self, ref=figref, **kwargs)
  3793.  
  3794.         for k,v in kwargs.items():
  3795.             fig.set_value(k,v)
  3796.            
  3797.         if figref not in self._get_dict_of_section('figure').keys():
  3798.             self._add_to_section('figure',fig) #calls rebuild trunk
  3799.        
  3800.         self.current_figure = figref
  3801.         self.current_axes = None
  3802.        
  3803.         return self.current_figure
  3804.    
  3805.     @rebuild_trunk
  3806.     def remove_figure(self, figref=None):
  3807.         """
  3808.        [FUTURE]
  3809.        
  3810.        Remove a figure and all of its children axes (and their children plots)
  3811.        that are not referenced elsewhere.
  3812.        
  3813.        :param figref: ref of the figure
  3814.        :type figref: str
  3815.        """
  3816.         if figref is None:
  3817.             figref = self.current_figure
  3818.            
  3819.         fig_ps = self._get_by_section(figref, "figure", kind=None)
  3820.         axesrefs = fig_ps['axesrefs']
  3821.         self.sections['figure'].remove(fig_ps)
  3822.        
  3823.         for axesref in axesrefs:
  3824.             if all([axesref not in ps['axesrefs'] for ps in self.sections['figure']]):
  3825.                 #~ print "*** remove_figure: removing axes", axesref
  3826.                 self.remove_axes(axesref)
  3827.             #~ else:
  3828.                 #~ print "*** remove_figure: keeping axes", axesref
  3829.        
  3830.     def _handle_current_figure(self, figref=None):
  3831.         """
  3832.        [FUTURE]
  3833.        
  3834.        similar to _handle_current_axes - except perhaps we shouldn't be creating figures by default,
  3835.        but only creating up to the axes level unless the user specifically requests a figure to be made?
  3836.        """
  3837.         if not self.current_figure or (figref is not None and figref not in self._get_dict_of_section('figure').keys()):
  3838.             figref = self.add_figure(figref=figref)
  3839.         if figref is not None:
  3840.             self.current_figure = figref
  3841.            
  3842.         return self.current_figure
  3843.        
  3844.     def draw_figure(self, figref=None, fig=None, **kwargs):
  3845.         """
  3846.        [FUTURE]
  3847.        
  3848.        Draw a figure that is attached to the bundle.
  3849.        
  3850.        If :envvar:`figref` is None, then the current figure will be plotted.
  3851.        
  3852.        If you don't provide :envvar:`fig` then the figure will be drawn to plt.gcf().
  3853.        
  3854.        :param figref: ref of the figure
  3855.        :type figref: str or None
  3856.        :param fig: mpl figure used for plotting (optional)
  3857.        :type fig: mpl.Figure
  3858.        """
  3859.         # TODO move clf option from self.draw to here
  3860.  
  3861.         if fig is None:
  3862.             fig = plt.gcf()
  3863.        
  3864.         live_plot = kwargs.pop('live_plot', False)
  3865.            
  3866.         fig_ps = self.get_figure(figref)
  3867.        
  3868.         axes_ret, plot_ret = {}, {}
  3869.         for (axesref,axesloc,axessharex,axessharey) in zip(fig_ps['axesrefs'], fig_ps['axeslocs'], fig_ps['axessharex'], fig_ps['axessharey']):
  3870.             # need to handle logic for live-plotting
  3871.             axes_ps = self.get_axes(axesref)
  3872.             plotrefs = axes_ps['plotrefs']
  3873.             # the following line will check (if we're live-plotting) to
  3874.             # see if all the plots in this axes have already been drawn,
  3875.             # and if they have, then we have no need to make a new subplot
  3876.             # or call the plotting function
  3877.             if not (live_plot and all([(axesref.split('@')[0], plotref.split('@')[0]) in self.currently_plotted for plotref in plotrefs])):
  3878.                 # the following line will default to the current mpl ax object if we've already
  3879.                 # created this subplot - TODO this might cause issues if switching back to a previous axesref
  3880.                 if live_plot and axesref.split('@')[0] in [p[0] for p in self.currently_plotted]:
  3881.                     #~ print "*** plot_figure resetting ax to None"
  3882.                     ax = None # will default to plt.gca() - this could still cause some odd things
  3883.                     xaxis_loc, yaxis_loc = None, None
  3884.                 else:
  3885.                     axes_existing_refs = [a.split('@')[0] for a in axes_ret.keys()]
  3886.                     #~ print "***", axesref, axes_ps.get_value('sharex'), axes_ps.get_value('sharey'), axes_existing_refs
  3887.                    
  3888.                     # handle axes sharing
  3889.                     # we also need to check to see if we're in the same location, and if so
  3890.                     # smartly move the location of the axesticks and labels
  3891.                     axessharex = axessharex.split('@')[0]
  3892.                     axessharey = axessharey.split('@')[0]
  3893.                     if axessharex != '_auto_' and axessharex in axes_existing_refs:
  3894.                         ind = axes_existing_refs.index(axessharex)
  3895.                         sharex = axes_ret.values()[ind]
  3896.                         if axesloc in [fig_ps.get_loc(axes_ret.keys()[ind])]:
  3897.                             # then we're drawing on the same location, so need to move the axes
  3898.                             yaxis_loc = 'right'
  3899.                     else:
  3900.                         sharex = None
  3901.                         yaxis_loc = None # just ignore, defaults will take over
  3902.                     if axessharey != '_auto_' and axessharey in axes_existing_refs:
  3903.                         ind = axes_existing_refs.index(axessharey)
  3904.                         sharey = axes_ret.values()[ind]
  3905.                         #~ print "*** draw_figure move xaxis_loc?", axesloc, fig_ps.get_loc(axes_ret.keys()[ind])
  3906.                         if axesloc in [fig_ps.get_loc(axes_ret.keys()[ind])]:
  3907.                             # then we're drawing on the same location, so need to move the axes
  3908.                             xaxis_loc = 'top'
  3909.                     else:
  3910.                         sharey = None
  3911.                         xaxis_loc = None # just ignore, defaults will take over
  3912.                        
  3913.                     #~ print "***", axesref, sharex, sharey
  3914.                        
  3915.                     ax = fig.add_subplot(axesloc[0],axesloc[1],axesloc[2],sharex=sharex,sharey=sharey)
  3916.                     # we must set the current axes so that subsequent live-plotting calls will
  3917.                     # go here unless overriden by the user
  3918.                     plt.sca(ax)
  3919.  
  3920.                 #~ print "*** draw_figure calling draw_axes", figref, axesref, axesloc
  3921.                 axes_ret_new, plot_ret_new = self.draw_axes(axesref, ax=ax, live_plot=live_plot)
  3922.                
  3923.                 #~ print "*** draw_figure", xaxis_loc, yaxis_loc
  3924.                
  3925.                 if xaxis_loc == 'top':
  3926.                     ax.xaxis.tick_top()
  3927.                     ax.xaxis.set_label_position("top")
  3928.                     #~ ax.xaxis.set_offset_position('top')
  3929.                     ax.yaxis.set_visible(False)
  3930.                     ax.patch.set_visible(False)
  3931.                 if yaxis_loc == 'right':
  3932.                     ax.yaxis.tick_right()
  3933.                     ax.yaxis.set_label_position("right")
  3934.                     #~ ax.yaxis.set_offset_position('right')
  3935.                     ax.xaxis.set_visible(False)
  3936.                     ax.patch.set_visible(False)
  3937.                
  3938.                 for k,v in axes_ret_new.items():
  3939.                     axes_ret[k] = v
  3940.                 for k,v in plot_ret_new.items():
  3941.                     plot_ret[k] = v
  3942.             #~ else:
  3943.                 #~ print "*** plot_figure skipping axesref {} entirely".format(axesref)
  3944.            
  3945.         if not live_plot:
  3946.             self.current_figure = None
  3947.             self.current_axes = None
  3948.             self.currently_plotted = []
  3949.            
  3950.         return ({figref: fig}, axes_ret, plot_ret)
  3951.        
  3952.    
  3953.     #}
  3954.    
  3955.     #{ Axes
  3956.     def get_axes(self, axesref=None):
  3957.         """
  3958.        [FUTURE]
  3959.        
  3960.        Get the ParameterSet object for an axes attached to the bundle
  3961.        
  3962.        If :envvar:`axesref` is None, then the current axes will be retrieved
  3963.        if one exists.  If one does not exist, an empty will first be created
  3964.        and attached to the bundle as well as the current figure.
  3965.        
  3966.        :param axesref: ref to the axes
  3967.        :type axesref: str or None
  3968.        :return: the axes
  3969.        :rtype: frontend.plotting.Axes (ParameterSet)
  3970.        """
  3971.         # TODO allow passing kwargs
  3972.        
  3973.         if axesref is None:
  3974.             axesref, figref = self._handle_current_axes()
  3975.         else:
  3976.             axesref = axesref.split('@')[0]
  3977.         self.current_axes = axesref
  3978.         return self._get_by_section(axesref, "axes", kind=None)
  3979.        
  3980.     def add_axes(self, axesref=None, figref=None, loc=(1,1,1), sharex='_auto_', sharey='_auto_', **kwargs):
  3981.         """
  3982.        [FUTURE]
  3983.        
  3984.        Add a new axes to the bundle, and set it as the current axes
  3985.        for any subsequent plotting calls.
  3986.        
  3987.        If :envvar:`axesref` is None, a default will be created.
  3988.        
  3989.        If :envvar:`figref` is None, the axes will be attached to the current figure.
  3990.        If :envvar:`figref` points to an existing figure, the axes will be attached
  3991.        to that figure and it will become the current figure.
  3992.        If :envvar:`figure` does not point to an existing figure, the figure will
  3993.        be created, attached, and will become the current figure.
  3994.        
  3995.        Any kwargs will get passed to the plotting:axes ParameterSet.
  3996.        
  3997.        :param axesref: ref for the axes
  3998.        :type axesref: str
  3999.        :param figref: ref to a parent figure
  4000.        :type figref: str or None
  4001.        :param loc: location in the figure to attach the axes (see matplotlib.figure.Figure.add_subplot)
  4002.        :type loc: tuple with length 3
  4003.        :return: (axesref, figref)
  4004.        :rtype: tuple of strings
  4005.        """
  4006.         #~ axesref = kwargs.pop('axesref', None)
  4007.         #~ figref = kwargs.pop('figref', None)
  4008.         add_to_section = True
  4009.         if axesref is None or axesref not in self._get_dict_of_section('axes').keys():
  4010.             if axesref is None:
  4011.                 axesref = "axes{:02}".format(len(self.sections['axes'])+1)
  4012.             axes_ps = Axes(self, ref=axesref, **kwargs)
  4013.         else:
  4014.             add_to_section = False
  4015.             axes_ps = self.get_axes(axesref)
  4016.  
  4017.         for k,v in kwargs.items():
  4018.             axes_ps.set_value(k,v)
  4019.  
  4020.         if add_to_section:
  4021.             self._add_to_section('axes',axes_ps)
  4022.        
  4023.         # now attach it to a figure
  4024.         figref = self._handle_current_figure(figref=figref)
  4025.         if loc in [None, '_auto_']: # just in case (this is necessary and used from defaults in other functions)
  4026.             loc = (1,1,1)
  4027.         self.get_figure(figref).add_axes(axesref, loc, sharex, sharey) # calls rebuild trunk and will also set self.current_figure
  4028.  
  4029.         self.current_axes = axesref
  4030.        
  4031.         return self.current_axes, self.current_figure
  4032.  
  4033.     @rebuild_trunk
  4034.     def remove_axes(self, axesref=None):
  4035.         """
  4036.        [FUTURE]
  4037.        
  4038.        Remove an axes and all of its children plots that are not referenced
  4039.        by other axes.
  4040.        
  4041.        :param axesref: ref of the axes
  4042.        :type axesref: str
  4043.        """
  4044.         axes_ps = self._get_by_section(axesref, "axes", kind=None)
  4045.         plotrefs = axes_ps['plotrefs']
  4046.  
  4047.         # we also need to check all figures, and remove this entry from any axesrefs
  4048.         # as well as any sharex or sharey
  4049.         for figref,fig_ps in self._get_dict_of_section('figure').items():
  4050.             fig_ps.remove_axes(axesref)
  4051.        
  4052.         self.sections['axes'].remove(axes_ps)
  4053.  
  4054.         for plotref in axes_ps['plotrefs']:
  4055.             if all([plotref not in ps['plotrefs'] for ps in self.sections['axes']]):
  4056.                 self.remove_plot(plotref)            
  4057.        
  4058.     def draw_axes(self, axesref=None, ax=None, **kwargs):
  4059.         """
  4060.        [FUTURE]
  4061.        
  4062.        Draw an axes that is attached to the bundle.
  4063.        
  4064.        If :envvar:`axesref` is None, then the current axes will be plotted.
  4065.        
  4066.        If you don't provide :envvar:`ax` then the figure will be drawn to plt.gca().
  4067.        
  4068.        :param axesref: ref of the axes
  4069.        :type axesref: str or None
  4070.        :param ax: mpl axes used for plotting (optional, overrides :envvar:`fig`)
  4071.        :type ax: mpl.Axes
  4072.        """
  4073.         # TODO move cla here from self.draw
  4074.  
  4075.         axes_ps = self.get_axes(axesref) #also sets to current
  4076.         axesref = self.current_axes
  4077.        
  4078.         if ax is None:
  4079.             ax = plt.gca()
  4080.            
  4081.         live_plot = kwargs.pop('live_plot', False)
  4082.  
  4083.         # right now all axes options are being set for /each/ draw_plot
  4084.         # call, which will work, but is overkill.  The reason for this is
  4085.         # so that draw_plot(plot_label) is smart enough to handle also setting
  4086.         # the axes options for the one single call
  4087.        
  4088.         plot_ret = {}
  4089.         for plotref in axes_ps['plotrefs']:
  4090.             # handle logic for live-plotting - don't duplicate something that has already been called
  4091.             #~ print "*** draw_axes", live_plot, (axesref.split('@')[0], plotref.split('@')[0]), self.currently_plotted
  4092.             if not (live_plot and (axesref.split('@')[0], plotref.split('@')[0]) in self.currently_plotted):
  4093.                 #~ print "*** draw_axes CALLING draw_plot for", axesref, plotref
  4094.                 plot_ret_new = self.draw_plot(plotref, axesref=axesref, ax=ax, live_plot=live_plot)
  4095.            
  4096.                 for k,v in plot_ret_new.items():
  4097.                     plot_ret[k] = v
  4098.             #~ else:
  4099.                 #~ print "*** draw_axes skipping draw_plot for", axesref, plotref
  4100.            
  4101.         if not live_plot:
  4102.             self.current_axes = None
  4103.             #~ self.current_figure = None
  4104.             self.currently_plotted = []
  4105.            
  4106.         return ({axesref: ax}, plot_ret)
  4107.            
  4108.     def _handle_current_axes(self, axesref=None, figref=None, loc=None, sharex='_auto_', sharey='_auto_', x_unit='_auto_', y_unit='_auto_'):
  4109.         """
  4110.        [FUTURE]
  4111.        this function should be called whenever the user calls a plotting function
  4112.        
  4113.        it will check to see if the current axes is defined, and if it isn't it will
  4114.        create a new axes instance with an intelligent default label
  4115.        
  4116.        the created plotting parameter set should then be added later by self.get_axes(label).add_plot(plot_twig)
  4117.        """
  4118.        
  4119.         #~ print "***", self.current_axes, axesref, figref, loc, sharex, sharey
  4120.        
  4121.         fig_ps = self.get_figure(figref)
  4122.        
  4123.         #~ figaxesrefs = fig_ps.get_value('axesrefs')
  4124.         #~ figaxesrefs_ref = [ar.split('@')[0] for ar in figaxesrefs]
  4125.         #~ figaxeslocs = fig_ps.get_value('axeslocs')
  4126.        
  4127.         if self.current_axes is not None:
  4128.             curr_axes_ps = self.get_axes(self.current_axes)
  4129.             curr_axes_loc = fig_ps.get_loc(self.current_axes)
  4130.         else:
  4131.             curr_axes_ps = None
  4132.             curr_axes_loc = None
  4133.  
  4134.         #~ print "*** _handle_current_axes", axesref, self.current_axes, loc, curr_axes_loc, loc in [None, curr_axes_loc]
  4135.  
  4136.         if not self.current_axes \
  4137.                 or (axesref is not None and axesref not in self._get_dict_of_section('axes')) \
  4138.                 or (loc is not None and loc not in fig_ps.get_value('axeslocs')):
  4139.             # no existing figure
  4140.             # or new axesref for this figure
  4141.             # or new loc for this figure (note: cannot retrieve by loc)
  4142.             #~ print "*** _handle_current_axes calling add_axes", axesref, loc, sharex, sharey
  4143.             axesref, figref = self.add_axes(axesref=axesref, figref=figref, loc=loc, sharex=sharex, sharey=sharey) # other stuff can be handled externally (eg by _handle_plotting_call)
  4144.  
  4145.         # handle units
  4146.         elif axesref is None and loc in [None, curr_axes_loc]:
  4147.             # we do not allow axesref==self.current_axes in this case, because then we'd be overriding the axesref
  4148.            
  4149.             # TODO: we need to be smarter here if any of the units are _auto_ - they may be referring to
  4150.             # different datasets with different default units, so we need to check to see what _auto_
  4151.             # actually refers to and see if that matches... :/
  4152.             add_axes = True
  4153.             if x_unit != curr_axes_ps.get_value('x_unit') and y_unit != curr_axes_ps.get_value('y_unit'):
  4154.                 #~ print "*** _handle_current_axes units caseA"
  4155.                 sharex = '_auto_'
  4156.                 sharey = '_auto_'
  4157.                 loc = curr_axes_loc
  4158.                 # axes locations (moving to top & right) is handled in draw_figure
  4159.             elif x_unit != curr_axes_ps.get_value('x_unit'):
  4160.                 #~ print "*** _handle_current_axes units caseB"
  4161.                 sharex = '_auto_'
  4162.                 sharey = self.current_axes
  4163.                 loc = curr_axes_loc
  4164.                 # axes locations (moving to top) is handled in draw_figure
  4165.             elif y_unit != curr_axes_ps.get_value('y_unit'):
  4166.                 #~ print "*** _handle_current_axes units caseC"
  4167.                 sharex = self.current_axes
  4168.                 sharey = '_auto_'
  4169.                 loc = curr_axes_loc
  4170.                 # axes locations (moving to right) is handled in draw_figure
  4171.             else:
  4172.                 #~ print "*** _handle_current_axes units caseD"
  4173.                 #~ sharex = '_auto_'
  4174.                 #~ sharey = '_auto_'
  4175.                 #~ loc = loc
  4176.                 add_axes = False
  4177.                
  4178.             if add_axes:
  4179.                 #~ print "*** _handle_current_axes calling add_axes", axesref, loc, sharex, sharey
  4180.                 axesref, figref = self.add_axes(axesref=axesref, figref=figref, loc=loc, x_unit=x_unit, y_unit=y_unit, sharex=sharex, sharey=sharey)
  4181.                
  4182.  
  4183.        
  4184.         if axesref is not None:
  4185.             self.current_axes = axesref
  4186.            
  4187.         return self.current_axes, self.current_figure
  4188.        
  4189.  
  4190.     #}
  4191.  
  4192.     #{ Plots
  4193.     def _handle_plotting_call(self, func_name, dsti=None, **kwargs):
  4194.         """
  4195.        [FUTURE]
  4196.        this function should be called by any plot_* function.
  4197.        
  4198.        It essentially handles storing all of the arguments passed to the
  4199.        function inside a parameterset, attaches it to the bundle, and attaches
  4200.        it to the list of plots of the current axes.
  4201.        """
  4202.         plotref = kwargs.pop('plotref', None)
  4203.         axesref = kwargs.pop('axesref', None)
  4204.         figref = kwargs.pop('figref', None)
  4205.        
  4206.         if plotref is None:
  4207.  
  4208.             if func_name in ['plot_mesh', 'plot_custom']:
  4209.                 plotref_base = func_name.split('_')[1]
  4210.             else:
  4211.                 # then we need an intelligent default
  4212.                 plotref_base = "_".join([dsti['ref'], dsti['context'], dsti['label']])
  4213.                
  4214.             plotref = plotref_base
  4215.  
  4216.             # change the ref if it already exists in the bundle
  4217.             existing_plotrefs = [pi['ref'] for pi in self.sections['plot']]
  4218.             i=1
  4219.             while plotref in existing_plotrefs:
  4220.                 i+=1
  4221.                 plotref = "{}_{:02}".format(plotref_base, i)
  4222.  
  4223.  
  4224.         loc = kwargs.pop('loc', None)
  4225.         sharex = kwargs.pop('sharex','_auto_')
  4226.         sharey = kwargs.pop('sharex','_auto_')
  4227.         figref = self._handle_current_figure(figref=figref)
  4228.         axesref, figref = self._handle_current_axes(axesref=axesref, figref=figref, loc=loc, sharex=sharex, sharey=sharey, x_unit=kwargs.get('x_unit','_auto_'), y_unit=kwargs.get('y_unit','_auto_'))
  4229.         axes_ps = self.get_axes(axesref)
  4230.         fig_ps = self.get_figure(figref)
  4231.  
  4232.         # TODO: plot_obs needs to be generalized to pull from the function called
  4233.         kwargs['datatwig'] = dsti.pop('twig',None) if dsti is not None else None
  4234.         ps = parameters.ParameterSet(context='plotting:{}'.format(func_name), ref=plotref)
  4235.         for k,v in kwargs.items():
  4236.             # try plot_ps first, then axes_ps and fig_ps
  4237.             if k in ps.keys():
  4238.                 ps.set_value(k,v)
  4239.             elif k in axes_ps.keys():
  4240.                 if k not in ['plotrefs']:
  4241.                     axes_ps.set_value(k,v)
  4242.             elif k in fig_ps.keys():
  4243.                 if k not in ['axesrefs','axeslocs','axessharex','axessharey']:
  4244.                     fig_ps.set_value(k,v)
  4245.             elif k in ['label']:
  4246.                 raise KeyError("parameter with qualifier {} forbidden".format(k))
  4247.             else:
  4248.                 if isinstance(v, float):
  4249.                     _cast_type = float
  4250.                     _repr = '%f'
  4251.                 elif isinstance(v, int):
  4252.                     _cast_type = int
  4253.                     _repr = '%d'
  4254.                 elif isinstance(v, bool):
  4255.                     _cast_type = 'make_bool'
  4256.                     _repr = ''
  4257.                 else:
  4258.                     _cast_type = str
  4259.                     _repr = '%s'
  4260.                
  4261.                 new_parameter = parameters.Parameter(qualifier=k,
  4262.                         description = 'user added parameter: see matplotlib',
  4263.                         repr = _repr,
  4264.                         cast_type = _cast_type,
  4265.                         value = v)
  4266.                 ps.add(new_parameter, with_qualifier=k)
  4267.        
  4268.         self._add_to_section('plot', ps)
  4269.  
  4270.         axes_ps.add_plot(plotref)
  4271.        
  4272.         return plotref, axesref, figref
  4273.        
  4274.     def get_plot(self, plotref):
  4275.         """
  4276.        [FUTURE]
  4277.        
  4278.        Get the ParameterSet object for a plot attached to the bundle
  4279.        
  4280.        :param plotref: ref to the axes
  4281.        :type plotref: str
  4282.        :return: the plot
  4283.        :rtype: ParameterSet
  4284.        """
  4285.         # TODO allow plotref=None ?
  4286.         # TODO allow passing kwargs ?
  4287.         plotref = plotref.split('@')[0]
  4288.         return self._get_by_section(plotref,"plot",kind=None)
  4289.        
  4290.     @rebuild_trunk
  4291.     def remove_plot(self, plotref=None):
  4292.         """
  4293.        [FUTURE]
  4294.        
  4295.        Remove a plot from the bundle.
  4296.        
  4297.        This does not check to make sure the plot is not referenced in
  4298.        any existing axes, so use with caution.
  4299.        
  4300.        :param plotref: ref of the plot
  4301.        :type plotref: str
  4302.        """
  4303.         plot_ps = self._get_by_section(plotref, "plot", kind=None)
  4304.        
  4305.         # we also need to check all axes, and remove this entry from any plotrefs
  4306.         #~ plotref_ref = plotref.split('@')[0]
  4307.         for axesref,axes_ps in self._get_dict_of_section('axes').items():
  4308.             axes_ps.remove_plot(plotref)
  4309.            
  4310.         self.sections['plot'].remove(plot_ps)
  4311.        
  4312.     def add_plot(self, plotref=None, axesref=None, figref=None, loc=None, **kwargs):
  4313.         """
  4314.        [FUTURE]
  4315.        
  4316.        Add an existing plot (created through plot_obs, plot_syn, plot_residuals, plot_mesh, etc)
  4317.        to an axes and figure in the bundle.
  4318.  
  4319.        If :envvar:`loc` points to a location in the :envvar:`figref` figure
  4320.        that is empty, a new axes will be created
  4321.                
  4322.        If :envvar:`axesref` is None and :envvar:`loc` is None, the plot will be attached to the current axes.
  4323.        If :envvar:`axesref` points to an existing axes, the plot will be attached
  4324.        to that axes and it will become the current axes.
  4325.        If :envvar:`axesref` does not point to an existing axes, the axes will be
  4326.        created, attached, and will become the current axes.
  4327.        
  4328.        If :envvar:`figref` is None, the axes will be attached to the current figure.
  4329.        If :envvar:`figref` points to an existing figure, the axes will be attached
  4330.        to that figure and it will become the current figure.
  4331.        If :envvar:`figure` does not point to an existing figure, the figure will
  4332.        be created, attached, and will become the current figure.
  4333.        
  4334.        Any kwargs will get passed to the :envvar:`plotref` ParameterSet.
  4335.        
  4336.        :param plotref: ref of the plot
  4337.        :type plotref: str
  4338.        :param axesref: ref to a parent axes
  4339.        :type axesref: str
  4340.        :param figref: ref to a parent figure
  4341.        :type figref: str or None
  4342.        :param loc: location in the figure to attach the axes (see matplotlib.figure.Figure.add_subplot)
  4343.        :type loc: tuple with length 3
  4344.        :return: (axesref, figref)
  4345.        :rtype: tuple of strings
  4346.        """
  4347.        
  4348.         figref = self._handle_current_figure(figref)
  4349.         axesref, figref = self._handle_current_axes(axesref, figref, loc=loc)
  4350.        
  4351.         axes_ps = self.get_axes(axesref)
  4352.         axes_ps.add_plot(plotref)
  4353.         plot_ps = self.get_plot(plotref)
  4354.        
  4355.         for k,v in kwargs.items():
  4356.             if k in axes_ps.keys():
  4357.                 axes_ps.set_value(k,v)
  4358.             else:
  4359.                 plot_ps.set_value(k,v)
  4360.                
  4361.         return plotref, axesref, figref
  4362.  
  4363.        
  4364.     def draw_plot(self, plotref, axesref=None, ax=None, **kwargs):
  4365.         """
  4366.        [FUTURE]
  4367.        
  4368.        this function should make all the calls to mpl, and can either
  4369.        be called by the user for a previously created plot, or by the
  4370.        functions that created the plot initially.
  4371.        
  4372.        this function /technically/ draws this one single plot on the current axes -
  4373.        we need to be careful about weird behavior here, or maybe we need to
  4374.        be more clever and try to find (one of?) its parent axes if the plot_ps is already attached
  4375.        """
  4376.         plkwargs = {k:v for k,v in self.get_plot(plotref).items() if v is not ''}
  4377.        
  4378.         if ax is None:
  4379.             ax = plt.gca()
  4380.        
  4381.         axes_ps = self.get_axes(axesref)
  4382.         # will default to gca if axesref is None
  4383.         # in this case we may get unexpected behavior - either odd plotrefs
  4384.         # that we previously set or overriding axes options that were _auto_
  4385.        
  4386.         # this plot needs to be attached as a member of the axes if it is not
  4387.         #~ if plotref not in axes_ps['plotrefs']:
  4388.             #~ axes_ps.add_plot(plotref)
  4389.            
  4390.         plot_fctn = self.get_plot(plotref).context.split(':')[1]
  4391.  
  4392.         # Retrieve the obs/syn and the object it belongs to
  4393.         datatwig = plkwargs.pop('datatwig', None)
  4394.  
  4395.         # when passing to mpl, plotref will be used for legends so we'll override that with the ref of the plot_ps
  4396.         plkwargs['label'] = plkwargs.pop('ref')
  4397.  
  4398.         # and we need to pass the mpl axes
  4399.         if plot_fctn not in ['plot_custom']:
  4400.             plkwargs['ax'] = ax
  4401.        
  4402.         if datatwig is not None and plot_fctn not in ['plot_custom']:
  4403.             dsti = self._get_by_search(datatwig, return_trunk_item=True)
  4404.             ds = dsti['item']
  4405.             obj = self.get_object(dsti['label'])
  4406.             context = ds.get_context()
  4407.        
  4408.             # when passing to mpl the backend plotting function needs ref to be the dataset ref
  4409.             plkwargs['ref'] = ds['ref']
  4410.        
  4411.         axkwargs = {}
  4412.         for key in ['x_unit', 'y_unit', 'phased', 'xlabel', 'ylabel', 'title']:
  4413.             value = plkwargs.pop(key, None)
  4414.  
  4415.             # if provided by the plotting call, rewrite the value stored
  4416.             # in the axes_ps
  4417.             if value is not None:
  4418.                 axes_ps.set_value(key, value)
  4419.  
  4420.             # either way, retrieve the value from axes_ps and handle defaults
  4421.             if plot_fctn not in ['plot_custom']:
  4422.                 if key in ['x_unit', 'y_unit']:
  4423.                     if axes_ps.get_value(key) not in ['_auto_', u'_auto_']:
  4424.                         plkwargs[key] = axes_ps.get_value(key)
  4425.                
  4426.                 elif key in ['phased']:
  4427.                     if 'x_unit' not in plkwargs.keys() or axes_ps.get_value(key) or value is not None:
  4428.                         # TODO: the if statement above is hideous
  4429.                         plkwargs[key] = axes_ps.get_value(key)
  4430.                     # else it won't exist in plkwargs and will use the backend defaults
  4431.                
  4432.                 elif key in ['xlabel', 'ylabel', 'title']:
  4433.                     # these don't get passed to the plotting call
  4434.                     # rather if they are not overriden here, they will receive
  4435.                     # there defaults from the output of the plotting call
  4436.                     pass
  4437.                    
  4438.        
  4439.         #~ if 'x_unit' in plkwargs.keys():
  4440.             #~ print '***', plkwargs['x_unit']
  4441.         #~ if 'y_unit' in plkwargs.keys():
  4442.             #~ print '***', plkwargs['y_unit']
  4443.                    
  4444.         # handle _auto_
  4445.         for key, value in plkwargs.items():
  4446.             if value=='_auto_':
  4447.                 dump = plkwargs.pop(key)
  4448.        
  4449.         if plot_fctn in ['plot_obs', 'plot_syn']:
  4450.             output = getattr(plotting, 'plot_{}'.format(context))(obj, **plkwargs)
  4451.            
  4452.             artists = output[0]
  4453.             ds_ret = output[1]
  4454.             fig_decs = output[2]
  4455.  
  4456.         elif plot_fctn in ['plot_residuals']:
  4457.             category = context[:-3]
  4458.             output = getattr(plotting, 'plot_{}res'.format(category))(obj, **plkwargs)
  4459.            
  4460.             artists = output[0]
  4461.             ds_ret = output[1]
  4462.             fig_decs = output[2]
  4463.            
  4464.         elif plot_fctn in ['plot_mesh']:
  4465.             select = plkwargs.pop('select', None)
  4466.             cmap = plkwargs.pop('cmap', None)
  4467.             vmin = plkwargs.pop('vmin', None)
  4468.             vmax = plkwargs.pop('vmax', None)
  4469.             size = plkwargs.pop('size', None)
  4470.             dpi = plkwargs.pop('dpi', None)
  4471.             background = plkwargs.pop('background', None)
  4472.             savefig = plkwargs.pop('savefig', False)
  4473.             with_partial_as_half = plkwargs.pop('with_partial_as_half', False)
  4474.             time = plkwargs.pop('time', None)
  4475.             phase = plkwargs.pop('phase', None)
  4476.             computelabel = plkwargs.pop('computelabel', None)
  4477.             objref = plkwargs.pop('objref', None)
  4478.             category = context[:-3] if context is not None else 'lc'
  4479.            
  4480.             # get rid of unused kwargs
  4481.             for k in ['x_unit', 'y_unit', 'phased', 'label']:
  4482.                 dump = plkwargs.pop(k, None)
  4483.            
  4484.             # Set the configuration to the correct time/phase, but only when one
  4485.             # (and only one) of them is given.
  4486.             if time is not None and phase is not None:
  4487.                 raise ValueError("You cannot set both time and phase, please choose one")
  4488.             elif phase is not None:
  4489.                 period, t0, shift = self.get_system().get_period()
  4490.                 time = phase * period + t0
  4491.            
  4492.             # Observe the system with the right computations
  4493.             if time is not None:
  4494.                 options = self.get_compute(computelabel, create_default=True).copy()
  4495.                 observatory.observe(self.get_system(), [time], lc=category=='lc',
  4496.                                     rv=category=='rv', sp=category=='sp',
  4497.                                     pl=category=='pl', save_result=False, **options)
  4498.            
  4499.             # Get the object and make an image.
  4500.             self.get_object(objref).plot2D(select=select, cmap=cmap, vmin=vmin,
  4501.                          vmax=vmax, size=size, dpi=dpi, background=background,
  4502.                          savefig=savefig, with_partial_as_half=with_partial_as_half,
  4503.                          **plkwargs)
  4504.                          
  4505.             fig_decs = [['changeme','changeme'],['changeme','changeme']]
  4506.             artists = None
  4507.  
  4508.            
  4509.         elif plot_fctn in ['plot_custom']:
  4510.             function = plkwargs.pop('function', None)
  4511.             args = plkwargs.pop('args', [])
  4512.             if hasattr(ax, function):
  4513.                 output = getattr(ax, function)(*args, **plkwargs)
  4514.                
  4515.             else:
  4516.                 logger.error("{} not an available function for plt.axes.Axes".format(function))
  4517.                 return
  4518.  
  4519.             # fake things for the logger
  4520.             # TODO handle this better
  4521.             fig_decs = [['changeme','changeme'],['changeme','changeme']]
  4522.             artists = None
  4523.             context = 'changeme'                
  4524.             ds = {'ref': 'changeme'}
  4525.            
  4526.         else:
  4527.             logger.error("non-recognized plot type: {}".format(plot_fctn))
  4528.             return
  4529.        
  4530.         # automatically set axes_ps plotrefs if they're not already
  4531.         # The x-label
  4532.         if axes_ps.get_value('xlabel') in ['_auto_', u'_auto_']:
  4533.             axes_ps.set_value('xlabel', r'{} ({})'.format(fig_decs[0][0], fig_decs[1][0]))
  4534.        
  4535.         ax.set_xlabel(axes_ps.get_value('xlabel'))
  4536.        
  4537.         # The y-label
  4538.         if axes_ps.get_value('ylabel') in ['_auto_', u'_auto_']:
  4539.             axes_ps.set_value('ylabel', r'{} ({})'.format(fig_decs[0][1], fig_decs[1][1]))
  4540.  
  4541.         ax.set_ylabel(axes_ps.get_value('ylabel'))
  4542.        
  4543.         # The plot title
  4544.         if axes_ps.get_value('title') in ['_auto_', u'_auto_']:
  4545.             axes_ps.set_value('title', '{}'.format('mesh' if plot_fctn=='plot_mesh' else config.nice_names[context[:-3]]))
  4546.        
  4547.         ax.set_title(axes_ps.get_value('title'))  
  4548.        
  4549.         # The limits
  4550.         if axes_ps.get_value('xlim') not in [(None,None), '_auto_', u'_auto_']:
  4551.             ax.set_xlim(axes_ps.get_value('xlim'))
  4552.         if axes_ps.get_value('ylim') not in [(None,None), '_auto_', u'_auto_']:
  4553.             ax.set_ylim(axes_ps.get_value('ylim'))
  4554.            
  4555.         # The ticks
  4556.         #~ if axes_ps.get_value('xticks') != ['_auto_', u'_auto_']:
  4557.             #~ ax.set_xticks(axes_ps.get_value('xticks')
  4558.         #~ if axes_ps.get_value('yticks') != ['_auto_', u'_auto_']:
  4559.             #~ ax.set_xticks(axes_ps.get_value('yticks')
  4560.         #~ if axes_ps.get_value('xticklabels') != ['_auto_', u'_auto_']:
  4561.             #~ ax.set_xticklabels(axes_ps.get_value('xticklabels')
  4562.         #~ if axes_ps.get_value('yticklabels') != ['_auto_', u'_auto_']:
  4563.             #~ ax.set_xticklabels(axes_ps.get_value('yticklabels')
  4564.        
  4565.         logger.info("Plotted {} vs {} of {}({})".format(fig_decs[0][0],
  4566.                                    fig_decs[0][1], context, ds['ref']))
  4567.                                    
  4568.         #~ return ds_ret
  4569.         return {plotref: artists}
  4570.  
  4571.  
  4572.    
  4573.     def new_plot_obs(self, twig=None, **kwargs):
  4574.         """
  4575.        Make a plot of the attached observations (wraps pyplot.errorbar).
  4576.        
  4577.        This function is designed to behave like matplotlib's
  4578.        `plt.errorbar() <http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.errorbar>`_
  4579.        function, with additional options.
  4580.        
  4581.        Thus, all kwargs (there are no args) are passed on to matplotlib's
  4582.        `plt.errorbars() <http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.errorbar>`_,
  4583.        except:
  4584.    
  4585.            - :envvar:`repeat=0`: handy if you are actually fitting a phase
  4586.              curve, and you want to repeat the phase curve a couple of times.
  4587.            - :envvar:`x_unit=None`: allows you to override the default units
  4588.              for the x-axis. If you plot times, you can set the unit to any
  4589.              time unit (days (``d``), seconds (``s``), years (``yr``) etc.). If
  4590.              you plot in phase, you can switch from cycle (``cy``) to radians
  4591.              (``rad``).
  4592.            - :envvar:`y_unit=None`: allows you to override the default units
  4593.              for the y-axis. Allowable values depend on the type of
  4594.              observations.
  4595.            - :envvar:`ax=plt.gca()`: the axes to plot on. Defaults to current
  4596.              active axes.
  4597.    
  4598.        Some of matplotlib's defaults are overriden. If you do not specify any
  4599.        of the following keywords, they will take the values:
  4600.    
  4601.            - :envvar:`ref`: the ref of the stored plotoptions.  This is the value
  4602.              that will also be passed on to matplotlib for the label of this curve
  4603.              in legends.
  4604.            - :envvar:`yerr`: defaults to the uncertainties from the obs if they
  4605.              are available.
  4606.        
  4607.        The DataSet that is returned is a copy of the original DataSet, but with the
  4608.        units of the columns the same as the ones plotted.
  4609.    
  4610.        **Example usage**
  4611.            
  4612.        Suppose you have the following setup::
  4613.        
  4614.            bundle = phoebe.Bundle()
  4615.            bundle.lc_fromarrays(dataref='mylc', time=np.linspace(0, 1, 100),
  4616.            ...                  flux=np.random.normal(size=100))
  4617.            bundle.rv_fromarrays(dataref='myrv', objref='secondary',
  4618.            ...                  phase=np.linspace(0, 1, 100),
  4619.            ...                  rv=np.random.normal(size=100),
  4620.            ...                  sigma=np.ones(100))
  4621.      
  4622.        Then you can plot these observations with any of the following commands::
  4623.            
  4624.            bundle.plot_obs('mylc')
  4625.            bundle.plot_obs('mylc', phased=True)
  4626.            bundle.plot_obs('mylc', phased=True, repeat=1)
  4627.            bundle.plot_obs('myrv@secondary')
  4628.            bundle.plot_obs('myrv@secondary', fmt='ko-')
  4629.            plt.legend()
  4630.            bundle.plot_obs('myrv@secondary', fmt='ko-', label='my legend label')
  4631.            plt.legend()
  4632.            bundle.plot_obs('myrv@secondary', fmt='ko-', x_unit='s', y_unit='nRsol/d')
  4633.        
  4634.        For more explanations and a list of defaults for each type of
  4635.        observaitons, see:
  4636.        
  4637.            - :py:func:`plot_lcobs <phoebe.backend.plotting.plot_lcobs>`: for light curve plots
  4638.            - :py:func:`plot_rvobs <phoebe.backend.plotting.plot_rvobs>`: for radial velocity plots
  4639.        
  4640.        The arguments are passed to the appropriate functions in
  4641.        :py:mod:`plotting`.
  4642.        
  4643.        For more info on :envvar:`kwargs`, see the
  4644.        pyplot documentation on `plt.errorbars() <http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.errorbar>`_,
  4645.        
  4646.        :param twig: the twig/twiglet to use when searching
  4647.        :type twig: str
  4648.        :return: observations used for plotting
  4649.        :rtype: DataSet
  4650.        :raises IOError: when observations are not available
  4651.        :raises ValueError: when x/y unit is not allowed
  4652.        :raises ValueError: when y-axis not available (flux, rv...)
  4653.        """
  4654.         # Retrieve the obs DataSet and the object it belongs to
  4655.         dsti = self._get_by_search(twig, context='*obs', class_name='*DataSet',
  4656.                                    return_trunk_item=True)
  4657.        
  4658.         ax = kwargs.pop('ax', None)
  4659.         fig = kwargs.pop('fig', None)
  4660.        
  4661.         plotref, axesref, figref = self._handle_plotting_call('plot_obs', dsti, **kwargs)
  4662.  
  4663.         # now call the command to plot
  4664.         if ax is None:
  4665.             output = self.draw_figure(figref, fig=fig, live_plot=True, attach=True)
  4666.         else:
  4667.             output = self.draw_axes(axesref, ax=ax, live_plot=True, attach=True)
  4668.        
  4669.         self.currently_plotted.append((axesref.split('@')[0], plotref.split('@')[0]))
  4670.        
  4671.         #~ return None, (plotref, axesref, figref)
  4672.         return output, (plotref, axesref, figref)
  4673.         #~ return obs
  4674.  
  4675.        
  4676.     def new_plot_syn(self, twig=None, *args, **kwargs):
  4677.         """
  4678.        Plot simulated/computed observations (wraps pyplot.plot).
  4679.        
  4680.        This function is designed to behave like matplotlib's
  4681.        `plt.plot() <http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.plot>`_
  4682.        function, with additional options.
  4683.        
  4684.        Thus, all args and kwargs are passed on to matplotlib's
  4685.        `plt.plot() <http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.plot>`_,
  4686.        except:
  4687.        
  4688.            - :envvar:`dataref=0`: the reference of the lc to plot
  4689.            - :envvar:`repeat=0`: handy if you are actually fitting a phase curve,
  4690.              and you want to repeat the phase curve a couple of times.
  4691.            - :envvar:`x_unit=None`: allows you to override the default units for
  4692.              the x-axis. If you plot times, you can set the unit to any time unit
  4693.              (days (``d``), seconds (``s``), years (``yr``) etc.). If you plot
  4694.              in phase, you switch from cycle (``cy``) to radians (``rad``).
  4695.            - :envvar:`y_unit=None`: allows you to override the default units for
  4696.              the y-axis.
  4697.            - :envvar:`scale='obs'`: correct synthetics for ``scale`` and ``offset``
  4698.              from the observations. If ``obs``, they will effectively be scaled to
  4699.              the level/units of the observations (if that was specified in the
  4700.              computations as least). If you want to plot the synthetics in the
  4701.              model units, set ``scale=None``.
  4702.            - :envvar:`ax=plt.gca()`: the axes to plot on. Defaults to current
  4703.              active axes.
  4704.        
  4705.        Some of matplotlib's defaults are overriden. If you do not specify any of
  4706.        the following keywords, they will take the values:
  4707.        
  4708.            - :envvar:`ref`: the ref of the stored plotoptions.  This is the value
  4709.              that will also be passed on to matplotlib for the label of this curve
  4710.              in legends.
  4711.        
  4712.            
  4713.        Example usage:
  4714.        
  4715.        >>> mybundle.plot_syn('lc01', 'r-', lw=2) # first light curve added via 'data_fromarrays'
  4716.        >>> mybundle.plot_syn('rv01@primary', 'r-', lw=2, scale=None)
  4717.        
  4718.        >>> mybundle.plot_syn('if01', 'k-') # first interferometry added via 'data_fromarrays'
  4719.        >>> mybundle.plot_syn('if01', 'k-', y='vis2') # first interferometry added via 'data_fromarrays'
  4720.        
  4721.        More information on arguments and keyword arguments:
  4722.        
  4723.        - :py:func:`phoebe.backend.plotting.plot_lcsyn`
  4724.        - :py:func:`phoebe.backend.plotting.plot_rvsyn`
  4725.        
  4726.        @param twig: the twig/twiglet to use when searching
  4727.        @type twig: str
  4728.        """
  4729.         # Retrieve the obs DataSet and the object it belongs to
  4730.         dsti = self._get_by_search(twig, context='*syn', class_name='*DataSet',
  4731.                                    return_trunk_item=True)
  4732.        
  4733.         ax = kwargs.pop('ax', None)
  4734.         fig = kwargs.pop('fig', None)
  4735.        
  4736.         plotref, axesref, figref = self._handle_plotting_call('plot_syn', dsti, **kwargs)
  4737.  
  4738.         # now call the command to plot
  4739.         if ax is None:
  4740.             output = self.draw_figure(figref, fig=fig, live_plot=True, attach=True)
  4741.         else:
  4742.             output = self.draw_axes(axesref, ax=ax, live_plot=True, attach=True)
  4743.        
  4744.         self.currently_plotted.append((axesref.split('@')[0], plotref.split('@')[0]))
  4745.        
  4746.         #~ return None, (plotref, axesref, figref)
  4747.         return output, (plotref, axesref, figref)
  4748.         #~ return obs
  4749.        
  4750.     def new_plot_residuals(self, twig=None, **kwargs):
  4751.         """
  4752.        Plot the residuals between computed and observed for a given dataset
  4753.        
  4754.        [FUTURE]
  4755.        
  4756.        @param twig: the twig/twiglet to use when searching
  4757.        @type twig: str
  4758.        """
  4759.         # Retrieve the obs DataSet and the object it belongs to
  4760.         dsti = self._get_by_search(twig, context='*syn', class_name='*DataSet',
  4761.                                    return_trunk_item=True)
  4762.        
  4763.         ax = kwargs.pop('ax', None)
  4764.         fig = kwargs.pop('fig', None)
  4765.        
  4766.         plotref, axesref, figref = self._handle_plotting_call('plot_residuals', dsti, **kwargs)
  4767.  
  4768.         # now call the command to plot
  4769.         if ax is None:
  4770.             output = self.draw_figure(figref, fig=fig, live_plot=True, attach=True)
  4771.         else:
  4772.             output = self.draw_axes(axesref, ax=ax, live_plot=True, attach=True)
  4773.        
  4774.         self.currently_plotted.append((axesref.split('@')[0], plotref.split('@')[0]))
  4775.        
  4776.         #~ return None, (plotref, axesref, figref)
  4777.         return output, (plotref, axesref, figref)
  4778.         #~ return obs
  4779.        
  4780.     def new_plot_custom(self, function, args=None, **kwargs):
  4781.         """
  4782.        [FUTURE]
  4783.        
  4784.        **VERY EXPERIMENTAL**
  4785.        Add a custom call through matplotlib and attach it to the bundle
  4786.        
  4787.        accepts function, args, and kwargs
  4788.        
  4789.        :param function: name of the matplotlib function to call, must be an attribute of matplotlib.axes.Axes
  4790.        :type function: str
  4791.        :param args: args to pass to the function
  4792.        """
  4793.         if not hasattr(plt.gca(), function):
  4794.             logger.error("{} not an available function for plt.axes.Axes".format(function))
  4795.            
  4796.         kwargs['function'] = function
  4797.         kwargs['args'] = args
  4798.        
  4799.         ax = kwargs.pop('ax', None)
  4800.         fig = kwargs.pop('fig', None)
  4801.        
  4802.         plotref, axesref, figref = self._handle_plotting_call('plot_custom', None, **kwargs)
  4803.        
  4804.         # now call the command to plot
  4805.         if ax is None:
  4806.             output = self.draw_figure(figref, fig=fig, live_plot=True, attach=True)
  4807.         else:
  4808.             output = self.draw_axes(axesref, ax=ax, live_plot=True, attach=True)
  4809.        
  4810.         self.currently_plotted.append((axesref.split('@')[0], plotref.split('@')[0]))
  4811.        
  4812.         #~ return None, (plotref, axesref, figref)
  4813.         return output, (plotref, axesref, figref)
  4814.         #~ return obs
  4815.  
  4816.     def plot_obs(self, twig=None, **kwargs):
  4817.         """
  4818.        Make a plot of the attached observations (wraps pyplot.errorbar).
  4819.        
  4820.        This function is designed to behave like matplotlib's
  4821.        `plt.errorbar() <http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.errorbar>`_
  4822.        function, with additional options.
  4823.        
  4824.        Thus, all kwargs (there are no args) are passed on to matplotlib's
  4825.        `plt.errorbars() <http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.errorbar>`_,
  4826.        except:
  4827.    
  4828.        - :envvar:`phased=False`: decide whether to phase the data or not.
  4829.          The default is ``True`` when the observations are phased. You can
  4830.          unphase them in that case by setting :envvar:`phased=False`
  4831.          explicitly. This setting is trumped by :envvar:`x_unit` (see
  4832.          below).
  4833.        - :envvar:`repeat=0`: handy if you are actually fitting a phase
  4834.          curve, and you want to repeat the phase curve a couple of times.
  4835.        - :envvar:`x_unit=None`: allows you to override the default units
  4836.          for the x-axis. If you plot times, you can set the unit to any
  4837.          time unit (days (``d``), seconds (``s``), years (``yr``) etc.). If
  4838.          you plot in phase, you can switch from cycle (``cy``) to radians
  4839.          (``rad``). This setting trumps :envvar:`phased`: if the x-unit is
  4840.          of type phase, the data will be phased and if they are time, they
  4841.          will be in time units.
  4842.        - :envvar:`y_unit=None`: allows you to override the default units
  4843.          for the y-axis. Allowable values depend on the type of
  4844.          observations.
  4845.        - :envvar:`ax=plt.gca()`: the axes to plot on. Defaults to current
  4846.          active axes.
  4847.    
  4848.        Some of matplotlib's defaults are overriden. If you do not specify any
  4849.        of the following keywords, they will take the values:
  4850.    
  4851.        - :envvar:`label`: the label for the legend defaults to
  4852.          ``<ref> (obs)``. If you don't want a label for this curve, set
  4853.          :envvar:`label='_nolegend_'`.
  4854.        - :envvar:`yerr`: defaults to the uncertainties from the obs if they
  4855.          are available.
  4856.        
  4857.        The DataSet that is returned is a copy of the original DataSet, but with the
  4858.        units of the columns the same as the ones plotted.
  4859.    
  4860.        **Example usage**
  4861.            
  4862.        Suppose you have the following setup::
  4863.        
  4864.            bundle = phoebe.Bundle()
  4865.            bundle.lc_fromarrays(dataref='mylc', time=np.linspace(0, 1, 100),
  4866.            ...                  flux=np.random.normal(size=100))
  4867.            bundle.rv_fromarrays(dataref='myrv', objref='secondary',
  4868.            ...                  phase=np.linspace(0, 1, 100),
  4869.            ...                  rv=np.random.normal(size=100),
  4870.            ...                  sigma=np.ones(100))
  4871.      
  4872.        Then you can plot these observations with any of the following commands::
  4873.            
  4874.            bundle.plot_obs('mylc')
  4875.            bundle.plot_obs('mylc', phased=True)
  4876.            bundle.plot_obs('mylc', phased=True, repeat=1)
  4877.            bundle.plot_obs('myrv@secondary')
  4878.            bundle.plot_obs('myrv@secondary', fmt='ko-')
  4879.            plt.legend()
  4880.            bundle.plot_obs('myrv@secondary', fmt='ko-', label='my legend label')
  4881.            plt.legend()
  4882.            bundle.plot_obs('myrv@secondary', fmt='ko-', x_unit='s', y_unit='nRsol/d')
  4883.        
  4884.        For more explanations and a list of defaults for each type of
  4885.        observations, see:
  4886.        
  4887.        - :py:func:`plot_lcobs <phoebe.backend.plotting.plot_lcobs>`: for light curve plots
  4888.        - :py:func:`plot_rvobs <phoebe.backend.plotting.plot_rvobs>`: for radial velocity plots
  4889.        
  4890.        The arguments are passed to the appropriate functions in
  4891.        :py:mod:`plotting`.
  4892.        
  4893.        For more info on :envvar:`kwargs`, see the
  4894.        pyplot documentation on `plt.errorbars() <http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.errorbar>`_,
  4895.        
  4896.        :param twig: the twig/twiglet to use when searching
  4897.        :type twig: str
  4898.        :return: observations used for plotting
  4899.        :rtype: DataSet
  4900.        :raises IOError: when observations are not available
  4901.        :raises ValueError: when x/y unit is not allowed
  4902.        :raises ValueError: when y-axis not available (flux, rv...)
  4903.        """
  4904.         # Retrieve the obs DataSet and the object it belongs to
  4905.         dsti = self._get_by_search(twig, context='*obs', class_name='*DataSet',
  4906.                                    return_trunk_item=True, all=True)
  4907.        
  4908.         # It's possible that we need to plot an SED: in that case, we have
  4909.         # more than one dataset but they are all light curves that are grouped
  4910.         if len(dsti) > 1:
  4911.             # Check if they are all lightcurves and collect group name
  4912.             groups = []
  4913.             for idsti in dsti:
  4914.                 correct_category = idsti['item'].get_context()[:-3] == 'lc'
  4915.                 is_grouped = 'group' in idsti['item']
  4916.                 if not correct_category or not is_grouped:
  4917.                     # raise the correct error:
  4918.                     self._get_by_search(twig, context='*obs', class_name='*DataSet',
  4919.                                    return_trunk_item=True)
  4920.                 else:
  4921.                     groups.append(idsti['item']['group'])
  4922.             # check if everything belongs to the same group
  4923.             if not len(set(groups)) == 1:
  4924.                 raise KeyError("More than one SED group found matching twig '{}', please be more specific".format(twig))
  4925.            
  4926.             obj = self.get_object(dsti[0]['label'])
  4927.             context = 'sed' + dsti[0]['item'].get_context()[-3:]
  4928.             ds = dict(ref=groups[0])
  4929.         else:      
  4930.             dsti = dsti[0]
  4931.             ds = dsti['item']
  4932.             obj = self.get_object(dsti['label'])
  4933.             context = ds.get_context()
  4934.        
  4935.         # Do we need automatic/custom xlabel, ylabel and/or title? We need to
  4936.         # pop the kwargs here because they cannot be passed to the lower level
  4937.         # plotting function
  4938.         xlabel = kwargs.pop('xlabel', '_auto_')
  4939.         ylabel = kwargs.pop('ylabel', '_auto_')
  4940.         title = kwargs.pop('title', '_auto_')
  4941.        
  4942.         # Now pass everything to the correct plotting function in the backend
  4943.         kwargs['ref'] = ds['ref']
  4944.         output = getattr(plotting, 'plot_{}'.format(context))(obj, **kwargs)
  4945.        
  4946.         # Now take care of figure decorations
  4947.         fig_decs = output[2]
  4948.         artists = output[0]
  4949.         obs = output[1]
  4950.        
  4951.         # The x-label
  4952.         if xlabel == '_auto_':
  4953.             plt.xlabel(r'{} ({})'.format(fig_decs[0][0], fig_decs[1][0]))
  4954.         elif xlabel:
  4955.             plt.xlabel(xlabel)
  4956.        
  4957.         # The y-label
  4958.         if ylabel == '_auto_':
  4959.             plt.ylabel(r'{} ({})'.format(fig_decs[0][1], fig_decs[1][1]))
  4960.         elif ylabel:
  4961.             plt.ylabel(ylabel)
  4962.        
  4963.         # The plot title
  4964.         if title == '_auto_':
  4965.             plt.title('{}'.format(config.nice_names[context[:-3]]))
  4966.         elif title:
  4967.             plt.title(title)        
  4968.        
  4969.         logger.info("Plotted {} vs {} of {}({})".format(fig_decs[0][0],
  4970.                                    fig_decs[0][1], context, ds['ref']))
  4971.        
  4972.         return obs
  4973.  
  4974.        
  4975.     def plot_syn(self, twig=None, *args, **kwargs):
  4976.         """
  4977.        Plot simulated/computed observations (wraps pyplot.plot).
  4978.        
  4979.        This function is designed to behave like matplotlib's
  4980.        `plt.plot() <http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.plot>`_
  4981.        function, with additional options.
  4982.        
  4983.        Thus, all args and kwargs are passed on to matplotlib's
  4984.        `plt.plot() <http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.plot>`_,
  4985.        except:
  4986.        
  4987.            - :envvar:`ref=0`: the reference of the lc to plot
  4988.            - :envvar:`phased=False`: decide whether to phase the data or not. If
  4989.              there are observations corresponding to :envvar:`ref`, the default
  4990.              is ``True`` when those are phased. The setting is overridden
  4991.              completely by ``x_unit`` (see below).
  4992.            - :envvar:`repeat=0`: handy if you are actually fitting a phase curve,
  4993.              and you want to repeat the phase curve a couple of times.
  4994.            - :envvar:`x_unit=None`: allows you to override the default units for
  4995.              the x-axis. If you plot times, you can set the unit to any time unit
  4996.              (days (``d``), seconds (``s``), years (``yr``) etc.). If you plot
  4997.              in phase, you switch from cycle (``cy``) to radians (``rad``). The
  4998.              :envvar:`x_unit` setting has preference over the :envvar:`phased`
  4999.              flag: if :envvar:`phased=True` but :envvar:`x_unit='s'`, then still
  5000.              the plot will be made in time, not in phase.
  5001.            - :envvar:`y_unit=None`: allows you to override the default units for
  5002.              the y-axis.
  5003.            - :envvar:`scale='obs'`: correct synthetics for ``scale`` and ``offset``
  5004.              from the observations. If ``obs``, they will effectively be scaled to
  5005.              the level/units of the observations (if that was specified in the
  5006.              computations as least). If you want to plot the synthetics in the
  5007.              model units, set ``scale=None``.
  5008.            - :envvar:`ax=plt.gca()`: the axes to plot on. Defaults to current
  5009.              active axes.
  5010.        
  5011.        Some of matplotlib's defaults are overriden. If you do not specify any of
  5012.        the following keywords, they will take the values:
  5013.        
  5014.            - :envvar:`label`: the label for the legend defaults to ``<ref> (syn)``.
  5015.              If you don't want a label for this curve, set :envvar:`label=_nolegend_`.
  5016.        
  5017.            
  5018.        Example usage:
  5019.        
  5020.        >>> mybundle.plot_syn('lc01', 'r-', lw=2) # first light curve added via 'data_fromarrays'
  5021.        >>> mybundle.plot_syn('rv01@primary', 'r-', lw=2, scale=None)
  5022.        
  5023.        >>> mybundle.plot_syn('if01', 'k-') # first interferometry added via 'data_fromarrays'
  5024.        >>> mybundle.plot_syn('if01', 'k-', y='vis2') # first interferometry added via 'data_fromarrays'
  5025.        
  5026.        More information on arguments and keyword arguments:
  5027.        
  5028.        - :py:func:`phoebe.backend.plotting.plot_lcsyn`
  5029.        - :py:func:`phoebe.backend.plotting.plot_rvsyn`
  5030.        
  5031.        @param twig: the twig/twiglet to use when searching
  5032.        @type twig: str
  5033.        """
  5034.         # Retrieve the obs DataSet and the object it belongs to
  5035.         dsti = self._get_by_search(twig, context='*syn', class_name='*DataSet',
  5036.                                    return_trunk_item=True, all=True)
  5037.        
  5038.         # It's possible that we need to plot an SED: in that case, we have
  5039.         # more than one dataset but they are all light curves that are grouped
  5040.         if len(dsti) > 1:
  5041.             # retrieve obs for sed grouping.
  5042.             dstiobs = self._get_by_search(twig, context='*obs', class_name='*DataSet',
  5043.                                    return_trunk_item=True, all=True)
  5044.            
  5045.             # Check if they are all lightcurves and collect group name
  5046.             groups = []
  5047.             if not len(dsti) == len(dstiobs):
  5048.                 raise ValueError("Cannot plot synthetics of SED '{}'".format(twig))
  5049.            
  5050.             for idsti, jdsti in zip(dsti, dstiobs):
  5051.                 correct_category = idsti['item'].get_context()[:-3] == 'lc'
  5052.                 is_grouped = 'group' in jdsti['item']                
  5053.                 if not correct_category or not is_grouped:
  5054.                     # raise the correct error:
  5055.                     self._get_by_search(twig, context='*syn', class_name='*DataSet',
  5056.                                    return_trunk_item=True)
  5057.                 else:
  5058.                     groups.append(jdsti['item']['group'])
  5059.             # check if everything belongs to the same group
  5060.             if not len(set(groups)) == 1:
  5061.                 raise KeyError("More than one SED group found matching twig '{}', please be more specific".format(twig))
  5062.            
  5063.             obj = self.get_object(dsti[0]['label'])
  5064.             context = 'sed' + dsti[0]['item'].get_context()[-3:]
  5065.             ds = dict(ref=groups[0])
  5066.         else:
  5067.             dsti = dsti[0]
  5068.             ds = dsti['item']
  5069.             obj = self.get_object(dsti['label'])
  5070.             context = ds.get_context()
  5071.        
  5072.         # For the pl context we just use the sp context
  5073.         if context[:-3] == 'pl':
  5074.             context = 'sp' + context[-3:]
  5075.             kwargs['category'] = 'pl'
  5076.        
  5077.         # Do we need automatic/custom xlabel, ylabel and/or title? We need to
  5078.         # pop the kwargs here because they cannot be passed to the lower level
  5079.         # plotting function
  5080.         xlabel = kwargs.pop('xlabel', '_auto_')
  5081.         ylabel = kwargs.pop('ylabel', '_auto_')
  5082.         title = kwargs.pop('title', '_auto_')
  5083.        
  5084.         # Now pass everything to the correct plotting function
  5085.         kwargs['ref'] = ds['ref']
  5086.         try:
  5087.             output = getattr(plotting, 'plot_{}'.format(context))(obj, *args, **kwargs)
  5088.         except ValueError:
  5089.             logger.warning("Cannot plot synthetics {}: no calculations found".format(kwargs['ref']))
  5090.             return None
  5091.         except IndexError:
  5092.             logger.warning("Cannot plot synthetics {}: no calculations found".format(kwargs['ref']))
  5093.             return None
  5094.         syn = output[1]
  5095.         fig_decs = output[2]
  5096.        
  5097.         # The x-label
  5098.         if xlabel == '_auto_':
  5099.             plt.xlabel(r'{} ({})'.format(fig_decs[0][0], fig_decs[1][0]))
  5100.         elif xlabel:
  5101.             plt.xlabel(xlabel)
  5102.        
  5103.         # The y-label
  5104.         if ylabel == '_auto_':
  5105.             plt.ylabel(r'{} ({})'.format(fig_decs[0][1], fig_decs[1][1]))
  5106.         elif ylabel:
  5107.             plt.ylabel(ylabel)
  5108.        
  5109.         # The plot title
  5110.         if title == '_auto_':
  5111.             plt.title('{}'.format(config.nice_names[context[:-3]]))
  5112.         elif title:
  5113.             plt.title(title)
  5114.            
  5115.         return syn
  5116.        
  5117.     def plot_residuals(self, twig=None, **kwargs):
  5118.         """
  5119.        Plot the residuals between computed and observed for a given dataset
  5120.        
  5121.        [FUTURE]
  5122.        
  5123.        @param twig: the twig/twiglet to use when searching
  5124.        @type twig: str
  5125.        """
  5126.         # Retrieve the obs DataSet and the object it belongs to
  5127.         dsti = self._get_by_search(twig, context='*syn', class_name='*DataSet',
  5128.                                    return_trunk_item=True)
  5129.         ds = dsti['item']
  5130.         obj = self.get_object(dsti['label'])
  5131.         category = ds.get_context()[:-3]
  5132.        
  5133.         # Do we need automatic/custom xlabel, ylabel and/or title? We need to
  5134.         # pop the kwargs here because they cannot be passed to the lower level
  5135.         # plotting function
  5136.         xlabel = kwargs.pop('xlabel', '_auto_')
  5137.         ylabel = kwargs.pop('ylabel', '_auto_')
  5138.         title = kwargs.pop('title', '_auto_')
  5139.        
  5140.         # Now pass everything to the correct plotting function
  5141.         kwargs['ref'] = ds['ref']
  5142.         try:
  5143.             output = getattr(plotting, 'plot_{}res'.format(category))(obj, **kwargs)
  5144.         except ValueError:
  5145.             logger.warning("Cannot plot residuals {}: no calculations found".format(kwargs['ref']))
  5146.             return None
  5147.         res = output[1]
  5148.         fig_decs = output[2]
  5149.        
  5150.         # The x-label
  5151.         if xlabel == '_auto_':
  5152.             plt.xlabel(r'{} ({})'.format(fig_decs[0][0], fig_decs[1][0]))
  5153.         elif xlabel:
  5154.             plt.xlabel(xlabel)
  5155.        
  5156.         # The y-label
  5157.         if ylabel == '_auto_':
  5158.             plt.ylabel(r'{} ({})'.format(fig_decs[0][1], fig_decs[1][1]))
  5159.         elif ylabel:
  5160.             plt.ylabel(ylabel)
  5161.        
  5162.         # The plot title
  5163.         if title == '_auto_':
  5164.             plt.title('{}'.format(config.nice_names[category]))
  5165.         elif title:
  5166.             plt.title(title)
  5167.            
  5168.         return res
  5169.    
  5170.    
  5171.     def plot_prior(self, twig=None, **kwargs):
  5172.         """
  5173.        Plot a prior.
  5174.        
  5175.        [FUTURE]
  5176.        """
  5177.         prior = self.get_prior(twig)
  5178.         prior.plot(**kwargs)
  5179.        
  5180.        
  5181.    
  5182.     def write_syn(self, dataref, output_file, use_user_units=True, fmt='%.18e',
  5183.                   delimiter=' ', newline='\n', footer=''):
  5184.         """
  5185.        Write synthetic datasets to an ASCII file.
  5186.        
  5187.        By default, this function writes out the model in the same units as the
  5188.        data (:envvar:`use_user_units=True`). It will then also attempt to use
  5189.        the same columns as the observations were given in. It is possible to
  5190.        override these settings and write out the synthetic data in the internal
  5191.        model units of Phoebe. In that case, set :envvar:`use_user_units=False`
  5192.        
  5193.        Extra keyword arguments come from ``np.savetxt``, though headers are not
  5194.        supported as they are auto-generated to give information on column names
  5195.        and units.
  5196.        
  5197.        [FUTURE]
  5198.        
  5199.        Export the contents of a synthetic parameterset to a file
  5200.        
  5201.        :param dataref: the dataref of the dataset to write out
  5202.        :type dataref: str
  5203.        :param output_file: path and filename of the exported file
  5204.        :type output_file: str
  5205.        """
  5206.         # Retrieve synthetics and observations
  5207.         this_syn = self.get_syn(dataref)
  5208.         this_obs = self.get_obs(dataref)
  5209.        
  5210.         # Which columns to write out?
  5211.         user_columns = this_obs['user_columns'] and use_user_units
  5212.         columns = this_obs['user_columns'] if user_columns else this_obs['columns']
  5213.        
  5214.         # Make sure to consider all synthetic columns (e.g. flux might not have
  5215.         # been given in the obs just for simulation purposes)
  5216.         columns = this_syn['columns'] + [col for col in columns if not col in this_syn['columns']]
  5217.        
  5218.         # Filter out column names that do not exist in the synthetics
  5219.         columns = [col for col in columns if col in this_syn and len(this_syn[col])==len(this_syn)]
  5220.        
  5221.         # Which units to use? Start with default ones and override with user
  5222.         # given values
  5223.         units = [this_syn.get_unit(col) if this_syn.has_unit(col) else '--' \
  5224.                                                              for col in columns]
  5225.        
  5226.         if use_user_units and this_obs['user_units']:
  5227.             for col in this_obs['user_units']:
  5228.                 units[columns.index(col)] = this_obs['user_units'][col]
  5229.        
  5230.         # Create header
  5231.         header = [" ".join(columns)]
  5232.         header+= [" ".join(units)]
  5233.         header = "\n".join(header)
  5234.        
  5235.         # Create data
  5236.         data = []
  5237.        
  5238.         # We might need the passband for some conversions
  5239.         try:
  5240.             passband = self.get_value_all('passband@{}'.format(dataref)).values()[0]
  5241.         except KeyError:
  5242.             passband = None
  5243.        
  5244.         for col, unit in zip(columns, units):
  5245.             this_col_data = this_syn[col]
  5246.            
  5247.             # Convert to right units if this column has units and their not
  5248.             # already the correct one
  5249.             if unit != '--':
  5250.                 this_col_unit = this_syn.get_unit(col)
  5251.                 if this_col_unit != unit:
  5252.                     this_col_data = conversions.convert(this_col_unit, unit,
  5253.                                                this_col_data, passband=passband)
  5254.            
  5255.             data.append(this_col_data)
  5256.        
  5257.         # Write out file
  5258.         np.savetxt(output_file, np.column_stack(data), header=header,
  5259.                    footer=footer, comments='# ', fmt=fmt, delimiter=delimiter,
  5260.                    newline=newline)
  5261.        
  5262.         # Report to the user
  5263.         info = ", ".join(['{} ({})'.format(col, unit) for col, unit in zip(columns, units)])
  5264.         logger.info("Wrote columns {} to file '{}'".format(info, output_file))
  5265.        
  5266.        
  5267.     def set_select_time(self,time=None):
  5268.         """
  5269.        Set the highlighted time to be shown in all plots
  5270.        
  5271.        set to None to not draw a highlighted time
  5272.        
  5273.        [FUTURE]
  5274.        
  5275.        @param time: the time to highlight
  5276.        @type time: float or None
  5277.        """
  5278.         self.select_time = time
  5279.         #~ self.system.set_time(time)
  5280.        
  5281.     def new_plot_mesh(self, objref=None, label=None, dataref=None, time=None, phase=None,
  5282.                   select='proj', cmap=None, vmin=None, vmax=None, size=800,
  5283.                   dpi=80, background=None, savefig=False,
  5284.                   with_partial_as_half=False, **kwargs):
  5285.         """
  5286.        Plot the mesh at a particular time or phase.
  5287.        
  5288.        This function has a lot of different use cases, which are all explained
  5289.        below.
  5290.        
  5291.        **Plotting the mesh at an arbitrary time or phase point**
  5292.        
  5293.        If you want to plot the mesh at a particular phase, give
  5294.        :envvar:`time` or :envvar:`phase` as a single float (but not both!):
  5295.        
  5296.            >>> mybundle.plot_mesh(time=0.12)
  5297.            >>> mybundle.plot_mesh(phase=0.25, select='teff')
  5298.            >>> mybundle.plot_mesh(phase=0.25, objref='secondary', dataref='lc01')
  5299.        
  5300.        You can use this function to plot the mesh of the entire system, or just
  5301.        one component (eclipses will show in projected light!). Any scalar
  5302.        quantity that is present in the mesh (effective temperature, logg ...)
  5303.        can be used as color values. For some of these quantities, there are
  5304.        smart defaults for the color maps.
  5305.        
  5306.        
  5307.        **Plotting the current status of the mesh after running computations**
  5308.        
  5309.        If you've just ran :py:func`Bundle.run_compute` for some observations,
  5310.        and you want to see what the mesh looks like after the last calculations
  5311.        have been performed, then this is what you need to do::
  5312.        
  5313.            >>> mybundle.plot_mesh()
  5314.            >>> mybundle.plot_mesh(objref='primary')
  5315.            >>> mybundle.plot_mesh(select='teff')
  5316.            >>> mybundle.plot_mesh(dataref='lc01')
  5317.        
  5318.        Giving a :envvar:`dataref` and/or setting :envvar:`select='proj'` plots
  5319.        the mesh coloured with the projected flux of that dataref.
  5320.        
  5321.        .. warning::
  5322.            
  5323.            1. It is strongly advised only to use this function this way when
  5324.               only one set of observations has been added: otherwise it is not
  5325.               guarenteed that the dataref has been computed for the last time
  5326.               point.
  5327.            
  5328.            2. Although it can sometimes be convenient to use this function
  5329.               plainly without arguments or with just the the dataref after
  5330.               computations, you need to be careful for the dataref that is used
  5331.               to make the plot. The dataref will show up in the logger
  5332.               information.
  5333.        
  5334.        :param objref: object/system label of which you want to plot the mesh.
  5335.         The default means the top level system.
  5336.        :type objref: str
  5337.        :param label: compute label which you want to use to calculate the mesh
  5338.         and its properties. The default means the ``default`` set.
  5339.        :type label: str
  5340.        """
  5341.         if dataref is not None:
  5342.             # Return just one pbdep, we only need the reference and context
  5343.             dsti = self._get_by_search(dataref, context='*dep',
  5344.                             class_name='ParameterSet', all=True, return_trunk_item=True)[0]
  5345.             deps = dsti['item']
  5346.             #~ ref = deps['ref']
  5347.             context = deps.get_context()
  5348.             #~ category = context[:-3]
  5349.             #~ kwargs.setdefault('dataref', ref)
  5350.             kwargs.setdefault('context', context)
  5351.         else:
  5352.             # meh, let's fake the information we need for the plotting call
  5353.             dsti = {'context': 'lcdep', 'ref': 'lc', 'label': self.get_system().get_label()}
  5354.  
  5355.         ax = kwargs.pop('ax', None)
  5356.         fig = kwargs.pop('fig', None)
  5357.        
  5358.         kwargs['computelabel'] = label
  5359.         kwargs['objref'] = objref
  5360.         plotref, axesref, figref = self._handle_plotting_call('plot_mesh', dsti, **kwargs)
  5361.  
  5362.         # now call the command to plot
  5363.         if ax is None:
  5364.             output = self.draw_figure(figref, fig=fig, live_plot=True, attach=True)
  5365.         else:
  5366.             output = self.draw_axes(axesref, ax=ax, live_plot=True, attach=True)
  5367.        
  5368.         self.currently_plotted.append((axesref.split('@')[0], plotref.split('@')[0]))
  5369.        
  5370.         #~ return None, (plotref, axesref, figref)
  5371.         return output, (plotref, axesref, figref)
  5372.         #~ return obs
  5373.        
  5374.        
  5375.     def plot_mesh(self, objref=None, label=None, dataref=None, time=None, phase=None,
  5376.                   select='teff', cmap=None, vmin=None, vmax=None, size=800,
  5377.                   dpi=80, background=None, savefig=False,
  5378.                   with_partial_as_half=False, **kwargs):
  5379.         """
  5380.        Plot the mesh at a particular time or phase.
  5381.        
  5382.        This function has a lot of different use cases, which are all explained
  5383.        below.
  5384.        
  5385.        **Plotting the mesh at an arbitrary time or phase point**
  5386.        
  5387.        If you want to plot the mesh at a particular phase, give
  5388.        :envvar:`time` or :envvar:`phase` as a single float (but not both!):
  5389.        
  5390.            >>> mybundle.plot_mesh(time=0.12)
  5391.            >>> mybundle.plot_mesh(phase=0.25, select='teff')
  5392.            >>> mybundle.plot_mesh(phase=0.25, objref='secondary', dataref='lc01')
  5393.        
  5394.        You can use this function to plot the mesh of the entire system, or just
  5395.        one component (eclipses will show in projected light!). Any scalar
  5396.        quantity that is present in the mesh (effective temperature, logg ...)
  5397.        can be used as color values. For some of these quantities, there are
  5398.        smart defaults for the color maps.
  5399.                
  5400.        **Plotting the current status of the mesh after running computations**
  5401.        
  5402.        If you've just ran :py:func:`Bundle.run_compute` for some observations,
  5403.        and you want to see what the mesh looks like after the last calculations
  5404.        have been performed, then this is what you need to do::
  5405.        
  5406.            >>> mybundle.plot_mesh()
  5407.            >>> mybundle.plot_mesh(objref='primary')
  5408.            >>> mybundle.plot_mesh(select='teff')
  5409.            >>> mybundle.plot_mesh(dataref='lc01')
  5410.        
  5411.        Giving a :envvar:`dataref` and/or setting :envvar:`select='proj'` plots
  5412.        the mesh coloured with the projected flux of that dataref.
  5413.        
  5414.        .. warning::
  5415.            
  5416.            1. If you have no observations attached, you cannot use ``select='proj'``
  5417.               because there will be no flux computed. You can then only plot
  5418.               mesh quantities like ``teff``, ``logg`` etc.
  5419.            
  5420.            2. It is strongly advised only to use this function this way when
  5421.               only one set of observations has been added: otherwise it is not
  5422.               guarenteed that the dataref has been computed for the last time
  5423.               point.
  5424.            
  5425.            3. Although it can sometimes be convenient to use this function
  5426.               plainly without arguments you need to be careful for the
  5427.               :envvar:`dataref` that is used to make the plot. The dataref
  5428.               will show up in the logger information.
  5429.        
  5430.        Extra keyword arguments and the return values are explained
  5431.        in :py:func:`phoebe.backend.observatory.image`.
  5432.        
  5433.        :param objref: object/system label of which you want to plot the mesh.
  5434.         The default means the top level system.
  5435.        :type objref: str
  5436.        :param label: compute label which you want to use to calculate the mesh
  5437.         and its properties. The default means the ``default`` set.
  5438.        :type label: str
  5439.        :return: figure properties, artist properties, patch collection
  5440.        :rtype: dict, dict, matplotlib patch collection
  5441.        """
  5442.         if dataref is not None:
  5443.             # Return just one pbdep, we only need the reference and context
  5444.             deps = self._get_by_search(dataref, context='*dep',
  5445.                             class_name='ParameterSet', all=True)[0]
  5446.             ref = deps['ref']
  5447.             context = deps.get_context()
  5448.             category = context[:-3]
  5449.             kwargs.setdefault('ref', ref)
  5450.             kwargs.setdefault('context', context)
  5451.         else:
  5452.             category = 'lc'
  5453.             kwargs.setdefault('ref', '__bol')
  5454.            
  5455.         # Set the configuration to the correct time/phase, but only when one
  5456.         # (and only one) of them is given.
  5457.         if time is not None and phase is not None:
  5458.             raise ValueError("You cannot set both time and phase to zero, please choose one")
  5459.         elif phase is not None:
  5460.             period, t0, shift = self.get_system().get_period()
  5461.             if np.isinf(period):
  5462.                 time = t0
  5463.             else:
  5464.                 time = phase * period + t0
  5465.        
  5466.         # Observe the system with the right computations
  5467.         if time is not None:
  5468.             options = self.get_compute(label, create_default=True).copy()
  5469.             observatory.observe(self.get_system(), [time], lc=category=='lc',
  5470.                                 rv=category=='rv', sp=category=='sp',
  5471.                                 pl=category=='pl', ifm=category=='if',
  5472.                                 save_result=False, **options)
  5473.        
  5474.         # Get the object and make an image.
  5475.         try:
  5476.             out = self.get_object(objref).plot2D(select=select, cmap=cmap, vmin=vmin,
  5477.                      vmax=vmax, size=size, dpi=dpi, background=background,
  5478.                      savefig=savefig, with_partial_as_half=with_partial_as_half,
  5479.                      **kwargs)
  5480.         except ValueError:
  5481.             # Most likely, the user did not add any data, did not run_compute
  5482.             # before or did not give a time and phase explicitly here...
  5483.             if time is None and not len(self.get_system().mesh):
  5484.                 raise ValueError("Insufficient information to plot mesh: please specify a phase or time")
  5485.             else:
  5486.                 raise
  5487.         return out
  5488.        
  5489.        
  5490.     def plot_orbitview(self,mplfig=None,mplaxes=None,orbitviewoptions=None):
  5491.         """
  5492.        [FUTURE]
  5493.        """
  5494.         if mplfig is None:
  5495.             if mplaxes is None: # no axes provided
  5496.                 axes = plt.axes()
  5497.             else: # use provided axes
  5498.                 axes = mplaxes
  5499.            
  5500.         else:
  5501.             axes = mplfig.add_subplot(111)
  5502.            
  5503.         po = self.get_orbitview() if orbitviewoptions is None else orbitviewoptions
  5504.            
  5505.         if po['data_times']:
  5506.             computeparams = parameters.ParameterSet(context='compute') #assume default (auto)
  5507.             observatory.extract_times_and_refs(self.get_system(),computeparams)
  5508.             times_data = computeparams['time']
  5509.         else:
  5510.             times_data = []
  5511.        
  5512.         orbits = self._get_by_search(context='orbit', kind='ParameterSet', all=True)
  5513.         periods = np.array([o.get_value('period') for o in orbits])
  5514.         top_orbit = orbits[periods.argmax()]
  5515.         bottom_orbit = orbits[periods.argmin()]
  5516.         if po['times'] == 'auto':
  5517.             times_full = np.arange(top_orbit.get_value('t0'),top_orbit.get_value('t0')+top_orbit.get_value('period'),bottom_orbit.get_value('period')/20.)
  5518.         else:
  5519.             times_full = po['times']
  5520.            
  5521.         for obj in self.get_system().get_bodies():
  5522.             orbits, components = obj.get_orbits()
  5523.            
  5524.             for times,marker in zip([times_full,times_data],['-','x']):
  5525.                 if len(times):
  5526.                     pos, vel, t = keplerorbit.get_barycentric_hierarchical_orbit(times, orbits, components)
  5527.                
  5528.                     positions = ['x','y','z']
  5529.                     velocities = ['vx','vy','vz']
  5530.                     if po['xaxis'] in positions:
  5531.                         x = pos[positions.index(po['xaxis'])]
  5532.                     elif po['xaxis'] in velocities:
  5533.                         x = vel[positions.index(po['xaxis'])]
  5534.                     else: # time
  5535.                         x = t
  5536.                     if po['yaxis'] in positions:
  5537.                         y = pos[positions.index(po['yaxis'])]
  5538.                     elif po['yaxis'] in positions:
  5539.                         y = vel[positions.index(po['yaxis'])]
  5540.                     else: # time
  5541.                         y = t
  5542.                        
  5543.                     axes.plot(x,y,marker)
  5544.         axes.set_xlabel(po['xaxis'])
  5545.         axes.set_ylabel(po['yaxis'])
  5546.        
  5547.     #}
  5548.    
  5549.     #{Server
  5550.     def server_job_status(self):
  5551.         """
  5552.        check whether the job is finished on the server and ready for
  5553.        the new bundle to be reloaded
  5554.        
  5555.        [FUTURE]
  5556.        """
  5557.         server = self.get_server(self.lock['server'])
  5558.         script = self.lock['script']
  5559.        
  5560.         if server is not None:
  5561.             return server.check_script_status(script)
  5562.         else:
  5563.             return 'no job'
  5564.        
  5565.     def server_cancel(self):
  5566.         """
  5567.        unlock the bundle and remove information about the running job
  5568.        NOTE: this will not actually kill the job on the server, but will remove files that have already been created
  5569.        
  5570.        [FUTURE]
  5571.        """
  5572.         server = self.get_server(self.lock['server'])
  5573.         script = self.lock['script']
  5574.         lock_files = self.lock['files']
  5575.        
  5576.         mount_dir = server.server_ps.get_value('mount_dir')
  5577.        
  5578.         if server.check_mount():
  5579.             logger.info('cleaning files in {}'.format(mount_dir))
  5580.             for fname in lock_files:
  5581.                 try:
  5582.                     os.remove(os.path.join(mount_dir,fname))
  5583.                 except:
  5584.                     pass
  5585.            
  5586.         self.lock = {'locked': False, 'server': '', 'script': '', 'command': '', 'files': [], 'rfile': None}
  5587.            
  5588.     def server_loop(self):
  5589.         """
  5590.        enter a loop to check the status of a job on a server and load
  5591.        the results when finished.
  5592.        
  5593.        You can always kill this loop and reenter (even after saving and loading the bundle)        
  5594.        
  5595.        [FUTURE]
  5596.        """
  5597.         server = self.get_server(self.lock['server'])
  5598.         script = self.lock['script']
  5599.        
  5600.         servername = server.server_ps.get_value('label')
  5601.        
  5602.         while True:
  5603.             logger.info('checking {} server'.format(servername))
  5604.             if self.server_job_status()=='complete':
  5605.                 self.server_get_results()
  5606.                 return
  5607.             sleep(5)
  5608.    
  5609.     def server_get_results(self):
  5610.         """
  5611.        reload the bundle from a finished job on the server
  5612.        
  5613.        [FUTURE]
  5614.        """
  5615.        
  5616.         server = self.get_server(self.lock['server'])
  5617.         script = self.lock['script']
  5618.         lock_files = self.lock['files']
  5619.        
  5620.         if self.server_job_status()!='complete':
  5621.             return False
  5622.  
  5623.         mount_dir = server.server_ps.get_value('mount_dir')
  5624.        
  5625.         logger.info('retrieving updated bundle from {}'.format(mount_dir))
  5626.         self_new = load(os.path.join(mount_dir,self.lock['rfile']))
  5627.  
  5628.         # reassign self_new -> self
  5629.         # (no one said it had to be pretty)
  5630.         for attr in ["__class__","__dict__"]:
  5631.                 setattr(self, attr, getattr(self_new, attr))
  5632.  
  5633.         # alternatively we could just set the system, but
  5634.         # then we lose flexibility in adjusting things outside
  5635.         # of bundle.get_system()
  5636.         #~ bundle.set_system(bundle_new.get_system()) # note: anything changed outside system will be lost
  5637.  
  5638.         # cleanup files
  5639.         logger.info('cleaning files in {}'.format(mount_dir))
  5640.         for fname in lock_files:
  5641.             os.remove(os.path.join(mount_dir,fname))
  5642.         os.remove(os.path.join(mount_dir,'%s.status' % script))
  5643.         os.remove(os.path.join(mount_dir,'%s.log' % script))
  5644.        
  5645.         return
  5646.    
  5647.     #}
  5648.        
  5649.     #{ Attached Signals
  5650.     def attach_signal(self,param,funcname,callbackfunc,*args):
  5651.         """
  5652.        Attaches a callback signal and keeps list of attached signals
  5653.        
  5654.        [FUTURE]
  5655.        
  5656.        @param param: the object to attach something to
  5657.        @type param: some class
  5658.        @param funcname: name of the class's method to add a callback to
  5659.        @type funcname: str
  5660.        @param callbackfunc: the callback function
  5661.        @type callbackfunc: callable function
  5662.        """
  5663.         system = self.get_system()
  5664.        
  5665.         # for some reason system.signals is becoming an 'instance' and
  5666.         # is giving the error that it is not iterable
  5667.         # for now this will get around that, until we can find the source of the problem
  5668.         if system is not None and not isinstance(system.signals, dict):
  5669.             #~ print "*system.signals not dict"
  5670.             system.signals = {}
  5671.         callbacks.attach_signal(param,funcname,callbackfunc,*args)
  5672.         self.attached_signals.append(param)
  5673.        
  5674.     def purge_signals(self,signals=None):
  5675.         """
  5676.        Purges all signals created through Bundle.attach_signal()
  5677.        
  5678.        [FUTURE]
  5679.        
  5680.        @param signals: a list of signals to purge
  5681.        @type signals: list
  5682.        """
  5683.        
  5684.         if signals is None:
  5685.             signals = self.attached_signals
  5686.         #~ print "* purge_signals", signals
  5687.         for param in signals:
  5688.             callbacks.purge_signals(param)
  5689.         if signals == self.attached_signals:
  5690.             self.attached_signals = []
  5691.         elif signals == self.attached_signals_system:
  5692.             self.attached_signals_system = []
  5693.         #~ elif signals == self.attached_signals + self.attached_signals_system:
  5694.             #~ self.attached_signals = []
  5695.             #~ self.attached_signals_system = []
  5696.            
  5697.     def attach_system_signals(self):
  5698.         """
  5699.        this function attaches signals to:
  5700.            - set_value (for all parameters)
  5701.            - load_data/remove_data
  5702.            - enable_obs/disable_obs/adjust_obs
  5703.            
  5704.        when any of these signals are emitted, _on_param_changed will be called
  5705.        
  5706.        [FUTURE]
  5707.        """
  5708.  
  5709.         self.purge_signals(self.attached_signals_system) # this will also clear the list
  5710.         # get_system_structure is not implemented yet
  5711.        
  5712.         # these might already be attached?
  5713.         self.attach_signal(self,'data_fromfile',self._on_param_changed)
  5714.         self.attach_signal(self,'remove_data',self._on_param_changed)
  5715.         self.attach_signal(self,'enable_data',self._on_param_changed)
  5716.         self.attach_signal(self,'disable_data',self._on_param_changed)
  5717.         #~ self.attach_signal(self,'adjust_obs',self._on_param_changed)
  5718.         #~ self.attach_signal(self,'restore_version',self._on_param_changed)
  5719.        
  5720.     def _attach_set_value_signals(self,ps):
  5721.         """
  5722.        [FUTURE]
  5723.        """
  5724.         for key in ps.keys():
  5725.             param = ps.get_parameter(key)
  5726.             self.attach_signal(param,'set_value',self._on_param_changed,ps)
  5727.             self.attached_signals_system.append(param)
  5728.    
  5729.     def _on_param_changed(self,param,ps=None):
  5730.         """
  5731.        this function is called whenever a signal is emitted that was attached
  5732.        in attach_system_signals
  5733.        
  5734.        [FUTURE]
  5735.        """
  5736.         system = self.get_system()
  5737.        
  5738.         if ps is not None and ps.context == 'compute': # then we only want to set the changed compute to uptodate
  5739.             if system.uptodate is not False and self.get_compute(system.uptodate) == ps:
  5740.                 system.uptodate=False
  5741.         else:
  5742.             system.uptodate = False
  5743.            
  5744.     #}
  5745.    
  5746.     #{ Saving
  5747.     def copy(self):
  5748.         """
  5749.        Copy this instance.
  5750.        
  5751.        [FUTURE]
  5752.        """
  5753.         return copy.deepcopy(self)
  5754.    
  5755.     def save(self, filename):
  5756.         """
  5757.        Save the bundle into a json-formatted ascii file
  5758.        
  5759.        @param filename: path to save the bundle
  5760.        @type filename: str
  5761.        """
  5762.         self._save_json(filename)
  5763.    
  5764.     def save_pickle(self,filename=None,purge_signals=True,save_usersettings=False):
  5765.         """
  5766.        Save a class to an file.
  5767.        Will automatically purge all signals attached through bundle
  5768.        
  5769.        [FUTURE]
  5770.        
  5771.        @param filename: path to save the bundle (or None to use same as last save)
  5772.        @type filename: str
  5773.        """
  5774.         if filename is None and self.filename is None:
  5775.             logger.error('save failed: need to provide filename')
  5776.             return
  5777.            
  5778.         if filename is None:
  5779.             filename = self.filename
  5780.         else:
  5781.             self.filename = filename
  5782.        
  5783.         if purge_signals:
  5784.             #~ self_copy = self.copy()
  5785.             self.purge_signals()
  5786.            
  5787.         # remove user settings
  5788.         if not save_usersettings:
  5789.             settings = self.usersettings
  5790.             self.usersettings = None
  5791.            
  5792.         trunk = self.trunk
  5793.         self._purge_trunk()
  5794.        
  5795.         # pickle
  5796.         ff = open(filename,'w')
  5797.         pickle.dump(self,ff)
  5798.         ff.close()  
  5799.         logger.info('Saved bundle to file {} (pickle)'.format(filename))
  5800.        
  5801.         # reset user settings
  5802.         if not save_usersettings:
  5803.             self.usersettings = settings
  5804.            
  5805.         self.trunk = trunk
  5806.        
  5807.         # call set_system so that purged (internal) signals are reconnected
  5808.         self.set_system(self.get_system())
  5809.    
  5810.     #}
  5811.    
  5812.     def check(self, return_errors=False):
  5813.         """
  5814.        Check if a system is OK.
  5815.        
  5816.        What 'OK' is, depends on a lot of stuff. Typically this function can be
  5817.        used to do some sanity checks when fitting, such that impossible systems
  5818.        can be avoided.
  5819.        
  5820.        We check if a parameter (or all) has a finite log likelihood.
  5821.        
  5822.        If ``qualifier=None``, all parameters with priors are checked. If any is
  5823.        found to be outside of bounds, ``False`` is returned. Any other parameter,
  5824.        even the ones without priors, are checked for their limits. If any is
  5825.        outside of the limits, ``False`` is returned. If no parameters are
  5826.        outside of their priors and/or limits, ``True`` is returned.
  5827.        
  5828.        We preprocess the system first.
  5829.        
  5830.        [FUTURE]
  5831.        """
  5832.        
  5833.         return self.get_system().check(return_errors=return_errors)
  5834.  
  5835.            
  5836.        
  5837.     def updateLD(self):
  5838.         """
  5839.        Update limbdarkening coefficients according to local quantities.
  5840.        
  5841.        [FUTURE]
  5842.        """
  5843.         atm_types = self.get_parameter('atm', all=True).values()
  5844.         ld_coeffs = self.get_parameter('ld_coeffs', all=True).values()
  5845.         for atm_type, ld_coeff in zip(atm_types, ld_coeffs):
  5846.             ld_coeff.set_value(atm_type)
  5847.    
  5848.     def set_beaming(self, on=True):
  5849.         """
  5850.        Include/exclude the boosting effect.
  5851.        
  5852.        [FUTURE]
  5853.        """
  5854.         self.set_value('beaming', on, apply_to='all')
  5855.        
  5856.     def set_ltt(self, on=True):
  5857.         """
  5858.        Include/exclude light-time travel effects.
  5859.        
  5860.        [FUTURE]
  5861.        """
  5862.         self.set_value('ltt', on, apply_to='all')
  5863.    
  5864.     def set_heating(self, on=True):
  5865.         """
  5866.        Include/exclude heating effects.
  5867.        
  5868.        [FUTURE]
  5869.        """
  5870.         self.set_value('heating', on, apply_to='all')
  5871.        
  5872.     def set_reflection(self, on=True):
  5873.         """
  5874.        Include/exclude reflection effects.
  5875.        
  5876.        [FUTURE]
  5877.        """
  5878.         self.set_value('refl', on, apply_to='all')
  5879.    
  5880.     def set_gray_scattering(self, on=True):
  5881.         """
  5882.        Force gray scattering.
  5883.        
  5884.        [FUTURE]
  5885.        """
  5886.         system = self.get_system()
  5887.         if on:
  5888.             system.add_preprocess('gray_scattering')
  5889.         else:
  5890.             system.remove_preprocess('gray_scattering')
  5891.            
  5892.    
  5893. def load(filename, load_usersettings=True):
  5894.     """
  5895.    Load a class from a file.
  5896.    
  5897.    [FUTURE]
  5898.    
  5899.    @param filename: filename of a Body or Bundle pickle file
  5900.    @type filename: str
  5901.    @param load_usersettings: flag to load custom user settings
  5902.    @type load_usersettings: bool
  5903.    @return: Bundle saved in file
  5904.    @rtype: Bundle
  5905.    """
  5906.     file_type, contents = guess_filetype(filename)
  5907.    
  5908.     if file_type == 'pickle_bundle':
  5909.         bundle = contents
  5910.     elif file_type == 'pickle_body':
  5911.         bundle = Bundle(system=contents)
  5912.     elif file_type == 'phoebe_legacy':
  5913.         bundle = Bundle(system=contents[0])
  5914.         bundle.add_compute(contents[1])
  5915.     elif file_type == 'wd':
  5916.         bundle = Bundle(system=contents)
  5917.    
  5918.     # Load this users settings into the bundle
  5919.     if load_usersettings:
  5920.         bundle.set_usersettings()
  5921.    
  5922.     logger.info("Loaded contents of {}-file from {} into a Bundle".format(file_type, filename))
  5923.    
  5924.     # That's it!
  5925.     return bundle
  5926.  
  5927.  
  5928. def guess_filetype(filename):
  5929.     """
  5930.    Guess what kind of file `filename` is and return the contents if possible.
  5931.    
  5932.    Possibilities and return values:
  5933.    
  5934.    1. Phoebe2.0 pickled Body: envvar:`file_type='pickle_body', contents=<phoebe.backend.Body>`
  5935.    2. Phoebe2.0 pickled Bundle: envvar:`file_type='pickle_bundle', contents=<phoebe.frontend.Bundle>`
  5936.    3. Phoebe Legacy file: envvar:`file_type='phoebe_legacy', contents=<phoebe.frontend.Body>`
  5937.    4. Wilson-Devinney lcin file: envvar:`file_type='wd', contents=<phoebe.frontend.Body>`
  5938.    5. Other existing loadable pickle file: envvar:`file_type='unknown', contents=<custom_class>`
  5939.    6. Other existing file: envvar:`file_type='unknown', contents=None`
  5940.    7. Nonexisting file: IOError
  5941.    
  5942.    [FUTURE]
  5943.    """
  5944.     file_type = 'unknown'
  5945.     contents = None
  5946.    
  5947.     # First: is this thing a file?
  5948.     if os.path.isfile(filename):
  5949.        
  5950.         # If it is a file, try to unpickle it:  
  5951.         try:
  5952.             with open(filename, 'r') as open_file:
  5953.                 contents = pickle.load(open_file)
  5954.             file_type = 'pickle'
  5955.         except AttributeError:
  5956.             logger.info("Probably old pickle file")
  5957.         except:
  5958.             pass
  5959.            
  5960.         # If the file is not a pickle file or json, it could be a Phoebe legacy file?
  5961.         if contents is None:
  5962.            
  5963.             try:
  5964.                 contents = parsers.legacy_to_phoebe2(filename)
  5965.                 file_type = 'phoebe_legacy'
  5966.             except IOError:
  5967.                 pass
  5968.             except TypeError:
  5969.                 pass
  5970.        
  5971.         # If it's not a pickle file nor a legacy file, is it a WD lcin file?
  5972.         if contents is None:
  5973.            
  5974.             try:
  5975.                 contents = parsers.wd_to_phoebe(filename, mesh='marching',
  5976.                                                 create_body=True)
  5977.                 file_type = 'wd'
  5978.             except:
  5979.                 contents = None
  5980.        
  5981.         # If we unpickled it, check if it is a Body(Bag) or a bundle.
  5982.         if file_type == 'pickle' and isinstance(contents, universe.Body):
  5983.             file_type += '_body'
  5984.            
  5985.         # If it a bundle, we don't need to initiate it anymore
  5986.         elif file_type == 'pickle' and isinstance(contents, Bundle):
  5987.             file_type += '_bundle'
  5988.        
  5989.         # If we don't know the filetype by now, we don't know it at all
  5990.     else:
  5991.         raise IOError(("Cannot guess type of file {}: "
  5992.                       "it does not exist").format(filename))
  5993.    
  5994.     return file_type, contents
  5995.  
  5996.  
  5997. def info():
  5998.     frames = {}
  5999.     for par in parameters.defs.defs:
  6000.         for frame in par['frame']:
  6001.             if not frame in ['phoebe','wd']: continue
  6002.             if frame not in frames:
  6003.                 if isinstance(par['context'],list):
  6004.                     frames[frame]+= par['context']
  6005.                 else:
  6006.                     frames[frame] = [par['context']]
  6007.             elif not par['context'] in frames[frame]:
  6008.                 if isinstance(par['context'],list):
  6009.                     frames[frame]+= par['context']
  6010.                 else:
  6011.                     frames[frame].append(par['context'])
  6012.     contexts = sorted(list(set(frames['phoebe'])))
  6013.     # Remove some experimental stuff
  6014.     ignore = 'analytical:binary', 'derived', 'gui', 'logger', 'plotting:axes',\
  6015.              'plotting:mesh', 'plotting:orbit', 'plotting:plot',\
  6016.              'plotting:selector', 'point_source', 'pssyn', 'server', 'root',\
  6017.              'circ_orbit'
  6018.     for ign in ignore:
  6019.         if ign in contexts:
  6020.             contexts.remove(ign)
  6021.    
  6022.     # Sort according to category/physical
  6023.     contexts_obs = []
  6024.     contexts_syn = []
  6025.     contexts_dep = []
  6026.     contexts_phy = []
  6027.     contexts_cpt = []
  6028.    
  6029.     for context in contexts:
  6030.         if context[-3:] == 'obs':
  6031.             contexts_obs.append(context)
  6032.         elif context[-3:] == 'syn':
  6033.             contexts_syn.append(context)
  6034.         elif context[-3:] == 'dep':
  6035.             contexts_dep.append(context)
  6036.         elif context.split(':')[0] in ['compute', 'fitting', 'mpi']:
  6037.             contexts_cpt.append(context)
  6038.         else:
  6039.             contexts_phy.append(context)
  6040.    
  6041.    
  6042.     def emphasize(text):
  6043.         return '\033[1m\033[4m' + text + '\033[m'
  6044.     def italicize(text):
  6045.         return '\x1B[3m' + text + '\033[m'
  6046.    
  6047.     summary = ['List of ParameterSets:\n========================\n']
  6048.     summary.append("Create a ParameterSet via:\n   >>> myps = phoebe.ParameterSet('<name>')")
  6049.     summary.append("Print with:\n   >>> print(myps)")
  6050.     summary.append("Info on a parameter:\n   >>> myps.info('<qualifier>')")
  6051.     summary.append('\n')
  6052.    
  6053.     summary.append(emphasize('Physical contexts:'))
  6054.    
  6055.     # first things with subcontexts
  6056.     current_context = None
  6057.     for context in contexts_phy:
  6058.         if ':' in context:
  6059.             this_context = context.split(':')[0]
  6060.             if this_context != current_context:
  6061.                 if current_context is not None:
  6062.                     summary[-1] = "\n".join(textwrap.wrap(summary[-1][:-2], initial_indent='',
  6063.                                                           subsequent_indent=' '*23,
  6064.                                                           width=79))
  6065.                 current_context = this_context
  6066.                 summary.append('{:25s}'.format(italicize(this_context))+' --> ')
  6067.             summary[-1] += context + ', '
  6068.    
  6069.     # then without
  6070.     summary.append('{:25s}'.format(italicize('other'))+' --> ')
  6071.     for context in contexts_phy:
  6072.         if not ':' in context:
  6073.             summary[-1] += context + ", "
  6074.     summary[-1] = "\n".join(textwrap.wrap(summary[-1][:-2], initial_indent='',
  6075.                                                           subsequent_indent=' '*23,
  6076.                                                           width=79))
  6077.    
  6078.     # Obs, deps and syns
  6079.     summary.append('\n')
  6080.     summary.append(emphasize('Observables:'))
  6081.     summary.append("\n".join(textwrap.wrap(italicize('dep: ')+", ".join(contexts_dep),
  6082.                                                    initial_indent='',
  6083.                                                    subsequent_indent=' '*5,
  6084.                                                    width=79)))
  6085.     summary.append("\n".join(textwrap.wrap(italicize('obs: ')+", ".join(contexts_obs),
  6086.                                                    initial_indent='',
  6087.                                                    subsequent_indent=' '*5,
  6088.                                                    width=79)))
  6089.     summary.append("\n".join(textwrap.wrap(italicize('syn: ')+", ".join(contexts_syn),
  6090.                                                    initial_indent='',
  6091.                                                    subsequent_indent=' '*5,
  6092.                                                    width=79)))
  6093.    
  6094.     # Computables:
  6095.     summary.append('\n')
  6096.     summary.append(emphasize('Computations and numerics:'))
  6097.     summary.append("\n".join(textwrap.wrap(", ".join(contexts_cpt),
  6098.                                                    initial_indent='',
  6099.                                                    subsequent_indent=' ',
  6100.                                                    width=79)))
  6101.    
  6102.     print("\n".join(summary))
  6103.     #frames_contexts = []
  6104.     #for frame in sorted(frames.keys()):
  6105.         #for context in sorted(frames[frame]):
  6106.             #if frame+context in frames_contexts: continue
  6107.             #frames_contexts.append(frame+context)
  6108.             #parset = parameters.ParameterSet(frame=frame,context=context)
  6109.             #if 'label' in parset:
  6110.                 #parset['label'] = 'mylbl'
  6111.             #if 'ref' in parset:
  6112.                 #parset['ref'] = 'myref'
  6113.             #if 'c1label' in parset:
  6114.                 #parset['c1label'] = 'primlbl'
  6115.             #if 'c2label' in parset:
  6116.                 #parset['c2label'] = 'secnlbl'
  6117.            
  6118.             #print parset
Add Comment
Please, Sign In to add comment