def test_update_with_obj_autoremove(): begin_update_count = 0 def begin_update(domain): nonlocal begin_update_count begin_update_count += 1 end_update_count = 0 def end_update(domain): nonlocal end_update_count end_update_count += 1 f = Curve() # Add and remove observer using object obj = T() f.add_observer(obj, begin=begin_update, end=end_update, autoremove=True) f.begin_update(Interval(1, 3)) assert begin_update_count == 1 f.end_update(Interval(1, 3)) assert end_update_count == 1 obj = None f.begin_update(Interval(1, 3)) assert begin_update_count == 1 f.end_update(Interval(1, 3)) assert end_update_count == 1
def test_first(): funcs = [[(0, None), (1, 12), (2, None)], [(0, 1), (1, 1), (2, 1)]] first = Curve.first(funcs) assert first(-1) is None assert first(0) == 1 assert first(1) == 12 assert first(2) == 1 assert first(3) is None funcs = [[(0, None), (1, 12), (2, None)], 1] first = Curve.first(funcs) assert first(-1) == 1 assert first(0) == 1 assert first(1) == 12 assert first(2) == 1 assert first(3) == 1 first = Curve.first([ Generic(lambda x: 1, domain=Interval(0, 2)), Generic(lambda x: 2, domain=Interval.gte(1)) ]) assert first(-1) is None assert first(0) == 1 assert first(1) == 1 assert first(2) == 1 assert first(2.1) == 2 assert first(3) == 2 assert first.sample_points(Interval(0, 3), min_step=1) == [(0, 1), (1, 1), (2, 1), (3, 2)]
def test_round(): assert Interval(1.2, 3.4).round() == (1, 3) assert Interval(1.2, 3.4).round(method=math.floor) == (1, 3) assert Interval(1.2, 3.4).round(method=math.ceil) == (2, 4) assert Interval.open_closed(1.2, 3.4).round() == Interval.open_closed(1, 3) assert Interval.closed_open(1.2, 3.4).round() == Interval.closed_open(1, 3) assert Interval.empty().round() == Interval.empty()
def update_extension(self): if self.start: x = self.curve.domain.start if x is not None and self.curve.domain.start_open and self.curve.domain.contains(x + self.min_step): x += self.min_step y = None d_y = None if self.regression_degree is not None: x1 = x for i in range(self.regression_degree): x1 = self.curve.x_next(x1, min_step=self.min_step) domain = Interval(x, x1) tangent = self.curve.regression(domain, min_step=self.min_step) if tangent is not None: y = tangent.y(x) d_y = tangent.slope elif self.regression_period is not None: domain = Interval(x, x + self.regression_period) tangent = self.curve.regression(domain, min_step=self.min_step) if tangent is not None: y = tangent.y(x) d_y = tangent.slope else: y = self.curve.y(x) d_y = self.curve.d_y(x, forward=True) self.start_valid = y is not None and d_y is not None if self.start_valid: self.update_extension_func(self.start_func, x, y, d_y) if self.end: x = self.curve.domain.end if x is not None and self.curve.domain.end_open and self.curve.domain.contains(x - self.min_step): x -= self.min_step y = None d_y = None if self.regression_degree is not None: x0 = x for i in range(self.regression_degree): x0 = self.curve.x_previous(x0, min_step=self.min_step) domain = Interval(x0, x) tangent = self.curve.regression(domain, min_step=self.min_step) if tangent is not None: y = tangent.y(x) d_y = tangent.slope elif self.regression_period is not None: domain = Interval(x - self.regression_period, x) tangent = self.curve.regression(domain, min_step=self.min_step) if tangent is not None: y = tangent.y(x) d_y = tangent.slope else: y = self.curve.y(x) d_y = self.curve.d_y(x, forward=False) self.end_valid = y is not None and d_y is not None if self.end_valid: self.update_extension_func(self.end_func, x, y, d_y)
def append_list(self, points): """ `points` are assumed to be strictly ordered in ascending order w.r.t. to `x`. """ points_len = len(self._points) if points_len == 0: return self.set(points) new_points_len = len(points) if new_points_len == 0: return if points[0][0] <= self._points[-1][0]: raise Exception( 'Attempting to append points in non-ascending order') if self.domain.is_empty: domain = Interval.closed(points[0][0], points[-1][0]) else: domain = Interval(self.domain.end, points[-1][0], start_open=True, end_open=False) self.begin_update(domain) self._is_equally_spaced = self._points_equally_spaced( self._points, points) if self._force_equally_spaced and not self._is_equally_spaced: raise Exception( 'Attempting to append points at non-regular intervals: {}{} + {}{}' .format('...' if len(self._points) > 2 else '', self._points[len(self._points) - 2:], points[:2], '...' if len(points) > 2 else '')) self._points += points self._did_change_points() self.end_update(domain)
def get_domain(self): if len(self._points) == 0: return Interval.empty() return Interval(self._points[0][0], self._points[-1][0], start_open=False, end_open=False)
def span_interval(self, interval, start_open=False) -> Interval: """ Returns the time interval which fully contains the specified interval. """ interval = Interval.parse(interval, default_inf=True) if interval.is_empty: return Interval.empty() elif interval.start == interval.end: return self.span_date(interval.start, start_open=start_open) end_open = not start_open if interval.is_negative_infinite: start = -math.inf else: # Move outward if interval is closed but should be open o = not interval.start_open and start_open span = self.span_date(interval.start, start_open=o) start = span.start if interval.is_positive_infinite: end = math.inf else: # Move outward if interval is closed but should be open o = not interval.end_open and end_open span = self.span_date(interval.end, start_open=not o) end = span.end return Interval(start, end, start_open=start_open, end_open=not start_open)
def domain(self): if self._domain is None: self._domain = Interval(self.start_date, self.end_date, start_open=DOMAIN_START_OPEN, end_open=not DOMAIN_START_OPEN) return self._domain
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)
def test_equals(): d = Interval(1, 3) assert d.equals((1, 3)) assert not d.equals(None) assert not d.equals(Interval.closed_open(1, 3)) assert Interval.empty().equals(Interval.empty()) # Empty intervals are always equal assert Interval.open(1, 1).equals(Interval.open(2, 2)) assert Interval.infinite().equals(Interval.infinite())
def test_subset(): d = Interval(1, 3) assert d.is_subset_of((0, 4)) assert d.is_subset_of((1, 3)) assert not d.is_subset_of(Interval.closed_open(1, 3)) assert d.is_superset_of((2, 2)) assert d.is_superset_of((1, 3)) assert d.is_superset_of(Interval.closed_open(1, 3))
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 _offset_interval(self, domain, floor=False): start = self._offset_x(domain.start, floor=floor) end = self._offset_x(domain.end, floor=floor) if start is None and end is None: return Interval.empty() elif start is None: return Interval(start, end, start_open=False, end_open=domain.end_open) elif end is None: return Interval(start, end, start_open=domain.start_open, end_open=False) else: return Interval(start, end, start_open=domain.start_open, end_open=domain.end_open)
def next_uniform_span(): nonlocal start, step, i, count if i == count: return None a = start + i * step b = a + step i += 1 return Interval(min(a, b), max(a, b), start_open=start_open, end_open=not start_open)
def next_span(): nonlocal x1, count if limit is not None and count >= limit: return None count += 1 x0 = x1 x1 = self.step(x0, count=size, backward=backward) span = Interval(min(x0, x1), max(x0, x1), start_open=start_open, end_open=not start_open) return span
def get_range(self, domain=None, **kwargs): points = self.sample_points(domain=domain, **kwargs) low = None high = None for p in points: if low is None or p[1] < low: low = p[1] if high is None or p[1] > high: high = p[1] if low is None or high is None: return Interval.empty() return Interval(low, high)
def test_partition(): ds = Interval(1, 3).partition([2]) assert list(map(tuple, ds)) == [(1, 2), (2, 3)] assert not ds[0].start_open assert ds[0].end_open assert not ds[1].start_open assert not ds[1].end_open ds = Interval(0, 3).partition([0, 1, 2, 3, 4], start_open=True) assert list(map(tuple, ds)) == [(0, 0), (0, 1), (1, 2), (2, 3)] assert not ds[0].start_open assert not ds[0].end_open assert ds[1].start_open assert not ds[1].end_open ds = Interval(0, 3).partition([0, 1, 2, 3, 4], start_open=False) assert list(map(tuple, ds)) == [(0, 1), (1, 2), (2, 3), (3, 3)] assert not ds[0].start_open assert ds[0].end_open assert not ds[1].start_open assert ds[1].end_open
def timespan(t1, t2, open=None, start_open=None, end_open=None): t1 = timestamp(t1) t2 = timestamp(t2) if open is not None: start_open = bool(open) end_open = bool(open) elif start_open is not None: start_open = bool(start_open) end_open = not bool(start_open) elif end_open is not None: start_open = not bool(end_open) end_open = bool(end_open) return Interval(t1, t2, start_open=start_open, end_open=end_open)
def test_duplicate_multiple_nested_function_update(): points = Points([]) f = (points * 0.5 + points * 0.5) * 2 points.append((0, 1)) assert f.is_updating is False assert f.domain == Interval.point(0) assert f.y(0) == 2 points.append((1, 1)) assert f.is_updating is False assert f.domain == Interval(0, 1) assert f.y(1) == 2
def reset(self, domain=None): points_len = len(self._points) if points_len == 0: return if domain is None: return self.set([]) domain = Interval.parse(domain) if not self.domain.intersects(domain): return if domain.is_superset_of(self.domain): self.set([]) return remove_start_i, remove_end_i = self._domain_indexes(domain) if remove_start_i == remove_end_i: # Nothing to remove return head = self._points[:remove_start_i] tail = self._points[remove_end_i:] if len(head) != 0 and domain.contains(head[-1][0]): del head[-1] if len(tail) != 0 and domain.contains(tail[0][0]): del tail[0] head_len = len(head) tail_len = len(tail) if head_len + tail_len == 0: self.set([]) return points = head + tail update_start = self._points[0][0] update_start_open = False if head_len != 0: update_start = head[-1][0] update_start_open = True update_end = self._points[-1][0] update_end_open = False if tail_len != 0: update_end = tail[0][0] update_end_open = True update_domain = Interval(update_start, update_end, start_open=update_start_open, end_open=update_end_open) self.set(points, update_domain=update_domain)
def test_constant_step(): c1 = Constant(2) c2 = Constant(3) ds = Interval(0, 3).partition([2]) pw = Piecewise([c1, c2], ds) assert pw.domain.start == 0 assert pw.domain.end == 3 assert pw(-0.1) is None assert pw(0) == 2 assert pw(1.9) == 2 assert pw(2) == 3 assert pw(3) == 3 assert pw(3.1) is None assert np.allclose(pw.sample_points(domain=(0, 3), step=1), [(0, 2), (1, 2), (2, 3), (3, 3)])
def test_varying_step(): c1 = Constant(2) c2 = Points(test_util.point_gen([3, 4], t_start=2)) ds = Interval(1, 3).partition([2]) pw = Piecewise([c1, c2], ds) assert pw.domain.start == 1 assert pw.domain.end == 3 assert pw(0.9) is None assert pw(1) == 2 assert pw(1.9) == 2 assert pw(2) == 3 assert pw(3) == 4 assert pw(3.1) is None assert np.allclose(pw.sample_points(domain=(0, 3), step=0.5), [(1, 2), (1.5, 2), (2, 3), (2.5, 3.5), (3, 4)])
def test_duplicate_function_update(): points = Points([]) f = points + points points.append((0, 1)) assert points.is_updating is False assert f.is_updating is False assert f.domain == Interval.point(0) assert f.y(0) == 2 points.append((1, 1)) assert points.is_updating is False assert f.is_updating is False assert f.domain == Interval(0, 1) assert f.y(1) == 2
def pad(self, interval, start=0, end=0, start_open=False) -> Interval: """ Appends calendar lengths to the start and end of a interval. """ interval = self.span_interval(interval, start_open=start_open) if interval.is_empty: return Interval.empty() l = interval.start if not interval.is_negative_infinite: l = self.step(l, count=start, backward=True) h = interval.end if not interval.is_positive_infinite: h = self.step(h, count=end) return Interval(l, h, start_open=start_open, end_open=not start_open)
def test_extensions(): d = Interval(1, 3) assert d.get_lte().equals(Interval.lte(3)) assert d.get_gte().equals(Interval.gte(1)) assert d.get_lt().equals(Interval.lt(1)) assert d.get_gt().equals(Interval.gt(3)) d = Interval.open(1, 3) assert d.get_lte().equals(Interval.lt(3)) assert d.get_gte().equals(Interval.gt(1)) assert d.get_lt().equals(Interval.lte(1)) assert d.get_gt().equals(Interval.gte(3)) d = Interval.empty() assert d.get_lte().is_empty assert d.get_gte().is_empty assert d.get_lt().is_empty assert d.get_gt().is_empty
def test_parse(): d = Interval.parse(Interval(0, 1, start_open=True, end_open=True)) assert d.start == 0 assert d.end == 1 assert d.start_open assert d.end_open d = Interval.parse((0, 1)) assert d.start == 0 assert d.end == 1 assert not d.start_open assert not d.end_open d = Interval.parse(1) assert d.start == 1 assert d.end == 1 assert not d.start_open assert not d.end_open with pytest.raises(Exception): _ = Interval.parse(None) with pytest.raises(Exception): _ = Interval.parse(None, default_inf=False) assert Interval.parse(None, default_inf=True) == Interval.infinite() d = Interval.parse(math.inf) assert math.isinf(d.start) assert math.isinf(d.end) assert d.start > 0 assert d.end > 0 assert not d.is_negative_infinite assert not d.is_positive_infinite d = Interval.parse(-math.inf) assert math.isinf(d.start) assert math.isinf(d.end) assert d.start < 0 assert d.end < 0 assert not d.is_negative_infinite assert not d.is_positive_infinite d = Interval.parse([]) assert d.is_empty
def missing_domains(cls, quotes, domain=None): # TODO: optimise by recursively dividing quotes # in half and checking if the quotes are # contiguous. Then collect domains between # contiguous quotes. quotes_len = len(quotes) if quotes_len == 0: if domain is None: return [] else: return [domain] if domain is None: domain = cls.list_domain(quotes) else: domain = Interval.parse(domain) if domain.is_empty: return [] missing_list = [] head = Interval.intersection([domain, quotes[0].domain.get_lt()]) if not head.is_empty: missing_list.append(head) for i in range(1, quotes_len): q0 = quotes[i - 1] q1 = quotes[i] if q0.end_date != q1.start_date: if q0.end_date > q1.start_date: continue missing = Interval(q0.domain.end, q1.domain.start, start_open=not q0.domain.end_open, end_open=not q1.domain.start_open) missing_list.append(missing) tail = Interval.intersection([domain, quotes[-1].domain.get_gt()]) if not tail.is_empty: missing_list.append(tail) return missing_list
def _non_calendar_span(self, date, start_open=False) -> Interval: # We can use seconds coef = self._seconds_coef() t = self._normalized_date(date) * coef if self.degree == 1: # Do not limit to parent for speed parent_start = 0 parent_secs = math.inf else: # Start from parent start parent_span = self.parent.span_date(date, start_open=start_open) parent_start = parent_span.start * coef parent_secs = parent_span.length * coef seconds = self.ave_seconds * coef parent_t = t - parent_start start = math.floor(parent_t / seconds) * seconds end = math.ceil(parent_t / seconds) * seconds if start == end: if start_open: start -= seconds else: end += seconds # Limit to parent end if end > parent_secs: end = parent_secs start_date = parent_start + start end_date = parent_start + end start_date /= coef end_date /= coef return Interval(start_date, end_date, start_open=start_open, end_open=not start_open)
def test_func_update(): begin_update_count = 0 begin_update_interval = None def begin_update(domain): nonlocal begin_update_count nonlocal begin_update_interval begin_update_count += 1 begin_update_interval = domain end_update_count = 0 end_update_interval = None def end_update(domain): nonlocal end_update_count nonlocal end_update_interval end_update_count += 1 end_update_interval = domain f = Curve() t = f.add_observer(domain=(0, 2), begin=begin_update, end=end_update) f.begin_update(Interval(1, 3)) assert begin_update_count == 1 assert end_update_count == 0 assert begin_update_interval.start == 1 assert begin_update_interval.end == 3 f.end_update(Interval(1, 3)) assert begin_update_count == 1 assert end_update_count == 1 assert end_update_interval.start == 1 assert end_update_interval.end == 3 f.begin_update(Interval(3, 4)) assert begin_update_count == 1 assert end_update_count == 1 f.end_update(Interval(3, 4)) assert begin_update_count == 1 assert end_update_count == 1 f.remove_observer(t) f.begin_update(Interval(1, 3)) assert begin_update_count == 1 assert end_update_count == 1 f.end_update(Interval(1, 3)) assert begin_update_count == 1 assert end_update_count == 1
def span(t1, t2, start_open=False): return Interval(t_(t1), t_(t2), start_open=start_open, end_open=not start_open)