""" Domain of the spacecraft
Domains (solar wind, induced magnetosphere, magnetosheath) are handled.
"""
import datetime as _datetime
import logging as _logging
from irfpy.mars import bowshock as _bs
from irfpy.mars import mpb as _mpb
from irfpy.mars import radius as _rm
from irfpy.mexpvat import mexspice as _ms
from irfpy.mexpvat import orbnum as _onr
from irfpy.asperacommon.domain_common import bisect_boundary_crossing as _boundary_crossing
[docs]def inside_bowshock(t, model=None, model_param=None):
""" Return if the spacecraft is inside the bow shock.
:param t:
:keyword model: The model used. As default, use the Vignes model
https://irfpy.irf.se/projects/planets/api/api_irfpy.mars.bowshock.html
is used.
As a default, Vignes original model is used, and only implemented
:keyword model_param: Parameter to pass to the model. Dictionary.
For example, {"scale": 1.3} will give the scale parameter to Vignes model.
:return: *True* if the MEX is inside the bow shock.
It include the magnetosheath,
induced magnetosphere, magnetotail, or innosphere
>>> import datetime
>>> t = datetime.datetime(2009, 2, 13, 8)
>>> print(inside_bowshock(t))
True
>>> t = datetime.datetime(2009, 2, 13, 12)
>>> print(inside_bowshock(t))
False
>>> inside_bowshock(t, model_param={"scale": 4.5})
True
"""
_ms.init() # For MEX, it is ok if multiple load was done.
pos = _ms.get_position(t, target='MEX', origin='MARS')
pos /= _rm # Now in Rm
if model_param is None:
model_param = {}
model = _bs.BowshockVignesScaled(**model_param)
return model.inside(pos[0], pos[1], pos[2])
[docs]def inside_mpb(t, model=None):
""" Return if the spacecraft is inside MPB
:param t:
:keyword model: The model used. As default, use the Vignes model
https://irfpy.irf.se/projects/planets/api/api_irfpy.mars.mpb.html
is used. **Currently, only Vignes model is implemented**
:return: *True* if the MEX is inside the MPB.
It include the induced magnetosphere,
magnetotail, or ionosphere.
>>> import datetime
>>> t = datetime.datetime(2009, 2, 13, 9)
>>> print(inside_mpb(t))
True
>>> t = datetime.datetime(2009, 2, 13, 12)
>>> print(inside_mpb(t))
False
"""
_ms.init()
pos = _ms.get_position(t, target='MEX', origin='MARS')
pos /= _rm # Now in Rm
return _mpb.MpbVignes.inside(pos[0], pos[1], pos[2])
[docs]def inside_eclipse(t):
r""" Return if the spacecraft is inside the geometric eclipse.
:param t: Time
:returns: Boolean value, *True* if the spacecraft is in the eclipse.
The definition of the eclipse is just geometric:
:math:`R(=\sqrt{x^2+y^2+z^2})\ge 1 [Rm]` and :math:`x<0`
and :math:`r(=\sqrt{y^2+z^2})\le 1 [Rm]`
where ``R`` is the radius and ``r`` is the distance from x-axis.
>>> import datetime
>>> t = datetime.datetime(2009, 2, 13, 10)
>>> print(inside_eclipse(t))
True
>>> t = datetime.datetime(2009, 2, 13, 11)
>>> print(inside_eclipse(t))
False
"""
_ms.init()
pos = _ms.get_position(t, target='MEX', origin='MARS')
x, y, z = pos / _rm # Now in Rm
if x >= 0:
return False
r3_2 = x ** 2 + y ** 2 + z ** 2
if r3_2 < 1:
return False
r2_2 = y ** 2 + z ** 2
if r2_2 > 1:
return False
return True
[docs]def bowshock_crossings(orbit_number, dt_survey=600, tolerance=0.1, model=None, model_param=None):
""" Return the list of bowshock crossing time for the specific orbit
:param orbit_number: Orbit nubmer. Int.
:param dt_survey: Time difference for surveying the position.
Every ``dt_survey`` seconds, the function calculates the position
and evaluate if the spacecraft cross the boundary.
If crossed, the detailed calculation is done when it is crossed, down
to ``tolerance`` seconds accuracy.
:param tolerance: The tolerance in seconds.
:param model: Model name, while only Vignes model is implemented.
:param model_param: Model parameter. {"scale": 1.3} gives larger bowshock by 30%.
:return: List of bow shock crossing times
This function returns the bowshock crossing times as a list.
>>> bowshock_crossings(900)
[datetime.datetime(2004, 10, 1, 17, 45, 20, 301269), datetime.datetime(2004, 10, 1, 20, 29, 27, 859863)]
>>> bowshock_crossings(900, model_param={"scale": 0.8})
[datetime.datetime(2004, 10, 1, 17, 56, 3, 807129), datetime.datetime(2004, 10, 1, 20, 6, 19, 554199)]
>>> bowshock_crossings(900, model_param={"scale": 1.2})
[datetime.datetime(2004, 10, 1, 17, 32, 27, 962402), datetime.datetime(2004, 10, 1, 20, 54, 43, 460449)]
>>> bowshock_crossings(900, model_param={"scale": 4}) # Unreasonably wide bow shock
[]
"""
# Due to the difference of orbit from VEX, MEX cannot determine the
# inbound and outbound crossings. So the number of crossing depends on
# the orbit.
logger = _logging.getLogger(__name__)
insidefunc = lambda t: inside_bowshock(t, model=model, model_param=model_param)
t_now = t0 = _onr.get_start_time(orbit_number)
t1 = _onr.get_stop_time(orbit_number)
# Survey the position, and specify the time of crossing roughly.
dt_survey = _datetime.timedelta(seconds=dt_survey)
t_prev = t0
c_prev = insidefunc(t_prev)
crossing_time = []
while t_now <= t1:
t_now += dt_survey
c_now = insidefunc(t_now)
logger.debug('Survey: t_prev={} c_prev={}'.format(t_prev, c_prev))
logger.debug('Survey: t_now={} c_now={}'.format(t_prev, c_prev))
if c_now is not c_prev:
logger.debug('Survey: CROSSED!')
crossing_time.append(t_prev)
t_prev = t_now
c_prev = c_now
# Calculate the time of crossing more precisely.
exact_crossing_time = []
for t_cross in crossing_time:
tc = _boundary_crossing(t_cross, t_cross + dt_survey, insidefunc, tolerance=tolerance)
exact_crossing_time.append(tc)
return exact_crossing_time
[docs]def mpb_crossings(orbit_number, dt_survey=600, tolerance=0.1):
""" Return the list of MPB crossing time for the specific orbit
:param orbit_number: Orbit nubmer. Int.
:param dt_survey: Time difference for surveying the position.
Every ``dt_survey`` seconds, the function calculates the position
and evaluate if the spacecraft cross the boundary.
If crossed, the detailed calculation is done when it is crossed, down
to ``tolerance`` seconds accuracy.
:param tolerance: The tolerance in seconds.
:return: List of MPB crossing times
This function returns the MPB crossing times as a list.
>>> mpb_crossings(900)
[datetime.datetime(2004, 10, 1, 18, 9, 57, 449707), datetime.datetime(2004, 10, 1, 19, 16, 18, 235840)]
"""
# Due to the difference of orbit from VEX, MEX cannot determine the
# inbound and outbound crossings. So the number of crossing depends on
# the orbit.
logger = _logging.getLogger(__name__)
t_now = t0 = _onr.get_start_time(orbit_number)
t1 = _onr.get_stop_time(orbit_number)
# Survey the position, and specify the time of crossing roughly.
dt_survey = _datetime.timedelta(seconds=dt_survey)
t_prev = t0
c_prev = inside_mpb(t_prev)
crossing_time = []
while t_now <= t1:
t_now += dt_survey
c_now = inside_mpb(t_now)
logger.debug('Survey: t_prev={} c_prev={}'.format(t_prev, c_prev))
logger.debug('Survey: t_now={} c_now={}'.format(t_prev, c_prev))
if c_now is not c_prev:
logger.debug('Survey: CROSSED!')
crossing_time.append(t_prev)
t_prev = t_now
c_prev = c_now
# Calculate the time of crossing more precisely.
exact_crossing_time = []
for t_cross in crossing_time:
tc = _boundary_crossing(t_cross, t_cross + dt_survey, inside_mpb, tolerance=tolerance)
exact_crossing_time.append(tc)
return exact_crossing_time