Exemplo n.º 1
0
    def _calendar_span(self, date, start_open=False) -> Interval:
        """
        Returns the time interval which contains the specified `date`.
        """
        date = arrow.get(date)
        t = self._normalized_date(date)
        msec = timedelta(microseconds=1)
        start = date - msec
        end = date + msec
        degree = self.degree
        unit = self.unit
        sunit = type(self).singular_unit(unit)
        date_span = None

        force_agr = unit == WEEKS
        if force_agr:
            # Special case
            sunit = type(self).singular_unit(DAYS)

        # Find matching unit interval (1d, 1h, etc)
        for r0, r1 in arrow.Arrow.span_range(sunit, start, end):
            # Normalize range end
            r1 += msec
            interval = Interval(r0.float_timestamp,
                                r1.float_timestamp,
                                start_open=start_open,
                                end_open=not start_open)
            if interval.contains(t):
                if degree == 1 and not force_agr:
                    return interval
                else:
                    date_span = r0, r1
                    break

        # Expand into aggregate interval
        if self.unit == YEARS:
            start_date, end_date = self._expand_years(date_span)
        elif self.parent is not None and self.parent.unit == YEARS:
            start_date, end_date = self._expand_within_year(date_span)
        else:
            raise Exception(f'Unable to expand unit: {self.unit}')

        return Interval(self._normalized_date(start_date),
                        self._normalized_date(end_date),
                        start_open=start_open,
                        end_open=not start_open)
Exemplo n.º 2
0
class Extremas(Scan):

    # TODO: Extremas doesn't need to be a subclass of `Scan` as it only needs to find the local extrema about a point.

    def __init__(self, func, ref_func, min_deviation=0, min_step=MIN_STEP):
        self.ref_func = Curve.parse(ref_func)
        self.min_deviation = abs(min_deviation)
        self.extremas = []
        self.extrema_xs = []
        self.extrema_interval = Interval.empty()
        self.possible_extrema = None
        self.possible_extrema_phase = None
        self._did_update_extremas()
        super().__init__(func, self._extrema_scan, min_step=min_step)
        self._ref_observer_token = self.ref_func.add_observer(
            begin=self.begin_update, end=self.end_update)

    def __del__(self):
        self.ref_func.remove_observer(self._ref_observer_token)

    def scanned_y(self, x):
        if not self.extrema_interval.contains(x):
            return None
        if x == self.extrema_interval.start:
            return self.extremas[0][1]
        elif x == self.extrema_interval.end:
            return self.extremas[-1][1]
        i = bisect.bisect(self.extrema_xs, x)
        p1 = self.extremas[i - 1]
        p2 = self.extremas[i]
        u = (x - p1[0]) / (p2[0] - p1[0])
        return (1 - u) * p1[1] + u * p2[1]

    def continue_scan(self, x):
        if super().continue_scan(x):
            return True
        if not self.domain.contains(self.current):
            return False
        return not self.extrema_interval.contains(x, enforce_start=False)

    def x_previous(self, x, min_step=MIN_STEP, limit=None):
        min_step = self.resolve_min_step(min_step)
        if self.extrema_interval.contains(x - min_step):
            i = bisect.bisect_left(self.extrema_xs, x - min_step)
            x1 = self.extremas[i][0]
            if x1 > x - min_step:
                x1 = self.extremas[i - 1][0]
            return x1
        return self.curve.x_previous(x, min_step=min_step, limit=limit)

    def x_next(self, x, min_step=MIN_STEP, limit=None):
        min_step = self.resolve_min_step(min_step)
        if self.extrema_interval.contains(x + min_step):
            i = bisect.bisect_left(self.extrema_xs, x + min_step)
            x1 = self.extremas[i][0]
            if x1 < x + min_step:
                x1 = self.extremas[i + 1][0]
            return x1
        return self.curve.x_next(x, min_step=min_step, limit=limit)

    def begin_update(self, domain):
        super().begin_update(domain)
        # remove stale points
        for i in reversed(range(len(self.extremas))):
            x = self.extrema_xs[i]
            if domain.contains(x):
                self._remove_extrema_index(i)
            elif domain.start > x:
                break

        # scan from last extrema
        extrema_count = len(self.extremas)
        if extrema_count == 0:
            self.current = None
        else:
            last_extrema = self.extremas[-1]
            x = last_extrema[0]
            self.current = x
            self.possible_extrema = last_extrema
            self.possible_extrema_phase = last_extrema[1] - self.ref_func(x)

    def sample_points(self, domain=None, min_step=MIN_STEP, step=None):
        points = super().sample_points(domain=domain,
                                       min_step=min_step,
                                       step=step)
        return list(filter(lambda p: p[1] is not None, points))

    def _extrema_scan(self, x, y):
        if y is None:
            return
        y0 = self.ref_func(x)
        if y0 is None:
            return
        div = y / y0 - 1
        if abs(div) <= self.min_deviation:
            # function is too close to reference function
            return
        phase = y - y0
        if self.possible_extrema is None:
            self.possible_extrema = (x, y)
            self.possible_extrema_phase = phase
            return
        is_possible_extrema = False
        if self.possible_extrema_phase * phase < 0:
            # phase inflection
            self._confirm_extrema()
            is_possible_extrema = True
        elif (phase > 0 and y > self.possible_extrema[1]) or (
                phase < 0 and y < self.possible_extrema[1]):
            is_possible_extrema = True
        if is_possible_extrema:
            self.possible_extrema = (x, y)
            self.possible_extrema_phase = phase

    def _remove_extrema_index(self, i):
        del self.extremas[i]
        del self.extrema_xs[i]
        self._did_update_extremas()

    def _confirm_extrema(self):
        extrema = self.possible_extrema

        self.extremas.append(extrema)
        self.extrema_xs.append(extrema[0])
        self._did_update_extremas()

    def _did_update_extremas(self):
        self.possible_extrema = None
        self.possible_extrema_phase = None

        if len(self.extremas) == 0:
            self.extrema_interval = Interval.empty()
        else:
            self.extrema_interval = Interval(self.extrema_xs[0],
                                             self.extrema_xs[-1],
                                             start_open=False,
                                             end_open=False)

    def _i(self, x):
        if not self.extrema_interval.contains(x):
            return None
        if x == self.extrema_interval.start:
            return 0
        elif x == self.extrema_interval.end:
            return len(self.extremas) - 1
        i = bisect.bisect(self.extrema_xs, x)
        p1 = self.extremas[i - 1]
        p2 = self.extremas[i]
        u = (x - p1[0]) / (p2[0] - p1[0])
        return i + u
