<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Datasets
Datasets tell PHOEBE how and at what times to compute the model.  In some cases these will include the actual observational data, and in other cases may only include the times at which you want to compute a synthetic model.
Adding a dataset - even if it doesn't contain any observational data - is required in order to compute a synthetic model (which will be described in the Compute Tutorial).
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).
Step1:  Adding a Dataset from Arrays
To add a dataset, you need to provide the function in
phoebe.parameters.dataset for the particular type of data you're dealing with, as well
as any of your "observed" arrays.
The current available methods include
Step2:  Without Observations
The simplest case of adding a dataset is when you do not have observational "data" and only want to compute a synthetic model.  Here all you need to provide is an array of times and information about the type of data and how to compute it.
Here we'll do just that - we'll add an orbit dataset which will track the positions and velocities of both our 'primary' and 'secondary' stars (by their component tags) at each of the provided times.
Unlike other datasets, the mesh and orb dataset cannot accept actual observations, so there is no times parameter, only the compute_times and compute_phases parameters.  For more details on these, see the Advanced
Step3:  Here we used phoebe.linspace.  This is essentially just a shortcut to np.linspace, but using nparray to allow these generated arrays to be serialized and stored easier within the Bundle.  Other nparray constructor functions available at the top-level of PHOEBE include
Step4:  You may notice that add_dataset does take some time to complete.  In the background, the passband is being loaded (when applicable) and many parameters are created and attached to the Bundle.
If you do not provide a list of component(s), they will be assumed for you based on the dataset method.  LCs (light curves) and meshes can only attach at the system level (component=None), for instance, whereas RVs and ORBs can attach for each star.
Step5:  Here we added an RV dataset and can see that it was automatically created for both stars in our system.  Under-the-hood, another entry is created for component='_default'.  The default parameters hold the values that will be replicated if a new component is added to the system in the future.  In order to see these hidden parameters, you need to pass check_default=False to any filter-type call (and note that '_default' is no longer exposed when calling .components).  Also note that for set_value_all, this is automatically set to False.
Since we did not explicitly state that we only wanted the primary and secondary components, the time array on '_default' is filled as well.  If we were then to add a tertiary component, its RVs would automatically be computed because of this replicated time array.
Step6:  With Observations
Loading datasets with observations is (nearly) as simple.  
Passing arrays to any of the dataset columns will apply it to all of the same components in which the time will be applied (see the 'Without Observations' section above for more details).  This make perfect sense for fluxes in light curves where the time and flux arrays are both at the system level
Step7:  For datasets which attach to individual components, however, this isn't always the desired behavior.
For a single-lined RV where we only attach to one component, everything is as expected.
Step8:  However, for a double-lined RV we probably don't want to do the following
Step9:  Instead we want to pass different arrays to the 'rvs@primary' and 'rvs@secondary'.  This can be done by explicitly stating the components in a dictionary sent to that argument
<ASSISTANT_TASK:>
Python Code:
#!pip install -I "phoebe>=2.4,<2.5"
import phoebe
from phoebe import u # units
logger = phoebe.logger()
b = phoebe.default_binary()
Explanation: Datasets
Datasets tell PHOEBE how and at what times to compute the model.  In some cases these will include the actual observational data, and in other cases may only include the times at which you want to compute a synthetic model.
Adding a dataset - even if it doesn't contain any observational data - is required in order to compute a synthetic model (which will be described in the Compute Tutorial).
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
phoebe.list_available_datasets()
Explanation: Adding a Dataset from Arrays
To add a dataset, you need to provide the function in
phoebe.parameters.dataset for the particular type of data you're dealing with, as well
as any of your "observed" arrays.
The current available methods include:
lc light curves (tutorial)
rv radial velocity curves (tutorial)
lp spectral line profiles (tutorial)
orb orbit/positional data (tutorial)
mesh discretized mesh of stars (tutorial)
which can always be listed via phoebe.list_available_datasets
End of explanation
b.add_dataset(phoebe.dataset.orb, 
              compute_times=phoebe.linspace(0,10,20), 
              dataset='orb01', 
              component=['primary', 'secondary'])
