Umbau auf server
This commit is contained in:
@@ -0,0 +1,225 @@
|
||||
"""Routines to search for maxima and zero crossings."""
|
||||
|
||||
from __future__ import print_function, division
|
||||
|
||||
from numpy import (add, append, argsort, bool_, concatenate, diff, flatnonzero,
|
||||
int8, issubdtype, linspace, multiply, reshape, sign)
|
||||
from .constants import DAY_S
|
||||
EPSILON = 0.001 / DAY_S
|
||||
|
||||
_trace = None # User can replace with a routine to save search iterations.
|
||||
|
||||
def find_discrete(start_time, end_time, f, epsilon=EPSILON, num=12):
|
||||
"""Find the times at which a discrete function of time changes value.
|
||||
|
||||
This routine is used to find instantaneous events like sunrise,
|
||||
transits, and the seasons. See :doc:`searches` for how to use it
|
||||
yourself.
|
||||
|
||||
"""
|
||||
ts = start_time.ts
|
||||
jd0 = start_time.tt
|
||||
jd1 = end_time.tt
|
||||
if jd0 >= jd1:
|
||||
raise ValueError('your start_time {0} is later than your end_time {1}'
|
||||
.format(start_time, end_time))
|
||||
|
||||
step_days = getattr(f, 'step_days', None)
|
||||
if step_days is None:
|
||||
# Legacy "rough_period" attribute.
|
||||
rough_period = getattr(f, 'rough_period', None)
|
||||
if rough_period is None:
|
||||
raise AttributeError('the function you have passed'
|
||||
' is missing a "step_days" attribute')
|
||||
periods = (jd1 - jd0) / rough_period
|
||||
if periods < 1.0:
|
||||
periods = 1.0
|
||||
sample_count = int(periods * num)
|
||||
else:
|
||||
# Insist on at least 2 samples even if the dates are less than
|
||||
# step_days apart, so the range at least has endpoints.
|
||||
sample_count = int((jd1 - jd0) / step_days) + 2
|
||||
|
||||
jd = linspace(jd0, jd1, sample_count)
|
||||
return _find_discrete(ts, jd, f, epsilon, num)
|
||||
|
||||
# TODO: pass in `y` so it can be precomputed?
|
||||
|
||||
def _find_discrete(ts, jd, f, epsilon, num):
|
||||
"""Algorithm core, for callers that already have a `jd` vector."""
|
||||
end_mask = linspace(0.0, 1.0, num)
|
||||
start_mask = end_mask[::-1]
|
||||
o = multiply.outer
|
||||
|
||||
while True:
|
||||
t = ts.tt_jd(jd)
|
||||
y = f(t)
|
||||
|
||||
indices = flatnonzero(diff(y))
|
||||
if not len(indices):
|
||||
# Nothing found, so immediately return empty arrays.
|
||||
ends = jd[indices]
|
||||
y = y[indices]
|
||||
break
|
||||
|
||||
starts = jd[indices]
|
||||
ends = jd[indices + 1]
|
||||
|
||||
if (ends - starts).max() <= epsilon:
|
||||
y = y[indices + 1]
|
||||
break
|
||||
|
||||
jd = o(starts, start_mask).flatten() + o(ends, end_mask).flatten()
|
||||
|
||||
return ts.tt_jd(ends), _fix_numpy_deprecation(y)
|
||||
|
||||
def find_minima(start_time, end_time, f, epsilon=1.0 / DAY_S, num=12):
|
||||
"""Find the local minima in the values returned by a function of time.
|
||||
|
||||
This routine is used to find events like minimum elongation. See
|
||||
:doc:`searches` for how to use it yourself.
|
||||
|
||||
"""
|
||||
def g(t): return -f(t)
|
||||
g.rough_period = getattr(f, 'rough_period', None)
|
||||
g.step_days = getattr(f, 'step_days', None)
|
||||
t, y = find_maxima(start_time, end_time, g, epsilon, num)
|
||||
return t, _fix_numpy_deprecation(-y)
|
||||
|
||||
def find_maxima(start_time, end_time, f, epsilon=1.0 / DAY_S, num=12):
|
||||
"""Find the local maxima in the values returned by a function of time.
|
||||
|
||||
This routine is used to find events like highest altitude and
|
||||
maximum elongation. See :doc:`searches` for how to use it yourself.
|
||||
|
||||
"""
|
||||
# @@ @@_@@ @@_@@_@@_@@
|
||||
# / \ / \ / \
|
||||
# @@ @@ @@ @@ @@ @@
|
||||
# +1 -1 +1 0 -1 +1 0 0 0 -1 sd = sign(diff(y))
|
||||
# -2 -1 -1 -1 0 0 -1 diff(sign(diff(y))
|
||||
|
||||
ts = start_time.ts
|
||||
jd0 = start_time.tt
|
||||
jd1 = end_time.tt
|
||||
|
||||
if jd0 >= jd1:
|
||||
raise ValueError('start_time {0} is not earlier than end_time {1}'
|
||||
.format(start_time, end_time))
|
||||
|
||||
# We find maxima by investigating every point that is higher than
|
||||
# both points next to it. This presents a problem: if the initial
|
||||
# heights are, for example, [1.7, 1.1, 0.3, ...], there might be a
|
||||
# maximum 1.8 hidden between the first two heights, but it would not
|
||||
# meet the criteria for further investigation because we can't see
|
||||
# whether the curve is on its way up or down to the left of 1.7. So
|
||||
# we put an extra point out beyond each end of our range, then
|
||||
# filter our final result to remove maxima that fall outside the
|
||||
# range.
|
||||
step_days = getattr(f, 'step_days', None)
|
||||
if step_days is None:
|
||||
bump = f.rough_period / num
|
||||
bumps = int((jd1 - jd0) / bump) + 3
|
||||
jd = linspace(jd0 - bump, jd1 + bump, bumps)
|
||||
else:
|
||||
# Insist on at least 3 samples, even for very close dates; and
|
||||
# add 2 more to stand outside the range.
|
||||
steps = int((jd1 - jd0) / step_days) + 3
|
||||
real_step = (jd1 - jd0) / steps
|
||||
jd = linspace(jd0 - real_step, jd1 + real_step, steps + 2)
|
||||
|
||||
end_alpha = linspace(0.0, 1.0, num)
|
||||
start_alpha = end_alpha[::-1]
|
||||
o = multiply.outer
|
||||
|
||||
while True:
|
||||
t = ts.tt_jd(jd)
|
||||
y = f(t)
|
||||
|
||||
# Since we start with equal intervals, they all should fall
|
||||
# below epsilon at around the same time; so for efficiency we
|
||||
# only test the first pair.
|
||||
if t[1] - t[0] <= epsilon:
|
||||
jd, y = _identify_maxima(jd, y)
|
||||
|
||||
# Filter out maxima that fell slightly outside our bounds.
|
||||
keepers = (jd >= jd0) & (jd <= jd1)
|
||||
jd = jd[keepers]
|
||||
y = y[keepers]
|
||||
|
||||
# Keep only the first of several maxima that are separated
|
||||
# by less than epsilon.
|
||||
if len(jd):
|
||||
mask = concatenate(((True,), diff(jd) > epsilon))
|
||||
jd = jd[mask]
|
||||
y = y[mask]
|
||||
|
||||
break
|
||||
|
||||
left, right = _choose_brackets(y)
|
||||
|
||||
if _trace is not None:
|
||||
_trace((t, y, left, right))
|
||||
|
||||
if not len(left):
|
||||
# No maxima found.
|
||||
jd = y = y[0:0]
|
||||
break
|
||||
|
||||
starts = jd[left]
|
||||
ends = jd[right]
|
||||
|
||||
jd = o(starts, start_alpha).flatten() + o(ends, end_alpha).flatten()
|
||||
jd = _remove_adjacent_duplicates(jd)
|
||||
|
||||
return ts.tt_jd(jd), _fix_numpy_deprecation(y)
|
||||
|
||||
def _choose_brackets(y):
|
||||
"""Return the indices between which we should search for maxima of `y`."""
|
||||
dsd = diff(sign(diff(y)))
|
||||
indices = flatnonzero(dsd < 0)
|
||||
left = reshape(add.outer(indices, [0, 1]), -1)
|
||||
left = _remove_adjacent_duplicates(left)
|
||||
right = left + 1
|
||||
return left, right
|
||||
|
||||
def _identify_maxima(x, y):
|
||||
"""Return the maxima we can see in the series y as simple points."""
|
||||
dsd = diff(sign(diff(y)))
|
||||
|
||||
# Choose every point that is higher than the two adjacent points.
|
||||
indices = flatnonzero(dsd == -2) + 1
|
||||
peak_x = x[indices]
|
||||
peak_y = y[indices]
|
||||
|
||||
# Also choose the midpoint between the edges of a plateau, if both
|
||||
# edges are in view. First we eliminate runs of zeroes, then look
|
||||
# for adjacent -1 values, then map those back to the main array.
|
||||
indices = flatnonzero(dsd)
|
||||
dsd2 = dsd[indices]
|
||||
minus_ones = dsd2 == -1
|
||||
plateau_indices = flatnonzero(minus_ones[:-1] & minus_ones[1:])
|
||||
plateau_left_indices = indices[plateau_indices]
|
||||
plateau_right_indices = indices[plateau_indices + 1] + 2
|
||||
plateau_x = x[plateau_left_indices] + x[plateau_right_indices]
|
||||
plateau_x /= 2.0
|
||||
plateau_y = y[plateau_left_indices + 1]
|
||||
|
||||
x = concatenate((peak_x, plateau_x))
|
||||
y = concatenate((peak_y, plateau_y))
|
||||
indices = argsort(x)
|
||||
return x[indices], y[indices]
|
||||
|
||||
def _remove_adjacent_duplicates(a):
|
||||
if not len(a):
|
||||
return a
|
||||
mask = diff(a) != 0
|
||||
mask = append(mask, [True])
|
||||
return a[mask]
|
||||
|
||||
def _fix_numpy_deprecation(y):
|
||||
# Alas, in the future NumPy will apparently disallow using Booleans
|
||||
# as indices, whereas we often encourage the use of `y` as an index.
|
||||
if issubdtype(y.dtype, bool_):
|
||||
y.dtype = int8
|
||||
return y
|
||||
Reference in New Issue
Block a user