<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 General Concepts
Step1:  Let's get started with some basic imports
Step2:  If running in IPython notebooks, you may see a "ShimWarning" depending on the version of Jupyter you are using - this is safe to ignore.
PHOEBE 2 uses constants defined in the IAU 2015 Resolution which conflict with the constants defined in astropy.  As a result, you'll see the warnings as phoebe.u and phoebe.c "hijacks" the values in astropy.units and astropy.constants.
Whenever providing units, please make sure to use phoebe.u instead of astropy.units, otherwise the conversions may be inconsistent.
Logger
Before starting any script, it is a good habit to initialize a logger and define which levels of information you want printed to the command line (clevel) and dumped to a file (flevel).  A convenience function is provided at the top-level via phoebe.logger to initialize the logger with any desired level.
The levels from most to least information are
Step3:  All of these arguments are optional and will default to clevel='WARNING' if not provided.  There is therefore no need to provide a filename if you don't provide a value for flevel.
So with this logger, anything with INFO, WARNING, ERROR, or CRITICAL levels will be printed to the screen.  All messages of any level will be written to a file named 'tutorial.log' in the current directory.
Note
Step4:  This object holds all the parameters and their respective values.  We'll see in this tutorial and the next tutorial on constraints how to search through these parameters and set their values.
Step5:  Next, we need to define our datasets via b.add_dataset.  This will be the topic of the following tutorial on datasets.  Datasets store observations to compare against the model, but also tell PHOEBE at what times to compute the forward model and store passband-dependent options.
Step6:  We'll then want to run our forward model to create a synthetic model of the observables defined by these datasets using b.run_compute, which will be the topic of the computing observables tutorial.  The compute options tell PHOEBE how to create a synthetic model from the system parameters for the added datasets.
Step7:  We can access the value of any parameter, including the arrays in the synthetic model just generated.  To export arrays to a file, we could call b.export_arrays
Step8:  We can then plot the resulting model with b.plot, which will be covered in the plotting tutorial.
Step9:  And then lastly, if we wanted to solve the inverse problem and "fit" parameters to observational data, we may want to add distributions to our system so that we can run estimators, optimizers, or samplers.
Default Binary Bundle
For this tutorial, let's start over and discuss this b object in more detail and how to access and change the values of the input parameters.
Everything for our system will be stored in this single Python object that we call the Bundle which we'll call b (short for bundle).
Step10:  The Bundle is just a collection of Parameter objects along with some callable methods.  Here we can see that the default binary Bundle consists of over 100 individual parameters.
Step11:  If we want to view or edit a Parameter in the Bundle, we first need to know how to access it.  Each Parameter object has a number of tags which can be used to filter (similar to a database query).  When filtering the Bundle, a ParameterSet is returned - this is essentially just a subset of the Parameters in the Bundle and can be further filtered until eventually accessing a single Parameter.
Step12:  Here we filtered on the context tag for all Parameters with context='compute' (i.e. the options for computing a model).  If we want to see all the available options for this tag in the Bundle, we can use the plural form of the tag as a property on the Bundle or any ParameterSet.
Step13:  Although there is no strict hierarchy or order to the tags, it can be helpful to think of the context tag as the top-level tag and is often very helpful to filter by the appropriate context first.
Other tags currently include
Step14:  This then tells us what can be used to filter further.
Step15:  The qualifier tag is the shorthand name of the Parameter itself.  If you don't know what you're looking for, it is often useful to list all the qualifiers of the Bundle or a given ParameterSet.
Step16:  Now that we know the options for the qualifier within this filter, we can choose to filter on one of those.  Let's look filter by the 'ntriangles' qualifier.
Step17:  Once we filter far enough to get to a single Parameter, we can use get_parameter to return the Parameter object itself (instead of a ParameterSet).
Step18:  As a shortcut, get_parameter also takes filtering keywords.  So the above line is also equivalent to the following
Step19:  Each Parameter object contains several keys that provide information about that Parameter.  The keys "description" and "value" are always included, with additional keys available depending on the type of Parameter.
Step20:  We can also see a top-level view of the filtered parameters and descriptions (note
Step21:  Since the Parameter for ntriangles is a FloatParameter, it also includes a key for the allowable limits.
Step22:  In this case, we're looking at the Parameter called ntriangles with the component tag set to 'primary'.  This Parameter therefore defines how many triangles should be created when creating the mesh for the star named 'primary'.  By default, this is set to 1500 triangles, with allowable values above 100.
If we wanted a finer mesh, we could change the value.
Step23:  If we choose the distortion_method qualifier from that same ParameterSet, we'll see that it has a few different keys in addition to description and value.
Step24:  Since the distortion_method Parameter is a ChoiceParameter, it contains a key for the allowable choices.
Step25:  We can only set a value if it is contained within this list - if you attempt to set a non-valid value, an error will be raised.
Step26:  Parameter types include
Step27:  However, this dictionary-style twig access will never return a ParameterSet with a single Parameter, instead it will return the Parameter itself.  This can be seen in the different output between the following two lines
Step28:  Because of this, this dictionary-style twig access can also set the value directly
Step29:  And can even provide direct access to the keys/attributes of the Parameter (value, description, limits, etc)
Step30:  As with the tags, you can call .twigs on any ParameterSet to see the "smallest unique twigs" of the contained Parameters
<ASSISTANT_TASK:>
Python Code:
#!pip install -I "phoebe>=2.4,<2.5"
Explanation: General Concepts: The PHOEBE Bundle
HOW TO RUN THIS FILE: if you're running this in a Jupyter notebook or Google Colab session, you can click on a cell and then shift+Enter to run the cell and automatically select the next cell.  Alt+Enter will run a cell and create a new cell below it.  Ctrl+Enter will run a cell but keep it selected.  To restart from scratch, restart the kernel/runtime.
All of these tutorials assume basic comfort with Python in general - particularly with the concepts of lists, dictionaries, and objects as well as basic comfort with using the numpy and matplotlib packages. This tutorial introduces all the general concepts of accessing parameters within the Bundle.
Setup
Let's first make sure we have the latest version of PHOEBE 2.4 installed (uncomment this line if running in an online notebook session such as colab).
End of explanation
import phoebe
from phoebe import u # units
Explanation: Let's get started with some basic imports:
End of explanation
logger = phoebe.logger(clevel='WARNING')
Explanation: If running in IPython notebooks, you may see a "ShimWarning" depending on the version of Jupyter you are using - this is safe to ignore.
PHOEBE 2 uses constants defined in the IAU 2015 Resolution which conflict with the constants defined in astropy.  As a result, you'll see the warnings as phoebe.u and phoebe.c "hijacks" the values in astropy.units and astropy.constants.
Whenever providing units, please make sure to use phoebe.u instead of astropy.units, otherwise the conversions may be inconsistent.
Logger
Before starting any script, it is a good habit to initialize a logger and define which levels of information you want printed to the command line (clevel) and dumped to a file (flevel).  A convenience function is provided at the top-level via phoebe.logger to initialize the logger with any desired level.
The levels from most to least information are:
DEBUG
INFO
WARNING
ERROR
CRITICAL
End of explanation
b = phoebe.default_binary()
Explanation: All of these arguments are optional and will default to clevel='WARNING' if not provided.  There is therefore no need to provide a filename if you don't provide a value for flevel.
So with this logger, anything with INFO, WARNING, ERROR, or CRITICAL levels will be printed to the screen.  All messages of any level will be written to a file named 'tutorial.log' in the current directory.
Note: the logger messages are not included in the outputs shown below.
Overview
As a quick overview of what's to come, here is a quick preview of some of the steps used when modeling a binary system with PHOEBE.  Each of these steps will be explained in more detail throughout these tutorials.
First we need to create our binary system.  For the sake of most of these tutorials, we'll use the default detached binary available through the phoebe.default_binary constructor.
End of explanation
b.set_value(qualifier='teff', component='primary', value=6500)
Explanation: This object holds all the parameters and their respective values.  We'll see in this tutorial and the next tutorial on constraints how to search through these parameters and set their values.
End of explanation
b.add_dataset('lc', compute_times=phoebe.linspace(0,1,101))
Explanation: Next, we need to define our datasets via b.add_dataset.  This will be the topic of the following tutorial on datasets.  Datasets store observations to compare against the model, but also tell PHOEBE at what times to compute the forward model and store passband-dependent options.
End of explanation
b.run_compute()
Explanation: We'll then want to run our forward model to create a synthetic model of the observables defined by these datasets using b.run_compute, which will be the topic of the computing observables tutorial.  The compute options tell PHOEBE how to create a synthetic model from the system parameters for the added datasets.
End of explanation
print(b.get_value(qualifier='fluxes', context='model'))
Explanation: We can access the value of any parameter, including the arrays in the synthetic model just generated.  To export arrays to a file, we could call b.export_arrays
End of explanation
afig, mplfig = b.plot(show=True)
Explanation: We can then plot the resulting model with b.plot, which will be covered in the plotting tutorial.
End of explanation
b = phoebe.default_binary()
Explanation: And then lastly, if we wanted to solve the inverse problem and "fit" parameters to observational data, we may want to add distributions to our system so that we can run estimators, optimizers, or samplers.
Default Binary Bundle
For this tutorial, let's start over and discuss this b object in more detail and how to access and change the values of the input parameters.
Everything for our system will be stored in this single Python object that we call the Bundle which we'll call b (short for bundle).
End of explanation
b
Explanation: The Bundle is just a collection of Parameter objects along with some callable methods.  Here we can see that the default binary Bundle consists of over 100 individual parameters.
End of explanation
b.filter(context='compute')
Explanation: If we want to view or edit a Parameter in the Bundle, we first need to know how to access it.  Each Parameter object has a number of tags which can be used to filter (similar to a database query).  When filtering the Bundle, a ParameterSet is returned - this is essentially just a subset of the Parameters in the Bundle and can be further filtered until eventually accessing a single Parameter.
End of explanation
b.contexts
Explanation: Here we filtered on the context tag for all Parameters with context='compute' (i.e. the options for computing a model).  If we want to see all the available options for this tag in the Bundle, we can use the plural form of the tag as a property on the Bundle or any ParameterSet.
End of explanation
b.filter(context='compute').components
Explanation: Although there is no strict hierarchy or order to the tags, it can be helpful to think of the context tag as the top-level tag and is often very helpful to filter by the appropriate context first.
Other tags currently include:
* kind
* figure
* component
* feature
* dataset
* distribution
* compute
* model
* solver
* solution
* time
* qualifier
Accessing the plural form of the tag as an attribute also works on a filtered ParameterSet
End of explanation
b.filter(context='compute').filter(component='primary')
Explanation: This then tells us what can be used to filter further.
End of explanation
b.filter(context='compute', component='primary').qualifiers
Explanation: The qualifier tag is the shorthand name of the Parameter itself.  If you don't know what you're looking for, it is often useful to list all the qualifiers of the Bundle or a given ParameterSet.
End of explanation
b.filter(context='compute', component='primary', qualifier='ntriangles')
Explanation: Now that we know the options for the qualifier within this filter, we can choose to filter on one of those.  Let's look filter by the 'ntriangles' qualifier.
End of explanation
b.filter(context='compute', component='primary', qualifier='ntriangles').get_parameter()
Explanation: Once we filter far enough to get to a single Parameter, we can use get_parameter to return the Parameter object itself (instead of a ParameterSet).
End of explanation
b.get_parameter(context='compute', component='primary', qualifier='ntriangles')
Explanation: As a shortcut, get_parameter also takes filtering keywords.  So the above line is also equivalent to the following:
End of explanation
b.get_parameter(context='compute', component='primary', qualifier='ntriangles').get_value()
b.get_parameter(context='compute', component='primary', qualifier='ntriangles').get_description()
Explanation: Each Parameter object contains several keys that provide information about that Parameter.  The keys "description" and "value" are always included, with additional keys available depending on the type of Parameter.
End of explanation
print(b.filter(context='compute', component='primary').info)
Explanation: We can also see a top-level view of the filtered parameters and descriptions (note: the syntax with @ symbols will be explained further in the section on twigs below.
End of explanation
b.get_parameter(context='compute', component='primary', qualifier='ntriangles').get_limits()
Explanation: Since the Parameter for ntriangles is a FloatParameter, it also includes a key for the allowable limits.
End of explanation
b.get_parameter(context='compute', component='primary', qualifier='ntriangles').set_value(2000)
b.get_parameter(context='compute', component='primary', qualifier='ntriangles')
Explanation: In this case, we're looking at the Parameter called ntriangles with the component tag set to 'primary'.  This Parameter therefore defines how many triangles should be created when creating the mesh for the star named 'primary'.  By default, this is set to 1500 triangles, with allowable values above 100.
If we wanted a finer mesh, we could change the value.
End of explanation
b.get_parameter(context='compute', component='primary', qualifier='distortion_method')
b.get_parameter(context='compute', component='primary', qualifier='distortion_method').get_value()
b.get_parameter(context='compute', component='primary', qualifier='distortion_method').get_description()
Explanation: If we choose the distortion_method qualifier from that same ParameterSet, we'll see that it has a few different keys in addition to description and value.
End of explanation
b.get_parameter(context='compute', component='primary', qualifier='distortion_method').get_choices()
Explanation: Since the distortion_method Parameter is a ChoiceParameter, it contains a key for the allowable choices.
End of explanation
try:
    b.get_parameter(context='compute', component='primary', qualifier='distortion_method').set_value('blah')
except Exception as e:
    print(e)
b.get_parameter(context='compute', component='primary', qualifier='distortion_method').set_value('rotstar')
b.get_parameter(context='compute', component='primary', qualifier='distortion_method').get_value()
Explanation: We can only set a value if it is contained within this list - if you attempt to set a non-valid value, an error will be raised.
End of explanation
b.filter(context='compute', component='primary')
b['primary@compute']
b['compute@primary']
Explanation: Parameter types include:
* IntParameter
* FloatParameter
* FloatArrayParameter
* BoolParameter
* StringParameter
* ChoiceParameter
* SelectParameter
* DictParameter
* ConstraintParameter
* DistributionParameter
* HierarchyParameter
* UnitParameter
* JobParameter
these Parameter types and their available options are all described in great detail in Advanced: Parameter Types
Twigs
As a shortcut to needing to filter by all these tags, the Bundle and ParameterSets can be filtered through what we call "twigs" (as in a Bundle of twigs).  These are essentially a single string-representation of the tags, separated by @ symbols.
This is very useful as a shorthand when working in an interactive Python console, but somewhat obfuscates the names of the tags and can make it difficult if you use them in a script and make changes earlier in the script.
For example, the following lines give identical results:
End of explanation
b.filter(context='compute', component='primary', qualifier='distortion_method')
b['distortion_method@primary@compute']
Explanation: However, this dictionary-style twig access will never return a ParameterSet with a single Parameter, instead it will return the Parameter itself.  This can be seen in the different output between the following two lines:
End of explanation
b['distortion_method@primary@compute'] = 'roche'
print(b['distortion_method@primary@compute'])
Explanation: Because of this, this dictionary-style twig access can also set the value directly:
End of explanation
print(b['value@distortion_method@primary@compute'])
print(b['description@distortion_method@primary@compute'])
Explanation: And can even provide direct access to the keys/attributes of the Parameter (value, description, limits, etc)
End of explanation
b['compute'].twigs
Explanation: As with the tags, you can call .twigs on any ParameterSet to see the "smallest unique twigs" of the contained Parameters
End of explanation
<END_TASK>