コード例 #1
0
    def _define_var_change_grid(self, feh, masses, ages):
        """
        Create a new grid with the given locations of the nodes.

        Creates self.grid with all arguments as same-name members and an
        additional weight member containing an empty array to fill with weights
        later when grid variables start to be calculated. Also creates
        self._defined_weigths to keep track if weights have been previously
        initialized.

        Args:
            feh:    The [Fe/H] values at which to tabulate the dependent
                variables.

            masses:    The stellar masses at which to tabulate the dependent
                variables.

            - ages:    The ages (in Gyrs) at which to tabulate the dependent
                variables.

        Returns:
            None
        """

        self.grid = Structure(feh=feh, masses=masses, ages=ages)
        #False positive, members created by setattr, so pylint does not see them
        #pylint: disable=no-member
        self.grid.weights = numpy.empty(
            (self.grid.masses.size, self.grid.ages.size, self.grid.feh.size),
            dtype=bool)
        #pylint: enable=no-member
        self.defined_weights = False
コード例 #2
0
        def reset_best_initial_conditions():
            """Reset the entries in self._best_initial_conditions."""

            self._best_initial_conditions = Structure(
                spin_frequency=scipy.inf,
                orbital_period=scipy.nan,
                initial_orbital_period=scipy.nan,
                disk_period=scipy.nan)
コード例 #3
0
    def _get_db_config(self, db_session):
        """Read some configuration from the database."""

        self._quantities = [
            Structure(id=q.id, name=q.name)
            for q in db_session.query(Quantity).all()
        ]

        self._suites = db_session.query(ModelSuite).all()
        self._new_track_id = db_session.query(Track).count() + 1
コード例 #4
0
ファイル: test_evolution.py プロジェクト: kpenev/poet
def test_ic_solver(interpolator) :
    """Find initial condition to reproduce some current state and plot."""

    find_ic = InitialConditionSolver(disk_dissipation_age = 5e-3,
                                     evolution_max_time_step = 1e-2)
    target = Structure(age = 5.0,
                       Porb = 3.0,
                       Psurf = 10.0,
                       planet_formation_age = 5e-3)
    star = create_star(interpolator)
    planet = create_planet()
    initial_porb, initial_psurf = find_ic(target = target,
                                          star = star,
                                          planet = planet)
    print('IC: Porb0 = %s, P*0 = %s' % (repr(initial_porb),
                                        repr(initial_psurf)))
コード例 #5
0
    def __init__(self,
                 system,
                 interpolator,
                 *,
                 current_age,
                 disk_period,
                 initial_obliquity,
                 disk_dissipation_age,
                 max_timestep,
                 dissipation,
                 primary_wind_strength,
                 primary_wind_saturation,
                 primary_core_envelope_coupling_timescale,
                 secondary_wind_strength,
                 secondary_wind_saturation,
                 secondary_core_envelope_coupling_timescale,
                 secondary_star=False,
                 orbital_period_tolerance=1e-6):
        """
        Get ready for the solver.

        Args:
            system:    The parameters of the system we are trying to reproduce.

            interpolator:    The stellar evolution interpolator to use, could
                also be a pair of interpolators, one to use for the primary and
                one for the secondary.

            disk_period: The period at which the primaly will initially spin.

            initial_obliquity:    The initial inclination of all zones
                of the primary relative to the orbit in which the secondary
                forms.

            disk_dissipation_age:    The age at which the disk dissipates and
                the secondary forms.

            max_timestep:    The maximum timestep the evolution is allowed to
                take.

            dissipation:    A dictionary containing the dissipation argument to
                pass to create_star() or create_planet() when creating the
                primary and secondary in the system. It should have two keys:
                `'primary'` and `'secondary'`, each containing the argument for
                the corresponding component.

            secondary_star:    True iff the secondary object is a star.

            orbital_period_tolerance:    How precisely do we need to match the
                present day orbital period (relative error).

        Returns:
            None
        """

        self.target_state = Structure(
            #False positive
            #pylint: disable=no-member
            age=current_age.to(units.Gyr).value,
            Porb=getattr(system, 'Porb',
                         system.orbital_period).to(units.day).value,
            Pdisk=disk_period.to(units.day).value,
            planet_formation_age=disk_dissipation_age.to(units.Gyr).value
            #pylint: enable=no-member
        )
        print('For system:\n\t' + repr(system))
        print('Targeting:\n\t' + self.target_state.format('\n\t'))

        self.system = system
        if isinstance(interpolator, MESAInterpolator):
            self.interpolator = dict(primary=interpolator,
                                     secondary=interpolator)
        else:
            self.interpolator = dict(primary=interpolator[0],
                                     secondary=interpolator[1])
        self.configuration = dict(
            #False positive
            #pylint: disable=no-member
            disk_dissipation_age=disk_dissipation_age.to(units.Gyr).value,
            max_timestep=max_timestep.to(units.Gyr).value,
            #pylint: enable=no-member
            orbital_period_tolerance=orbital_period_tolerance,
            dissipation=dissipation,
            initial_obliquity=initial_obliquity,
            primary_wind_strength=primary_wind_strength,
            primary_wind_saturation=primary_wind_saturation,
            primary_core_envelope_coupling_timescale=(
                primary_core_envelope_coupling_timescale),
            secondary_core_envelope_coupling_timescale=(
                secondary_core_envelope_coupling_timescale),
            secondary_wind_strength=secondary_wind_strength,
            secondary_wind_saturation=secondary_wind_saturation)
        self.porb_initial = None
        self.psurf = None
        self.secondary_star = secondary_star