Exemplo n.º 3
0
def test_intersection():
    # closed, closed
    d1 = Interval(0, 2, start_open=False, end_open=False)
    d2 = Interval(1, 3, start_open=False, end_open=False)

    assert d1.contains(0)
    assert d1.contains(1)
    assert d1.contains(2)

    d = Interval.intersection([d1, d2])
    assert d.start == 1
    assert d.end == 2
    assert not d.start_open
    assert not d.end_open

    d = Interval.union([d1, d2])
    assert d.start == 0
    assert d.end == 3
    assert not d.start_open
    assert not d.end_open

    # closed, open
    d1 = Interval(0, 2, start_open=False, end_open=False)
    d2 = Interval(1, 3, start_open=True, end_open=True)

    d = Interval.intersection([d1, d2])
    assert d.start == 1
    assert d.end == 2
    assert d.start_open
    assert not d.end_open

    d = Interval.union([d1, d2])
    assert d.start == 0
    assert d.end == 3
    assert not d.start_open
    assert d.end_open

    # open, open
    d1 = Interval(0, 2, start_open=True, end_open=True)
    d2 = Interval(1, 3, start_open=True, end_open=True)

    assert not d1.contains(0)
    assert d1.contains(1)
    assert not d1.contains(2)

    d = Interval.intersection([d1, d2])
    assert d.start == 1
    assert d.end == 2
    assert d.start_open
    assert d.end_open

    d = Interval.union([d1, d2])
    assert d.start == 0
    assert d.end == 3
    assert d.start_open
    assert d.end_open

    d = Interval.intersection([Interval(0, 1), Interval(2, 3)])
    assert d.is_empty

    d = Interval.intersection([Interval(0, 1, end_open=True), Interval(1, 3, start_open=True)])
    assert d.is_empty

    d = Interval.intersection([Interval(0, 1), Interval.empty()])
    assert d.is_empty

    d = Interval.union([Interval.empty(), 1])
    assert d.start == 1
    assert d.end == 1