def evolve_state(hamiltonian, initial_state, tlist, return_all_states=False):
    """Evolve state with (generally time-dependent) hamiltonian.

    This is a wrapper around `qutip.mesolve`, to which `hamiltonian` and
    `initial_state` are directly fed.

    Parameters
    ----------
    hamiltonian : list of qutip objects
        The Hamiltonian specification in `qutip.mesolve` is the
        "function based" one, as per qutip.mesolve documentation. This means
        that `hamiltonian` is to be given as a list of constant Hamiltonians,
        each one pairs with a time-dependent (numeric) coefficient.
        The simplest example would be `hamiltonian = [H0, [H1, coeffFun]]`.
    initial_state : qutip.Qobj
    time : float or list of floats
        If a single number, it is divided into a number of subintervals and
        the result used as input for `qutip.mesolve`. If a list of numbers,
        it is directly fed to `qutip.mesolve`.
    """
    if isinstance(tlist, numbers.Number):
        tlist = np.linspace(0, tlist, 40)

    try:
        evolving_states = qutip.mesolve(hamiltonian, initial_state, tlist)
    except Exception:
        error_data_filename = 'evolution_error_details_{}.pickle'.format(
            timestamp())
        error_data_filename = os.path.join(os.getcwd(), error_data_filename)
        error_data_filename = autonumber_filename(error_data_filename)
        logging.info('Something went wrong while trying to evolve from\n'
                     'initial_state={}\nwith hamiltonian={}.\nSaving data to '
                     'reproduce in "{}".'.format(initial_state, hamiltonian,
                                                 error_data_filename))
        with open(error_data_filename, 'wb') as fh:
            if len(hamiltonian) == 2 and len(hamiltonian[1]) == 2:
                pulse_samples = [
                    hamiltonian[1][1](t) for t in np.linspace(0, time, 100)
                ]
                hamiltonian = (hamiltonian[0], hamiltonian[1][0])
            data = dict(hamiltonian=hamiltonian,
                        initial_state=initial_state,
                        times_list=tlist)
            data = data.update(dict(pulse_samples=pulse_samples))
            print('data: {}'.format(data))
            pickle.dump(data, fh)
        raise

    if return_all_states:
        return evolving_states.states
    else:
        return evolving_states.states[-1]
if '../../' not in sys.path:
    sys.path.append('../../')
import src.optimization as optimization
import src.protocol_ansatz as protocol_ansatz
from src.utils import autonumber_filename, basic_logger_configuration

model = 'lmg'
model_parameters = dict(num_spins=50)
optimization_method = 'Powell'
protocol = 'doublebang'
initial_parameters = [[-1, 1], 'halftime', [-1, 1]]
parameters_constraints = [-6, 6]

# ------ build and check name for output file
output_file_name = os.path.basename(__file__)[7:-3] + '.csv'
output_file_name = autonumber_filename(output_file_name)
basic_logger_configuration(filename=output_file_name[:-3] + 'log')
logging.info('Output file name will be "{}"'.format(output_file_name))

# ------ start optimization
results = optimization.find_best_protocol(
    problem_specification=dict(
        model=model,
        model_parameters=model_parameters,
        task='critical point'
    ),
    optimization_specs=dict(
        protocol=protocol,
        optimization_method=optimization_method,
        initial_parameters=initial_parameters,
        parameters_constraints=parameters_constraints