コード例 #6
0
class PeriodSolverWrapper:
    """Provide methods to pass to a solver for initial ecc. or obliquity."""
    def _create_system_components(self):
        """Create the two objects comprising the system to evolve."""

        mprimary = getattr(self.system, 'Mprimary', self.system.primary_mass)
        msecondary = getattr(self.system, 'Msecondary',
                             self.system.secondary_mass)

        primary = create_star(
            mprimary,
            self.system.feh,
            self.interpolator['primary'],
            self.configuration['dissipation']['primary'],
            wind_strength=self.configuration['primary_wind_strength'],
            wind_saturation_frequency=(
                self.configuration['primary_wind_saturation']),
            diff_rot_coupling_timescale=(
                self.configuration['primary_core_envelope_coupling_timescale']
            ))
        if self.secondary_star:
            secondary = create_star(
                msecondary,
                self.system.feh,
                self.interpolator['secondary'],
                self.configuration['dissipation']['secondary'],
                wind_strength=self.configuration['secondary_wind_strength'],
                wind_saturation_frequency=(
                    self.configuration['secondary_wind_saturation']),
                diff_rot_coupling_timescale=self.
                configuration['secondary_core_envelope_coupling_timescale'],
                interpolation_age=self.configuration['disk_dissipation_age'])
        else:
            secondary = create_planet(
                msecondary,
                getattr(self.system, 'Rsecondary',
                        self.system.secondary_radius),
                self.configuration['dissipation']['secondary'])
        return primary, secondary

    def _create_system(self, primary, secondary, *, porb_initial,
                       initial_eccentricity, initial_obliquity):
        """
        Create the system to evolve from the two bodies (primary & secondary).

        Args:
            primary:    The primary in the system. Usually created by calling
                create_star().

            planet:    The secondary in the system. Usually created by calling
                create_star() or create_planet().

            porb_initial:    Initial orbital period in days.

            initial_eccentricity:    The initial eccentricity of the system.

            initial_obliquity:     The initial obliquity to assume for the
                system in rad.

        Returns:
            Binary:
                The binary system ready to evolve.
        """

        #False positive
        #pylint: disable=no-member
        binary = Binary(
            primary=primary,
            secondary=secondary,
            initial_orbital_period=porb_initial,
            initial_eccentricity=initial_eccentricity,
            initial_inclination=initial_obliquity,
            disk_lock_frequency=(2.0 * scipy.pi / self.target_state.Pdisk),
            disk_dissipation_age=self.configuration['disk_dissipation_age'],
            secondary_formation_age=self.target_state.planet_formation_age)
        #pylint: enable=no-member
        binary.configure(age=primary.core_formation_age(),
                         semimajor=float('nan'),
                         eccentricity=float('nan'),
                         spin_angmom=scipy.array([0.0]),
                         inclination=None,
                         periapsis=None,
                         evolution_mode='LOCKED_SURFACE_SPIN')
        if isinstance(secondary, EvolvingStar):
            initial_obliquity = scipy.array([0.0])
            initial_periapsis = scipy.array([0.0])
        else:
            initial_obliquity = None
            initial_periapsis = None
        secondary.configure(
            #False positive
            #pylint: disable=no-member
            age=self.target_state.planet_formation_age,
            #pylint: enable=no-member
            companion_mass=primary.mass,
            #False positive
            #pylint: disable=no-member
            semimajor=binary.semimajor(porb_initial),
            #pylint: enable=no-member
            eccentricity=initial_eccentricity,
            spin_angmom=(scipy.array([0.01, 0.01]) if isinstance(
                secondary, EvolvingStar) else scipy.array([0.0])),
            inclination=initial_obliquity,
            periapsis=initial_periapsis,
            locked_surface=False,
            zero_outer_inclination=True,
            zero_outer_periapsis=True)
        primary.detect_stellar_wind_saturation()
        if isinstance(secondary, EvolvingStar):
            secondary.detect_stellar_wind_saturation()
        return binary

    @staticmethod
    def _format_evolution(binary, interpolators, secondary_star):
        """
        Return pre-calculated evolution augmented with stellar quantities.

        Args:
            binary:    The binary used to calculate the evolution to format.

            interpolators:


        The returned evolution contains the full evolution produced by
        Binary.get_evolution(), as well as the evolution of the following
        quantities:

            * **orbital_period**: the orbital period

            * **(primary/secondary)_radius: The radius of the primary/secondary
              star.

            * **(primary/secondary)_lum: The luminosity of the primary/secondary
              star.

            * **(primary/secondary)_(iconv/irad): The convective/radiative
              zone moment of inertia of the primary/secondary star.
        """

        evolution = binary.get_evolution()
        #False positive
        #pylint: disable=no-member
        evolution.orbital_period = binary.orbital_period(evolution.semimajor)

        components_to_get = ['primary']
        if secondary_star:
            components_to_get.append('secondary')

        for component in components_to_get:

            if (len(interpolators[component].track_masses) == 1
                    and len(interpolators[component].track_feh) == 1):
                star_params = dict(
                    mass=interpolators[component].track_masses[0],
                    feh=interpolators[component].track_feh[0])
            else:
                star_params = dict(mass=getattr(binary, component).mass,
                                   feh=getattr(binary, component).metallicity)

            for quantity_name in ['radius', 'lum', 'iconv', 'irad']:
                print('Start params: ' + repr(star_params))
                quantity = interpolators[component](quantity_name,
                                                    **star_params)
                print(quantity_name + ' age range: ' +
                      repr((quantity.min_age, quantity.max_age)))
                values = scipy.full(evolution.age.shape, scipy.nan)

                #TODO: determine age range properly (requires C/C++ code
                #modifications)
                valid_ages = scipy.logical_and(
                    evolution.age > quantity.min_age * 2.0,
                    evolution.age < quantity.max_age / 2.0)
                if quantity_name in ['iconv', 'irad']:
                    values[valid_ages] = getattr(
                        getattr(binary, component),
                        (('envelope' if quantity_name == 'iconv' else 'core') +
                         '_inertia'))(evolution.age[valid_ages])
                else:
                    values[valid_ages] = quantity(evolution.age[valid_ages])
                setattr(evolution, component + '_' + quantity_name, values)

        return evolution

    def _get_final_state(self, initial_eccentricity, initial_obliquity):
        """Return the final state of the system given initial conditions."""

        find_ic = InitialConditionSolver(
            disk_dissipation_age=self.configuration['disk_dissipation_age'],
            evolution_max_time_step=self.configuration['max_timestep'],
            initial_eccentricity=initial_eccentricity,
            initial_inclination=initial_obliquity,
            orbital_period_tolerance=(
                self.configuration['orbital_period_tolerance']),
            max_porb_initial=1000.0)
        primary, secondary = self._create_system_components()
        self.porb_initial, self.psurf = find_ic(self.target_state, primary,
                                                secondary)
        #False positive
        #pylint: disable=no-member
        self.porb_initial *= units.day
        self.psurf *= units.day

        result = find_ic.binary.final_state()

        primary.delete()
        secondary.delete()
        find_ic.binary.delete()

        return result

    def __init__(self,
                 system,
                 interpolator,
                 *,
                 current_age,
                 disk_period,
                 initial_obliquity,
                 disk_dissipation_age,
                 max_timestep,
                 dissipation,
                 primary_wind_strength,
                 primary_wind_saturation,
                 primary_core_envelope_coupling_timescale,
                 secondary_wind_strength,
                 secondary_wind_saturation,
                 secondary_core_envelope_coupling_timescale,
                 secondary_star=False,
                 orbital_period_tolerance=1e-6):
        """
        Get ready for the solver.

        Args:
            system:    The parameters of the system we are trying to reproduce.

            interpolator:    The stellar evolution interpolator to use, could
                also be a pair of interpolators, one to use for the primary and
                one for the secondary.

            disk_period: The period at which the primaly will initially spin.

            initial_obliquity:    The initial inclination of all zones
                of the primary relative to the orbit in which the secondary
                forms.

            disk_dissipation_age:    The age at which the disk dissipates and
                the secondary forms.

            max_timestep:    The maximum timestep the evolution is allowed to
                take.

            dissipation:    A dictionary containing the dissipation argument to
                pass to create_star() or create_planet() when creating the
                primary and secondary in the system. It should have two keys:
                `'primary'` and `'secondary'`, each containing the argument for
                the corresponding component.

            secondary_star:    True iff the secondary object is a star.

            orbital_period_tolerance:    How precisely do we need to match the
                present day orbital period (relative error).

        Returns:
            None
        """

        self.target_state = Structure(
            #False positive
            #pylint: disable=no-member
            age=current_age.to(units.Gyr).value,
            Porb=getattr(system, 'Porb',
                         system.orbital_period).to(units.day).value,
            Pdisk=disk_period.to(units.day).value,
            planet_formation_age=disk_dissipation_age.to(units.Gyr).value
            #pylint: enable=no-member
        )
        print('For system:\n\t' + repr(system))
        print('Targeting:\n\t' + self.target_state.format('\n\t'))

        self.system = system
        if isinstance(interpolator, MESAInterpolator):
            self.interpolator = dict(primary=interpolator,
                                     secondary=interpolator)
        else:
            self.interpolator = dict(primary=interpolator[0],
                                     secondary=interpolator[1])
        self.configuration = dict(
            #False positive
            #pylint: disable=no-member
            disk_dissipation_age=disk_dissipation_age.to(units.Gyr).value,
            max_timestep=max_timestep.to(units.Gyr).value,
            #pylint: enable=no-member
            orbital_period_tolerance=orbital_period_tolerance,
            dissipation=dissipation,
            initial_obliquity=initial_obliquity,
            primary_wind_strength=primary_wind_strength,
            primary_wind_saturation=primary_wind_saturation,
            primary_core_envelope_coupling_timescale=(
                primary_core_envelope_coupling_timescale),
            secondary_core_envelope_coupling_timescale=(
                secondary_core_envelope_coupling_timescale),
            secondary_wind_strength=secondary_wind_strength,
            secondary_wind_saturation=secondary_wind_saturation)
        self.porb_initial = None
        self.psurf = None
        self.secondary_star = secondary_star

    def eccentricity_difference(self, initial_eccentricity):
        """
        Return the discrepancy in eccentricity for the given initial value.

        An evolution is found which reproduces the present day orbital period of
        the system, starting with the given initial eccentricity and the result
        of this function is the difference between the present day eccentricity
        predicted by that evolution and the measured value supplied at
        construction through the system argument. In addition, the initial
        orbital period and (initial or final, depending on which is not
        specified) stellar spin period are stored in the
        :attr:porb_initial and :attr:psurf attributes.

        Args:
            initial_eccentricity(float):    The initial eccentricity with which
                the secondary forms.

        Returns:
            float:
                The difference between the predicted and measured values of the
                eccentricity.
        """

        final_eccentricity = self._get_final_state(
            initial_eccentricity=initial_eccentricity,
            initial_obliquity=self.system.obliquity).eccentricity
        #pylint: enable=no-member
        print('Final eccentricity: ' + repr(final_eccentricity))

        return final_eccentricity - self.system.eccentricity

    def obliquity_difference(self, initial_obliquity):
        """Same as eccentricity_difference, but for obliquity."""

        final_obliquity = self._get_final_state(
            initial_eccentricity=self.system.eccentricity,
            initial_obliquity=initial_obliquity).envelope_inclination
        #pylint: enable=no-member
        print('Final obliquity: ' + repr(final_obliquity))

        return final_obliquity - self.system.obliquity

    def get_found_evolution(self,
                            initial_eccentricity,
                            initial_obliquity,
                            max_age=None,
                            **evolve_kwargs):
        """
        Return the evolution matching the current system configuration.

        Args:
            initial_eccentricity:    The initial eccentricity found to reproduce
                the current state.

            max_age:    The age up to which to calculate the evolution. If None
                (default), the star lifetime is used.

            evolve_kwargs:    Additional keyword arguments to pass to
                Binary.evolve()

        Returns:
            See EccentricitySolverCallable._format_evolution().
        """

        if self.porb_initial is None:
            self._get_final_state(initial_eccentricity, initial_obliquity)
        primary, secondary = self._create_system_components()

        binary = self._create_system(
            primary,
            secondary,
            #False positive
            #pylint: disable=no-member
            porb_initial=self.porb_initial.to(units.day).value,
            #pylint: enable=no-member
            initial_eccentricity=initial_eccentricity,
            initial_obliquity=initial_obliquity)
        if max_age is None:
            if isinstance(primary, EvolvingStar):
                max_age = primary.lifetime()
            else:
                max_age = (self.system.age).to(units.Gyr).value
        else:
            max_age = max_age.to(units.Gyr).value

        binary.evolve(
            #False positive
            #pylint: disable=no-member
            max_age,
            self.configuration['max_timestep'],
            #pylint: enable=no-member
            1e-6,
            None,
            **evolve_kwargs)

        result = self._format_evolution(binary, self.interpolator,
                                        self.secondary_star)

        primary.delete()
        secondary.delete()
        binary.delete()

        return result
