Skip to content

File SolarRadiationPressure.cpp

File List > astrea > astro > astro > propagation > force_models > SolarRadiationPressure.cpp

Go to the documentation of this file

/*
 * The GNU Lesser General Public License (LGPL)
 *
 * Copyright (c) 2025 Jay Iuliano
 *
 * This file is part of Astrea.
 * Astrea is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License
 * as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
 * Astrea is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should
 * have received a copy of the GNU General Public License along with Astrea. If not, see <https://www.gnu.org/licenses/>.
 */

#include <astro/propagation/force_models/SolarRadiationPressure.hpp>

#include <mp-units/math.h>
#include <mp-units/systems/angular/math.h>
#include <mp-units/systems/iau.h>

#include <astro/platforms/Vehicle.hpp>
#include <astro/state/State.hpp>
#include <astro/state/orbital_elements/OrbitalElements.hpp>
#include <astro/state/orbital_elements/instances/Cartesian.hpp>
#include <astro/systems/AstrodynamicsSystem.hpp>
#include <astro/types/enums.hpp>

namespace astrea {
namespace astro {

using namespace mp_units;

using mp_units::pow;
using mp_units::angular::acos;
using mp_units::angular::asin;

using mp_units::one;
using mp_units::non_si::unit_symbols::au;
using mp_units::si::unit_symbols::kg;
using mp_units::si::unit_symbols::km;
using mp_units::si::unit_symbols::m;
using mp_units::si::unit_symbols::N;
using mp_units::si::unit_symbols::s;

AccelerationVector<frames::earth::icrf> SolarRadiationPressure::compute_force(const State& state, const Vehicle& vehicle) const
{
    // Extract
    const AstrodynamicsSystem& sys       = state.get_system();
    const Date date                      = state.get_epoch();
    const CelestialBodyUniquePtr& center = sys.get_central_body();
    const CelestialBodyUniquePtr& sun    = sys.add_body(CelestialBodyId::SUN);

    const RadiusVector<frames::earth::icrf> rCenterToVehicle = state.get_position();
    const Distance rMagCenterToVehicle                       = rCenterToVehicle.norm();

    // Central body properties
    const bool isSun = (center->get_id() == CelestialBodyId::SUN);

    // Find day nearest to current time
    const RadiusVector<frames::solar_system_barycenter::icrf> rSsbToCenter = center->get_position_at(date);
    const RadiusVector<frames::solar_system_barycenter::icrf> rSsbToSun    = sun->get_position_at(date);

    // Radius from central body to sun
    const RadiusVector<frames::earth::icrf> rCenterToSun =
        (rSsbToSun - rSsbToCenter).force_frame_conversion<frames::earth::icrf>(); // TODO: Should this use the translate function? I hate that function.
    const Distance rMagCenterToSun = rCenterToSun.norm();

    const RadiusVector<frames::earth::icrf> rVehicleToSun = rCenterToSun - rCenterToVehicle;
    const Distance rMagVehicleToSun                       = rVehicleToSun.norm();

    // Average solar radiation pressure at 1 AU scaled to average distance from Sun
    static const quantity<N / pow<2>(m)> srpAtOneAU = 4.556485540406757e-6 * N / pow<2>(m);
    const quantity<N / pow<2>(m)> srp =
        srpAtOneAU * (au * au) / (rMagVehicleToSun * rMagVehicleToSun); // Scale by(1AU/R)^2 for other bodies

    // Scale by umbria/penumbra
    Unitless fractionOfRecievedSunlight = 1.0 * one;
    if (!isSun) {
        static const Distance& equitorialR = center->get_equitorial_radius();

        //  This part calculates the angle between the occulating body and the Sun, the body and the satellite, and the Sun and the
        //  satellite. It then compares them to decide if the s/c is lit, in umbra, or in penumbra. See Vallado for details.
        const Angle refAngle  = acos(rCenterToSun.dot(rCenterToVehicle) / (rMagCenterToSun * rMagCenterToVehicle));
        const Angle refAngle1 = acos(equitorialR / rMagCenterToVehicle);
        const Angle refAngle2 = acos(equitorialR / rMagCenterToSun);

        if (refAngle1 + refAngle2 <= refAngle) { // In shadow
            static const Distance diamSun = 696000.0 * km;
            const Distance Xu             = equitorialR * rMagCenterToSun / (diamSun - equitorialR);

            const RadiusVector<frames::earth::icrf> rP = -Xu * rCenterToSun / rMagCenterToSun;
            const Distance normRP                      = rP.norm();

            const RadiusVector<frames::earth::icrf> rPs = rCenterToVehicle - rP;
            const Distance normRPs                      = rPs.norm();
            const Angle alphaps                         = abs(asin(-rPs.dot(rP) / (normRP * normRPs)));

            if (alphaps < asin(equitorialR / Xu)) { // Umbra
                fractionOfRecievedSunlight = 0.0 * one;
            }
            else { // Penumbra
                fractionOfRecievedSunlight = 0.5 * one;
            }
        }
    }

    // accel due to srp
    const Unitless coefficientOfReflectivity = vehicle.get_coefficient_of_reflectivity();
    const SurfaceArea areaSun                = vehicle.get_solar_area();
    const Mass mass                          = vehicle.get_mass();
    const Acceleration accelRelMag = -srp * fractionOfRecievedSunlight * coefficientOfReflectivity * areaSun / mass;

    return accelRelMag * rVehicleToSun / rMagVehicleToSun;
}

} // namespace astro
} // namespace astrea