Explanation: Without Observations
The simplest case of adding a dataset is when you do not have observational "data" and only want to compute a synthetic model.  Here all you need to provide is an array of times and information about the type of data and how to compute it.
Here we'll do just that - we'll add an orbit dataset which will track the positions and velocities of both our 'primary' and 'secondary' stars (by their component tags) at each of the provided times.
Unlike other datasets, the mesh and orb dataset cannot accept actual observations, so there is no times parameter, only the compute_times and compute_phases parameters.  For more details on these, see the Advanced: Compute Times & Phases tutorial.
End of explanation
b.add_dataset('orb', 
              compute_times=phoebe.linspace(0,10,20), 
              component=['primary', 'secondary'], 
              dataset='orb01', 
              overwrite=True)
Explanation: Here we used phoebe.linspace.  This is essentially just a shortcut to np.linspace, but using nparray to allow these generated arrays to be serialized and stored easier within the Bundle.  Other nparray constructor functions available at the top-level of PHOEBE include:
phoebe.arange
phoebe.invspace
phoebe.linspace
phoebe.logspace
phoebe.geomspace
Any nparray object, list, or numpy array is acceptable as input to FloatArrayParameters.
b.add_dataset can either take a function or the name of a function in phoebe.parameters.dataset as its first argument.  The following line would do the same thing (and we'll pass overwrite=True to avoid the error of overwriting dataset='orb01').
End of explanation
b.add_dataset('rv', times=phoebe.linspace(0,10,20), dataset='rv01')
print(b.filter(qualifier='times', dataset='rv01').components)
Explanation: You may notice that add_dataset does take some time to complete.  In the background, the passband is being loaded (when applicable) and many parameters are created and attached to the Bundle.
If you do not provide a list of component(s), they will be assumed for you based on the dataset method.  LCs (light curves) and meshes can only attach at the system level (component=None), for instance, whereas RVs and ORBs can attach for each star.
End of explanation
print(b.filter(qualifier='times', dataset='rv01', check_default=False).components)
print(b.get('times@_default@rv01', check_default=False))
Explanation: Here we added an RV dataset and can see that it was automatically created for both stars in our system.  Under-the-hood, another entry is created for component='_default'.  The default parameters hold the values that will be replicated if a new component is added to the system in the future.  In order to see these hidden parameters, you need to pass check_default=False to any filter-type call (and note that '_default' is no longer exposed when calling .components).  Also note that for set_value_all, this is automatically set to False.
Since we did not explicitly state that we only wanted the primary and secondary components, the time array on '_default' is filled as well.  If we were then to add a tertiary component, its RVs would automatically be computed because of this replicated time array.
End of explanation
b.add_dataset('lc', times=[0,1], fluxes=[1,0.5], dataset='lc01')
print(b.get_parameter(qualifier='fluxes', dataset='lc01', context='dataset'))
Explanation: With Observations
Loading datasets with observations is (nearly) as simple.  
Passing arrays to any of the dataset columns will apply it to all of the same components in which the time will be applied (see the 'Without Observations' section above for more details).  This make perfect sense for fluxes in light curves where the time and flux arrays are both at the system level:
End of explanation
b.add_dataset('rv', 
              times=[0,1], 
              rvs=[-3,3], 
              component='primary', 
              dataset='rv01', 
              overwrite=True)
print(b.get_parameter(qualifier='rvs', dataset='rv01', context='dataset'))
Explanation: For datasets which attach to individual components, however, this isn't always the desired behavior.
For a single-lined RV where we only attach to one component, everything is as expected.
End of explanation
b.add_dataset('rv', 
              times=[0,0.5,1], 
              rvs=[-3,3], 
              dataset='rv02')
print(b.filter(qualifier='rvs', dataset='rv02', context='dataset'))
Explanation: However, for a double-lined RV we probably don't want to do the following:
End of explanation
b.add_dataset('rv', 
              times=[0,0.5,1], 
              rvs={'primary': [-3,3], 'secondary': [4,-4]}, 
              dataset='rv02',
              overwrite=True)
print(b.filter(qualifier='rvs', dataset='rv02', context='dataset'))
Explanation: Instead we want to pass different arrays to the 'rvs@primary' and 'rvs@secondary'.  This can be done by explicitly stating the components in a dictionary sent to that argument:
End of explanation
<END_TASK>