コード例 #7
0
    def __init__(self,
                 system,
                 interpolator,
                 *,
                 current_age,
                 disk_period,
                 initial_obliquity,
                 disk_dissipation_age,
                 max_timestep,
                 dissipation,
                 primary_wind_strength,
                 primary_wind_saturation,
                 primary_core_envelope_coupling_timescale,
                 secondary_wind_strength,
                 secondary_wind_saturation,
                 secondary_core_envelope_coupling_timescale,
                 secondary_disk_period=None,
                 primary_star=True,
                 secondary_star=False,
                 orbital_period_tolerance=1e-6,
                 period_search_factor=2.0,
                 scaled_period_guess=1.0,
                 **extra_evolve_args):
        """
        Get ready for the solver.

        Args:
            system:    The parameters of the system we are trying to reproduce.

            interpolator:    The stellar evolution interpolator to use, could
                also be a pair of interpolators, one to use for the primary and
                one for the secondary.

            current_age:    The present day age of the system (when the target
                state is to be reproduced).

            disk_period: The period at which the primaly will initially spin.

            initial_obliquity:    The initial inclination of all zones
                of the primary relative to the orbit in which the secondary
                forms.

            disk_dissipation_age:    The age at which the disk dissipates and
                the secondary forms.

            max_timestep:    The maximum timestep the evolution is allowed to
                take.

            dissipation:    A dictionary containing the dissipation argument to
                pass to :meth:`_create_star` or :meth:`_create_planet` when
                creating the primary and secondary in the system. It should have
                two keys: `'primary'` and `'secondary'`, each containing the
                argument for the corresponding component.

            primary_wind_strength:    The normilazation constant defining how
                fast angular momentum is lost by the primary star.

            primary_wind_saturation:    The angular velocity above which the
                rate of angular momentum loss switches from being proportional
                to the cube of the angular velocity to linear for the primary
                star.

            primary_core_envelope_coupling_timescale:    The timescale over
                which the core and envelope of the primary converge toward solid
                body rotation.

            secondary_wind_strength:    Analogous to primary_wind_strength but
                for the secondary.

            secondary_wind_saturation:    Analogous to primary_wind_saturation
                but for the secondary.

            secondary_core_envelope_coupling_timescale:    Analogous to
                primary_core_envelope_coupling_timescale but for the secondary.

            secondary_disk_period:    The period to which the surface spin of
                the secondary is locked before the binary forms. If None, the
                primary disk period is used.

            primary_star:    True iff the primary object is a star.

            secondary_star:    True iff the secondary object is a star.

            orbital_period_tolerance:    How precisely do we need to match the
                present day orbital period (relative error).

            period_search_factor:    See same name argument to
                :meth:`InitialConditionSolver.__init__`

            scaled_period_guess:    See same name argument to
                :meth:`InitialConditionSolver.__init__`

            extra_evolve_args:    Additional arguments to pass to binary.evolve.

        Returns:
            None
        """

        self.target_state = Structure(
            #False positive
            #pylint: disable=no-member
            age=current_age.to(units.Gyr).value,
            Porb=getattr(system,
                         'Porb',
                         system.orbital_period).to(units.day).value,
            Pdisk=disk_period.to(units.day).value,
            planet_formation_age=disk_dissipation_age.to(units.Gyr).value
            #pylint: enable=no-member
        )
        logging.getLogger(__name__).info(
            'For system:\n\t%s'
            '\nTargeting:\n\t%s',
            repr(system),
            self.target_state.format('\n\t')
        )

        self.system = system
        if isinstance(interpolator, MESAInterpolator):
            self.interpolator = dict(primary=interpolator,
                                     secondary=interpolator)
        else:
            self.interpolator = dict(primary=interpolator[0],
                                     secondary=interpolator[1])
        self.configuration = dict(
            #False positive
            #pylint: disable=no-member
            disk_dissipation_age=disk_dissipation_age.to(units.Gyr).value,
            max_timestep=max_timestep.to(units.Gyr).value,
            #pylint: enable=no-member
            orbital_period_tolerance=orbital_period_tolerance,
            dissipation=dissipation,
            initial_obliquity=initial_obliquity,
            primary_wind_strength=primary_wind_strength,
            primary_wind_saturation=primary_wind_saturation,
            primary_core_envelope_coupling_timescale=(
                primary_core_envelope_coupling_timescale
            ),
            secondary_core_envelope_coupling_timescale=(
                secondary_core_envelope_coupling_timescale
            ),
            secondary_wind_strength=secondary_wind_strength,
            secondary_wind_saturation=secondary_wind_saturation,
            secondary_disk_period=(secondary_disk_period
                                   or
                                   disk_period).to_value(units.day),
            period_search_factor=period_search_factor,
            scaled_period_guess=scaled_period_guess
        )
        self.porb_initial = None
        self.psurf = None
        self.secondary_star = secondary_star
        self.primary_star = primary_star

        if 'precision' not in extra_evolve_args:
            extra_evolve_args['precision'] = 1e-6
        if 'required_ages' not in extra_evolve_args:
            extra_evolve_args['required_ages'] = None
        self._extra_evolve_args = extra_evolve_args
