#!pip install -I "phoebe>=2.4,<2.5"

"""
Explanation: Advanced: solver_times
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
import numpy as np
import matplotlib.pyplot as plt

"""
Explanation: Let's get started with some basic imports
End of explanation
"""


b = phoebe.default_binary()
b.add_dataset('lc', times=phoebe.linspace(0,5,1001))
b.add_compute('ellc', compute='ellc01')
b.set_value_all('ld_mode', 'lookup')
b.run_compute('ellc01')

times = b.get_value('times@model')
fluxes = b.get_value('fluxes@model')
sigmas = np.ones_like(times) * 0.01

b = phoebe.default_binary()
b.add_dataset('lc', compute_phases=phoebe.linspace(0,1,101),
              times=times, fluxes=fluxes, sigmas=sigmas)
b.add_compute('ellc', compute='ellc01')
b.set_value_all('ld_mode', 'lookup')
b.add_solver('optimizer.nelder_mead', compute='ellc01', fit_parameters=['teff'], solver='nm_solver')

"""
Explanation: And then we'll build a synthetic "dataset" and initialize a new bundle with those data
End of explanation
"""


print(b.filter(qualifier='solver_times'))

print(b.get_parameter(qualifier='solver_times', dataset='lc01').choices)

"""
Explanation: solver_times parameter and options
End of explanation
"""


help(b.parse_solver_times)

"""
Explanation: The logic for solver times is generally only used internally within b.run_solver (for optimizers and samplers which require a forward-model to be computed).  However, it is useful (in order to diagnose any issues, for example) to be able to see how the combination of solver_times, times, compute_times/compute_phases, mask_enabled, and mask_phases will be interpretted within PHOEBE during b.run_solver.
See also:
* Advanced: mask_phases
* Advanced: Compute Times & Phases
To access the underlying times that would be used, we can call b.parse_solver_times.  Let's first look at the docstring (also available from the link above):
End of explanation
"""


#logger = phoebe.logger('info')

"""
Explanation: Additionally, we can pass solver to b.run_compute to have the forward-model computed as it would be within the solver itself (this just calls run_compute with the compute option referenced by the solver and with the parsed solver_times).
Below we'll go through each of the scenarios listed above and demonstrate how that logic changes the times at which the forward model will be computed within b.run_solver (with the cost-function interpolating between the resulting forward-model and the observations as necessary).
The messages regarding the internal choice of logic for solver_times will be exposed at the 'info' level of the logger.  We'll leave that off here to avoid the noise of the logger messages from run_compute calls, but you can uncomment the following line to see those messages.
End of explanation
"""


b.set_value('solver_times', 'times')
b.set_value('compute_phases', phoebe.linspace(0,1,101))
b.set_value('mask_enabled', False)
b.set_value('dperdt', 0.0)

dataset_times = b.get_value('times', context='dataset')
_ = plt.plot(times, np.ones_like(times)*1, 'k.')

compute_times = b.get_value('compute_times', context='dataset')
_ = plt.plot(compute_times, np.ones_like(compute_times)*2, 'b.')

solver_times = b.parse_solver_times()
print(solver_times)
_ = plt.plot(solver_times['lc01'], np.ones_like(solver_times['lc01'])*3, 'g.')

b.run_compute(solver='nm_solver')
_ = b.plot(show=True)

"""
Explanation: solver_times = 'times'
without phase_mask enabled
End of explanation
"""


b.set_value('solver_times', 'times')
b.set_value('compute_phases', phoebe.linspace(0,1,101))
b.set_value('mask_enabled', True)
b.set_value('mask_phases', [(-0.1, 0.1), (0.45,0.55)])
b.set_value('dperdt', 0.0)

dataset_times = b.get_value('times', context='dataset')
_ = plt.plot(times, np.ones_like(times)*1, 'k.')

compute_times = b.get_value('compute_times', context='dataset')
_ = plt.plot(compute_times, np.ones_like(compute_times)*2, 'b.')

solver_times = b.parse_solver_times()
_ = plt.plot(solver_times['lc01'], np.ones_like(solver_times['lc01'])*3, 'g.')

b.run_compute(solver='nm_solver')
_ = b.plot(show=True)

"""
Explanation: with phase_mask enabled
End of explanation
"""


b.set_value('solver_times', 'compute_times')
b.set_value('compute_phases', phoebe.linspace(0,1,101))
b.set_value('mask_enabled', False)
b.set_value('dperdt', 0.0)

dataset_times = b.get_value('times', context='dataset')
_ = plt.plot(times, np.ones_like(times)*1, 'k.')

compute_times = b.get_value('compute_times', context='dataset')
_ = plt.plot(compute_times, np.ones_like(compute_times)*2, 'b.')

solver_times = b.parse_solver_times()
_ = plt.plot(solver_times['lc01'], np.ones_like(solver_times['lc01'])*3, 'g.')

b.run_compute(solver='nm_solver')
_ = b.plot(show=True)

"""
Explanation: solver_times = 'compute_times'
without phase_mask enabled
End of explanation
"""


b.set_value('solver_times', 'compute_times')
b.set_value('compute_phases', phoebe.linspace(0,1,101))
b.set_value('mask_enabled', True)
b.set_value('mask_phases', [(-0.1, 0.1), (0.45,0.55)])
b.set_value('dperdt', 0.0)

dataset_times = b.get_value('times', context='dataset')
_ = plt.plot(times, np.ones_like(times)*1, 'k.')

compute_times = b.get_value('compute_times', context='dataset')
_ = plt.plot(compute_times, np.ones_like(compute_times)*2, 'b.')

solver_times = b.parse_solver_times()
_ = plt.plot(solver_times['lc01'], np.ones_like(solver_times['lc01'])*3, 'g.')

b.run_compute(solver='nm_solver')
_ = b.plot(show=True)

"""
Explanation: with phase_mask enabled and time-independent hierarchy
End of explanation
"""


b.set_value('solver_times', 'compute_times')
b.set_value('compute_phases', phoebe.linspace(0,1,101))
b.set_value('mask_enabled', True)
b.set_value('mask_phases', [(-0.1, 0.1), (0.45,0.55)])
b.set_value('dperdt', 0.1)

print(b.hierarchy.is_time_dependent())

"""
Explanation: with phase_mask enabled and time-dependent hierarchy
End of explanation
"""


print(b.run_checks_solver())

"""
Explanation: In the case where we have a time-dependent system b.run_solver will fail with an error from b.run_checks_solver if compute_times does not fully encompass the dataset times.
End of explanation
"""


b.flip_constraint('compute_times', solve_for='compute_phases')
b.set_value('compute_times', phoebe.linspace(0,5,501))

print(b.run_checks_solver())

dataset_times = b.get_value('times', context='dataset')
_ = plt.plot(times, np.ones_like(times)*1, 'k.')

compute_times = b.get_value('compute_times', context='dataset')
_ = plt.plot(compute_times, np.ones_like(compute_times)*2, 'b.')

solver_times = b.parse_solver_times()
_ = plt.plot(solver_times['lc01'], np.ones_like(solver_times['lc01'])*3, 'g.')

b.run_compute(solver='nm_solver')
_ = b.plot(show=True)

"""
Explanation: This will always be the case when providing compute_phases when the dataset times cover more than a single cycle.  Here we'll follow the advice from the error and provide compute_times instead.
End of explanation
"""


_ = b.flip_constraint('compute_phases', solve_for='compute_times')

"""
Explanation: Now we'll just flip the constraint back for the remaining examples
End of explanation
"""


b.set_value('solver_times', 'auto')
b.set_value('compute_phases', phoebe.linspace(0,1,101))
b.set_value('mask_enabled', False)
b.set_value('dperdt', 0.0)

dataset_times = b.get_value('times', context='dataset')
_ = plt.plot(times, np.ones_like(times)*1, 'k.')

compute_times = b.get_value('compute_times', context='dataset')
_ = plt.plot(compute_times, np.ones_like(compute_times)*2, 'b.')

solver_times = b.parse_solver_times()
_ = plt.plot(solver_times['lc01'], np.ones_like(solver_times['lc01'])*3, 'g.')

b.run_compute(solver='nm_solver')
_ = b.plot(show=True)

"""
Explanation: solver_times = 'auto'
solver_times='auto' determines the times array under both conditions (solver_times='times' and solver_times='compute_times') and ultimately chooses whichever of the two is shorter.
To see this, we'll stick with the no-mask, time-independent case and change the length of compute_phases to show the switch to the shorter of the available options.
compute_times shorter
End of explanation
"""


b.set_value('solver_times', 'auto')
b.set_value('compute_phases', phoebe.linspace(0,1,2001))
b.set_value('mask_enabled', False)
b.set_value('dperdt', 0.0)

dataset_times = b.get_value('times', context='dataset')
_ = plt.plot(times, np.ones_like(times)*1, 'k.')

compute_times = b.get_value('compute_times', context='dataset')
_ = plt.plot(compute_times, np.ones_like(compute_times)*2, 'b.')

solver_times = b.parse_solver_times()
_ = plt.plot(solver_times['lc01'], np.ones_like(solver_times['lc01'])*3, 'g.')

b.run_compute(solver='nm_solver')
_ = b.plot(show=True)

"""
Explanation: times shorter
End of explanation
"""


