<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Advanced
Step1:  As always, let's do imports and create a new Bundle.  See Building a System for more details.
Step2:  Overriding Computation Times
If compute_times is not empty (by either providing compute_times or compute_phases), the provided value will be used to compute the model instead of those in the times parameter.
In the case of a mesh dataset or orbit dataset, observations cannot be attached to the dataset, so a times parameter does not exist.  In this case compute_times or compute_phases will always be used.
Step3:  compute_times (when not empty) overrides the value of times when computing the model.  However, passing times as a keyword argument to run_compute will take precedence over either - and override the computed times across all enabled datasets.
Step4:  Phase-Time Conversion
In addition to the ability to provide compute_times, we can alternatively provide compute_phases.  These two parameters are linked via a constraint (see the constraints tutorial), with compute_phases constrained by default.
Step5:  Essentially, this constraint does the same thing as b.to_phase or b.to_time, using the appropriate t0 according to compute_phases_t0 from the top-level orbit in the hierarchy.
Step6:  In order to provide compute_phases instead of compute_times, we must call b.flip_constraint.
Step7:  Note that under the hood, PHOEBE always works in time-space, meaning it is the constrained value of compute_times that is being passed under-the-hood.
Also note that if directly passing compute_phases to b.add_dataset, the constraint will be flipped on our behalf.  We would then need to flip the constraint in order to provide compute_times instead.
Finally, it is important to make the distinction that this is not adding support for observations in phases.  If we have an old light curve that is only available in phase, we still must convert these to times manually (or via b.to_time).  This restriction is intentional
Step8:  In the case of times, this will automatically interpolate in phase-space if the provided time is outside the range of the referenced times array.  If you have a logger enabled with at least the 'warning' level, this will raise a warning and state the phases at which the interpolation will be completed.
Step9:  Determining & Plotting Residuals
One particularly useful case for interpolating is to compare a model (perhaps computed in phase-space) to a dataset with a large number of datapoints.  We can do this directly by calling compute_residuals, which will handle any necessary interpolation and compare the dependent variable between the dataset and models.
Note that if there are more than one dataset or model attached to the bundle, it may be necessary to pass dataset and/or model (or filter in advanced and call compute_residuals on the filtered ParameterSet.
To see this in action, we'll first create a "fake" observational dataset, add some noise, recompute the model using compute_phases, and then calculate the residuals.
Step10:  If we plot the dataset and model, we see that the model was only computed for one cycle, whereas the dataset extends further in time.
Step11:  But we can also plot the residuals.  Here, calculate_residuals is called internally, interpolating in phase-space, and then plotted in time-space.  See the options for y in the plot API docs for more details.
<ASSISTANT_TASK:>
Python Code:
!pip install -I "phoebe>=2.2,<2.3"
Explanation: Advanced: compute_times & compute_phases
Setup
Let's first make sure we have the latest version of PHOEBE 2.2 installed. (You can comment out this line if you don't use pip for your installation or don't want to update to the latest release).
End of explanation
import phoebe
from phoebe import u # units
b = phoebe.default_binary()
b.add_dataset('lc', times=phoebe.linspace(0,10,101), dataset='lc01')
Explanation: As always, let's do imports and create a new Bundle.  See Building a System for more details.
End of explanation
print(b.filter(qualifier=['times', 'compute_times'], context='dataset'))
b.set_value('compute_times', phoebe.linspace(0,3,11))
b.run_compute()
print("dataset times: {}\ndataset compute_times: {}\nmodel times: {}".format(
    b.get_value('times', context='dataset'),
    b.get_value('compute_times', context='dataset'),
    b.get_value('times', context='model')
    ))
Explanation: Overriding Computation Times
If compute_times is not empty (by either providing compute_times or compute_phases), the provided value will be used to compute the model instead of those in the times parameter.
In the case of a mesh dataset or orbit dataset, observations cannot be attached to the dataset, so a times parameter does not exist.  In this case compute_times or compute_phases will always be used.
End of explanation
b.run_compute(times=[0,0.2])
print("dataset times: {}\ndataset compute_times: {}\nmodel times: {}".format(
    b.get_value('times', context='dataset'),
    b.get_value('compute_times', context='dataset'),
    b.get_value('times', context='model')
    ))
b.run_compute()
print("dataset times: {}\ndataset compute_times: {}\nmodel times: {}".format(
    b.get_value('times', context='dataset'),
    b.get_value('compute_times', context='dataset'),
    b.get_value('times', context='model')
    ))
Explanation: compute_times (when not empty) overrides the value of times when computing the model.  However, passing times as a keyword argument to run_compute will take precedence over either - and override the computed times across all enabled datasets.
End of explanation
print(b.filter(qualifier=['times', 'compute_times', 'compute_phases', 'compute_phases_t0'], context='dataset'))
Explanation: Phase-Time Conversion
In addition to the ability to provide compute_times, we can alternatively provide compute_phases.  These two parameters are linked via a constraint (see the constraints tutorial), with compute_phases constrained by default.
End of explanation
print(b.get_constraint('compute_phases'))
print(b.get_parameter('compute_phases_t0').choices)
Explanation: Essentially, this constraint does the same thing as b.to_phase or b.to_time, using the appropriate t0 according to compute_phases_t0 from the top-level orbit in the hierarchy.
End of explanation
b.flip_constraint('compute_phases', solve_for='compute_times')
b.set_value('compute_phases', phoebe.linspace(0,1,11))
print(b.filter(qualifier=['times', 'compute_times', 'compute_phases', 'compute_phases_t0'], context='dataset'))
Explanation: In order to provide compute_phases instead of compute_times, we must call b.flip_constraint.
End of explanation
b.get_parameter('fluxes', context='model').get_value()
b.get_parameter('fluxes', context='model').interp_value(times=1.0)
b.get_parameter('fluxes', context='model').interp_value(times=phoebe.linspace(0,3,101))
Explanation: Note that under the hood, PHOEBE always works in time-space, meaning it is the constrained value of compute_times that is being passed under-the-hood.
Also note that if directly passing compute_phases to b.add_dataset, the constraint will be flipped on our behalf.  We would then need to flip the constraint in order to provide compute_times instead.
Finally, it is important to make the distinction that this is not adding support for observations in phases.  If we have an old light curve that is only available in phase, we still must convert these to times manually (or via b.to_time).  This restriction is intentional: we do not want the mapping between phase and time to change as the ephemeris is changed or fitted, rather we want to try to map from phase to time using the ephemeris that was originally used when the dataset was recorded (if possible, or the best possible guess).
Interpolating the Model
Whether or not we used compute_times/compute_phases or not, it is sometimes useful to be able to interpolate on the resulting model.  In the case where we provided compute_times/compute_phases to "down-sample" from a large dataset, this can be particularly useful.
We can call interp_value on any FloatArrayParameter.
End of explanation
b.get_parameter('fluxes', context='model').interp_value(times=5)
Explanation: In the case of times, this will automatically interpolate in phase-space if the provided time is outside the range of the referenced times array.  If you have a logger enabled with at least the 'warning' level, this will raise a warning and state the phases at which the interpolation will be completed.
End of explanation
b.add_dataset('lc', 
              times=phoebe.linspace(0,10,1000),
              dataset='lc01',
              overwrite=True)
b.run_compute(irrad_method='none')
fluxes = b.get_value('fluxes', context='model')
b.set_value('fluxes', context='dataset', value=fluxes)
b.flip_constraint('compute_phases', solve_for='compute_times')
b.set_value('compute_phases', phoebe.linspace(0,1,101))
b.set_value('teff', component='primary', value=5950)
b.run_compute(irrad_method='none')
print(len(b.get_value('fluxes', context='dataset')), len(b.get_value('fluxes', context='model')))
b.calculate_residuals()
Explanation: Determining & Plotting Residuals
One particularly useful case for interpolating is to compare a model (perhaps computed in phase-space) to a dataset with a large number of datapoints.  We can do this directly by calling compute_residuals, which will handle any necessary interpolation and compare the dependent variable between the dataset and models.
Note that if there are more than one dataset or model attached to the bundle, it may be necessary to pass dataset and/or model (or filter in advanced and call compute_residuals on the filtered ParameterSet.
To see this in action, we'll first create a "fake" observational dataset, add some noise, recompute the model using compute_phases, and then calculate the residuals.
End of explanation
afig, mplfig = b.plot(show=True)
Explanation: If we plot the dataset and model, we see that the model was only computed for one cycle, whereas the dataset extends further in time.
End of explanation
afig, mplfig = b.plot(y='residuals', show=True)
Explanation: But we can also plot the residuals.  Here, calculate_residuals is called internally, interpolating in phase-space, and then plotted in time-space.  See the options for y in the plot API docs for more details.
End of explanation
<END_TASK>