コード例 #8
0
class PeriodSolverWrapper:
    """Provide methods to pass to a solver for initial ecc. or obliquity."""

    @staticmethod
    def _create_planet(mass,
                       radius,
                       dissipation=None):
        """
        Return the configured planet in the given system.

        Args:
            mass:    The mass of the planets, along with astropy units.

            radius:    The radius of the planets, along with astropy units.

            dissipation:    If None, no dissipation is set. Otherwise, should be
                a dictionary of keyword arguments to pass to
                `LockedPlanet.set_dissipation`.

        """

        planet = LockedPlanet(
            #False positive
            #pylint: disable=no-member
            mass=mass.to(units.M_sun).value,
            radius=radius.to(units.R_sun).value
            #pylint: enable=no-member
        )
        if dissipation is not None:
            planet.set_dissipation(**dissipation)
        return planet

    @staticmethod
    def _create_star(mass,
                     feh,
                     interpolator,
                     dissipation=None,
                     *,
                     wind_strength=0.17,
                     wind_saturation_frequency=2.78,
                     diff_rot_coupling_timescale=5e-3,
                     interpolation_age=None):
        """
        Create the star to use in the evolution.

        Args:
            mass:    The mass of the star to create, along with astropy units.

            feh:    The [Fe/H] value of the star to create.

            interpolator:    POET stellar evolution interpolator giving the
                evolution of the star's properties.

            dissipation:    If None, no dissipation is set. Otherwise, a
                dictionary of keyword arguments to pass to
                `EvolvingStar.set_dissipation()`.

            wind_strength:    See same name argument to EvolvingStar.__init__()

            wind_saturation_frequency:    See same name argument to
                EvolvingStar.__init__()

            diff_rot_coupling_timescale:    See same name argument to
                EvolvingStar.__init__()

            interpolation_age:    The age at which to initialize the
                interpolation for the star. If `None`, the core formation age is
                used.

        Returns:
            EvolvingStar:
                The star in the system useable for calculating obital evolution.
        """

        #False positive
        #pylint: disable=no-member
        star = EvolvingStar(
            mass=mass.to_value(units.M_sun),
            metallicity=feh,
            wind_strength=wind_strength,
            wind_saturation_frequency=wind_saturation_frequency,
            diff_rot_coupling_timescale=(
                diff_rot_coupling_timescale.to_value(units.Gyr)
            ),
            interpolator=interpolator
        )
        #pylint: enable=no-member
        star.select_interpolation_region(star.core_formation_age()
                                         if interpolation_age is None else
                                         interpolation_age)
        if dissipation is not None:
            star.set_dissipation(zone_index=0, **dissipation)
        return star



    def _create_primary(self):
        """Create the primary object in the system."""

        mprimary = getattr(self.system,
                           'Mprimary',
                           self.system.primary_mass)

        if self.primary_star:
            return self._create_star(
                mprimary,
                self.system.feh,
                self.interpolator['primary'],
                self.configuration['dissipation']['primary'],
                wind_strength=self.configuration['primary_wind_strength'],
                wind_saturation_frequency=(
                    self.configuration['primary_wind_saturation']
                ),
                diff_rot_coupling_timescale=(
                    self.configuration['primary_core_envelope_coupling_timescale']
                )
            )
        return self._create_planet(
            mprimary,
            getattr(self.system,
                    'Rprimary',
                    self.system.primary_radius),
            self.configuration['dissipation']['primary']
        )


    def _create_secondary(self):
        """Create the two objects comprising the system to evolve."""

        msecondary = getattr(self.system,
                             'Msecondary',
                             self.system.secondary_mass)

        if self.secondary_star:
            return self._create_star(
                msecondary,
                self.system.feh,
                self.interpolator['secondary'],
                self.configuration['dissipation']['secondary'],
                wind_strength=self.configuration['secondary_wind_strength'],
                wind_saturation_frequency=(
                    self.configuration['secondary_wind_saturation']
                ),
                diff_rot_coupling_timescale=self.configuration[
                    'secondary_core_envelope_coupling_timescale'
                ],
                interpolation_age=self.configuration['disk_dissipation_age']
            )

        return self._create_planet(
            msecondary,
            getattr(self.system,
                    'Rsecondary',
                    self.system.secondary_radius),
            self.configuration['dissipation']['secondary']
        )

    def _create_system(self,
                       primary,
                       secondary,
                       *,
                       porb_initial,
                       initial_eccentricity,
                       initial_obliquity,
                       initial_secondary_angmom):
        """
        Create the system to evolve from the two bodies (primary & secondary).

        Args:
            primary:    The primary in the system. Usually created by calling
                :meth:`_create_star`.

            planet:    The secondary in the system. Usually created by calling
                :meth:`_create_star` or :meth:`_create_planet`.

            porb_initial:    Initial orbital period in days.

            initial_eccentricity:    The initial eccentricity of the system.

            initial_obliquity:     The initial obliquity to assume for the
                system in rad.

        Returns:
            Binary:
                The binary system ready to evolve.
        """

        #False positive
        #pylint: disable=no-member
        binary = Binary(
            primary=primary,
            secondary=secondary,
            initial_orbital_period=porb_initial,
            initial_eccentricity=initial_eccentricity,
            initial_inclination=initial_obliquity,
            disk_lock_frequency=(2.0 * numpy.pi
                                 /
                                 self.target_state.Pdisk),
            disk_dissipation_age=self.configuration['disk_dissipation_age'],
            secondary_formation_age=self.target_state.planet_formation_age
        )
        #pylint: enable=no-member
        binary.configure(age=primary.core_formation_age(),
                         semimajor=float('nan'),
                         eccentricity=float('nan'),
                         spin_angmom=numpy.array([0.0]),
                         inclination=None,
                         periapsis=None,
                         evolution_mode='LOCKED_SURFACE_SPIN')
        if isinstance(secondary, EvolvingStar):
            initial_obliquity = numpy.array([0.0])
            initial_periapsis = numpy.array([0.0])
        else:
            initial_obliquity = None
            initial_periapsis = None
        secondary.configure(
            #False positive
            #pylint: disable=no-member
            age=self.target_state.planet_formation_age,
            #pylint: enable=no-member
            companion_mass=primary.mass,
            #False positive
            #pylint: disable=no-member
            semimajor=binary.semimajor(porb_initial),
            #pylint: enable=no-member
            eccentricity=initial_eccentricity,
            spin_angmom=initial_secondary_angmom,
            inclination=initial_obliquity,
            periapsis=initial_periapsis,
            locked_surface=False,
            zero_outer_inclination=True,
            zero_outer_periapsis=True
        )
        primary.detect_stellar_wind_saturation()
        if isinstance(secondary, EvolvingStar):
            secondary.detect_stellar_wind_saturation()
        return binary

    @staticmethod
    def _format_evolution(binary, interpolators, secondary_star):
        """
        Return pre-calculated evolution augmented with stellar quantities.

        Args:
            binary:    The binary used to calculate the evolution to format.

            interpolators:


        The returned evolution contains the full evolution produced by
        Binary.get_evolution(), as well as the evolution of the following
        quantities:

            * **orbital_period**: the orbital period

            * **(primary/secondary)_radius: The radius of the primary/secondary
              star.

            * **(primary/secondary)_lum: The luminosity of the primary/secondary
              star.

            * **(primary/secondary)_(iconv/irad): The convective/radiative
              zone moment of inertia of the primary/secondary star.
        """

        evolution = binary.get_evolution()
        #False positive
        #pylint: disable=no-member
        evolution.orbital_period = binary.orbital_period(evolution.semimajor)

        components_to_get = ['primary']
        if secondary_star:
            components_to_get.append('secondary')

        for component in components_to_get:

            if (
                    len(interpolators[component].track_masses) == 1
                    and
                    len(interpolators[component].track_feh) == 1
            ):
                star_params = dict(
                    mass=interpolators[component].track_masses[0],
                    feh=interpolators[component].track_feh[0]
                )
            else:
                star_params = dict(
                    mass=getattr(binary, component).mass,
                    feh=getattr(binary, component).metallicity
                )

            for quantity_name in ['radius', 'lum', 'iconv', 'irad']:
                quantity = interpolators[component](
                    quantity_name,
                    **star_params
                )
                values = numpy.full(evolution.age.shape, numpy.nan)

                #TODO: determine age range properly (requires C/C++ code
                #modifications)
                valid_ages = numpy.logical_and(
                    evolution.age > quantity.min_age * 2.0,
                    evolution.age < quantity.max_age / 2.0
                )
                if quantity_name in ['iconv', 'irad']:
                    values[valid_ages] = getattr(
                        getattr(binary, component),
                        (
                            ('envelope' if quantity_name == 'iconv' else 'core')
                            +
                            '_inertia'
                        )
                    )(evolution.age[valid_ages])
                else:
                    values[valid_ages] = quantity(evolution.age[valid_ages])
                setattr(evolution,
                        component + '_' + quantity_name,
                        values)

        return evolution

    def _match_target_state(self,
                            *,
                            initial_eccentricity,
                            initial_obliquity,
                            initial_secondary_angmom,
                            evolve_kwargs,
                            full_evolution=False):
        """Return the final state or evolution of the system matching target."""

        find_ic = InitialConditionSolver(
            evolution_max_time_step=self.configuration['max_timestep'],
            evolution_precision=evolve_kwargs.get('precision', 1e-6),
            evolution_timeout=evolve_kwargs.get('timeout', 0),
            initial_eccentricity=initial_eccentricity,
            initial_inclination=initial_obliquity,
            initial_secondary_angmom=initial_secondary_angmom,
            max_porb_initial=1000.0,
            **{
                param: self.configuration[param]
                for param in ['disk_dissipation_age',
                              'orbital_period_tolerance',
                              'period_search_factor',
                              'scaled_period_guess']
            }
        )
        primary = self._create_primary()
        secondary = self._create_secondary()
        self.porb_initial, self.psurf = find_ic(self.target_state,
                                                primary,
                                                secondary)
        #False positive
        #pylint: disable=no-member
        self.porb_initial *= units.day
        self.psurf *= units.day

        if full_evolution:
            result = self._format_evolution(find_ic.binary,
                                            self.interpolator,
                                            self.secondary_star)
        else:
            result = find_ic.binary.final_state()

        primary.delete()
        secondary.delete()
        find_ic.binary.delete()

        return result

    def _get_combined_evolve_args(self, overwrite_args):
        """Return `self._extra_evolve_args` overwritten by `overwrite_args`."""

        combined_evolve_kwargs = dict(self._extra_evolve_args)
        combined_evolve_kwargs.update(overwrite_args)
        return combined_evolve_kwargs

    #TODO: cleanup
    #pylint: disable=too-many-locals
    def __init__(self,
                 system,
                 interpolator,
                 *,
                 current_age,
                 disk_period,
                 initial_obliquity,
                 disk_dissipation_age,
                 max_timestep,
                 dissipation,
                 primary_wind_strength,
                 primary_wind_saturation,
                 primary_core_envelope_coupling_timescale,
                 secondary_wind_strength,
                 secondary_wind_saturation,
                 secondary_core_envelope_coupling_timescale,
                 secondary_disk_period=None,
                 primary_star=True,
                 secondary_star=False,
                 orbital_period_tolerance=1e-6,
                 period_search_factor=2.0,
                 scaled_period_guess=1.0,
                 **extra_evolve_args):
        """
        Get ready for the solver.

        Args:
            system:    The parameters of the system we are trying to reproduce.

            interpolator:    The stellar evolution interpolator to use, could
                also be a pair of interpolators, one to use for the primary and
                one for the secondary.

            current_age:    The present day age of the system (when the target
                state is to be reproduced).

            disk_period: The period at which the primaly will initially spin.

            initial_obliquity:    The initial inclination of all zones
                of the primary relative to the orbit in which the secondary
                forms.

            disk_dissipation_age:    The age at which the disk dissipates and
                the secondary forms.

            max_timestep:    The maximum timestep the evolution is allowed to
                take.

            dissipation:    A dictionary containing the dissipation argument to
                pass to :meth:`_create_star` or :meth:`_create_planet` when
                creating the primary and secondary in the system. It should have
                two keys: `'primary'` and `'secondary'`, each containing the
                argument for the corresponding component.

            primary_wind_strength:    The normilazation constant defining how
                fast angular momentum is lost by the primary star.

            primary_wind_saturation:    The angular velocity above which the
                rate of angular momentum loss switches from being proportional
                to the cube of the angular velocity to linear for the primary
                star.

            primary_core_envelope_coupling_timescale:    The timescale over
                which the core and envelope of the primary converge toward solid
                body rotation.

            secondary_wind_strength:    Analogous to primary_wind_strength but
                for the secondary.

            secondary_wind_saturation:    Analogous to primary_wind_saturation
                but for the secondary.

            secondary_core_envelope_coupling_timescale:    Analogous to
                primary_core_envelope_coupling_timescale but for the secondary.

            secondary_disk_period:    The period to which the surface spin of
                the secondary is locked before the binary forms. If None, the
                primary disk period is used.

            primary_star:    True iff the primary object is a star.

            secondary_star:    True iff the secondary object is a star.

            orbital_period_tolerance:    How precisely do we need to match the
                present day orbital period (relative error).

            period_search_factor:    See same name argument to
                :meth:`InitialConditionSolver.__init__`

            scaled_period_guess:    See same name argument to
                :meth:`InitialConditionSolver.__init__`

            extra_evolve_args:    Additional arguments to pass to binary.evolve.

        Returns:
            None
        """

        self.target_state = Structure(
            #False positive
            #pylint: disable=no-member
            age=current_age.to(units.Gyr).value,
            Porb=getattr(system,
                         'Porb',
                         system.orbital_period).to(units.day).value,
            Pdisk=disk_period.to(units.day).value,
            planet_formation_age=disk_dissipation_age.to(units.Gyr).value
            #pylint: enable=no-member
        )
        logging.getLogger(__name__).info(
            'For system:\n\t%s'
            '\nTargeting:\n\t%s',
            repr(system),
            self.target_state.format('\n\t')
        )

        self.system = system
        if isinstance(interpolator, MESAInterpolator):
            self.interpolator = dict(primary=interpolator,
                                     secondary=interpolator)
        else:
            self.interpolator = dict(primary=interpolator[0],
                                     secondary=interpolator[1])
        self.configuration = dict(
            #False positive
            #pylint: disable=no-member
            disk_dissipation_age=disk_dissipation_age.to(units.Gyr).value,
            max_timestep=max_timestep.to(units.Gyr).value,
            #pylint: enable=no-member
            orbital_period_tolerance=orbital_period_tolerance,
            dissipation=dissipation,
            initial_obliquity=initial_obliquity,
            primary_wind_strength=primary_wind_strength,
            primary_wind_saturation=primary_wind_saturation,
            primary_core_envelope_coupling_timescale=(
                primary_core_envelope_coupling_timescale
            ),
            secondary_core_envelope_coupling_timescale=(
                secondary_core_envelope_coupling_timescale
            ),
            secondary_wind_strength=secondary_wind_strength,
            secondary_wind_saturation=secondary_wind_saturation,
            secondary_disk_period=(secondary_disk_period
                                   or
                                   disk_period).to_value(units.day),
            period_search_factor=period_search_factor,
            scaled_period_guess=scaled_period_guess
        )
        self.porb_initial = None
        self.psurf = None
        self.secondary_star = secondary_star
        self.primary_star = primary_star

        if 'precision' not in extra_evolve_args:
            extra_evolve_args['precision'] = 1e-6
        if 'required_ages' not in extra_evolve_args:
            extra_evolve_args['required_ages'] = None
        self._extra_evolve_args = extra_evolve_args
    #pylint: disable=too-many-locals


    def get_secondary_initial_angmom(self, **evolve_kwargs):
        """Return the angular momentum of the secondary when binary forms."""

        if not self.secondary_star:
            return (0.0, 0.0)

        secondary = self._create_secondary()
        mock_companion = self._create_planet(1.0 * units.M_jup,
                                             1.0 * units.R_jup)
        binary = Binary(
            primary=secondary,
            secondary=mock_companion,
            initial_orbital_period=1.0,
            initial_eccentricity=0.0,
            initial_inclination=0.0,
            disk_lock_frequency=(
                2.0 * numpy.pi
                /
                self.configuration['secondary_disk_period']
            ),
            disk_dissipation_age=(
                2.0
                *
                self.configuration['disk_dissipation_age']
            )
        )
        binary.configure(age=secondary.core_formation_age(),
                         semimajor=float('nan'),
                         eccentricity=float('nan'),
                         spin_angmom=numpy.array([0.0]),
                         inclination=None,
                         periapsis=None,
                         evolution_mode='LOCKED_SURFACE_SPIN')

        secondary.detect_stellar_wind_saturation()

        binary.evolve(
            final_age=self.configuration['disk_dissipation_age'],
            max_time_step=self.configuration['max_timestep'],
            **self._get_combined_evolve_args(evolve_kwargs)
        )
        final_state = binary.final_state()

        secondary.delete()
        mock_companion.delete()
        binary.delete()

        return final_state.envelope_angmom, final_state.core_angmom


    def eccentricity_difference(self,
                                initial_eccentricity,
                                initial_secondary_angmom,
                                **evolve_kwargs):
        """
        Return the discrepancy in eccentricity for the given initial value.

        An evolution is found which reproduces the present day orbital period of
        the system, starting with the given initial eccentricity and the result
        of this function is the difference between the present day eccentricity
        predicted by that evolution and the measured value supplied at
        construction through the system argument. In addition, the initial
        orbital period and (initial or final, depending on which is not
        specified) stellar spin period are stored in the
        :attr:porb_initial and :attr:psurf attributes.

        Args:
            initial_eccentricity(float):    The initial eccentricity with which
                the secondary forms.

            evolve_kwargs:    Any additional keyword argument to pass to binary
                evolve.

        Returns:
            float:
                The difference between the predicted and measured values of the
                eccentricity.
        """

        final_eccentricity = self._match_target_state(
            initial_eccentricity=initial_eccentricity,
            initial_obliquity=self.system.obliquity,
            initial_secondary_angmom=initial_secondary_angmom,
            evolve_kwargs=self._get_combined_evolve_args(evolve_kwargs)
        ).eccentricity
        #pylint: enable=no-member

        return final_eccentricity - self.system.eccentricity

    def obliquity_difference(self,
                             initial_obliquity,
                             initial_secondary_angmom,
                             **evolve_kwargs):
        """Same as eccentricity_difference, but for obliquity."""

        final_obliquity = self._match_target_state(
            initial_eccentricity=self.system.eccentricity,
            initial_obliquity=initial_obliquity,
            initial_secondary_angmom=initial_secondary_angmom,
            evolve_kwargs=self._get_combined_evolve_args(evolve_kwargs)
        ).envelope_inclination
        #pylint: enable=no-member

        return final_obliquity - self.system.obliquity

    def get_found_evolution(self,
                            initial_eccentricity,
                            initial_obliquity,
                            max_age=None,
                            **evolve_kwargs):
        """
        Return the evolution matching the current system configuration.

        Args:
            initial_eccentricity:    The initial eccentricity found to reproduce
                the current state.

            max_age:    The age up to which to calculate the evolution. If None
                (default), the star lifetime is used.

            evolve_kwargs:    Additional keyword arguments to pass to
                Binary.evolve()

        Returns:
            See EccentricitySolverCallable._format_evolution().
        """

        evolve_kwargs = self._get_combined_evolve_args(evolve_kwargs)
        initial_secondary_angmom = self.get_secondary_initial_angmom(
            **evolve_kwargs
        )
        if self.porb_initial is None:
            return self._match_target_state(
                initial_eccentricity=initial_eccentricity,
                initial_obliquity=initial_obliquity,
                initial_secondary_angmom=initial_secondary_angmom,
                evolve_kwargs=evolve_kwargs,
                full_evolution=True
            )
        primary = self._create_primary()
        secondary = self._create_secondary()

        binary = self._create_system(
            primary,
            secondary,
            #False positive
            #pylint: disable=no-member
            porb_initial=self.porb_initial.to(units.day).value,
            #pylint: enable=no-member
            initial_eccentricity=initial_eccentricity,
            initial_obliquity=initial_obliquity,
            initial_secondary_angmom=initial_secondary_angmom
        )
        if max_age is None:
            if isinstance(primary, EvolvingStar):
                max_age = primary.lifetime()
            else:
                max_age = (self.system.age).to(units.Gyr).value
        else:
            max_age = max_age.to(units.Gyr).value

        binary.evolve(
            #False positive
            #pylint: disable=no-member
            max_age,
            self.configuration['max_timestep'],
            #pylint: enable=no-member
            **evolve_kwargs
        )

        result = self._format_evolution(binary,
                                        self.interpolator,
                                        self.secondary_star)

        primary.delete()
        secondary.delete()
        binary.delete()

        return result