diff --git a/docs/impulse/docs/config/configuration.md b/docs/impulse/docs/config/configuration.md index 3ad82f76..ccab0582 100644 --- a/docs/impulse/docs/config/configuration.md +++ b/docs/impulse/docs/config/configuration.md @@ -141,6 +141,7 @@ Two independent filter families: | `drop_implausible_data` | `bool` | `false` | When `true`, drops `channels` rows where `is_plausible = false`. Requires `data_type = "RAW"`; combining with `"RLE"` raises a validation error. | | `max_channels_per_batch` | `int` | `500` | Caps how many distinct channels are read in one solve batch, to bound per-solve memory. Counts channel selections (each distinct `channel(...)` / `poi_channel(...)` in your expressions). Applies to events, aggregations, and calculated channels; for calculated channels it counts the *input* channels they read, not the number of calculated channels. The cap is a packing target, not a hard limit: an expression that on its own reads more channels than the cap still runs, in a batch of its own. Must be `>= 1`. The old name `batch_size` is a **deprecated alias** (still accepted, warns, removed in a future release); setting both raises a validation error. | | `max_containers_per_batch` | `int` | `null` | Caps how many containers are solved at once, to bound per-solve memory. `null` disables the cap; when set it must be `>= 1`. On an **incremental** run, `Report.run()` commits at most this many upserted containers per iteration and loops until the population is drained (see [incremental](#incremental-optional)). On a **full** run, and when a changed definition recomputes over all containers, the population is split into chunks of about this size and solved chunk by chunk. Chunk sizes are approximate (the cap is a memory heuristic, not an exact bound). | +| `enable_pushdown` | `bool` | `false` | When `true`, supported expression subtrees (currently `channel.resample(rate)` on a physical channel) are computed natively in Spark before the pandas evaluation, so fewer rows are shipped to it. A channel is only pushed down when every use of it in a solve batch is; results are unchanged. Meant for downsampling: upsampling produces more rows than the raw channel. See [Spark pushdown](../references/query_engine/query_solvers.md#spark-pushdown-opt-in). | | `solver_config` | `SolverConfig` | `null` | Per-table column mappings, per-table equality filters, and project scoping. Set `project_id` to scope reads by project — it is applied to `container_tags` (if configured), `container_metrics`, and `channel_mapping` (if configured), so it works in both narrow EAV and wide-only data models. Omit it when you don't need project scoping. See [Solver column mappings and filters](#solver-column-mappings-and-filters). | | `measurement_db` | `str` | `"MeasurementDB"` | Registered name of the MeasurementDB implementation to use. The default is the built-in reader; a custom implementation can reshape a different source schema at read time. See [Custom measurement DB](../extensibility/custom_measurement_db.md). | | `measurement_db_config` | `dict` | `null` | Keyword arguments for that implementation's config class, merged with the `source` tables (e.g. extra source tables, prefilter keys). | diff --git a/docs/impulse/docs/references/api/impulse_query_engine/analyze/metadata/time_series_expression.md b/docs/impulse/docs/references/api/impulse_query_engine/analyze/metadata/time_series_expression.md index e20fc5c0..5a3c9a7e 100644 --- a/docs/impulse/docs/references/api/impulse_query_engine/analyze/metadata/time_series_expression.md +++ b/docs/impulse/docs/references/api/impulse_query_engine/analyze/metadata/time_series_expression.md @@ -353,6 +353,10 @@ def build(cache: SeriesCache) Build the time series from cache. +A result the cache already holds for this node (see +:meth:`SeriesCache.materialized`) is returned as-is, without building the +operands. + **Arguments**: - `cache` (`SeriesCache`): Cache containing time series data. diff --git a/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/context.md b/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/context.md new file mode 100644 index 00000000..a2624b32 --- /dev/null +++ b/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/context.md @@ -0,0 +1,267 @@ +--- +sidebar_label: context +title: impulse_query_engine.analyze.query.optimizer.context +--- + +What a pushdown rule sees of the samples it computes on. + +A rule never handles DataFrames. The pipeline (:mod:`.pipeline`) evaluates the +rule's hooks with a :class:`SeriesContext`, whose properties are Spark columns +describing the current sample of one of the rule's series: its times and value, +its neighbours, the run (e.g. continuous block) it belongs to, and facts about +the whole series. Columns returned by a hook become part of the shared pass +over the channel data. + + +#### ELEMENT\_TYPE + +Element type of the arrays returned by :meth:`PushdownRule.emit`. + + +#### BLOCK + +Name of the built-in run of continuous samples. + + +#### run\_col + +```python +def run_col(run: str, name: str) -> str +``` + +Name of the pipeline column *name* of the run *run*. + + +## Sample + +```python +class Sample() +``` + +One sample as columns: ``tstart`` and ``tend`` as double, and its value as double. + + +#### RunBreak + +A run definition: whether the sample *cur* starts a new run after *prev*. It + +is called with ``prev`` columns that are null before the series' first +sample; a null result counts as a break. + + +## Run + +```python +class Run() +``` + +Where the current sample sits in its run, a maximal stretch without a break. + +**Arguments**: + +- `starts` (`Column`): The sample starts its run. +- `id` (`Column`): Number of the run within the series, from 1. +- `start` (`Column`): ``tstart`` of the run's first sample. +- `next_starts` (`Column`): The next sample starts a new run; null for the series' last sample. +- `is_last` (`Column`): The run is the last of its series. + +#### block\_break + +```python +def block_break(prev: Sample, cur: Sample) -> Column +``` + +Continuous blocks: a sample continues its block when it starts where the previous ended. + + +## SeriesContext + +```python +class SeriesContext() +``` + +Columns a rule's hooks build on, for the current sample of one of its series. + +Every series is one channel's samples of one variant, ordered by +``(tstart, tend, value)``; all variants of a channel have the same samples. +Rules running in series order (``ordered``) see neighbours, runs and series +facts; per-sample rules see only the sample. + +**Arguments**: + +- `rule` (`PushdownRule`): The rule the hooks belong to. +- `variants` (`Mapping[int, Pushdown]`): The rule's variants in this solve. +- `key` (`Sequence[str]`): Columns identifying a series. +- `order` (`Sequence[str]`): The channel-data ``tstart``, ``tend`` and value columns: the series order. +- `ordered` (`bool`): Whether the pipeline runs the rule in series order. +- `runs` (`Mapping[str, str]`): The rule's run names to pipeline run names. +- `kept` (`bool`): Whether the hook runs on the kept samples (``kept_columns``, ``emit``). + +#### kept\_stage + +```python +def kept_stage() -> SeriesContext +``` + +This context, for the hooks that run on the kept samples. + + +#### active + +```python +def active() -> Column +``` + +The sample belongs to one of the rule's variants. + + +#### name + +```python +def name(column: str) -> str +``` + +Pipeline name of the rule's column *column*. + + +#### col + +```python +def col(column: str) -> Column +``` + +The rule's column *column*, defined by an earlier hook entry. + + +#### param + +```python +def param(name: str) -> Column +``` + +The pushdown parameter *name* of the sample's variant. + + +#### tstart + +```python +def tstart() -> Column +``` + +The sample's ``tstart`` as double. + + +#### tend + +```python +def tend() -> Column +``` + +The sample's ``tend`` as double. + + +#### value + +```python +def value() -> Column +``` + +The sample's value as double. + + +#### sample + +```python +def sample() -> Sample +``` + +The current sample. + + +#### element + +```python +def element(tstart: Column | None = None, + tend: Column | None = None, + value: Column | None = None) -> Column +``` + +One emitted row; ``tstart`` / ``tend`` left out keep the sample's own times. + + +#### prev + +```python +def prev() -> Sample +``` + +The previous sample of the series; null columns for its first sample. + + +#### next + +```python +def next() -> Sample +``` + +The next sample of the series; null columns for its last sample. + + +#### n + +```python +def n() -> Column +``` + +Number of samples in the series. + + +#### last + +```python +def last() -> Sample +``` + +The series' last sample. + + +#### in\_chunk + +```python +def in_chunk() -> WindowSpec +``` + +Window over the sample's chunk in series order, for local neighbour access. + +Chunks are consecutive slices of a channel, the same for all its series; +use :attr:`prev`, :attr:`next` and runs for anything that must see across +chunk boundaries. + + +#### block + +```python +def block() -> Run +``` + +The continuous block the sample belongs to (gaps end blocks). + + +#### run + +```python +def run(name: str) -> Run +``` + +The sample's run of the rule's run definition *name* (see :meth:`PushdownRule.runs`). + + +#### block\_end + +```python +def block_end() -> Column +``` + +``nanmax`` of the ``tend`` values of the sample's block (kept samples only). + + diff --git a/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/pipeline.md b/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/pipeline.md new file mode 100644 index 00000000..76d806eb --- /dev/null +++ b/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/pipeline.md @@ -0,0 +1,75 @@ +--- +sidebar_label: pipeline +title: impulse_query_engine.analyze.query.optimizer.pipeline +--- + +Spark execution of a pushdown plan: one shared pass for all rules. + + +#### CHUNK\_ROWS + +Average number of rows per chunk of a channel, counting a sample once per variant. + + +#### apply\_pushdowns + +```python +def apply_pushdowns(rows: DataFrame, + variants: DataFrame, + plan: PushdownPlan, + config, + include_raw: bool = True) -> DataFrame +``` + +The rows a solve ships: raw rows (when *include_raw*) and all derived rows. + +**Arguments**: + +- `rows` (`pyspark.sql.DataFrame`): Channel data ``(container_id, channel_id, tstart, tend, value)``, one row +per sample. +- `variants` (`pyspark.sql.DataFrame`): ``(container_id, channel_id, VARIANTS_COL)``: the variant ids each matched +channel ships, one row per channel (see :meth:`PushdownPlan.channel_variants`). +Channels without a row are dropped. +- `plan` (`PushdownPlan`): The solve's pushdown plan. +- `config` (`SolverConfig`): Provides the internal column names. +- `include_raw` (`bool`): Whether to keep the raw rows (variant 0). + +**Returns**: + +`pyspark.sql.DataFrame`: Rows with the columns and types of *rows* plus :data:`VARIANT_COL`. + +## SeriesPipeline + +```python +class SeriesPipeline() +``` + +The ordered pass shared by all rules that need their series in order. + +Chunking and everything that crosses chunk edges runs once per channel; its +samples fan out into one series per variant only for the rules' hooks. The +shuffle therefore carries each sample once, while a task after it processes +about k rows per shuffled sample of a channel with k variants (chunks are k +times smaller to keep the rows per chunk at :data:`CHUNK_ROWS`). + +Rule runs are computed for every channel of the pass; a rule only reads its +own runs, on its own series. + +**Arguments**: + +- `rules` (`Sequence of (PushdownRule, Mapping[int, Pushdown])`): Ordered rules with their variants in this solve. +- `config` (`SolverConfig`): Provides the internal column names. + +#### run + +```python +def run(rows: DataFrame, variants: DataFrame) -> DataFrame +``` + +Replace the samples of the rules' variants with their derived rows. + +*rows* holds the channel data, one row per sample; *variants* the variant +ids of each channel (see :func:`apply_pushdowns`). Only channels shipping a +variant of the rules are processed. + + diff --git a/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/plan.md b/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/plan.md new file mode 100644 index 00000000..06e1006e --- /dev/null +++ b/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/plan.md @@ -0,0 +1,147 @@ +--- +sidebar_label: plan +title: impulse_query_engine.analyze.query.optimizer.plan +--- + +The outcome of query optimization: which subtrees Spark computes ahead of the UDF. + + +## PushdownPlan + +```python +class PushdownPlan() +``` + +Pushdowns selected for one solve call, shipped to the solver and the UDF. + +Variant ``0`` is the raw channel data; variants ``1..n`` are derived series, +one per distinct :attr:`Pushdown.variant_key`. + +**Arguments**: + +- `rules` (`tuple of PushdownRule`): Rules used to recognize pushed-down nodes during evaluation. +- `variant_ids` (`Mapping[tuple, int]`): Variant key to variant id. +- `pushdowns_by_variant` (`Mapping[int, Pushdown]`): Variant id to a representative pushdown (rule and parameters). +- `variants_by_selector` (`Mapping[int, tuple of int]`): Selector id to the variant ids derived from it. +- `raw_selector_ids` (`frozenset of int`): Selectors whose raw channel data is still required. + +#### build + +```python +def build(rules: Iterable[PushdownRule], pushdowns: Iterable[Pushdown], + selector_ids: Iterable[int]) -> PushdownPlan +``` + +Assign variant ids to *pushdowns*, in discovery order. + +**Arguments**: + +- `rules` (`Iterable[PushdownRule]`): The optimizer's rules. +- `pushdowns` (`Iterable[Pushdown]`): Accepted pushdowns; each one's selector is pushed down entirely. +- `selector_ids` (`Iterable[int]`): Ids of all selectors in the solve. + +**Raises**: + +- `ValueError`: If a pushdown computes on another pushdown's result (``source``), +which the pipeline cannot compute. + +#### is\_empty + +```python +def is_empty() -> bool +``` + +``True`` when nothing is pushed down. + + +#### pushed\_selector\_ids + +```python +def pushed_selector_ids() -> frozenset[int] +``` + +Selectors whose raw channel data is replaced by derived variants. + + +#### variants\_by\_rule + +```python +def variants_by_rule() -> list[tuple[PushdownRule, dict[int, Pushdown]]] +``` + +Group the variants by the rule that computes them. + +**Raises**: + +- `ValueError`: If two different rules share a name: their pipeline columns would +collide. + +#### lookup + +```python +def lookup(node: TimeSeriesExpression, + classify: Classifier | None = None) -> tuple[Pushdown, int] | None +``` + +Return ``(pushdown, variant)`` when *node* is a pushed-down subtree. + +Pass a *classify* shared by the lookups of one evaluation so that every +node is classified once. + + +#### native\_targets + +```python +def native_targets( + selections: Iterable[Any] +) -> dict[int, dict[int, list[tuple[int, bool]]]] | None +``` + +Sources of calculated channels that are pushed down as a whole. + +**Arguments**: + +- `selections` (`Iterable`): The selections of a ``solve_calculated_channels`` call. + +**Returns**: + +`dict or None`: ``{variant: {selector_id: [(channel_id, uses_alias), ...]}}``: the +calculated channels (by output ``channel_id``) whose rows are the +given variant's rows of the channel resolved for ``selector_id``. +``None`` unless every selection is a ``CalculatedChannel`` whose +whole expression is a pushdown emitting its result rows. + +#### channel\_variants + +```python +def channel_variants(selector_ids: Column) -> Column +``` + +Variant ids a matched channel ships, given its ``selector_ids`` array. + +A channel ships only its raw data (``[0]``) unless every one of its +selectors is pushed down; then it ships the derived variants instead. + + +#### channel\_selector\_ids + +```python +def channel_selector_ids(selector_ids: Column | str) -> Column +``` + +A matched channel's distinct, non-null ``selector_ids``, as ``long``. + +Variant tagging (:meth:`channel_variants`) and the native calculated-channel +rows both read a channel's selectors through this, so they agree on which +selectors it carries. + + +#### describe + +```python +def describe() -> str +``` + +One-line summary of the plan for logging. + + diff --git a/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/pushdown.md b/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/pushdown.md new file mode 100644 index 00000000..1a3bc2c3 --- /dev/null +++ b/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/pushdown.md @@ -0,0 +1,261 @@ +--- +sidebar_label: pushdown +title: impulse_query_engine.analyze.query.optimizer.pushdown +--- + +Building blocks of Spark pushdown rules. + + +#### VARIANT\_COL + +Column tagging each channel-data row with the series variant it belongs to. + + +#### VARIANTS\_COL + +Column listing the variant ids a matched channel ships, one row per channel. + + +#### RAW\_VARIANT + +Variant id of the unmodified (raw) channel data. + + +#### check\_name + +```python +def check_name(kind: str, name: str) -> str +``` + +Return *name* if it is a valid rule, rule column or run name, else raise. + +Names are lowercase letters, digits and single underscores, starting with a +letter. Pipeline columns join them with ``__``, which a name never contains, +so names of different rules cannot collide. + + +## PushdownContext + +```python +class PushdownContext() +``` + +Plan-time facts about the channel data a pushdown would run on. + +**Arguments**: + +- `tstart_type, tend_type` (`pyspark.sql.types.DataType`): Spark types of the channel-data ``tstart`` / ``tend`` columns. + +#### from\_schema + +```python +def from_schema(schema: T.StructType, config: SolverConfig) -> PushdownContext +``` + +Build the context from the column-mapped channel-data schema. + + +## Pushdown + +```python +class Pushdown() +``` + +A subtree that a :class:`PushdownRule` computes natively in Spark. + +Expression nodes overload ``==``, so pushdowns compare by identity; use +:attr:`variant_key` to identify equivalent pushdowns. + +A pushdown reads the samples of its channel, possibly absorbing pushed +operands into that pass, or computes on the result of another pushdown, its +:attr:`source`. The pipeline computes the former only. + +**Arguments**: + +- `rule` (`PushdownRule`): The rule that matched the subtree. +- `selector` (`TimeSeriesSelector`): The physical channel selector the subtree reads. +- `params` (`tuple of (str, Any)`): Canonical, hashable operation parameters (e.g. ``(("sample_rate", 1e6),)``). +- `consumed` (`tuple of TimeSeriesSelector`): Every selector occurrence inside the subtree (its ``get_selectors()``). +- `source` (`Pushdown`): The pushdown whose result this one computes on; ``None`` when it reads +the channel's samples. + +#### variant\_key + +```python +def variant_key() -> tuple +``` + +Identity of the derived series, independent of the input channel. + + +#### describe + +```python +def describe() -> str +``` + +Human-readable ``rule(param=value, ...)`` label. + + +#### Operand + +Classification of an operand of the node a rule matches: the selector of a + +channel, the :class:`Pushdown` computing the operand, or ``None`` when the +operand is evaluated in pandas or is not an expression. + + +## PushdownRule + +```python +class PushdownRule(abc.ABC) +``` + +Recognizes a subtree and computes it in Spark ahead of the pandas UDF. + +Planning and decoding run in Python: :meth:`match` recognizes a node from +its operands' classification, :meth:`decode` turns one +container/channel/variant slice of the derived rows back into the object the +subtree evaluates to. The result must equal evaluating the subtree in pandas +on the same channel data. + +The Spark side is a set of hooks returning columns, evaluated by the shared +pushdown pipeline (:mod:`.pipeline`) for the samples of the rule's variants, +one series (channel and variant) at a time, with a :class:`SeriesContext`: + +1. :meth:`row_columns` per sample; +2. :meth:`keep` selects the samples that go on; +3. :meth:`kept_columns` per kept sample; +4. :meth:`emit` the derived rows of a kept sample. + +Rules never handle DataFrames, so every rule shares the pipeline's reads and +shuffles. Derived rows have the channel-data shape +``(container_id, channel_id, variant, tstart, tend, value)``. + + +#### name + +Unique rule name, part of every :attr:`Pushdown.variant_key`; lowercase + +letters, digits and single underscores (see :func:`check_name`). + + +#### ordered + +Whether the rule needs its series in order (neighbours, runs, series + +facts). Per-sample rules set ``False`` and skip the series scan. + + +#### match + +```python +def match(node: TimeSeriesExpression, operand: Operand) -> Pushdown | None +``` + +Return the pushdown for *node*, or ``None`` if the rule does not apply. + +Nodes are classified bottom-up: *operand* returns the classification of +any operand of *node*. A pushdown reading the channel's samples sets no +``source``; when it absorbs a pushed operand into that pass, its +``consumed`` includes the operand's and :meth:`supports` covers both. A +pushdown computing on a pushed operand's result sets ``source`` to it. + +Purely syntactic: it runs on the driver while planning and again inside +the UDF to recognize the node, so it must not depend on data or schemas. + + +#### supports + +```python +def supports(pushdown: Pushdown, context: PushdownContext) -> bool +``` + +Whether *pushdown* can run on channel data described by *context*. + + +#### result\_type + +```python +def result_type(pushdown: Pushdown) -> type +``` + +Core-model class :meth:`decode` returns for *pushdown*. + + +#### runs + +```python +def runs() -> Mapping[str, RunBreak] +``` + +Run definitions beyond the built-in continuous blocks, by name. + +Each maps ``(prev, cur)`` samples to whether ``cur`` starts a new run; + + +#### row\_columns + +```python +def row_columns(ctx: SeriesContext) -> Mapping[str, Column] +``` + +Columns computed for every sample, in order; refer to them via ``ctx.col``. + +Column names follow :func:`check_name` and only need to be unique within +the rule. + + +#### keep + +```python +def keep(ctx: SeriesContext) -> Column +``` + +Whether a sample can emit rows; the others are dropped early. + + +#### kept\_columns + +```python +def kept_columns(ctx: SeriesContext) -> Mapping[str, Column] +``` + +Columns computed for the kept samples, in order (``ctx.block_end`` available). + + +#### emit + +```python +def emit(ctx: SeriesContext) -> Column +``` + +The derived rows of a kept sample. + +**Returns**: + +`Column`: An array of :data:`~.context.ELEMENT_TYPE` structs built with +:meth:`SeriesContext.element`; empty or null emits nothing. Times +left out keep the sample's own ``tstart`` / ``tend``. + +#### decode + +```python +def decode(rows: pd.DataFrame, pushdown: Pushdown, + cache: TimeSeriesCache) -> Any +``` + +Build the subtree's result from one container/channel/variant slice. + + +#### emits\_result\_rows + +```python +def emits_result_rows(pushdown: Pushdown) -> bool +``` + +Whether *pushdown*'s derived rows are its result's samples as they are. + +Holds when the result is a :class:`SampleSeries` built by the default + + diff --git a/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/query_optimizer.md b/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/query_optimizer.md new file mode 100644 index 00000000..31d21612 --- /dev/null +++ b/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/query_optimizer.md @@ -0,0 +1,58 @@ +--- +sidebar_label: query_optimizer +title: impulse_query_engine.analyze.query.optimizer.query_optimizer +--- + +Rule-based optimizer choosing the subtrees Spark computes ahead of the pandas UDF. + + +#### default\_rules + +```python +def default_rules() -> tuple[PushdownRule, ...] +``` + +All built-in pushdown rules. + + +## QueryOptimizer + +```python +class QueryOptimizer() +``` + +Plans which subtrees of a solve's selections are computed natively in Spark. + +Nodes are classified bottom-up (:class:`~.traversal.Classifier`), and the +topmost classified nodes whose pushdown the channel data supports are the +candidates; a node that is not supported leaves its operands as candidates. + +All selections of a solve share the raw data of their physical channels, so +a subtree is pushed down only when **every** occurrence of its input selector, +across all selections, sits inside a pushed-down subtree. Then the raw rows +of that selector are no longer needed and are replaced by the (smaller) +derived rows. Selectors used anywhere else keep their raw data, and their +subtrees are evaluated in pandas as before. + +**Arguments**: + +- `rules` (`Sequence[PushdownRule]`): Rules to apply, in priority order. Defaults to :func:`default_rules`. + +#### optimize + +```python +def optimize(selections: Iterable[Any], + context: PushdownContext) -> PushdownPlan +``` + +Build the pushdown plan for the *selections* of one solve call. + +**Arguments**: + +- `selections` (`Iterable`): The query's selections. +- `context` (`PushdownContext`): Plan-time facts about the channel data. + +**Returns**: + +`PushdownPlan`: The plan; empty when nothing can be pushed down. + diff --git a/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/resample_rule.md b/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/resample_rule.md new file mode 100644 index 00000000..af0cdf38 --- /dev/null +++ b/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/optimizer/resample_rule.md @@ -0,0 +1,61 @@ +--- +sidebar_label: resample_rule +title: impulse_query_engine.analyze.query.optimizer.resample_rule +--- + +Pushdown rule computing ``SampleSeries.resample`` natively in Spark. + + +## ResamplePushdownRule + +```python +class ResamplePushdownRule(PushdownRule) +``` + +Pushes ``channel.resample(sample_rate)`` on a physical channel down to Spark. + +Reproduces :meth:`SampleSeries.resample`: per continuous block of samples, a +regular grid starting at the block's first ``tstart`` (built like +``numpy.arange``), each grid point holding the value of the last sample that +starts at or before it. Meant for downsampling: a rate finer than the native +sample spacing yields more rows than the raw channel. + +Each sample is assigned the bucket ``c`` of the first grid point at or after its +``tstart``. The last sample of a bucket holds the value of grid points +``c, c + 1, …`` up to the next sample's bucket, so only those samples are kept +and every row carries just the grid points it covers. Values are looked up +within a block; this matches the pandas evaluation unless intervals overlap. + +Grid points are computed as ``t_min + k * delta`` in IEEE double arithmetic. +numpy builds that fuse this into a multiply-add can differ by one ulp for +fractional grids (e.g. seconds-as-double with a rate of ``0.1``); integral +grids are exact. + + +## \_Grid + +```python +class _Grid() +``` + +The numpy.arange grid of the sample's block. + +``numpy.arange(t_min, t_max + rate, rate)`` has length +``ceil(((t_max + rate) - t_min) / rate)`` and is filled as ``t_min``, +``t_min + rate``, then ``t_min + k * ((t_min + rate) - t_min)``. + + +#### first\_index + +```python +def first_index(name: str, x: Column, strict: Column, + active: Column) -> dict[str, Column] +``` + +Columns ending in *name*: the smallest ``k >= 0`` with ``point(k) >= x`` (``> x`` if *strict*). + +Estimated from ``(x - t_min) / delta`` and corrected by one step against the +exact grid points; a result that still is not exact raises instead of +misplacing a sample. Only evaluated where *active*. + + diff --git a/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/solvers/default_solver.md b/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/solvers/default_solver.md index 9df87eef..463e0f3c 100644 --- a/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/solvers/default_solver.md +++ b/docs/impulse/docs/references/api/impulse_query_engine/analyze/query/solvers/default_solver.md @@ -48,6 +48,11 @@ silver layer. ``RawEncoder.RLE`` (default) run-length encodes equal-valued runs; ``RawEncoder.INTERVAL`` only derives ``tend`` and drops exact duplicates. Only consulted when ``is_raw_data`` is ``True``. +- `enable_pushdown` (`bool`): Whether to compute supported expression subtrees (e.g. +``channel.resample(rate)``) natively in Spark before the pandas UDF, so +fewer rows are shipped to it (see :class:`QueryOptimizer`). A channel +is only pushed down when every use of it in the solve is. Default +``False``. #### filter\_container\_tags diff --git a/docs/impulse/docs/references/api/impulse_reporting/config/config_parser.md b/docs/impulse/docs/references/api/impulse_reporting/config/config_parser.md index 5c92a6b9..c28b8873 100644 --- a/docs/impulse/docs/references/api/impulse_reporting/config/config_parser.md +++ b/docs/impulse/docs/references/api/impulse_reporting/config/config_parser.md @@ -211,6 +211,11 @@ project/toolbox filtering is applied. - `measurement_db` (`str, default="MeasurementDB"`): Registered name of the MeasurementDB implementation to use. - `measurement_db_config` (`dict`): Keyword arguments for that implementation's config class, merged with the ``source`` tables. +- `enable_pushdown` (`bool, default=False`): Compute supported expression subtrees (currently +``channel.resample(rate)`` on a physical channel) natively in Spark +before the pandas UDF, so fewer rows are shipped to it. A channel is +only pushed down when every use of it in a solve batch is. Meant for +downsampling. #### validate\_drop\_implausible\_data\_requires\_raw diff --git a/docs/impulse/docs/references/api/sidebar.json b/docs/impulse/docs/references/api/sidebar.json index 3f08df0f..92c8d3f9 100644 --- a/docs/impulse/docs/references/api/sidebar.json +++ b/docs/impulse/docs/references/api/sidebar.json @@ -30,6 +30,18 @@ "label": "impulse_query_engine.analyze.query.channels", "type": "category" }, + { + "items": [ + "references/api/impulse_query_engine/analyze/query/optimizer/context", + "references/api/impulse_query_engine/analyze/query/optimizer/pipeline", + "references/api/impulse_query_engine/analyze/query/optimizer/plan", + "references/api/impulse_query_engine/analyze/query/optimizer/pushdown", + "references/api/impulse_query_engine/analyze/query/optimizer/query_optimizer", + "references/api/impulse_query_engine/analyze/query/optimizer/resample_rule" + ], + "label": "impulse_query_engine.analyze.query.optimizer", + "type": "category" + }, { "items": [ "references/api/impulse_query_engine/analyze/query/solvers/default_solver", diff --git a/docs/impulse/docs/references/query_engine/img/solve_flow.svg b/docs/impulse/docs/references/query_engine/img/solve_flow.svg new file mode 100644 index 00000000..9f6b5b79 --- /dev/null +++ b/docs/impulse/docs/references/query_engine/img/solve_flow.svg @@ -0,0 +1,244 @@ + + How a query is solved + Five stages: plan on the driver, resolve channels from the metadata tables, load the channel data, compute pushed-down subtrees natively in Spark, and evaluate the rest of each container's expressions in a pandas UDF, which consumes the precomputed subtree results. Example: eng_rpm.where(event).histogram2d(veh_spd.where(event), bins) with event = veh_spd > 0. Spark computes the event intervals and the filtered veh_spd samples; the UDF filters the raw eng_rpm rows to the event and computes the 2D histogram. Calculated channels that are pushed down entirely skip the UDF. + + + + + + + + + + + + + + + + 1 + Plan · driver + + + query.select(e1, …, en) + .solve(spark, solver) + + + Infer result types + solve(): build on an empty cache + + + Plan pushdowns (opt-in) + QueryOptimizer picks subtrees for Spark + + + + + + + + + 2 + Resolve channels · Spark, metadata tables (no sample data) + + + container_tags + container filters + + + container_metrics + project, metric filters + + + channel_tags + channel tags (EAV) + + + channel_metrics + match each selector + + + channel_mapping + alias resolution + + + + + + + + channel matches: container_id · channel_id · selector_ids + + + + + + 3 + Load channel data · Spark, channels table + + + channels + column mapping, filters + + + Raw mode: encode + RLE or intervals + + + Add POI channels + when POI channels are selected + + + Join channel matches + keep matched channels + + + + + + + + + + 4 + Compute subtrees natively · Spark pushdown (opt-in) + + + + + Split the expression at its pushed-down subtrees + eng_rpm.where(veh_spd > 0).histogram2d(veh_spd.where(veh_spd > 0), bins) + + + eng_rpm + + + ① veh_spd > 0 + + ② veh_spd.where(veh_spd > 0) + + + + + + .where(·) + + + + + + .histogram2d(…) + rest: pandas UDF + + + Rules + e.g. resample(…) or filter + conditions like veh_spd > 0 + each reads one channel; + all share the reads + of the channel data + + + + + + + 5 + Evaluate per container · pandas UDF (GROUPED_MAP) + + + results of pushed-down subtrees (derived rows) and raw rows still needed + + + Group by container + repartition by container_id + + + TimeSeriesCache + one container's rows + + + Evaluate the rest of the expression + + eng_rpm + + ① event intervals + + ② filtered veh_spd + + + + + + .where(·) + + + + + + .histogram2d(…) + Precomputed results arrive ready-made; pandas evaluates only the rest. + + + Encode each result + solve(): serialized value · calculated channels: rows + + + + + + + + + + + solve() · toPandas() + one row per container, one column per selected expression + + + solve_calculated_channels() + rows: container_id, channel_id, tstart, tend, value, identity + + + + native: every calculated channel pushed down + + + + optional table or opt-in step + + computed natively in Spark + + raw channel rows + + evaluated in pandas + + no pandas UDF + diff --git a/docs/impulse/docs/references/query_engine/index.md b/docs/impulse/docs/references/query_engine/index.md index 8156a6c4..73a1a335 100644 --- a/docs/impulse/docs/references/query_engine/index.md +++ b/docs/impulse/docs/references/query_engine/index.md @@ -12,6 +12,9 @@ silver-layer data. It has two parts: - **[Query Solvers](query_solvers.md)** — the `DefaultSolver` that knows how your silver tables are laid out, reads them, and evaluates the expressions per container. +[How a Query Is Solved](solving.md) walks through a `solve()` call stage by stage, from the +metadata tables to the per-container results. + ## Two ways to use it - **Directly, for ad-hoc analysis.** Build a query against a `MeasurementDB` and solve it diff --git a/docs/impulse/docs/references/query_engine/query_solvers.md b/docs/impulse/docs/references/query_engine/query_solvers.md index c71cd297..f2f2426b 100644 --- a/docs/impulse/docs/references/query_engine/query_solvers.md +++ b/docs/impulse/docs/references/query_engine/query_solvers.md @@ -126,6 +126,9 @@ Solver tuning lives under the `query_engine` section of your report config: — adapt the solver to a silver layer whose physical column names diverge from Impulse's internal names, scope reads by `project_id`, or apply per-table equality filters. +- [`query_engine.enable_pushdown`](../../config/configuration.md#query_engine-optional) + — compute supported expressions natively in Spark before the pandas UDF + (see [Spark pushdown](#spark-pushdown-opt-in)). Off by default. If `query_engine` is omitted from your config entirely, the engine runs with `DefaultSolver` and `data_type = "RLE"`. @@ -142,6 +145,8 @@ Every selection must be a [`CalculatedChannel`](../report/channel.md) (identity need not match across selections), and each wrapped expression must evaluate to a `SampleSeries`. The stage reuses the same metadata filter pipeline as `solve()` to resolve the input channels, then runs a grouped-map UDF per container. Only `DefaultSolver` implements it — the base `QuerySolver` raises `NotImplementedError`. +The `channel_id` column of `channels` must be an integer or long column to hold the calculated +channels' ids. ```python from impulse_query_engine.analyze.query.channels.calculated_channel import CalculatedChannel @@ -152,12 +157,89 @@ cc = CalculatedChannel( {"channel_name": "speed_kmh", "data_key": "CALC"}, ) df = db.query.select(cc).solve_calculated_channels(spark, solver=DefaultSolver(spark)) -# df: [container_id, channel_id, tstart, tend, value, channel_name, data_key] +# df: [container_id, channel_id, tstart, tend, value, identity] ``` The reporting layer builds on this stage to persist derived channels to the gold layer — see [Channels](../report/channel.md). +## Spark pushdown (opt-in) + +Every selection of a solve is evaluated in one pandas UDF per container, and by default the raw +rows of every channel the selections reference are shipped into it. With pushdown enabled, a +rule-based optimizer finds expression subtrees that Spark can compute natively, computes them +**before** the UDF, and ships their (smaller) result instead of the raw rows. + +Supported today: + +| Expression | Computed in Spark as | +|------------------------------------|---------------------------------------------------------------------------| +| `channel.resample(sample_rate)` | The resampled series, identical to `SampleSeries.resample`, on a physical `channel()` or `channel_with_alias()` selection | + +The subtree can sit anywhere in an expression, e.g. `veh_spd.resample(1e6).cumtrapz()`, inside a +`CalculatedChannel`, an aggregation, or an event condition; only the pushed-down part runs in Spark. + +```python +solver = DefaultSolver(spark, enable_pushdown=True) +``` + +In a report config, set `query_engine.enable_pushdown` to `true`. + +:::note When a channel is pushed down +All selections of a solve share the raw data of their channels, so a channel is pushed down only +if **every** use of it in the solve is a pushed-down subtree. If the same channel is also used in +any other way, e.g. `rpm.resample(1e6)` next to `rpm.max()`, its raw rows are still needed: they are +shipped as before and the resample runs in pandas. Results are the same either way. +::: + +:::caution Intended for downsampling +`resample` is pushed down regardless of the rate. A rate finer than the channel's native sample +spacing (upsampling) produces **more** rows than the raw channel, so enable pushdown for workloads +that downsample. +::: + +Results match the pandas evaluation exactly for integer timestamp columns with an integral +`sample_rate` (e.g. µs or ns epochs), and for double timestamps whose grid is exact in binary +floating point. For double timestamps with a fractional rate such as `0.1`, the grid points can +differ in the last bit from numpy builds that fuse multiply-add operations. The order of samples +that share the same `tstart` is unspecified, as in the pandas evaluation, and channels whose +`[tstart, tend)` intervals overlap are not supported (the RLE and interval encoders never +produce overlaps). + +#### How Spark runs pushed-down subtrees + +All pushed-down subtrees of a solve share one pass over the channel data: + +- Spark cuts each pushed channel into chunks, equal time slices, and processes the chunks in + parallel. A chunk holds about 100 000 samples on average, divided by the number of pushed-down + expressions that read the channel (e.g. about 50 000 for `spd.resample(1e6)` next to + `spd.resample(5e6)`), as each of them computes on every sample of the chunk. A task holds one + chunk at a time, not a whole channel or container; with strongly varying sampling density a + chunk can hold more samples. +- Within a chunk the samples are ordered by `tstart`, then `tend`, then value; samples with equal + times are ordered by value. For `resample`, each sample is assigned the grid bucket it falls + into, and only the last sample of each bucket emits rows: the grid points up to the next + sample's bucket. +- What crosses a chunk boundary (neighbouring samples, gaps, the start and end of a continuous + block) comes from one summary row per chunk. +- Each sample is shuffled once, however many pushed-down expressions read its channel: chunks, + neighbours and continuous blocks are computed once per channel, and only the expressions' own + computations run per expression. + +The channel data is read **twice**, however many expressions are pushed down: once in full, and +once with only `container_id`, `channel_id`, `tstart` and `tend` (the channel boundaries that +define the chunks). Channels that are also used outside pushed-down subtrees, e.g. `rpm.max()` +next to `spd.resample(1e6)`, are read once more for their raw rows. + +### Calculated channels without the pandas UDF + +When every calculated channel of a `solve_calculated_channels` call (in a report: of a solve +batch) is a pushed-down expression as a whole, e.g. +`CalculatedChannel(veh_spd.resample(1e6), {...})`, its rows are produced entirely in Spark and no +pandas UDF runs. If any calculated channel of the call needs the pandas evaluation, e.g. +`veh_spd.resample(1e6) * 3.6`, all of them are evaluated in the UDF as usual; their pushed-down +parts still run in Spark. Either way the rows are identical. + ## API reference Auto-generated symbol-level docs: @@ -167,3 +249,9 @@ Auto-generated symbol-level docs: — abstract base class defining the six-stage solver pipeline. - [`SolverConfig`](../api/impulse_query_engine/analyze/query/solvers/solver_config.md) — per-table column mappings, filters, and project scoping. +- [`QueryOptimizer`](../api/impulse_query_engine/analyze/query/optimizer/query_optimizer.md) + and [`PushdownRule`](../api/impulse_query_engine/analyze/query/optimizer/pushdown.md) + — the Spark pushdown optimizer and the interface for its rules; + [`pipeline`](../api/impulse_query_engine/analyze/query/optimizer/pipeline.md) and + [`SeriesContext`](../api/impulse_query_engine/analyze/query/optimizer/context.md) — how rules + run in Spark and what they can use. diff --git a/docs/impulse/docs/references/query_engine/solving.md b/docs/impulse/docs/references/query_engine/solving.md new file mode 100644 index 00000000..aa6728fe --- /dev/null +++ b/docs/impulse/docs/references/query_engine/solving.md @@ -0,0 +1,151 @@ +--- +sidebar_position: 3 +title: How a Query Is Solved +--- + +# How a Query Is Solved + +One `solve()` call evaluates **every selected expression for every matching container**. The +expressions themselves are lazy ([Evaluation](tsal/evaluation.md)): nothing is computed until the +solver runs them through five stages. Query planning (stage 1) runs on the driver; query +execution (stages 2 to 5) runs in Spark. With [Spark pushdown](query_solvers.md#spark-pushdown-opt-in) +enabled, Spark computes supported subtrees natively in stage 4, and the pandas UDF in stage 5 +consumes their results instead of evaluating them. + +![How a query is solved: plan on the driver, resolve channels from the metadata tables, load the channel data, compute pushed-down subtrees natively in Spark, and evaluate the rest of each container's expressions in a pandas UDF](./img/solve_flow.svg) + +The example computes a 2D histogram of engine rpm and vehicle speed while the vehicle is +moving: + +```python +eng_rpm = db.query.channel(channel_name="Engine RPM") +veh_spd = db.query.channel(channel_name="Vehicle Speed") +event = veh_spd > 0 +result = db.query.select( + eng_rpm.where(event) + .histogram2d(veh_spd.where(event), [0, 1000, 2000, 3000, 4000], [0, 50, 100, 150, 200]) + .alias("rpm_vs_speed"), +).solve(spark, solver=DefaultSolver(spark, enable_pushdown=True)) +``` + +## 1. Plan (driver) + +- `select(...)` stores the expressions; `solve()` starts by **building each expression once + against an empty cache**. This infers every result's type, and with it the schema of the output + DataFrame, before any data is read (see + [Planning: type inference before solve](tsal/evaluation.md#planning-type-inference-before-solve)). + `solve_calculated_channels()` skips this step: its results are always sample rows. +- With pushdown enabled, the optimizer also picks the subtrees Spark computes natively in + stage 4 (see stage 4 for the example). + +## 2. Resolve channels (metadata tables) + +The solver finds the physical channels each expression reads, using only the metadata tables. No +sample data is read in this stage. It covers the first five stages of the `QuerySolver` pipeline +(container tags, container metrics, channel tags, channel metrics, alias resolution); the sixth, +`solve`, covers stages 3 to 5 here. + +1. **Containers.** `container_tags` (optional) and `container_metrics` select the containers of + the configured project that match the query's container filters. Incremental report runs pass + their pre-filtered containers here. +2. **Channels.** Every selector of every expression, e.g. `channel(channel_name="Engine RPM")`, is + matched against the channel attributes on `channel_tags` (optional, EAV) or `channel_metrics`. + A selector should match at most one channel per container + (see [Channel selection](tsal/defining_expressions.md#channel-selection)). +3. **Aliases.** Alongside the direct selectors, selectors written with `channel_with_alias(...)` + are resolved through `channel_mapping` (optional), by alias priority. +4. **Container metadata.** Container tags and metrics that expressions read are attached. + +The result is a small table of channel matches: `container_id`, `channel_id` and the +`selector_ids` of the selectors that read the channel, plus the container metadata and, for +aliased channels, their source and target units. + +## 3. Load channel data (Spark) + +- The `channels` table is read with its column mapping and filters, keeping only `container_id`, + `channel_id`, `tstart`, `tend` and `value`. Raw data is first encoded into RLE or interval + samples; points-in-time data from `poi_channels` is added when an expression selects POI + channels. +- Conversion factors for aliased channels are looked up in `unit_conversion` (optional). +- The samples are joined with the channel matches from stage 2, which keeps only the matched + channels and attaches their selector ids, conversion factors and container metadata. With + pushdown, the rows first carry only which results they feed; the rest is attached after + stage 4. + +## 4. Compute subtrees natively (Spark pushdown, opt-in) + +Pushed-down subtrees are computed **in Spark, before any pandas code runs**, and only the rest of +each expression is left for the pandas UDF. The optimizer is rule-based: each rule computes one +kind of subtree on the rows of one channel, e.g. `resample(...)` or filter conditions such as +`veh_spd > 0`. All rules share the same reads of the channel data; rules that need a channel's +samples in order run in parallel per chunk of the channel (see +[How Spark runs pushed-down subtrees](query_solvers.md#how-spark-runs-pushed-down-subtrees)). + +The example expression forms this tree (`event` is used twice, so it appears twice): + +``` +histogram2d(rpm_bins, speed_bins) pandas UDF +├── where pandas UDF +│ ├── eng_rpm raw rows +│ └── veh_spd > 0 ① Spark: condition → Intervals +└── veh_spd.where(veh_spd > 0) ② Spark: filter → SampleSeries +``` + +The optimizer cuts out a subtree when + +1. **a rule matches it.** It takes the largest matching subtree: ② as a whole, not just the event + inside it. +2. **it reads a single channel.** `eng_rpm.where(veh_spd > 0)` and `histogram2d` combine two + channels and stay in pandas. +3. **every use of its channel is cut out.** All three uses of `veh_spd` lie in ① or ②, so its raw + rows are no longer needed. `eng_rpm` is read by pandas directly, so its raw rows are shipped as + before. + +The results of cut-out subtrees are **derived rows** that replace the raw rows of their channel. +Here, instead of the full `veh_spd` channel, the UDF receives the event intervals ① (usually a few +rows) and the samples with `veh_spd > 0` ②. A channel can ship several derived results like this. + +:::note Rules available today +The optimizer currently implements the `resample` rule; rules for further operations such as +filter conditions follow the same flow. Until a rule covers a subtree, it is evaluated in stage 5 +as before. +::: + +## 5. Evaluate per container (pandas UDF) + +- The rows are **grouped by container**. Each container's rows reach one call of a pandas + `GROUPED_MAP` UDF, so one task holds one container's rows in memory. +- A `TimeSeriesCache` serves the container's channels, its container tags and metrics, and the + results of pushed-down subtrees. +- Each expression is **evaluated bottom-up, except for its pushed-down subtrees**: those arrive as + finished results from stage 4. In the example, the UDF clips the raw `eng_rpm` rows to the + precomputed event intervals ① and computes the 2D histogram against the precomputed `veh_spd` + samples ②. Every other leaf loads its channel from the cache as a `SampleSeries` (or + `PointsInTimeSeries` for POI channels), multiplied by the conversion factor for aliased + selectors, and each operation then works on its children's results. +- Each result is encoded for the output. `solve()` serializes it into its column: `SampleSeries` + as binary, `Intervals` and points as arrays, histograms as structs, scalars as doubles (see + [From expression to result](tsal/evaluation.md#from-expression-to-result)). + `solve_calculated_channels()` emits the samples of each calculated channel as rows. + +## Results + +| Call | Output | +|------|--------| +| `solve()` | One row per container, one column per selected expression, named by its alias. | +| `toPandas()` | The same, as a pandas DataFrame with `SampleSeries` objects deserialized. | +| `solve_calculated_channels()` | Silver-shaped rows `container_id, channel_id, tstart, tend, value` plus the channel's `identity`. | + +When **every** calculated channel of a `solve_calculated_channels()` call is pushed down as a +whole, e.g. `CalculatedChannel(veh_spd.resample(1e6), {...})`, its rows come straight from stage 4 +and no pandas UDF runs (see +[Calculated channels without the pandas UDF](query_solvers.md#calculated-channels-without-the-pandas-udf)). + +:::note Where the work happens +Stages 2 to 4 are distributed Spark jobs; for rules that need samples in order, stage 4 even +splits each channel into chunks that run in parallel. Stage 5 runs in parallel across containers, +but each container is evaluated in a single task: the more channels and samples a container's +expressions read, the more memory that task needs. Every pushed-down subtree moves work from +stage 5 into stage 4 and usually reduces the rows that reach stage 5 (resampling to a finer rate +than the channel's own adds rows). +::: diff --git a/docs/impulse/docs/references/query_engine/tsal/defining_expressions.md b/docs/impulse/docs/references/query_engine/tsal/defining_expressions.md index dda8ed93..25980249 100644 --- a/docs/impulse/docs/references/query_engine/tsal/defining_expressions.md +++ b/docs/impulse/docs/references/query_engine/tsal/defining_expressions.md @@ -31,6 +31,12 @@ Each tag passed to `channel(...)` must be resolvable by the solver — either as table is configured — see [Query Solvers](../query_solvers.md#how-defaultsolver-adapts). ::: +:::note When a selection matches several channels +A selection should identify at most one channel per container. If several channels of a container +match its tags (e.g. the same `channel_name` under two `data_key`s), one of them is used, and which +one is unspecified; add tags such as `data_key` to select a specific channel. +::: + ### Points-in-Time (POI) channels Not every channel can be interpreted as a continuous signal within its `[tstart, tend)` intervals. @@ -95,6 +101,9 @@ channel-identifying columns (by default `channel_name` and `data_key`). See [Query Solvers](../query_solvers.md) for details. ::: +If several mapping entries of an alias resolve in a container, the one with the lowest `priority` +wins; without priorities, or on ties, the lowest `channel_id` is used. + When the `channel_mapping` table carries `source_unit` and `target_unit` columns and the report config sets `source.unit_conversion_table`, values returned from `channel_with_alias()` are automatically converted from source to target unit before any expression is evaluated. Constants and parameters in expressions over an aliased selector @@ -177,6 +186,10 @@ type) at execution time. distance_km = veh_spd.resample(1e6).cumtrapz() / 3600 / 1e6 ``` +When downsampling large channels, `.resample(...)` on a physical channel can be computed in Spark +before the data reaches the pandas evaluation — see +[Spark pushdown](../query_solvers.md#spark-pushdown-opt-in). + ### Filtering | Method | Signature | Description | diff --git a/docs/impulse/docs/references/query_engine/tsal/evaluation.md b/docs/impulse/docs/references/query_engine/tsal/evaluation.md index a8734e14..99345aeb 100644 --- a/docs/impulse/docs/references/query_engine/tsal/evaluation.md +++ b/docs/impulse/docs/references/query_engine/tsal/evaluation.md @@ -63,7 +63,12 @@ following per container: | `PointsInTimeSeries` | `ArrayType(ArrayType(DoubleType))` | `[[tstart, value], ...]`| | scalar | `DoubleType` | the value | +With [Spark pushdown](../query_solvers.md#spark-pushdown-opt-in) enabled, some subtrees (e.g. +`channel.resample(rate)`) are computed natively in Spark before step 1. The solver ships their +results instead of the raw channel rows, and step 2 uses those results in place of evaluating the +subtrees in pandas. The tree itself is unchanged, and so is every result. + `toPandas()` deserializes the binary `SampleSeries` columns back into objects; the array-backed types are returned as nested lists. See the [Core Data Model](core_data_model.md) for the semantics of each -result class, and [Query Solvers](../query_solvers.md) for the full solver pipeline and the -`DefaultSolver` that reads the silver layer. +result class, [How a Query Is Solved](../solving.md) for the full flow of a `solve()` call, and +[Query Solvers](../query_solvers.md) for the `DefaultSolver` that reads the silver layer. diff --git a/docs/impulse/pydoc-markdown.yml b/docs/impulse/pydoc-markdown.yml index f52dda2b..7fa9bb8d 100644 --- a/docs/impulse/pydoc-markdown.yml +++ b/docs/impulse/pydoc-markdown.yml @@ -24,6 +24,12 @@ loaders: - impulse_query_engine.analyze.query.solvers.default_solver - impulse_query_engine.analyze.query.solvers.solver_config - impulse_query_engine.analyze.query.solvers.query_solver + - impulse_query_engine.analyze.query.optimizer.pushdown + - impulse_query_engine.analyze.query.optimizer.context + - impulse_query_engine.analyze.query.optimizer.pipeline + - impulse_query_engine.analyze.query.optimizer.plan + - impulse_query_engine.analyze.query.optimizer.query_optimizer + - impulse_query_engine.analyze.query.optimizer.resample_rule - impulse_query_engine.model.series.sample_series - impulse_query_engine.model.series.intervals - impulse_query_engine.model.series.points_in_time diff --git a/resources/e2e_job.yml b/resources/e2e_job.yml index 84d5678a..c61ecf06 100644 --- a/resources/e2e_job.yml +++ b/resources/e2e_job.yml @@ -58,6 +58,12 @@ resources: depends_on: [{task_key: setup}] notebook_task: {notebook_path: ../tests/e2e/e2e_events.py, base_parameters: *params} + - task_key: pushdown + environment_key: e2e_env + disable_auto_optimization: true + depends_on: [{task_key: setup}] + notebook_task: {notebook_path: ../tests/e2e/e2e_pushdown.py, base_parameters: *params} + # Runs only when all tests passed. Otherwise it ends as "upstream failed", which fails # the job run, and the schema is kept for debugging. - task_key: teardown @@ -70,6 +76,7 @@ resources: - {task_key: container_filter} - {task_key: incremental_updates} - {task_key: events} + - {task_key: pushdown} notebook_task: {notebook_path: ../tests/e2e/teardown.py, base_parameters: *params} environments: diff --git a/src/impulse_query_engine/analyze/metadata/time_series_expression.py b/src/impulse_query_engine/analyze/metadata/time_series_expression.py index 6a2b75bd..8b335011 100644 --- a/src/impulse_query_engine/analyze/metadata/time_series_expression.py +++ b/src/impulse_query_engine/analyze/metadata/time_series_expression.py @@ -1226,6 +1226,10 @@ def build(self, cache: SeriesCache): """ Build the time series from cache. + A result the cache already holds for this node (see + :meth:`SeriesCache.materialized`) is returned as-is, without building the + operands. + Parameters ---------- cache : SeriesCache @@ -1236,6 +1240,10 @@ def build(self, cache: SeriesCache): Any Built time series object. """ + # Looked up on the type so caches without the hook (or test doubles) are skipped. + materialized = getattr(type(cache), "materialized", None) + if materialized is not None and (result := materialized(cache, self)) is not None: + return result argsb = [a.build(cache) if isinstance(a, TimeSeriesExpression) else a for a in self.args] kwargsb = { k: a.build(cache) if isinstance(a, TimeSeriesExpression) else a diff --git a/src/impulse_query_engine/analyze/query/optimizer/__init__.py b/src/impulse_query_engine/analyze/query/optimizer/__init__.py new file mode 100644 index 00000000..5c2b8c31 --- /dev/null +++ b/src/impulse_query_engine/analyze/query/optimizer/__init__.py @@ -0,0 +1,37 @@ +from .context import ELEMENT_TYPE, Run, RunBreak, Sample, SeriesContext +from .pipeline import SeriesPipeline, apply_pushdowns +from .plan import PushdownPlan +from .pushdown import ( + RAW_VARIANT, + VARIANT_COL, + VARIANTS_COL, + Operand, + Pushdown, + PushdownContext, + PushdownRule, +) +from .query_optimizer import QueryOptimizer, default_rules +from .resample_rule import ResamplePushdownRule +from .traversal import Classifier + +__all__ = [ + "ELEMENT_TYPE", + "RAW_VARIANT", + "VARIANT_COL", + "VARIANTS_COL", + "Classifier", + "Operand", + "Pushdown", + "PushdownContext", + "PushdownPlan", + "PushdownRule", + "QueryOptimizer", + "ResamplePushdownRule", + "Run", + "RunBreak", + "Sample", + "SeriesContext", + "SeriesPipeline", + "apply_pushdowns", + "default_rules", +] diff --git a/src/impulse_query_engine/analyze/query/optimizer/context.py b/src/impulse_query_engine/analyze/query/optimizer/context.py new file mode 100644 index 00000000..062125fe --- /dev/null +++ b/src/impulse_query_engine/analyze/query/optimizer/context.py @@ -0,0 +1,282 @@ +"""What a pushdown rule sees of the samples it computes on. + +A rule never handles DataFrames. The pipeline (:mod:`.pipeline`) evaluates the +rule's hooks with a :class:`SeriesContext`, whose properties are Spark columns +describing the current sample of one of the rule's series: its times and value, +its neighbours, the run (e.g. continuous block) it belongs to, and facts about +the whole series. Columns returned by a hook become part of the shared pass +over the channel data. +""" + +from __future__ import annotations + +from collections.abc import Callable, Mapping, Sequence +from dataclasses import dataclass +from typing import TYPE_CHECKING + +import pyspark.sql.functions as F +import pyspark.sql.types as T +from pyspark.sql import Column, Window, WindowSpec + +from .pushdown import VARIANT_COL + +if TYPE_CHECKING: + from .pushdown import Pushdown, PushdownRule + +#: Element type of the arrays returned by :meth:`PushdownRule.emit`. +ELEMENT_TYPE = T.StructType( + [ + T.StructField("tstart", T.DoubleType()), + T.StructField("tend", T.DoubleType()), + T.StructField("value", T.DoubleType()), + ] +) + +#: Name of the built-in run of continuous samples. +BLOCK = "block" + +# Columns the pipeline provides, shared by all rules. +TS, TE = "_pd_ts", "_pd_te" +CHUNK = "_pd_chunk" +PREV_TS, PREV_TE, PREV_VALUE = "_pd_prev_ts", "_pd_prev_te", "_pd_prev_value" +NEXT_TS, NEXT_TE, NEXT_VALUE = "_pd_next_ts", "_pd_next_te", "_pd_next_value" +COUNT = "_pd_n" +LAST = "_pd_last" +BLOCK_END = "_pd_block_end" + + +def run_col(run: str, name: str) -> str: + """Name of the pipeline column *name* of the run *run*.""" + return f"_pd_run__{run}__{name}" + + +@dataclass(frozen=True) +class Sample: + """One sample as columns: ``tstart`` and ``tend`` as double, and its value as double.""" + + tstart: Column + tend: Column + value: Column + + +#: A run definition: whether the sample *cur* starts a new run after *prev*. It +#: is called with ``prev`` columns that are null before the series' first +#: sample; a null result counts as a break. +RunBreak = Callable[[Sample, Sample], Column] + + +@dataclass(frozen=True) +class Run: + """Where the current sample sits in its run, a maximal stretch without a break. + + Attributes + ---------- + starts : Column + The sample starts its run. + id : Column + Number of the run within the series, from 1. + start : Column + ``tstart`` of the run's first sample. + next_starts : Column + The next sample starts a new run; null for the series' last sample. + is_last : Column + The run is the last of its series. + """ + + starts: Column + id: Column + start: Column + next_starts: Column + is_last: Column + + +def block_break(prev: Sample, cur: Sample) -> Column: + """Continuous blocks: a sample continues its block when it starts where the previous ended.""" + return prev.tend.isNull() | ((cur.tstart - prev.tend) != 0) + + +class SeriesContext: + """Columns a rule's hooks build on, for the current sample of one of its series. + + Every series is one channel's samples of one variant, ordered by + ``(tstart, tend, value)``; all variants of a channel have the same samples. + Rules running in series order (``ordered``) see neighbours, runs and series + facts; per-sample rules see only the sample. + + Parameters + ---------- + rule : PushdownRule + The rule the hooks belong to. + variants : Mapping[int, Pushdown] + The rule's variants in this solve. + key : Sequence[str] + Columns identifying a series. + order : Sequence[str] + The channel-data ``tstart``, ``tend`` and value columns: the series order. + ordered : bool + Whether the pipeline runs the rule in series order. + runs : Mapping[str, str] + The rule's run names to pipeline run names. + kept : bool + Whether the hook runs on the kept samples (``kept_columns``, ``emit``). + """ + + def __init__( + self, + rule: PushdownRule, + variants: Mapping[int, Pushdown], + key: Sequence[str], + order: Sequence[str], + ordered: bool, + runs: Mapping[str, str], + kept: bool = False, + ): + self._rule = rule + self._variants = variants + self._key = tuple(key) + self._order = tuple(order) + self._ordered = ordered + self._runs = dict(runs) + self._kept = kept + + def kept_stage(self) -> SeriesContext: + """This context, for the hooks that run on the kept samples.""" + return SeriesContext( + self._rule, + self._variants, + self._key, + self._order, + self._ordered, + self._runs, + kept=True, + ) + + # --- always available ------------------------------------------------------- + + @property + def active(self) -> Column: + """The sample belongs to one of the rule's variants.""" + return F.col(VARIANT_COL).isin(sorted(self._variants)) + + def name(self, column: str) -> str: + """Pipeline name of the rule's column *column*.""" + return f"_pd_rule__{self._rule.name}__{column}" + + def col(self, column: str) -> Column: + """The rule's column *column*, defined by an earlier hook entry.""" + return F.col(self.name(column)) + + def param(self, name: str) -> Column: + """The pushdown parameter *name* of the sample's variant.""" + return F.try_element_at( + F.create_map( + *[ + item + for vid, pushdown in sorted(self._variants.items()) + for item in (F.lit(vid), F.lit(dict(pushdown.params)[name])) + ] + ), + F.col(VARIANT_COL), + ) + + @property + def tstart(self) -> Column: + """The sample's ``tstart`` as double.""" + return F.col(TS) + + @property + def tend(self) -> Column: + """The sample's ``tend`` as double.""" + return F.col(TE) + + @property + def value(self) -> Column: + """The sample's value as double.""" + return F.col(self._order[2]).cast("double") + + @property + def sample(self) -> Sample: + """The current sample.""" + return Sample(self.tstart, self.tend, self.value) + + @staticmethod + def element( + tstart: Column | None = None, tend: Column | None = None, value: Column | None = None + ) -> Column: + """One emitted row; ``tstart`` / ``tend`` left out keep the sample's own times.""" + + def field(col: Column | None, name: str) -> Column: + return (F.lit(None) if col is None else col).cast("double").alias(name) + + return F.struct(field(tstart, "tstart"), field(tend, "tend"), field(value, "value")) + + # --- rules running in series order -------------------------------------------- + + def _require_ordered(self, what: str) -> None: + if not self._ordered: + raise TypeError(f"{self._rule.name}: {what} needs an ordered rule") + + @property + def prev(self) -> Sample: + """The previous sample of the series; null columns for its first sample.""" + self._require_ordered("prev") + return Sample(F.col(PREV_TS), F.col(PREV_TE), F.col(PREV_VALUE)) + + @property + def next(self) -> Sample: + """The next sample of the series; null columns for its last sample.""" + self._require_ordered("next") + return Sample(F.col(NEXT_TS), F.col(NEXT_TE), F.col(NEXT_VALUE)) + + @property + def n(self) -> Column: + """Number of samples in the series.""" + self._require_ordered("n") + return F.col(COUNT) + + @property + def last(self) -> Sample: + """The series' last sample.""" + self._require_ordered("last") + last = F.col(LAST) + return Sample(last.getField("tstart"), last.getField("tend"), last.getField("value")) + + @property + def in_chunk(self) -> WindowSpec: + """Window over the sample's chunk in series order, for local neighbour access. + + Chunks are consecutive slices of a channel, the same for all its series; + use :attr:`prev`, :attr:`next` and runs for anything that must see across + chunk boundaries. + """ + self._require_ordered("in_chunk") + return Window.partitionBy(*self._key, CHUNK).orderBy(*self._order) + + @property + def block(self) -> Run: + """The continuous block the sample belongs to (gaps end blocks).""" + self._require_ordered("block") + return _run(BLOCK) + + def run(self, name: str) -> Run: + """The sample's run of the rule's run definition *name* (see :meth:`PushdownRule.runs`).""" + self._require_ordered("run") + return _run(self._runs[name]) + + @property + def block_end(self) -> Column: + """``nanmax`` of the ``tend`` values of the sample's block (kept samples only).""" + self._require_ordered("block_end") + if not self._kept: + raise TypeError(f"{self._rule.name}: block_end is available to kept samples only") + return F.col(BLOCK_END) + + +def _run(run: str) -> Run: + return Run( + starts=F.col(run_col(run, "starts")), + id=F.col(run_col(run, "id")), + start=F.col(run_col(run, "start")), + next_starts=F.col(run_col(run, "next_starts")), + is_last=F.col(run_col(run, "is_last")), + ) diff --git a/src/impulse_query_engine/analyze/query/optimizer/pipeline.py b/src/impulse_query_engine/analyze/query/optimizer/pipeline.py new file mode 100644 index 00000000..3d9083a9 --- /dev/null +++ b/src/impulse_query_engine/analyze/query/optimizer/pipeline.py @@ -0,0 +1,660 @@ +"""Spark execution of a pushdown plan: one shared pass for all rules. + +:func:`apply_pushdowns` turns the channel data of a solve into the rows the +solver ships: raw rows where still needed, plus every rule's derived rows, each +tagged with its variant. Rules contribute columns only (see +:class:`~.pushdown.PushdownRule`), so the number of reads of the channel data +does not grow with the number of rules: + +* **Ordered rules** (:class:`SeriesPipeline`) read the channel data once in full + and once narrowly (channel boundaries, aggregated before their shuffle). Each + sample is shuffled once, by channel and chunk, however many variants its + channel ships, and processed per chunk in parallel. +* **Raw rows and per-sample rules** share one more read, only when present. + +The ordered pass, per channel (its samples ordered by ``(tstart, tend, value)``): + +1. Channel boundaries give each sample its chunk: equal time slices holding + about :data:`CHUNK_ROWS` / k samples on average for a channel with k variants. +2. A window per chunk derives each sample's neighbours and run breaks. +3. One summary row per chunk, scanned in chunk order, carries what crosses chunk + boundaries: neighbours at chunk edges, run numbers and starts, the channel's + sample count and last sample, and block ends. +4. The samples fan out into one row per variant: a series, one channel's samples + of one variant. Stages 1 to 3 are the same for every variant of a channel, + so they run once; a chunk again holds about :data:`CHUNK_ROWS` rows. +5. Rules add per-sample columns (``row_columns``) and select samples (``keep``). +6. Kept samples get their block end; rules add ``kept_columns``. +7. Rules ``emit`` the derived rows. + +Windows never span more than a chunk of samples (or the chunk summaries of a +channel), so the memory per task is bounded by the chunk size. + +Keeping one full read for the chunk pass and its summaries relies on both +seeing the same plan below the shuffle: + +* The same projected rows: the raw sample columns and a chunk id that cannot be + null. Windows over the samples read raw columns only; a derived window input + is computed before the shuffle, and each consumer would keep only the inputs + it uses. +* The same runtime filters: the full read is joined with *variants* like the + narrow read, so both copies are pruned alike, and the summaries are joined on + keys the planner cannot trace to the scan, so no filter on the chunk pass is + built from its own summaries. +* The value as the last sort key, and summaries that depend on the chunk window. +""" + +from __future__ import annotations + +from collections.abc import Mapping, Sequence +from dataclasses import dataclass +from functools import reduce + +import pyspark.sql.functions as F +import pyspark.sql.types as T +from pyspark.sql import Column, DataFrame, Window + +from .context import ( + BLOCK, + BLOCK_END, + CHUNK, + COUNT, + LAST, + NEXT_TE, + NEXT_TS, + NEXT_VALUE, + PREV_TE, + PREV_TS, + PREV_VALUE, + TE, + TS, + RunBreak, + Sample, + SeriesContext, + block_break, + run_col, +) +from .plan import PushdownPlan +from .pushdown import ( + RAW_VARIANT, + VARIANT_COL, + VARIANTS_COL, + Pushdown, + PushdownRule, + check_name, +) + +#: Average number of rows per chunk of a channel, counting a sample once per variant. +CHUNK_ROWS = 100_000 + +_ELEMENT = "_pd_element" +_KEPT = "_pd_kept" + + +def apply_pushdowns( + rows: DataFrame, variants: DataFrame, plan: PushdownPlan, config, include_raw: bool = True +) -> DataFrame: + """The rows a solve ships: raw rows (when *include_raw*) and all derived rows. + + Parameters + ---------- + rows : pyspark.sql.DataFrame + Channel data ``(container_id, channel_id, tstart, tend, value)``, one row + per sample. + variants : pyspark.sql.DataFrame + ``(container_id, channel_id, VARIANTS_COL)``: the variant ids each matched + channel ships, one row per channel (see :meth:`PushdownPlan.channel_variants`). + Channels without a row are dropped. + plan : PushdownPlan + The solve's pushdown plan. + config : SolverConfig + Provides the internal column names. + include_raw : bool + Whether to keep the raw rows (variant 0). + + Returns + ------- + pyspark.sql.DataFrame + Rows with the columns and types of *rows* plus :data:`VARIANT_COL`. + """ + groups = plan.variants_by_rule() + ordered = [(rule, variants) for rule, variants in groups if rule.ordered] + per_sample = [(rule, variants) for rule, variants in groups if not rule.ordered] + key = [config.container_id_col, config.channel_id_col] + schema = _output_schema(rows) + parts = [] + row_variants = ([RAW_VARIANT] if include_raw else []) + _variant_ids(per_sample) + if row_variants: + # Only the channels shipping a row-branch variant, so the join drops the others. + shipping = variants.select(*key, _shipped(row_variants).alias(VARIANTS_COL)) + tagged = rows.join(F.broadcast(shipping.where(F.size(VARIANTS_COL) > 0)), on=key).select( + *rows.columns, F.explode(VARIANTS_COL).alias(VARIANT_COL) + ) + parts.append(_row_branch(tagged, per_sample, config, schema)) + if ordered: + parts.append(SeriesPipeline(ordered, config).run(rows, variants)) + if not parts: + return rows.withColumn(VARIANT_COL, F.lit(None).cast(T.IntegerType())).limit(0) + return reduce(lambda a, b: a.unionByName(b), parts) + + +def _variant_ids(groups: Sequence[tuple[PushdownRule, Mapping[int, Pushdown]]]) -> list[int]: + return sorted(vid for _, variants in groups for vid in variants) + + +def _shipped(ids: Sequence[int]) -> Column: + """The variant ids among *ids* that a channel ships, from its :data:`VARIANTS_COL`.""" + return F.filter(F.col(VARIANTS_COL), lambda vid: vid.isin(sorted(ids))) + + +def _fan_out(df: DataFrame, variants: DataFrame, key: Sequence[str], ids: Sequence[int]): + """*df* with one row per variant among *ids* its channel ships, in :data:`VARIANT_COL`. + + Channels shipping none of *ids* are dropped. Unlike ``explode``, ``explode_outer`` + adds no inferred filter to the broadcast side of the join, so the pipeline's joins + with *variants* share one broadcast. + """ + return ( + df.join(F.broadcast(variants), on=key) + .withColumn(VARIANT_COL, F.explode_outer(_shipped(ids))) + .where(F.col(VARIANT_COL).isNotNull()) + .drop(VARIANTS_COL) + ) + + +def _opaque_join(df: DataFrame, small: DataFrame, on: Sequence[str]) -> DataFrame: + """*df* joined with the broadcast *small* on keys the planner cannot trace to a scan. + + *df*'s keys are copied behind an always-true non-deterministic guard, so no + runtime filter built from *small* reaches *df*'s scans. When *small* is + computed from *df*, such a filter would make the two copies of *df*'s plan + differ and keep them from sharing their shuffle. The keys must not be null. + """ + guarded = {c: f"_pd_join_{c}" for c in on} + # spark_partition_id() is never negative; Spark folds comparisons on rand() away. + guard = F.spark_partition_id() >= 0 + left = df.select("*", *[F.when(guard, F.col(c)).alias(g) for c, g in guarded.items()]) + right = small.select(*[F.col(c).alias(guarded.get(c, c)) for c in small.columns]) + return left.join(F.broadcast(right), on=list(guarded.values())).drop(*guarded.values()) + + +def _output_schema(rows: DataFrame) -> T.StructType: + """The columns and types of the shipped rows: those of *rows* plus the variant.""" + return T.StructType([*rows.schema.fields, T.StructField(VARIANT_COL, T.IntegerType())]) + + +@dataclass(frozen=True) +class _Run: + """A run definition of the pipeline.""" + + name: str + breaks: RunBreak + + +class SeriesPipeline: + """The ordered pass shared by all rules that need their series in order. + + Chunking and everything that crosses chunk edges runs once per channel; its + samples fan out into one series per variant only for the rules' hooks. The + shuffle therefore carries each sample once, while a task after it processes + about k rows per shuffled sample of a channel with k variants (chunks are k + times smaller to keep the rows per chunk at :data:`CHUNK_ROWS`). + + Rule runs are computed for every channel of the pass; a rule only reads its + own runs, on its own series. + + Parameters + ---------- + rules : Sequence of (PushdownRule, Mapping[int, Pushdown]) + Ordered rules with their variants in this solve. + config : SolverConfig + Provides the internal column names. + """ + + def __init__(self, rules: Sequence[tuple[PushdownRule, Mapping[int, Pushdown]]], config): + self.rules = list(rules) + self.config = config + self.channel_key = [config.container_id_col, config.channel_id_col] + self.key = [*self.channel_key, VARIANT_COL] + self.order = [config.tstart_col, config.tend_col, config.value_col] + self.variant_ids = _variant_ids(self.rules) + self.runs = [_Run(BLOCK, block_break)] + self.rule_runs: dict[str, dict[str, str]] = {} + for rule, _ in self.rules: + names = {} + for name, breaks in rule.runs().items(): + names[name] = f"{rule.name}__{check_name('run', name)}" + self.runs.append(_Run(names[name], breaks)) + self.rule_runs[rule.name] = names + + def _context(self, rule: PushdownRule, variants: Mapping[int, Pushdown]) -> SeriesContext: + return SeriesContext(rule, variants, self.key, self.order, True, self.rule_runs[rule.name]) + + def run(self, rows: DataFrame, variants: DataFrame) -> DataFrame: + """Replace the samples of the rules' variants with their derived rows. + + *rows* holds the channel data, one row per sample; *variants* the variant + ids of each channel (see :func:`apply_pushdowns`). Only channels shipping a + variant of the rules are processed. + """ + df = self._chunked(rows, variants) + df = self._chunk_scan(df) + df = _opaque_join(df, self._chunk_summaries(df), [*self.channel_key, CHUNK]) + df = self._resolve_neighbours_and_runs(df) + # The rules' columns read the variant (SeriesContext.active, .param). + df = _fan_out(df, variants, self.channel_key, self.variant_ids) + contexts = [(rule, self._context(rule, variants)) for rule, variants in self.rules] + for rule, ctx in contexts: + df = _with_rule_columns(df, ctx, rule.row_columns(ctx)) + df = _keep(df, contexts, extra=_flag(run_col(BLOCK, "ends"))) + df = self._with_block_end(df) + kept = [(rule, ctx.kept_stage()) for rule, ctx in contexts] + for rule, ctx in kept: + df = _with_rule_columns(df, ctx, rule.kept_columns(ctx)) + return _emit(df, kept, _output_schema(rows), self.order, raw=None) + + # --- stages ---------------------------------------------------------------------- + + def _chunked(self, rows: DataFrame, variants: DataFrame) -> DataFrame: + """Channel boundaries and each sample's chunk, with the raw sample columns. + + A channel with k variants gets k times as many chunks: its samples fan out + per chunk before the rules' hooks (see :meth:`run`). + """ + ts, te, value = self.order + ts_d, te_d = F.col(ts).cast("double"), F.col(te).cast("double") + matched = rows.join(F.broadcast(variants), on=self.channel_key) + # The time range spans the finite times; a channel without a finite tend is + # still cut by its tstart range. + series = ( + matched.groupBy(*self.channel_key).agg( + F.min(_nan_free(ts_d)).alias("_pd_first"), + F.greatest(F.max(_nan_free(te_d)), F.max(_nan_free(ts_d))).alias("_pd_end"), + F.count(F.lit(1)).alias("_pd_count"), + F.max(F.size(_shipped(self.variant_ids))).alias("_pd_k"), + ) + # Only the channels shipping a variant of the rules; filtering the aggregate + # keeps the broadcast of *variants* the same as for the other joins. + .where(F.col("_pd_k") > 0) + ) + first, end = F.col("_pd_first"), F.col("_pd_end") + n_chunks = F.ceil(F.col("_pd_count") * F.col("_pd_k") / F.lit(CHUNK_ROWS)) + position = F.least(F.floor((ts_d - first) / (end - first) * n_chunks), n_chunks - 1) + # Null and NaN tstarts go where the series order puts them: first and last. + chunk = F.coalesce( + F.when(ts_d.isNull(), F.lit(0).cast("long")) + .when(F.isnan(ts_d), n_chunks - 1) + .when(end > first, position), + F.lit(0).cast("long"), + ) + return matched.join(F.broadcast(series), on=self.channel_key).select( + *self.channel_key, ts, te, value, chunk.alias(CHUNK) + ) + + def _chunk_scan(self, df: DataFrame) -> DataFrame: + """Neighbours within the chunk, and each run's breaks and segments.""" + ts, te, value = self.order + in_chunk = Window.partitionBy(*self.channel_key, CHUNK).orderBy(*self.order) + running = in_chunk.rowsBetween(Window.unboundedPreceding, Window.currentRow) + # The neighbour windows read the raw columns and cast their results. + df = ( + df.withColumn("_pd_first_in_chunk", F.row_number().over(in_chunk) == 1) + .withColumn("_pd_last_in_chunk", F.lead(F.lit(True)).over(in_chunk).isNull()) + .withColumn("_pd_lag_ts", F.lag(ts).over(in_chunk).cast("double")) + .withColumn("_pd_lag_te", F.lag(te).over(in_chunk).cast("double")) + .withColumn("_pd_lag_value", F.lag(value).over(in_chunk)) + .withColumn("_pd_lead_ts", F.lead(ts).over(in_chunk).cast("double")) + .withColumn("_pd_lead_te", F.lead(te).over(in_chunk).cast("double")) + .withColumn("_pd_lead_value", F.lead(value).over(in_chunk)) + .withColumn(TS, F.col(ts).cast("double")) + .withColumn(TE, F.col(te).cast("double")) + ) + prev = Sample( + F.col("_pd_lag_ts"), F.col("_pd_lag_te"), F.col("_pd_lag_value").cast("double") + ) + cur = Sample(F.col(TS), F.col(TE), F.col(value).cast("double")) + for run in self.runs: + brk = F.col(run_col(run.name, "brk")) + df = ( + df.withColumn( + run_col(run.name, "brk"), + F.when(F.col("_pd_first_in_chunk"), F.lit(0).cast("long")).otherwise( + _breaks(run, prev, cur) + ), + ) + .withColumn(run_col(run.name, "seg"), F.sum(brk).over(running)) + .withColumn( + run_col(run.name, "seg_start"), + F.last(F.when(brk == 1, F.col(TS)), ignorenulls=True).over(running), + ) + .withColumn(run_col(run.name, "lead_brk"), F.lead(brk).over(in_chunk)) + ) + return df + + def _chunk_summaries(self, df: DataFrame) -> DataFrame: + """One row per chunk with everything that crosses chunk boundaries.""" + order = F.struct(*self.order) + sample = F.struct( + F.col(TS).alias("tstart"), + F.col(TE).alias("tend"), + F.col(self.order[2]).cast("double").alias("value"), + ) + block_brk, block_seg = F.col(run_col(BLOCK, "brk")), F.col(run_col(BLOCK, "seg")) + # Head and tail are taken via the chunk window's flags, so the summaries always + # run on the chunk-partitioned samples and share their read. + head = F.when(F.col("_pd_first_in_chunk"), F.lit(1)) + tail = F.when(F.col("_pd_last_in_chunk"), F.lit(1)) + aggregates = [ + F.max_by(sample, head).alias("_pd_head"), + F.max_by(sample, tail).alias("_pd_tail"), + F.max_by(block_brk, tail).alias("_pd_tail_brk"), + F.count(F.lit(1)).alias("_pd_chunk_count"), + # The block of the chunk's first sample: its last sample within the chunk. + F.max_by(sample, F.when(block_seg == 0, order)).alias("_pd_seg0_last"), + F.sum(F.when(block_seg == 0, 1).otherwise(0)).alias("_pd_seg0_count"), + ] + for run in self.runs: + brk = F.col(run_col(run.name, "brk")) + aggregates += [ + F.sum(brk).alias(run_col(run.name, "inner")), + F.max(F.when(brk == 1, F.col(TS))).alias(run_col(run.name, "last_start")), + ] + chunks = df.groupBy(*self.channel_key, CHUNK).agg(*aggregates) + + by_chunk = Window.partitionBy(*self.channel_key).orderBy(CHUNK) + before = by_chunk.rowsBetween(Window.unboundedPreceding, -1) + so_far = by_chunk.rowsBetween(Window.unboundedPreceding, Window.currentRow) + everything = by_chunk.rowsBetween(Window.unboundedPreceding, Window.unboundedFollowing) + later = ( + Window.partitionBy(*self.channel_key) + .orderBy(F.col(CHUNK).desc()) + .rowsBetween(Window.unboundedPreceding, -1) + ) + head, tail = F.col("_pd_head"), F.col("_pd_tail") + for field in ("tstart", "tend", "value"): + chunks = chunks.withColumn( + f"_pd_prev_tail_{field}", F.lag(tail.getField(field)).over(by_chunk) + ).withColumn(f"_pd_next_head_{field}", F.lead(head.getField(field)).over(by_chunk)) + prev_tail = Sample(*(F.col(f"_pd_prev_tail_{f}") for f in ("tstart", "tend", "value"))) + head_sample = Sample(*(head.getField(f) for f in ("tstart", "tend", "value"))) + keep = [*self.channel_key, CHUNK, "_pd_head", "_pd_tail"] + keep += [f"_pd_prev_tail_{f}" for f in ("tstart", "tend", "value")] + keep += [f"_pd_next_head_{f}" for f in ("tstart", "tend", "value")] + for run in self.runs: + head_brk = F.col(run_col(run.name, "head_brk")) + inner = F.col(run_col(run.name, "inner")) + starts_here = F.coalesce( + F.col(run_col(run.name, "last_start")), + F.when(head_brk == 1, head.getField("tstart")), + ) + chunks = ( + chunks.withColumn( + run_col(run.name, "head_brk"), + _breaks(run, prev_tail, head_sample), + ) + .withColumn(run_col(run.name, "breaks"), head_brk + inner) + .withColumn( + run_col(run.name, "base"), + F.sum(run_col(run.name, "breaks")).over(so_far) - inner, + ) + .withColumn( + run_col(run.name, "head_start"), + F.when(head_brk == 1, head.getField("tstart")).otherwise( + F.last(starts_here, ignorenulls=True).over(before) + ), + ) + .withColumn(run_col(run.name, "next_head_brk"), F.lead(head_brk).over(by_chunk)) + .withColumn( + run_col(run.name, "total"), F.sum(run_col(run.name, "breaks")).over(everything) + ) + ) + keep += [ + run_col(run.name, c) + for c in ("head_brk", "base", "head_start", "next_head_brk", "total") + ] + + # Block ends, from the block's last sample (see _block_end_at), wrapped in a + # struct: a block's end may be null, and must not be mistaken for none. + head_brk = F.col(run_col(BLOCK, "head_brk")) == 1 + count = F.col("_pd_chunk_count") + tail_single = F.when(count == 1, head_brk).otherwise(F.col("_pd_tail_brk") == 1) + seg0_single = (F.col("_pd_seg0_count") == 1) & head_brk + tail_ends = F.coalesce(F.col(run_col(BLOCK, "next_head_brk")) == 1, F.lit(True)) + tail_end = _found(_block_end_at(tail, tail_single)) + chunks = ( + chunks.withColumn(COUNT, F.sum(count).over(everything)) + .withColumn(LAST, F.last(tail).over(everything)) + # The end of the block the chunk starts in, if that block ends here. + .withColumn( + "_pd_first_end", + F.when( + F.col(run_col(BLOCK, "inner")) > 0, + _found(_block_end_at(F.col("_pd_seg0_last"), seg0_single)), + ).when(tail_ends, tail_end), + ) + .withColumn( + "_pd_tail_block_end", + F.when(tail_ends, tail_end).otherwise( + F.last("_pd_first_end", ignorenulls=True).over(later) + ), + ) + ) + return chunks.select(*keep, COUNT, LAST, "_pd_tail_block_end") + + def _resolve_neighbours_and_runs(self, df: DataFrame) -> DataFrame: + """Neighbours and run facts of every sample, across chunk boundaries.""" + first, last = F.col("_pd_first_in_chunk"), F.col("_pd_last_in_chunk") + df = ( + df.withColumn( + PREV_TS, + F.when(first, F.col("_pd_prev_tail_tstart")).otherwise(F.col("_pd_lag_ts")), + ) + .withColumn( + PREV_TE, F.when(first, F.col("_pd_prev_tail_tend")).otherwise(F.col("_pd_lag_te")) + ) + .withColumn( + PREV_VALUE, + F.when(first, F.col("_pd_prev_tail_value")).otherwise( + F.col("_pd_lag_value").cast("double") + ), + ) + .withColumn( + NEXT_TS, + F.when(last, F.col("_pd_next_head_tstart")).otherwise(F.col("_pd_lead_ts")), + ) + .withColumn( + NEXT_TE, F.when(last, F.col("_pd_next_head_tend")).otherwise(F.col("_pd_lead_te")) + ) + .withColumn( + NEXT_VALUE, + F.when(last, F.col("_pd_next_head_value")).otherwise( + F.col("_pd_lead_value").cast("double") + ), + ) + ) + for run in self.runs: + + def c(name: str, run: _Run = run) -> Column: + return F.col(run_col(run.name, name)) + + df = ( + df.withColumn( + run_col(run.name, "starts"), + F.when(first, c("head_brk") == 1).otherwise(c("brk") == 1), + ) + .withColumn(run_col(run.name, "id"), c("base") + c("seg")) + .withColumn( + run_col(run.name, "start"), F.coalesce(c("seg_start"), c("head_start")) + ) + .withColumn( + run_col(run.name, "next_starts"), + F.when(last, c("next_head_brk") == 1).otherwise(c("lead_brk") == 1), + ) + .withColumn(run_col(run.name, "is_last"), c("id") == c("total")) + ) + # The last sample of a block (also the series' last sample). + return df.withColumn( + run_col(BLOCK, "ends"), + F.coalesce(F.col(run_col(BLOCK, "next_starts")), F.lit(True)), + ) + + def _with_block_end(self, df: DataFrame) -> DataFrame: + """Each kept sample's block end: the nearest block end at or after it. + + Every block's last sample is kept, so within a chunk a reverse scan over + the kept samples finds it; a block that continues past the chunk ends + where the chunk summary says. + """ + reverse = ( + Window.partitionBy(*self.key, CHUNK) + .orderBy(*[F.col(c).desc() for c in self.order]) + .rowsBetween(Window.unboundedPreceding, Window.currentRow) + ) + here = F.when( + F.col(run_col(BLOCK, "ends")), + _found( + _block_end_at( + F.struct(F.col(TS).alias("tstart"), F.col(TE).alias("tend")), + F.col(run_col(BLOCK, "starts")), + ) + ), + ) + found = F.coalesce( + F.last(here, ignorenulls=True).over(reverse), F.col("_pd_tail_block_end") + ) + return df.withColumn(BLOCK_END, found.getField("end")) + + +def _row_branch( + rows: DataFrame, + rules: Sequence[tuple[PushdownRule, Mapping[int, Pushdown]]], + config, + schema: T.StructType, +) -> DataFrame: + """Raw rows unchanged, and the derived rows of per-sample rules. + + *rows* are tagged with :data:`VARIANT_COL`, one copy per variant; *schema* gives + the output columns. + """ + if not rules: + return rows + key = [config.container_id_col, config.channel_id_col, VARIANT_COL] + order = [config.tstart_col, config.tend_col, config.value_col] + df = rows.withColumn(TS, F.col(order[0]).cast("double")).withColumn( + TE, F.col(order[1]).cast("double") + ) + contexts = [ + (rule, SeriesContext(rule, variants, key, order, False, {})) for rule, variants in rules + ] + for rule, ctx in contexts: + df = _with_rule_columns(df, ctx, rule.row_columns(ctx)) + df = _keep(df, contexts, extra=F.col(VARIANT_COL) == RAW_VARIANT) + kept = [(rule, ctx.kept_stage()) for rule, ctx in contexts] + for rule, ctx in kept: + df = _with_rule_columns(df, ctx, rule.kept_columns(ctx)) + return _emit(df, kept, schema, order, raw=F.col(VARIANT_COL) == RAW_VARIANT) + + +# --- helpers shared by both branches ------------------------------------------------ + + +def _with_rule_columns(df: DataFrame, ctx: SeriesContext, columns: Mapping[str, Column]): + """Add a rule's columns, defined for the rule's samples only.""" + for name, column in columns.items(): + df = df.withColumn(ctx.name(check_name("column", name)), F.when(ctx.active, column)) + return df + + +def _keep_col(rule: PushdownRule) -> str: + return f"_pd_keep__{rule.name}" + + +def _keep(df: DataFrame, contexts, extra: Column) -> DataFrame: + """Drop the samples no rule keeps (*extra* marks samples kept regardless). + + The filter reads a flag column behind an always-true non-deterministic guard. + Spark pushes filters through deterministic projections by substituting their + column definitions, without a cost check; rule columns that refer to each other + several times would then multiply into one huge condition. The guard stops the + pushdown, so the rule columns are computed once, in their own projections. + """ + keep = extra + for rule, ctx in contexts: + df = df.withColumn( + _keep_col(rule), F.coalesce(F.when(ctx.active, rule.keep(ctx)), F.lit(False)) + ) + keep = keep | F.col(_keep_col(rule)) + # spark_partition_id() is never negative; Spark folds comparisons on rand() away. + guarded = F.when(F.spark_partition_id() >= 0, keep) + return df.withColumn(_KEPT, guarded).where(F.col(_KEPT)).drop(_KEPT) + + +def _emit( + df: DataFrame, contexts, schema: T.StructType, order: Sequence[str], raw: Column | None +) -> DataFrame: + """Explode the rules' emitted rows into rows with the columns and types of *schema*. + + *raw* marks rows passed through unchanged. + """ + elements = None + for rule, ctx in contexts: + emitted = F.when(F.col(_keep_col(rule)), rule.emit(ctx)) + elements = ( + F.when(ctx.active, emitted) if elements is None else elements.when(ctx.active, emitted) + ) + original = F.lit(False) if raw is None else raw + if raw is not None: + # One placeholder element keeps each raw row. + elements = F.when(raw, F.array(SeriesContext.element())).otherwise(elements) + element = F.col(_ELEMENT) + fields = dict(zip(order, ("tstart", "tend", "value"), strict=True)) + + def out(col: str) -> Column: + if col not in df.columns: + # Columns beyond the channel-data shape (e.g. POI string values) are not + # carried through the series scan; derived rows have none. + return F.lit(None).cast(schema[col].dataType).alias(col) + if col not in fields: + return F.col(col) + derived = element.getField(fields[col]).cast(schema[col].dataType) + # Times left out of an element keep the sample's own. + keep = ( + original + if fields[col] == "value" + else original | element.getField(fields[col]).isNull() + ) + return F.when(keep, F.col(col)).otherwise(derived).alias(col) + + return df.withColumn(_ELEMENT, F.explode(elements)).select( + *[out(c) for c in schema.fieldNames()] + ) + + +def _breaks(run: _Run, prev: Sample, cur: Sample) -> Column: + """1 where *cur* starts a new run after *prev* (a null break counts as one).""" + return F.coalesce(run.breaks(prev, cur), F.lit(True)).cast("long") + + +def _flag(name: str) -> Column: + return F.coalesce(F.col(name), F.lit(False)) + + +def _nan_free(col: Column) -> Column: + """Map NaN to null so ``min`` / ``max`` behave like ``numpy.nanmin`` / ``nanmax``.""" + return F.when(~F.isnan(col), col) + + +def _found(end: Column) -> Column: + """*end* in a struct that is not null even when *end* is.""" + return F.struct(end.alias("end")) + + +def _block_end_at(last_sample: Column, single: Column) -> Column: + """``nanmax`` of a block's ``tend`` values, from its last sample. + + Within a block every ``tend`` equals the next sample's ``tstart``, so the + maximum is the last ``tend`` or, for blocks of several samples, the last + ``tstart`` if larger (NaN ``tend`` ignored). + """ + tend = _nan_free(last_sample.getField("tend")) + return F.when(single, tend).otherwise(F.greatest(last_sample.getField("tstart"), tend)) diff --git a/src/impulse_query_engine/analyze/query/optimizer/plan.py b/src/impulse_query_engine/analyze/query/optimizer/plan.py new file mode 100644 index 00000000..295b918f --- /dev/null +++ b/src/impulse_query_engine/analyze/query/optimizer/plan.py @@ -0,0 +1,230 @@ +"""The outcome of query optimization: which subtrees Spark computes ahead of the UDF.""" + +from __future__ import annotations + +from collections.abc import Iterable, Mapping +from dataclasses import dataclass, field +from typing import Any + +import pyspark.sql.functions as F +from pyspark.sql import Column + +from impulse_query_engine.analyze.metadata.time_series_expression import TimeSeriesExpression + +from .pushdown import RAW_VARIANT, Pushdown, PushdownRule +from .traversal import Classifier + + +@dataclass(frozen=True, eq=False) +class PushdownPlan: + """Pushdowns selected for one solve call, shipped to the solver and the UDF. + + Variant ``0`` is the raw channel data; variants ``1..n`` are derived series, + one per distinct :attr:`Pushdown.variant_key`. + + Parameters + ---------- + rules : tuple of PushdownRule + Rules used to recognize pushed-down nodes during evaluation. + variant_ids : Mapping[tuple, int] + Variant key to variant id. + pushdowns_by_variant : Mapping[int, Pushdown] + Variant id to a representative pushdown (rule and parameters). + variants_by_selector : Mapping[int, tuple of int] + Selector id to the variant ids derived from it. + raw_selector_ids : frozenset of int + Selectors whose raw channel data is still required. + """ + + rules: tuple[PushdownRule, ...] = () + variant_ids: Mapping[tuple, int] = field(default_factory=dict) + pushdowns_by_variant: Mapping[int, Pushdown] = field(default_factory=dict) + variants_by_selector: Mapping[int, tuple[int, ...]] = field(default_factory=dict) + raw_selector_ids: frozenset[int] = frozenset() + + @staticmethod + def build( + rules: Iterable[PushdownRule], + pushdowns: Iterable[Pushdown], + selector_ids: Iterable[int], + ) -> PushdownPlan: + """Assign variant ids to *pushdowns*, in discovery order. + + Parameters + ---------- + rules : Iterable[PushdownRule] + The optimizer's rules. + pushdowns : Iterable[Pushdown] + Accepted pushdowns; each one's selector is pushed down entirely. + selector_ids : Iterable[int] + Ids of all selectors in the solve. + + Raises + ------ + ValueError + If a pushdown computes on another pushdown's result (``source``), + which the pipeline cannot compute. + """ + variant_ids: dict[tuple, int] = {} + pushdowns_by_variant: dict[int, Pushdown] = {} + variants_by_selector: dict[int, list[int]] = {} + for pushdown in pushdowns: + if pushdown.source is not None: + raise ValueError(f"pushdown {pushdown.describe()} has a source") + key = pushdown.variant_key + if key not in variant_ids: + variant_ids[key] = len(variant_ids) + 1 + pushdowns_by_variant[variant_ids[key]] = pushdown + variants = variants_by_selector.setdefault(pushdown.selector.selector_id, []) + if variant_ids[key] not in variants: + variants.append(variant_ids[key]) + return PushdownPlan( + rules=tuple(rules), + variant_ids=variant_ids, + pushdowns_by_variant=pushdowns_by_variant, + variants_by_selector={sid: tuple(v) for sid, v in variants_by_selector.items()}, + raw_selector_ids=frozenset(selector_ids) - variants_by_selector.keys(), + ) + + @property + def is_empty(self) -> bool: + """``True`` when nothing is pushed down.""" + return not self.variant_ids + + @property + def pushed_selector_ids(self) -> frozenset[int]: + """Selectors whose raw channel data is replaced by derived variants.""" + return frozenset(self.variants_by_selector) + + def variants_by_rule(self) -> list[tuple[PushdownRule, dict[int, Pushdown]]]: + """Group the variants by the rule that computes them. + + Raises + ------ + ValueError + If two different rules share a name: their pipeline columns would + collide. + """ + groups: dict[str, tuple[PushdownRule, dict[int, Pushdown]]] = {} + for variant, pushdown in self.pushdowns_by_variant.items(): + rule = pushdown.rule + group = groups.setdefault(rule.name, (rule, {})) + if group[0] is not rule: + raise ValueError(f"two different pushdown rules are named {rule.name!r}") + group[1][variant] = pushdown + return list(groups.values()) + + def lookup( + self, node: TimeSeriesExpression, classify: Classifier | None = None + ) -> tuple[Pushdown, int] | None: + """Return ``(pushdown, variant)`` when *node* is a pushed-down subtree. + + Pass a *classify* shared by the lookups of one evaluation so that every + node is classified once. + """ + if classify is None: + classify = Classifier(self.rules) + pushdown = classify(node) + if pushdown is None: + return None + variant = self.variant_ids.get(pushdown.variant_key) + if variant in self.variants_by_selector.get(pushdown.selector.selector_id, ()): + return pushdown, variant + return None + + def native_targets( + self, selections: Iterable[Any] + ) -> dict[int, dict[int, list[tuple[int, bool]]]] | None: + """Sources of calculated channels that are pushed down as a whole. + + Parameters + ---------- + selections : Iterable + The selections of a ``solve_calculated_channels`` call. + + Returns + ------- + dict or None + ``{variant: {selector_id: [(channel_id, uses_alias), ...]}}``: the + calculated channels (by output ``channel_id``) whose rows are the + given variant's rows of the channel resolved for ``selector_id``. + ``None`` unless every selection is a ``CalculatedChannel`` whose + whole expression is a pushdown emitting its result rows. + """ + from impulse_query_engine.analyze.query.channels.calculated_channel import ( + CalculatedChannel, + ) + + selections = list(selections) + if not selections: + return None + targets: dict[int, dict[int, list[tuple[int, bool]]]] = {} + classify = Classifier(self.rules) + for selection in selections: + if not isinstance(selection, CalculatedChannel): + return None + hit = self.lookup(selection.expr, classify) + if hit is None or not hit[0].rule.emits_result_rows(hit[0]): + return None + pushdown, variant = hit + by_selector = targets.setdefault(variant, {}) + by_selector.setdefault(pushdown.selector.selector_id, []).append( + (selection.channel_id, pushdown.selector.uses_alias) + ) + return targets + + def channel_variants(self, selector_ids: Column) -> Column: + """Variant ids a matched channel ships, given its ``selector_ids`` array. + + A channel ships only its raw data (``[0]``) unless every one of its + selectors is pushed down; then it ships the derived variants instead. + """ + ids = PushdownPlan.channel_selector_ids(selector_ids) + pushed = sorted(self.variants_by_selector) + needs_raw = F.exists(ids, lambda sid: ~sid.isin(pushed)) | (F.size(ids) == 0) + mapping = F.create_map( + *[ + item + for sid in pushed + for item in ( + F.lit(sid).cast("long"), + F.array(*[F.lit(v) for v in self.variants_by_selector[sid]]), + ) + ] + ) + no_variants = F.array().cast("array") + derived = F.array_distinct( + F.flatten( + F.transform( + ids, lambda sid: F.coalesce(F.try_element_at(mapping, sid), no_variants) + ) + ) + ) + return F.when(F.coalesce(needs_raw, F.lit(True)), F.array(F.lit(RAW_VARIANT))).otherwise( + derived + ) + + @staticmethod + def channel_selector_ids(selector_ids: Column | str) -> Column: + """A matched channel's distinct, non-null ``selector_ids``, as ``long``. + + Variant tagging (:meth:`channel_variants`) and the native calculated-channel + rows both read a channel's selectors through this, so they agree on which + selectors it carries. + """ + return F.array_distinct( + F.filter( + F.transform(selector_ids, lambda sid: sid.cast("long")), + lambda sid: sid.isNotNull(), + ) + ) + + def describe(self) -> str: + """One-line summary of the plan for logging.""" + variants = ", ".join( + f"{vid}={pushdown.describe()}" for vid, pushdown in self.pushdowns_by_variant.items() + ) + return ( + f"variants [{variants}]; pushed selectors {sorted(self.pushed_selector_ids)}; " + f"raw selectors {sorted(self.raw_selector_ids)}" + ) diff --git a/src/impulse_query_engine/analyze/query/optimizer/pushdown.py b/src/impulse_query_engine/analyze/query/optimizer/pushdown.py new file mode 100644 index 00000000..f05e9590 --- /dev/null +++ b/src/impulse_query_engine/analyze/query/optimizer/pushdown.py @@ -0,0 +1,230 @@ +"""Building blocks of Spark pushdown rules.""" + +from __future__ import annotations + +import abc +import re +from collections.abc import Callable, Mapping +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any, ClassVar + +import pyspark.sql.functions as F +import pyspark.sql.types as T +from pyspark.sql import Column + +from impulse_query_engine.analyze.metadata.time_series_expression import ( + TimeSeriesExpression, + TimeSeriesSelector, +) +from impulse_query_engine.model.series.sample_series import SampleSeries + +if TYPE_CHECKING: + import pandas as pd + + from impulse_query_engine.analyze.query.solvers.default_solver import TimeSeriesCache + from impulse_query_engine.analyze.query.solvers.solver_config import SolverConfig + + from .context import RunBreak, SeriesContext + +#: Column tagging each channel-data row with the series variant it belongs to. +VARIANT_COL = "__impulse_variant" +#: Column listing the variant ids a matched channel ships, one row per channel. +VARIANTS_COL = "__impulse_variants" +#: Variant id of the unmodified (raw) channel data. +RAW_VARIANT = 0 + +_NAME = re.compile(r"[a-z][a-z0-9]*(_[a-z0-9]+)*") + + +def check_name(kind: str, name: str) -> str: + """Return *name* if it is a valid rule, rule column or run name, else raise. + + Names are lowercase letters, digits and single underscores, starting with a + letter. Pipeline columns join them with ``__``, which a name never contains, + so names of different rules cannot collide. + """ + if not isinstance(name, str) or not _NAME.fullmatch(name): + raise ValueError( + f"{kind} name {name!r} must consist of lowercase letters, digits and single " + "underscores, starting with a letter" + ) + return name + + +@dataclass(frozen=True) +class PushdownContext: + """Plan-time facts about the channel data a pushdown would run on. + + Parameters + ---------- + tstart_type, tend_type : pyspark.sql.types.DataType + Spark types of the channel-data ``tstart`` / ``tend`` columns. + """ + + tstart_type: T.DataType + tend_type: T.DataType + + @staticmethod + def from_schema(schema: T.StructType, config: SolverConfig) -> PushdownContext: + """Build the context from the column-mapped channel-data schema.""" + return PushdownContext( + tstart_type=schema[config.tstart_col].dataType, + tend_type=schema[config.tend_col].dataType, + ) + + +@dataclass(frozen=True, eq=False) +class Pushdown: + """A subtree that a :class:`PushdownRule` computes natively in Spark. + + Expression nodes overload ``==``, so pushdowns compare by identity; use + :attr:`variant_key` to identify equivalent pushdowns. + + A pushdown reads the samples of its channel, possibly absorbing pushed + operands into that pass, or computes on the result of another pushdown, its + :attr:`source`. The pipeline computes the former only. + + Parameters + ---------- + rule : PushdownRule + The rule that matched the subtree. + selector : TimeSeriesSelector + The physical channel selector the subtree reads. + params : tuple of (str, Any) + Canonical, hashable operation parameters (e.g. ``(("sample_rate", 1e6),)``). + consumed : tuple of TimeSeriesSelector + Every selector occurrence inside the subtree (its ``get_selectors()``). + source : Pushdown, optional + The pushdown whose result this one computes on; ``None`` when it reads + the channel's samples. + """ + + rule: PushdownRule + selector: TimeSeriesSelector + params: tuple[tuple[str, Any], ...] + consumed: tuple[TimeSeriesSelector, ...] + source: Pushdown | None = None + + @property + def variant_key(self) -> tuple: + """Identity of the derived series, independent of the input channel.""" + key = (self.rule.name, self.params) + return key if self.source is None else (*key, self.source.variant_key) + + def describe(self) -> str: + """Human-readable ``rule(param=value, ...)`` label.""" + params = ", ".join(f"{k}={v}" for k, v in self.params) + label = f"{self.rule.name}({params})" + return label if self.source is None else f"{label} on {self.source.describe()}" + + +#: Classification of an operand of the node a rule matches: the selector of a +#: channel, the :class:`Pushdown` computing the operand, or ``None`` when the +#: operand is evaluated in pandas or is not an expression. +Operand = Callable[[Any], "TimeSeriesSelector | Pushdown | None"] + + +class PushdownRule(abc.ABC): + """Recognizes a subtree and computes it in Spark ahead of the pandas UDF. + + Planning and decoding run in Python: :meth:`match` recognizes a node from + its operands' classification, :meth:`decode` turns one + container/channel/variant slice of the derived rows back into the object the + subtree evaluates to. The result must equal evaluating the subtree in pandas + on the same channel data. + + The Spark side is a set of hooks returning columns, evaluated by the shared + pushdown pipeline (:mod:`.pipeline`) for the samples of the rule's variants, + one series (channel and variant) at a time, with a :class:`SeriesContext`: + + 1. :meth:`row_columns` per sample; + 2. :meth:`keep` selects the samples that go on; + 3. :meth:`kept_columns` per kept sample; + 4. :meth:`emit` the derived rows of a kept sample. + + Rules never handle DataFrames, so every rule shares the pipeline's reads and + shuffles. Derived rows have the channel-data shape + ``(container_id, channel_id, variant, tstart, tend, value)``. + """ + + #: Unique rule name, part of every :attr:`Pushdown.variant_key`; lowercase + #: letters, digits and single underscores (see :func:`check_name`). + name: ClassVar[str] + #: Whether the rule needs its series in order (neighbours, runs, series + #: facts). Per-sample rules set ``False`` and skip the series scan. + ordered: ClassVar[bool] = True + + @abc.abstractmethod + def match(self, node: TimeSeriesExpression, operand: Operand) -> Pushdown | None: + """Return the pushdown for *node*, or ``None`` if the rule does not apply. + + Nodes are classified bottom-up: *operand* returns the classification of + any operand of *node*. A pushdown reading the channel's samples sets no + ``source``; when it absorbs a pushed operand into that pass, its + ``consumed`` includes the operand's and :meth:`supports` covers both. A + pushdown computing on a pushed operand's result sets ``source`` to it. + + Purely syntactic: it runs on the driver while planning and again inside + the UDF to recognize the node, so it must not depend on data or schemas. + """ + + def supports(self, pushdown: Pushdown, context: PushdownContext) -> bool: + """Whether *pushdown* can run on channel data described by *context*.""" + return True + + def result_type(self, pushdown: Pushdown) -> type: + """Core-model class :meth:`decode` returns for *pushdown*.""" + return SampleSeries + + def runs(self) -> Mapping[str, RunBreak]: + """Run definitions beyond the built-in continuous blocks, by name. + + Each maps ``(prev, cur)`` samples to whether ``cur`` starts a new run; + :meth:`SeriesContext.run` then describes the sample's run. Names follow + :func:`check_name` and only need to be unique within the rule. + """ + return {} + + def row_columns(self, ctx: SeriesContext) -> Mapping[str, Column]: + """Columns computed for every sample, in order; refer to them via ``ctx.col``. + + Column names follow :func:`check_name` and only need to be unique within + the rule. + """ + return {} + + def keep(self, ctx: SeriesContext) -> Column: + """Whether a sample can emit rows; the others are dropped early.""" + return F.lit(True) + + def kept_columns(self, ctx: SeriesContext) -> Mapping[str, Column]: + """Columns computed for the kept samples, in order (``ctx.block_end`` available).""" + return {} + + @abc.abstractmethod + def emit(self, ctx: SeriesContext) -> Column: + """The derived rows of a kept sample. + + Returns + ------- + Column + An array of :data:`~.context.ELEMENT_TYPE` structs built with + :meth:`SeriesContext.element`; empty or null emits nothing. Times + left out keep the sample's own ``tstart`` / ``tend``. + """ + + def decode(self, rows: pd.DataFrame, pushdown: Pushdown, cache: TimeSeriesCache) -> Any: + """Build the subtree's result from one container/channel/variant slice.""" + return cache.to_sample_series(rows, uses_alias=pushdown.selector.uses_alias) + + def emits_result_rows(self, pushdown: Pushdown) -> bool: + """Whether *pushdown*'s derived rows are its result's samples as they are. + + Holds when the result is a :class:`SampleSeries` built by the default + :meth:`decode`: the variant's rows, with values multiplied by the + conversion factor for aliased selectors, are then exactly the result, so + it can be emitted without the pandas evaluation. + """ + return ( + type(self).decode is PushdownRule.decode and self.result_type(pushdown) is SampleSeries + ) diff --git a/src/impulse_query_engine/analyze/query/optimizer/query_optimizer.py b/src/impulse_query_engine/analyze/query/optimizer/query_optimizer.py new file mode 100644 index 00000000..009d837d --- /dev/null +++ b/src/impulse_query_engine/analyze/query/optimizer/query_optimizer.py @@ -0,0 +1,120 @@ +"""Rule-based optimizer choosing the subtrees Spark computes ahead of the pandas UDF.""" + +from __future__ import annotations + +from collections import Counter +from collections.abc import Iterable, Sequence +from functools import partial +from typing import Any + +from impulse_query_engine.analyze.metadata.time_series_expression import TimeSeriesExpression + +from .plan import PushdownPlan +from .pushdown import Pushdown, PushdownContext, PushdownRule, check_name +from .resample_rule import ResamplePushdownRule +from .traversal import Classifier, discover + + +def default_rules() -> tuple[PushdownRule, ...]: + """All built-in pushdown rules.""" + return (ResamplePushdownRule(),) + + +class QueryOptimizer: + """Plans which subtrees of a solve's selections are computed natively in Spark. + + Nodes are classified bottom-up (:class:`~.traversal.Classifier`), and the + topmost classified nodes whose pushdown the channel data supports are the + candidates; a node that is not supported leaves its operands as candidates. + + All selections of a solve share the raw data of their physical channels, so + a subtree is pushed down only when **every** occurrence of its input selector, + across all selections, sits inside a pushed-down subtree. Then the raw rows + of that selector are no longer needed and are replaced by the (smaller) + derived rows. Selectors used anywhere else keep their raw data, and their + subtrees are evaluated in pandas as before. + + Parameters + ---------- + rules : Sequence[PushdownRule], optional + Rules to apply, in priority order. Defaults to :func:`default_rules`. + """ + + def __init__(self, rules: Sequence[PushdownRule] | None = None): + self.rules: tuple[PushdownRule, ...] = ( + tuple(rules) if rules is not None else default_rules() + ) + names = [check_name("rule", rule.name) for rule in self.rules] + duplicates = sorted({name for name in names if names.count(name) > 1}) + if duplicates: + raise ValueError(f"pushdown rule names must be unique: {duplicates}") + + def optimize(self, selections: Iterable[Any], context: PushdownContext) -> PushdownPlan: + """Build the pushdown plan for the *selections* of one solve call. + + Parameters + ---------- + selections : Iterable + The query's selections. + context : PushdownContext + Plan-time facts about the channel data. + + Returns + ------- + PushdownPlan + The plan; empty when nothing can be pushed down. + """ + selections = list(selections) + if not all(isinstance(s, TimeSeriesExpression) for s in selections): + return PushdownPlan(rules=self.rules) + + classify = Classifier(self.rules) + accept = partial(_accepted, context=context) + total: Counter[int] = Counter() + candidates: list[tuple[TimeSeriesExpression, Pushdown]] = [] + for selection in selections: + expected = Counter(s.selector_id for s in selection.get_selectors()) + total.update(expected) + found, leaves = discover(selection, classify, accept) + walked = Counter(s.selector_id for s in leaves) + walked.update(s.selector_id for _, p in found for s in p.consumed) + # A walk that disagrees with get_selectors() missed or double-counted part + # of the tree; its candidates can't be trusted to cover all occurrences. + if walked == expected: + candidates.extend(found) + + # Candidates read a single selector, so the operands of a dropped candidate + # read one whose raw rows are shipped anyway and need not be reconsidered. + while True: + consumed = Counter(s.selector_id for _, p in candidates for s in p.consumed) + fully_pushed = {sid for sid, count in consumed.items() if count == total[sid]} + kept = [ + (node, pushdown) + for node, pushdown in candidates + if all(s.selector_id in fully_pushed for s in pushdown.consumed) + ] + if len(kept) == len(candidates): + break + candidates = kept + + return PushdownPlan.build(self.rules, [p for _, p in candidates], total) + + +def _accepted(node: TimeSeriesExpression, pushdown: Pushdown, context: PushdownContext) -> bool: + """Whether the pipeline can compute *pushdown* for *node* on the channel data.""" + if pushdown.source is not None: + # Computing on another pushdown's result needs a pipeline stage per rule. + return False + if any(s.selector_id != pushdown.selector.selector_id for s in pushdown.consumed): + return False + return pushdown.rule.supports(pushdown, context) and _has_result_type(node, pushdown) + + +def _has_result_type(node: TimeSeriesExpression, pushdown: Pushdown) -> bool: + """Whether *node* evaluates to the type the rule decodes into.""" + try: + actual = node.evaluation_type() + except Exception: + # Only disqualifies the candidate; the query's own type inference reports errors. + return False + return actual is pushdown.rule.result_type(pushdown) diff --git a/src/impulse_query_engine/analyze/query/optimizer/resample_rule.py b/src/impulse_query_engine/analyze/query/optimizer/resample_rule.py new file mode 100644 index 00000000..d71f1020 --- /dev/null +++ b/src/impulse_query_engine/analyze/query/optimizer/resample_rule.py @@ -0,0 +1,201 @@ +"""Pushdown rule computing ``SampleSeries.resample`` natively in Spark.""" + +from __future__ import annotations + +import math +import numbers +from collections.abc import Mapping +from typing import Any + +import pyspark.sql.functions as F +import pyspark.sql.types as T +from pyspark.sql import Column + +from impulse_query_engine.analyze.metadata.time_series_expression import ( + SeriesType, + TimeSeriesExpression, + TimeSeriesOp, + TimeSeriesSelector, +) + +from .context import SeriesContext +from .pushdown import Operand, Pushdown, PushdownContext, PushdownRule + +_RATE = "sample_rate" +_DEFAULT_RATE = 1.0 +_TIME_TYPES = (T.ByteType, T.ShortType, T.IntegerType, T.LongType, T.DoubleType) + + +class ResamplePushdownRule(PushdownRule): + """Pushes ``channel.resample(sample_rate)`` on a physical channel down to Spark. + + Reproduces :meth:`SampleSeries.resample`: per continuous block of samples, a + regular grid starting at the block's first ``tstart`` (built like + ``numpy.arange``), each grid point holding the value of the last sample that + starts at or before it. Meant for downsampling: a rate finer than the native + sample spacing yields more rows than the raw channel. + + Each sample is assigned the bucket ``c`` of the first grid point at or after its + ``tstart``. The last sample of a bucket holds the value of grid points + ``c, c + 1, …`` up to the next sample's bucket, so only those samples are kept + and every row carries just the grid points it covers. Values are looked up + within a block; this matches the pandas evaluation unless intervals overlap. + + Grid points are computed as ``t_min + k * delta`` in IEEE double arithmetic. + numpy builds that fuse this into a multiply-add can differ by one ulp for + fractional grids (e.g. seconds-as-double with a rate of ``0.1``); integral + grids are exact. + """ + + name = "resample" + + def match(self, node: TimeSeriesExpression, operand: Operand) -> Pushdown | None: + if ( + type(node) is not TimeSeriesOp + or node.optype != "cls" + or not isinstance(node.operation, str) + or node.operation != "resample" + or not node.args + ): + return None + selector = operand(node.args[0]) + if not isinstance(selector, TimeSeriesSelector): + return None + if selector.series_type is not SeriesType.CONTINUOUS: + return None + rate = _sample_rate(node.args[1:], node.kwargs) + if rate is None: + return None + return Pushdown( + rule=self, selector=selector, params=((_RATE, rate),), consumed=(selector,) + ) + + def supports(self, pushdown: Pushdown, context: PushdownContext) -> bool: + time_types = (context.tstart_type, context.tend_type) + if not all(isinstance(t, _TIME_TYPES) for t in time_types): + return False + # Integral time columns receive the grid cast back from double; an integral + # rate keeps every grid point integral, so that cast is exact. + rate = dict(pushdown.params)[_RATE] + return rate.is_integer() or not any(isinstance(t, T.IntegralType) for t in time_types) + + def row_columns(self, ctx: SeriesContext) -> Mapping[str, Column]: + grid = _Grid(ctx) + resampled = ctx.col("resampled") + return { + # A single sample is returned as it is. + "resampled": ctx.n >= 2, + **grid.first_index("c", ctx.tstart, F.lit(False), resampled), + **grid.first_index( + "next_c", ctx.next.tstart, F.lit(False), resampled & _next_in_block(ctx) + ), + } + + def keep(self, ctx: SeriesContext) -> Column: + # The last sample of each bucket. + return ~ctx.col("resampled") | ~_next_in_block(ctx) | (ctx.col("next_c") != ctx.col("c")) + + def kept_columns(self, ctx: SeriesContext) -> Mapping[str, Column]: + grid = _Grid(ctx) + t_max = ctx.block_end + # The last block keeps a grid point at t_max when the series ends with a + # zero-duration sample. + inclusive = ctx.block.is_last & (ctx.last.tstart == ctx.last.tend) + length = F.greatest(F.ceil(((t_max + grid.rate) - grid.t_min) / grid.rate), F.lit(0)) + n_grid = ctx.col("n_grid") + return { + **grid.first_index("grid_end", t_max, inclusive, ctx.col("resampled")), + "n_grid": F.least(ctx.col("grid_end"), length), + # Grid points up to the next sample's bucket, or up to the block's end. + "c_stop": F.when(_next_in_block(ctx), F.least(ctx.col("next_c"), n_grid)).otherwise( + n_grid + ), + } + + def emit(self, ctx: SeriesContext) -> Column: + grid = _Grid(ctx) + c, c_stop, n_grid = ctx.col("c"), ctx.col("c_stop"), ctx.col("n_grid") + points = F.transform( + F.sequence(c, c_stop - 1), + lambda k: ctx.element( + grid.point(k), + F.when(k == n_grid - 1, ctx.block_end).otherwise(grid.point(k + 1)), + ctx.value, + ), + ) + return F.when(~ctx.col("resampled"), F.array(ctx.element(value=ctx.value))).when( + c_stop > c, points + ) + + +def _next_in_block(ctx: SeriesContext) -> Column: + """The next sample exists and continues the block.""" + return F.coalesce(~ctx.block.next_starts, F.lit(False)) + + +class _Grid: + """The numpy.arange grid of the sample's block. + + ``numpy.arange(t_min, t_max + rate, rate)`` has length + ``ceil(((t_max + rate) - t_min) / rate)`` and is filled as ``t_min``, + ``t_min + rate``, then ``t_min + k * ((t_min + rate) - t_min)``. + """ + + def __init__(self, ctx: SeriesContext): + self.ctx = ctx + self.rate = ctx.param(_RATE) + self.t_min = ctx.block.start + self.delta = (self.t_min + self.rate) - self.t_min + + def point(self, k: Column) -> Column: + return ( + F.when(k == 0, self.t_min) + .when(k == 1, self.t_min + self.rate) + .otherwise(self.t_min + k.cast("double") * self.delta) + ) + + def first_index( + self, name: str, x: Column, strict: Column, active: Column + ) -> dict[str, Column]: + """Columns ending in *name*: the smallest ``k >= 0`` with ``point(k) >= x`` (``> x`` if *strict*). + + Estimated from ``(x - t_min) / delta`` and corrected by one step against the + exact grid points; a result that still is not exact raises instead of + misplacing a sample. Only evaluated where *active*. + """ + + def reached(k: Column) -> Column: + g = self.point(k) + return F.when(strict, g > x).otherwise(g >= x) + + estimate, step = self.ctx.col(f"{name}_estimate"), self.ctx.col(f"{name}_step") + return { + f"{name}_estimate": F.when( + active, + F.when( + self.delta > 0, + F.greatest(F.ceil((x - self.t_min) / self.delta), F.lit(0)), + ).otherwise( + F.raise_error( + F.lit("resample: sample_rate is below the resolution of the timestamps") + ) + ), + ), + f"{name}_step": F.when((estimate > 0) & reached(estimate - 1), estimate - 1) + .when(~reached(estimate), estimate + 1) + .otherwise(estimate), + name: F.when(~active, F.lit(None)) + .when(reached(step) & ((step == 0) | ~reached(step - 1)), step) + .otherwise(F.raise_error(F.lit("resample: grid index could not be determined"))), + } + + +def _sample_rate(args: tuple, kwargs: dict[str, Any]) -> float | None: + """The resample rate of a ``resample(...)`` call, or ``None`` if unsupported.""" + if len(args) > 1 or set(kwargs) - {_RATE} or (args and kwargs): + return None + rate = args[0] if args else kwargs.get(_RATE, _DEFAULT_RATE) + if isinstance(rate, bool) or not isinstance(rate, numbers.Real): + return None + rate = float(rate) + return rate if math.isfinite(rate) and rate > 0 else None diff --git a/src/impulse_query_engine/analyze/query/optimizer/traversal.py b/src/impulse_query_engine/analyze/query/optimizer/traversal.py new file mode 100644 index 00000000..9db5d358 --- /dev/null +++ b/src/impulse_query_engine/analyze/query/optimizer/traversal.py @@ -0,0 +1,141 @@ +"""Bottom-up classification of TSAL expression trees into pushdowns.""" + +from __future__ import annotations + +from collections.abc import Callable, Iterable, Iterator, Sequence +from typing import Any + +from impulse_query_engine.analyze.metadata.time_series_expression import ( + TimeSeriesAliasSelector, + TimeSeriesExpression, + TimeSeriesOp, + TimeSeriesSelector, +) + +from .pushdown import Pushdown, PushdownRule + +#: Plan-time check whether a classified node's pushdown is planned. +Accept = Callable[[TimeSeriesExpression, Pushdown], bool] + + +def child_expressions(node: TimeSeriesExpression) -> Iterator[TimeSeriesExpression]: + """Yield the direct sub-expressions of *node*. + + Operations yield their operands; other node types (aggregations, events, + calculated channels, …) yield every expression held in their attributes, + including inside lists, tuples and dicts. The walk is best-effort: the + optimizer checks the leaves it finds against ``get_selectors()``. + """ + if isinstance(node, TimeSeriesSelector): + return + if isinstance(node, TimeSeriesOp): + # Covers TimeSeriesUDF, whose ``optype`` attribute holds its first operand. + values: Iterable = (*node.args, *node.kwargs.values()) + elif isinstance(node, TimeSeriesAliasSelector): + values = node._aliases + else: + values = (value for key, value in vars(node).items() if key != "_alias") + yield from _expressions_in(values) + + +def _expressions_in(values: Iterable) -> Iterator[TimeSeriesExpression]: + for value in values: + if isinstance(value, TimeSeriesExpression): + yield value + elif isinstance(value, list | tuple): + yield from _expressions_in(value) + elif isinstance(value, dict): + yield from _expressions_in(value.values()) + + +class Classifier: + """Classifies expression nodes bottom-up into the pushdowns computing them. + + A node is classified after its operands: the first rule whose + :meth:`~.pushdown.PushdownRule.match` returns a pushdown for the node, given + its operands' classification, computes it. Only plain :class:`TimeSeriesOp` + nodes can be pushed down: their ``build`` serves precomputed results from the + cache, other node types (UDFs, aggregations, events, …) would evaluate on + missing raw rows instead. + + Classification is syntactic, so the driver and the UDF classify alike. + Results are memoized per node object: use one classifier per optimization or + per container, and never store it on a plan or a node. + + Parameters + ---------- + rules : Sequence[PushdownRule] + Rules in priority order. + """ + + def __init__(self, rules: Sequence[PushdownRule]): + self.rules = tuple(rules) + # Nodes are not hashable; holding each node keeps its id from being reused. + self._memo: dict[int, tuple[TimeSeriesOp, Pushdown | None]] = {} + + def __call__(self, node: TimeSeriesExpression) -> Pushdown | None: + """The pushdown computing *node*, or ``None``.""" + if type(node) is not TimeSeriesOp: + return None + stack: list[tuple[TimeSeriesOp, bool]] = [(node, False)] + while stack: + current, operands_classified = stack.pop() + if id(current) in self._memo: + continue + if operands_classified: + self._memo[id(current)] = (current, self._match(current)) + else: + stack.append((current, True)) + stack.extend( + (child, False) + for child in child_expressions(current) + if type(child) is TimeSeriesOp + ) + return self._memo[id(node)][1] + + def operand(self, value: Any) -> TimeSeriesSelector | Pushdown | None: + """Classification of an operand: its selector for a channel, else its pushdown.""" + if isinstance(value, TimeSeriesSelector): + return value + if isinstance(value, TimeSeriesExpression): + return self(value) + return None + + def _match(self, node: TimeSeriesOp) -> Pushdown | None: + for rule in self.rules: + pushdown = rule.match(node, self.operand) + if pushdown is not None: + return pushdown + return None + + +def discover( + root: TimeSeriesExpression, classify: Classifier, accept: Accept | None = None +) -> tuple[list[tuple[TimeSeriesExpression, Pushdown]], list[TimeSeriesSelector]]: + """Find the topmost subtrees of *root* that are pushed down. + + A classified node is a candidate when *accept* (by default every pushdown) + accepts its pushdown; otherwise the walk continues into its operands, whose + pushdowns can still be candidates. The walk does not descend into a + candidate and keeps no visited set, so a node referenced twice is counted + twice, like ``get_selectors()`` does. + + Returns + ------- + tuple + ``(candidates, leaves)``: the ``(node, pushdown)`` candidates and the + selectors reached outside of them. + """ + candidates: list[tuple[TimeSeriesExpression, Pushdown]] = [] + leaves: list[TimeSeriesSelector] = [] + stack: list[TimeSeriesExpression] = [root] + while stack: + node = stack.pop() + pushdown = classify(node) + if pushdown is not None and (accept is None or accept(node, pushdown)): + candidates.append((node, pushdown)) + elif isinstance(node, TimeSeriesSelector): + leaves.append(node) + else: + stack.extend(child_expressions(node)) + return candidates, leaves diff --git a/src/impulse_query_engine/analyze/query/solvers/default_solver.py b/src/impulse_query_engine/analyze/query/solvers/default_solver.py index f9e6d7b0..bae40a38 100644 --- a/src/impulse_query_engine/analyze/query/solvers/default_solver.py +++ b/src/impulse_query_engine/analyze/query/solvers/default_solver.py @@ -1,5 +1,6 @@ from __future__ import annotations +import logging from collections.abc import Iterable from functools import partial from typing import TYPE_CHECKING, Any @@ -7,7 +8,7 @@ import pandas as pd import pyspark.sql.functions as F import pyspark.sql.types as T -from pyspark.sql import DataFrame, Window +from pyspark.sql import Column, DataFrame, Window from impulse_query_engine.analyze.metadata.metric_expression import MetricExpression from impulse_query_engine.analyze.metadata.tag_expression import TagExpression @@ -16,6 +17,16 @@ SeriesValueType, TimeSeriesExpression, ) +from impulse_query_engine.analyze.query.optimizer import ( + RAW_VARIANT, + VARIANT_COL, + VARIANTS_COL, + Classifier, + PushdownContext, + PushdownPlan, + QueryOptimizer, + apply_pushdowns, +) from impulse_query_engine.model.series.points_in_time_series import PointsInTimeSeries from impulse_query_engine.model.series.sample_series import SampleSeries @@ -29,6 +40,8 @@ if TYPE_CHECKING: from impulse_query_engine.measurement_db import MeasurementDB +logger = logging.getLogger(__name__) + class TimeSeriesCache(SeriesCache): def __init__( @@ -37,6 +50,7 @@ def __init__( col_map: dict[str, str], container_tag_cols=None, container_metric_cols=None, + plan: PushdownPlan | None = None, ): """ Initialize the TimeSeriesCache. @@ -52,7 +66,9 @@ def __init__( the caller's frame is reordered by ``(cid, ch, ts)`` and its index reset. The sort makes each channel's rows contiguous, which the constructor exploits to build a ``(cid, ch)`` → - ``(start, stop)`` range index for :meth:`load_blob`. + ``(start, stop)`` range index for :meth:`load_blob`. When *pdf* + carries the pushdown variant column, rows are additionally keyed + by variant (raw data is variant ``0``). col_map : dict[str, str] Mapping with keys ``"cid"``, ``"ch"``, ``"ts"``, ``"te"``, ``"val"``, ``"conv"`` to the actual column names in *pdf*. The @@ -65,6 +81,9 @@ def __init__( container_metric_cols : list of str, optional Columns on *pdf* holding container-level metric values, exposed via :attr:`container_metrics`. These are constant per container. + plan : PushdownPlan, optional + Pushdowns whose derived rows *pdf* carries; :meth:`materialized` + serves the matching subtrees from them. """ self._cid_col = col_map["cid"] self._ch_col = col_map["ch"] @@ -78,28 +97,34 @@ def __init__( self._value_string_col = col_map.get("value_string") self._container_tag_cols = list(container_tag_cols or []) self._container_metric_cols = list(container_metric_cols or []) + self._plan = plan + # Classifies each node once for all lookups of this container. + self._classify = Classifier(plan.rules) if plan is not None else None + has_variants = VARIANT_COL in pdf.columns # *pdf* holds channel data for a whole container, so avoid creating unnecessary copies of the data. meta_cols = [ - c for c in pdf.columns if c not in (self._ts_col, self._te_col, self._val_col) + c + for c in pdf.columns + if c not in (self._ts_col, self._te_col, self._val_col, VARIANT_COL) ] meta_source = pdf if "selector_ids" in pdf.columns: meta_source = pdf.loc[pdf["selector_ids"].notna()] first_rows = ~meta_source.duplicated(subset=[self._cid_col, self._ch_col]) self.mdf = meta_source.loc[first_rows, meta_cols].reset_index(drop=True) - pdf.sort_values( - [self._cid_col, self._ch_col, self._ts_col], inplace=True, ignore_index=True - ) + group_cols = [self._cid_col, self._ch_col] + ([VARIANT_COL] if has_variants else []) + pdf.sort_values([*group_cols, self._ts_col], inplace=True, ignore_index=True) self.pdf = pdf - # Each (cid, ch) group is a contiguous block after the sort, so its + # Each (cid, ch, variant) group is a contiguous block after the sort, so its # row positions collapse to a (start, stop) range self._ranges = { - key: (int(positions[0]), int(positions[-1]) + 1) - for key, positions in pdf.groupby( - [self._cid_col, self._ch_col], sort=False - ).indices.items() + (key if has_variants else (*key, RAW_VARIANT)): ( + int(positions[0]), + int(positions[-1]) + 1, + ) + for key, positions in pdf.groupby(group_cols, sort=False).indices.items() } def resolve(self, selection): @@ -169,8 +194,7 @@ def load_blob( ------- SampleSeries or PointsInTimeSeries """ - lo, hi = self._ranges.get((mid, cid), (0, 0)) - s = self.pdf.iloc[lo:hi] + s = self._slice(mid, cid, RAW_VARIANT) if series_type == SeriesType.POINTS_IN_TIME: # The selector's declared value type picks the column; the constructor @@ -185,12 +209,72 @@ def load_blob( ) return PointsInTimeSeries(s[self._ts_col], s[value_col], value_type=value_type) - values = s[self._val_col] - if self._has_conversion and len(s) > 0 and uses_alias: - factor = s[self._conv_col].iloc[0] + return self.to_sample_series(s, uses_alias=uses_alias) + + def materialized(self, expression): + """Serve a pushed-down subtree from its derived rows. + + Returns ``None`` (evaluate normally) when *expression* is not pushed down + or its channel carries no derived rows in this container. + + Parameters + ---------- + expression : TimeSeriesExpression + The node about to be built. + + Returns + ------- + Any or None + The rule-decoded result, or ``None``. + """ + if self._plan is None: + return None + hit = self._plan.lookup(expression, self._classify) + if hit is None: + return None + pushdown, variant = hit + candidates = self.resolve(pushdown.selector) + if len(candidates) == 0: + return None + rows = self._slice( + candidates[self._cid_col].iloc[0], candidates[self._ch_col].iloc[0], variant + ) + if len(rows) == 0: + return None + return pushdown.rule.decode(rows, pushdown, self) + + def to_sample_series(self, rows: pd.DataFrame, uses_alias: bool = False) -> SampleSeries: + """Build a :class:`SampleSeries` from channel-data *rows* of one channel. + + Applies the per-channel conversion factor when *uses_alias* is ``True`` + (see :meth:`load_blob`). + """ + values = rows[self._val_col] + if self._has_conversion and len(rows) > 0 and uses_alias: + factor = rows[self._conv_col].iloc[0] if pd.notna(factor): values = values * factor - return SampleSeries(s[self._ts_col], s[self._te_col], values) + return SampleSeries(rows[self._ts_col], rows[self._te_col], values) + + @property + def tstart_col(self) -> str: + """Name of the ``tstart`` column of the cached rows.""" + return self._ts_col + + @property + def tend_col(self) -> str: + """Name of the ``tend`` column of the cached rows.""" + return self._te_col + + @property + def value_col(self) -> str: + """Name of the numeric value column of the cached rows.""" + return self._val_col + + def _slice(self, mid, cid, variant: int) -> pd.DataFrame: + """Rows of one container/channel/variant (empty when absent).""" + lo, hi = self._ranges.get((mid, cid, variant), (0, 0)) + return self.pdf.iloc[lo:hi] @property def container_tags(self) -> dict: @@ -256,6 +340,12 @@ class DefaultSolver(QuerySolver): ``RawEncoder.RLE`` (default) run-length encodes equal-valued runs; ``RawEncoder.INTERVAL`` only derives ``tend`` and drops exact duplicates. Only consulted when ``is_raw_data`` is ``True``. + enable_pushdown : bool, optional + Whether to compute supported expression subtrees (e.g. + ``channel.resample(rate)``) natively in Spark before the pandas UDF, so + fewer rows are shipped to it (see :class:`QueryOptimizer`). A channel + is only pushed down when every use of it in the solve is. Default + ``False``. """ def __init__( @@ -265,12 +355,14 @@ def __init__( is_raw_data: bool = False, drop_implausible_data: bool = False, raw_encoder: RawEncoder = RawEncoder.RLE, + enable_pushdown: bool = False, ): super().__init__(config=config) self.spark = spark self.is_raw_data = is_raw_data self.drop_implausible_data: bool = drop_implausible_data self.raw_encoder: RawEncoder = raw_encoder + self.optimizer: QueryOptimizer | None = QueryOptimizer() if enable_pushdown else None # In RAW mode an is_plausible channels filter drops samples before raw # encoding, bridging intervals instead of splitting them. Reject it. self.config.reject_implausible_channels_filter_in_raw(is_raw=self.is_raw_data) @@ -714,8 +806,9 @@ def filter_aliased_channel_metrics( resolved = resolved.withColumn(source_unit_col, F.col("_map_source_unit")) resolved = resolved.withColumn(target_unit_col, F.col("_map_target_unit")) + # Ties in priority (e.g. no priorities configured) go to the lowest channel_id. dedup_window = Window.partitionBy(container_id_col, channel_alias_col).orderBy( - F.col(alias_priority_col).asc_nulls_last() + F.col(alias_priority_col).asc_nulls_last(), F.col(channel_id_col).asc() ) resolved = resolved.withColumn("_rank", F.row_number().over(dedup_window)) resolved = resolved.where(F.col("_rank") == 1).drop("_rank") @@ -806,12 +899,13 @@ def resolve_channel_selections( agg_exprs = [F.flatten(F.collect_list("selector_ids")).alias("selector_ids")] if has_unit_cols: - # collect_set serves a dual purpose: (a) it deduplicates so we - # can detect a conflict by size > 1, and (b) the single - # remaining element materializes the scalar unit value the - # downstream code expects. - agg_exprs.append(F.collect_set(source_unit_col).alias("_source_units")) - agg_exprs.append(F.collect_set(target_unit_col).alias("_target_units")) + # Distinct units: size > 1 is a conflict, else the single element is + # the unit. Not collect_set: Databricks may plan copies of several + # collect_set differently, which blocks exchange reuse. + source_units = F.array_distinct(F.collect_list(source_unit_col)) + target_units = F.array_distinct(F.collect_list(target_unit_col)) + agg_exprs.append(source_units.alias("_source_units")) + agg_exprs.append(target_units.alias("_target_units")) grouped = merged.groupBy( self.config.container_id_col, @@ -1007,6 +1101,7 @@ def _solve_udf( col_map: dict[str, str], container_tag_cols=None, container_metric_cols=None, + plan: PushdownPlan | None = None, ) -> pd.DataFrame: """ UDF to solve for a single container by applying selections. @@ -1024,6 +1119,8 @@ def _solve_udf( container_metric_cols : list of str, optional Columns on *pdf* holding container-level metric values, exposed to expressions via ``cache.container_metrics``. + plan : PushdownPlan, optional + Pushdowns whose derived rows *pdf* carries. Returns ------- @@ -1037,6 +1134,7 @@ def _solve_udf( col_map=col_map, container_tag_cols=container_tag_cols, container_metric_cols=container_metric_cols, + plan=plan, ) for s in selections: res = s.build(cache) @@ -1075,7 +1173,7 @@ def solve(self, query, channels_df, selections, dtypes) -> DataFrame: DataFrame containing results for each container. """ col_map = self.config.col_map - q, joined_df, container_count = self._prepare_channels_join(query, channels_df) + q, joined_df, container_count, plan, _ = self._prepare_channels_join(query, channels_df) container_tag_cols, container_metric_cols = self._container_meta_cols(query) schema = self._build_solve_output_schema(q, selections, dtypes) @@ -1086,6 +1184,7 @@ def solve(self, query, channels_df, selections, dtypes) -> DataFrame: col_map=col_map, container_tag_cols=container_tag_cols, container_metric_cols=container_metric_cols, + plan=plan, ), schema, F.PandasUDFType.GROUPED_MAP, @@ -1098,7 +1197,9 @@ def solve(self, query, channels_df, selections, dtypes) -> DataFrame: container_meta_cols=[*container_tag_cols, *container_metric_cols], ) - def _prepare_channels_join(self, query, channels_df) -> tuple[DataFrame, DataFrame, int]: + def _prepare_channels_join( + self, query, channels_df, native_selections=None + ) -> tuple[DataFrame, DataFrame, int, PushdownPlan | None, dict | None]: """Shared prelude for :meth:`solve` and :meth:`solve_calculated_channels`. Applies optional per-channel unit conversion, reads and column-maps the @@ -1110,20 +1211,31 @@ def _prepare_channels_join(self, query, channels_df) -> tuple[DataFrame, DataFra *channels_df* (attached by :meth:`attach_container_metadata`) ride through the broadcast join into ``joined_df``. + With pushdown enabled, subtrees chosen by the optimizer are computed on + the channel data before the join (see :meth:`_apply_pushdowns`), and + ``joined_df`` carries their derived rows instead of the raw rows they + replace. + Parameters ---------- query : QueryBuilder Query object containing database and filter information. channels_df : pyspark.sql.DataFrame Channel-match DataFrame from the filter pipeline. + native_selections : list, optional + Calculated-channel selections that may be solved without the UDF + (see :meth:`PushdownPlan.native_targets`). Returns ------- - tuple[DataFrame, DataFrame, int] - ``(channels_q, joined_df, container_count)`` — the column-mapped - channel-data DataFrame (authoritative for output ``container_id`` / - ``channel_id`` types), the join fed to the grouped-map UDF, and the - number of distinct containers. + tuple[DataFrame, DataFrame, int, PushdownPlan or None, dict or None] + ``(channels_q, joined_df, container_count, plan, native_targets)`` — + the column-mapped channel-data DataFrame (authoritative for output + ``container_id`` / ``channel_id`` types), the join fed to the + grouped-map UDF, the number of distinct containers, the pushdown + plan the UDF must receive (``None`` when nothing is pushed down), + and the native targets of *native_selections* (``None`` unless + they are solved natively; ``joined_df`` then holds no raw rows). """ source_unit_col = self.config.source_unit_col target_unit_col = self.config.target_unit_col @@ -1168,12 +1280,57 @@ def _prepare_channels_join(self, query, channels_df) -> tuple[DataFrame, DataFra if DefaultSolver._query_contains_poi_selections(query.selections): q = self._union_poi_channel_data(query, q) - joined_df = q.join( + container_count = channels_df.select(self.config.container_id_col).distinct().count() + plan = self._plan_pushdowns(query, q) if container_count > 0 else None + channel_data = q + targets = None + if plan is not None: + if native_selections is not None: + targets = plan.native_targets(native_selections) + # Raw rows are only read by the UDF, for selectors that are not pushed down. + include_raw = targets is None and bool(plan.raw_selector_ids) + channel_data = self._apply_pushdowns(q, channels_df, plan, include_raw) + + joined_df = channel_data.join( F.broadcast(channels_df), on=[self.config.container_id_col, self.config.channel_id_col], ) - container_count = channels_df.select(self.config.container_id_col).distinct().count() - return q, joined_df, container_count + return q, joined_df, container_count, plan, targets + + def _plan_pushdowns(self, query, channels_q: DataFrame) -> PushdownPlan | None: + """Plan the pushdowns for *query*, or ``None`` when disabled or nothing applies.""" + if self.optimizer is None: + return None + plan = self.optimizer.optimize( + query.selections, PushdownContext.from_schema(channels_q.schema, self.config) + ) + if plan.is_empty: + return None + logger.info("Spark pushdown plan: %s", plan.describe()) + return plan + + def _apply_pushdowns( + self, + channels_q: DataFrame, + channels_df: DataFrame, + plan: PushdownPlan, + include_raw: bool, + ) -> DataFrame: + """Replace raw channel data with the derived rows of *plan* where possible. + + Each matched channel ships the variants of + :meth:`PushdownPlan.channel_variants`: its raw data (variant ``0``) + while any of its selectors still needs it, otherwise the derived + variants of its selectors, computed by their rules (see + :func:`~impulse_query_engine.analyze.query.optimizer.apply_pushdowns`). + The result carries a :data:`VARIANT_COL` column and keeps the + channel-data columns and types; raw rows are kept only if *include_raw*. + """ + key = [self.config.container_id_col, self.config.channel_id_col] + variants = channels_df.select( + *key, plan.channel_variants(F.col("selector_ids")).alias(VARIANTS_COL) + ) + return apply_pushdowns(channels_q, variants, plan, self.config, include_raw=include_raw) @staticmethod def _query_contains_poi_selections(selections: Iterable[Any]) -> bool: @@ -1382,6 +1539,7 @@ def _solve_calculated_channels_udf( col_map: dict[str, str], container_tag_cols=None, container_metric_cols=None, + plan: PushdownPlan | None = None, ) -> pd.DataFrame: """ UDF to solve calculated channels for a single container. @@ -1409,6 +1567,8 @@ def _solve_calculated_channels_udf( container_metric_cols : list of str, optional Columns on *pdf* holding container-level metric values, exposed to expressions via ``cache.container_metrics``. + plan : PushdownPlan, optional + Pushdowns whose derived rows *pdf* carries. Returns ------- @@ -1421,6 +1581,7 @@ def _solve_calculated_channels_udf( col_map=col_map, container_tag_cols=container_tag_cols, container_metric_cols=container_metric_cols, + plan=plan, ) cid_col = col_map["cid"] ch_col = col_map["ch"] @@ -1470,9 +1631,24 @@ def solve_calculated_channels(self, query, channels_df, selections) -> DataFrame ------- pyspark.sql.DataFrame Narrow DataFrame of calculated-channel samples. + + Notes + ----- + When every selection is a pushdown as a whole (see + :meth:`PushdownPlan.native_targets`), the rows are produced in Spark from + the derived rows and no pandas UDF runs. """ + self._validate_calculated_channel_id_type(query) col_map = self.config.col_map - q, joined_df, container_count = self._prepare_channels_join(query, channels_df) + q, joined_df, container_count, plan, targets = self._prepare_channels_join( + query, channels_df, native_selections=selections + ) + + if targets is not None: + logger.info("Calculated channels solved natively in Spark, without the pandas UDF") + native = self._native_calculated_channel_rows(joined_df, q, targets) + return self._attach_identity(native, selections) + container_tag_cols, container_metric_cols = self._container_meta_cols(query) schema = self._build_calculated_channels_udf_schema(q) @@ -1483,6 +1659,7 @@ def solve_calculated_channels(self, query, channels_df, selections) -> DataFrame col_map=col_map, container_tag_cols=container_tag_cols, container_metric_cols=container_metric_cols, + plan=plan, ), schema, F.PandasUDFType.GROUPED_MAP, @@ -1495,8 +1672,14 @@ def solve_calculated_channels(self, query, channels_df, selections) -> DataFrame container_meta_cols=[*container_tag_cols, *container_metric_cols], ) - # Identity is constant per channel_id, so attach it here via a - # channel_id-keyed CASE instead of the UDF shipping a copy per row. + return self._attach_identity(res, selections) + + def _attach_identity(self, rows: DataFrame, selections) -> DataFrame: + """Add the ``identity`` map of each calculated channel to its rows. + + Identity is constant per channel_id, so it is attached via a + channel_id-keyed CASE instead of being shipped with every row. + """ channel_id_col = self.config.channel_id_col identity_expr = None for cc in selections: @@ -1507,4 +1690,123 @@ def solve_calculated_channels(self, query, channels_df, selections) -> DataFrame if identity_expr is None else identity_expr.when(branch, id_map) ) - return res.withColumn("identity", identity_expr) + return rows.withColumn("identity", identity_expr) + + def _validate_calculated_channel_id_type(self, query) -> None: + """Raise unless the ``channels`` table's ``channel_id`` can hold calculated-channel ids. + + :attr:`CalculatedChannel.channel_id` is a 31-bit integer, which only fits + ``IntegerType`` and ``LongType`` columns. Only the table schema is read, so + the check runs no Spark job. + """ + channels = self._apply_column_mapping( + query.db.channels(self.spark), self.config.channels.column_name_mapping + ) + dtype = channels.schema[self.config.channel_id_col].dataType + if not isinstance(dtype, T.IntegerType | T.LongType): + raise ValueError( + "Calculated channels need an integer or long channel_id column, got " + f"{dtype.simpleString()}." + ) + + def _native_calculated_channel_rows( + self, + joined_df: DataFrame, + channels_q: DataFrame, + targets: dict[int, dict[int, list[tuple[int, bool]]]], + ) -> DataFrame: + """Calculated-channel rows taken directly from the pushed-down rows. + + *targets* (:meth:`PushdownPlan.native_targets`) maps each variant and + selector to the calculated channels built from them. Every derived row + is emitted once per such channel, so the result equals the pandas + evaluation: values get the conversion factor for aliased selectors, and + the round-trip through float64 and Arrow is mirrored (integral times via + double, NaN as null). + + Returns + ------- + pyspark.sql.DataFrame + ``[container_id, channel_id, tstart, tend, value]`` as typed by + :meth:`_build_calculated_channels_udf_schema`. + """ + cfg = self.config + cid, ch = cfg.container_id_col, cfg.channel_id_col + ts, te, val, conv = cfg.tstart_col, cfg.tend_col, cfg.value_col, cfg.conversion_factor_col + target_type = T.ArrayType( + T.StructType( + [ + T.StructField("_cc_channel_id", T.LongType()), + T.StructField("_cc_uses_alias", T.BooleanType()), + ] + ) + ) + by_variant = F.create_map( + *[ + item + for variant, by_selector in sorted(targets.items()) + for item in ( + F.lit(variant), + F.create_map( + *[ + entry + for selector_id, channels in sorted(by_selector.items()) + for entry in ( + F.lit(selector_id).cast("long"), + F.array( + *[ + F.struct( + F.lit(channel_id).cast("long").alias("_cc_channel_id"), + F.lit(uses_alias).alias("_cc_uses_alias"), + ) + for channel_id, uses_alias in channels + ] + ), + ) + ] + ), + ) + ] + ) + by_selector = F.try_element_at(by_variant, F.col(VARIANT_COL)) + no_targets = F.array().cast(target_type) + channel_targets = F.flatten( + F.transform( + PushdownPlan.channel_selector_ids("selector_ids"), + lambda sid: F.coalesce(F.try_element_at(by_selector, sid), no_targets), + ) + ) + has_conversion = conv in joined_df.columns + # joined_df holds no raw rows here; any other row without targets is + # dropped by inline. + rows = joined_df.select( + cid, + ts, + te, + val, + *([conv] if has_conversion else []), + F.inline(channel_targets), + ) + + value = F.col(val).cast("double") + if has_conversion: + factor = F.col(conv) + # Like TimeSeriesCache.to_sample_series: a null or NaN factor leaves values as they are. + applies = F.col("_cc_uses_alias") & factor.isNotNull() & ~F.isnan(factor) + value = F.when(applies, value * factor).otherwise(value) + + def as_udf_output(col: Column, dtype: T.DataType) -> Column: + # The UDF emits float64 arrays; Arrow casts them back and masks NaN as null. + col = col.cast("double") + if isinstance(dtype, T.IntegralType): + return col.cast(dtype) + return F.when(F.isnan(col), F.lit(None)).otherwise(col).cast(dtype) + + schema = channels_q.schema + return rows.select( + cid, + F.col("_cc_channel_id").cast(schema[ch].dataType).alias(ch), + as_udf_output(F.col(ts), schema[ts].dataType).alias(ts), + as_udf_output(F.col(te), schema[te].dataType).alias(te), + as_udf_output(value, T.DoubleType()).alias(val), + ) diff --git a/src/impulse_query_engine/analyze/query/solvers/series_cache.py b/src/impulse_query_engine/analyze/query/solvers/series_cache.py index 33def4b8..fbf252a3 100644 --- a/src/impulse_query_engine/analyze/query/solvers/series_cache.py +++ b/src/impulse_query_engine/analyze/query/solvers/series_cache.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from typing import Any import pandas as pd @@ -70,6 +71,25 @@ def load_blob( """ pass + def materialized(self, expression) -> Any | None: + """Return the precomputed result of *expression*, if the cache holds one. + + Consulted by :meth:`TimeSeriesOp.build` before evaluating the node, so a + subtree computed ahead of time (e.g. pushed down to Spark) replaces its + in-memory evaluation. Default: no precomputed results. + + Parameters + ---------- + expression : TimeSeriesExpression + The node about to be built. + + Returns + ------- + Any or None + The precomputed result, or ``None`` to evaluate *expression* normally. + """ + return None + @property def container_tags(self) -> dict: """Container-level tag values available to UDFs. diff --git a/src/impulse_reporting/config/config_parser.py b/src/impulse_reporting/config/config_parser.py index 79b27472..a6301ca3 100644 --- a/src/impulse_reporting/config/config_parser.py +++ b/src/impulse_reporting/config/config_parser.py @@ -395,6 +395,12 @@ class QueryEngine(BaseModel): measurement_db_config : dict, optional Keyword arguments for that implementation's config class, merged with the ``source`` tables. + enable_pushdown : bool, default=False + Compute supported expression subtrees (currently + ``channel.resample(rate)`` on a physical channel) natively in Spark + before the pandas UDF, so fewer rows are shipped to it. A channel is + only pushed down when every use of it in a solve batch is. Meant for + downsampling. Notes ----- @@ -423,6 +429,7 @@ class QueryEngine(BaseModel): measurement_db_config: dict[str, Any] | None = None max_channels_per_batch: int = 500 max_containers_per_batch: int | None = None + enable_pushdown: bool = False @model_validator(mode="before") @classmethod diff --git a/src/impulse_reporting/core/report.py b/src/impulse_reporting/core/report.py index f8d9bdab..bd2d625d 100644 --- a/src/impulse_reporting/core/report.py +++ b/src/impulse_reporting/core/report.py @@ -362,6 +362,7 @@ def create_solver(spark: SparkSession, config: ImpulseConfig) -> QuerySolver: is_raw_data=config.query_engine.data_type is DataType.RAW, drop_implausible_data=config.query_engine.drop_implausible_data, raw_encoder=config.query_engine.raw_encoder, + enable_pushdown=config.query_engine.enable_pushdown, ) case _: raise ValueError( diff --git a/tests/e2e/README.md b/tests/e2e/README.md index de7eabfc..02c0b664 100644 --- a/tests/e2e/README.md +++ b/tests/e2e/README.md @@ -15,6 +15,7 @@ The job is defined in `resources/e2e_job.yml`. Each task is a notebook in this f | `pipeline` | `e2e_pipeline` | Container event, per-container statistics, an RPM x speed heatmap and a calculated channel; the calculated channel's samples must give the same statistics as the StatsAggregator | | `container_filter` | `e2e_container_filter` | Baseline report on (`experiment_1` or `experiment_2`) and longer than 30 minutes; must equal those containers' `rpm_histogram` rows, with the containers selected independently in SQL | | `events` | `e2e_events` | Points-in-time, sequence and 5-minute window events with point values and window statistics; point events have zero length; windows tile each container without gaps or overlaps; each slow_down has one value per channel, with speed at most 100 | +| `pushdown` | `e2e_pushdown` | A report of `resample` expressions (calculated channels, one channel at two rates, an event condition, a histogram next to a raw-channel event, a `diff` above a resample, and a channel also used raw) runs without Spark pushdown, with it, and with it on small chunks; the pushdown runs log a pushdown plan with both rates and solve the calculated channels without the pandas UDF, and their gold tables equal the run without pushdown; the calculated channels lie on their 1 s or 5 s grid | | `teardown` | `teardown` | Drops the schema; runs only if all tests passed, otherwise it ends as upstream failed, which fails the job run | Shared code (report config, baseline report, comparison) is in `e2e_utils.py`. When a test fails, its schema is kept for debugging. @@ -37,7 +38,7 @@ The reports read the silver tables in `.` (bundle ## Expected results -`expected//.csv` holds the expected gold rows of a test (without `_` bookkeeping columns). `incremental` and `container_filter` reuse `expected/rpm_histogram/`; `incremental_updates` compares with full reference runs instead of expected files. +`expected//
.csv` holds the expected gold rows of a test (without `_` bookkeeping columns). `incremental` and `container_filter` reuse `expected/rpm_histogram/`; `incremental_updates` compares with full reference runs instead of expected files. `pushdown` also compares with a reference run without pushdown, and has no expected file for `calculated_channel_fact` (too many rows). To refresh them after an intended change, run the job. On a mismatch, the test uploads its actual rows as CSV to `/actual//` and logs the path. Review them, then copy them into `expected/`. diff --git a/tests/e2e/e2e_pushdown.py b/tests/e2e/e2e_pushdown.py new file mode 100644 index 00000000..fecc9b3b --- /dev/null +++ b/tests/e2e/e2e_pushdown.py @@ -0,0 +1,211 @@ +# Databricks notebook source +# MAGIC %md +# MAGIC # e2e: pushdown +# MAGIC Runs a report of `resample` expressions without Spark pushdown, with pushdown, and with +# MAGIC pushdown on small chunks (so channels span many chunks). The pushdown runs must log a +# MAGIC pushdown plan and solve the calculated channels without the pandas UDF, and their gold +# MAGIC tables must equal those of the run without pushdown. Covers calculated channels computed +# MAGIC in Spark (one channel at two rates, so its samples fan out into two variants), pushed +# MAGIC subtrees inside events and aggregations, a nested op above a pushdown, and a channel also +# MAGIC used raw, which is not pushed down. + +# COMMAND ---------- + +import logging +from contextlib import contextmanager + +import pyspark.sql.functions as F +from e2e_utils import channel, check, config, event_ids, gold, new_report, params +from pyspark.sql import Window +from pyspark.testing import assertDataFrameEqual + +from impulse_query_engine.analyze.query.optimizer import pipeline +from impulse_reporting.aggregations.histogram import HistogramDuration +from impulse_reporting.aggregations.stats_aggregator import StatsAggregator +from impulse_reporting.channels.calculated_channel import CalculatedChannel +from impulse_reporting.core.page import Page +from impulse_reporting.events.basic_event import BasicEvent +from impulse_reporting.events.container_event import ContainerEvent + +p = params() +RATE = 1.0 # The source timestamps are epoch seconds; an integral rate gives an exact grid. +SLOW_RATE = 5 * RATE # A second rate on speed: two pushed-down variants of one channel. +SOLVER_LOGGER = "impulse_query_engine.analyze.query.solvers.default_solver" +PLAN_LOG = "Spark pushdown plan" +NATIVE_LOG = "Calculated channels solved natively in Spark" +#: Rate of each calculated channel, by channel id (filled by build()). +CALCULATED_RATES = {} + + +def build(prefix, pushdown): + cfg = config( + p, + calculated_channels={"emit_channel_metrics": True}, + query_engine={"enable_pushdown": pushdown}, + ) + cfg["unity_sink"]["table_prefix"] = prefix + report = new_report(cfg) + rpm, speed, coolant = ( + channel(report, name) + for name in ["Engine RPM", "Vehicle Speed Sensor", "Engine Coolant Temperature"] + ) + + # Only resamples, so the calculated channels are computed in Spark without the UDF. + for name, source, rate in [ + ("speed_1s", speed, RATE), + ("coolant_1s", coolant, RATE), + ("speed_5s", speed, SLOW_RATE), + ]: + calculated = CalculatedChannel( + name=name, expr=source.resample(rate), identity={"channel_name": name} + ) + report.add_calculated_channel(calculated) + CALCULATED_RATES[calculated.get_id()] = rate + + high_rpm = BasicEvent(name="high_rpm", expr=rpm > 2000) + fast = BasicEvent(name="fast", expr=speed.resample(RATE) > 100) + container = ContainerEvent(name="container", desc="Whole measurement") + for event in [high_rpm, fast, container]: + report.add_event(event) + + page = Page(page_number=1) + page.add_aggregation( + HistogramDuration( + name="speed_at_high_rpm", + base_expr=speed.resample(RATE), + bins=[float(x) for x in range(0, 201, 20)], + event=high_rpm, + channel_name="Vehicle Speed Sensor", + bins_unit="km/h", + values_unit="s", + ) + ) + page.add_aggregation( + StatsAggregator( + name="resampled_stats", + # The speed change per second: an op above the pushed-down resample. + input_expressions=[coolant.resample(RATE), speed.resample(RATE).diff()], + channel_names=["coolant_1s", "speed_1s_diff"], + statistics=["min", "mean", "max"], + event=container, + ) + ) + # rpm is also used raw in the same solve, so its resample runs in pandas. + page.add_aggregation( + StatsAggregator( + name="raw_and_resampled_rpm", + input_expressions=[rpm, rpm.resample(RATE)], + channel_names=["Engine RPM", "rpm_1s"], + statistics=["min", "mean", "max"], + event=container, + ) + ) + report.add_page(page) + return report + + +@contextmanager +def solver_logs(): + """Messages the solver logs at INFO and above while the block runs.""" + messages = [] + handler = logging.Handler(logging.INFO) + handler.emit = lambda record: messages.append(record.getMessage()) + logger = logging.getLogger(SOLVER_LOGGER) + level = logger.level + logger.setLevel(logging.INFO) + logger.addHandler(handler) + try: + yield messages + finally: + logger.removeHandler(handler) + logger.setLevel(level) + + +def run(prefix, pushdown, chunk_rows=None): + """Run the report into `_*`; returns the solver's log messages.""" + report = build(prefix, pushdown) + default_chunk_rows = pipeline.CHUNK_ROWS + if chunk_rows is not None: + pipeline.CHUNK_ROWS = chunk_rows + try: + with solver_logs() as messages: + report.run() + finally: + pipeline.CHUNK_ROWS = default_chunk_rows + return messages + + +ref, chunked = f"{p.prefix}_ref", f"{p.prefix}_chunked" +logs = { + ref: run(ref, pushdown=False), + p.prefix: run(p.prefix, pushdown=True), + # About 40 chunks per channel instead of one. + chunked: run(chunked, pushdown=True, chunk_rows=1000), +} + +# COMMAND ---------- + +assert not any(PLAN_LOG in m or NATIVE_LOG in m for m in logs[ref]), "pushdown without opt-in" +for prefix in [p.prefix, chunked]: + plans = [m for m in logs[prefix] if m.startswith(PLAN_LOG)] + assert plans, f"{prefix}: no pushdown plan" + assert all(f"resample(sample_rate={RATE})" in m for m in plans), plans + assert any(f"resample(sample_rate={SLOW_RATE})" in m for m in plans), plans + assert any(NATIVE_LOG in m for m in logs[prefix]), f"{prefix}: calculated channels in UDF" + print(f"{prefix}: {plans}") + +# COMMAND ---------- + +# Sample rows, events and pandas-computed aggregations are identical (compared in Spark, as +# the calculated channels have many rows); the calculated channel metrics are sums in Spark, +# whose order is not deterministic. +EXACT = [ + "calculated_channel_fact", + "event_instance_fact", + "histogram_fact", + "stats_aggregator_fact", +] +for prefix in [p.prefix, chunked]: + for table in EXACT: + actual, expected = gold(p, table, prefix=prefix), gold(p, table, prefix=ref) + for name, diff in [ + ("extra", actual.exceptAll(expected)), + ("missing", expected.exceptAll(actual)), + ]: + assert diff.count() == 0, f"{prefix}_{table}: {name} rows {diff.take(5)}" + print(f"{prefix}_{table}: equal to {ref} ({actual.count()} rows)") + assertDataFrameEqual( + gold(p, "calculated_channel_metrics", prefix=prefix), + gold(p, "calculated_channel_metrics", prefix=ref), + rtol=1e-12, + atol=0, + ) + +# COMMAND ---------- + +# The calculated channels lie on their grids (1 s or 5 s): every sample lasts exactly the rate, +# except the last one of each continuous block (whose end isn't the next sample's start). +series = Window.partitionBy("container_id", "channel_id").orderBy("tstart") +rate = F.create_map( + *[x for cid, r in CALCULATED_RATES.items() for x in (F.lit(cid).cast("long"), F.lit(r))] +)[F.col("channel_id").cast("long")] +samples = gold(p, "calculated_channel_fact").withColumn("next", F.lead("tstart").over(series)) +duration = F.col("tend") - F.col("tstart") +off_grid = samples.where( + (duration != rate) & ((duration > rate) | (F.col("next") == F.col("tend"))) +) +assert off_grid.count() == 0, f"samples off the grid: {off_grid.take(5)}" +n_series = samples.select("container_id", "channel_id").distinct().count() +n_containers = gold(p, "measurement_dimension").count() +assert n_series == len(CALCULATED_RATES) * n_containers, "missing series" + +ids = event_ids(p) +fast = gold(p, "event_instance_fact").where(F.col("event_id") == ids["fast"]) +assert fast.agg(F.sum(F.col("end_ts") - F.col("start_ts"))).first()[0] > 0, "no fast events" +assert gold(p, "histogram_fact").agg(F.sum("hist_value")).first()[0] > 0, "empty histogram" + +# COMMAND ---------- + +# The calculated channel samples are covered by their metrics and the comparison above. +for table in [*EXACT[1:], "calculated_channel_metrics"]: + check(p, table, gold(p, table)) diff --git a/tests/e2e/expected/pushdown/calculated_channel_metrics.csv b/tests/e2e/expected/pushdown/calculated_channel_metrics.csv new file mode 100644 index 00000000..39df8254 --- /dev/null +++ b/tests/e2e/expected/pushdown/calculated_channel_metrics.csv @@ -0,0 +1,244 @@ +container_id,channel_id,channel_name,value_type,duration,min,max,mean +14,1698605908,speed_5s,double,4895.842000007629,0.0,201.0,77.609734954561 +19,1698605908,speed_5s,double,3789.2969999313354,0.0,170.0,68.12873205892228 +21,1604839375,coolant_1s,double,3527.805000066757,41.0,93.0,81.94178674855078 +28,1698605908,speed_5s,double,3254.2829999923706,0.0,130.0,61.87230797090236 +56,1604839375,coolant_1s,double,2314.15700006485,62.0,95.0,89.53733216897544 +56,22179408,speed_1s,double,2314.15700006485,0.0,126.0,80.90851225511194 +67,1698605908,speed_5s,double,1448.0329999923706,0.0,88.0,24.840594102613352 +74,1604839375,coolant_1s,double,1296.5739998817444,18.0,95.0,59.68428026208551 +81,1604839375,coolant_1s,double,731.2820000648499,22.0,79.0,53.439409149490345 +6,1604839375,coolant_1s,double,5081.025000095367,25.0,94.0,85.90773515201593 +7,22179408,speed_1s,double,4971.943000078201,0.0,182.0,75.07407063880844 +9,22179408,speed_1s,double,4663.890000104904,0.0,191.0,75.63278722098201 +17,1698605908,speed_5s,double,3945.3499999046326,0.0,125.0,49.534261853757954 +24,22179408,speed_1s,double,3292.1470000743866,0.0,126.0,59.34151785919213 +25,1698605908,speed_5s,double,3282.6349999904633,0.0,125.0,59.476609492242424 +26,22179408,speed_1s,double,3738.061000108719,0.0,125.0,51.42987233071066 +27,1698605908,speed_5s,double,3238.0629999637604,0.0,126.0,60.4450870789699 +31,1698605908,speed_5s,double,3556.890000104904,0.0,128.0,56.04334123184041 +47,22179408,speed_1s,double,2917.132999897003,0.0,128.0,67.05693569916306 +54,1604839375,coolant_1s,double,2352.483999967575,67.0,94.0,89.93323482747094 +55,1604839375,coolant_1s,double,3004.49799990654,34.0,94.0,84.60425601861019 +59,1604839375,coolant_1s,double,3440.821000099182,25.0,101.0,86.59994838482683 +67,1604839375,coolant_1s,double,1448.0329999923706,27.0,95.0,69.42530660544675 +6,1698605908,speed_5s,double,5081.025000095367,0.0,196.0,73.55799272646463 +9,1698605908,speed_5s,double,4663.890000104904,0.0,190.0,75.65144117722842 +11,1698605908,speed_5s,double,6304.121000051498,0.0,182.0,59.99646897591437 +15,22179408,speed_1s,double,4031.986999988556,0.0,126.0,48.8248101991794 +25,22179408,speed_1s,double,3282.6349999904633,0.0,126.0,59.51042379081669 +26,1604839375,coolant_1s,double,3738.061000108719,14.0,94.0,80.88859170596483 +29,1698605908,speed_5s,double,3401.3180000782013,0.0,125.0,59.21822070014302 +36,1698605908,speed_5s,double,2921.0889999866486,0.0,130.0,68.21428583686111 +38,22179408,speed_1s,double,2858.1579999923706,0.0,128.0,69.8085970056703 +38,1698605908,speed_5s,double,2858.1579999923706,0.0,128.0,69.78795434000935 +39,22179408,speed_1s,double,2791.648999929428,0.0,130.0,71.91842527662877 +42,1698605908,speed_5s,double,2709.4609999656677,0.0,130.0,73.73975857284222 +48,1698605908,speed_5s,double,2593.265000104904,0.0,126.0,75.181286907475 +49,22179408,speed_1s,double,2373.802999973297,0.0,125.0,79.56557473477143 +50,22179408,speed_1s,double,2875.108999967575,0.0,125.0,64.31790933911914 +51,1604839375,coolant_1s,double,3914.0039999485016,31.0,94.0,85.44686515384528 +58,1604839375,coolant_1s,double,1914.832999944687,11.0,95.0,71.05970651180458 +62,1698605908,speed_5s,double,1835.1440000534058,0.0,128.0,55.63323640925664 +77,22179408,speed_1s,double,1233.3819999694824,0.0,87.0,29.38829981376156 +79,1698605908,speed_5s,double,1127.119999885559,0.0,86.0,32.201540211942984 +80,1698605908,speed_5s,double,1204.9849998950958,0.0,89.0,30.834408729763986 +5,22179408,speed_1s,double,5398.681999921799,0.0,216.0,66.43102890764727 +6,22179408,speed_1s,double,5081.025000095367,0.0,198.0,73.56803007129152 +9,1604839375,coolant_1s,double,4663.890000104904,42.0,96.0,88.33248425676416 +18,1604839375,coolant_1s,double,4335.055000066757,17.0,94.0,80.69768318984516 +18,1698605908,speed_5s,double,4335.055000066757,0.0,197.0,87.88123795295176 +20,1604839375,coolant_1s,double,3700.523999929428,30.0,94.0,84.58550302593561 +24,1698605908,speed_5s,double,3292.1470000743866,0.0,125.0,59.36247682609076 +30,1604839375,coolant_1s,double,3214.0999999046326,34.0,94.0,83.29358140675642 +57,1604839375,coolant_1s,double,2048.8069999217987,73.0,92.0,88.20734359065447 +60,22179408,speed_1s,double,1853.6219999790192,0.0,107.0,61.20341687856753 +62,1604839375,coolant_1s,double,1835.1440000534058,88.0,91.0,89.70193074767752 +66,1604839375,coolant_1s,double,1490.2360000610352,-1.0,64.0,45.183517242335085 +69,1698605908,speed_5s,double,1243.1129999160767,0.0,65.0,14.479777784654486 +76,1604839375,coolant_1s,double,1227.5350000858307,11.0,84.0,57.1490059364652 +78,1604839375,coolant_1s,double,862.3629999160767,88.0,91.0,89.44107063957185 +80,1604839375,coolant_1s,double,1204.9849998950958,16.0,91.0,62.131615743914985 +2,1604839375,coolant_1s,double,6522.724999904633,17.0,98.0,86.48472072621517 +4,22179408,speed_1s,double,5986.755000114441,0.0,190.0,63.68441668194404 +11,22179408,speed_1s,double,6304.121000051498,0.0,184.0,59.97711655548986 +12,22179408,speed_1s,double,5138.710000038147,0.0,194.0,82.68086737660735 +27,22179408,speed_1s,double,3238.0629999637604,0.0,126.0,60.50469061355282 +29,22179408,speed_1s,double,3401.3180000782013,0.0,126.0,59.23439090239954 +30,22179408,speed_1s,double,3214.0999999046326,0.0,130.0,61.07744003168066 +30,1698605908,speed_5s,double,3214.0999999046326,0.0,130.0,61.083693726309086 +35,1698605908,speed_5s,double,2939.588000059128,0.0,131.0,67.90747546798558 +37,1604839375,coolant_1s,double,3100.582999944687,15.0,94.0,78.68194046069054 +40,1604839375,coolant_1s,double,3131.84299993515,29.0,94.0,84.2353432147225 +46,1604839375,coolant_1s,double,2491.7430000305176,11.0,94.0,77.08755838797703 +51,1698605908,speed_5s,double,3914.0039999485016,0.0,215.0,95.3997492094829 +52,22179408,speed_1s,double,2443.24799990654,0.0,125.0,78.59947087129342 +55,1698605908,speed_5s,double,3004.49799990654,0.0,125.0,64.95727273104659 +1,22179408,speed_1s,double,8083.973999977112,0.0,203.0,49.36829015050111 +10,1698605908,speed_5s,double,4837.854000091553,0.0,125.0,45.55222212076461 +14,1604839375,coolant_1s,double,4895.842000007629,31.0,93.0,85.51840970358967 +16,1604839375,coolant_1s,double,4374.618999958038,12.0,94.0,79.50146401315618 +17,1604839375,coolant_1s,double,3945.3499999046326,29.0,94.0,84.91004600337592 +20,1698605908,speed_5s,double,3700.523999929428,0.0,127.0,52.71010267835578 +23,1698605908,speed_5s,double,4696.368999958038,0.0,206.0,79.58275851053003 +43,1604839375,coolant_1s,double,2630.640000104904,12.0,94.0,75.0400358853205 +43,22179408,speed_1s,double,2630.640000104904,0.0,131.0,75.90092144574616 +46,22179408,speed_1s,double,2491.7430000305176,0.0,132.0,63.55952439640056 +47,1698605908,speed_5s,double,2917.132999897003,0.0,128.0,67.03842437314471 +60,1604839375,coolant_1s,double,1853.6219999790192,87.0,91.0,89.50172041549492 +63,1604839375,coolant_1s,double,1625.7799999713898,2.0,70.0,51.822878863979106 +65,1604839375,coolant_1s,double,2216.9100000858307,24.0,94.0,76.26011430381257 +66,1698605908,speed_5s,double,1490.2360000610352,0.0,103.0,26.599813719690356 +70,1698605908,speed_5s,double,1239.236999988556,0.0,103.0,31.4104565957597 +74,1698605908,speed_5s,double,1296.5739998817444,0.0,84.0,28.116405236665955 +1,1698605908,speed_5s,double,8083.973999977112,0.0,202.0,49.36235247677775 +3,1604839375,coolant_1s,double,6491.930000066757,31.0,101.0,86.7964519026567 +22,1698605908,speed_5s,double,3521.632999897003,0.0,210.0,68.53354679691459 +23,22179408,speed_1s,double,4696.368999958038,0.0,208.0,79.59106279837461 +25,1604839375,coolant_1s,double,3282.6349999904633,33.0,94.0,85.00035794413705 +26,1698605908,speed_5s,double,3738.061000108719,0.0,125.0,51.404993121945125 +27,1604839375,coolant_1s,double,3238.0629999637604,31.0,94.0,84.65791740302966 +34,1698605908,speed_5s,double,3547.53200006485,0.0,130.0,60.29402976381607 +35,1604839375,coolant_1s,double,2939.588000059128,9.0,95.0,77.25033440079919 +45,1698605908,speed_5s,double,2761.46799993515,0.0,113.0,40.436825631375164 +47,1604839375,coolant_1s,double,2917.132999897003,54.0,94.0,88.95273887062815 +50,1604839375,coolant_1s,double,2875.108999967575,12.0,94.0,79.43062367363801 +54,22179408,speed_1s,double,2352.483999967575,0.0,126.0,79.84793945573661 +54,1698605908,speed_5s,double,2352.483999967575,0.0,125.0,79.82306532267843 +61,22179408,speed_1s,double,2315.7400000095367,0.0,126.0,83.33857859657142 +62,22179408,speed_1s,double,1835.1440000534058,0.0,128.0,55.72260269331676 +65,22179408,speed_1s,double,2216.9100000858307,0.0,102.0,18.82928490534107 +70,1604839375,coolant_1s,double,1239.236999988556,25.0,94.0,72.15030700325742 +71,1604839375,coolant_1s,double,1184.8540000915527,24.0,95.0,73.02575676121619 +73,1698605908,speed_5s,double,980.5539999008179,0.0,126.0,51.848240897637886 +76,22179408,speed_1s,double,1227.5350000858307,0.0,92.0,30.264717500847112 +76,1698605908,speed_5s,double,1227.5350000858307,0.0,92.0,30.32092771073536 +77,1698605908,speed_5s,double,1233.3819999694824,0.0,87.0,29.508295078816232 +78,1698605908,speed_5s,double,862.3629999160767,0.0,118.0,57.69854342613318 +2,1698605908,speed_5s,double,6522.724999904633,0.0,192.0,57.23068196274685 +3,1698605908,speed_5s,double,6491.930000066757,0.0,184.0,58.166831742812526 +13,1698605908,speed_5s,double,4708.522000074387,0.0,178.0,80.93516394188654 +16,1698605908,speed_5s,double,4374.618999958038,0.0,186.0,86.55153740328743 +44,22179408,speed_1s,double,3321.704999923706,0.0,197.0,78.15775332426057 +45,1604839375,coolant_1s,double,2761.46799993515,89.0,93.0,90.3648305900915 +48,22179408,speed_1s,double,2593.265000104904,0.0,126.0,75.19324094996536 +53,22179408,speed_1s,double,3152.9649999141693,0.0,130.0,62.329585011362255 +5,1604839375,coolant_1s,double,5398.681999921799,30.0,93.0,86.50359106161959 +10,1604839375,coolant_1s,double,4837.854000091553,8.0,93.0,74.14057927361539 +11,1604839375,coolant_1s,double,6304.121000051498,30.0,93.0,86.09463730157664 +12,1604839375,coolant_1s,double,5138.710000038147,8.0,95.0,82.9118961763229 +12,1698605908,speed_5s,double,5138.710000038147,0.0,194.0,82.61898414132114 +19,22179408,speed_1s,double,3789.2969999313354,0.0,172.0,68.11316188851836 +21,22179408,speed_1s,double,3527.805000066757,0.0,115.0,42.330854454022855 +33,1604839375,coolant_1s,double,4302.207999944687,75.0,103.0,90.50106921824994 +39,1698605908,speed_5s,double,2791.648999929428,0.0,130.0,71.91985812151725 +40,22179408,speed_1s,double,3131.84299993515,0.0,126.0,65.2788150632817 +40,1698605908,speed_5s,double,3131.84299993515,0.0,126.0,65.24912008814982 +46,1698605908,speed_5s,double,2491.7430000305176,0.0,132.0,63.664270351339255 +52,1604839375,coolant_1s,double,2443.24799990654,64.0,93.0,89.34073331886282 +53,1698605908,speed_5s,double,3152.9649999141693,0.0,130.0,62.31436124579514 +64,1698605908,speed_5s,double,1545.9430000782013,0.0,101.0,25.11088701073474 +69,1604839375,coolant_1s,double,1243.1129999160767,90.0,92.0,90.62423930893844 +72,22179408,speed_1s,double,1156.648999929428,0.0,98.0,33.5365352863027 +73,1604839375,coolant_1s,double,980.5539999008179,4.0,72.0,46.54092278189158 +73,22179408,speed_1s,double,980.5539999008179,0.0,126.0,51.827844264711985 +75,1698605908,speed_5s,double,1268.12700009346,0.0,92.0,28.597293486636033 +13,1604839375,coolant_1s,double,4708.522000074387,17.0,94.0,80.0719329761365 +15,1604839375,coolant_1s,double,4031.986999988556,29.0,100.0,86.09125401444594 +18,22179408,speed_1s,double,4335.055000066757,0.0,199.0,87.89761606118773 +19,1604839375,coolant_1s,double,3789.2969999313354,16.0,93.0,77.2196362542339 +28,22179408,speed_1s,double,3254.2829999923706,0.0,130.0,61.916864636687215 +31,22179408,speed_1s,double,3556.890000104904,0.0,128.0,56.04643382115289 +37,22179408,speed_1s,double,3100.582999944687,0.0,129.0,64.29597272627645 +39,1604839375,coolant_1s,double,2791.648999929428,55.0,96.0,88.40019071146253 +41,22179408,speed_1s,double,2719.5569999217987,0.0,128.0,73.4248261778451 +41,1698605908,speed_5s,double,2719.5569999217987,0.0,127.0,73.41453038334593 +58,1698605908,speed_5s,double,1914.832999944687,0.0,91.0,30.25329101894181 +68,22179408,speed_1s,double,1308.3269999027252,0.0,102.0,30.248554071682708 +1,1604839375,coolant_1s,double,8083.973999977112,20.0,93.0,81.47659307165077 +3,22179408,speed_1s,double,6491.930000066757,0.0,184.0,58.180078958971535 +4,1698605908,speed_5s,double,5986.755000114441,0.0,188.0,63.690596991644256 +28,1604839375,coolant_1s,double,3254.2829999923706,16.0,94.0,80.05025592424866 +32,1604839375,coolant_1s,double,3080.598999977112,13.0,94.0,78.1569788861685 +32,1698605908,speed_5s,double,3080.598999977112,0.0,130.0,64.76337881090085 +34,1604839375,coolant_1s,double,3547.53200006485,14.0,94.0,74.21999745200145 +36,1604839375,coolant_1s,double,2921.0889999866486,5.0,95.0,76.04038870429954 +44,1604839375,coolant_1s,double,3321.704999923706,31.0,97.0,85.31638420615018 +45,22179408,speed_1s,double,2761.46799993515,0.0,114.0,40.462898720037316 +52,1698605908,speed_5s,double,2443.24799990654,0.0,125.0,78.59824299757773 +58,22179408,speed_1s,double,1914.832999944687,0.0,91.0,30.277836240379585 +64,1604839375,coolant_1s,double,1545.9430000782013,28.0,94.0,75.63724535841438 +75,1604839375,coolant_1s,double,1268.12700009346,30.0,94.0,72.03738821251518 +79,22179408,speed_1s,double,1127.119999885559,0.0,86.0,32.19000639123062 +8,22179408,speed_1s,double,4819.085999965668,0.0,201.0,70.55196773878329 +8,1698605908,speed_5s,double,4819.085999965668,0.0,200.0,70.53308448996792 +15,1698605908,speed_5s,double,4031.986999988556,0.0,126.0,48.867965100224595 +16,22179408,speed_1s,double,4374.618999958038,0.0,188.0,86.59131229568415 +21,1698605908,speed_5s,double,3527.805000066757,0.0,115.0,42.295421656575826 +22,1604839375,coolant_1s,double,3521.632999897003,40.0,93.0,86.71665559683699 +38,1604839375,coolant_1s,double,2858.1579999923706,9.0,95.0,77.4673975336255 +42,1604839375,coolant_1s,double,2709.4609999656677,12.0,94.0,76.7494228555472 +56,1698605908,speed_5s,double,2314.15700006485,0.0,125.0,80.87141753771259 +59,1698605908,speed_5s,double,3440.821000099182,0.0,170.0,73.25722552633054 +64,22179408,speed_1s,double,1545.9430000782013,0.0,101.0,25.02097425198945 +68,1604839375,coolant_1s,double,1308.3269999027252,10.0,74.0,52.49620163606516 +74,22179408,speed_1s,double,1296.5739998817444,0.0,85.0,28.1595959839778 +80,22179408,speed_1s,double,1204.9849998950958,0.0,91.0,30.916567428842082 +10,22179408,speed_1s,double,4837.854000091553,0.0,125.0,45.56131706244726 +13,22179408,speed_1s,double,4708.522000074387,0.0,179.0,80.964472501982 +22,22179408,speed_1s,double,3521.632999897003,0.0,211.0,68.50799047119791 +23,1604839375,coolant_1s,double,4696.368999958038,26.0,94.0,85.88982616138162 +32,22179408,speed_1s,double,3080.598999977112,0.0,131.0,64.76110652554333 +35,22179408,speed_1s,double,2939.588000059128,0.0,131.0,67.90203252836285 +37,1698605908,speed_5s,double,3100.582999944687,0.0,129.0,64.24598212773328 +41,1604839375,coolant_1s,double,2719.5569999217987,7.0,95.0,75.75952443698638 +50,1698605908,speed_5s,double,2875.108999967575,0.0,125.0,64.4132100738054 +53,1604839375,coolant_1s,double,3152.9649999141693,40.0,94.0,80.60631342221673 +61,1698605908,speed_5s,double,2315.7400000095367,0.0,126.0,83.34764697212304 +63,22179408,speed_1s,double,1625.7799999713898,0.0,99.0,24.299720749852515 +67,22179408,speed_1s,double,1448.0329999923706,0.0,89.0,24.94349231004425 +71,1698605908,speed_5s,double,1184.8540000915527,0.0,111.0,32.77197021489537 +77,1604839375,coolant_1s,double,1233.3819999694824,30.0,95.0,71.54822269117298 +81,1698605908,speed_5s,double,731.2820000648499,0.0,100.0,43.062763748160876 +5,1698605908,speed_5s,double,5398.681999921799,0.0,215.0,66.42917660369602 +14,22179408,speed_1s,double,4895.842000007629,0.0,201.0,77.59278179308238 +31,1604839375,coolant_1s,double,3556.890000104904,14.0,94.0,79.9971604410171 +48,1604839375,coolant_1s,double,2593.265000104904,37.0,94.0,85.42180610184076 +49,1604839375,coolant_1s,double,2373.802999973297,17.0,94.0,79.0921866724866 +59,22179408,speed_1s,double,3440.821000099182,0.0,170.0,73.26245683595097 +61,1604839375,coolant_1s,double,2315.7400000095367,51.0,95.0,87.06961921460437 +66,22179408,speed_1s,double,1490.2360000610352,0.0,103.0,26.60987923951365 +71,22179408,speed_1s,double,1184.8540000915527,0.0,111.0,32.749182597182205 +72,1698605908,speed_5s,double,1156.648999929428,0.0,97.0,33.592732110061654 +78,22179408,speed_1s,double,862.3629999160767,0.0,118.0,57.63002355717546 +17,22179408,speed_1s,double,3945.3499999046326,0.0,126.0,49.57785747898871 +20,22179408,speed_1s,double,3700.523999929428,0.0,127.0,52.736855646314346 +24,1604839375,coolant_1s,double,3292.1470000743866,29.0,94.0,84.96984794432757 +29,1604839375,coolant_1s,double,3401.3180000782013,9.0,95.0,75.96046944173662 +33,1698605908,speed_5s,double,4302.207999944687,0.0,127.0,66.98072245779491 +42,22179408,speed_1s,double,2709.4609999656677,0.0,131.0,73.70875609670358 +43,1698605908,speed_5s,double,2630.640000104904,0.0,131.0,75.90928442965848 +49,1698605908,speed_5s,double,2373.802999973297,0.0,125.0,79.57644463424228 +51,22179408,speed_1s,double,3914.0039999485016,0.0,218.0,95.40920244458448 +55,22179408,speed_1s,double,3004.49799990654,0.0,125.0,64.97391577763489 +57,1698605908,speed_5s,double,2048.8069999217987,0.0,129.0,75.40369298120333 +65,1698605908,speed_5s,double,2216.9100000858307,0.0,101.0,18.873039500699583 +68,1698605908,speed_5s,double,1308.3269999027252,0.0,101.0,30.28677081719336 +69,22179408,speed_1s,double,1243.1129999160767,0.0,65.0,14.41140105622695 +75,22179408,speed_1s,double,1268.12700009346,0.0,92.0,28.583887889248118 +79,1604839375,coolant_1s,double,1127.119999885559,24.0,95.0,67.72829867036053 +2,22179408,speed_1s,double,6522.724999904633,0.0,193.0,57.2452464277521 +4,1604839375,coolant_1s,double,5986.755000114441,12.0,95.0,84.76381528899793 +7,1604839375,coolant_1s,double,4971.943000078201,30.0,96.0,86.36500257550907 +7,1698605908,speed_5s,double,4971.943000078201,0.0,182.0,75.07728869661797 +8,1604839375,coolant_1s,double,4819.085999965668,65.0,93.0,86.09475987773212 +33,22179408,speed_1s,double,4302.207999944687,0.0,127.0,67.01442608160897 +34,22179408,speed_1s,double,3547.53200006485,0.0,131.0,60.30107691659708 +36,22179408,speed_1s,double,2921.0889999866486,0.0,130.0,68.19785360901723 +44,1698605908,speed_5s,double,3321.704999923706,0.0,196.0,78.13457245780742 +57,22179408,speed_1s,double,2048.8069999217987,0.0,129.0,75.40388138360358 +60,1698605908,speed_5s,double,1853.6219999790192,0.0,106.0,61.185613896082224 +63,1698605908,speed_5s,double,1625.7799999713898,0.0,99.0,24.437500769291763 +70,22179408,speed_1s,double,1239.236999988556,0.0,103.0,31.349128536638887 +72,1604839375,coolant_1s,double,1156.648999929428,22.0,94.0,71.81384326508437 +81,22179408,speed_1s,double,731.2820000648499,0.0,100.0,43.26241313100778 diff --git a/tests/e2e/expected/pushdown/event_instance_fact.csv b/tests/e2e/expected/pushdown/event_instance_fact.csv new file mode 100644 index 00000000..b875ad3c --- /dev/null +++ b/tests/e2e/expected/pushdown/event_instance_fact.csv @@ -0,0 +1,3660 @@ +container_id,event_instance_id,event_id,start_ts,end_ts +58,-392167370839906768,479635942,1.5194049718860002E9,1.519404972756E9 +58,-577730803822243282,479635942,1.519405126926E9,1.519405127677E9 +58,-1607115580174927158,479635942,1.5194051384160001E9,1.5194051391650002E9 +58,-1707683064145550443,479635942,1.5194052788140001E9,1.5194052797440002E9 +58,-8584964339849135253,479635942,1.519405314184E9,1.5194053159550002E9 +58,-1421226166730715875,479635942,1.519405385286E9,1.519405386187E9 +58,-1876304493150135480,479635942,1.519405427798E9,1.519405440457E9 +58,-6154401651170875487,479635942,1.519405468206E9,1.519405468957E9 +58,-1406917860190929118,479635942,1.5194054834180002E9,1.519405485667E9 +58,-8909632217749248406,479635942,1.5194054958960001E9,1.519405497726E9 +58,-2648942039680002738,479635942,1.519405639806E9,1.519405642565E9 +58,-4884515493043158578,479635942,1.5194059158040001E9,1.5194059174840002E9 +21,-6899411427707798469,479635942,1.5188770660960002E9,1.5188770670270002E9 +21,-7119357877423866742,479635942,1.5188771023070002E9,1.5188771031460001E9 +21,-6264600875148061610,479635942,1.518877115296E9,1.5188771170370002E9 +21,4974826582884942551,479635942,1.518877206045E9,1.5188772078460002E9 +21,1289029136727313106,479635942,1.5188772771460001E9,1.518877278647E9 +21,8101410539130513915,479635942,1.518877284736E9,1.5188772893850002E9 +21,8752606268609374984,479635942,1.5188773419160001E9,1.518877344585E9 +21,-1480847104797789343,479635942,1.518877352625E9,1.5188773534950001E9 +21,-8338341015569994825,479635942,1.5188773650170002E9,1.5188773675080001E9 +21,8154730559722274258,479635942,1.5188773958590002E9,1.5188773996950002E9 +21,-3158721213861171555,479635942,1.5188774546550002E9,1.518877455556E9 +21,-6871179186759736467,479635942,1.5188774583160002E9,1.5188774592150002E9 +21,-1829041438898315475,479635942,1.5188778007340002E9,1.518877801663E9 +21,2059476694655684232,479635942,1.5188778034940002E9,1.5188778052930002E9 +21,1694605092864503726,479635942,1.518877827973E9,1.5188778315730002E9 +21,-1745174359212764099,479635942,1.5188778352020001E9,1.5188778370310001E9 +21,-139531584369840434,479635942,1.518877842404E9,1.5188778556350002E9 +21,2607686719068508503,479635942,1.518877858335E9,1.518877861003E9 +21,-119566172197811932,479635942,1.5188779573930001E9,1.5188779609940002E9 +21,-4733250411904369960,479635942,1.5188780001430001E9,1.5188780018830001E9 +21,7255531698784217731,479635942,1.518878005243E9,1.518878007795E9 +21,513519212452121020,479635942,1.5188780291820002E9,1.518878034522E9 +21,-1176947577378637820,479635942,1.518878079433E9,1.518878080332E9 +21,1685195814674221943,479635942,1.518878254367E9,1.518878255954E9 +21,-3112809955600316429,479635942,1.518878317782E9,1.5188783232120001E9 +21,-8710966488530415760,479635942,1.5188784892610002E9,1.518878491571E9 +21,-1045224000766056863,479635942,1.5188785017110002E9,1.518878502581E9 +21,7867369122101485919,479635942,1.518878568071E9,1.5188785707710001E9 +21,-3742457767832808475,479635942,1.518878572601E9,1.518878577911E9 +21,8312151138059009802,479635942,1.518878652042E9,1.518878681831E9 +21,8414858666053075109,479635942,1.518878683661E9,1.5188786972510002E9 +21,-8219263397949537991,479635942,1.518878740693E9,1.518878756592E9 +21,-3304979534718454068,479635942,1.5188787656820002E9,1.518878791602E9 +21,-1934919521937342953,479635942,1.5188788042910001E9,1.5188788651890001E9 +21,-370981617701841497,479635942,1.5188788674410002E9,1.5188788780890002E9 +21,6355139955981713229,479635942,1.518878917661E9,1.5188789248590002E9 +21,-8952215723121251998,479635942,1.5188791500100002E9,1.518879157209E9 +21,8094389502770662682,479635942,1.518879378397E9,1.5188794001460001E9 +21,7246192116860339378,479635942,1.518879470078E9,1.518879474726E9 +21,7175156655889102509,479635942,1.518879528486E9,1.518879538325E9 +21,-5822746784247612928,479635942,1.5188796440170002E9,1.518879645818E9 +21,-7315354526747371087,479635942,1.51887967297E9,1.518879673868E9 +21,-7196778552377116883,479635942,1.5188796882680001E9,1.5188796890200002E9 +21,4440939622405681780,479635942,1.518879711009E9,1.518879714009E9 +21,6488693368657868428,479635942,1.518879715509E9,1.5188797190770001E9 +21,-5655791661006958610,479635942,1.518879783667E9,1.518879789937E9 +21,4821727960606958404,479635942,1.5188799552350001E9,1.518879957065E9 +21,-3418011628657036574,479635942,1.5188799615660002E9,1.5188799624650002E9 +21,-1241142162227330849,479635942,1.518880064736E9,1.5188800700740001E9 +21,-7531538706908136964,479635942,1.518880105864E9,1.518880106765E9 +21,-3483513624368168626,479635942,1.518880119334E9,1.518880121164E9 +21,2977822913768495977,479635942,1.518880149844E9,1.5188801505930002E9 +21,-6222786963020322569,479635942,1.5188802360070002E9,1.5188802396660001E9 +21,-1098647243308978850,479635942,1.518880248457E9,1.5188802493260002E9 +21,6724973428078230276,479635942,1.5188803934450002E9,1.518880395154E9 +21,8130984584943230121,479635942,1.518880403617E9,1.5188804067050002E9 +71,-4489390871663989789,479635942,1.501231062964E9,1.501231068962E9 +71,5206177872305096274,479635942,1.501231075533E9,1.501231076463E9 +71,-4192053480864001720,479635942,1.501231092213E9,1.501231093112E9 +71,-8254019672698524351,479635942,1.501231161123E9,1.501231161873E9 +71,-7392519746642868786,479635942,1.5012311633739998E9,1.501231164873E9 +71,-2963164630870117144,479635942,1.501231230393E9,1.501231231325E9 +71,-5990506934678448865,479635942,1.501231233184E9,1.501231235885E9 +71,-278389414754127081,479635942,1.501231236784E9,1.501231238646E9 +71,780385810968596253,479635942,1.501231261174E9,1.501231262914E9 +71,7210320572063479283,479635942,1.50123126611E9,1.501231270746E9 +71,7151462058824111706,479635942,1.501231276895E9,1.501231280134E9 +71,1586813079414789139,479635942,1.501231280884E9,1.5012312824429998E9 +71,-7038077438129468939,479635942,1.501231333235E9,1.501231335034E9 +71,-7227225762381369104,479635942,1.5012313413639998E9,1.501231344964E9 +71,-3778616821865773687,479635942,1.501231346764E9,1.5012313503639998E9 +71,-5682487224265024281,479635942,1.501231355704E9,1.501231356576E9 +71,1583533552934774866,479635942,1.501231644847E9,1.5012316457159998E9 +71,4773761074037179597,479635942,1.501231698578E9,1.501231699507E9 +71,-5059436520065358221,479635942,1.5012317148899999E9,1.501231717621E9 +71,-5819772863819020717,479635942,1.501231762383E9,1.501231764121E9 +71,6026457628452452747,479635942,1.501231765951E9,1.501231766733E9 +71,-7955541008994306897,479635942,1.501231827422E9,1.501231828351E9 +71,-5353205977548936132,479635942,1.501231921652E9,1.501231923452E9 +71,-6896668616549247688,479635942,1.5012319252519999E9,1.501231927053E9 +71,-8588831663195296280,479635942,1.501231950692E9,1.501231951593E9 +71,7368758490086608583,479635942,1.501232062743E9,1.501232064544E9 +21,-4301959432199305070,627460548,1.518879727209E9,1.518879750209E9 +71,-6675533528471306315,627460548,1.5012313008E9,1.5012313098E9 +71,8420902226317329417,627460548,1.5012313568E9,1.5012313818E9 +2,6428881338307012591,479635942,1.524466816435E9,1.5244668172749999E9 +2,3608378248033897188,479635942,1.5244669526209998E9,1.524466954122E9 +2,7900657249880286998,479635942,1.524466957274E9,1.5244669603349998E9 +2,3760264827360994613,479635942,1.5244669828679998E9,1.5244669837979999E9 +2,-8666981863875370668,479635942,1.524467046047E9,1.524467047698E9 +2,-3085541496776081601,479635942,1.5244670656369998E9,1.5244670663869998E9 +2,-2425461742533251016,479635942,1.5244670695979998E9,1.524467072837E9 +2,734577216518266441,479635942,1.5244671582189999E9,1.5244671590579998E9 +2,-1993999618294923600,479635942,1.5244671616379998E9,1.5244671633179998E9 +2,-2621726907686913263,479635942,1.5244672068479998E9,1.5244672085879998E9 +2,-3821005119971238146,479635942,1.5244672113179998E9,1.524467212218E9 +2,4089823852297291605,479635942,1.524467221248E9,1.524467222148E9 +2,6430346601165202106,479635942,1.5244673325189998E9,1.5244673397189999E9 +2,4077963347682511371,479635942,1.524467399119E9,1.524467401699E9 +2,3293682431627794236,479635942,1.5244674277689998E9,1.52446742936E9 +2,2692926594913195496,479635942,1.524467708422E9,1.524467813182E9 +2,3529383243168161374,479635942,1.524468131237E9,1.524468132077E9 +2,-939484201965019641,479635942,1.524468146238E9,1.524468160007E9 +2,293036198218521617,479635942,1.5244689333779998E9,1.5244689342779999E9 +2,1434803248378001878,479635942,1.524469130089E9,1.5244691318899999E9 +2,5048621473473296117,479635942,1.524469193449E9,1.5244692056E9 +2,-9150013433860073968,479635942,1.5244692333189998E9,1.52446923512E9 +2,3680292025329744175,479635942,1.5244693180089998E9,1.52446931882E9 +2,-1642564822255826089,479635942,1.524469413379E9,1.5244694159589999E9 +2,3758435368791722745,479635942,1.5244694192889998E9,1.5244694328799999E9 +2,-8059549587276591825,479635942,1.5244694496799998E9,1.5244694545089998E9 +2,-3029148802393051644,479635942,1.52446946162E9,1.5244694837889998E9 +2,-1591319615282683150,479635942,1.5244698628999999E9,1.52446993151E9 +2,5350367763502789966,479635942,1.524469935261E9,1.5244699869199998E9 +2,-1664548746545410241,479635942,1.524469995921E9,1.524470029941E9 +2,1717591356661177602,479635942,1.5244700796799998E9,1.524470129211E9 +2,-3427002422015830987,479635942,1.5244701392299998E9,1.5244701426799998E9 +2,-7129141920227876786,479635942,1.5244701966509998E9,1.524470280951E9 +2,-1155500824059021327,479635942,1.5244702885119998E9,1.5244704935909998E9 +2,3319824501553106817,479635942,1.524470495211E9,1.524470546181E9 +2,-2355847032375364518,479635942,1.524470644043E9,1.524470645543E9 +2,8543181478380905706,479635942,1.5244706618019998E9,1.5244706898219998E9 +2,-6228702062621336173,479635942,1.5244707075219998E9,1.524470724893E9 +2,-3110428313527381105,479635942,1.524470746102E9,1.5244707485319998E9 +2,-7532193968443324498,479635942,1.524470762872E9,1.524470820383E9 +2,1016417222900581189,479635942,1.5244708230219998E9,1.5244708361929998E9 +2,1344924229594086868,479635942,1.5244708764229999E9,1.524470880382E9 +2,562571204393351006,479635942,1.524470886712E9,1.524470893043E9 +2,5795463611740781210,479635942,1.5244709282619998E9,1.5244710126829998E9 +2,6738994380435536255,479635942,1.5244710216829998E9,1.524471113784E9 +2,-9183279540747628614,479635942,1.524471132863E9,1.5244711436629999E9 +2,-5220472668867677849,479635942,1.5244711922629998E9,1.5244713056339998E9 +2,-2628277987157342201,479635942,1.524471331314E9,1.5244713531539998E9 +2,2746166188718263427,479635942,1.524471356814E9,1.5244713586139998E9 +2,-44603451606756140,479635942,1.5244713883739998E9,1.524471391074E9 +2,-6205464925420997478,479635942,1.524471395574E9,1.5244714295949998E9 +2,6579061671960854680,479635942,1.5244714413839998E9,1.524471461185E9 +2,-1750677098349466194,479635942,1.5244723598669999E9,1.5244723632879999E9 +2,-2906537603049956629,479635942,1.5244724998479998E9,1.5244725005979998E9 +2,-5077247164212358936,479635942,1.5244725192879999E9,1.524472521599E9 +2,2051295939379277460,479635942,1.524472555648E9,1.5244725565479999E9 +2,5209212310043592781,479635942,1.5244728050379999E9,1.524472805938E9 +2,-3422328057157434933,479635942,1.5244729387789998E9,1.524472939679E9 +2,158446166795455731,479635942,1.524473063909E9,1.5244730648089998E9 +75,-969344755953198875,479635942,1.499974195436E9,1.4999741977150002E9 +75,-8184068060056686042,479635942,1.4999743014910002E9,1.4999743022410002E9 +75,1393722219846118446,479635942,1.4999743521420002E9,1.499974353671E9 +75,-4100211533129267640,479635942,1.499974427502E9,1.4999744291520002E9 +75,4462164656315198460,479635942,1.4999744306520002E9,1.499974433561E9 +75,-3375624799758144240,479635942,1.49997460399E9,1.499974605551E9 +75,4286020811146110156,479635942,1.4999747010410001E9,1.499974702601E9 +75,5930394635173492755,479635942,1.499974724476E9,1.4999747259710002E9 +75,-8132417072585646740,479635942,1.49997489469E9,1.49997489592E9 +75,-3020731302601077640,479635942,1.49997493063E9,1.4999749317440002E9 +75,9193106912931198964,479635942,1.49997493363E9,1.49997493549E9 +75,9166106347110276410,479635942,1.4999749373500001E9,1.4999749383400002E9 +75,7024209480978008053,479635942,1.49997495748E9,1.499974960811E9 +75,-7250479276706265556,479635942,1.49997498415E9,1.49997498526E9 +75,-5306326062299008932,479635942,1.4999749875440001E9,1.499974994051E9 +75,-7895199489110900965,479635942,1.4999750400140002E9,1.499975041061E9 +75,7536782177835813566,479635942,1.4999750469720001E9,1.4999750479320002E9 +75,-5088014885737652708,479635942,1.499975079796E9,1.4999750829720001E9 +75,-5622553165211996885,479635942,1.499975153621E9,1.499975156743E9 +75,-1654556088811395401,479635942,1.4999751592020001E9,1.499975160161E9 +75,3071895908825449354,479635942,1.4999751698530002E9,1.4999751708130002E9 +75,4050046434697820056,479635942,1.499975249502E9,1.499975254032E9 +2,2493439660761732656,627460548,1.524467242245E9,1.524467248245E9 +2,8946178248164710761,627460548,1.524467713245E9,1.524467818245E9 +2,-8853963897832703531,627460548,1.524469349245E9,1.524469361245E9 +2,8671949497985902729,627460548,1.524469451245E9,1.524469565245E9 +2,2811815646353338462,627460548,1.524469586245E9,1.524470042245E9 +2,-210603526432604769,627460548,1.524470054245E9,1.524470055245E9 +2,-5551831272299988146,627460548,1.524470058245E9,1.524470555245E9 +2,8446249224466709581,627460548,1.524470565245E9,1.524470588245E9 +2,-1382790657226354902,627460548,1.524470713245E9,1.524470720245E9 +2,4585644074316800636,627460548,1.524470746245E9,1.524470825245E9 +2,8762222841335602636,627460548,1.524470826245E9,1.524470850245E9 +2,-2262671936032733214,627460548,1.524470878245E9,1.524470880245E9 +2,3433822255857845541,627460548,1.524470888245E9,1.524471161245E9 +2,7559420633593932777,627460548,1.524471165245E9,1.524471358245E9 +2,6498776139947240734,627460548,1.524471390245E9,1.524471446245E9 +2,8486322444058506005,627460548,1.524471448245E9,1.524471461245E9 +13,4817799264842501958,479635942,1.521659509202E9,1.521659510102E9 +13,7147772458447570332,479635942,1.521659545352E9,1.521659546253E9 +13,-4932185226528181672,479635942,1.521660098205E9,1.521660100455E9 +13,-2997641485597601676,479635942,1.5216601363639998E9,1.521660141583E9 +13,402567769413879711,479635942,1.521660360853E9,1.521660468888E9 +13,-3411377884205898521,479635942,1.5216604747359998E9,1.5216605003569999E9 +13,8437075331488845659,479635942,1.5216605038679998E9,1.521660649636E9 +13,-3742205350777598555,479635942,1.521660656898E9,1.521660666138E9 +13,-4026177149477485256,479635942,1.521660682786E9,1.5216606934369998E9 +13,-6978322282385129884,479635942,1.5216606968569999E9,1.521660714287E9 +13,-6422077922835698539,479635942,1.521660754938E9,1.5216607617779999E9 +13,-4864554648212862312,479635942,1.5216607727259998E9,1.521660776596E9 +13,-6066192730293781880,479635942,1.521660796757E9,1.521660968027E9 +13,-6882182369509597035,479635942,1.521661029708E9,1.5216610305779998E9 +13,6136162275528816797,479635942,1.5216610350779998E9,1.521661035949E9 +13,5903075173780523616,479635942,1.521661058659E9,1.521661059468E9 +13,-1346495427689087586,479635942,1.521661061178E9,1.5216610630379999E9 +13,8717479127552464751,479635942,1.521661104438E9,1.521661108938E9 +13,-9074484878331569590,479635942,1.5216611098379998E9,1.5216611207879999E9 +13,1206920225367988278,479635942,1.521661135218E9,1.521661140768E9 +13,7078961414964755886,479635942,1.5216612035579998E9,1.521661204309E9 +13,6023006092693409346,479635942,1.521661315218E9,1.521661367898E9 +13,3245635549036903735,479635942,1.521661374228E9,1.521661383919E9 +13,8166491176997001250,479635942,1.521661394389E9,1.521661429878E9 +13,5128112370791334652,479635942,1.5216614728379998E9,1.521661704199E9 +13,8533662849561025432,479635942,1.521661713949E9,1.5216618244689999E9 +13,-7647637127377450343,479635942,1.5216618262089999E9,1.5216618329599998E9 +13,-8871088638699198633,479635942,1.5216618511999998E9,1.521661905199E9 +13,6282710171146024966,479635942,1.521661932109E9,1.521661956109E9 +13,925025361002840436,479635942,1.5216619958899999E9,1.521662005909E9 +13,809875666491140486,479635942,1.52166201761E9,1.521662179037E9 +13,-8272064035502067169,479635942,1.521662189777E9,1.521662191277E9 +13,1428235939705298331,479635942,1.521662201777E9,1.521662220678E9 +13,2770503645448460679,479635942,1.5216622576369998E9,1.5216622594369998E9 +13,8375653396621733908,479635942,1.521662280258E9,1.521662284158E9 +13,-58078453003816465,479635942,1.521662287247E9,1.521662305337E9 +13,3776595335092534106,479635942,1.521662582898E9,1.521662587429E9 +13,-7703579568060325019,479635942,1.52166263774E9,1.5216628424299998E9 +13,4496920418649241915,479635942,1.521662846931E9,1.52166298079E9 +13,-2698669699054422977,479635942,1.5216630069499998E9,1.52166311699E9 +13,-6860375553783689619,479635942,1.521663358852E9,1.521663364402E9 +13,4773233191951197115,479635942,1.521663366712E9,1.5216633674629998E9 +13,4133005825801737440,479635942,1.5216633697129998E9,1.5216633720519998E9 +13,-8174713008942603155,479635942,1.5216633909529998E9,1.5216633924819999E9 +13,-6009824077439626139,479635942,1.521663487523E9,1.521663488273E9 +13,-5975373578820469544,479635942,1.521663489113E9,1.5216634899229999E9 +13,-275726931500538122,479635942,1.5216634992519999E9,1.5216635010519998E9 +13,715807089942874627,479635942,1.5216635137419999E9,1.521663514642E9 +13,-4063175586583164999,479635942,1.5216636139429998E9,1.5216636157429998E9 +13,-5756669265794165985,479635942,1.521663616643E9,1.5216636184429998E9 +13,857151176166707288,479635942,1.521663619343E9,1.5216636238439999E9 +13,-4374425671241739461,479635942,1.521663632843E9,1.521663634643E9 +65,4719668373508124611,479635942,1.499761614769E9,1.499761615668E9 +65,-7169939569812516543,479635942,1.4997616771369998E9,1.4997616780089998E9 +65,-7273538344788941719,479635942,1.4997617179399998E9,1.4997617224399998E9 +65,5013659030936275641,479635942,1.499761745779E9,1.499761746679E9 +65,237303886416469529,479635942,1.4997617494099998E9,1.4997617503079998E9 +65,6055127308425681282,479635942,1.499761754839E9,1.499761755738E9 +65,-3934014457530951276,479635942,1.4997617864299998E9,1.4997617889789999E9 +65,-6712142563860992483,479635942,1.499761804612E9,1.4997618078509998E9 +65,7549865158803695691,479635942,1.499761809382E9,1.4997618154989998E9 +65,-4086833120984252772,479635942,1.499761850659E9,1.499761855189E9 +65,-5673687569610918985,479635942,1.49976185879E9,1.499761864189E9 +65,-4495511763572570555,479635942,1.499761869619E9,1.4997618714199998E9 +65,2338187898689186004,479635942,1.499761874179E9,1.499761875109E9 +65,7853596920018868921,479635942,1.49976267728E9,1.4997626782089999E9 +65,4385722441516789054,479635942,1.499762750083E9,1.499762750984E9 +65,-8633816536481225059,479635942,1.499762835916E9,1.499762836667E9 +65,4212563341699859262,479635942,1.499762844615E9,1.499762846415E9 +65,6015693465804936212,479635942,1.499762899905E9,1.499762901646E9 +65,-4344380978372460117,479635942,1.499762903325E9,1.4997629145449998E9 +65,-4902386205708669207,479635942,1.499762978266E9,1.4997629791959999E9 +65,8372012408235330481,479635942,1.499763347385E9,1.4997633481339998E9 +65,-5097910357593736211,479635942,1.499763542954E9,1.4997635438539999E9 +65,-6085413192047102426,479635942,1.499763582615E9,1.4997635834259999E9 +13,-3154544354092539908,627460548,1.521660277202E9,1.521660283202E9 +13,-6621488678396043684,627460548,1.521660297202E9,1.521660298202E9 +13,3953943173052118814,627460548,1.521660312202E9,1.521660332202E9 +13,5579633878529810246,627460548,1.521660352202E9,1.521660720202E9 +13,2700728227723515615,627460548,1.521660757202E9,1.521660972202E9 +13,-80149770694396452,627460548,1.521661252202E9,1.521661268202E9 +13,721999558888855777,627460548,1.521661306202E9,1.521661965202E9 +13,-1999473390221597630,627460548,1.521661990202E9,1.521662353202E9 +13,8157187037751996369,627460548,1.521662407202E9,1.521662436202E9 +13,-6997215574325460783,627460548,1.521662523202E9,1.521662591202E9 +13,-8690912574394479226,627460548,1.521662627202E9,1.521663135202E9 +65,4197275359830286825,627460548,1.499761891866E9,1.499761893866E9 +65,7571294578525124640,627460548,1.499761909866E9,1.499761926866E9 +5,8902958916463720654,479635942,1.4999294426920002E9,1.499929487813E9 +5,-7692040218201770403,479635942,1.4999295233930001E9,1.499929527172E9 +5,-3423026619375348253,479635942,1.499929624374E9,1.499929626774E9 +5,212846580323713450,479635942,1.499929647174E9,1.499929648254E9 +5,-1793682281292532537,479635942,1.4999299060110002E9,1.499930020494E9 +5,-3959351044829722336,479635942,1.4999300599720001E9,1.499930090273E9 +5,2647283471769003350,479635942,1.499930108752E9,1.499930112502E9 +5,-369599271055775771,479635942,1.4999301186230001E9,1.499930301349E9 +5,4205040294636417611,479635942,1.4999305141690001E9,1.499930515069E9 +5,4379746394465828628,479635942,1.499930555989E9,1.499930556829E9 +5,7747753480868146931,479635942,1.499930591301E9,1.499930592051E9 +5,6630432870107444104,479635942,1.499930757351E9,1.49993076008E9 +5,-6506652238997531020,479635942,1.499930770971E9,1.499930772741E9 +5,8307308487971482530,479635942,1.499930890522E9,1.499930891421E9 +5,-7411978661435902772,479635942,1.499930895053E9,1.499930896882E9 +5,8450771440901965979,479635942,1.499930971762E9,1.499930974342E9 +5,6015794412134086279,479635942,1.499930988083E9,1.499930989013E9 +5,-387730786331893414,479635942,1.499931037015E9,1.499931038757E9 +5,-1043688133102033435,479635942,1.499931134063E9,1.4999311367940001E9 +5,1096288484248534419,479635942,1.499931187343E9,1.499931188093E9 +5,7243772014294287411,479635942,1.499931198894E9,1.499931201983E9 +5,-4553159679081070131,479635942,1.4999312035440001E9,1.499931207594E9 +5,7692558223503993869,479635942,1.4999312196560001E9,1.499931220583E9 +5,-7993117733540396190,479635942,1.4999312342350001E9,1.4999312415540001E9 +5,-4860414650489795137,479635942,1.499931425418E9,1.4999314305180001E9 +5,-5068205994520341057,479635942,1.499931691371E9,1.49993170676E9 +5,5296398872499190013,479635942,1.4999317295E9,1.49993174582E9 +5,1647590290195022729,479635942,1.49993176208E9,1.49993176844E9 +5,4827762125733049996,479635942,1.49993178818E9,1.499931809181E9 +5,-898308553129896206,479635942,1.49993181077E9,1.499931853881E9 +5,1429825881893739035,479635942,1.4999318792910001E9,1.499931887361E9 +5,-8195681600547059099,479635942,1.4999319647020001E9,1.4999319764620001E9 +5,-1379040740224905692,479635942,1.499931991912E9,1.499932314707E9 +5,3695388058995681464,479635942,1.499932493538E9,1.499932508808E9 +5,-222983529212670781,479635942,1.499932509708E9,1.49993258708E9 +5,-4642433530398408947,479635942,1.499932598778E9,1.4999327054290001E9 +5,-5028526390189212073,479635942,1.4999327146100001E9,1.499932733595E9 +5,130533773238010555,479635942,1.499932737223E9,1.499932840874E9 +5,6394946711252475861,479635942,1.499932851734E9,1.499932877384E9 +5,6597089585081998261,479635942,1.499932891184E9,1.499932967144E9 +5,-3868322482483179853,479635942,1.499933007855E9,1.499933012355E9 +5,-7916319366091270320,479635942,1.499933023005E9,1.499933097046E9 +45,6266181406396054200,479635942,1.50235246699E9,1.50235246789E9 +45,7200835445480681161,479635942,1.50235251178E9,1.502352512619E9 +45,4571098392909437877,479635942,1.502352518831E9,1.50235252108E9 +45,135675698970736221,479635942,1.502352934902E9,1.502352936731E9 +45,-5577686260897590271,479635942,1.502352963848E9,1.502352964748E9 +45,8291955083287461620,479635942,1.502353260671E9,1.502353290281E9 +45,-2682605357669538559,479635942,1.502353302071E9,1.502353307471E9 +45,-7330090020582467320,479635942,1.502353457622E9,1.502353459422E9 +45,4981235174882312208,479635942,1.502353648538E9,1.502353649287E9 +45,1140921559780434240,479635942,1.502353665697E9,1.502353672958E9 +45,2100420905882065348,479635942,1.502354151427E9,1.502354152177E9 +45,-7278279068581340108,479635942,1.502354162947E9,1.502354167567E9 +45,-7012973211423787624,479635942,1.502354308806E9,1.502354311596E9 +45,5721192339350477135,479635942,1.502354374987E9,1.50235437634E9 +45,-2570074008800897080,479635942,1.502354430878E9,1.50235443439E9 +45,4215643679546429471,479635942,1.502354497749E9,1.50235449991E9 +45,925311042337340058,479635942,1.50235451881E9,1.502354541009E9 +45,-1587075061726872088,479635942,1.50235470511E9,1.502354708859E9 +45,8709899580297832521,479635942,1.502354714556E9,1.502354717106E9 +45,-1857290141244922299,479635942,1.502354826551E9,1.502354829188E9 +45,-4531323013506911281,479635942,1.502354906591E9,1.502354907728E9 +45,435295589608270350,479635942,1.502355016601E9,1.502355017952E9 +5,9034480364792910277,627460548,1.499929899381E9,1.499930317381E9 +5,3098729330709378421,627460548,1.499931296381E9,1.499931377381E9 +5,-3624193296538870209,627460548,1.499931674381E9,1.499931713381E9 +5,6466824894604173497,627460548,1.499931731381E9,1.499931865381E9 +5,-8571204180817890063,627460548,1.499931870381E9,1.499931896381E9 +5,7808981918047423877,627460548,1.499931907381E9,1.499931926381E9 +5,7809691020130174005,627460548,1.499931938381E9,1.499931946381E9 +5,-2988930365537617065,627460548,1.499931954381E9,1.499931982381E9 +5,-3322243079765283902,627460548,1.499932000381E9,1.499932324381E9 +5,8141979530607143028,627460548,1.499932406381E9,1.499932409381E9 +5,-6556680061057295706,627460548,1.499932451381E9,1.499932981381E9 +5,817601014755015407,627460548,1.499932982381E9,1.499933107381E9 +5,-5190891934745124218,627460548,1.499933112381E9,1.499933150381E9 +5,2685161687734480169,627460548,1.499933159381E9,1.499933178381E9 +45,536028785320424480,627460548,1.502352708421E9,1.502352770421E9 +45,8305880036004809305,627460548,1.502352849421E9,1.502352870421E9 +7,-1808444978587944336,479635942,1.499367547936E9,1.499367548775E9 +7,7566209710823937298,479635942,1.499367746717E9,1.4993677476160002E9 +7,-102897415790291370,479635942,1.499368004446E9,1.499368005345E9 +7,-8889937423372047749,479635942,1.499368032075E9,1.499368035555E9 +7,1312564108312011945,479635942,1.499368061115E9,1.499368062676E9 +7,-9053341801436345413,479635942,1.499368096756E9,1.4993680994550002E9 +7,6026559957067583640,479635942,1.4993681121160002E9,1.499368118476E9 +7,-6692539519040106197,479635942,1.499368144786E9,1.4993682083270001E9 +7,-6555487177726919675,479635942,1.499368216726E9,1.499368259176E9 +7,5015061172082147878,479635942,1.4993682763660002E9,1.499368292476E9 +7,-9141243021315057686,479635942,1.499368306036E9,1.4993683910870001E9 +7,1934331560186185199,479635942,1.4993684264580002E9,1.499368435877E9 +7,5106685400200901038,479635942,1.4993684847470002E9,1.499368487477E9 +7,-3637733582875247986,479635942,1.499368490177E9,1.499368492877E9 +7,-969012846056418675,479635942,1.499368580834E9,1.499368630005E9 +7,6102372668010452217,479635942,1.4993686354640002E9,1.499368694088E9 +7,2166567492366169776,479635942,1.499368768069E9,1.4993687691780002E9 +7,-1826984801005310387,479635942,1.4993692567110002E9,1.4993692612410002E9 +7,4258659392126689540,479635942,1.4993693675270002E9,1.499369406407E9 +7,-210196320991639744,479635942,1.4993694180470002E9,1.4993694730080001E9 +7,-8867247233755604431,479635942,1.499369480237E9,1.4993696713100002E9 +7,7946327702765303496,479635942,1.49936973593E9,1.499369739019E9 +7,2938349546860434379,479635942,1.4993697818890002E9,1.4993699703500001E9 +7,2007310469855512286,479635942,1.499370327676E9,1.499370338536E9 +7,4023301720356759627,479635942,1.499370344956E9,1.499370372917E9 +7,39148491874846965,479635942,1.499370380657E9,1.4993703853070002E9 +7,-7622894766729398540,479635942,1.4993703883070002E9,1.499370393707E9 +7,623124786120858274,479635942,1.499370409036E9,1.499370481727E9 +7,-1725003033276983277,479635942,1.4993707381650002E9,1.499370739065E9 +7,8423304772946172202,479635942,1.4993707504350002E9,1.499370835305E9 +7,7402190212066389388,479635942,1.4993708362050002E9,1.499370910426E9 +7,-1940219217588224784,479635942,1.499370916516E9,1.499370991216E9 +7,4543245080622306889,479635942,1.499371008342E9,1.499371062223E9 +7,-6240201281733892615,479635942,1.4993710768030002E9,1.499371243844E9 +7,-6645225289200000888,479635942,1.499371548105E9,1.4993715519160001E9 +7,-4931535407836327335,479635942,1.4993716262210002E9,1.4993716280210001E9 +7,7104521075700542246,479635942,1.499371834752E9,1.499371835502E9 +7,-7629915031770549489,479635942,1.499371879753E9,1.499371880653E9 +7,7723518591563702616,479635942,1.499371959223E9,1.4993719599740002E9 +7,-2773104361333438343,627460548,1.499367927538E9,1.499367940538E9 +7,4561401412736649794,627460548,1.499367952538E9,1.499367966538E9 +7,-9096136500403375882,627460548,1.499367979538E9,1.499367982538E9 +7,5025036125181142561,627460548,1.499368112538E9,1.499368452538E9 +7,-7040298260106543148,627460548,1.499368543538E9,1.499368553538E9 +7,1320492427382947647,627460548,1.499368569538E9,1.499368701538E9 +7,-3693230908287934432,627460548,1.499369360538E9,1.499369675538E9 +7,-4566464079515358922,627460548,1.499369687538E9,1.499369702538E9 +7,-572149208593503650,627460548,1.499369707538E9,1.499369715538E9 +7,7251964566713875602,627460548,1.499369719538E9,1.499369745538E9 +7,2893453764439219333,627460548,1.499369777538E9,1.499369982538E9 +7,9041852605052855848,627460548,1.499369987538E9,1.499370020538E9 +7,-3736564651179375684,627460548,1.499370051538E9,1.499370070538E9 +7,1783094021464126029,627460548,1.499370321538E9,1.499370489538E9 +7,-5767613228858276254,627460548,1.499370505538E9,1.499370524538E9 +7,8332374850927550445,627460548,1.499370738538E9,1.499370998538E9 +7,5231020659771374701,627460548,1.499371001538E9,1.499371264538E9 +41,-4677523405622048088,479635942,1.51988760536E9,1.51988760626E9 +41,-178695453151093496,479635942,1.5198876253E9,1.51988762629E9 +41,6127897547816621601,479635942,1.519887697543E9,1.519887698286E9 +41,8272017924394962172,479635942,1.5198877487749999E9,1.519887749648E9 +41,-3093168700136956182,479635942,1.519887752375E9,1.519887753245E9 +41,-3249193701604923720,479635942,1.519887789756E9,1.519887792065E9 +41,2573557067244083826,479635942,1.519887840665E9,1.519887841564E9 +41,-6661984088213940905,479635942,1.519887844323E9,1.519887846123E9 +41,-7720343372977107946,479635942,1.519887885034E9,1.5198878859029999E9 +41,-7440337806377233149,479635942,1.519887888514E9,1.519887890224E9 +41,5382125340492849363,479635942,1.5198879366629999E9,1.519887937623E9 +41,-1363181056850043860,479635942,1.519887961263E9,1.5198879621629999E9 +41,-7808272543654999306,479635942,1.519887964053E9,1.519887965853E9 +41,-8153572807600975955,479635942,1.519887969514E9,1.519887974102E9 +41,1588119377546676693,479635942,1.519887999483E9,1.519888000233E9 +41,788596363199245797,479635942,1.519888001733E9,1.519888004312E9 +41,8624612020489102104,479635942,1.519888006082E9,1.519888008725E9 +41,-5261895149861224231,479635942,1.519888010343E9,1.519888012925E9 +41,2439517011922788417,479635942,1.5198881569229999E9,1.519888158511E9 +41,-7986516893518690268,479635942,1.519888187281E9,1.519888190011E9 +41,5733419275909412287,479635942,1.5198882411299999E9,1.519888244491E9 +41,-2563130171662740927,479635942,1.5198882501929998E9,1.519888267533E9 +41,4394627724010566465,479635942,1.519888332514E9,1.519888369743E9 +41,-6518689595040719673,479635942,1.519888392514E9,1.519888521302E9 +41,7628191332091892683,479635942,1.519888547582E9,1.519888550282E9 +41,-1806250691208316253,479635942,1.519888552981E9,1.519888563541E9 +41,8946440547963602708,479635942,1.5198885975609999E9,1.519888669501E9 +41,-7657111425514477905,479635942,1.519888786411E9,1.5198887882389998E9 +41,-9204072247326138662,479635942,1.5198888121499999E9,1.519888812991E9 +41,4378113508704496282,479635942,1.519888854691E9,1.5198888555909998E9 +41,-4809677895435621791,479635942,1.519888886461E9,1.519888889221E9 +41,957919953322641549,479635942,1.51988908377E9,1.51988908557E9 +41,-1668987856019589076,479635942,1.519889921516E9,1.5198899224459999E9 +41,-3975455588039475088,479635942,1.519889933306E9,1.519889940477E9 +41,-74575833505549571,479635942,1.519889943026E9,1.5198899490579998E9 +41,7574304553408663397,479635942,1.519890086009E9,1.5198900875049999E9 +41,5380358890321647678,479635942,1.5198901051529999E9,1.519890106648E9 +41,509908886070930648,479635942,1.5198901811629999E9,1.519890182664E9 +41,2576910454007426840,479635942,1.519890183414E9,1.519890186654E9 +41,5982470602958907473,627460548,1.519888018198E9,1.519888079198E9 +41,8070345324659771998,627460548,1.519888108198E9,1.519888129198E9 +41,-1043996108886573113,627460548,1.519888241198E9,1.519888674198E9 +41,-2423033387007625924,627460548,1.519889144198E9,1.519889187198E9 +41,-4727060375464731483,627460548,1.519889205198E9,1.519889231198E9 +41,-6245524963796692913,627460548,1.519889253198E9,1.519889264198E9 +41,78802444463084265,627460548,1.519889275198E9,1.519889286198E9 +41,-7791822701797037495,627460548,1.519889307198E9,1.519889311198E9 +41,8764762745713966564,627460548,1.519889314198E9,1.519889437198E9 +41,-5675205819948262275,627460548,1.519889574198E9,1.519889622198E9 +41,-3654878556763806290,627460548,1.519889844198E9,1.519889847198E9 +41,-7786629805285420609,627460548,1.519889855198E9,1.519889869198E9 +72,7349084231938519861,479635942,1.5011450202689998E9,1.501145021138E9 +72,-3549146352338583379,479635942,1.5011451072389998E9,1.50114510904E9 +72,3837283931272050234,479635942,1.501145118099E9,1.5011451189989998E9 +72,-1839616692179130239,479635942,1.5011451500219998E9,1.50114515482E9 +72,-2715197297216098958,479635942,1.501145161813E9,1.5011451635219998E9 +72,8286645675138383523,479635942,1.501145169914E9,1.5011451706629999E9 +72,-6817751754951934533,479635942,1.5011452261299999E9,1.501145227931E9 +72,-4679699424704212085,479635942,1.501145230631E9,1.5011452315319998E9 +72,-2742896797415491301,479635942,1.50114526204E9,1.5011452655809999E9 +72,-6395694713084343142,479635942,1.501145273771E9,1.501145275271E9 +72,5368072267026914267,479635942,1.501145320151E9,1.501145326422E9 +72,-2126303297105770488,479635942,1.5011453291209998E9,1.501145335543E9 +72,5784208471437148692,479635942,1.501145342862E9,1.501145345592E9 +72,5823934772091517279,479635942,1.501145357501E9,1.501145362001E9 +72,-2469297329014265550,479635942,1.501145513138E9,1.501145518148E9 +72,6502904036972389269,479635942,1.50114555733E9,1.501145558079E9 +72,7214989092161437164,479635942,1.501145560418E9,1.501145562668E9 +72,-6326010689359857321,479635942,1.501145564168E9,1.5011455679789999E9 +72,-6178611041498736965,479635942,1.501145612319E9,1.5011456132189999E9 +72,-324060818884686191,479635942,1.501145617809E9,1.501145618711E9 +72,977063501159212129,479635942,1.501145670672E9,1.5011456714789999E9 +72,-8316004670462761355,479635942,1.501145676879E9,1.501145679191E9 +72,-8654916052714027138,479635942,1.5011456824299998E9,1.5011456831799998E9 +72,-3348750295688071206,479635942,1.501145687079E9,1.50114568981E9 +72,3595361889405729230,479635942,1.501145726412E9,1.50114572731E9 +72,3323330250236163804,479635942,1.501145870563E9,1.501145871402E9 +72,7793321357906836428,479635942,1.501145900623E9,1.501145901461E9 +63,-387868821401755592,479635942,1.521534681523E9,1.521534691693E9 +63,2307824192423988307,479635942,1.521534708793E9,1.521534710293E9 +63,-1080721506877563135,479635942,1.521534720072E9,1.521534720822E9 +63,1877791111076666096,479635942,1.5215347951920002E9,1.521534796002E9 +63,-1088656736121464004,479635942,1.521534797592E9,1.521534798463E9 +63,-5882842860394767340,479635942,1.5215348017020001E9,1.521534802482E9 +63,1161124801694752899,479635942,1.521534834431E9,1.521534835181E9 +63,2854158283434250227,479635942,1.521534848891E9,1.521534850691E9 +63,5860587540692531972,479635942,1.52153489665E9,1.52153489755E9 +63,1432305960810305325,479635942,1.5215349001890001E9,1.52153491141E9 +63,-2209237361749169686,479635942,1.521534915309E9,1.5215349176200001E9 +63,3840183682456072919,479635942,1.5215349198700001E9,1.5215349206200001E9 +63,-8334207964566990846,479635942,1.5215352797480001E9,1.5215352805040002E9 +63,-1356390637588517158,479635942,1.5215352828370001E9,1.521535283589E9 +63,6439699972430138574,479635942,1.521535345176E9,1.5215353460770001E9 +63,-7083250554452252151,479635942,1.521535348777E9,1.521535349707E9 +63,1371665037533329470,479635942,1.521535423029E9,1.521535423926E9 +63,-622020087765505622,479635942,1.521535482006E9,1.521535482816E9 +63,4479076988115521000,479635942,1.521535574375E9,1.521535575275E9 +64,-7708674167454275526,479635942,1.499330430144E9,1.499330431944E9 +64,-56539365439692692,479635942,1.499330441874E9,1.499330442775E9 +64,-2216180734663872570,479635942,1.4993305039160001E9,1.4993305047250001E9 +64,332371965965655320,479635942,1.499330535237E9,1.499330536015E9 +64,-8518325267389109714,479635942,1.4993306083760002E9,1.499330609247E9 +64,2953638335034800189,479635942,1.499330646236E9,1.4993306492970002E9 +64,-6806905253670888201,479635942,1.499330659016E9,1.4993306599160001E9 +64,2969987012616826320,479635942,1.499330685266E9,1.4993306888660002E9 +64,6864806943596030189,479635942,1.499330714907E9,1.4993307158070002E9 +64,3368038195188030217,479635942,1.4993307373760002E9,1.499330740407E9 +64,4971043108251322373,479635942,1.4993307419680002E9,1.4993307442180002E9 +64,-38472601267021523,479635942,1.499330840964E9,1.4993308437240002E9 +64,6644949758242835283,479635942,1.4993309151850002E9,1.499330916115E9 +64,2226283497680774849,479635942,1.499330918815E9,1.4993309197150002E9 +64,-5561867658391240094,479635942,1.4993310321560001E9,1.499331033775E9 +64,-6396572371930544009,479635942,1.499331035575E9,1.499331036505E9 +64,3308990702071930347,479635942,1.4993310960860002E9,1.499331096986E9 +64,-7436936749515992944,479635942,1.499331098697E9,1.499331102986E9 +64,-948422790415422742,627460548,1.499330778298E9,1.499330791298E9 +66,-7722668711470158764,479635942,1.519719607197E9,1.5197196102580001E9 +66,-7784765305257264729,479635942,1.5197196604480002E9,1.5197196631460001E9 +66,3200463398401226877,479635942,1.519719671367E9,1.519719672299E9 +66,-6228087251117047021,479635942,1.5197197349970002E9,1.519719735926E9 +66,4245331785510372279,479635942,1.519719768506E9,1.519719773967E9 +66,8726810908537607247,479635942,1.5197197874970002E9,1.5197197883660002E9 +66,-8304742241430141584,479635942,1.5197197927770002E9,1.519719793648E9 +66,7252332276579439706,479635942,1.519719827786E9,1.519719828716E9 +66,3727139577522669009,479635942,1.519719830516E9,1.5197198323760002E9 +66,8158661245170802947,479635942,1.519719833276E9,1.519719838706E9 +66,-3554608247727530664,479635942,1.519719844167E9,1.519719845066E9 +66,2445160304740286163,479635942,1.5197198541550002E9,1.519719855056E9 +66,8449821975889812097,479635942,1.519719857845E9,1.519719859677E9 +66,1685217310746362649,479635942,1.5197198687050002E9,1.5197198723660002E9 +66,8658326976840453268,479635942,1.519720050421E9,1.519720051167E9 +66,6615015572829396137,479635942,1.519720053509E9,1.519720055009E9 +66,-6328876798551859687,479635942,1.519720161686E9,1.5197201625270002E9 +66,3692635464107712470,479635942,1.519720236505E9,1.5197202374060001E9 +66,552857259367872954,479635942,1.519720242865E9,1.519720243765E9 +66,-8533238888322276948,479635942,1.5197204596450002E9,1.5197204605760002E9 +66,-7720810768648913084,479635942,1.5197208185040002E9,1.519720819344E9 +66,-8134504750248642783,479635942,1.5197208621230001E9,1.5197208630540001E9 +66,-3253883043109902158,627460548,1.519719809367E9,1.519719821367E9 +1,2011702610517061746,479635942,1.5223420552389998E9,1.5223420561399999E9 +1,8052312756486327108,479635942,1.5223420850289998E9,1.5223420859289997E9 +1,-7724838520056834887,479635942,1.5223420948699996E9,1.5223420957689998E9 +1,-8703191068718194937,479635942,1.5223421153289998E9,1.5223421162299998E9 +1,-7612356545069565773,479635942,1.5223422415089998E9,1.5223422423799996E9 +1,-4801122709854909103,479635942,1.5223422558789997E9,1.5223422567489998E9 +1,-3861814096722306717,479635942,1.5223426537969997E9,1.5223426547269998E9 +1,4534398901693039288,479635942,1.5223426838569996E9,1.5223426848169997E9 +1,4782429586747185523,479635942,1.5223427622479997E9,1.5223427637479997E9 +1,-1665583644534407272,479635942,1.5223430351269999E9,1.5223430360269997E9 +1,-4672661020375272926,479635942,1.5223430414569998E9,1.5223430423569996E9 +1,788012111625654361,479635942,1.5223431455559998E9,1.5223431465759997E9 +1,-5263746901235689052,479635942,1.5223431776869998E9,1.5223431785869997E9 +1,3983974241423904455,479635942,1.5223432161459997E9,1.5223432168969998E9 +1,-4191943150157127094,479635942,1.5223432829259996E9,1.5223432838259997E9 +1,5384525810414840800,479635942,1.5223432856569998E9,1.5223432865559998E9 +1,996506958026982933,479635942,1.5223434435159998E9,1.5223434443869998E9 +1,9058670877734439452,479635942,1.5223441430089998E9,1.5223441442699997E9 +1,-2188653196871002330,479635942,1.5223443272679996E9,1.5223443282899997E9 +1,1396086404134580402,479635942,1.5223445999379997E9,1.5223446017379997E9 +1,-7580848256663138879,479635942,1.5223446404379997E9,1.5223446412779996E9 +1,-2408265882598384355,479635942,1.5223446429579997E9,1.5223446438279998E9 +1,-2518356896661729862,479635942,1.5223447079679997E9,1.5223447088679998E9 +1,-976904041326757485,479635942,1.5223447648489997E9,1.5223447666179998E9 +1,-6047289796479193406,479635942,1.5223448661579998E9,1.5223448722479997E9 +1,-6050026585726393484,479635942,1.5223448757879999E9,1.5223448828979998E9 +1,-1364325958713954078,479635942,1.5223449163489997E9,1.5223449211169999E9 +1,-3518661501516058856,479635942,1.5223449218989997E9,1.5223449242369998E9 +1,406509367928660936,479635942,1.5223449462289999E9,1.5223449516279998E9 +1,-788463548014622857,479635942,1.5223450665899997E9,1.5223450719909997E9 +1,-5063114389807714829,479635942,1.5223450728899999E9,1.5223450783199997E9 +1,-6587471117942044255,479635942,1.5223450864199998E9,1.5223451135109997E9 +1,-7643593366575074942,479635942,1.5223451386199996E9,1.5223451604599998E9 +1,-6656698791426814590,479635942,1.5223451636099997E9,1.5223453503599997E9 +1,1189018417848857299,479635942,1.5223453664119997E9,1.5223454184009998E9 +1,5907070023440599095,479635942,1.5223454265299997E9,1.5223455010509996E9 +1,-178188395778287975,479635942,1.5223456265109997E9,1.5223456272609997E9 +1,4391583515375772602,479635942,1.5223460073919997E9,1.5223460100919998E9 +1,2029013473896337574,479635942,1.5223460309129999E9,1.5223460336119998E9 +1,3494940479816335368,479635942,1.5223461898819997E9,1.5223461906929998E9 +1,-2474217368813907887,479635942,1.5223461953429997E9,1.5223461983429997E9 +1,5035097814576773127,479635942,1.5223462447219996E9,1.5223462456219997E9 +1,-4602546580498750229,479635942,1.5223462555829997E9,1.5223462582819998E9 +1,-8065544650026521852,479635942,1.5223463559919999E9,1.5223463568929996E9 +1,4612658976108604600,479635942,1.5223463640919998E9,1.5223463650219998E9 +1,-793232335097168089,479635942,1.5223469105309997E9,1.5223469116109998E9 +1,7814650910290370884,479635942,1.5223470608669999E9,1.5223470637769997E9 +1,8523221661415107386,479635942,1.5223471431559997E9,1.5223471457369998E9 +1,-6124619826740612524,479635942,1.5223471502659998E9,1.5223471529979997E9 +1,442426065745810156,479635942,1.5223471583369997E9,1.5223471608569996E9 +1,507497286636504211,479635942,1.5223471816179998E9,1.5223471823679998E9 +1,1282036458393761530,479635942,1.5223471831179998E9,1.5223471861179998E9 +1,3045984571673582708,479635942,1.5223472529529998E9,1.5223472842729998E9 +1,-9124668695529772826,479635942,1.5223473116329997E9,1.5223473134329998E9 +1,-1984102843527604709,479635942,1.5223473601729999E9,1.5223475315629997E9 +1,5267385840744473615,479635942,1.5223475495939999E9,1.5223475594639997E9 +1,-4181015596023858505,479635942,1.5223475994539998E9,1.5223476754439998E9 +1,1256497883890158481,479635942,1.5223477562049997E9,1.5223477589049997E9 +1,3044774076748321148,479635942,1.5223477859849997E9,1.5223477897639997E9 +1,-1602804958307063948,479635942,1.5223477983149998E9,1.5223478001749997E9 +1,5268082680764618884,479635942,1.5223479262069998E9,1.5223479282449996E9 +1,6682364632461235758,479635942,1.5223479427959998E9,1.5223479445969996E9 +1,7441181468021537861,479635942,1.5223479545259998E9,1.5223479555149999E9 +1,3639357921960080647,479635942,1.5223480425459998E9,1.5223480458769999E9 +1,-4689540127585079298,479635942,1.5223480528969998E9,1.5223480678059998E9 +1,-8619594752486031033,479635942,1.5223486840539997E9,1.5223486933839998E9 +1,-5629077619490409960,479635942,1.5223487145049996E9,1.5223487305849998E9 +1,-5483390653874302473,479635942,1.5223487389249997E9,1.5223487507149997E9 +1,-7886269455361735245,479635942,1.5223487737259998E9,1.5223487767259998E9 +1,3660333314119397104,479635942,1.5223487862949998E9,1.5223487935849998E9 +1,-7451784041563779563,479635942,1.5223489000859997E9,1.5223489812079997E9 +1,-6862727367400738824,479635942,1.5223489902369998E9,1.5223491436569998E9 +1,5781003997132495666,479635942,1.5223491499869998E9,1.5223491607569997E9 +1,9051425867701705569,479635942,1.5223491831369998E9,1.5223492075279996E9 +1,7311614263889312027,479635942,1.5223494101199996E9,1.5223494157589998E9 +1,-8130963386133965028,479635942,1.5223495064189997E9,1.5223495072899997E9 +1,5486362317664265069,479635942,1.5223495189899998E9,1.5223495212399998E9 +1,6068915525844548076,479635942,1.5223495575689998E9,1.5223495593689997E9 +1,4301749932214998094,479635942,1.5223496488899999E9,1.5223496511399999E9 +1,352885825063062633,479635942,1.5223496541399999E9,1.5223496563909998E9 +1,972550385184240571,479635942,1.5223496737899997E9,1.5223496755899997E9 +1,3287700133110600200,479635942,1.5223496827909997E9,1.5223496863899999E9 +1,-3778493555693101334,479635942,1.5223496917899997E9,1.5223496944899998E9 +1,4803809497308140556,479635942,1.5223497316899998E9,1.5223497342099998E9 +1,-6068290437933413614,479635942,1.5223498018609998E9,1.5223498036609998E9 +1,-5647945425913720358,479635942,1.5223498072609997E9,1.5223498081609998E9 +1,2391299093138852528,479635942,1.5223498421809998E9,1.5223498429609997E9 +15,5674514831207226738,479635942,1.5010548616969998E9,1.5010548624469998E9 +15,8526628640297578421,479635942,1.5010548812259998E9,1.501054882126E9 +15,-3221350351955076309,479635942,1.5010549694559999E9,1.5010549702059999E9 +15,7979388045735057634,479635942,1.501054972517E9,1.501054974018E9 +15,-1148147588669840887,479635942,1.501055002097E9,1.501055003597E9 +15,-1087504450246344602,479635942,1.501055071247E9,1.5010550721469998E9 +15,6380196404669273142,479635942,1.501055075748E9,1.501055077518E9 +15,4447359542693422444,479635942,1.501055096418E9,1.5010550971669998E9 +15,8226393635034838095,479635942,1.501055103408E9,1.5010551057189999E9 +15,2199135515111423810,479635942,1.5010551310679998E9,1.501055131968E9 +15,-6701777319647864697,479635942,1.501055133768E9,1.501055135629E9 +15,-229050406819685918,479635942,1.5010551365279999E9,1.501055137428E9 +15,-3535700018836067696,479635942,1.5010551518579998E9,1.5010551527589998E9 +15,6496399619893984357,479635942,1.50105515456E9,1.5010551563879998E9 +15,305962883988710371,479635942,1.501055158188E9,1.501055159988E9 +15,-2838698212771148377,479635942,1.5010551968279998E9,1.5010551985979998E9 +15,7289599539836723674,479635942,1.501055201178E9,1.501055202918E9 +15,3773360981308185894,479635942,1.5010552344799998E9,1.5010552353189998E9 +15,5380653810171671020,479635942,1.501055240629E9,1.501055242189E9 +15,-1915559223150357543,479635942,1.5010552452789998E9,1.50105524678E9 +15,-3315701241537856866,479635942,1.501055492836E9,1.501055498175E9 +15,3559031427999251940,479635942,1.5010555105059998E9,1.501055515905E9 +15,-4101595302292380662,479635942,1.5010555195059998E9,1.501055583796E9 +15,-205186731877998722,479635942,1.5010556054859998E9,1.501055614636E9 +15,-4198471446518016008,479635942,1.501055645626E9,1.5010556917359998E9 +15,519371735146566519,479635942,1.501055698066E9,1.501055719067E9 +15,1980695642775245625,479635942,1.501055731908E9,1.5010557450519998E9 +15,-4622136540444779429,479635942,1.501055831929E9,1.501055833518E9 +15,4918945119565506828,479635942,1.501055834267E9,1.5010558395779998E9 +15,-1236905120090552224,479635942,1.5010559877749999E9,1.5010559885249999E9 +15,7876309239950267596,479635942,1.5010561760849998E9,1.501056176985E9 +15,91548746927325249,479635942,1.501056264435E9,1.5010562689959998E9 +15,-5242650524055454950,479635942,1.5010564831969998E9,1.5010564846969998E9 +15,-5230930335982649349,479635942,1.501056622771E9,1.501056623511E9 +15,5330014354181555602,479635942,1.5010571064439998E9,1.5010571071929998E9 +15,-2411722358562652780,479635942,1.5010572754899998E9,1.5010572772889998E9 +15,-1904021872399996187,479635942,1.50105729181E9,1.5010572927099998E9 +15,-1286669139893299188,479635942,1.5010576310549998E9,1.501057633452E9 +15,682353131493516386,479635942,1.5010577048179998E9,1.5010577066179998E9 +15,3372857720372184710,479635942,1.5010578099109998E9,1.50105781084E9 +15,5554832599010177850,479635942,1.5010578324399998E9,1.501057835889E9 +15,-533249632036496060,479635942,1.5010582746069999E9,1.5010582791379998E9 +15,-6352278968384264393,479635942,1.5010583226399999E9,1.501058324408E9 +15,-6695557396181775501,479635942,1.50105836776E9,1.5010583785589998E9 +15,2518132122871798975,479635942,1.5010584027389998E9,1.501058406339E9 +15,7805968684218304970,479635942,1.5010584502559998E9,1.5010584511269999E9 +15,-7410036492877817202,479635942,1.5010584520279999E9,1.501058455478E9 +15,-3692940415117112549,479635942,1.501058469099E9,1.5010584699669998E9 +15,9002585348644348226,479635942,1.5010584743189998E9,1.5010584759069998E9 +15,-7097406725363016431,479635942,1.501058497958E9,1.501058498797E9 +15,7724194224988043391,479635942,1.501058516797E9,1.5010585186279998E9 +15,7818753916548148637,479635942,1.5010585250159998E9,1.5010585259169998E9 +15,3125702859676108468,479635942,1.501058534087E9,1.501058539537E9 +15,-5481357432299743806,479635942,1.501058541337E9,1.501058543143E9 +15,-2092580797704714807,479635942,1.5010585485359998E9,1.501058550337E9 +15,-4034078988507495331,479635942,1.5010585692069998E9,1.5010585701069999E9 +15,7908448017415971211,479635942,1.501058572807E9,1.5010585753869998E9 +15,-5802439914802332250,479635942,1.501058585257E9,1.5010585861569998E9 +15,3985068091752069696,479635942,1.5010587099959998E9,1.5010587117359998E9 +15,7011441579650767157,479635942,1.5010587133869998E9,1.5010587167759998E9 +15,-7462698104047495543,479635942,1.501058756527E9,1.5010587574559999E9 +77,1207667927464859018,479635942,1.501530873715E9,1.501530874705E9 +77,-6434891464697196824,479635942,1.501531097579E9,1.501531098328E9 +77,1496426376066523478,479635942,1.501531145669E9,1.50153114657E9 +77,2500279291343453256,479635942,1.501531155628E9,1.501531156528E9 +77,8322933800296372175,479635942,1.501531183302E9,1.501531184112E9 +77,-4071385490699071698,479635942,1.501531294419E9,1.50153129517E9 +77,-4080601915773239018,479635942,1.501531305009E9,1.501531306508E9 +77,-5200824067668337378,479635942,1.501531314009E9,1.501531315509E9 +77,2247317944957634122,479635942,1.501531380399E9,1.5015313813E9 +77,8048481326882221158,479635942,1.501531498064E9,1.5015315059E9 +77,-554676247354202311,479635942,1.501531537165E9,1.501531540882E9 +77,-851786572561386675,479635942,1.501531610015E9,1.501531611996E9 +77,8734079615436554953,479635942,1.501531615707E9,1.501531617604E9 +77,4169351128071739799,479635942,1.501531665869E9,1.5015316678470001E9 +77,5911136307152762713,479635942,1.501531746715E9,1.501531748749E9 +77,-3565420134326535136,479635942,1.501531789944E9,1.501531792088E9 +77,-4423152424965677804,479635942,1.501531804491E9,1.501531807544E9 +77,-1156874418084337014,479635942,1.501531821184E9,1.501531823828E9 +1,8724902421903630867,627460548,1.5223449201579998E9,1.5223449881579998E9 +1,-7133278293675844760,627460548,1.5223450201579998E9,1.5223450471579998E9 +1,-7032243117222112801,627460548,1.5223450741579998E9,1.5223451211579998E9 +1,502018113555828644,627460548,1.5223451271579998E9,1.5223455041579998E9 +1,-8136262909607767,627460548,1.5223471591579998E9,1.5223471751579998E9 +1,4206817155372211865,627460548,1.5223472051579998E9,1.5223472171579998E9 +1,-5917649767893816599,627460548,1.5223472231579998E9,1.5223473521579998E9 +1,6856560965358601071,627460548,1.5223473551579998E9,1.5223475781579998E9 +1,2579696317140897087,627460548,1.5223475851579998E9,1.5223476931579998E9 +1,-1711969672346902692,627460548,1.5223477001579998E9,1.5223477671579998E9 +1,2943293789736059765,627460548,1.5223479551579998E9,1.5223479571579998E9 +1,53407197793942360,627460548,1.5223479631579998E9,1.5223479831579998E9 +1,2422378131931299845,627460548,1.5223481071579998E9,1.5223481311579998E9 +1,-8479939479382248179,627460548,1.5223482991579998E9,1.5223483071579998E9 +1,-6859386369307958961,627460548,1.5223483361579998E9,1.5223483401579998E9 +1,3096654080314498211,627460548,1.5223483841579998E9,1.5223483931579998E9 +1,-2216710971175188305,627460548,1.5223484931579998E9,1.5223485271579998E9 +1,2674867824878445166,627460548,1.5223486361579998E9,1.5223488041579998E9 +1,-3383072363441494449,627460548,1.5223488111579998E9,1.5223492321579998E9 +15,4184610140458662981,627460548,1.50105551187E9,1.50105559187E9 +15,3799577609150081280,627460548,1.50105559787E9,1.50105575587E9 +15,1069756466712498702,627460548,1.50105729287E9,1.50105741687E9 +15,4728997244984685431,627460548,1.50105742287E9,1.50105749487E9 +15,2829432426447922465,627460548,1.50105825787E9,1.50105828687E9 +15,3755894065056053234,627460548,1.50105829687E9,1.50105831487E9 +3,1514957805362266958,479635942,1.5012635685469997E9,1.5012635692959998E9 +3,7999461460725244078,479635942,1.5012636699199998E9,1.5012636707569997E9 +3,2462932405019587011,479635942,1.5012637193869998E9,1.5012637202879999E9 +3,-4336080123780192837,479635942,1.5012640550899997E9,1.5012640558369997E9 +3,-8071297743325020025,479635942,1.5012641640169997E9,1.5012641727479997E9 +3,7234574314960815222,479635942,1.5012642019379997E9,1.5012642026899998E9 +3,4541044529897639889,479635942,1.5012643153969998E9,1.5012643300369997E9 +3,7751222854500301110,479635942,1.5012644160799997E9,1.5012644169749997E9 +3,-4758976890291903048,479635942,1.5012644468549998E9,1.5012644936249998E9 +3,-6596748138873331727,479635942,1.5012645099149997E9,1.5012645843159997E9 +3,-6599927583317810016,479635942,1.5012646136249998E9,1.5012646145249999E9 +3,-5985188656802829103,479635942,1.5012646253249998E9,1.5012646262249997E9 +3,-6751922704480618538,479635942,1.5012647199439998E9,1.5012647227759998E9 +3,-1178019866244300013,479635942,1.5012648014809997E9,1.5012648033789997E9 +3,-1401126923522355579,479635942,1.5012648195489998E9,1.5012648217759998E9 +3,-1391882352226554566,479635942,1.5012652878039997E9,1.5012652885539997E9 +3,-6123491999176752515,479635942,1.5012653279429998E9,1.5012653295029998E9 +3,7670246949588577375,479635942,1.5012654261649997E9,1.5012654269149997E9 +3,-1291641994180930968,479635942,1.5012655228199997E9,1.5012655245589998E9 +3,-6246942355577032357,479635942,1.5012655678179998E9,1.5012655713879998E9 +3,-9053224774303788837,479635942,1.5012655722879999E9,1.5012655749879997E9 +3,9055867540294919037,479635942,1.5012655803879998E9,1.5012655822179997E9 +3,815741452890975614,479635942,1.5012655876479998E9,1.5012655903479998E9 +3,3539884616619483446,479635942,1.5012656227479997E9,1.5012657068389997E9 +3,-4918051360885637254,479635942,1.5012657123289998E9,1.5012657714899998E9 +3,-1650095159834422017,479635942,1.5012659234419997E9,1.5012659249999998E9 +3,2692551895520284500,479635942,1.5012659334299998E9,1.5012659357429998E9 +3,2106021580738181621,479635942,1.5012660267029998E9,1.5012660275719998E9 +3,7840673105508985180,479635942,1.5012660789269998E9,1.5012660796769998E9 +3,-2711556646462993225,479635942,1.5012664331089997E9,1.5012664339199998E9 +3,2200403446136681589,479635942,1.5012664517999997E9,1.5012664526109998E9 +3,937363912532196641,479635942,1.5012665519699998E9,1.5012665536819997E9 +3,-6019166495123879963,479635942,1.5012665763009996E9,1.5012665857219996E9 +3,-2843575162192371936,479635942,1.5012666366009998E9,1.5012666384009998E9 +3,4567288904308494367,479635942,1.5012666574509997E9,1.5012667213519998E9 +3,6464089092096721732,479635942,1.5012667527019997E9,1.5012667656319997E9 +3,8265153762712079831,479635942,1.5012667854619997E9,1.5012668892329998E9 +3,-8841473587459913087,479635942,1.5012670650429997E9,1.5012670706509998E9 +3,-1055536280048435105,479635942,1.5012670726309998E9,1.5012671600539997E9 +3,268473589104666821,479635942,1.5012671617579997E9,1.5012671767459998E9 +3,-3611918485320426074,479635942,1.5012672313889997E9,1.5012672797619998E9 +3,5613256615659636122,479635942,1.5012673297859998E9,1.5012673306089997E9 +3,1053943597106945424,479635942,1.5012680455539997E9,1.5012681595649998E9 +3,2745110061494033838,479635942,1.5012681938299997E9,1.5012682231179998E9 +3,-4759210778525383191,479635942,1.5012682288659997E9,1.5012684924809997E9 +3,-5215474932563982068,479635942,1.5012688863079998E9,1.5012688880179996E9 +3,8185311292586694395,479635942,1.5012688896889997E9,1.5012688930439997E9 +3,2925109149439138408,479635942,1.5012688938969998E9,1.5012688947219996E9 +3,-8676821592753044433,479635942,1.5012689404279997E9,1.5012689420769997E9 +3,-8481056729004665640,479635942,1.5012689438099997E9,1.5012689454879997E9 +3,-652663400770816662,479635942,1.5012689754079998E9,1.5012689762329998E9 +3,-6146173594742349990,479635942,1.5012690318659997E9,1.5012690327459998E9 +3,5327062569814547095,479635942,1.5012690402529998E9,1.5012690419029999E9 +3,5771726674692863617,479635942,1.5012690510339997E9,1.5012690526839998E9 +3,-4066089606644232973,479635942,1.5012691918049998E9,1.5012691934819999E9 +3,5090121014098658421,479635942,1.5012691984869998E9,1.5012691993119998E9 +3,-3428976067225053815,479635942,1.5012692168289998E9,1.5012692176539998E9 +3,4964843974808640673,479635942,1.5012692426249998E9,1.5012692443019998E9 +3,-1635467916613769432,479635942,1.5012693958559997E9,1.5012693967079997E9 +3,2477207882402308793,479635942,1.5012695648169997E9,1.5012695656419997E9 +3,-2404963927861004326,479635942,1.5012695681159997E9,1.5012695697669997E9 +3,3132853260534630014,479635942,1.5012695989409997E9,1.5012695997379997E9 +3,8520236112298881690,479635942,1.5012696923329997E9,1.5012696931839998E9 +37,-2259015754605110091,479635942,1.520406393157E9,1.520406394146E9 +37,6764542643902577672,479635942,1.520406406056E9,1.520406406987E9 +37,6657848334227690407,479635942,1.520406499548E9,1.520406500448E9 +37,-6408271416809763810,479635942,1.520406536207E9,1.520406536988E9 +37,5368626669846744881,479635942,1.520406539389E9,1.520406540978E9 +37,-6699428095899409690,479635942,1.520406593627E9,1.520406594527E9 +37,-4683897012566044374,479635942,1.520406596327E9,1.520406598157E9 +37,6302426830809562176,479635942,1.520406633407E9,1.520406634308E9 +37,-4147738867631198535,479635942,1.520406637937E9,1.520406639737E9 +37,-3421817422779313004,479635942,1.520406688338E9,1.520406689088E9 +37,-4513305667404281142,479635942,1.520406713867E9,1.520406714767E9 +37,-8440336818753734388,479635942,1.520406815558E9,1.520406816787E9 +37,308337843659633171,479635942,1.520406821767E9,1.520406824167E9 +37,5447834893035142644,479635942,1.520406849307E9,1.5204068505970001E9 +37,1726483753099582267,479635942,1.520406859777E9,1.520406862416E9 +37,-1749444934080090260,479635942,1.520406875106E9,1.520406878705E9 +37,-7853350339498812881,479635942,1.520406892085E9,1.520406896735E9 +37,7548293822285268972,479635942,1.520407127164E9,1.520407433254E9 +37,2433673396311040339,479635942,1.520407435954E9,1.520407479844E9 +37,-488221232316041521,479635942,1.520407527874E9,1.520407532464E9 +37,298723057501563137,479635942,1.520407865344E9,1.520407869873E9 +37,8860475477743996862,479635942,1.520407888803E9,1.520407891564E9 +37,7066039219720110445,479635942,1.520407968363E9,1.520407969263E9 +37,2549704497553875761,479635942,1.520408081264E9,1.520408082852E9 +37,-2051859332148730936,479635942,1.520408085914E9,1.520408087503E9 +37,5369472328529967685,479635942,1.520408258502E9,1.520408263843E9 +37,3009315165343892103,479635942,1.520408306143E9,1.520408306893E9 +37,742275709871709128,479635942,1.520408546864E9,1.520408628283E9 +37,-5266893512751160249,479635942,1.520408642773E9,1.520408677123E9 +37,1444672617222438358,479635942,1.520408684323E9,1.520408686122E9 +37,-7537824954834727440,479635942,1.520408967374E9,1.520408992994E9 +37,5345458963708941164,479635942,1.520409047294E9,1.520409057764E9 +37,4450078221822330443,479635942,1.520409146624E9,1.5204091493240001E9 +37,-9206478895523538727,479635942,1.520409180794E9,1.520409182534E9 +37,-1475139408965160187,479635942,1.520409184004E9,1.520409184754E9 +37,-5001335555076417755,479635942,1.520409206834E9,1.520409209834E9 +37,1676839279775904427,479635942,1.520409212083E9,1.520409215084E9 +37,9050282813543740529,479635942,1.520409227715E9,1.520409229514E9 +37,-7689783217235717284,479635942,1.520409249314E9,1.520409252014E9 +37,-7908175636611681680,479635942,1.520409259244E9,1.520409260145E9 +37,-8742079032283345525,479635942,1.520409266504E9,1.520409267404E9 +37,6129631311525069083,479635942,1.520409278774E9,1.520409283725E9 +37,-4080934468868162961,479635942,1.520409344339E9,1.520409345269E9 +37,114868866606611121,479635942,1.520409360299E9,1.52040936102E9 +3,2086326190347903593,627460548,1.5012643006019998E9,1.5012643456019998E9 +3,-3116221803458301946,627460548,1.5012643516019998E9,1.5012643966019998E9 +3,-6309253449589861540,627460548,1.5012644416019998E9,1.5012645906019998E9 +3,7847460242594550885,627460548,1.5012656246019998E9,1.5012657746019998E9 +3,-6676479350840676759,627460548,1.5012666376019998E9,1.5012669436019998E9 +3,-5773039151576390786,627460548,1.5012669816019998E9,1.5012669916019998E9 +3,7049421528852272708,627460548,1.5012669936019998E9,1.5012670076019998E9 +3,-8221261718612649608,627460548,1.5012670126019998E9,1.5012670176019998E9 +3,1712593096930041612,627460548,1.5012670186019998E9,1.5012670416019998E9 +3,-3594666096670506324,627460548,1.5012670476019998E9,1.5012673216019998E9 +3,4868339307241172958,627460548,1.5012676446019998E9,1.5012678206019998E9 +3,5064247092816624760,627460548,1.5012678296019998E9,1.5012678526019998E9 +3,-5696498078713153790,627460548,1.5012680366019998E9,1.5012681806019998E9 +3,778189447883207514,627460548,1.5012681876019998E9,1.5012685366019998E9 +37,-6028233170004681707,627460548,1.520407119296E9,1.520407499296E9 +37,-903639349529085439,627460548,1.520408306296E9,1.520408308296E9 +37,2578219410578628435,627460548,1.520408326296E9,1.520408439296E9 +37,-3445779213865074308,627460548,1.520408442296E9,1.520408472296E9 +37,-6571148882511144742,627460548,1.520408484296E9,1.520408695296E9 +37,6862737239824681180,627460548,1.520408710296E9,1.520408721296E9 +37,-4715510912133192615,627460548,1.520408961296E9,1.520408998296E9 +27,8674353898574550141,479635942,1.4994122658549998E9,1.499412266636E9 +27,695281303909472462,479635942,1.4994124804769998E9,1.499412481228E9 +27,-8864080507758269857,479635942,1.4994124844669998E9,1.4994124860279999E9 +27,5402183394216494358,479635942,1.4994126095379999E9,1.4994126110679998E9 +27,-3591895910128644059,479635942,1.499412647998E9,1.4994126498589997E9 +27,3915479474320450706,479635942,1.4994129112759998E9,1.4994129158059998E9 +27,3053381746211365606,479635942,1.4994129222569997E9,1.4994129584069998E9 +27,-2866501197743070330,479635942,1.4994129655169997E9,1.4994131448589997E9 +27,-3939067592918533534,479635942,1.4994131510679998E9,1.4994131828949997E9 +27,-4652280311614165217,479635942,1.4994131856249998E9,1.499413232126E9 +27,3207051283557539044,479635942,1.499413267465E9,1.4994132883149998E9 +27,-2732529320601835300,479635942,1.499413421366E9,1.4994134268259997E9 +27,-1363334564744447690,479635942,1.4994136946349998E9,1.4994136973349998E9 +27,3893654913370439254,479635942,1.4994138611649997E9,1.4994138620049999E9 +27,8242568482103878969,479635942,1.4994141418159997E9,1.4994141440649998E9 +27,-6144419108510199687,479635942,1.4994146467159998E9,1.499414649807E9 +27,-5078310701503483746,479635942,1.4994147685729997E9,1.499414769353E9 +27,8579090422341008492,479635942,1.4994150927549999E9,1.4994150954829998E9 +27,4039316153655645498,479635942,1.4994151016049998E9,1.4994151073939998E9 +27,-2237134870613399443,479635942,1.499415223014E9,1.4994152246649997E9 +27,8202051261902046912,479635942,1.4994152743449998E9,1.4994152750949998E9 +27,5086261691144969555,479635942,1.4994153203939998E9,1.4994153212949998E9 +27,2733625987049382211,479635942,1.4994153674659998E9,1.4994153682159998E9 +57,-6554230948777517037,479635942,1.5192423368619998E9,1.5192423386619997E9 +57,7173916698745329875,479635942,1.5192423713609996E9,1.5192423722609997E9 +57,-9090840472072964219,479635942,1.5192423740909996E9,1.5192423749909997E9 +57,-1671633887400698586,479635942,1.5192424589009998E9,1.5192424604009998E9 +57,9048040990016721025,479635942,1.5192425608099997E9,1.5192425638099997E9 +57,-5870775394791778962,479635942,1.5192425750599997E9,1.5192425813599997E9 +57,-4253372485325844225,479635942,1.5192425867599998E9,1.5192425912899997E9 +57,-7542605970773775985,479635942,1.5192426221589997E9,1.5192426434889996E9 +57,6637335121187019215,479635942,1.5192426488289998E9,1.5192426617909997E9 +57,3976298108922824775,479635942,1.5192426706709998E9,1.5192426776299996E9 +57,-4137165481925903370,479635942,1.5192426889689996E9,1.5192427179489996E9 +57,6579444281817735706,479635942,1.5192427206789997E9,1.5192427324689996E9 +57,-3611796496342995915,479635942,1.5192428705879996E9,1.5192428804269998E9 +57,7525755179545259719,479635942,1.5192429808709998E9,1.5192430006099997E9 +57,7783490503564084382,479635942,1.5192430477399998E9,1.5192430499899998E9 +57,-8609858951172947820,479635942,1.5192430557799997E9,1.5192431224389997E9 +57,-4458153139553405095,479635942,1.5192431383089998E9,1.5192434818999996E9 +57,6378783642114550610,479635942,1.5192437361459997E9,1.5192437376759996E9 +57,-4797598619547570490,479635942,1.5192437407959998E9,1.5192437438569996E9 +57,4304386528415966236,479635942,1.5192437453559997E9,1.5192437461059997E9 +57,5523042718259901713,479635942,1.5192438357779996E9,1.5192438397669997E9 +57,4986024019145050466,479635942,1.5192438496069996E9,1.5192438518579998E9 +57,-5254904973701572879,479635942,1.5192438649069996E9,1.5192438656569996E9 +57,5438032168873030028,479635942,1.5192439249979997E9,1.5192439258669996E9 +57,-993545838186621405,479635942,1.5192439863159997E9,1.5192439870649996E9 +27,-6393753909343424005,627460548,1.4994129130089998E9,1.4994132390089998E9 +27,-3070683365875284593,627460548,1.4994132460089998E9,1.4994132920089998E9 +27,2110288011233501027,627460548,1.4994138510089998E9,1.4994140440089998E9 +27,-9131597979868475747,627460548,1.4994147850089998E9,1.4994148280089998E9 +27,4045771157844725277,627460548,1.4994150130089998E9,1.4994150200089998E9 +27,1783021425772426411,627460548,1.4994150460089998E9,1.4994150610089998E9 +57,884067157795190068,627460548,1.5192426086949997E9,1.5192427496949997E9 +57,7678212195377752192,627460548,1.5192427676949997E9,1.5192428076949997E9 +57,16815563956577441,627460548,1.5192428796949997E9,1.5192428816949997E9 +57,4030803934067838860,627460548,1.5192428926949997E9,1.5192428996949997E9 +57,1020006338934776264,627460548,1.5192429056949997E9,1.5192429246949997E9 +57,2415756925561855884,627460548,1.5192429526949997E9,1.5192431266949997E9 +57,28885189448055567,627460548,1.5192431326949997E9,1.5192434946949997E9 +24,-8271726994879254810,479635942,1.502263533326E9,1.502263534226E9 +24,-323950731939568250,479635942,1.502263562246E9,1.5022635630870001E9 +24,4933679438375242511,479635942,1.502263604009E9,1.502263605508E9 +24,1297735555378282246,479635942,1.502263633468E9,1.502263634367E9 +24,-2738979816088928422,479635942,1.502263636167E9,1.502263638027E9 +24,-5467593927101091507,479635942,1.502263679998E9,1.502263680898E9 +24,-2337223226789559487,479635942,1.502263682579E9,1.502263684318E9 +24,3884426066974528872,479635942,1.502263769998E9,1.502263770898E9 +24,-8009399546736316308,479635942,1.502263773659E9,1.502263775459E9 +24,-2587155742064256033,479635942,1.502263899331E9,1.502263900259E9 +24,-5620942151316782854,479635942,1.502263902991E9,1.502263903951E9 +24,2532325572054373917,479635942,1.502263945352E9,1.50226394703E9 +24,-3798675711688482769,479635942,1.502264236828E9,1.502264276758E9 +24,-424952667515034468,479635942,1.502264301298E9,1.502264529536E9 +24,817993339488982970,479635942,1.502264789965E9,1.502264790804E9 +24,-8467590875612072123,479635942,1.502264821134E9,1.502264825033E9 +24,-397737126793059872,479635942,1.5022648295970001E9,1.502264830345E9 +24,-7702149830799955036,479635942,1.502264838775E9,1.502264840336E9 +24,1721611287864174997,479635942,1.502265010225E9,1.502265012927E9 +24,6382146468482351297,479635942,1.502265016525E9,1.502265018325E9 +24,-5782306477470861236,479635942,1.502265102175E9,1.502265138566E9 +24,-8662668889700624181,479635942,1.502265441025E9,1.502265441836E9 +24,-292978311600729389,479635942,1.502265637435E9,1.5022656383370001E9 +24,-6151445071170154327,479635942,1.502265656306E9,1.502265657986E9 +24,1900236812222391203,479635942,1.502266046843E9,1.502266050382E9 +24,8827479991731110079,479635942,1.502266056924E9,1.502266059263E9 +24,2827888587990366695,479635942,1.502266068412E9,1.502266071623E9 +24,-7122049896024673105,479635942,1.502266199362E9,1.502266201673E9 +24,1739314643299862673,479635942,1.502266244992E9,1.502266247693E9 +24,-7600160568368630556,479635942,1.502266266713E9,1.502266269443E9 +24,-2036494577029594759,479635942,1.502266288403E9,1.502266291945E9 +24,5615280465657652424,479635942,1.502266407774E9,1.502266408644E9 +24,2917463559832182088,479635942,1.502266436396E9,1.502266438795E9 +24,-4678353026325301096,479635942,1.502266442784E9,1.502266450646E9 +24,1188988666242901836,479635942,1.502266460724E9,1.502266462224E9 +24,-319100049241786755,479635942,1.502266509351E9,1.502266510281E9 +24,-2971217135755330652,479635942,1.502266512081E9,1.50226651394E9 +24,5995742182165642488,479635942,1.502266515771E9,1.502266518502E9 +24,-5073062917195522933,479635942,1.502266536651E9,1.502266540912E9 +24,-966943468313349187,479635942,1.502266594732E9,1.502266596232E9 +24,4518543085308497195,479635942,1.502266599801E9,1.502266601632E9 +24,-8897807749355510685,479635942,1.502266642553E9,1.502266644352E9 +24,1010443384744067324,479635942,1.502266747044E9,1.502266747943E9 +24,7629939448402436848,627460548,1.502264193684E9,1.502264535684E9 +24,6888774802012843780,627460548,1.502264542684E9,1.502264543684E9 +24,6209606946974175230,627460548,1.502264556684E9,1.502264566684E9 +24,-2471707800494912078,627460548,1.502264577684E9,1.502264583684E9 +24,7211745083105806118,627460548,1.502264591684E9,1.502264632684E9 +24,3481248545506522859,627460548,1.502265016684E9,1.502265019684E9 +24,-6252444754938667161,627460548,1.502265039684E9,1.502265161684E9 +24,1979807095150432588,627460548,1.502265182684E9,1.502265221684E9 +24,7295909730768766305,627460548,1.502265857684E9,1.502265858684E9 +52,-2087632722845524189,479635942,1.499455476099E9,1.499455477899E9 +52,3448233945657650516,479635942,1.499455490379E9,1.499455492179E9 +52,5992452535513510995,479635942,1.4994554989899998E9,1.499455499829E9 +52,1866691306126058830,479635942,1.499455524369E9,1.499455526681E9 +52,-3948887270595223067,479635942,1.499455528181E9,1.4994555289299998E9 +52,-2975362549562094253,479635942,1.4994555531709998E9,1.4994555548189998E9 +52,-1768576756537310847,479635942,1.49945555752E9,1.4994555593189998E9 +52,7449648592234594284,479635942,1.49945556112E9,1.4994555620189998E9 +52,-3266469965124748900,479635942,1.4994555819399998E9,1.49945558377E9 +52,-9179968419444721001,479635942,1.4994556271799998E9,1.49945562808E9 +52,-3616734105770689146,479635942,1.4994556306899998E9,1.49945563153E9 +52,3723223346697916687,479635942,1.4994556507299998E9,1.4994556531299999E9 +52,-3019095699150699055,479635942,1.4994556538799999E9,1.4994556562199998E9 +52,-1953376434088184126,479635942,1.4994556599699998E9,1.499455662281E9 +52,5755530235233645867,479635942,1.499455665281E9,1.49945566837E9 +52,-8307054000355814225,479635942,1.499455672931E9,1.499455673681E9 +52,3858400344203402424,479635942,1.4994556842099998E9,1.49945568607E9 +52,2665598858018186435,479635942,1.4994556905709999E9,1.4994557014899998E9 +52,-2233514826563582784,479635942,1.49945570329E9,1.4994557294209998E9 +52,-2500338387891296896,479635942,1.49945628757E9,1.49945628832E9 +52,-8543066368144693654,479635942,1.499456353658E9,1.4994563545579998E9 +52,8062392373497619213,479635942,1.4994568633579998E9,1.4994570707199998E9 +52,7851620437001948117,479635942,1.499457078339E9,1.49945712376E9 +52,848383177218628469,479635942,1.4994571299689999E9,1.49945713087E9 +52,-4074119981866861472,479635942,1.499457599471E9,1.4994576031E9 +52,8429349343242411215,479635942,1.499457628418E9,1.4994576293179998E9 +52,-7951357391260011843,479635942,1.4994577863909998E9,1.4994577884819999E9 +52,8846306731700379444,627460548,1.499455696949E9,1.499455779949E9 +52,-9035506030366779319,627460548,1.499455951949E9,1.499455958949E9 +52,7065988900713281098,627460548,1.499456398949E9,1.499456438949E9 +52,2437777036022907665,627460548,1.499456462949E9,1.499456488949E9 +52,-9152985168502729191,627460548,1.499456528949E9,1.499456549949E9 +52,5792270040883499398,627460548,1.499456725949E9,1.499456727949E9 +52,4714015256334737331,627460548,1.499456735949E9,1.499456751949E9 +52,-2001566054420712475,627460548,1.499456752949E9,1.499456766949E9 +52,1020457402724611751,627460548,1.499456767949E9,1.499456778949E9 +52,-4457654314229111696,627460548,1.499456789949E9,1.499457130949E9 +52,-5695599057815644292,627460548,1.499457142949E9,1.499457173949E9 +28,2989719843892638139,479635942,1.521789301609E9,1.52178930248E9 +28,-6546071482316512068,479635942,1.521789372378E9,1.521789373278E9 +28,3587260167952106219,479635942,1.521789375078E9,1.5217893759780002E9 +28,2179278434497127602,479635942,1.521789419027E9,1.521789419839E9 +28,8018325600470558660,479635942,1.521789421427E9,1.521789422989E9 +28,-934247401822315610,479635942,1.521789425328E9,1.521789426139E9 +28,3420096387648749466,479635942,1.521789539598E9,1.521789540348E9 +28,7584186644877240987,479635942,1.521789542598E9,1.521789544848E9 +28,-5670014871484998878,479635942,1.521789586398E9,1.521789587298E9 +28,-8988529172338256401,479635942,1.521789590027E9,1.521789590928E9 +28,8064245848125124982,479635942,1.5217896675770001E9,1.5217896687480001E9 +28,7025848776605415659,479635942,1.5217896712680001E9,1.5217896724090002E9 +28,5803688828261080643,479635942,1.521789707719E9,1.5217897090080001E9 +28,3311195045597475729,479635942,1.521789905116E9,1.521789906617E9 +28,-9219023194579910593,479635942,1.521789909617E9,1.521789911178E9 +28,8723088841604986933,479635942,1.521789918017E9,1.521789919517E9 +28,-7905251261920329566,479635942,1.5217899949060001E9,1.521789997487E9 +28,734507364879299218,479635942,1.5217900599160001E9,1.521790087937E9 +28,-6948859773793809975,479635942,1.521790094236E9,1.521790143647E9 +28,-4813329296749751112,479635942,1.521790163657E9,1.521790272286E9 +28,-896873121140455280,479635942,1.521790295686E9,1.521790372816E9 +28,-3176428470923601424,479635942,1.521790379206E9,1.521790380767E9 +28,6772600188298482330,479635942,1.521790381606E9,1.521790389167E9 +28,5595521599310606776,479635942,1.521790392706E9,1.5217904026660001E9 +28,-6350544209921415499,479635942,1.521790422527E9,1.521790424326E9 +28,-4537315618059827377,479635942,1.521790483487E9,1.521790485076E9 +28,8580869026912468006,479635942,1.521790545257E9,1.521790550687E9 +28,-2665185072265794832,479635942,1.521790556086E9,1.521790566736E9 +28,-8938280773641438702,479635942,1.5217905736660001E9,1.521790576157E9 +28,2330001219452104673,479635942,1.521790594997E9,1.521790598236E9 +28,6733620230882008436,479635942,1.521790651157E9,1.521790653857E9 +28,5877522186661116480,479635942,1.521790667446E9,1.521790668346E9 +28,-361088117889028396,479635942,1.521790701167E9,1.521790703417E9 +28,189455269329405450,479635942,1.521790775356E9,1.521790776256E9 +28,4518935997959129305,479635942,1.5217907843860002E9,1.5217907896660001E9 +28,5647976886843573768,479635942,1.521791032908E9,1.521791035907E9 +28,5903742365224137150,479635942,1.521791722968E9,1.521791736468E9 +28,1622335935126789139,479635942,1.521791758459E9,1.521791767459E9 +28,-5977472103327219007,479635942,1.521791928979E9,1.52179193066E9 +28,-5223558511203374263,479635942,1.5217920465800002E9,1.5217920488300002E9 +28,-6788269653895207535,479635942,1.52179207976E9,1.52179208066E9 +28,5671175831315955221,479635942,1.521792101509E9,1.52179210421E9 +28,5097287225211377241,479635942,1.521792174469E9,1.521792175219E9 +28,3736919765184195364,479635942,1.521792263662E9,1.521792264411E9 +28,-6321656678343779921,479635942,1.52179228664E9,1.52179228739E9 +28,6644070681382979192,627460548,1.521789995098E9,1.521790004098E9 +28,-8316686508540267169,627460548,1.521790005098E9,1.521790020098E9 +28,6833298294237936702,627460548,1.521790037098E9,1.521790377098E9 +28,5321933356163115789,627460548,1.521791104098E9,1.521791337098E9 +28,-7674243211080680621,627460548,1.521791375098E9,1.521791518098E9 +28,-4664527021828171774,627460548,1.521791725098E9,1.521791774098E9 +28,-192423587251135279,627460548,1.521791785098E9,1.521791794098E9 +54,5884422611613760258,479635942,1.5017895723669999E9,1.501789573268E9 +54,-6632521396907378471,479635942,1.501789576809E9,1.501789578549E9 +54,-8544994393027598490,479635942,1.501789585958E9,1.5017895876069999E9 +54,-7425676856160693953,479635942,1.501790205669E9,1.501790215599E9 +54,1021945395007737335,479635942,1.50179022505E9,1.5017902866999998E9 +54,7130584181550608,479635942,1.50179031559E9,1.501790323721E9 +54,-2003436196878806087,479635942,1.501790604481E9,1.5017906440619998E9 +54,3932072332709365639,479635942,1.501790658168E9,1.501790703751E9 +54,-1666438681689735740,479635942,1.5017907241829998E9,1.5017908194989998E9 +54,8656173152238052690,479635942,1.5017908374039998E9,1.501790954554E9 +54,-8521628532193775465,479635942,1.5017909591439998E9,1.5017909624989998E9 +54,-5315738317568988502,479635942,1.501790970034E9,1.5017909834269998E9 +54,-7434133036494917831,479635942,1.501790988432E9,1.501791000064E9 +54,-1974156482553828232,479635942,1.5017910091669998E9,1.501791017527E9 +54,2599935394869429313,479635942,1.5017910274269998E9,1.501791128957E9 +54,4104353716081471096,479635942,1.501791361688E9,1.5017913641899998E9 +54,5092652387796307429,479635942,1.5017913666109998E9,1.501791369113E9 +54,3036675870441418807,479635942,1.501791370764E9,1.5017913732659998E9 +54,9195331733543381068,479635942,1.501791395899E9,1.501791396751E9 +54,-5934748721544570706,479635942,1.5017914026079998E9,1.5017914060189998E9 +54,2244290691548681295,479635942,1.501791506781E9,1.501791508429E9 +54,-4334013784194347496,479635942,1.501791521574E9,1.501791522399E9 +54,8574502040419010817,479635942,1.50179161304E9,1.501791614745E9 +54,1306396689189238469,479635942,1.501791616395E9,1.501791618072E9 +54,1031552160557126126,479635942,1.501791692791E9,1.501791693615E9 +54,-1049759118489475353,627460548,1.501790199646E9,1.501790341646E9 +54,1258962116866064326,627460548,1.501790413646E9,1.501790451646E9 +54,6695355669994504649,627460548,1.501790599646E9,1.501791133646E9 +39,2217679845298225747,479635942,1.5018728299259999E9,1.501872830825E9 +39,7429129270833811340,479635942,1.501872833525E9,1.501872835325E9 +39,-2205412637285000343,479635942,1.501872927698E9,1.501872929197E9 +39,-2262513564006162990,479635942,1.501872964385E9,1.501872966245E9 +39,-4538657594522823140,479635942,1.501873035545E9,1.501873037105E9 +39,642871938461495812,479635942,1.5018730386069999E9,1.5018730408569999E9 +39,8388514415251228254,479635942,1.501873042356E9,1.501873043856E9 +39,6676464437231046948,479635942,1.501873111057E9,1.501873111986E9 +39,6254671758320742423,479635942,1.501873112886E9,1.501873118317E9 +39,-8723353049507850817,479635942,1.501873137966E9,1.501873140576E9 +39,9021226933992420292,479635942,1.501873150808E9,1.501873153267E9 +39,-8352077785127594621,479635942,1.501873154767E9,1.501873155518E9 +39,7394615352148428027,479635942,1.501873172408E9,1.501873174747E9 +39,-4680562287917372401,479635942,1.501873761667E9,1.501873776697E9 +39,-6306273737284460527,479635942,1.501873805587E9,1.501873875909E9 +39,-1045836146745966823,479635942,1.501874148309E9,1.50187415644E9 +39,-2208627613166407546,479635942,1.50187418806E9,1.50187420456E9 +39,8757164211530508676,479635942,1.501874211552E9,1.50187432621E9 +39,9035390723419109917,479635942,1.501874337611E9,1.501874519112E9 +39,2562574547047384209,479635942,1.501874553642E9,1.501874629783E9 +39,-6752555350224995283,479635942,1.501874640612E9,1.501874669143E9 +39,-8971213737537990398,479635942,1.501874842082E9,1.501874855134E9 +39,-260625663470644455,479635942,1.501874930793E9,1.501874934304E9 +39,722760640995362381,479635942,1.501874961154E9,1.501874961906E9 +39,7037525964559988538,479635942,1.501875001593E9,1.501875003393E9 +39,-5678376788961029492,479635942,1.501875004293E9,1.501875007053E9 +39,4831392706876999725,479635942,1.501875069934E9,1.501875071616E9 +39,-6699269542960821323,479635942,1.501875086404E9,1.501875087154E9 +39,8150811091564660034,479635942,1.501875119014E9,1.501875120845E9 +39,4774107043749094763,479635942,1.501875126304E9,1.501875127204E9 +39,5722532102437117965,479635942,1.501875221613E9,1.501875222362E9 +39,-7225941824705592368,479635942,1.501875238263E9,1.501875239763E9 +39,6598872003948840095,479635942,1.501875246362E9,1.501875247262E9 +39,-6141972856624708171,479635942,1.501875276302E9,1.501875277202E9 +39,597376076782374913,479635942,1.501875279063E9,1.501875279964E9 +39,-6964320635298983148,479635942,1.501875357725E9,1.501875358474E9 +39,6166801384617271629,627460548,1.501873755635E9,1.501873932635E9 +39,3763874668790095729,627460548,1.501873961635E9,1.501874012635E9 +39,8408206159893819107,627460548,1.501874142635E9,1.501874674635E9 +35,3807156136094092477,479635942,1.5199755269329998E9,1.519975528431E9 +35,-7868151759452467913,479635942,1.5199755753799999E9,1.519975576281E9 +35,-1012290315859943529,479635942,1.519975578981E9,1.5199755798809998E9 +35,-7445769394435248618,479635942,1.519975583511E9,1.519975584411E9 +35,6400465824860810860,479635942,1.51997568605E9,1.5199756878799999E9 +35,6285222692204463578,479635942,1.5199757052189999E9,1.519975706119E9 +35,-7941861147535292487,479635942,1.5199757224989998E9,1.5199757234299998E9 +35,5835777072089175065,479635942,1.519975726159E9,1.51997572709E9 +35,2518343011503706020,479635942,1.51997576756E9,1.5199757690619998E9 +35,2456924796795394609,479635942,1.519975771313E9,1.519975772149E9 +35,-7119773801040918960,479635942,1.519975847419E9,1.5199758482889998E9 +35,-4917539598947089040,479635942,1.5199758536299999E9,1.519975855369E9 +35,-1223175280759925677,479635942,1.5199758579789999E9,1.519975858908E9 +35,3520977062530178324,479635942,1.519976024892E9,1.519976025793E9 +35,-6735443420804731016,479635942,1.519976027622E9,1.519976029422E9 +35,-4968085838182275418,479635942,1.519976032122E9,1.519976036622E9 +35,3628193686305137308,479635942,1.5199761119819999E9,1.519976174501E9 +35,659191462920116663,479635942,1.5199761985609999E9,1.519976288681E9 +35,9124031735557573651,479635942,1.519976310402E9,1.519976364942E9 +35,3525754609373030386,479635942,1.5199763702519999E9,1.519976472701E9 +35,-4221657168373797184,479635942,1.5199766384789999E9,1.51997664658E9 +35,1349770851040280296,479635942,1.519976687228E9,1.5199766894789999E9 +35,-4433036976473232254,479635942,1.519976778909E9,1.519976779747E9 +35,-7213473412068006276,479635942,1.5199767950479999E9,1.5199767958E9 +35,-3194353193933720338,479635942,1.5199767996999998E9,1.5199768141899998E9 +35,-7303683789813629858,479635942,1.519976849619E9,1.5199768550479999E9 +35,6460727155122702223,479635942,1.5199768668079998E9,1.519976872208E9 +35,3422950762716315227,479635942,1.519976890057E9,1.519976903079E9 +35,-1528010770785603060,479635942,1.5199769521279998E9,1.519976954829E9 +35,5737490194055173790,479635942,1.5199770951069999E9,1.519977098738E9 +35,-1321838096581725488,479635942,1.5199771023679998E9,1.5199771032979999E9 +35,3269477752945050724,479635942,1.519977787863E9,1.5199778366729999E9 +35,-2299505510720164333,479635942,1.5199778563249998E9,1.5199778601629999E9 +35,-7838391747680193884,479635942,1.5199778925029998E9,1.5199778934029999E9 +35,-1584244933793512447,479635942,1.5199779636959999E9,1.519977964533E9 +35,4864248771182400045,479635942,1.5199779667849998E9,1.5199779690939999E9 +35,-8776671137839204180,479635942,1.519977980737E9,1.519977983316E9 +35,3615342812623510476,479635942,1.5199779960349998E9,1.5199779978349998E9 +35,-2640278058879556125,479635942,1.5199780803049998E9,1.519978084143E9 +35,5794457005668239110,479635942,1.5199780878969998E9,1.519978089484E9 +35,-5034104462427228891,479635942,1.519978107935E9,1.519978109824E9 +35,-6350543409292125905,479635942,1.519978125304E9,1.519978127133E9 +35,2246607572088895757,479635942,1.519978152274E9,1.5199781531729999E9 +35,3848633351862000975,627460548,1.519976105235E9,1.519976489235E9 +35,-6433962027570020609,627460548,1.519977103235E9,1.519977246235E9 +35,4375002370194676905,627460548,1.519977251235E9,1.519977253235E9 +35,-4007153983025216719,627460548,1.519977275235E9,1.519977294235E9 +35,7579346900117515166,627460548,1.519977296235E9,1.519977320235E9 +35,8057918823554949052,627460548,1.519977351235E9,1.519977361235E9 +35,839537418906605514,627460548,1.519977367235E9,1.519977591235E9 +35,4679591872902996954,627460548,1.519977752235E9,1.519977844235E9 +43,-7058008765899197432,479635942,1.518592085119E9,1.5185920860180001E9 +43,4099126706977885074,479635942,1.518592208955E9,1.5185922099160001E9 +43,6451356444034115562,479635942,1.518592246516E9,1.518592247356E9 +43,-8150602038896385731,479635942,1.518592293576E9,1.518592294476E9 +43,-7102617389349683731,479635942,1.518592296275E9,1.518592298076E9 +43,9174022562754907217,479635942,1.518592305276E9,1.5185923070770001E9 +43,2422301470465107897,479635942,1.518592337076E9,1.5185923379750001E9 +43,1950901516163481914,479635942,1.518592373376E9,1.518592374276E9 +43,-1833478260161155518,479635942,1.518592378206E9,1.518592378961E9 +43,-4565122457542768112,479635942,1.518592399566E9,1.518592400466E9 +43,6979453964667639134,479635942,1.518592402326E9,1.5185924032250001E9 +43,-4413016090813513212,479635942,1.518592437756E9,1.518592438685E9 +43,6171648788862839373,479635942,1.5185925448860002E9,1.518592546685E9 +43,7712676961613195260,479635942,1.5185925493860002E9,1.518592551184E9 +43,1500305998960258232,479635942,1.5185928114620001E9,1.518592812362E9 +43,1899578016838962269,479635942,1.518592840353E9,1.51859311926E9 +43,321256945866508792,479635942,1.518593540729E9,1.518593544839E9 +43,6349644486090024992,479635942,1.518593561039E9,1.518593569379E9 +43,6853817542902001778,479635942,1.518593806197E9,1.5185938198170002E9 +43,8057354203374075125,479635942,1.518593848826E9,1.518593865926E9 +43,5428921319659140702,479635942,1.518593953105E9,1.518593979116E9 +43,3058655587346765347,479635942,1.518594042176E9,1.518594043076E9 +43,7483600543748994185,479635942,1.518594063086E9,1.518594063992E9 +43,1438897541422177990,479635942,1.518594345956E9,1.5185943468270001E9 +43,-1532292184569246003,479635942,1.518594395185E9,1.518594397045E9 +43,4400534172488044475,479635942,1.518594397945E9,1.518594400647E9 +43,7399425679228370350,479635942,1.518594401546E9,1.518594403346E9 +43,-8437452438337175529,479635942,1.518594429535E9,1.518594432265E9 +43,1270742671164561227,479635942,1.518594436766E9,1.518594438535E9 +43,115487077398780973,479635942,1.518594444535E9,1.51859444874E9 +43,-4808354739683817918,479635942,1.518594513205E9,1.518594516924E9 +43,-6308949655469903080,627460548,1.518592797006E9,1.518592818006E9 +43,6464573402665774094,627460548,1.518592823006E9,1.518593365006E9 +43,8346798350924835948,627460548,1.518593422006E9,1.518593428006E9 +43,5938567433821079553,627460548,1.518593526006E9,1.518593755006E9 +43,2886888829017893948,627460548,1.518593770006E9,1.518593828006E9 +43,-1980224228425834260,627460548,1.518593836006E9,1.518594001006E9 +49,-9107542811376712554,479635942,1.518804968773E9,1.518804969672E9 +49,-5067644735208933170,479635942,1.518804990734E9,1.518804991493E9 +49,-2666009975662904868,479635942,1.518805017073E9,1.518805017823E9 +49,8073829210336210285,479635942,1.518805099033E9,1.518805099902E9 +49,7943529268706742413,479635942,1.518805102481E9,1.518805104251E9 +49,6032250005349690304,479635942,1.518805105931E9,1.51880510758E9 +49,6436874953734956466,479635942,1.518805170249E9,1.518805171151E9 +49,-767164038784487233,479635942,1.518805213763E9,1.518805215912E9 +49,-294130005838402358,479635942,1.518805234882E9,1.518805237411E9 +49,-8760229126709107486,479635942,1.518805558009E9,1.518805580669E9 +49,1651676104084734951,479635942,1.518806150329E9,1.518806177031E9 +49,-8979553314074613263,479635942,1.518806213688E9,1.5188062625E9 +49,-4842058215165844819,479635942,1.518806934844E9,1.518806937346E9 +49,-4469580179019911241,479635942,1.518806961325E9,1.518806973811E9 +49,-7820837760682198275,479635942,1.518807123414E9,1.518807125945E9 +49,1050792728431251470,479635942,1.518807127595E9,1.518807133451E9 +49,-3998395617141563823,479635942,1.518807148384E9,1.518807149209E9 +49,3287360646335771100,627460548,1.518805310421E9,1.518805316421E9 +49,7000134458797106677,627460548,1.518805534421E9,1.518805895421E9 +49,-3741305235141471234,627460548,1.518805908421E9,1.518805912421E9 +49,4991928393789004847,627460548,1.518806082421E9,1.518806182421E9 +49,7899776172822746806,627460548,1.518806194421E9,1.518806288421E9 +49,-6784152684712676730,627460548,1.518806306421E9,1.518806322421E9 +49,-5414965408042151121,627460548,1.518806348421E9,1.518806358421E9 +49,3777595674135698594,627460548,1.518806383421E9,1.518806424421E9 +49,-9136935570379168161,627460548,1.518807131421E9,1.518807135421E9 +22,-5896597027850248888,479635942,1.499780633591E9,1.499780635152E9 +22,7186125788303790293,479635942,1.499780675052E9,1.499780675951E9 +22,2412148044735908029,479635942,1.499780729262E9,1.4997807300740001E9 +22,7798991882888057475,479635942,1.499780765203E9,1.499780766792E9 +22,5202673324534620856,479635942,1.4997807774120002E9,1.4997807792120001E9 +22,-386642346121350473,479635942,1.499780835433E9,1.499780837232E9 +22,5929840982891225472,479635942,1.4997808722740002E9,1.4997808737740002E9 +22,-8981032894100369185,479635942,1.499780875423E9,1.499780877013E9 +22,-3855955331048401240,479635942,1.499780955161E9,1.499780956066E9 +22,7475747809140334547,479635942,1.499780994852E9,1.499780995635E9 +22,-3494481583210250767,479635942,1.499781001721E9,1.499781020111E9 +22,1236147292822658510,479635942,1.4997810276720002E9,1.4997810291720002E9 +22,6112863226965943190,479635942,1.4997810445010002E9,1.499781058931E9 +22,5773179323339585353,479635942,1.4997810652610002E9,1.499781067961E9 +22,-1771613916691416206,479635942,1.499781195612E9,1.499781204642E9 +22,-4236842894713427019,479635942,1.499781210072E9,1.499781225252E9 +22,-4308372485789840397,479635942,1.4997812468530002E9,1.499781248502E9 +22,9081538531300407835,479635942,1.499781275444E9,1.499781311772E9 +22,4834188891837984225,479635942,1.4997813127020001E9,1.499781328993E9 +22,-6345126932983930831,479635942,1.499781341713E9,1.499781388753E9 +22,-4819556096174284400,479635942,1.499781394004E9,1.49978141278E9 +22,-763926201650936003,479635942,1.49978142823E9,1.499781461801E9 +22,58292236850751263,479635942,1.4997815183500001E9,1.499781562211E9 +22,6683831598837137130,479635942,1.499781660341E9,1.499781664842E9 +22,1411212696096643537,479635942,1.499781669161E9,1.499781682812E9 +22,52852672845617987,479635942,1.499781699071E9,1.4997817017710001E9 +22,3345397891700601777,479635942,1.499781721601E9,1.499781725951E9 +22,-8932913868609031800,479635942,1.499781729371E9,1.4997817302410002E9 +22,2474915859971147000,479635942,1.4997817659120002E9,1.4997817689720001E9 +22,-866635493092282493,479635942,1.499781823902E9,1.4997818266030002E9 +22,6164177422194639818,479635942,1.4997818302020001E9,1.499781833832E9 +22,-723593460536740098,479635942,1.499781971082E9,1.499781971983E9 +22,3678401050055269458,479635942,1.49978202892E9,1.49978202967E9 +22,2490252344147428514,479635942,1.4997823096920002E9,1.499782364473E9 +22,2117734938487868256,479635942,1.499782408152E9,1.499782417124E9 +22,-6768875195471919041,479635942,1.499782447063E9,1.499782629254E9 +22,6544906540127044360,479635942,1.49978276045E9,1.4997828143E9 +22,-999100488552378757,479635942,1.4997828341230001E9,1.499782839434E9 +22,-7604241434750071025,479635942,1.499782840934E9,1.499782845522E9 +22,1092395173625558393,479635942,1.499782975785E9,1.499782976685E9 +22,2869856165843789039,479635942,1.4997832687680001E9,1.499783270565E9 +22,8334409195797582672,479635942,1.4997832714950001E9,1.4997832769550002E9 +22,-8968791445652546090,479635942,1.499783327355E9,1.4997833306560001E9 +22,-6438788932495869939,479635942,1.499783362993E9,1.499783367163E9 +22,4075747738832979129,479635942,1.499783368963E9,1.499783369862E9 +22,718849825638016006,479635942,1.499783548542E9,1.499783580584E9 +22,-5774266889087139995,479635942,1.499783632273E9,1.499783633174E9 +22,-3306266905293366488,479635942,1.499783636833E9,1.499783642263E9 +22,-493087465170145080,479635942,1.499783647783E9,1.4997836505440001E9 +22,6371821713090872612,479635942,1.499783658734E9,1.499783663263E9 +22,-27518988888910856,479635942,1.499783669625E9,1.499783672384E9 +22,4849384397952781469,479635942,1.499783705444E9,1.499783706283E9 +22,-1560713681876793463,479635942,1.499783707933E9,1.499783710183E9 +22,-5068703575023954692,479635942,1.499783710934E9,1.499783713333E9 +22,-4295601171174149999,479635942,1.499783731273E9,1.499783735925E9 +22,8426616595836198065,479635942,1.499783738234E9,1.499783739763E9 +22,5419412095421874800,479635942,1.499783773844E9,1.499783774744E9 +22,-1174591054879262699,479635942,1.4997838017150002E9,1.499783802584E9 +22,1657710588205256678,479635942,1.499783824605E9,1.499783825443E9 +22,-6449559305410223238,479635942,1.499783827184E9,1.499783829644E9 +22,-602043046948197464,479635942,1.499783836068E9,1.499783837805E9 +22,5981615962974144895,479635942,1.4997838429650002E9,1.499783844645E9 +22,8690613384222675243,479635942,1.499783919134E9,1.499783921835E9 +22,3937160509130180812,479635942,1.499783934435E9,1.499783935335E9 +76,-1105845952950245550,479635942,1.51967927791E9,1.51967927881E9 +76,5920097817553487994,479635942,1.519679416328E9,1.519679417229E9 +76,462367718546954289,479635942,1.519679536747E9,1.519679538548E9 +76,5487179622702546582,479635942,1.519679539448E9,1.519679541248E9 +76,-2146354098996149394,479635942,1.519679617797E9,1.519679618934E9 +76,1041142898128823602,479635942,1.519679622384E9,1.519679623643E9 +76,6098506547500618988,479635942,1.519679795939E9,1.519679800588E9 +76,2768182684889614369,479635942,1.519679808506E9,1.519679810698E9 +76,7782395365222991857,479635942,1.5196798130970001E9,1.519679814178E9 +76,-4659682481312204476,479635942,1.51967982408E9,1.51967982978E9 +76,-4019722658362444328,479635942,1.519679830921E9,1.519679833227E9 +76,-4261328116098121612,479635942,1.519679862357E9,1.519679863497E9 +76,-9158845563864338448,479635942,1.519679864758E9,1.519679868208E9 +76,3904467482979114234,479635942,1.519679884889E9,1.519679886026E9 +76,-7630807212104375517,479635942,1.519679902107E9,1.519679903337E9 +76,9151136906136994793,479635942,1.519679913599E9,1.519679915907E9 +76,-2019607882880990654,479635942,1.519679977825E9,1.519679979057E9 +76,-8537295039923796871,479635942,1.519679986945E9,1.519679989255E9 +76,-3207974231983701376,479635942,1.519680019345E9,1.519680020425E9 +76,-2048748740986875253,479635942,1.519680035425E9,1.519680038967E9 +22,7453634497762763387,627460548,1.499781109611E9,1.499781157611E9 +22,-2667840623629106412,627460548,1.499781162611E9,1.499781165611E9 +22,3658548711854299695,627460548,1.499781248611E9,1.499781569611E9 +22,7959932615450571977,627460548,1.499781578611E9,1.499781586611E9 +22,-1526987754415272387,627460548,1.499781661611E9,1.499781708611E9 +22,4489693828107356701,627460548,1.499781752611E9,1.499781755611E9 +22,1694102862087938930,627460548,1.499781787611E9,1.499781807611E9 +22,-263350719581045661,627460548,1.499781906611E9,1.499781925611E9 +22,5648384458542277604,627460548,1.499781938611E9,1.499781948611E9 +22,-5089699600337277020,627460548,1.499782264611E9,1.499782374611E9 +22,8132696178219463756,627460548,1.499782378611E9,1.499782423611E9 +22,-7087653142422353105,627460548,1.499782435611E9,1.499782648611E9 +22,-5877691627726887504,627460548,1.499782685611E9,1.499782692611E9 +22,4479280310250551514,627460548,1.499782696611E9,1.499782732611E9 +22,-147885727688475139,627460548,1.499782742611E9,1.499782820611E9 +22,6931678911227148041,627460548,1.499783542611E9,1.499783588611E9 +16,-4835422748163153092,479635942,1.519755878227E9,1.519755879127E9 +16,-7944667711158368039,479635942,1.519755945459E9,1.5197559463860002E9 +16,-4320676910727870764,479635942,1.519756041246E9,1.519756042088E9 +16,-8873097009693721787,479635942,1.519756340344E9,1.519756341183E9 +16,7768673216496133832,479635942,1.5197563470340002E9,1.519756347874E9 +16,487294542318201677,479635942,1.5197565150600002E9,1.5197565159610002E9 +16,1187681031301874890,479635942,1.519756562249E9,1.5197565848690002E9 +16,-2429833818515115371,479635942,1.5197565999290001E9,1.5197566077910001E9 +16,-7069086383233093899,479635942,1.519756612289E9,1.5197566167910001E9 +16,4810758493534637137,479635942,1.5197566206890001E9,1.5197566252800002E9 +16,-6181135641813650112,479635942,1.519756640249E9,1.519756649338E9 +16,2666886561404715274,479635942,1.519756799766E9,1.519756809911E9 +16,5460807173925225660,479635942,1.5197568137900002E9,1.5197571293470001E9 +16,8842098101795433501,479635942,1.519757182647E9,1.519757186937E9 +16,6777686955071808335,479635942,1.519757210946E9,1.519757244275E9 +16,-547117406604438075,479635942,1.5197572486490002E9,1.519757411887E9 +16,-6209365452399336460,479635942,1.519757583704E9,1.519757587884E9 +16,1441354691417098818,479635942,1.5197575903590002E9,1.5197576984880002E9 +16,-8258231792585524440,479635942,1.51975774774E9,1.5197579262710001E9 +16,1299417524337361056,479635942,1.519757933779E9,1.519757937079E9 +16,4041459375818170055,479635942,1.5197579454120002E9,1.519757974644E9 +16,5052791538369553896,479635942,1.519757987078E9,1.519758160184E9 +16,-2195595709962290905,479635942,1.5197582259940002E9,1.519758255911E9 +16,-1133735532446012360,479635942,1.5197582659220002E9,1.5197584638920002E9 +16,-3815594941682790112,479635942,1.519758734631E9,1.519758735486E9 +16,5036161273076758188,479635942,1.519758770436E9,1.5197587712610002E9 +16,8578113621891167676,479635942,1.519758855493E9,1.5197588571490002E9 +16,431103479953852746,479635942,1.51975886465E9,1.519758867125E9 +16,-4952489109208497132,479635942,1.5197588879160001E9,1.519758890473E9 +16,-2812082221183416367,479635942,1.519759236144E9,1.5197594859290001E9 +16,566906191349775020,479635942,1.519759494427E9,1.519759521736E9 +16,-6045810387841523693,479635942,1.5197598429850001E9,1.519759845515E9 +16,6971406564904154602,479635942,1.5197598912220001E9,1.5197598920470002E9 +16,5060414737808162330,479635942,1.5197598953760002E9,1.5197598969980001E9 +16,-6554071425759889315,479635942,1.51975990874E9,1.5197599228760002E9 +16,-1996093016827180782,479635942,1.5197599489450002E9,1.5197599540340002E9 +16,4728952328184572349,479635942,1.5197599807080002E9,1.519759981533E9 +16,-3353886951813461091,479635942,1.5197599998200002E9,1.5197600014970002E9 +16,1527556662811698572,479635942,1.519760134568E9,1.519760136246E9 +16,7928295504514018886,479635942,1.519760141223E9,1.519760142048E9 +70,-509005689115366972,479635942,1.50157709735E9,1.501577098851E9 +70,6521682553365299603,479635942,1.501577108901E9,1.50157710971E9 +70,-622821019216253274,479635942,1.501577157081E9,1.501577160741E9 +70,-1147642693879407564,479635942,1.501577163445E9,1.501577164341E9 +70,-1719432998864520753,479635942,1.501577200435E9,1.501577202683E9 +70,2532846129743573755,479635942,1.501577267092E9,1.501577267994E9 +70,-8385069588646240591,479635942,1.501577270783E9,1.501577271682E9 +70,-5994758805273643702,479635942,1.501577317525E9,1.501577318333E9 +70,-946746684094057234,479635942,1.501577389944E9,1.501577391775E9 +70,1782019380705962809,479635942,1.50157759844E9,1.501577599971E9 +70,5956479429352131992,479635942,1.501577602674E9,1.50157760357E9 +70,-2490928093214390957,479635942,1.501577677821E9,1.50157767869E9 +70,-606288794264288998,479635942,1.501577742022E9,1.501577742921E9 +70,8741690489709291574,479635942,1.501577752881E9,1.501577754683E9 +70,6523620502606192215,479635942,1.5015777583109999E9,1.501577760171E9 +70,3166832361691151802,479635942,1.501577880652E9,1.5015778815839999E9 +70,-1251625012631277163,479635942,1.501578082579E9,1.501578083331E9 +16,-3131788387669208619,627460548,1.519756664446E9,1.519756686446E9 +16,3589590186528271340,627460548,1.519756738446E9,1.519756779446E9 +16,7497853340005543603,627460548,1.519756802446E9,1.519757137446E9 +16,-7063621413719097680,627460548,1.519757174446E9,1.519757449446E9 +16,-7700552614807772297,627460548,1.519757584446E9,1.519757715446E9 +16,4778570380157291994,627460548,1.519757727446E9,1.519758597446E9 +16,3734631109805916415,627460548,1.519758650446E9,1.519758691446E9 +16,2645177865872206883,627460548,1.519759068446E9,1.519759084446E9 +16,-6695814739707253795,627460548,1.519759119446E9,1.519759122446E9 +16,-5285543817577578533,627460548,1.519759137446E9,1.519759139446E9 +16,-762924391285812763,627460548,1.519759158446E9,1.519759186446E9 +16,-7868963532725186035,627460548,1.519759191446E9,1.519759572446E9 +70,1818956261512233195,627460548,1.501577403043E9,1.501577428043E9 +4,5698522733215146848,479635942,1.521442429307E9,1.521442430267E9 +4,1454533965849290447,479635942,1.521442450246E9,1.521442451116E9 +4,7853551631203065571,479635942,1.5214425267450001E9,1.521442527676E9 +4,1833410277002282101,479635942,1.521442529476E9,1.521442531276E9 +4,-6451212302676283085,479635942,1.521442562625E9,1.521442564425E9 +4,-3997782922552825098,479635942,1.521442566195E9,1.521442567036E9 +4,406136036182675561,479635942,1.521442611375E9,1.521442612305E9 +4,8935088791150433135,479635942,1.521442614106E9,1.521442615815E9 +4,5883141217375091076,479635942,1.521442617436E9,1.521442618336E9 +4,-1562504477348617903,479635942,1.521442681996E9,1.521442682895E9 +4,6442162853111072300,479635942,1.521442686405E9,1.521442688085E9 +4,-8281095471555881789,479635942,1.521442690636E9,1.521442691505E9 +4,-1648692117170179720,479635942,1.5214427591230001E9,1.521442760023E9 +4,-8679815270693998385,479635942,1.521442800613E9,1.521442801484E9 +4,-238919976005673680,479635942,1.521442803343E9,1.521442804213E9 +4,2547097415411733833,479635942,1.521442834483E9,1.521442835233E9 +4,17839593406518140,479635942,1.521442838233E9,1.521442839013E9 +4,-1597116562559993379,479635942,1.521442842194E9,1.521442843813E9 +4,1919742531973120014,479635942,1.5214430213830001E9,1.521443022253E9 +4,-8071599083672122831,479635942,1.521443034493E9,1.521443036233E9 +4,-2867097201446592630,479635942,1.521443058912E9,1.521443059663E9 +4,-2856142782889317323,479635942,1.521443063473E9,1.5214430725640001E9 +4,9220563198211307167,479635942,1.521443084053E9,1.521443089513E9 +4,8817697574276404783,479635942,1.521443121072E9,1.521443335302E9 +4,-3238860579118206654,479635942,1.521443450382E9,1.521443451282E9 +4,-3561508015441693492,479635942,1.521443725092E9,1.521443725903E9 +4,6822797498750745699,479635942,1.521443922311E9,1.521443924111E9 +4,4953013065632445643,479635942,1.521444020111E9,1.521444021011E9 +4,7760988766985286192,479635942,1.521444029112E9,1.521444036342E9 +4,-8762038282842783786,479635942,1.521444243252E9,1.521444244182E9 +4,-5350319228689018845,479635942,1.521444258852E9,1.521444260682E9 +4,-8742950905512247522,479635942,1.521444288552E9,1.521444300402E9 +4,1676183236839755511,479635942,1.521444361247E9,1.5214443621460001E9 +4,8139167809038270383,479635942,1.521444488117E9,1.521444489016E9 +4,-2565920918797393891,479635942,1.521444492647E9,1.521444494446E9 +4,-4350399611172187771,479635942,1.521444516048E9,1.521444522167E9 +4,-1153579934777545451,479635942,1.521444577727E9,1.521444580427E9 +4,-8234476568187380094,479635942,1.521444621857E9,1.521444623657E9 +4,-4186649922188925357,479635942,1.521444655367E9,1.521444656957E9 +4,2371002038867060936,479635942,1.521444671537E9,1.521444672287E9 +4,-5163969143044480876,479635942,1.521444682127E9,1.521444683747E9 +4,5899276795438100606,479635942,1.521444707088E9,1.521444713387E9 +4,6165936064654459822,479635942,1.521444744048E9,1.521444744948E9 +4,-805466746891542436,479635942,1.5214447485180001E9,1.521444752808E9 +4,-338115955708042987,479635942,1.521444754548E9,1.521444757069E9 +4,4645870033043675307,479635942,1.521444802098E9,1.521444803658E9 +4,-2250719218250569279,479635942,1.521444817158E9,1.521444818058E9 +4,-1888591562771703483,479635942,1.521444875058E9,1.5214448766790001E9 +4,5349594770159211086,479635942,1.521445000129E9,1.52144500634E9 +4,-5677458873443610006,479635942,1.5214455159520001E9,1.521445536323E9 +4,3345784560919517059,479635942,1.521445543102E9,1.521445607963E9 +4,4918967835272334586,479635942,1.521445616992E9,1.521445657583E9 +4,212834596276290437,479635942,1.521445685993E9,1.521445689593E9 +4,7430485492785476126,479635942,1.521445734864E9,1.521445886598E9 +4,-2978637804348362758,479635942,1.521445888848E9,1.521445916147E9 +4,-5605233993261985758,479635942,1.521445924247E9,1.521445950438E9 +4,8526761030841014695,479635942,1.521445987908E9,1.521445989408E9 +4,7922769253991317306,479635942,1.521445995408E9,1.521446158398E9 +4,1791538812528344202,479635942,1.521446186778E9,1.521446188519E9 +4,7848372095398405158,479635942,1.521446220529E9,1.521446222088E9 +4,133923890735449951,479635942,1.5214462589290001E9,1.521446261629E9 +4,6600398029814266248,479635942,1.521446267029E9,1.5214463070210001E9 +4,2206602076773924495,479635942,1.5214463439490001E9,1.521446346649E9 +4,3101094139201458471,479635942,1.521446347549E9,1.5214463601790001E9 +4,4818368575650506504,479635942,1.521446402599E9,1.52144650466E9 +4,-4248081932763410101,479635942,1.52144653958E9,1.52144654333E9 +4,-7612100732780712818,479635942,1.52144655197E9,1.521446733322E9 +4,978441040342089694,479635942,1.521446741032E9,1.5214467987220001E9 +4,1517119277454205417,479635942,1.5214468174720001E9,1.521446903002E9 +4,-2396236914939302776,479635942,1.521446908432E9,1.521446956552E9 +4,-4087847698739617760,479635942,1.521446975632E9,1.521447029603E9 +4,-5473467692081629597,479635942,1.521447074124E9,1.521447089153E9 +4,-2294853631030743933,479635942,1.521447170845E9,1.521447173183E9 +4,-6907537373787705721,479635942,1.521447178435E9,1.521447179185E9 +4,-1894127036481014615,479635942,1.521447189624E9,1.521447190524E9 +4,6519738396995980928,479635942,1.5214472721560001E9,1.521447272935E9 +4,-5145387251576462634,479635942,1.521447350005E9,1.521447350875E9 +4,5146993993359125762,479635942,1.521447414926E9,1.521447415826E9 +4,4610312666950954573,479635942,1.521447418526E9,1.521447420326E9 +4,-7498215498881490442,479635942,1.521447500426E9,1.521447501176E9 +4,-3600444927354891029,479635942,1.521447580256E9,1.521447581006E9 +4,4354068086212018794,479635942,1.521447641157E9,1.521447642086E9 +4,8693965426244736035,479635942,1.5214476448170002E9,1.521447646617E9 +4,-971911796417160282,479635942,1.521447650218E9,1.521447652917E9 +4,-918411083375874244,479635942,1.521447679678E9,1.5214476805770001E9 +4,-5525241125451737234,479635942,1.521447683188E9,1.521447687447E9 +4,1790624727480502608,479635942,1.521447699629E9,1.521447701878E9 +4,-4049256949871871827,479635942,1.521447749517E9,1.521447750417E9 +4,3529510055304631237,479635942,1.521447752217E9,1.521447753118E9 +4,-1172687350779436820,479635942,1.521447837478E9,1.521447839278E9 +4,-6268652303377976641,479635942,1.521447893368E9,1.521447894119E9 +4,7076946242247979971,479635942,1.52144797353E9,1.52144797443E9 +4,-7715230822356643021,479635942,1.52144799642E9,1.5214479981890001E9 +4,7680507504214796419,479635942,1.52144800248E9,1.521448004219E9 +4,-223108706738756500,479635942,1.52144804517E9,1.521448046969E9 +4,-2881526089818103868,479635942,1.521448047869E9,1.5214480524E9 +4,-8585300386736512915,479635942,1.52144812248E9,1.5214481241000001E9 +4,1499968694765296755,479635942,1.52144812581E9,1.521448128181E9 +8,5879659693373493356,479635942,1.519629388147E9,1.519629389048E9 +8,7000572295194635527,479635942,1.5196294062680001E9,1.519629407168E9 +8,-730751835115696982,479635942,1.519629432278E9,1.519629433177E9 +8,5981485234707577675,479635942,1.51962943492E9,1.519629435788E9 +8,2985217633962242548,479635942,1.519629449107E9,1.519629449858E9 +8,-6221364235205256441,479635942,1.519629453159E9,1.519629455857E9 +8,6500849691958272716,479635942,1.519629469448E9,1.519629470259E9 +8,2514854307262065587,479635942,1.519629471848E9,1.519629473348E9 +8,-8673274891412489569,479635942,1.519629725106E9,1.519629726936E9 +8,-2638264967837450423,479635942,1.519629737826E9,1.519629739625E9 +8,-7220312266624374900,479635942,1.519629816725E9,1.519629819786E9 +8,-7276728189848032775,479635942,1.519630080244E9,1.519630081204E9 +8,6023019915936845420,479635942,1.519630374101E9,1.519630374846E9 +8,-8965577381358949965,479635942,1.519630401935E9,1.519630403436E9 +8,-3508578100474321113,479635942,1.519630460373E9,1.519630462172E9 +8,-3857349432749872481,479635942,1.519630468293E9,1.519630470003E9 +8,1228434321453580016,479635942,1.519630502705E9,1.519630504234E9 +8,-3871080537778103703,479635942,1.519630520282E9,1.519630522772E9 +8,-8620146291320601879,479635942,1.519630527302E9,1.519630530062E9 +8,4760977236215949022,479635942,1.519630622792E9,1.519630638871E9 +8,4045861814072622109,479635942,1.519630711471E9,1.519630714891E9 +8,915524887984917716,479635942,1.519630719573E9,1.519630722572E9 +8,-7611305195042552879,479635942,1.519630744862E9,1.51963076418E9 +8,-7157067388187488120,479635942,1.5196311680970001E9,1.519631324819E9 +8,5762462466114178963,479635942,1.519631360099E9,1.519631434409E9 +8,-529344309654443661,479635942,1.519631470558E9,1.519631482408E9 +8,4933631662716116035,479635942,1.519631506499E9,1.519631511903E9 +8,-7908011693332036987,479635942,1.519631514418E9,1.51963153107E9 +8,7839886249009574762,479635942,1.519631532627E9,1.519631533377E9 +8,-649290018360883606,479635942,1.519631535627E9,1.519631552277E9 +8,293834598050901991,479635942,1.519631554977E9,1.519631746106E9 +8,1256100537099493447,479635942,1.519631769386E9,1.519631819365E9 +8,6644786439117645530,479635942,1.519631910987E9,1.519631913717E9 +8,-7497080258802514379,479635942,1.5196319263470001E9,1.519631929137E9 +8,4622769510674872004,479635942,1.519631972369E9,1.519631999817E9 +8,-1141953560934671341,479635942,1.519632031407E9,1.519632051386E9 +8,5349087180229490448,479635942,1.519632065395E9,1.519632072085E9 +8,-4747120930623285308,479635942,1.51963209054E9,1.519632132746E9 +8,-4377259361624472221,479635942,1.519632133647E9,1.519632136346E9 +8,2292475001329359162,479635942,1.519632137276E9,1.519632153566E9 +8,5431245649784024517,479635942,1.519632190795E9,1.519632209335E9 +8,42979432707295922,479635942,1.519632229255E9,1.519632722964E9 +8,3984345857400187262,479635942,1.519632907794E9,1.519632908725E9 +8,3830666979235437205,479635942,1.519632996714E9,1.519632997464E9 +8,-2342396138591788338,479635942,1.519633009105E9,1.519633010604E9 +8,5897253987423056632,479635942,1.519633087855E9,1.519633088633E9 +8,-7952629300780611977,479635942,1.519633091096E9,1.519633091933E9 +8,1405574861634135866,479635942,1.5196331022220001E9,1.519633103034E9 +8,5451425371067267955,479635942,1.519633184813E9,1.519633185682E9 +8,7083620308083773677,479635942,1.5196332462220001E9,1.519633248025E9 +8,1210433639115223226,479635942,1.519633351311E9,1.519633352211E9 +8,3002616498141307069,479635942,1.519633528432E9,1.519633529362E9 +8,-7229575619417935795,479635942,1.519633599201E9,1.519633600131E9 +8,6291220743044631006,479635942,1.519633675611E9,1.519633676511E9 +8,3527089241349133991,479635942,1.51963375181E9,1.51963375271E9 +8,-6554336874573040199,479635942,1.519634057631E9,1.519634058531E9 +4,-8813298524014584704,627460548,1.521443122766E9,1.521443341766E9 +4,1531767722795445557,627460548,1.521444889766E9,1.521444892766E9 +4,-7110809316548994353,627460548,1.521445068766E9,1.521445089766E9 +4,655350747368713556,627460548,1.521445092766E9,1.521445101766E9 +4,5930162479930420653,627460548,1.521445114766E9,1.521445459766E9 +4,5206709799691970442,627460548,1.521445469766E9,1.521445666766E9 +4,-959897090936021781,627460548,1.521445671766E9,1.521445697766E9 +4,-7680723325071274924,627460548,1.521445698766E9,1.521445700766E9 +4,204882228613436029,627460548,1.521445703766E9,1.521445707766E9 +4,6407896818791341170,627460548,1.521445711766E9,1.521445721766E9 +4,428153403250121348,627460548,1.521445727766E9,1.521446182766E9 +4,4288176115098687383,627460548,1.521446355766E9,1.521446512766E9 +4,-7547315903682837562,627460548,1.521446521766E9,1.521446526766E9 +4,-7399436758347559255,627460548,1.521446529766E9,1.521446804766E9 +4,2046858044437825919,627460548,1.521446808766E9,1.521447102766E9 +4,-5936044964195480951,627460548,1.521447110766E9,1.521447121766E9 +4,-6954135686990999696,627460548,1.521447127766E9,1.521447169766E9 +8,-9137750955228826260,627460548,1.519629824513E9,1.519629848513E9 +8,-6908759077158596339,627460548,1.519629914513E9,1.519629941513E9 +8,-7530471852639074558,627460548,1.519630624513E9,1.519630637513E9 +8,-954170402881087712,627460548,1.519630720513E9,1.519630864513E9 +8,-7274891943979374522,627460548,1.519630867513E9,1.519631834513E9 +8,-743390810149580574,627460548,1.519632036513E9,1.519632048513E9 +8,-9020241438604905728,627460548,1.519632065513E9,1.519632136513E9 +8,-7073068529258976682,627460548,1.519632144513E9,1.519632156513E9 +8,428814765587638922,627460548,1.519632164513E9,1.519632215513E9 +8,-7434917374319927985,627460548,1.519632222513E9,1.519632789513E9 +8,-1350785415966073806,627460548,1.519632797513E9,1.519632823513E9 +20,-5382481647183198032,479635942,1.4998442261620002E9,1.4998442269420002E9 +20,1398528250319112894,479635942,1.499844250904E9,1.499844252492E9 +20,1693662272651064915,479635942,1.4998442956920002E9,1.499844296592E9 +20,-7325575978669824068,479635942,1.499844331752E9,1.4998443335240002E9 +20,7860348933928939920,479635942,1.499844357582E9,1.499844358423E9 +20,-3864232224800149754,479635942,1.499844360102E9,1.499844361003E9 +20,-985755583265118537,479635942,1.499844485713E9,1.499844487364E9 +20,-393482977650394470,479635942,1.499844502963E9,1.499844504463E9 +20,6392595503183202572,479635942,1.499844505964E9,1.499844506714E9 +20,-408288388429413362,479635942,1.4998446221850002E9,1.499844622937E9 +20,-4538912419618731587,479635942,1.4998446251850002E9,1.4998446259380002E9 +20,-842343570071425163,479635942,1.499844662025E9,1.4998446648760002E9 +20,-6265993917731982606,479635942,1.4998449551260002E9,1.499844963227E9 +20,-1509884689013456963,479635942,1.4998449641260002E9,1.4998450392480001E9 +20,-1744524171501944267,479635942,1.499845059288E9,1.4998450884780002E9 +20,142168371532837201,479635942,1.4998450930080001E9,1.499845156038E9 +20,-655704192365399504,479635942,1.499845159068E9,1.499845235109E9 +20,4058817743057105228,479635942,1.49984523766E9,1.49984529817E9 +20,4502167199387201203,479635942,1.499845381872E9,1.499845382622E9 +20,-1644303620433773352,479635942,1.499845550112E9,1.499845551012E9 +20,-6559419888634467752,479635942,1.499845664023E9,1.499845664923E9 +20,-4547895194101985037,479635942,1.4998456893730001E9,1.499845690273E9 +20,-5472266330908296277,479635942,1.4998457175440001E9,1.499845718503E9 +20,926430593630520281,479635942,1.499845743943E9,1.499845745533E9 +20,3904926934982446797,479635942,1.499845756934E9,1.499845759364E9 +20,-2392288144699125293,479635942,1.499845760927E9,1.4998457633860002E9 +20,-1830800002427358914,479635942,1.499845906838E9,1.499845909598E9 +20,-8873520486869837015,479635942,1.499845953279E9,1.4998459578990002E9 +20,-4368514347912812486,479635942,1.4998460836660001E9,1.499846086359E9 +20,-1428421493148291899,479635942,1.4998465025800002E9,1.499846503418E9 +20,-4678123555691334308,479635942,1.499846668749E9,1.4998466695310001E9 +20,-2476468622354613631,479635942,1.4998469973690002E9,1.499847001898E9 +20,-2375692227549851517,479635942,1.4998470637610002E9,1.499847066519E9 +20,-6019814139971918226,479635942,1.4998470701490002E9,1.499847074709E9 +20,-1640692660151311931,479635942,1.49984729926E9,1.49984730151E9 +20,923067085950549949,479635942,1.49984730913E9,1.4998473114120002E9 +20,44731912975200737,479635942,1.4998474519610002E9,1.499847464832E9 +20,-7378325069341625500,479635942,1.499847491982E9,1.499847503742E9 +20,379793201703886583,479635942,1.499847521023E9,1.499847523693E9 +20,5436189141174789783,479635942,1.499847538782E9,1.499847539622E9 +20,1233072008490193348,479635942,1.499847592503E9,1.4998475933130002E9 +20,5206319406890702596,479635942,1.499847596043E9,1.499847596943E9 +20,-5352596564898023182,479635942,1.4998476005730002E9,1.499847601473E9 +20,1907283039276208634,479635942,1.499847673444E9,1.499847675184E9 +20,7751745042807262957,479635942,1.499847693035E9,1.4998476946230001E9 +20,7109650448824691457,479635942,1.499847729756E9,1.499847730655E9 +20,4322196049344734775,479635942,1.499847755164E9,1.499847756065E9 +20,5577902752255699992,627460548,1.49984495666E9,1.49984531266E9 +20,-3117896488428308513,627460548,1.49984584366E9,1.49984584666E9 +20,-3089668326737931626,627460548,1.49984684266E9,1.49984690166E9 +20,1285556521498637281,627460548,1.49984723466E9,1.49984724866E9 +20,3972572812869066761,627460548,1.49984744366E9,1.49984750066E9 +44,-4637271998962938849,479635942,1.499282411138E9,1.499282412669E9 +44,-2092229975352822834,479635942,1.499282577219E9,1.49928257902E9 +44,1049913845051400955,479635942,1.499282580849E9,1.49928258175E9 +44,-211215896574089824,479635942,1.4992826098E9,1.4992826107E9 +44,7370248448609067639,479635942,1.49928264151E9,1.49928264244E9 +44,-1778957851715528286,479635942,1.49928264424E9,1.49928264592E9 +44,1389191646178811019,479635942,1.49928264769E9,1.49928264853E9 +44,8168243402445675110,479635942,1.499282683781E9,1.49928268762E9 +44,-8876888668219959148,479635942,1.49928268912E9,1.499282692121E9 +44,7475017446856161645,479635942,1.499282697521E9,1.49928269902E9 +44,-3723394765263766759,479635942,1.499282976308E9,1.499283028569E9 +44,-7429615039432726871,479635942,1.499283060339E9,1.499283088119E9 +44,1347874962452191384,479635942,1.499283101649E9,1.499283169359E9 +44,-9217296491176434890,479635942,1.499283201401E9,1.4992832181399999E9 +44,-314125919632192747,479635942,1.49928322816E9,1.49928326851E9 +44,-6140603891082981173,479635942,1.499283281681E9,1.499283520268E9 +44,-7857330425916769274,479635942,1.499283713798E9,1.4992838484129999E9 +44,4295255570355324601,479635942,1.499283897665E9,1.499283901267E9 +44,-3305953162844076328,479635942,1.499284063793E9,1.499284140106E9 +44,2210778841204328361,479635942,1.499284150089E9,1.499284233081E9 +44,2406270417491863861,479635942,1.499284236466E9,1.499284271556E9 +44,1394152169360239198,479635942,1.499284289762E9,1.499284302164E9 +44,-4566322393517920709,479635942,1.49928431619E9,1.4992843618669999E9 +44,-1010041142446435004,479635942,1.499284527249E9,1.499284528954E9 +44,-3666842480733855395,479635942,1.499284550624E9,1.499284553099E9 +44,10930907405632125,479635942,1.499284671772E9,1.499284673835E9 +44,-9110182607008228626,479635942,1.499284760212E9,1.49928476211E9 +44,7798407804141648331,479635942,1.499284764007E9,1.4992847659589999E9 +44,8115514891653413031,479635942,1.499284954391E9,1.499284956619E9 +44,7560449628883860911,479635942,1.499284967315E9,1.499284970066E9 +44,-5320048537236409798,479635942,1.499284985824E9,1.499284988628E9 +44,5940334651139579011,479635942,1.499285026386E9,1.499285028889E9 +44,6387616634846144065,479635942,1.499285121702E9,1.499285125662E9 +44,6686525378666461145,479635942,1.499285161298E9,1.499285163526E9 +44,7483199910247397278,479635942,1.4992851863799999E9,1.49928518836E9 +44,1815115588707164796,479635942,1.499285238437E9,1.49928524248E9 +44,-6508976704422882408,627460548,1.499282707418E9,1.499282735418E9 +44,2561808373245313648,627460548,1.499282741418E9,1.499282797418E9 +44,8196876697210806276,627460548,1.499282968418E9,1.499283035418E9 +44,-7862908639673745989,627460548,1.499283036418E9,1.499283038418E9 +44,-7552396558642230191,627460548,1.499283044418E9,1.499283174418E9 +44,-5547151162994732173,627460548,1.499283195418E9,1.499283553418E9 +44,3989077605622453340,627460548,1.499283629418E9,1.499283640418E9 +44,7678424484398778148,627460548,1.499283646418E9,1.499283653418E9 +44,5042189237814686589,627460548,1.499283696418E9,1.499283871418E9 +44,-1014649183845097465,627460548,1.499283914418E9,1.499284420418E9 +44,-7074608251705529132,627460548,1.499284422418E9,1.499284470418E9 +44,1926196190632142284,627460548,1.499284919418E9,1.499284944418E9 +30,1381765217366987910,479635942,1.521702918745E9,1.521702919645E9 +30,-3294655792050400975,479635942,1.5217029213539999E9,1.521702922974E9 +30,-5718600010759177330,479635942,1.5217029245349998E9,1.521702926124E9 +30,3844435200978857876,479635942,1.521702975893E9,1.521702976793E9 +30,-1162217087515206821,479635942,1.521703091513E9,1.5217030924129999E9 +30,-6122269681650267389,479635942,1.521703095113E9,1.521703096013E9 +30,7536986203953647695,479635942,1.521703148689E9,1.5217031507289999E9 +30,2325169921450009372,479635942,1.521703263049E9,1.521703264309E9 +30,-996106118149702428,479635942,1.521703364507E9,1.5217033673869998E9 +30,-5746591137419452351,479635942,1.521703435787E9,1.521703669516E9 +30,-6273515633008229200,479635942,1.521703670266E9,1.521703671105E9 +30,-4479825348419641412,479635942,1.521703757295E9,1.521703758165E9 +30,-9082238007086586498,479635942,1.521703822694E9,1.521703823595E9 +30,3594338235991473819,479635942,1.521703849725E9,1.521703850626E9 +30,4242300907256383083,479635942,1.521703878554E9,1.521703879485E9 +30,-7665876455377814136,479635942,1.5217038977849998E9,1.521703900874E9 +30,7917895577705523388,479635942,1.5217039247549999E9,1.5217039398439999E9 +30,959787402597842355,479635942,1.5217039480049999E9,1.521703952564E9 +30,8055958848017718547,479635942,1.521704363023E9,1.521704369863E9 +30,-5472820722516914569,479635942,1.521704411353E9,1.521704412253E9 +30,2623397767340082296,479635942,1.521704521303E9,1.521704522203E9 +30,-2839886472099990582,479635942,1.521704630113E9,1.5217046319129999E9 +30,-6801935567505257201,479635942,1.521704644573E9,1.521704646373E9 +30,6368471110432347330,479635942,1.521704708953E9,1.521704709703E9 +30,4800062430288851269,479635942,1.5217047346629999E9,1.521704735563E9 +30,4010981228850596464,479635942,1.521705706694E9,1.521705708944E9 +30,-1469401792899758712,479635942,1.521705786853E9,1.5217057876929998E9 +30,6914175599468825885,479635942,1.521705789314E9,1.521705791564E9 +30,-7960810170750439193,479635942,1.521705795464E9,1.521705799964E9 +30,-7593077001334727309,479635942,1.521705843343E9,1.521705844243E9 +30,8541418143942666736,479635942,1.521705887624E9,1.521705888434E9 +30,-8999900358435167988,479635942,1.521705890024E9,1.521705890773E9 +30,-7449604252464815325,479635942,1.521705915674E9,1.521705918014E9 +30,6640854498431894253,479635942,1.521705918763E9,1.521705926353E9 +30,7731870956717998413,479635942,1.521705929053E9,1.521705931753E9 +30,7389418419162051529,479635942,1.5217059461829998E9,1.521705947984E9 +30,7289728920100530745,479635942,1.521705956083E9,1.521705956983E9 +30,-7120581446131048506,479635942,1.521705962383E9,1.521705964213E9 +30,-1219176403905642378,479635942,1.521706014194E9,1.521706014944E9 +30,5189264411278701918,479635942,1.521706017194E9,1.521706020944E9 +50,8533858820337304398,479635942,1.521744183215E9,1.5217441841139998E9 +50,3560356231578910845,479635942,1.5217442881839998E9,1.5217442890839999E9 +50,1748575149657710250,479635942,1.521744290885E9,1.5217442917849998E9 +50,-8799249892086896195,479635942,1.5217443249699998E9,1.521744326949E9 +50,-8642126730840857825,479635942,1.521744328839E9,1.5217443307589998E9 +50,-6169911094748600160,479635942,1.521744390847E9,1.521744392136E9 +50,8907508074867811719,479635942,1.521744393487E9,1.521744394896E9 +50,3895291104380527435,479635942,1.521744396245E9,1.521744397596E9 +50,168185476870227533,479635942,1.521744435636E9,1.5217444369269998E9 +50,8636852723142439205,479635942,1.5217444623669999E9,1.5217444648569999E9 +50,-5847807258660539548,479635942,1.521744471156E9,1.521744473406E9 +50,-9189687574318445053,479635942,1.521744588066E9,1.521744589326E9 +50,-5100840284981432430,479635942,1.521744616866E9,1.5217446195649998E9 +50,4497487509453454711,479635942,1.521744643656E9,1.521744645096E9 +50,4004871161195546717,479635942,1.5217446637849998E9,1.5217446652559998E9 +50,-8389117530910320304,479635942,1.5217446711049998E9,1.5217446765049999E9 +50,-3401963401424953099,479635942,1.521744681635E9,1.5217446866439998E9 +50,-5251821711440645252,479635942,1.521744687816E9,1.521744688985E9 +50,-1529408046372439742,479635942,1.5217447545049999E9,1.5217447556769998E9 +50,4472694011216418964,479635942,1.5217448977549999E9,1.521744898896E9 +50,-1876002449511129254,479635942,1.5217450684529998E9,1.5217450696239998E9 +50,-4703935011461701790,479635942,1.521745121073E9,1.521745122304E9 +50,4193874263778369621,479635942,1.521745144323E9,1.5217451456139998E9 +50,-6670978770975637758,479635942,1.521745148883E9,1.5217451546439998E9 +50,2026699222657945396,479635942,1.5217451569829998E9,1.521745159415E9 +50,-8737691554683567301,479635942,1.5217451874039998E9,1.5217451886039999E9 +50,-1593609965153129060,479635942,1.521745301373E9,1.5217453025129998E9 +50,4768164188694678881,479635942,1.5217453108839998E9,1.521745312054E9 +50,8956058034066115685,479635942,1.521745344155E9,1.521745345204E9 +50,4568560792602626850,479635942,1.5217454373339999E9,1.5217454384139998E9 +50,-6966616625484529104,479635942,1.521745474264E9,1.5217454753739998E9 +50,1678778636347263160,479635942,1.5217454881839998E9,1.521745489324E9 +50,2128464950379447278,479635942,1.521745519293E9,1.5217455446739998E9 +50,8230071782802070799,479635942,1.521745762324E9,1.5217457659529998E9 +50,-9163939346262825256,479635942,1.5217463259029999E9,1.5217463394329998E9 +50,-810766040702141218,479635942,1.5217464237329998E9,1.5217465564529998E9 +50,-4497038220427467687,479635942,1.521746732074E9,1.521746733574E9 +50,4117200318478077002,479635942,1.521746735074E9,1.521746736574E9 +50,-8431845267733393171,479635942,1.521746793273E9,1.521746797774E9 +50,-7472050666034561536,479635942,1.521746832724E9,1.521746833533E9 +50,3747544507295065148,479635942,1.5217468984529998E9,1.521746899353E9 +50,-2387977556999851591,479635942,1.5217469234129999E9,1.5217469251829998E9 +50,-7271451754365306737,479635942,1.521746951554E9,1.521746955304E9 +80,2659083652218610721,479635942,1.519154662783E9,1.519154663681E9 +80,-1129925315249971514,479635942,1.5191547773530002E9,1.519154778252E9 +80,-4128199781180746250,479635942,1.5191549621520002E9,1.5191549637410002E9 +80,-5165781753239550393,479635942,1.519154965302E9,1.5191549668030002E9 +80,2499630658728324810,479635942,1.519154990322E9,1.5191549912210002E9 +80,1360364372284828260,479635942,1.519155001181E9,1.519155002081E9 +80,8401671525057267390,479635942,1.519155003941E9,1.5191550057410002E9 +80,-7238246847319510538,479635942,1.5191550075410001E9,1.5191550084720001E9 +80,2509485127681747059,479635942,1.5191550480110002E9,1.519155048911E9 +80,90187419845953120,479635942,1.5191550515210001E9,1.5191550532010002E9 +80,6589972329856057218,479635942,1.5191550548820002E9,1.5191550557220001E9 +80,6186168014992745881,479635942,1.51915507126E9,1.519155072763E9 +80,3737636894400622299,479635942,1.5191551134650002E9,1.519155115857E9 +80,6803764460634835263,479635942,1.519155239441E9,1.51915524167E9 +80,6250314760774739554,479635942,1.519155267407E9,1.519155268865E9 +80,-1477778064799997236,479635942,1.5191552701850002E9,1.5191552733200002E9 +80,8485855056433952408,479635942,1.5191552753820002E9,1.519155279342E9 +80,5078557318103571687,479635942,1.519155281239E9,1.519155284872E9 +80,5822866718800208402,479635942,1.519155304092E9,1.519155306237E9 +80,-1837489621806747089,479635942,1.5191553166890001E9,1.519155320676E9 +80,2837691774257628004,479635942,1.5191553870860002E9,1.5191553892310002E9 +80,-940835999679064243,479635942,1.519155448355E9,1.5191554504450002E9 +80,-9000932918796050575,479635942,1.519155458696E9,1.5191554690900002E9 +80,627956856981620656,479635942,1.5191554964E9,1.519155500772E9 +80,-555598077542216994,479635942,1.519155519252E9,1.519155525608E9 +80,-5205561935037158310,479635942,1.5191556829320002E9,1.519155685242E9 +30,-3107531139122921790,627460548,1.521703411744E9,1.521703751744E9 +30,-7253452917277046534,627460548,1.521704814744E9,1.521704827744E9 +30,6492374719586161949,627460548,1.521704963744E9,1.521704977744E9 +30,-790365411172299920,627460548,1.521704991744E9,1.521705030744E9 +30,-9047979532457185798,627460548,1.521705036744E9,1.521705093744E9 +30,977880050342378422,627460548,1.521705105744E9,1.521705355744E9 +30,5712233931133130309,627460548,1.521705370744E9,1.521705392744E9 +30,309689767334852799,627460548,1.521705595744E9,1.521705649744E9 +50,-694096982042669143,627460548,1.521744487314E9,1.521744510314E9 +50,-4090817667259290754,627460548,1.521745489314E9,1.521745640314E9 +50,2094500064346394083,627460548,1.521745659314E9,1.521745698314E9 +50,-4635247095524136080,627460548,1.521745779314E9,1.521745794314E9 +50,4443598965928952453,627460548,1.521745966314E9,1.521746018314E9 +50,7015174958425370913,627460548,1.521746021314E9,1.521746071314E9 +50,1115101793338262647,627460548,1.521746091314E9,1.521746106314E9 +50,5083786052484355976,627460548,1.521746117314E9,1.521746128314E9 +50,-4055757595066983206,627460548,1.521746133314E9,1.521746163314E9 +50,5091998011161160946,627460548,1.521746170314E9,1.521746182314E9 +50,-4820716226830569406,627460548,1.521746220314E9,1.521746236314E9 +50,998315113627117151,627460548,1.521746266314E9,1.521746267314E9 +50,4546258366444818094,627460548,1.521746297314E9,1.521746563314E9 +33,3858430809643087710,479635942,1.501924896766E9,1.501924897815E9 +33,3896987748868385804,479635942,1.501924935825E9,1.501924936964E9 +33,-7446762863357396447,479635942,1.5019249417640002E9,1.501924946657E9 +33,-4496013176797839263,479635942,1.501925029635E9,1.5019250307450001E9 +33,5369056297001197612,479635942,1.501925051657E9,1.501925055227E9 +33,2767786118610359050,479635942,1.501925123357E9,1.501925134007E9 +33,9150604624774486415,479635942,1.501925192599E9,1.5019251958070002E9 +33,6737505784756924791,479635942,1.501925330354E9,1.501925331584E9 +33,948508249011544707,479635942,1.501925719637E9,1.501925990655E9 +33,2268916892944896373,479635942,1.5019259918860002E9,1.5019260012440002E9 +33,-3545040679921350089,479635942,1.5019260167540002E9,1.5019261027350001E9 +33,-8469051943704654215,479635942,1.501926137145E9,1.5019261395170002E9 +33,4080093433785993674,479635942,1.501926171798E9,1.501926235907E9 +33,-5586518668884266751,479635942,1.501926261801E9,1.5019264441330001E9 +33,-8587186543032205764,479635942,1.501926634125E9,1.501926637724E9 +33,-3012571462123890843,479635942,1.501926657283E9,1.5019266597150002E9 +33,-1188695500818149594,479635942,1.501926668026E9,1.501926704174E9 +33,4693559876971234790,479635942,1.501926711256E9,1.501926722805E9 +33,4695195228484392284,479635942,1.501926792132E9,1.5019269320830002E9 +33,3212023215827207735,479635942,1.5019269651430001E9,1.5019269744750001E9 +33,-1203206319294200628,479635942,1.501927005736E9,1.501927006903E9 +33,-7628795270406202699,479635942,1.5019270360040002E9,1.5019270387050002E9 +33,-7465130733264694093,479635942,1.501927042184E9,1.501927053553E9 +33,6382529754415590698,479635942,1.501927085565E9,1.501927087604E9 +33,-646205837486144512,479635942,1.501927088864E9,1.501927091204E9 +33,-4164187262230234980,479635942,1.501927221793E9,1.501927223029E9 +33,3887495905483113511,479635942,1.501927246332E9,1.5019272566520002E9 +33,-3433391353912368987,479635942,1.5019273005730002E9,1.501927301822E9 +33,377956745368926078,479635942,1.501927310263E9,1.501927313832E9 +33,1846498266370811436,479635942,1.501927522428E9,1.501927523622E9 +33,-4887754648942702068,479635942,1.5019278928040001E9,1.501927895233E9 +33,-4023362769939516434,479635942,1.501928304342E9,1.5019283054880002E9 +33,-6555220640315993103,479635942,1.5019285644120002E9,1.501928565553E9 +33,2850759686925681774,479635942,1.5019285857740002E9,1.501928587002E9 +33,5658773340969934577,479635942,1.501928826586E9,1.501928828233E9 +33,-4486385315453128463,479635942,1.50192883819E9,1.501928839303E9 +33,7374836464985291244,479635942,1.5019288833140001E9,1.501928884302E9 +33,5297731012355832898,479635942,1.5019289267510002E9,1.5019289279810002E9 +33,-6853049530670363515,479635942,1.501928935931E9,1.501928939173E9 +33,4077286640672801070,479635942,1.5019289602310002E9,1.501928961581E9 +33,-7936874687764443097,479635942,1.501928963951E9,1.501928968753E9 +33,8129726187503205449,479635942,1.501928969801E9,1.501928982371E9 +33,-4798691110118906969,479635942,1.501928984591E9,1.501928995181E9 +33,8307401047263836530,479635942,1.501929013783E9,1.501929014802E9 +33,-8911702973600911726,479635942,1.5019290178600001E9,1.50192902101E9 +33,2925354630311382579,479635942,1.5019290287210002E9,1.501929033311E9 +33,352973918942053231,479635942,1.501929054161E9,1.501929059681E9 +33,-8510895984176683422,479635942,1.5019290642410002E9,1.5019290694320002E9 +33,4980285528909552251,627460548,1.501925064614E9,1.501925073614E9 +33,-1379172195619850954,627460548,1.501925074614E9,1.501925153614E9 +33,5391808398998002444,627460548,1.501925159614E9,1.501925257614E9 +33,8874624227850671002,627460548,1.501925721614E9,1.501925978614E9 +33,2594020836664985310,627460548,1.501925982614E9,1.501925983614E9 +33,-7079084592751185452,627460548,1.501925993614E9,1.501925999614E9 +33,-6105182787576561544,627460548,1.501926018614E9,1.501926102614E9 +33,-6627363941945925880,627460548,1.501926137614E9,1.501926240614E9 +33,3544332513814905914,627460548,1.501926248614E9,1.501926464614E9 +33,-2255062831619284382,627460548,1.501926716614E9,1.501926728614E9 +33,-1779238583978771108,627460548,1.501926741614E9,1.501926980614E9 +10,-694329848772155884,479635942,1.519925040411E9,1.5199250413409998E9 +10,-3848674829203599271,479635942,1.5199250516309998E9,1.51992505253E9 +10,-6466927667216822707,479635942,1.51992511607E9,1.519925116971E9 +10,-2046327642399210634,479635942,1.5199251188009999E9,1.519925119702E9 +10,-221387692078910765,479635942,1.519925180451E9,1.519925181201E9 +10,2370011032020554035,479635942,1.519925202291E9,1.51992521078E9 +10,8567537360142041036,479635942,1.519925315689E9,1.519925317188E9 +10,6894961929101351522,479635942,1.5199256889469998E9,1.519925689849E9 +10,-5228549865492255678,479635942,1.5199258006409998E9,1.5199258015089998E9 +10,-1080641528184924837,479635942,1.5199258042099998E9,1.519925805109E9 +10,253334587267668212,479635942,1.5199258187289999E9,1.519925819628E9 +10,2294338982017915622,479635942,1.519925828689E9,1.5199258295879998E9 +10,5411491795584465178,479635942,1.5199259913799999E9,1.519925992217E9 +10,-2994815581710315378,479635942,1.519926061007E9,1.5199260619069998E9 +10,-2036881050902187208,479635942,1.5199262204259999E9,1.519926221297E9 +10,1948899031500132796,479635942,1.5199262246869998E9,1.5199262272649999E9 +10,3587645585241922140,479635942,1.519926335355E9,1.5199263371239998E9 +10,2324899470920976319,479635942,1.5199264975059998E9,1.519926498254E9 +10,616367067398025496,479635942,1.51992680651E9,1.5199268087589998E9 +10,4184109330493839929,479635942,1.5199268110089998E9,1.5199268132879999E9 +10,-4055601633496161725,479635942,1.5199268725679998E9,1.5199268743679998E9 +10,6223347576632231661,479635942,1.519926923507E9,1.519926924257E9 +10,-702527363376483355,479635942,1.519926926569E9,1.519926928067E9 +10,-1745195706842959513,479635942,1.5199269798479998E9,1.519926980777E9 +10,-4932122414423095672,479635942,1.5199270305749998E9,1.519927032405E9 +10,-3725820379386398582,479635942,1.5199270350159998E9,1.5199270359169998E9 +10,5592294175963738021,479635942,1.519927446105E9,1.519927447007E9 +10,-5287526182807978540,479635942,1.5199274931139998E9,1.519927500254E9 +10,-3855756482143877845,479635942,1.519927513245E9,1.519927679323E9 +10,951970005387399458,479635942,1.519927723033E9,1.5199277239329998E9 +10,-8849369739873446858,479635942,1.5199277725619998E9,1.519927785612E9 +10,-8797672936030168315,479635942,1.519928057951E9,1.5199280602849998E9 +10,3920538051563461167,479635942,1.5199281422089999E9,1.5199281442719998E9 +10,5767703135856322273,479635942,1.519928446632E9,1.5199287098869998E9 +10,9209642235166957271,479635942,1.5199289819459999E9,1.5199289827719998E9 +10,-2885841343153770496,479635942,1.519928988573E9,1.519928991103E9 +10,8500861711369598034,479635942,1.519929011921E9,1.5199290135709999E9 +10,7981669223981208322,479635942,1.519929091919E9,1.5199290927429998E9 +10,-9089444996569473499,479635942,1.5199291193899999E9,1.51992912107E9 +10,7545106266455823383,479635942,1.519929224439E9,1.519929225264E9 +10,-6124408801329058095,479635942,1.5199292278209999E9,1.519929228646E9 +10,1613508512581420285,479635942,1.519929343155E9,1.51992934398E9 +10,9002152297424685439,479635942,1.5199293854499998E9,1.5199293863019998E9 +10,664422771803581537,627460548,1.519927417475E9,1.519927422475E9 +10,7555262360710436896,627460548,1.519927494475E9,1.519927499475E9 +10,-4236829700110294690,627460548,1.519927514475E9,1.519927521475E9 +10,-3885485739390708138,627460548,1.519927522475E9,1.519927679475E9 +10,9141327326280738106,627460548,1.519927774475E9,1.519927830475E9 +10,455065934460074031,627460548,1.519927889475E9,1.519927896475E9 +10,-6031221678959024738,627460548,1.519928233475E9,1.519928252475E9 +10,-6029566416850502386,627460548,1.519928276475E9,1.519928287475E9 +10,297369570454899712,627460548,1.519928363475E9,1.519928726475E9 +40,-2128667663786149119,479635942,1.499886201476E9,1.499886202405E9 +40,-4499492632572217994,479635942,1.499886231506E9,1.499886232406E9 +40,-1403285438922050221,479635942,1.499886256496E9,1.499886258266E9 +40,1759348061655052581,479635942,1.499886292019E9,1.49988629352E9 +40,4359680840207732098,479635942,1.499886297418E9,1.499886299006E9 +40,2229694917955397248,479635942,1.499886302546E9,1.499886304346E9 +40,6083415027352742946,479635942,1.499886318926E9,1.499886324358E9 +40,-4351048654858054263,479635942,1.499886363387E9,1.499886364287E9 +40,-5166894204459964314,479635942,1.499886366987E9,1.499886367857E9 +40,2311082647342722875,479635942,1.499886402957E9,1.499886403679E9 +40,-8157414216892400851,479635942,1.499886407517E9,1.499886409017E9 +40,6368382748637709167,479635942,1.499886426658E9,1.499886428968E9 +40,3850793345159264120,479635942,1.499886432869E9,1.499886437638E9 +40,6848189831090944004,479635942,1.49988644344E9,1.49988644494E9 +40,-1336082787424414530,479635942,1.499887350218E9,1.499887350999E9 +40,5318976844826443487,479635942,1.499887750627E9,1.499887786432E9 +40,2482749292040363445,479635942,1.499887791301E9,1.499887795892E9 +40,-2415561457730032252,479635942,1.499887799467E9,1.4998879042350001E9 +40,-3705638154481773507,479635942,1.49988796402E9,1.499888003263E9 +40,177432733303531730,479635942,1.499888027243E9,1.499888031396E9 +40,-5406904514118003571,479635942,1.4998880330740001E9,1.499888036455E9 +40,5118318927660914446,479635942,1.499888321083E9,1.499888321908E9 +40,3506822557916821954,479635942,1.499888360353E9,1.499888362003E9 +40,-2542096204815812361,479635942,1.499888398491E9,1.499888401819E9 +40,-8787317948707902132,479635942,1.49988846199E9,1.499888463695E9 +40,-4399798985995798920,479635942,1.49988856627E9,1.49988856792E9 +40,811789926456241423,479635942,1.4998885687450001E9,1.499888570395E9 +40,-6031606375200563222,479635942,1.499888650476E9,1.499888660431E9 +40,-3682565786674385527,479635942,1.499888714331E9,1.499888715156E9 +40,-8512495387199779420,479635942,1.499888719308E9,1.499888720986E9 +40,5293617543245991074,479635942,1.499888722636E9,1.499888723489E9 +40,-1431035558974961442,479635942,1.499888764161E9,1.499888764986E9 +40,-6284082264885696337,479635942,1.499888785832E9,1.499888786657E9 +40,8189941698387242275,479635942,1.499888789187E9,1.499888790012E9 +40,8670087417793525524,479635942,1.499888826582E9,1.499888827407E9 +40,-3886100699827300385,479635942,1.499888885653E9,1.49988888821E9 +40,-8741274859527454375,479635942,1.499889001428E9,1.499889002253E9 +46,-3386680420861006908,479635942,1.5188674600440001E9,1.518867461755E9 +46,5301728692581838984,479635942,1.518867476839E9,1.518867477588E9 +46,-2763392567196711517,479635942,1.5188676260240002E9,1.5188676278240001E9 +46,8331190510886899861,479635942,1.518867700515E9,1.5188677024020002E9 +46,-713334474755320980,479635942,1.518867733002E9,1.518867734503E9 +46,-9174905736468618724,479635942,1.518867812022E9,1.518867813523E9 +46,-8113014737180214521,479635942,1.5188678151420002E9,1.518867816673E9 +46,-7465942728863565363,479635942,1.518867824778E9,1.518867825581E9 +46,-1101342095754698007,479635942,1.5188678552220001E9,1.518867857022E9 +46,5205541789616543816,479635942,1.5188678634120002E9,1.518867864341E9 +46,-8343736430450837871,479635942,1.518868089913E9,1.5188683851100001E9 +46,6485147502421143464,479635942,1.5188686780600002E9,1.518868684421E9 +46,-1346652621999171051,479635942,1.51886869177E9,1.518868693601E9 +46,7554914135213063768,479635942,1.518868852299E9,1.518868854999E9 +46,-6398888877273128820,479635942,1.5188688686200001E9,1.5188688732080002E9 +46,-1554822601177516152,479635942,1.518869094247E9,1.5188691033370001E9 +46,1962520126706657240,479635942,1.518869118368E9,1.518869121368E9 +46,-6782345448233934725,479635942,1.518869152299E9,1.518869154999E9 +46,5680745123160974426,479635942,1.518869167719E9,1.518869169519E9 +46,4243868082702061061,479635942,1.5188693279480002E9,1.5188693305270002E9 +46,-1566829234809142319,479635942,1.518869331367E9,1.5188693339480002E9 +46,-1847964570088236835,479635942,1.518869484397E9,1.5188694851460001E9 +46,-8209653442060560603,479635942,1.5188694935470002E9,1.518869494447E9 +46,-3564531886365618265,479635942,1.5188697855640001E9,1.518869787154E9 +46,8338501812493172092,479635942,1.5188698863330002E9,1.518869887233E9 +40,5734455086234358513,627460548,1.499886444457E9,1.499886452457E9 +40,4151690063604635080,627460548,1.499886459457E9,1.499886494457E9 +40,7869494181094017743,627460548,1.499887022457E9,1.499887164457E9 +40,-341589078194487227,627460548,1.499887226457E9,1.499887256457E9 +40,-2922879837909943204,627460548,1.499887495457E9,1.499887497457E9 +40,7217347143671948905,627460548,1.499887499457E9,1.499887506457E9 +40,5512026596709036316,627460548,1.499887510457E9,1.499887520457E9 +40,-8773312499084945466,627460548,1.499887549457E9,1.499887676457E9 +40,3430716370405572856,627460548,1.499887709457E9,1.499887921457E9 +40,1201920478073766500,627460548,1.499887930457E9,1.499887956457E9 +40,1698794885912782453,627460548,1.499887958457E9,1.499888018457E9 +46,-5719055632173539064,627460548,1.518868091239E9,1.518868428239E9 +46,1912938464721822750,627460548,1.518868467239E9,1.518868653239E9 +46,-5619135015759137843,627460548,1.518868693239E9,1.518868694239E9 +46,-7130261418066497890,627460548,1.518868704239E9,1.518868719239E9 +46,5764861178217039580,627460548,1.518868721239E9,1.518868737239E9 +46,-4311952123983752254,627460548,1.518868739239E9,1.518868758239E9 +46,2008732475398282871,627460548,1.518868888239E9,1.518868894239E9 +46,-2598591406595950374,627460548,1.518868989239E9,1.518868999239E9 +46,616464715567148650,627460548,1.518869002239E9,1.518869012239E9 +46,-3936229051375488234,627460548,1.518869025239E9,1.518869032239E9 +46,8089312538434247534,627460548,1.518869037239E9,1.518869065239E9 +53,4719537997627477294,479635942,1.518629576522E9,1.5186295774520001E9 +53,-3961541965162208929,479635942,1.518629581981E9,1.518629583812E9 +53,8503022251468215435,479635942,1.518629605801E9,1.518629607572E9 +53,713199775218361505,479635942,1.518629736256E9,1.518629737186E9 +53,2425805708253376019,479635942,1.518629783058E9,1.518629783809E9 +53,-7515043356625034258,479635942,1.518629792206E9,1.518629793709E9 +53,-4792884246056359027,479635942,1.518629985436E9,1.5186299863040001E9 +53,3707434878573148820,479635942,1.518630076849E9,1.518630079609E9 +53,8129160923945830370,479635942,1.51863008144E9,1.5186300841690001E9 +53,-8014330601047704350,479635942,1.518630097459E9,1.518630100219E9 +53,-3121402578790030156,479635942,1.51863013064E9,1.518630132289E9 +53,1603606642234752669,479635942,1.51863014699E9,1.518630148579E9 +53,2858617537404216434,479635942,1.518630169398E9,1.518630170298E9 +53,-5169750675118715776,479635942,1.518630172098E9,1.518630173058E9 +53,-2304206432183455319,479635942,1.518630199339E9,1.5186302002380002E9 +53,-1807222241646641934,479635942,1.518631485453E9,1.518631626181E9 +53,2091908338149281847,479635942,1.518631649941E9,1.51863165804E9 +53,8789285373404760892,479635942,1.5186316717810001E9,1.51863179043E9 +53,4931398278855665069,479635942,1.518631915292E9,1.518631916494E9 +53,-7759446220816372652,479635942,1.518631918683E9,1.518631920842E9 +53,-1548779265395687539,479635942,1.5186319218930001E9,1.5186319242E9 +53,-5510390377098441463,479635942,1.518631925851E9,1.518631928161E9 +53,3464079960435575554,479635942,1.51863214929E9,1.51863215004E9 +53,-173803762134672206,479635942,1.51863219288E9,1.518632193809E9 +53,-4535314705741276360,479635942,1.518632196569E9,1.518632197469E9 +53,8704017807762211453,479635942,1.5186322448990002E9,1.5186322475440001E9 +53,1610135799173198474,479635942,1.518632372701E9,1.51863237345E9 +53,-6377371896905616609,479635942,1.51863242181E9,1.518632423609E9 +53,-5540642517750075570,479635942,1.518632462759E9,1.518632463629E9 +53,-8425565646658279085,479635942,1.51863251271E9,1.518632514568E9 +53,-2642410273673643222,479635942,1.518632545469E9,1.518632546369E9 +53,2538228681333689978,627460548,1.518630555223E9,1.518630808223E9 +53,562979490760547513,627460548,1.518630826223E9,1.518630835223E9 +53,-4688564986980665542,627460548,1.518630854223E9,1.518630893223E9 +53,-1989663854288220342,627460548,1.518630953223E9,1.518630983223E9 +53,8201289497627416439,627460548,1.518631354223E9,1.518631399223E9 +53,-1429215875582936632,627460548,1.518631414223E9,1.518631811223E9 +34,-6000858646915987928,479635942,1.5204479944160001E9,1.520447995346E9 +34,-2314123508697726837,479635942,1.520448010706E9,1.520448011606E9 +34,5581043896274190172,479635942,1.520448109606E9,1.5204481107480001E9 +34,-6837627839302235162,479635942,1.5204485367910001E9,1.520448537691E9 +34,3770754727273036423,479635942,1.520448540421E9,1.520448541321E9 +34,-4523084511348774216,479635942,1.520448563911E9,1.52044856571E9 +34,-5497367115604635664,479635942,1.52044868823E9,1.52044868913E9 +34,8814609216675781130,479635942,1.5204487329E9,1.520448733681E9 +34,-3490289058455024266,479635942,1.52044873893E9,1.520448739681E9 +34,-8174951488481971175,479635942,1.52044880055E9,1.5204488023500001E9 +34,-8317944317676117748,479635942,1.520448870239E9,1.520448871141E9 +34,-12744667098081097,479635942,1.520448892799E9,1.5204488936990001E9 +34,293713869701688346,479635942,1.5204489244490001E9,1.520448926279E9 +34,6950106888937749318,479635942,1.52044895097E9,1.520448951809E9 +34,2237797513140888629,479635942,1.520448954209E9,1.520448956729E9 +34,3037870119588580492,479635942,1.5204489584090002E9,1.520448960149E9 +34,-4391813490183835592,479635942,1.520448978749E9,1.520448981899E9 +34,-8887674756581127149,479635942,1.520449025039E9,1.520449026839E9 +34,-555061566564083561,479635942,1.520449028638E9,1.520449029539E9 +34,-8463407994976925256,479635942,1.520449068059E9,1.52044906893E9 +34,-8428209022374972541,479635942,1.52044908732E9,1.520449090589E9 +34,-4171994716942111520,479635942,1.520449093859E9,1.520449110448E9 +34,753323244456129368,479635942,1.5204491112E9,1.5204491126990001E9 +34,3780275552049392469,479635942,1.52044911726E9,1.520449141289E9 +34,-954460041138789062,479635942,1.52044938096E9,1.52044938276E9 +34,-5364370288568675355,479635942,1.52044957476E9,1.52044959375E9 +34,-3181746273712470859,479635942,1.520449600079E9,1.520449734539E9 +34,5615705212364081547,479635942,1.520449836779E9,1.52044984218E9 +34,7396459936587200903,479635942,1.520450132369E9,1.520450523297E9 +34,-81958421013514564,479635942,1.520450786277E9,1.520450787418E9 +34,-649421775118629863,479635942,1.520450792308E9,1.520450793297E9 +34,-4296196419647938515,479635942,1.520450824678E9,1.520450827496E9 +34,-2130448844552829855,479635942,1.5204508287580001E9,1.520450829868E9 +34,827706226946107518,479635942,1.520450955418E9,1.520450956407E9 +34,-3635211437937948347,479635942,1.520451044038E9,1.520451046198E9 +34,-2816549764914966441,479635942,1.520451047278E9,1.520451055618E9 +34,-3333312543193866264,479635942,1.520451061559E9,1.5204510627280002E9 +34,849630890640015477,627460548,1.520449102177E9,1.520449174177E9 +34,3596018359152660547,627460548,1.520449223177E9,1.520449292177E9 +34,1448719135241197326,627460548,1.520449295177E9,1.520449339177E9 +34,8819969892212945172,627460548,1.520449347177E9,1.520449431177E9 +34,-3951893586210102027,627460548,1.520449441177E9,1.520449443177E9 +34,-8681924579718721836,627460548,1.520449473177E9,1.520449534177E9 +34,-1781805203761157607,627460548,1.520449538177E9,1.520449748177E9 +34,8153404959014326632,627460548,1.520449915177E9,1.520449951177E9 +34,5556185552738641632,627460548,1.520449974177E9,1.520449998177E9 +34,-5924911185796044082,627460548,1.520450023177E9,1.520450540177E9 +17,1174641044501369031,479635942,1.499239215381E9,1.499239216131E9 +17,-113513919750267864,479635942,1.499239238721E9,1.499239240551E9 +17,1914961879829331968,479635942,1.499239247871E9,1.499239249671E9 +17,6168836024175779195,479635942,1.499239267792E9,1.499239268721E9 +17,-7968231014627295188,479635942,1.499239326863E9,1.499239329115E9 +17,-8499415123635551694,479635942,1.499239333703E9,1.499239335969E9 +17,6627994625602525233,479635942,1.499239448812E9,1.499239449562E9 +17,-5039958006018807985,479635942,1.499239481153E9,1.499239482053E9 +17,4741967673194050437,479635942,1.499239497383E9,1.499239499183E9 +17,-85070342197273786,479635942,1.49923962548E9,1.49923962638E9 +17,438770993668007878,479635942,1.49923963718E9,1.499239638979E9 +17,-6203021544952714408,479635942,1.499239758171E9,1.49923977623E9 +17,-7159997191543915936,479635942,1.499239832242E9,1.499239955992E9 +17,-5775443037996156590,479635942,1.499239979182E9,1.499239979932E9 +17,-893708132266951796,479635942,1.499239988602E9,1.499240019352E9 +17,-6912547387988450452,479635942,1.499240039122E9,1.499240069183E9 +17,5541402448862509122,479635942,1.499240191014E9,1.499240191764E9 +17,4853777467545763960,479635942,1.499240374789E9,1.499240375688E9 +17,-1966508044092881506,479635942,1.499240485039E9,1.49924048684E9 +17,-1643031902044395359,479635942,1.499240721594E9,1.499240722341E9 +17,1619773578788551390,479635942,1.499240745051E9,1.499240745951E9 +17,-1733489000928163983,479635942,1.499240748651E9,1.499240749581E9 +17,1705536870240618908,479635942,1.499240947547E9,1.499240952348E9 +17,2830335267853659225,479635942,1.499240953187E9,1.499240954059E9 +17,6470982455355185698,479635942,1.499241435978E9,1.499241437658E9 +17,-4607800810413217230,479635942,1.499241488328E9,1.499241490038E9 +17,-1826568725686642718,479635942,1.499241493698E9,1.499241495529E9 +17,-4619194132686886559,479635942,1.499241898035E9,1.499241898935E9 +17,-2212123172000043182,479635942,1.499241907155E9,1.499241908054E9 +17,-7071242323630529159,479635942,1.499242135247E9,1.499242136838E9 +17,-8115366638117663932,479635942,1.49924223908E9,1.499242243277E9 +17,6349028877385368540,479635942,1.499242247207E9,1.499242247959E9 +17,-6891175381736931759,479635942,1.499242573066E9,1.499242573963E9 +17,-1524786648176917025,479635942,1.499242587673E9,1.499242588572E9 +17,8049304979851678142,479635942,1.499242616532E9,1.499242618093E9 +17,-797518848902721648,479635942,1.499242619593E9,1.499242621152E9 +17,378123319668265763,479635942,1.499242637533E9,1.499242638283E9 +17,-8370198626871080547,479635942,1.499242702393E9,1.499242703293E9 +17,-5850100425849207414,479635942,1.499242705127E9,1.499242706024E9 +17,6208343689566820418,479635942,1.499242773705E9,1.499242775418E9 +17,-1550605339149442068,479635942,1.499242776973E9,1.499242777785E9 +17,3176154601877577621,479635942,1.499242802264E9,1.499242804094E9 +17,-1853380393051858652,627460548,1.499239751444E9,1.499239783444E9 +17,-3207614366998862337,627460548,1.499239824444E9,1.499240119444E9 +17,7553345385419259252,627460548,1.499240124444E9,1.499240160444E9 +17,2972559417706573068,627460548,1.499240952444E9,1.499241001444E9 +17,-3099714540054183611,627460548,1.499242275444E9,1.499242296444E9 +17,6479166978187231620,627460548,1.499242311444E9,1.499242315444E9 +17,-7869978596500489005,627460548,1.499242490444E9,1.499242508444E9 +17,-5139822931339225334,627460548,1.499242509444E9,1.499242541444E9 +17,366823539272850670,627460548,1.499242550444E9,1.499242559444E9 +11,-5543695754868643088,479635942,1.500055767817E9,1.500055768748E9 +11,-2480867838113463367,479635942,1.500055954738E9,1.5000559556629999E9 +11,-5484082981511469405,479635942,1.5000560360019999E9,1.500056037112E9 +11,4034839475437669677,479635942,1.500056054154E9,1.5000560559819999E9 +11,4488805995615899206,479635942,1.500056057872E9,1.500056059464E9 +11,7883624875693060474,479635942,1.500056157503E9,1.5000561584029999E9 +11,4618971283163984574,479635942,1.5000561899029999E9,1.500056191465E9 +11,6188937660737349562,479635942,1.500056192965E9,1.500056194553E9 +11,8903899750443294539,479635942,1.500056229144E9,1.500056230944E9 +11,-3394480610283052964,479635942,1.500056266374E9,1.500056267244E9 +11,-3469125817796304842,479635942,1.500056269074E9,1.500056271744E9 +11,2795423957250455775,479635942,1.5000562725839999E9,1.5000562760939999E9 +11,7089143145452215105,479635942,1.5000565436399999E9,1.5000565447779999E9 +11,5794082623021317137,479635942,1.5000567601499999E9,1.500056761049E9 +11,-5984407172659360821,479635942,1.500056858848E9,1.500056860139E9 +11,4592988333463663302,479635942,1.500056864158E9,1.500056865389E9 +11,40389292891179278,479635942,1.5000568711499999E9,1.500056873189E9 +11,-4569347279358008062,479635942,1.500057010651E9,1.50005709591E9 +11,4664456424607955005,479635942,1.500057098431E9,1.500057103021E9 +11,-8308622012746565941,479635942,1.50005710683E9,1.500057297632E9 +11,4298366364783496558,479635942,1.5000573356729999E9,1.500057495568E9 +11,905207645040959628,479635942,1.500057503698E9,1.500057504598E9 +11,8321563119332864354,479635942,1.500057506398E9,1.500057510066E9 +11,3829336336412831454,479635942,1.500057921434E9,1.5000579280049999E9 +11,-5292375675026104456,479635942,1.500057931244E9,1.500057937695E9 +11,-6585130645265929790,479635942,1.500058373474E9,1.500058379323E9 +11,-1179295113201761636,479635942,1.500058603427E9,1.500058607144E9 +11,-5204755513226210711,479635942,1.500058612876E9,1.500058614016E9 +11,-9054883140929113592,479635942,1.500058656735E9,1.500058657996E9 +11,-631040589778758465,479635942,1.500058702245E9,1.500058704405E9 +11,5578892479226305874,479635942,1.500058732968E9,1.500058734225E9 +11,2603520482047056700,479635942,1.500058753726E9,1.5000588374559999E9 +11,-8154586923657674779,479635942,1.500058846336E9,1.500058866736E9 +11,-7306787708996232856,479635942,1.500058905885E9,1.500058907056E9 +11,7636997053628670900,479635942,1.500058922356E9,1.500058944496E9 +11,1478375614263718545,479635942,1.500059025766E9,1.500059144477E9 +11,-171987320273188646,479635942,1.500059191218E9,1.500059236189E9 +11,-9157845083007472577,479635942,1.5000592530509999E9,1.500059349203E9 +11,-5754058400308241748,479635942,1.5000595350509999E9,1.500059536372E9 +11,857173072131487462,479635942,1.5000598545839999E9,1.500059871053E9 +11,-1502173484578183518,479635942,1.500059872225E9,1.500059881495E9 +11,-4181829232066143726,479635942,1.500060277259E9,1.500060372057E9 +11,-9084904019957069745,479635942,1.500060380128E9,1.500060424047E9 +11,-1828512306184840082,479635942,1.50006045951E9,1.500060741143E9 +11,8680232449804734557,479635942,1.500060767542E9,1.500060799416E9 +11,-6807911939256734373,479635942,1.500060859475E9,1.5000608613439999E9 +11,1909083224071200532,479635942,1.500060892339E9,1.500060893383E9 +11,-4119442477063225613,479635942,1.500060928666E9,1.500060930096E9 +11,3791025594053356146,479635942,1.500060931141E9,1.500060942691E9 +11,-7044025460470057660,479635942,1.500061121966E9,1.5000611246629999E9 +11,-1057809992410855944,479635942,1.50006115436E9,1.5000611556529999E9 +11,7675744265113592931,479635942,1.500061255893E9,1.500061258448E9 +11,-2635098009724116695,479635942,1.500061282486E9,1.500061283803E9 +11,6326765872777977471,479635942,1.500061364736E9,1.5000613701E9 +11,-3497590175997740077,479635942,1.500061372794E9,1.500061375297E9 +11,-3918505463904649565,479635942,1.500061380246E9,1.500061383849E9 +11,-5233742646009138621,479635942,1.500061399883E9,1.500061400983E9 +11,8533749416559801035,479635942,1.500061482987E9,1.500061484007E9 +11,6578800892235992313,479635942,1.500061609763E9,1.500061611276E9 +11,2665557495979508305,627460548,1.500056905984E9,1.500056942984E9 +11,-3613450615585479724,627460548,1.500056967984E9,1.500056990984E9 +11,8589792464854971908,627460548,1.500057010984E9,1.500057499984E9 +11,863602497122901259,627460548,1.500058735984E9,1.500058742984E9 +11,2591426395849682796,627460548,1.500058744984E9,1.500058875984E9 +11,4129276314654245211,627460548,1.500058881984E9,1.500058884984E9 +11,-5560997421686112404,627460548,1.500058906984E9,1.500058910984E9 +11,7675034701163374306,627460548,1.500058917984E9,1.500058958984E9 +11,-4542691943903189271,627460548,1.500058976984E9,1.500058981984E9 +11,9183554001340422098,627460548,1.500058992984E9,1.500059001984E9 +11,-4456396871196047336,627460548,1.500059009984E9,1.500059150984E9 +11,-5212416744950382231,627460548,1.500059161984E9,1.500059169984E9 +11,-4429629996414924494,627460548,1.500059177984E9,1.500059357984E9 +11,-5355993751012974249,627460548,1.500059854984E9,1.500060039984E9 +11,-7263636355199048301,627460548,1.500060103984E9,1.500060105984E9 +11,-7318378108656503778,627460548,1.500060110984E9,1.500060118984E9 +11,7532861815097238860,627460548,1.500060270984E9,1.500060808984E9 +12,4283593092442975370,479635942,1.5189377686100001E9,1.5189377701100001E9 +12,-6730773103290777465,479635942,1.5189378472080002E9,1.518937848078E9 +12,-2235796387731170381,479635942,1.5189379015080001E9,1.518937902408E9 +12,6605800188608690183,479635942,1.518937929678E9,1.518937930578E9 +12,7561622485425587688,479635942,1.518937979477E9,1.518937980257E9 +12,-6252053639086100173,479635942,1.5189379829880002E9,1.5189379853270001E9 +12,-5957596880538714093,479635942,1.518938028256E9,1.518938029186E9 +12,6481997373775911886,479635942,1.5189381085370002E9,1.518938109437E9 +12,5364980274529319432,479635942,1.518938139617E9,1.518938140516E9 +12,-2054936555839058013,479635942,1.518938147806E9,1.5189381505370002E9 +12,-870844027063078872,479635942,1.518938152367E9,1.518938154196E9 +12,-2421935695111372078,479635942,1.5189383302050002E9,1.518938335005E9 +12,-2077205704846135523,479635942,1.518938401484E9,1.5189384042450001E9 +12,2569312354927215010,479635942,1.5189384115040002E9,1.518938619525E9 +12,3593046407837387529,479635942,1.5189386377050002E9,1.518938691046E9 +12,815443863766839883,479635942,1.518939280572E9,1.518939282884E9 +12,-3012067486874307021,479635942,1.518939416171E9,1.518939419772E9 +12,5433916974079917102,479635942,1.518939538811E9,1.51893954514E9 +12,-236863565709865616,479635942,1.5189399854190001E9,1.51893998614E9 +12,8350609525672883483,479635942,1.518940155638E9,1.5189401646980002E9 +12,-3859450603864411763,479635942,1.5189402635170002E9,1.518940265318E9 +12,-3812269890092998697,479635942,1.518940287997E9,1.518940321716E9 +12,-7535625842753015943,479635942,1.518940801115E9,1.518940911545E9 +12,7872024605165270739,479635942,1.5189409244150002E9,1.518940936658E9 +12,6247176614357948326,479635942,1.518940938996E9,1.5189410276790001E9 +12,851026044269946819,479635942,1.5189410909780002E9,1.5189413166950002E9 +12,-3208158205689746372,479635942,1.518941329414E9,1.518941383805E9 +12,-8491322549521376889,479635942,1.518941478365E9,1.5189414830740001E9 +12,-182841290945481409,479635942,1.518941530265E9,1.518941534765E9 +12,-5301279482316321186,479635942,1.5189415464950001E9,1.5189415682150002E9 +12,3394733332275496354,479635942,1.518941579885E9,1.518941580785E9 +12,360798525811066928,479635942,1.5189416025040002E9,1.5189416453140001E9 +12,-7528480465664847909,479635942,1.518941646214E9,1.518941736035E9 +12,-5462127132215690834,479635942,1.5189418230040002E9,1.518941873882E9 +12,-8520394149655783988,479635942,1.518941878382E9,1.5189421005340002E9 +12,8979004929858640027,479635942,1.518942105036E9,1.518942144753E9 +12,-3956961855162800075,479635942,1.518942146523E9,1.518942200674E9 +12,-1321483133108390696,479635942,1.518942202924E9,1.518942293282E9 +12,6238810720709420671,479635942,1.518942524492E9,1.5189425261420002E9 +12,2218382725502986503,479635942,1.518942540905E9,1.5189425416520002E9 +12,-5629500424456825869,479635942,1.5189425774120002E9,1.518942578342E9 +12,-4627176174521996455,479635942,1.5189426204620001E9,1.518942623012E9 +12,5187513082717779153,479635942,1.5189426428170002E9,1.518942643563E9 +12,-7507594365009239360,479635942,1.5189427098000002E9,1.5189427107020001E9 +12,493910791917772289,479635942,1.5189427390800002E9,1.518942742321E9 +12,-8086246983915300563,479635942,1.518942765061E9,1.518942765811E9 +12,-3337236280372941465,479635942,1.518942770311E9,1.518942771809E9 +12,8685259439690032410,627460548,1.518938402489E9,1.518939222489E9 +12,-6868661479380126344,627460548,1.518940264489E9,1.518940269489E9 +12,-8069229001313049753,627460548,1.518940277489E9,1.518940465489E9 +12,5821047234509037071,627460548,1.518940473489E9,1.518940546489E9 +12,-6145076890289439677,627460548,1.518940551489E9,1.518940638489E9 +12,-6700879027725373579,627460548,1.518940764489E9,1.518940777489E9 +12,-1665937948720462442,627460548,1.518940787489E9,1.518941388489E9 +12,2307096764922462312,627460548,1.518941603489E9,1.518941762489E9 +12,6591131193758243285,627460548,1.518941784489E9,1.518941790489E9 +12,6359970645227808880,627460548,1.518941808489E9,1.518942325489E9 +81,192518120778095635,479635942,1.500020543701E9,1.500020544598E9 +81,-7227601353525798436,479635942,1.500020548107E9,1.5000205489490001E9 +81,-64712697685216447,479635942,1.5000205583370001E9,1.500020559088E9 +81,-5132480708142171343,479635942,1.500020583238E9,1.50002058999E9 +81,7607560735302201258,479635942,1.500020592238E9,1.500020599077E9 +81,-3075084665810469277,479635942,1.500020693609E9,1.500020694359E9 +81,616389932782925533,479635942,1.500020727089E9,1.500020727989E9 +81,1747310175578242858,479635942,1.500020762339E9,1.500020767769E9 +81,6488563634091348225,479635942,1.500020773109E9,1.500020775869E9 +81,232486055601429644,479635942,1.5000207776100001E9,1.500020780219E9 +81,949868499096912208,479635942,1.500020815765E9,1.500020818195E9 +81,6604962992337975618,479635942,1.500020819696E9,1.5000208319950001E9 +81,4588742148984955312,479635942,1.5000208349950001E9,1.500020839435E9 +81,2353503775928897875,479635942,1.500020848526E9,1.500020849426E9 +25,-1918576272533331593,479635942,1.501659060553E9,1.501659061454E9 +25,475574475082999312,479635942,1.501659162974E9,1.501659163874E9 +25,1296706644873663684,479635942,1.501659165645E9,1.501659167414E9 +25,-3451216801643177610,479635942,1.501659207375E9,1.5016592088760002E9 +25,2632304374512274923,479635942,1.501659335176E9,1.5016593367680001E9 +25,-6146646490068665335,479635942,1.5016593526660001E9,1.5016593549160001E9 +25,1798347674854031652,479635942,1.50165945374E9,1.501659454576E9 +25,582209014612805769,479635942,1.5016594568860002E9,1.501659458476E9 +25,2412189380582982752,479635942,1.501659462437E9,1.501659467838E9 +25,7046244486747443510,479635942,1.5016594885670002E9,1.501659489466E9 +25,-3211193533496446790,479635942,1.501659494868E9,1.501659496697E9 +25,-5291335758908400797,479635942,1.501659499397E9,1.501659502157E9 +25,-5458151725296583238,479635942,1.501659769604E9,1.501659774164E9 +25,2694961801384400888,479635942,1.5016598106750002E9,1.501659812384E9 +25,6578363047576436691,479635942,1.501659819404E9,1.5016598202150002E9 +25,-1050316684683158896,479635942,1.501659821056E9,1.501659821894E9 +25,-3572827924039450409,479635942,1.5016598509060001E9,1.5016600926720002E9 +25,4999474947403218734,479635942,1.501660098013E9,1.501660176372E9 +25,-4657384137351831521,479635942,1.501660239163E9,1.501660240663E9 +25,-4221317794909143287,479635942,1.501660618571E9,1.501660623911E9 +25,813603138422814918,479635942,1.501660626431E9,1.501660627331E9 +25,7403618885556415160,479635942,1.5016607085410001E9,1.501660710311E9 +25,3078057363207950509,479635942,1.5016607139710002E9,1.5016607149020002E9 +25,1389898489787163430,479635942,1.501660795302E9,1.501660796232E9 +25,-488294058476917788,479635942,1.5016608796920002E9,1.501660884193E9 +25,-2501611817757284153,479635942,1.501661054739E9,1.5016610556390002E9 +25,5745648008296236700,479635942,1.5016611721000001E9,1.501661173E9 +25,5536885424141391184,479635942,1.5016612506100001E9,1.50166125226E9 +25,6967703414404161427,479635942,1.501661289401E9,1.501661291201E9 +25,6865718704262931946,479635942,1.5016616524320002E9,1.501661654677E9 +25,838757312249711460,479635942,1.5016618080960002E9,1.501661834377E9 +25,-3250595588765349460,479635942,1.501661881359E9,1.5016618821980002E9 +25,603372751702739863,479635942,1.5016618956990001E9,1.5016618979480002E9 +25,4879259615023668465,479635942,1.5016619159780002E9,1.501661918706E9 +25,-1357716852533659053,479635942,1.501661926898E9,1.5016619277970002E9 +25,-5919127137214935923,479635942,1.5016619295970001E9,1.501661931398E9 +25,2530284298257046273,479635942,1.501661933227E9,1.501661934127E9 +25,-5057144647355815593,479635942,1.501661995509E9,1.501661996257E9 +25,-1804655490542361307,479635942,1.501661998658E9,1.501662000278E9 +25,309374400425423835,479635942,1.5016620224480002E9,1.5016620231980002E9 +25,1085217367503410273,479635942,1.501662174941E9,1.5016621758400002E9 +25,6744335931331136197,627460548,1.50165955942E9,1.50165956442E9 +25,-4950803005028863969,627460548,1.50165977142E9,1.50166021942E9 +25,7603458606368951735,627460548,1.50166031242E9,1.50166031542E9 +25,-549765673095479241,627460548,1.50166042242E9,1.50166042542E9 +25,1679327534199456962,627460548,1.50166052342E9,1.50166052942E9 +25,-2723924659348105878,627460548,1.50166053242E9,1.50166055542E9 +25,4585404511370006495,627460548,1.50166056242E9,1.50166060942E9 +25,4821797035227592088,627460548,1.50166180242E9,1.50166184042E9 +9,6090515753093118009,479635942,1.5008809643E9,1.50088096517E9 +9,8607518278720194933,479635942,1.500880995471E9,1.500880996341E9 +9,-6328344669637137655,479635942,1.500881024362E9,1.500881025261E9 +9,5846071625939405160,479635942,1.500881131732E9,1.500881132482E9 +9,-8413814638006670958,479635942,1.500881136982E9,1.500881137732E9 +9,-2511537132679287528,479635942,1.500881180212E9,1.500881182913E9 +9,2513652891765636627,479635942,1.500881184712E9,1.500881185612E9 +9,2671628838296329268,479635942,1.500881396754E9,1.50088140402E9 +9,4965812998068513038,479635942,1.500881410404E9,1.500881422133E9 +9,2551276844589223272,479635942,1.500881423033E9,1.500881426693E9 +9,1711634055538610619,479635942,1.500881440283E9,1.500881443793E9 +9,-755894269467376115,479635942,1.500881458104E9,1.500881502355E9 +9,1595802499149206750,479635942,1.500881518585E9,1.500881557646E9 +9,1663139309445998059,479635942,1.500881567575E9,1.500881665496E9 +9,-3932532742074429241,479635942,1.500881678996E9,1.500881688086E9 +9,-4537658734341497416,479635942,1.500881707406E9,1.500881712837E9 +9,-5068144471233551489,479635942,1.500881736836E9,1.500881753067E9 +9,2656315172392767202,479635942,1.500881896402E9,1.500881901863E9 +9,-2195040381408539092,479635942,1.500881914522E9,1.500881918153E9 +9,-7284052012417037083,479635942,1.500881957453E9,1.500881965082E9 +9,-1186588711760806569,479635942,1.500881980974E9,1.500881986854E9 +9,-5290452604235714733,479635942,1.500882097613E9,1.500882099292E9 +9,-6035739451328999118,479635942,1.500882113872E9,1.500882114622E9 +9,4091804748993463023,479635942,1.500882208404E9,1.500882210655E9 +9,-4575871135502892229,479635942,1.500882336205E9,1.500882340406E9 +9,4949924325371426599,479635942,1.500882382555E9,1.500882387054E9 +9,-872903691188889680,479635942,1.500882495925E9,1.500882496825E9 +9,5913911087254124493,479635942,1.500882588773E9,1.500882589526E9 +9,5780265201893861110,479635942,1.500882819384E9,1.500882820283E9 +9,-5060064149019610810,479635942,1.500882859554E9,1.500882860454E9 +9,6187173133999865237,479635942,1.500882903057E9,1.500882903954E9 +9,5151355861737342949,479635942,1.500883368917E9,1.500883369817E9 +9,-4659309597230372100,479635942,1.500883385297E9,1.500883386197E9 +9,8856506679410435688,479635942,1.500883389887E9,1.500883401707E9 +9,-5502722262409781109,479635942,1.500883441789E9,1.500883449917E9 +9,-4226422481924672357,479635942,1.500883453128E9,1.500883454628E9 +9,-320154619360412133,479635942,1.50088368665E9,1.50088368926E9 +9,-8171623311157724159,479635942,1.500883701802E9,1.50088370339E9 +9,-3915895445743928837,479635942,1.500883708703E9,1.500883709451E9 +9,-3754416137751822639,479635942,1.50088371173E9,1.5008837300740001E9 +9,8881009627207109386,479635942,1.500883779742E9,1.500883781541E9 +9,-2641563171317723985,479635942,1.500883782442E9,1.500883783342E9 +9,-7942470475451468418,479635942,1.500883787901E9,1.500883832603E9 +9,-3362705630856244793,479635942,1.500883887682E9,1.500883928512E9 +9,-305311769432346044,479635942,1.500883931931E9,1.500883989412E9 +9,-4711922360961168655,479635942,1.50088402028E9,1.500884360872E9 +9,-6776836309038358794,479635942,1.500884530099E9,1.500884604832E9 +9,6951188376775391253,479635942,1.500884639031E9,1.500884678572E9 +9,5217572947880372369,479635942,1.50088471052E9,1.50088471682E9 +9,-5789678890717688600,479635942,1.500884768241E9,1.50088484507E9 +9,-6918581243470671782,479635942,1.500884864148E9,1.50088493792E9 +9,2870663491214235785,479635942,1.500884980069E9,1.500885024649E9 +9,-4663032202397766868,479635942,1.500885030559E9,1.500885032239E9 +9,-4474943182138209540,479635942,1.500885045289E9,1.50088509131E9 +9,-577076490395417539,479635942,1.500885113072E9,1.50088513214E9 +9,-3874067388838806318,627460548,1.500881247319E9,1.500881249319E9 +9,749036971346092554,627460548,1.500881301319E9,1.500881328319E9 +9,1808500096443673397,627460548,1.500881424319E9,1.500881790319E9 +9,-7352971782388487486,627460548,1.500883702319E9,1.500883736319E9 +9,5359625835878371672,627460548,1.500883745319E9,1.500883752319E9 +9,8936881148278854430,627460548,1.500883766319E9,1.500883857319E9 +9,-2182114211114290636,627460548,1.500883876319E9,1.500884001319E9 +9,5829145618465743460,627460548,1.500884014319E9,1.500884367319E9 +9,-7460876090151743721,627460548,1.500884470319E9,1.500884612319E9 +9,4670152704022440228,627460548,1.500884630319E9,1.500884689319E9 +9,8720622542923076327,627460548,1.500884698319E9,1.500884758319E9 +9,6876792168881524510,627460548,1.500884761319E9,1.500884958319E9 +9,671801985722518798,627460548,1.500884963319E9,1.500885156319E9 +9,302524817626752602,627460548,1.500885161319E9,1.500885185319E9 +9,-3190508053959966824,627460548,1.500885192319E9,1.500885204319E9 +38,8131974661146223925,479635942,1.522048687981E9,1.522048689752E9 +38,-7138217712064283202,479635942,1.522048759862E9,1.5220487607610002E9 +38,2238269102845267436,479635942,1.5220487974520001E9,1.522048799161E9 +38,-4462245165621976760,479635942,1.522048848871E9,1.5220488497710001E9 +38,8101481607241039878,479635942,1.522048851572E9,1.522048852471E9 +38,3812991432502658978,479635942,1.522048855171E9,1.522048856071E9 +38,-8892851273280719223,479635942,1.522048922821E9,1.522048924501E9 +38,-7495215679650315887,479635942,1.5220489425310001E9,1.522048943252E9 +38,-4760747130502563132,479635942,1.522048944751E9,1.522048945502E9 +38,-958203103780352875,479635942,1.5220490396100001E9,1.5220490405410001E9 +38,7182840473596955637,479635942,1.522049042941E9,1.522049043721E9 +38,-4177663431188146798,479635942,1.52204908602E9,1.52204908782E9 +38,2431421281061135486,479635942,1.522049330099E9,1.5220493336990001E9 +38,3848923199715530916,479635942,1.522049340959E9,1.522049598539E9 +38,-5960738625727644228,479635942,1.522049612068E9,1.522049664539E9 +38,6773885161319474223,479635942,1.522049702757E9,1.522049709868E9 +38,-2489970910499104008,479635942,1.5220497107380002E9,1.522049763147E9 +38,-8281944706982810090,479635942,1.52205008301E9,1.52205008598E9 +38,-4344074848256580206,479635942,1.522050309539E9,1.522050311039E9 +38,3144395660400653551,479635942,1.522051297529E9,1.52205129828E9 +38,1937342201065397105,479635942,1.522051303019E9,1.52205130383E9 +38,-244970285071672010,479635942,1.522051318319E9,1.52205131907E9 +38,4212050593528162588,479635942,1.5220513229090002E9,1.5220513244090002E9 +38,7211650041960773880,479635942,1.522051343579E9,1.522051344479E9 +38,491403866198003510,479635942,1.52205139764E9,1.522051398389E9 +38,294204943368387387,627460548,1.522049331131E9,1.522049772131E9 +38,-6223246366435122896,627460548,1.522050404131E9,1.522050423131E9 +38,-299093952694109024,627460548,1.522050429131E9,1.522050471131E9 +38,588203095281303882,627460548,1.522050559131E9,1.522050680131E9 +38,746210364012139525,627460548,1.522050746131E9,1.522050817131E9 +38,6506854525593490785,627460548,1.522050838131E9,1.522050880131E9 +38,-4876674528238848355,627460548,1.522051037131E9,1.522051042131E9 +38,6151580069918737466,627460548,1.522051043131E9,1.522051046131E9 +38,961136104895701124,627460548,1.522051058131E9,1.522051063131E9 +38,6378313643022324144,627460548,1.522051087131E9,1.522051097131E9 +14,-1021702737721583270,479635942,1.5016119639129999E9,1.501611964814E9 +14,4415002113845876129,479635942,1.501611975734E9,1.501611976635E9 +14,-7943467882940527550,479635942,1.501611994753E9,1.501611995654E9 +14,3323967674802340929,479635942,1.5016122335249999E9,1.501612234425E9 +14,6693875695754850733,479635942,1.501612445477E9,1.501612446437E9 +14,-6481226270892374582,479635942,1.5016124548409998E9,1.501612456608E9 +14,-2112136325040388893,479635942,1.5016124936269999E9,1.5016124946859999E9 +14,-7814402393733373894,479635942,1.501612601777E9,1.5016126036669998E9 +14,1204787480036344335,479635942,1.501612607178E9,1.501612611888E9 +14,-2563243885709823695,479635942,1.501612678098E9,1.501612679148E9 +14,8318719176490939498,479635942,1.501612959678E9,1.501612969066E9 +14,-3474770349561694616,479635942,1.5016129789659998E9,1.501613202258E9 +14,1013020036325701798,479635942,1.501613205258E9,1.501613409915E9 +14,-7838687942193028895,479635942,1.501613432895E9,1.501613550256E9 +14,3551549597481415472,479635942,1.501613662904E9,1.501613666563E9 +14,1866801837750067161,479635942,1.5016136686629999E9,1.5016136696529999E9 +14,-2059668158275998437,479635942,1.501613704725E9,1.501613876475E9 +14,-2891958191777042440,479635942,1.501613908546E9,1.5016140376929998E9 +14,-2851017660901732904,479635942,1.501614059383E9,1.501614085633E9 +14,-3780078887654384401,479635942,1.501614093793E9,1.501614142873E9 +14,-2203157316035126569,479635942,1.501614153613E9,1.501614172066E9 +14,2111736377436960368,479635942,1.501614205454E9,1.501614212684E9 +14,-4939796187505064756,479635942,1.501614262454E9,1.501614265484E9 +14,-5258032461743755707,479635942,1.50161438705E9,1.501614397335E9 +14,-4332372976802204713,479635942,1.501614404296E9,1.501614414135E9 +14,-2008726048910922592,479635942,1.501614476442E9,1.501614480942E9 +14,7020519559909589505,479635942,1.5016146195149999E9,1.5016146466039999E9 +14,-3665578959722742849,479635942,1.501614650353E9,1.501614651853E9 +14,-7119982566538871663,479635942,1.5016153092779999E9,1.501615311077E9 +14,-5146814387774832162,479635942,1.501615341619E9,1.501615342458E9 +14,-1968475871047378773,479635942,1.501615351158E9,1.5016153535E9 +14,-8872554050103052086,479635942,1.5016153987389998E9,1.501615399638E9 +14,-5808111080258819721,479635942,1.501615482194E9,1.501615482945E9 +14,281959769992484241,479635942,1.5016154875349998E9,1.501615489095E9 +14,2069680721360166668,479635942,1.501615491347E9,1.501615493685E9 +14,-2203194315871195098,479635942,1.5016155132749999E9,1.501615515075E9 +14,-5644959211383613757,479635942,1.501615525905E9,1.501615535955E9 +14,-4184214561838531671,479635942,1.501615537756E9,1.501615540457E9 +14,1705370279262435115,479635942,1.5016155772059999E9,1.501615589655E9 +14,7473027341214179601,479635942,1.501615641797E9,1.501615657996E9 +14,-3876124526351470397,479635942,1.501615714817E9,1.501615864817E9 +14,2106191494671183615,479635942,1.5016158731829998E9,1.5016161044859998E9 +14,-68653067807653614,479635942,1.5016163370119998E9,1.501616338812E9 +14,-4913037207397076415,479635942,1.501616459922E9,1.501616460823E9 +14,8407941901201923190,479635942,1.501616475283E9,1.501616477082E9 +14,-9161338843413088220,479635942,1.501616571103E9,1.5016165819329998E9 +14,161073715723794521,479635942,1.50161669183E9,1.501616692656E9 +29,6373613954626632990,479635942,1.519801585983E9,1.5198015877819998E9 +29,889862951668856209,479635942,1.5198016228209999E9,1.519801623692E9 +29,-7578821178615434161,479635942,1.519801682011E9,1.519801682912E9 +29,8625747728379018858,479635942,1.519801685671E9,1.5198016865709999E9 +29,-6158644541272785541,479635942,1.519801709191E9,1.51980171099E9 +29,-7833842548579193423,479635942,1.5198018046069999E9,1.519801806835E9 +29,-1116885831784161835,479635942,1.519801971494E9,1.519801973493E9 +29,906889542286998317,479635942,1.519802235997E9,1.5198025133309999E9 +29,874698431778755260,479635942,1.5198025441859999E9,1.519802570751E9 +29,-2618047012476716658,479635942,1.5198029208539999E9,1.519802921679E9 +29,3949533197419373451,479635942,1.519802926655E9,1.5198029308379998E9 +29,2943292331990812807,479635942,1.5198029508009999E9,1.5198029566569998E9 +29,-2011700701636547229,479635942,1.5198031067519999E9,1.519803108434E9 +29,8841704935251570427,479635942,1.519803153666E9,1.51980315452E9 +29,8772210033948648407,479635942,1.519803303075E9,1.519803308052E9 +29,708620121875807316,479635942,1.519803345616E9,1.519803387168E9 +29,-4852919504576283196,479635942,1.5198034422779999E9,1.519803443102E9 +29,3604698577670393771,479635942,1.519803531102E9,1.519803532754E9 +29,9026976508836738865,479635942,1.51980440054E9,1.519804401366E9 +29,6873272688709542036,479635942,1.5198044247949998E9,1.519804427353E9 +29,8276039023708252039,479635942,1.519804456475E9,1.5198044573E9 +29,7833009565387084629,479635942,1.5198044589759998E9,1.519804460655E9 +29,-4122928436402255343,479635942,1.519804463184E9,1.51980446401E9 +29,-3881433529630694608,479635942,1.519804545105E9,1.5198045459589999E9 +29,-3791273973291884494,479635942,1.5198046671499999E9,1.519804675483E9 +29,-7262801969275249263,479635942,1.5198047145319998E9,1.519804716182E9 +29,-2799763091563717935,479635942,1.519804719483E9,1.519804720307E9 +29,2530627489001561912,479635942,1.519804737799E9,1.519804740302E9 +29,-3933413081449918832,479635942,1.519804792937E9,1.519804793817E9 +14,-307883905288284849,627460548,1.501612827218E9,1.501612837218E9 +14,-8649994580952985792,627460548,1.501612923218E9,1.501612933218E9 +14,7058878073385005352,627460548,1.501612942218E9,1.501613569218E9 +14,5695109220663831475,627460548,1.501613649218E9,1.501614225218E9 +14,4000464388460685497,627460548,1.501614240218E9,1.501614319218E9 +14,-2217101916127203187,627460548,1.501614335218E9,1.501614366218E9 +14,1936956361818924209,627460548,1.501614614218E9,1.501614661218E9 +14,5853897049447232754,627460548,1.501614665218E9,1.501614670218E9 +14,-3869527058619321867,627460548,1.501614686218E9,1.501614799218E9 +14,5695189144668947542,627460548,1.501614840218E9,1.501614883218E9 +14,-7929147682195086959,627460548,1.501615571218E9,1.501616115218E9 +14,-5151618354262112056,627460548,1.501616251218E9,1.501616311218E9 +29,1929241547214098437,627460548,1.519802225524E9,1.519802519524E9 +29,-5614142304865639570,627460548,1.519802536524E9,1.519802615524E9 +29,5346892523983513214,627460548,1.519803347524E9,1.519803386524E9 +29,-2419547652203965706,627460548,1.519803670524E9,1.519803686524E9 +29,6053722807640517315,627460548,1.519803691524E9,1.519803767524E9 +29,-7674888954688925827,627460548,1.519803769524E9,1.519803944524E9 +29,6596488148397808413,627460548,1.519804056524E9,1.519804106524E9 +29,-2570503273283267027,627460548,1.519804108524E9,1.519804127524E9 +29,5739566477168771813,627460548,1.519804322524E9,1.519804348524E9 +61,3942676439566451263,479635942,1.5023918717930002E9,1.5023918731120002E9 +61,7558798174767674546,479635942,1.502391875694E9,1.5023918767470002E9 +61,5936843667444631105,479635942,1.5023919138260002E9,1.502391916194E9 +61,-7792208692954458371,479635942,1.502391933776E9,1.502391935184E9 +61,-1919721854392181822,479635942,1.5023925374640002E9,1.5023926866260002E9 +61,-5556648844250083073,479635942,1.5023929283860002E9,1.502392954052E9 +61,8909244349072401079,479635942,1.5023929630830002E9,1.502393016394E9 +61,-8406301509623552706,479635942,1.502393022153E9,1.502393028453E9 +61,1305685328096165151,479635942,1.502393157996E9,1.5023932556160002E9 +61,542108248419044515,479635942,1.502393264015E9,1.5023934108390002E9 +61,-8451436607884143589,479635942,1.502393420466E9,1.5023934662470002E9 +61,1849653628501251096,479635942,1.502393626203E9,1.502393628486E9 +61,-1458985599779847027,479635942,1.5023937323430002E9,1.5023937358240001E9 +61,-1842935852170325383,479635942,1.5023937580240002E9,1.502393759163E9 +61,-4571047198721082734,479635942,1.5023937725180001E9,1.5023937738330002E9 +61,4286645336572824125,479635942,1.502393923026E9,1.502393925515E9 +61,-313403767917633089,479635942,1.502393991367E9,1.502393992625E9 +61,2987429939682193652,479635942,1.50239405874E9,1.502394059792E9 +61,1257792799527490371,479635942,1.5023941311910002E9,1.5023941323930001E9 +68,-659846217501791887,479635942,1.519201958528E9,1.519201959339E9 +68,2060401380080743560,479635942,1.519202004969E9,1.519202005869E9 +68,6753288535632267848,479635942,1.519202019579E9,1.519202020479E9 +68,-5462639336689000255,479635942,1.519202090441E9,1.519202091278E9 +68,3257060850950043763,479635942,1.51920209353E9,1.519202094282E9 +68,6120795976430160732,479635942,1.51920209659E9,1.519202097341E9 +68,-8615025112689578399,479635942,1.5192021290770001E9,1.519202130878E9 +68,-5245101556940209298,479635942,1.5192021408700001E9,1.51920214267E9 +68,6529954211446053002,479635942,1.519202145369E9,1.519202147168E9 +68,-654200351740303506,479635942,1.519202184009E9,1.519202185718E9 +68,7200653626390166797,479635942,1.51920218731E9,1.519202189648E9 +68,5487976080443953836,479635942,1.519202208849E9,1.519202211307E9 +68,-305378061517564704,479635942,1.519202214007E9,1.519202224028E9 +68,-5436351900679562256,479635942,1.519202577787E9,1.519202578747E9 +68,7315291334904581500,479635942,1.519202636737E9,1.519202638446E9 +68,-7500040465621554565,479635942,1.519202645946E9,1.5192026483470001E9 +68,3315393717469316193,479635942,1.519202650596E9,1.519202653658E9 +68,-5263300673648927295,479635942,1.519202691306E9,1.5192026922350001E9 +68,4728834677068284191,479635942,1.519202694287E9,1.519202696075E9 +68,8524917417667562170,479635942,1.519203007144E9,1.519203007925E9 +68,-4666987856606545871,479635942,1.5192030254160001E9,1.519203027728E9 +61,2739059226238388352,627460548,1.502392531903E9,1.502392766903E9 +61,2010524747431456206,627460548,1.502392923903E9,1.502393086903E9 +61,5475534554314568792,627460548,1.502393101903E9,1.502393113903E9 +61,-4693778579235448871,627460548,1.502393132903E9,1.502393478903E9 +68,7087003071191319852,627460548,1.519202162118E9,1.519202167118E9 +51,543750207684301555,479635942,1.502132828904E9,1.502132830073E9 +51,4497356937130965253,479635942,1.502132880925E9,1.502132882154E9 +51,3286615959954598788,479635942,1.502133030925E9,1.502133032066E9 +51,3070137280515368601,479635942,1.502133054057E9,1.5021330551069999E9 +51,-6783953758166186495,479635942,1.502133192955E9,1.502133194276E9 +51,3762461902215959332,479635942,1.5021332219069998E9,1.50213322644E9 +51,-7782111808452972427,479635942,1.50213346645E9,1.5021336166299999E9 +51,-3322364525940459703,479635942,1.502133618529E9,1.502133798428E9 +51,-4460637422399144253,479635942,1.502133817073E9,1.502134039825E9 +51,-5465175562490924841,479635942,1.502134162089E9,1.502134172237E9 +51,-439721881505226500,479635942,1.502134184612E9,1.502134497945E9 +51,-8567499036176092659,479635942,1.502134515847E9,1.502134672654E9 +51,-433683495461338756,479635942,1.5021346890709999E9,1.502134739865E9 +51,-3864134445828993716,479635942,1.502135040165E9,1.5021351941399999E9 +51,-4292814945283134318,479635942,1.502135434951E9,1.502135916947E9 +51,-4736875507479866621,479635942,1.50213616711E9,1.502136170026E9 +51,4011101075862701804,479635942,1.50213618977E9,1.502136190899E9 +51,2586511385772063378,479635942,1.5021362763969998E9,1.5021362778E9 +51,6731204817223828629,479635942,1.50213630021E9,1.5021363027159998E9 +51,-6764618376058154485,479635942,1.5021363507849998E9,1.502136352158E9 +51,6217368938625732377,479635942,1.502136403856E9,1.5021364049029999E9 +51,315892509708435408,479635942,1.502136407322E9,1.502136408723E9 +51,6044768987979669608,479635942,1.5021364124359999E9,1.502136417496E9 +51,4106314998867256567,479635942,1.502136425225E9,1.5021364300349998E9 +51,8674784803480153578,479635942,1.502136434768E9,1.5021364359229999E9 +51,3304363438203012574,479635942,1.502136480912E9,1.502136482177E9 +51,6254839126541836790,627460548,1.502133329389E9,1.502133380389E9 +51,-8716395367076826556,627460548,1.502133401389E9,1.502133414389E9 +51,7067720531491531927,627460548,1.502133435389E9,1.502133801389E9 +51,8753268841033951747,627460548,1.502133810389E9,1.502134059389E9 +51,-2460379312560727788,627460548,1.502134148389E9,1.502134744389E9 +51,-7532067162463005517,627460548,1.502134755389E9,1.502134785389E9 +51,-3674515065337974130,627460548,1.502135033389E9,1.502135210389E9 +51,-7856585878364129353,627460548,1.502135232389E9,1.502135280389E9 +51,-9065149239914724017,627460548,1.502135414389E9,1.502135925389E9 +23,8743841902486875519,479635942,1.502091441667E9,1.502091442926E9 +23,-1815580840427260185,479635942,1.502091564843E9,1.502091566072E9 +23,1831040082004246906,479635942,1.502091606362E9,1.5020916078E9 +23,5270146368629148846,479635942,1.502091648813E9,1.502091650013E9 +23,-1727642305565292716,479635942,1.5020916523830001E9,1.5020916536460001E9 +23,3774907707132856675,479635942,1.502091780935E9,1.502091782253E9 +23,7821256584854543491,479635942,1.502091785522E9,1.502091789062E9 +23,-8742197624678799052,479635942,1.502091791706E9,1.502091792842E9 +23,7762330275797652110,479635942,1.502091795302E9,1.502091797762E9 +23,-8398575317146482131,479635942,1.5020918618140001E9,1.5020918639720001E9 +23,7280248696194713716,479635942,1.502091941496E9,1.502091942513E9 +23,3126541511176481818,479635942,1.502091948607E9,1.502091951996E9 +23,7520215219730619596,479635942,1.5020919532250001E9,1.502091955684E9 +23,-2517561300612258391,479635942,1.50209206482E9,1.502092067311E9 +23,-5314944328307161117,479635942,1.502092135592E9,1.5020921373630002E9 +23,3198581219875969683,479635942,1.50209213925E9,1.5020921404520001E9 +23,6771874141471754178,479635942,1.502092142614E9,1.502092144775E9 +23,7913393774671415057,479635942,1.502092208253E9,1.502092213171E9 +23,8360142497055685968,479635942,1.502092239272E9,1.502092477951E9 +23,1321633043861626785,479635942,1.502092610971E9,1.50209261832E9 +23,9155526153704761756,479635942,1.502092949399E9,1.5020929551590002E9 +23,3169582306503763089,479635942,1.502092956089E9,1.502092959628E9 +23,-7897431255897957192,479635942,1.5020929650310001E9,1.502092969649E9 +23,1776219904924426574,479635942,1.502092971809E9,1.502092974089E9 +23,3019312906767128780,479635942,1.50209299953E9,1.50209300346E9 +23,5534923394134588498,479635942,1.502093332647E9,1.5020933339390001E9 +23,7755700028751275147,479635942,1.5020934563400002E9,1.502093481694E9 +23,-6661520972218287233,479635942,1.502093656773E9,1.50209366028E9 +23,-3316585860975761736,479635942,1.50209367927E9,1.502093724059E9 +23,8840941970603122908,479635942,1.502093758468E9,1.502093762069E9 +23,-2082341475622691172,479635942,1.502093788888E9,1.502093813879E9 +23,-1443976185612522745,479635942,1.502093861009E9,1.502093875197E9 +23,-4793781874195099802,479635942,1.50209388795E9,1.502093912368E9 +23,-2143264385123146658,479635942,1.502093939968E9,1.502094254727E9 +23,-311803229808367634,479635942,1.502094420718E9,1.502094799828E9 +23,6686205899795050510,479635942,1.5020948166920002E9,1.502094819989E9 +23,-5057128576255093012,479635942,1.502094842487E9,1.502094845637E9 +23,-7181504577915952360,479635942,1.502094864061E9,1.502095028336E9 +23,-2607794686996352512,479635942,1.502095232395E9,1.502095237918E9 +23,-2396571768388247142,479635942,1.5020953859650002E9,1.5020953871330001E9 +23,-2672466432812372448,479635942,1.502095407444E9,1.502095408677E9 +23,7133652549603479553,479635942,1.502095470714E9,1.502095471884E9 +23,595821520593960475,479635942,1.502095521204E9,1.502095522526E9 +23,-3551747066318806915,479635942,1.502095541336E9,1.5020955426560001E9 +23,1740025291928650981,479635942,1.502095672645E9,1.502095673726E9 +23,-3948943269101169837,479635942,1.5020956760080001E9,1.502095677115E9 +23,8400426520138412748,479635942,1.502095723255E9,1.502095724399E9 +23,2134700134595476609,479635942,1.502095781302E9,1.5020957825340002E9 +23,-167616118949794257,479635942,1.502095784783E9,1.502095786081E9 +23,-1193902352413803975,627460548,1.502092210075E9,1.502092482075E9 +23,3462681701974189718,627460548,1.502092535075E9,1.502092554075E9 +23,7743903351294224373,627460548,1.502092563075E9,1.502092629075E9 +23,3809305830801805325,627460548,1.502092640075E9,1.502092662075E9 +23,-7076994491943853816,627460548,1.502092670075E9,1.502092714075E9 +23,-2101983596415915612,627460548,1.502092719075E9,1.502092793075E9 +23,-491556812470713919,627460548,1.502092955075E9,1.502092956075E9 +23,7789936837502469369,627460548,1.502092958075E9,1.502092960075E9 +23,8644528257382294923,627460548,1.502092966075E9,1.502093148075E9 +23,-4636040524457572399,627460548,1.502093620075E9,1.502093668075E9 +23,-3018339305190296126,627460548,1.502093674075E9,1.502093734075E9 +23,-1099296226433255731,627460548,1.502093753075E9,1.502093819075E9 +23,-27370822391443730,627460548,1.502093826075E9,1.502093839075E9 +23,-1999810213917561252,627460548,1.502093847075E9,1.502094320075E9 +23,-4855933800844096675,627460548,1.502094361075E9,1.502094413075E9 +23,3478282783141948789,627460548,1.502094415075E9,1.502094802075E9 +23,-5624830077630378336,627460548,1.502094843075E9,1.502095077075E9 +36,-6055753206250783153,479635942,1.5198367671680002E9,1.5198367679180002E9 +36,4386541687749300082,479635942,1.519836864278E9,1.519836865028E9 +36,3797686429492012788,479635942,1.5198368868390002E9,1.5198368877670002E9 +36,2808234819795757153,479635942,1.5198369140470002E9,1.5198369149480002E9 +36,-424181240516748671,479635942,1.5198369555080001E9,1.5198369564060001E9 +36,-4747319851952021269,479635942,1.5198369650470002E9,1.5198369665470002E9 +36,2531665241675691570,479635942,1.5198370275660002E9,1.5198370284970002E9 +36,8110920152329178923,479635942,1.519837031196E9,1.5198370320670002E9 +36,-3722257502341928075,479635942,1.519837073706E9,1.5198370746060002E9 +36,-2185984822815736965,479635942,1.5198371478660002E9,1.519837149696E9 +36,5822896447778858343,479635942,1.5198371551850002E9,1.5198371560860002E9 +36,7146284416221669138,479635942,1.5198372425740001E9,1.5198372434750001E9 +36,-5976672557143701873,479635942,1.5198376230070002E9,1.519837623877E9 +36,-8190225499557280914,479635942,1.5198380225440001E9,1.519838024857E9 +36,-6057182085704737882,479635942,1.519838199397E9,1.519838202966E9 +36,3653485765639364848,479635942,1.5198382057250001E9,1.5198382093250003E9 +36,2435223857680124326,479635942,1.5198383039470003E9,1.519838309344E9 +36,-2365257864108798165,479635942,1.519838385423E9,1.5198383863830001E9 +36,-1587802966150975571,479635942,1.5198385538140001E9,1.5198387595210001E9 +36,6166197683570272587,479635942,1.5198387879600003E9,1.519838795821E9 +36,4429707809253824246,479635942,1.5198388153200002E9,1.5198388730390003E9 +36,4668575776155057916,479635942,1.5198391563000002E9,1.5198391585500002E9 +36,-5450798862295898118,479635942,1.5198391828810003E9,1.5198391837810001E9 +36,-6406885925288789127,479635942,1.5198392664610002E9,1.5198392696100001E9 +36,2248648754414924262,479635942,1.5198392781000001E9,1.5198392804120002E9 +36,-9149770372330624644,479635942,1.51983928341E9,1.5198392850000002E9 +36,4529843190981844135,479635942,1.5198393328190002E9,1.519839333719E9 +36,2442318781916160208,479635942,1.5198394556390002E9,1.519839456538E9 +36,-5712772855668453246,627460548,1.5198376376390002E9,1.5198376826390002E9 +36,-936071058274127192,627460548,1.5198376846390002E9,1.5198379896390002E9 +36,6182466801921697880,627460548,1.5198384056390002E9,1.5198388956390002E9 +36,2764937084838059256,627460548,1.5198390616390002E9,1.5198390676390002E9 +60,2934912818293942492,479635942,1.519406854441E9,1.51940685534E9 +60,-1260990045655202805,479635942,1.519406856241E9,1.519406858064E9 +60,7545715224929917771,479635942,1.519406859872E9,1.519406861641E9 +60,-8083103261115791360,479635942,1.51940689023E9,1.5194068924819999E9 +60,58595489647474327,479635942,1.51940698566E9,1.5194069865E9 +60,3092960690980761370,479635942,1.51940713125E9,1.519407132E9 +60,2018476724893868400,479635942,1.519407153329E9,1.519407155578E9 +60,380086147145237127,479635942,1.519407318989E9,1.51940732076E9 +60,2739552313725409850,479635942,1.5194075147089999E9,1.519407515457E9 +60,3758754905959647568,479635942,1.519407674907E9,1.519407676707E9 +60,5984495362242184591,479635942,1.519407701577E9,1.519407708689E9 +60,-2296604483576504222,479635942,1.5194077104589999E9,1.519407711296E9 +60,-2003248384817158649,479635942,1.519407740486E9,1.519407742075E9 +60,-5957105046511196668,479635942,1.519407743576E9,1.519407747326E9 +60,-1265979175120690788,479635942,1.519407748136E9,1.519407749637E9 +60,-8566514391736445,479635942,1.519407852449E9,1.519407854008E9 +60,-5069380025109030493,479635942,1.519407870147E9,1.519407871047E9 +60,-6055740349077778112,479635942,1.519407872877E9,1.519407879237E9 +60,1473546834077392745,479635942,1.519407915597E9,1.519407918296E9 +60,-7284278355992301035,479635942,1.519407941606E9,1.519407945117E9 +60,-5340812375235639293,479635942,1.519407989246E9,1.519407990748E9 +60,740834529638730249,479635942,1.519408219764E9,1.519408221264E9 +60,-7071204841825428040,479635942,1.519408224385E9,1.519408227624E9 +60,7795594737268911898,479635942,1.519408253994E9,1.519408254924E9 +60,-8266519952786158444,479635942,1.519408259455E9,1.519408262185E9 +60,533367514623804258,479635942,1.519408405675E9,1.5194084075049999E9 +60,1427775550834252078,479635942,1.519408411856E9,1.519408416744E9 +60,6861773023251980982,479635942,1.519408542383E9,1.519408544843E9 +60,1972420266941434551,627460548,1.51940743701E9,1.51940744301E9 +60,8502387868844848767,627460548,1.51940752801E9,1.51940754301E9 +60,-2973429542682111206,627460548,1.51940762401E9,1.51940766701E9 +18,-3067329546929472089,479635942,1.5203612223750002E9,1.5203612232740002E9 +18,855667287532086690,479635942,1.5203612764930003E9,1.5203612772430003E9 +18,-865732098691420796,479635942,1.5203614495310001E9,1.5203614504300003E9 +18,-7573410709887323988,479635942,1.5203615999210002E9,1.5203616006420002E9 +18,7350953419317389592,479635942,1.5203616913610003E9,1.5203616922600002E9 +18,-2699540771637265069,479635942,1.5203616948110003E9,1.5203616956800003E9 +18,-1245435920705750258,479635942,1.5203620215390003E9,1.5203620360890002E9 +18,8524860812756793355,479635942,1.5203620575700002E9,1.5203621710600002E9 +18,6865513907305010252,479635942,1.5203621790390003E9,1.5203623500990002E9 +18,-5648864626730826723,479635942,1.5203623546590002E9,1.5203623708890002E9 +18,2346706427553166791,479635942,1.5203623997200003E9,1.5203625564700003E9 +18,-8469937292856013198,479635942,1.5203625603700004E9,1.5203626063890002E9 +18,-92057532065615200,479635942,1.5203626454190001E9,1.5203626471580002E9 +18,5072294032365793401,479635942,1.5203626687900002E9,1.5203626775190003E9 +18,8666457031223333664,479635942,1.5203626812690003E9,1.5203626827700002E9 +18,8691499208442153154,479635942,1.5203627061680002E9,1.5203627106690001E9 +18,-8745072969325832104,479635942,1.5203627495980003E9,1.5203627533480003E9 +18,7667558454476846407,479635942,1.5203627552680001E9,1.5203627592270002E9 +18,-2537500513366012888,479635942,1.5203628058470004E9,1.5203628121470003E9 +18,-4077576540543430347,479635942,1.5203628145170002E9,1.5203628222280002E9 +18,-1461324079301056527,479635942,1.5203628407070003E9,1.5203628642870002E9 +18,-8169034302972550824,479635942,1.5203628680970004E9,1.5203629028680003E9 +18,-1886677737163335314,479635942,1.5203629287880003E9,1.5203629507170002E9 +18,-7814509901498706674,479635942,1.5203629678770003E9,1.5203630192060003E9 +18,3930024948530012324,479635942,1.5203630330070002E9,1.5203632433370001E9 +18,805222571233747500,479635942,1.5203632608280003E9,1.5203634886460001E9 +18,-9011889575356403221,479635942,1.5203635424070003E9,1.5203635567460003E9 +18,-8346629542750559307,479635942,1.5203635594170003E9,1.5203636844270003E9 +18,8356553013206954811,479635942,1.5203636933370001E9,1.5203638430460002E9 +18,3680174806659670485,479635942,1.5203640437170002E9,1.5203640475570002E9 +18,-3393406703534878562,479635942,1.5203641263960001E9,1.5203641288560002E9 +18,-7045113756597439853,479635942,1.5203641415470002E9,1.5203641687880003E9 +18,4728653995467216718,479635942,1.5203642106970003E9,1.5203644748780003E9 +18,-3538298193453544904,479635942,1.5203644771570003E9,1.5203646470780003E9 +18,7092346266920155746,479635942,1.5203647351580002E9,1.5203647361490002E9 +18,-8510285598460868821,479635942,1.5203647400180001E9,1.5203647413980002E9 +18,6446444733357926836,479635942,1.5203649295580003E9,1.5203649313590002E9 +18,-4303032119411501748,479635942,1.5203649715590003E9,1.5203649728190002E9 +18,-659150799576613378,479635942,1.5203650543270001E9,1.5203650551100001E9 +18,-7730642124052350267,479635942,1.5203650878980002E9,1.5203650887980003E9 +18,-5222473174776703482,479635942,1.5203651835080001E9,1.5203651842890003E9 +18,-1785119036470513843,479635942,1.5203651865990002E9,1.5203651881890001E9 +18,-6718587564997163128,479635942,1.5203651896880002E9,1.5203651904380002E9 +18,-5806820180983958879,479635942,1.5203652040580003E9,1.5203652049580002E9 +32,8934657918536855286,479635942,1.5192824531660001E9,1.519282454065E9 +32,6049557701565648016,479635942,1.519282456767E9,1.519282458595E9 +32,-7078122234678541414,479635942,1.5192824992450001E9,1.519282500987E9 +32,-1084137115098581840,479635942,1.519282501856E9,1.519282503657E9 +32,2945048538349196271,479635942,1.5192825053370001E9,1.519282508005E9 +32,-6031593413107805380,479635942,1.519282594345E9,1.519282596175E9 +32,158746424866914199,479635942,1.519282620565E9,1.519282621464E9 +32,149915628509184101,479635942,1.519282763514E9,1.519282765134E9 +32,-1366924908160702712,479635942,1.519282767503E9,1.519282768254E9 +32,6209820256056437869,479635942,1.519283029162E9,1.519283032223E9 +32,-6432555845472254846,479635942,1.519283047194E9,1.519283258241E9 +32,-5506749818469597869,479635942,1.51928353193E9,1.519283537362E9 +32,-3446827409990342929,479635942,1.51928365652E9,1.51928365742E9 +32,-1930570731160562298,479635942,1.5192837516790001E9,1.519283753509E9 +32,7658598484312204229,479635942,1.5192837616990001E9,1.519283763558E9 +32,2734822677523225278,479635942,1.519283765359E9,1.519283767158E9 +32,5997939773893072129,479635942,1.519283852118E9,1.51928385395E9 +32,-8199057249826061271,479635942,1.5192838566790001E9,1.519283857579E9 +32,-5568831989031947570,479635942,1.519283911069E9,1.519283922589E9 +32,-1665332462298658611,479635942,1.519283998968E9,1.519284002568E9 +32,-683060160117814657,479635942,1.519284168677E9,1.519284170177E9 +32,7000561940650477507,479635942,1.5192842626990001E9,1.519284263536E9 +32,-970354370344721562,479635942,1.5192842671660001E9,1.519284269866E9 +32,6011199701168848071,479635942,1.519284312076E9,1.519284314326E9 +32,-1783342807546124916,479635942,1.519284379397E9,1.519284382037E9 +32,-7691360342129082254,479635942,1.5192843928370001E9,1.519284402197E9 +32,-3678550659039911234,479635942,1.5192844139880002E9,1.519284416989E9 +32,4803828546838365438,479635942,1.519284426288E9,1.519284431628E9 +32,-4693860957991396146,479635942,1.519284450287E9,1.519284492589E9 +32,6560172732529654812,479635942,1.519284570647E9,1.519284571546E9 +32,-53803932618865730,479635942,1.519284600496E9,1.519284617265E9 +32,368900461102482337,479635942,1.519284639407E9,1.519284641895E9 +32,-7251387275386436079,479635942,1.519284922124E9,1.5192849257250001E9 +32,4351028392153228315,479635942,1.5192849953860002E9,1.519284997874E9 +32,-5874576065582818741,479635942,1.519285006274E9,1.5192850163240001E9 +32,-9172987873079649263,479635942,1.519285033274E9,1.519285035103E9 +32,6793256314041164540,479635942,1.5192850605440001E9,1.519285061444E9 +32,-4534581833271138381,479635942,1.5192850641430001E9,1.5192850659750001E9 +32,-5219473623828731622,479635942,1.519285148263E9,1.519285149193E9 +32,-6914519818091662060,479635942,1.5192851546230001E9,1.519285155522E9 +32,8485520029320013134,479635942,1.519285201243E9,1.519285202868E9 +32,9000857604708628967,479635942,1.519285223292E9,1.519285224793E9 +32,-3926682035437754824,479635942,1.519285262772E9,1.519285263703E9 +32,-1853907465556525539,479635942,1.519285266491E9,1.519285268322E9 +18,-2859186189766336797,627460548,1.5203618431940002E9,1.5203618601940002E9 +18,-4249004404059317400,627460548,1.5203619141940002E9,1.5203619201940002E9 +18,7371028017153776667,627460548,1.5203619611940002E9,1.5203626311940002E9 +18,181758904111519434,627460548,1.5203628071940002E9,1.5203628111940002E9 +18,5637869652697200842,627460548,1.5203628161940002E9,1.5203628251940002E9 +18,878450484024994845,627460548,1.5203628271940002E9,1.5203629141940002E9 +18,4093885003405775926,627460548,1.5203629241940002E9,1.5203634951940002E9 +18,8007889217450764117,627460548,1.5203635191940002E9,1.5203638731940002E9 +18,-6962095228683673558,627460548,1.5203640451940002E9,1.5203641011940002E9 +18,4346470844899869974,627460548,1.5203641181940002E9,1.5203646591940002E9 +18,5241561255946406098,627460548,1.5203648651940002E9,1.5203648671940002E9 +32,-4256920046965479257,627460548,1.519282803154E9,1.519282826154E9 +32,7483473958230435926,627460548,1.519283030154E9,1.519283036154E9 +32,3005432382863606927,627460548,1.519283037154E9,1.519283392154E9 +32,-4244045975232735173,627460548,1.519283456154E9,1.519283464154E9 +32,1344934605056030447,627460548,1.519283912154E9,1.519283923154E9 +32,-5926231927670243563,627460548,1.519283932154E9,1.519283939154E9 +32,-3166164134456886287,627460548,1.519284022154E9,1.519284024154E9 +32,6174671180011375179,627460548,1.519284033154E9,1.519284048154E9 +32,2585718785484032007,627460548,1.519284395154E9,1.519284401154E9 +32,7877891411236254166,627460548,1.519284427154E9,1.519284702154E9 +32,-3811067830832172452,627460548,1.519284916154E9,1.519284933154E9 +42,-4134113363336087883,479635942,1.519370107867E9,1.519370108798E9 +42,-3388073998530469018,479635942,1.5193701769720001E9,1.519370177783E9 +42,-8463177006792599292,479635942,1.5193702627440002E9,1.5193702643040001E9 +42,-7459853409362475299,479635942,1.5193702682910001E9,1.5193702706920002E9 +42,8845189384494358332,479635942,1.519370346621E9,1.5193703475210001E9 +42,1154537846906642197,479635942,1.5193703897640002E9,1.519370390631E9 +42,835405396048213771,479635942,1.519370469801E9,1.5193704707010002E9 +42,5760182802117772295,479635942,1.519370473341E9,1.519370475141E9 +42,4511498811783002287,479635942,1.519370507811E9,1.519370508561E9 +42,3512965613532280728,479635942,1.519370510963E9,1.5193705127010002E9 +42,3232814488176612701,479635942,1.519370518611E9,1.519370520111E9 +42,-3434573738155782907,479635942,1.5193707191590002E9,1.519370720959E9 +42,-7067900348463904811,479635942,1.5193707218590002E9,1.519370730079E9 +42,4767581667772941877,479635942,1.519370793348E9,1.5193708456390002E9 +42,446790151727806869,479635942,1.519370848338E9,1.5193709949480002E9 +42,6327615009309145792,479635942,1.5193710116880002E9,1.5193710185870001E9 +42,4950633481711560151,479635942,1.5193710200870001E9,1.519371020838E9 +42,-8733481403670855711,479635942,1.519371027887E9,1.519371154487E9 +42,-740349156455077247,479635942,1.519371286516E9,1.5193712883460002E9 +42,3903280845553390002,479635942,1.519371340068E9,1.519371340845E9 +42,-5303107034098492109,479635942,1.519371448305E9,1.5193714492350001E9 +42,-2799496898824949138,479635942,1.519371524895E9,1.5193715330240002E9 +42,6264483971090658439,479635942,1.5193715928760002E9,1.519371594437E9 +42,4625783453460589368,479635942,1.5193716065870001E9,1.519371649848E9 +42,5546299446988002901,479635942,1.519371709094E9,1.5193717167480001E9 +42,-3636876291274977720,479635942,1.519372067022E9,1.519372070023E9 +42,3333784659751097057,479635942,1.5193720870010002E9,1.519372116913E9 +42,7778969535395385039,479635942,1.519372145173E9,1.519372147693E9 +42,3609659744209016781,479635942,1.519372150933E9,1.519372153996E9 +42,-1733236380887484169,479635942,1.5193723239720001E9,1.5193723339610002E9 +42,227672378979233137,479635942,1.5193723891620002E9,1.5193723932710001E9 +42,5212567250034234716,479635942,1.519372420091E9,1.5193724218920002E9 +42,-572955803921532859,479635942,1.5193724553690002E9,1.51937245627E9 +42,-2423576877274826193,479635942,1.5193724635E9,1.5193724653600001E9 +42,3265687558345174295,479635942,1.5193725375700002E9,1.519372538499E9 +42,-5092552551673860752,479635942,1.5193725439E9,1.5193725457E9 +42,-3006883077513335746,479635942,1.519372564809E9,1.51937256571E9 +42,728742179982519379,479635942,1.51937258551E9,1.5193725881790001E9 +42,1132235550827174095,479635942,1.519372595108E9,1.519372596881E9 +42,-841880160778604851,479635942,1.519372602194E9,1.519372603059E9 +42,4086894968779327609,479635942,1.5193726596690001E9,1.519372660569E9 +42,1470318877404890547,479635942,1.519372662368E9,1.5193726642280002E9 +55,-48025441560230825,479635942,1.502177640911E9,1.50217764193E9 +55,-19979916166841293,479635942,1.502177746122E9,1.502177747351E9 +55,-5795292549698599562,479635942,1.50217778275E9,1.502177783922E9 +55,7539125758013350596,479635942,1.502177835613E9,1.502177836663E9 +55,-8156276679153796233,479635942,1.502177838493E9,1.5021778397220001E9 +55,-1414055780388177597,479635942,1.502177866755E9,1.502177867986E9 +55,-2208719609941398075,479635942,1.502177910192E9,1.502177911425E9 +55,-5957308379540630610,479635942,1.502177913612E9,1.502177914932E9 +55,5704106334575510346,479635942,1.502177954202E9,1.502177955312E9 +55,8654364280051868767,479635942,1.502178023565E9,1.502178026803E9 +55,7445159219532497199,479635942,1.502178029503E9,1.502178030761E9 +55,5442119162762560274,479635942,1.502178117134E9,1.502178118243E9 +55,-8184944023954090738,479635942,1.502178315099E9,1.50217842343E9 +55,-2426180511212951508,479635942,1.50217842709E9,1.502178507433E9 +55,-6708510743915431498,479635942,1.5021785145410001E9,1.502178650054E9 +55,-1261129340543141979,479635942,1.5021790253470001E9,1.502179032935E9 +55,-3623059917793340700,479635942,1.502179069329E9,1.502179071876E9 +55,-4135913523759044851,479635942,1.502179401667E9,1.502179402778E9 +55,3434714314680258029,479635942,1.50217949548E9,1.502179499138E9 +55,-2554038418762009953,479635942,1.50217950193E9,1.502179503008E9 +55,-4471715255006922851,479635942,1.50217967518E9,1.50217967659E9 +55,-8210513061077247281,479635942,1.50217968019E9,1.502179682591E9 +55,-965390834935908422,479635942,1.502180339165E9,1.502180340276E9 +55,5281527833886871623,479635942,1.502180344955E9,1.502180347389E9 +55,-3285865106376720227,479635942,1.502180376368E9,1.502180377478E9 +55,5011614125230843231,479635942,1.502180465137E9,1.502180466247E9 +55,4702728297047247994,479635942,1.502180470806E9,1.502180472128E9 +55,-1824006112483855637,479635942,1.502180524117E9,1.502180525258E9 +55,-2683383206332124003,479635942,1.502180559248E9,1.502180561619E9 +42,-7912935343280471697,627460548,1.519370783173E9,1.519370999173E9 +42,-851685137523786953,627460548,1.519371004173E9,1.519371231173E9 +42,-5350538308380134869,627460548,1.519371505173E9,1.519371509173E9 +42,3379928035265915344,627460548,1.519371594173E9,1.519371826173E9 +42,1538379527211864972,627460548,1.519371905173E9,1.519371918173E9 +42,875889519431894876,627460548,1.519371942173E9,1.519372004173E9 +42,6205166047096595600,627460548,1.519372019173E9,1.519372029173E9 +42,-3738117419962829203,627460548,1.519372090173E9,1.519372116173E9 +42,2490413319664980551,627460548,1.519372279173E9,1.519372374173E9 +55,5487529334640880100,627460548,1.502178315786E9,1.502178752786E9 +55,-7307326885282895040,627460548,1.502178755786E9,1.502178836786E9 +55,7009414100077454645,627460548,1.502178842786E9,1.502178853786E9 +55,-920859862867254028,627460548,1.502178885786E9,1.502178898786E9 +55,-8583451963099113910,627460548,1.502179038786E9,1.502179039786E9 +55,8815549264414514294,627460548,1.502179070786E9,1.502179184786E9 +55,2188868774875393159,627460548,1.502179206786E9,1.502179236786E9 +55,-4544591479733560606,627460548,1.502179842786E9,1.502179866786E9 +67,-3815805161973396274,479635942,1.501189930017E9,1.5011899309169998E9 +67,8730435977513892906,479635942,1.5011899533769999E9,1.5011899556269999E9 +67,-133607035920218108,479635942,1.501189971468E9,1.501189972218E9 +67,-7395884725851964126,479635942,1.5011899936699998E9,1.5011899944169998E9 +67,608255642897355567,479635942,1.50119004977E9,1.501190051508E9 +67,5022999938454482429,479635942,1.5011902553E9,1.501190256201E9 +67,-8471217710021983369,479635942,1.501190853291E9,1.5011908570419998E9 +67,8642749783684377471,479635942,1.5011908663109999E9,1.5011908672099998E9 +67,8730754906652675946,479635942,1.501190869971E9,1.501190871771E9 +67,-364163425457667417,479635942,1.50119087357E9,1.50119087627E9 +67,-7021725377944245298,479635942,1.50119087807E9,1.5011908808E9 +67,-8168724823461864768,479635942,1.5011908826009998E9,1.50119089259E9 +67,-5674261482481767898,479635942,1.50119089529E9,1.50119089802E9 +67,7915533170076608903,479635942,1.501190923937E9,1.5011909266069999E9 +67,-5125506919487958647,479635942,1.501190929967E9,1.501190942297E9 +67,8117487510307080933,479635942,1.5011909651859999E9,1.501190965937E9 +67,4159376833233017526,479635942,1.5011909893669999E9,1.501190991227E9 +67,5299496890732364785,479635942,1.50119099303E9,1.5011909948279998E9 +67,395668964739582154,479635942,1.501191004728E9,1.5011910156169999E9 +67,8373950708020039731,479635942,1.501191020118E9,1.501191021948E9 +67,4924485034467824004,479635942,1.501191037368E9,1.501191040007E9 +69,4727516487544984000,479635942,1.499677049372E9,1.4996770503019998E9 +69,6424569148418921661,479635942,1.499677275965E9,1.4996772768639998E9 +69,-2253760923462333686,479635942,1.499677285896E9,1.499677287695E9 +69,6908173154531340168,479635942,1.499677334189E9,1.499677335093E9 +69,3653240058018382574,479635942,1.499677380899E9,1.499677383149E9 +69,6366518650006339483,479635942,1.49967741402E9,1.49967741951E9 +69,186780110437232471,479635942,1.499677516921E9,1.499677518511E9 +69,-7929904322239886738,479635942,1.499677605842E9,1.499677608393E9 +69,-3261042088359356438,479635942,1.499677618832E9,1.499677619672E9 +69,7032152434587761662,479635942,1.4996776810519998E9,1.4996776820119998E9 +69,-5680440152726278459,479635942,1.499677687472E9,1.4996776883709998E9 +69,3187804350783910137,479635942,1.4996777459129999E9,1.4996777475939999E9 +69,-8896716654490465770,479635942,1.49967783177E9,1.499677832669E9 +69,7006643973680009199,479635942,1.499677973434E9,1.499677974303E9 +69,-6693344335721958609,479635942,1.4996781064859998E9,1.499678108313E9 +59,3350481714876024846,479635942,1.501095007345E9,1.501095008095E9 +59,-263242049190437257,479635942,1.501095026755E9,1.501095027655E9 +59,7200825810760123433,479635942,1.501095068366E9,1.501095070165E9 +59,4748604066403151000,479635942,1.501095089638E9,1.501095091376E9 +59,8582984287519172746,479635942,1.501095113277E9,1.501095114779E9 +59,8185678414630450983,479635942,1.501095168851E9,1.501095169751E9 +59,8229345466977713381,479635942,1.50109517245E9,1.50109517515E9 +59,-9101456639018303657,479635942,1.50109517599E9,1.501095177731E9 +59,2729289401462033310,479635942,1.501095241601E9,1.501095242531E9 +59,5429075264786362749,479635942,1.501095244331E9,1.501095245231E9 +59,-1080616164905810183,479635942,1.501095268781E9,1.501095270552E9 +59,2564572583354547501,479635942,1.501095272322E9,1.501095278711E9 +59,-3308528293073106383,479635942,1.501095284713E9,1.501095287142E9 +59,4115231572194915299,479635942,1.501095305921E9,1.501095308232E9 +59,5532530606205423292,479635942,1.501095311321E9,1.501095313632E9 +59,-846367447846750194,479635942,1.50109556563E9,1.501095567129E9 +59,-3864831769372273617,479635942,1.501095592119E9,1.50109561204E9 +59,5639042423344374593,479635942,1.5010956138700001E9,1.501095622929E9 +59,2377848345732066714,479635942,1.50109566865E9,1.5010957101100001E9 +59,7974752384273336244,479635942,1.501095746547E9,1.50109574905E9 +59,-2466782466495866825,479635942,1.501095751524E9,1.501095764315E9 +59,6573997764258830319,479635942,1.501095836006E9,1.501095844588E9 +59,4966886326858506422,479635942,1.501095870243E9,1.501095889715E9 +59,-2767740011135421697,479635942,1.501095914051E9,1.5010959302220001E9 +59,-7086667094388192316,479635942,1.501095934182E9,1.501096046846E9 +59,5328559055791554716,479635942,1.501096058312E9,1.501096075887E9 +59,-589989017151786518,479635942,1.501096140153E9,1.501096213248E9 +59,7419762243858291660,479635942,1.501096215476E9,1.501096217292E9 +59,9099795995296812798,479635942,1.501096219438E9,1.501096221499E9 +59,-5550926322996191170,479635942,1.501096269267E9,1.501096277269E9 +59,-284655148077937779,479635942,1.501096364721E9,1.5010963668370001E9 +59,-6893556550553645614,479635942,1.5010965953600001E9,1.501096606333E9 +59,-8389659655529651788,479635942,1.5010966273700001E9,1.501096629763E9 +59,410262070962712932,479635942,1.501096631826E9,1.501096633557E9 +59,-4606593424084476743,479635942,1.501096639498E9,1.501096641561E9 +59,-318946654971018645,479635942,1.50109670921E9,1.501096717544E9 +59,-5015318753063608449,479635942,1.50109672167E9,1.501096731652E9 +59,-4138423948683528377,479635942,1.50109674155E9,1.501096745511E9 +59,7925092161796871972,479635942,1.501096778429E9,1.501096780327E9 +59,2359896765514720549,479635942,1.501096784701E9,1.501096788412E9 +59,8365071322529314144,479635942,1.501096807057E9,1.501096811567E9 +59,480457813073038484,479635942,1.501096815721E9,1.501096823805E9 +59,3072485737982897898,479635942,1.501096825838E9,1.5010968319720001E9 +59,8810057974074351431,479635942,1.501096927892E9,1.501096951982E9 +59,-7531354630400008639,479635942,1.501096957261E9,1.501097003022E9 +59,4815116084446622697,479635942,1.501097021696E9,1.5010970273600001E9 +59,6980557923375114361,479635942,1.501097044635E9,1.501097174652E9 +59,-6131887073244088573,479635942,1.501097203034E9,1.501097211037E9 +59,-6701476241207502825,479635942,1.50109721747E9,1.501097288308E9 +59,3741774792690559203,479635942,1.501097305055E9,1.501097307366E9 +59,5307304353214543868,479635942,1.501097344491E9,1.501097351993E9 +59,-8625391522545896299,479635942,1.5010973781990001E9,1.501097385818E9 +59,1574440280973894149,479635942,1.501097399595E9,1.50109742022E9 +59,3794601120187185550,479635942,1.50109745248E9,1.501097491419E9 +59,-151541347759527609,479635942,1.501097510558E9,1.501097512704E9 +59,3521607041605394326,479635942,1.501097518645E9,1.5010975272220001E9 +59,-7187053144326125550,479635942,1.501097748379E9,1.501097751486E9 +59,-815688429997384126,479635942,1.501097775494E9,1.501097778492E9 +59,-7108337468662161626,479635942,1.501097822131E9,1.50109782609E9 +59,8520246161409867533,479635942,1.501097857496E9,1.501097861897E9 +59,-376454326251101423,479635942,1.501097863795E9,1.50109786792E9 +59,6510323870441083165,479635942,1.501097871964E9,1.501097873943E9 +59,8257877450335423678,479635942,1.501097897045E9,1.501097901073E9 +59,5135009814020022804,479635942,1.501097931693E9,1.501097937636E9 +59,7583242018559711280,479635942,1.501097943461E9,1.501097945305E9 +59,3223441157440427288,479635942,1.501097978716E9,1.50109798309E9 +59,6462731995811607988,479635942,1.501097999342E9,1.501098001652E9 +59,-2080898615186177042,479635942,1.501098047881E9,1.501098050634E9 +59,1912011379066757021,479635942,1.501098067875E9,1.501098070764E9 +59,-8159078852503161638,479635942,1.501098104067E9,1.501098106484E9 +59,-1951913650752808896,479635942,1.501098122323E9,1.501098124719E9 +59,3110665973802534117,479635942,1.501098137752E9,1.50109814325E9 +59,-8245497611270561643,479635942,1.501098269391E9,1.501098270214E9 +59,-2488168393221053635,627460548,1.501095322184E9,1.501095386184E9 +59,-1236381197693815490,627460548,1.501095596184E9,1.501095597184E9 +59,1075521169233739938,627460548,1.501095608184E9,1.501095633184E9 +59,7257814405411876514,627460548,1.501095637184E9,1.501095713184E9 +59,-2580682828761725792,627460548,1.501095729184E9,1.501095777184E9 +59,-8605515118457787891,627460548,1.501095790184E9,1.501095804184E9 +59,8337734177692190825,627460548,1.501095820184E9,1.501096102184E9 +59,3962756607804556038,627460548,1.501096117184E9,1.501096214184E9 +59,-6746492169296944402,627460548,1.501096742184E9,1.501096744184E9 +59,1271352433827133997,627460548,1.501096816184E9,1.501096822184E9 +59,-7207200223368718107,627460548,1.501096826184E9,1.501096854184E9 +59,6228546177887084689,627460548,1.501096888184E9,1.501096914184E9 +59,3000546082855196111,627460548,1.501096920184E9,1.501097012184E9 +59,3897751311146723253,627460548,1.501097014184E9,1.501097183184E9 +59,-1867956002051251955,627460548,1.501097199184E9,1.501097316184E9 +59,5895677467021490133,627460548,1.501097321184E9,1.501097366184E9 +59,-2329144350355433791,627460548,1.501097368184E9,1.501097496184E9 +59,4471501886336186196,627460548,1.501097511184E9,1.501097561184E9 +59,-1958495126941261161,627460548,1.501097586184E9,1.501097592184E9 +59,-3053390146284596499,627460548,1.501097638184E9,1.501097657184E9 +26,-7951946553818325760,479635942,1.521825959717E9,1.521825960887E9 +26,3357184544023295870,479635942,1.521826054367E9,1.521826055658E9 +26,-4399631736425259600,479635942,1.521826128736E9,1.521826129997E9 +26,6022110397615505219,479635942,1.521826136657E9,1.521826138007E9 +26,8305759309897957289,479635942,1.521826158016E9,1.521826162007E9 +26,7706842616310080497,479635942,1.521826163417E9,1.5218261647680001E9 +26,-5162050327556312502,479635942,1.5218262007970002E9,1.5218262020570002E9 +26,-919501539067090173,479635942,1.5218262033470001E9,1.5218262058960001E9 +26,5486337863640098508,479635942,1.5218262071560001E9,1.521826208446E9 +26,5060304467944278326,479635942,1.521826227767E9,1.521826230437E9 +26,506129992933519210,479635942,1.521826246187E9,1.521826247566E9 +26,8817547663100190470,479635942,1.521826250147E9,1.521826252846E9 +26,-5872473440852740808,479635942,1.521826255486E9,1.521826263467E9 +26,1732642658931322388,479635942,1.521826272496E9,1.521826278887E9 +26,-9185125183389841228,479635942,1.5218263447350001E9,1.5218263461160002E9 +26,6999236563657057971,479635942,1.5218263913860002E9,1.521826394117E9 +26,3750575424071875728,479635942,1.5218264003560002E9,1.521826401676E9 +26,-5824662355972113106,479635942,1.521826486845E9,1.5218264881660001E9 +26,5466232292523319184,479635942,1.5218265031660001E9,1.521826506346E9 +26,-4476733568267304747,479635942,1.521826508625E9,1.521826511206E9 +26,2565221287677766301,479635942,1.5218266852350001E9,1.5218266873660002E9 +26,-5670014974571691424,479635942,1.521826704765E9,1.5218267060540001E9 +26,-2165418173409783981,479635942,1.5218267272640002E9,1.5218267295440001E9 +26,-8457613487091296301,479635942,1.5218270235140002E9,1.521827026815E9 +26,3106702097391505352,479635942,1.521827035844E9,1.521827037045E9 +26,7585905348253573254,479635942,1.521827203035E9,1.521827207713E9 +26,6776830795303022736,479635942,1.5218272152740002E9,1.521827217525E9 +26,4953735811770734320,479635942,1.521827513744E9,1.521827518244E9 +26,-8328271299637690925,479635942,1.5218275930340002E9,1.5218275937840002E9 +26,35614618283757807,479635942,1.521827731605E9,1.521827737095E9 +26,-2979046840457590769,479635942,1.5218280616330001E9,1.521828063194E9 +26,-2728367000091771848,479635942,1.521828074023E9,1.5218280747740002E9 +26,-7514454981204438683,479635942,1.521828079423E9,1.521828081734E9 +26,3626426901206878019,479635942,1.521828084973E9,1.5218280880340002E9 +26,-3221552733544669704,479635942,1.5218281942940001E9,1.5218281966330001E9 +26,5357573905117106417,479635942,1.521828264673E9,1.5218282683630002E9 +26,-7678168463197119856,479635942,1.5218284523830001E9,1.521828492163E9 +26,-5868569999655719067,479635942,1.521828673634E9,1.521828676334E9 +26,7783873759049040400,479635942,1.521829133505E9,1.5218291407050002E9 +26,-1164031311247914982,479635942,1.5218291497640002E9,1.5218291542640002E9 +26,7592441470054466012,479635942,1.521829221316E9,1.5218292349650002E9 +26,-5297745363352181993,479635942,1.5218292861460001E9,1.521829287885E9 +26,2723091489567818587,479635942,1.521829289565E9,1.521829296046E9 +26,4868516268449791634,479635942,1.521829360065E9,1.5218293609650002E9 +26,527393528765485641,479635942,1.521829362765E9,1.521829364565E9 +26,6339666651420998039,479635942,1.5218295007050002E9,1.521829501605E9 +26,-4425295826530714774,627460548,1.521826451374E9,1.521826456374E9 +26,-8430066309921190473,627460548,1.521826468374E9,1.521826473374E9 +26,-6669008984552126746,627460548,1.521826803374E9,1.521826807374E9 +26,106157166059108654,627460548,1.521826814374E9,1.521826855374E9 +26,2840894071843762229,627460548,1.521826866374E9,1.521826937374E9 +26,-5342290114834841782,627460548,1.521826952374E9,1.521826956374E9 +26,6657864152520354542,627460548,1.521828113374E9,1.521828125374E9 +26,3323477762541739233,627460548,1.521828195374E9,1.521828228374E9 +26,6623122784089319800,627460548,1.521828277374E9,1.521828279374E9 +26,-1777794211665605813,627460548,1.521828354374E9,1.521828370374E9 +26,-5616297213604956334,627460548,1.521828377374E9,1.521828383374E9 +26,2638863292813237142,627460548,1.521828387374E9,1.521828388374E9 +26,-4802923805251574890,627460548,1.521828406374E9,1.521828408374E9 +26,-3162815773302240229,627460548,1.521828423374E9,1.521828533374E9 +26,833812741686708854,627460548,1.521828786374E9,1.521828792374E9 +26,-7651870039932161040,627460548,1.521828811374E9,1.521828832374E9 +47,-7394957341913254038,479635942,1.4997981351660001E9,1.4997981360970001E9 +47,-8551242594078874640,479635942,1.4997981584450002E9,1.4997981593760002E9 +47,-6377163397148163621,479635942,1.4997981837970002E9,1.49979818548E9 +47,-6295132995662633695,479635942,1.499798267588E9,1.4997982684880002E9 +47,-5318597204528146860,479635942,1.499798270318E9,1.4997982730170002E9 +47,-6254466032864658640,479635942,1.499798273917E9,1.499798276647E9 +47,7677123060856263821,479635942,1.4997982977380002E9,1.499798298638E9 +47,4454235247555596930,479635942,1.49979830044E9,1.499798301338E9 +47,1614145214393745704,479635942,1.49979833968E9,1.49979834193E9 +47,5907756074770712058,479635942,1.4997983426790001E9,1.499798345079E9 +47,6381969761403398165,479635942,1.499798345829E9,1.499798347329E9 +47,-6299726893066242148,479635942,1.4997983649090002E9,1.499798367608E9 +47,4251153438197704520,479635942,1.499798369408E9,1.4997983730080001E9 +47,-1938336866210303686,479635942,1.499798381168E9,1.49979838297E9 +47,-7460474352123181197,479635942,1.499798401028E9,1.499798405588E9 +47,-9032752971001076911,479635942,1.4997984064880002E9,1.4997984119490001E9 +47,4491545254968270050,479635942,1.499798421818E9,1.499798426348E9 +47,5096849667214515801,479635942,1.499798432469E9,1.499798450512E9 +47,-7786563792363918080,479635942,1.499798953296E9,1.499798982118E9 +47,3577437384975642652,479635942,1.4997990938240001E9,1.499799104384E9 +47,8136309372218302977,479635942,1.4997991540530002E9,1.499799170549E9 +47,2698621795661466618,479635942,1.4997994034150002E9,1.499799431052E9 +47,-5291859461102665736,479635942,1.4997994932580001E9,1.499799495404E9 +47,-1367132018171691623,479635942,1.499799526259E9,1.4997995475440001E9 +47,8250323024989648373,479635942,1.4997995494420002E9,1.499799567427E9 +47,628723615451037838,479635942,1.499799575482E9,1.499799583844E9 +47,-206585888389049898,479635942,1.499799600647E9,1.4997996318300002E9 +47,-1048239980980874445,479635942,1.499799674568E9,1.4997997416660001E9 +47,1778960464130521539,479635942,1.4997997483760002E9,1.499799750026E9 +47,1050206711866613923,479635942,1.4997997592110002E9,1.499799780799E9 +47,8475581809093468859,479635942,1.499799818831E9,1.4997998897540002E9 +47,-5620283494443865738,479635942,1.499800003187E9,1.4998000081100001E9 +47,-1677214701488082796,479635942,1.499800053898E9,1.4998000564280002E9 +47,7214425382359845503,479635942,1.4998002808030002E9,1.4998002824520001E9 +47,-162083872625507495,479635942,1.499800321585E9,1.4998003232350001E9 +47,-7523120664886112291,479635942,1.499800415522E9,1.4998004180240002E9 +47,6552979296264424107,479635942,1.4998004296590002E9,1.4998004304810002E9 +47,8192841680952521862,479635942,1.499800476489E9,1.4998004831720002E9 +47,-7680457163052518179,479635942,1.499800483997E9,1.499800484822E9 +74,-7100940967587284080,479635942,1.5214874243070002E9,1.521487425207E9 +74,1277248096502282592,479635942,1.521487464147E9,1.5214874650770001E9 +74,830598226874001399,479635942,1.521487499907E9,1.521487500657E9 +74,-8374182940471393581,479635942,1.5214875163890002E9,1.521487518249E9 +74,-2751237974560907456,479635942,1.521487650095E9,1.5214876514750001E9 +74,-7683165260674316323,479635942,1.5214877409950001E9,1.5214877424050002E9 +74,-3695300300739008445,479635942,1.521487755305E9,1.521487756624E9 +74,3211629094050434423,479635942,1.5214878762340002E9,1.5214878776130002E9 +74,1888656962774903147,479635942,1.5214879817140002E9,1.521487982884E9 +74,-9015144092354152096,479635942,1.5214879932930002E9,1.521487994523E9 +74,-1323593585012316836,479635942,1.521488068084E9,1.521488069344E9 +74,1636015664682159993,479635942,1.5214881690930002E9,1.521488170203E9 +74,-4445037065289837715,479635942,1.521488262394E9,1.5214882635930002E9 +74,2635207561236243114,479635942,1.5214882730730002E9,1.5214882743330002E9 +74,-1635881411443778117,479635942,1.521488290503E9,1.5214882942240002E9 +74,3404172992977330892,479635942,1.521488297523E9,1.5214882987540002E9 +74,3423727598100793694,479635942,1.521488319513E9,1.5214883240140002E9 +74,-6479681697317270682,479635942,1.521488327433E9,1.5214883286030002E9 +74,5192896085133715711,479635942,1.521488350834E9,1.521488351914E9 +74,8425091437886945886,479635942,1.5214883851220002E9,1.5214883875530002E9 +74,-3891276535790995220,479635942,1.521488431923E9,1.521488435163E9 +74,681535430259442243,479635942,1.5214884490240002E9,1.5214884515730002E9 +74,-4593482852994321444,479635942,1.5214884538830001E9,1.5214884551430001E9 +74,1917257299477074749,479635942,1.521488481453E9,1.5214884826230001E9 +74,1778026896245754764,479635942,1.5214884999320002E9,1.5214885031720002E9 +74,-9150537040027592262,479635942,1.5214886090430002E9,1.5214886108430002E9 +47,2143952575760754579,627460548,1.499798422275E9,1.499798467275E9 +47,-1102366506098408593,627460548,1.499798477275E9,1.499798478275E9 +47,1070604568328400281,627460548,1.499798949275E9,1.499799041275E9 +47,-5519527282280551264,627460548,1.499799048275E9,1.499799128275E9 +47,156298530050084862,627460548,1.499799154275E9,1.499799182275E9 +47,-6817764927555924154,627460548,1.499799212275E9,1.499799220275E9 +47,-7623388730705804666,627460548,1.499799360275E9,1.499799480275E9 +47,-5707692991057582718,627460548,1.499799482275E9,1.499799910275E9 +62,233465819468974483,479635942,1.522087738648E9,1.5220877395180001E9 +62,-1665235544586458329,479635942,1.522087933588E9,1.522087934519E9 +62,5650126530783012121,479635942,1.522087941838E9,1.5220879427380002E9 +62,-3808777790712200263,479635942,1.5220881100900002E9,1.52208811201E9 +62,-6387801822708595721,479635942,1.5220881861090002E9,1.522088259371E9 +62,3567274854459386327,479635942,1.5220882674090002E9,1.52208850618E9 +62,-3217050604814709529,479635942,1.5220885375900002E9,1.5220885628200002E9 +62,-4286501893762232022,479635942,1.522088864921E9,1.5220888672310002E9 +62,-7104670778578012518,479635942,1.522088957351E9,1.5220889596620002E9 +62,7165201073217535671,479635942,1.522088960742E9,1.522088961822E9 +62,8400627279522988925,479635942,1.5220889809910002E9,1.522088982042E9 +62,-8511373289042049777,479635942,1.5220890079320002E9,1.5220890102110002E9 +62,3102605401485095728,479635942,1.5220890232020001E9,1.5220890254520001E9 +62,8081285124269112968,479635942,1.522089134381E9,1.522089135312E9 +62,-7434812294372272992,479635942,1.5220891373820002E9,1.522089142032E9 +62,8695563931371837955,479635942,1.522089159433E9,1.522089164683E9 +62,-4923828897717141335,479635942,1.5220892875630002E9,1.522089288673E9 +62,-4992727507577582518,627460548,1.52208814715E9,1.52208857515E9 +31,7921209197648174965,479635942,1.5204927300280004E9,1.5204927309280002E9 +31,2192460630149830418,479635942,1.5204928264170003E9,1.5204928274970002E9 +31,4718873921805288997,479635942,1.5204928786470003E9,1.5204928796970003E9 +31,-2122507024878303514,479635942,1.5204928807170002E9,1.5204928825780003E9 +31,212670736209963259,479635942,1.5204928844080002E9,1.5204928862970002E9 +31,6374557663447655357,479635942,1.5204929134470003E9,1.5204929143480003E9 +31,-9098709584164636914,479635942,1.5204929162380002E9,1.5204929189970002E9 +31,7641140155503107961,479635942,1.5204929997580001E9,1.5204930007180002E9 +31,4117373660847629830,479635942,1.5204930026360002E9,1.5204930036270003E9 +31,-514622113686124186,479635942,1.5204930206970003E9,1.5204930215680003E9 +31,383719709637353600,479635942,1.5204930234580002E9,1.5204930243580003E9 +31,-4158489170791971768,479635942,1.5204930261580002E9,1.5204930271480002E9 +31,864024801799181361,479635942,1.5204931281870003E9,1.5204931300180001E9 +31,8844950406650908608,479635942,1.5204931316970003E9,1.5204931334670002E9 +31,-2269205392455893346,479635942,1.5204931585170002E9,1.5204931595370002E9 +31,-2114760810447394921,479635942,1.5204931941270003E9,1.5204931989570003E9 +31,-4430806205437969470,479635942,1.5204933497960002E9,1.5204933507560003E9 +31,-8089879190856624777,479635942,1.5204933535160003E9,1.5204933554660003E9 +31,-3466292861467391247,479635942,1.5204933581960003E9,1.5204933590960002E9 +31,643253390856272901,479635942,1.5204934448960001E9,1.5204934468470001E9 +31,8389055379227883246,479635942,1.5204934578560002E9,1.5204936887360003E9 +31,3821752786606544586,479635942,1.5204936986370003E9,1.5204936995670002E9 +31,5502783586907073770,479635942,1.5204939322150002E9,1.5204939331760004E9 +31,92039323423905987,479635942,1.5204939370150003E9,1.5204939417260003E9 +31,4240750638010388468,479635942,1.5204946767280002E9,1.5204946776870003E9 +31,4931898673170617537,479635942,1.5204947732380002E9,1.5204947742570002E9 +31,6863282184143612984,479635942,1.5204947873080003E9,1.5204947892880003E9 +31,-1653625742806097825,479635942,1.5204948617980003E9,1.5204948626390002E9 +31,5483340186273732609,479635942,1.5204948782980003E9,1.5204948813880002E9 +31,-2937538317926627262,479635942,1.5204950760900002E9,1.5204950872790003E9 +31,-8217686576126044503,479635942,1.5204953445590003E9,1.5204954473700001E9 +31,9091122659934996368,479635942,1.5204954581700003E9,1.5204954955210001E9 +31,-8052039232102359049,479635942,1.5204956271610003E9,1.5204956290210001E9 +31,3202452113092810722,479635942,1.5204958540820003E9,1.5204958631420002E9 +31,6784409005471784859,479635942,1.5204958816230001E9,1.5204958846510003E9 +31,-8152664018392036595,479635942,1.5204959239520001E9,1.5204959249120002E9 +31,3929441459210376731,479635942,1.5204959267430003E9,1.5204959287220001E9 +31,7618703669974760907,479635942,1.5204960112230003E9,1.5204960122430003E9 +31,3121793074855035377,479635942,1.5204960266420002E9,1.5204960285630002E9 +31,-7889253677765381533,479635942,1.5204960314420002E9,1.5204960381020002E9 +31,5593788379240306940,479635942,1.5204960589230003E9,1.5204960616240003E9 +31,-4869409006441440319,479635942,1.5204960670230002E9,1.5204960696330001E9 +31,2584397914297307428,479635942,1.5204960792620003E9,1.5204960808510003E9 +31,6808007830433701211,479635942,1.5204961608890002E9,1.5204961618480003E9 +31,-6374212182751913759,627460548,1.5204934468980002E9,1.5204937308980002E9 +31,-4197511859593274763,627460548,1.5204937378980002E9,1.5204937448980002E9 +31,6209070167463257299,627460548,1.5204937608980002E9,1.5204937888980002E9 +31,-4875834047199230818,627460548,1.5204950858980002E9,1.5204950998980002E9 +31,-4320597367016870533,627460548,1.5204951638980002E9,1.5204952078980002E9 +31,-8017881740808565020,627460548,1.5204952088980002E9,1.5204952608980002E9 +31,3299433999620492931,627460548,1.5204952678980002E9,1.5204952738980002E9 +31,-276408725174261182,627460548,1.5204952828980002E9,1.5204955178980002E9 +31,4100004042559332621,627460548,1.5204955498980002E9,1.5204955688980002E9 +31,1953503504700232897,627460548,1.5204957348980002E9,1.5204958228980002E9 +56,7286031052324599941,479635942,1.502217249289E9,1.50221725079E9 +56,-7995058155203102065,479635942,1.502217262217E9,1.502217264017E9 +56,125212586940396863,479635942,1.502217841065E9,1.502217844065E9 +56,6141018012366946690,479635942,1.502217857926E9,1.502217859486E9 +56,-4108158772573806331,479635942,1.502217871395E9,1.5022178758660002E9 +56,-4875838995510048156,479635942,1.5022182645970001E9,1.502218272367E9 +56,-8776940733478037134,479635942,1.502218273175E9,1.5022183511160002E9 +56,4598698361605545852,479635942,1.502218397436E9,1.502218401247E9 +56,919305116641171376,479635942,1.502218431457E9,1.5022185647740002E9 +56,2209496077421409303,479635942,1.502218566604E9,1.502218612114E9 +56,256068826457431573,479635942,1.502218615504E9,1.502218706345E9 +56,-8979504765109427979,479635942,1.5022187180740001E9,1.5022187480140002E9 +56,-5062807074700727668,479635942,1.5022187723760002E9,1.502218784915E9 +56,-7516225137999424141,479635942,1.502219112993E9,1.502219133333E9 +56,3378231168590052764,479635942,1.502219144194E9,1.5022191449450002E9 +56,955956651639658129,479635942,1.502219169036E9,1.502219175333E9 +56,-8542388884598226679,479635942,1.502219186194E9,1.5022191879950001E9 +56,-2883769545959296089,479635942,1.502219201614E9,1.502219202513E9 +56,-6208194100392552112,479635942,1.502219293504E9,1.502219294434E9 +56,-2674432477455902243,479635942,1.5022192962640002E9,1.502219298994E9 +56,-6383873380722153309,479635942,1.502219299894E9,1.5022193007940001E9 +56,-273869646409472698,479635942,1.5022193170240002E9,1.502219318885E9 +56,5250843682173965751,479635942,1.502219321644E9,1.502219323444E9 +56,763449791328314297,479635942,1.5022193660870001E9,1.502219366917E9 +56,-2570583434181382529,627460548,1.502217835236E9,1.502217849236E9 +56,-4244945049449255719,627460548,1.502217851236E9,1.502218015236E9 +56,760471664002184084,627460548,1.502218051236E9,1.502218059236E9 +56,-6097291144490623940,627460548,1.502218068236E9,1.502218105236E9 +56,-2122505610086791721,627460548,1.502218244236E9,1.502218767236E9 +73,4698746056602067206,479635942,1.519628148179E9,1.51962814908E9 +73,-6528280314230347635,479635942,1.519628279257E9,1.5196282801569998E9 +73,-810164440262703953,479635942,1.51962832648E9,1.519628327228E9 +73,7974722580389124481,479635942,1.519628457366E9,1.519628458115E9 +73,2499672124474546301,479635942,1.519628558047E9,1.519628558885E9 +73,5762312710716643448,479635942,1.519628560385E9,1.519628561945E9 +73,-4800123670424522558,479635942,1.519628564195E9,1.5196285664459999E9 +73,-7103174223550522392,479635942,1.5196285695349998E9,1.519628571036E9 +73,6913005204637502732,479635942,1.519628577185E9,1.5196285787749999E9 +73,-3453847138190940414,479635942,1.5196285920049999E9,1.5196286653539999E9 +73,5664278828011553720,479635942,1.519628667964E9,1.5196286965839999E9 +73,759890121094263352,479635942,1.519629005796E9,1.519629006665E9 +73,2184777283105237031,479635942,1.519629009847E9,1.519629010597E9 +73,5406120560287454292,479635942,1.519629027184E9,1.5196290309359999E9 +73,5909391876979633603,479635942,1.519629036274E9,1.519629037836E9 +73,-872640667733504566,479635942,1.5196290461139998E9,1.5196290468639998E9 +73,2229309029957584289,627460548,1.519628578501E9,1.519628579501E9 +73,413457750693962833,627460548,1.519628580501E9,1.519628702501E9 +6,8141306054365381644,479635942,1.501486198958E9,1.501486199859E9 +6,9065018149763811076,479635942,1.501486329099E9,1.501486329999E9 +6,-1190819671160564535,479635942,1.5014863327E9,1.501486333599E9 +6,5675679548293145415,479635942,1.501486367741E9,1.501486368491E9 +6,-392481383116102237,479635942,1.501486402631E9,1.5014864033799999E9 +6,6466481676484687062,479635942,1.501486405632E9,1.50148640734E9 +6,7314023513402070129,479635942,1.5014864091399999E9,1.50148641007E9 +6,3172890637915615512,479635942,1.50148643281E9,1.50148643371E9 +6,2489143921017646798,479635942,1.501486534991E9,1.501486535891E9 +6,-5965547874463461661,479635942,1.501486656612E9,1.501486657513E9 +6,-5677556238851400321,479635942,1.501486689162E9,1.501486690062E9 +6,-5488803820677031148,479635942,1.501486699122E9,1.501486700023E9 +6,-2253785796264139997,479635942,1.501486708182E9,1.501486709052E9 +6,2429417179846311158,479635942,1.501486946234E9,1.5014869633339999E9 +6,-7394894551209760989,479635942,1.501486969393E9,1.501487004343E9 +6,-5422988830067458338,479635942,1.5014870127749999E9,1.501487180625E9 +6,7264317951308118050,479635942,1.501487185155E9,1.501487397527E9 +6,5252442164430851011,479635942,1.501487662389E9,1.501487664188E9 +6,3571910543164118603,479635942,1.501487768064E9,1.501487769595E9 +6,-2838242289859826192,479635942,1.501487774214E9,1.501487777184E9 +6,-355372327093400807,479635942,1.501487810305E9,1.501487811985E9 +6,-4547088266076729406,479635942,1.501487865444E9,1.501487866945E9 +6,-4229849076597703889,479635942,1.501488067912E9,1.5014880695019999E9 +6,3874307978585448372,479635942,1.501488072532E9,1.501488073942E9 +6,-3053811312933356931,479635942,1.501488082822E9,1.5014880845609999E9 +6,-3488278371421040274,479635942,1.501488313523E9,1.5014883144819999E9 +6,-666309752389973768,479635942,1.501488842991E9,1.501488843891E9 +6,-2112707463042219799,479635942,1.501488845692E9,1.5014888474919999E9 +6,6498580997500453772,479635942,1.501488857391E9,1.5014888609919999E9 +6,-2962833679997753002,479635942,1.501488885682E9,1.501488888082E9 +6,3053897756119763326,479635942,1.501488890632E9,1.501488892432E9 +6,357680524675104482,479635942,1.501488902632E9,1.501488905872E9 +6,-4633237063126712372,479635942,1.501488909684E9,1.501488910434E9 +6,6858043685481218482,479635942,1.5014889171829998E9,1.501488940824E9 +6,8661377745411371706,479635942,1.501488951503E9,1.501488974122E9 +6,692562988186632292,479635942,1.501488995842E9,1.501489009523E9 +6,8454345605335880828,479635942,1.501489016573E9,1.5014890209229999E9 +6,-1116070765837062941,479635942,1.501489024943E9,1.5014890333439999E9 +6,-9013787633869098893,479635942,1.501489075374E9,1.501489083533E9 +6,1575198716093482053,479635942,1.501489103417E9,1.5014891266959999E9 +6,4103956260681959250,479635942,1.5014891339259999E9,1.501489146136E9 +6,5320552298985772173,479635942,1.501489152136E9,1.50148916573E9 +6,3117657923915150890,479635942,1.501489198097E9,1.501489215257E9 +6,-2681847254649723544,479635942,1.501489227917E9,1.501489524438E9 +6,-7772234340436546031,479635942,1.50148970186E9,1.50148970369E9 +6,6984113525933631377,479635942,1.501489709119E9,1.5014899605809999E9 +6,4861397373548277002,479635942,1.501490031291E9,1.501490059822E9 +6,-8127007269034585581,479635942,1.501490077942E9,1.501490098822E9 +6,-6758332066512509935,479635942,1.501490139863E9,1.501490171149E9 +6,285230501814966796,479635942,1.5014901772389998E9,1.501490213899E9 +6,-166111266919970594,479635942,1.501490221128E9,1.5014903246599998E9 +6,-4100706704446654045,479635942,1.501490620699E9,1.501490621598E9 +6,-4605411364910003765,479635942,1.501490648628E9,1.50149064944E9 +6,-3866689808461687774,479635942,1.5014907025379999E9,1.501490703439E9 +6,-8903984103475669235,479635942,1.501490737879E9,1.501490738779E9 +6,3475379203004552842,479635942,1.50149077775E9,1.501490778501E9 +6,6771927619391553641,479635942,1.50149084699E9,1.501490848819E9 +6,3555111570529820649,479635942,1.501490857972E9,1.5014908598309999E9 +6,-1511442427265712942,479635942,1.501490867779E9,1.50149086961E9 +6,-7695862010491739525,479635942,1.501490948272E9,1.5014909501E9 +6,-1279058410837997392,479635942,1.5014909708899999E9,1.501490971792E9 +78,-9011361941223280730,479635942,1.5194087128470001E9,1.5194087135960002E9 +78,597828038336683425,479635942,1.5194087225660002E9,1.519408723466E9 +78,3564213200788206699,479635942,1.5194087315660002E9,1.519408732466E9 +78,-3251410450130275703,479635942,1.519408734296E9,1.519408736095E9 +78,-4827707693981813972,479635942,1.519408736996E9,1.519408743296E9 +78,6842396962829650299,479635942,1.519408780227E9,1.519408781124E9 +78,-1691601457848570275,479635942,1.5194088643140001E9,1.5194088670140002E9 +78,6742106736870529029,479635942,1.519408867914E9,1.519408868844E9 +78,667682855295149288,479635942,1.5194088805740001E9,1.5194088912540002E9 +78,1136228721632838783,479635942,1.5194090755730002E9,1.5194090773730001E9 +78,4702241742953304014,479635942,1.5194090810630002E9,1.519409081963E9 +78,6331248500942309874,479635942,1.5194090901520002E9,1.5194090919520001E9 +78,1687352770564940314,479635942,1.5194091073720002E9,1.519409110101E9 +78,-8175958674706710905,479635942,1.519409111002E9,1.519409112832E9 +78,35316266755353470,479635942,1.519409141934E9,1.519409142805E9 +78,5003653884098335467,479635942,1.5194092052640002E9,1.5194092070930002E9 +78,-4370452520119706380,479635942,1.519409240604E9,1.5194092415040002E9 +78,-5935907186492763735,479635942,1.519409313414E9,1.519409322473E9 +78,-1008782424325960707,479635942,1.519409337923E9,1.5194093388230002E9 +78,-9001833240160249965,479635942,1.5194093550830002E9,1.5194093559840002E9 +78,-103088198835953130,479635942,1.519409375513E9,1.5194093773130002E9 +78,-8729031188867460064,479635942,1.519409382325E9,1.519409383163E9 +6,9210188441730540451,627460548,1.501486947017E9,1.501487401017E9 +6,-343490424433488126,627460548,1.501487564017E9,1.501487587017E9 +6,7501229949551273215,627460548,1.501487664017E9,1.501487698017E9 +6,7023726278771727989,627460548,1.501488904017E9,1.501489043017E9 +6,1013831176693935851,627460548,1.501489046017E9,1.501489090017E9 +6,3459515849223643364,627460548,1.501489096017E9,1.501489537017E9 +6,-8281019678260577843,627460548,1.501489600017E9,1.501489630017E9 +6,3997983914223212962,627460548,1.501489680017E9,1.501490019017E9 +6,1724053087047461813,627460548,1.501490020017E9,1.501490064017E9 +6,-2746570370054937424,627460548,1.501490069017E9,1.501490350017E9 +6,5989748000031786107,627460548,1.501490367017E9,1.501490433017E9 +78,-3644405701563227601,627460548,1.519408888288E9,1.519408957288E9 +78,-4617602559320993340,627460548,1.519409049288E9,1.519409074288E9 +78,5088943403831986973,627460548,1.519409320288E9,1.519409325288E9 +19,6028848355768087671,479635942,1.519237302516E9,1.519237303324E9 +19,-5051221762931044720,479635942,1.519237314781E9,1.519237315591E9 +19,-5799211519740820623,479635942,1.519237333201E9,1.519237333952E9 +19,1146966086063006305,479635942,1.51923734022E9,1.51923734115E9 +19,6213942601054942341,479635942,1.51923737301E9,1.51923737391E9 +19,-2832685776793357555,479635942,1.51923737577E9,1.51923737667E9 +19,-351100257979666537,479635942,1.519237419721E9,1.51923742056E9 +19,-3285374475791400302,479635942,1.51923742281E9,1.51923742362E9 +19,-5518538856266410847,479635942,1.519237640337E9,1.5192376411529999E9 +19,8946134929553692674,479635942,1.519237647298E9,1.519237648798E9 +19,-4780427220932175267,479635942,1.519237680388E9,1.519237681889E9 +19,2566508364047548458,479635942,1.519237737717E9,1.519237738616E9 +19,295652401805827072,479635942,1.519237767508E9,1.519237768347E9 +19,-7096441995564781476,479635942,1.519237769846E9,1.519237771346E9 +19,-1165846100694611862,479635942,1.519237772846E9,1.519237774347E9 +19,-5162730470194357797,479635942,1.519237880075E9,1.519237882473E9 +19,1444482675075310923,479635942,1.519237901015E9,1.519237901765E9 +19,-3383998895735685034,479635942,1.519237909473E9,1.519237911753E9 +19,1402333567164908550,479635942,1.519237921833E9,1.519237925433E9 +19,6903295436849698658,479635942,1.519238084792E9,1.519238085692E9 +19,-5261750990350492111,479635942,1.519238269442E9,1.519238270192E9 +19,-2128280113966390728,479635942,1.51923836685E9,1.519238367661E9 +19,4627887667438372412,479635942,1.519238449018E9,1.519238449891E9 +19,-2173480526031768290,479635942,1.519238454509E9,1.519238457149E9 +19,5239742934098383464,479635942,1.519238477369E9,1.51923848052E9 +19,-8379047387576512746,479635942,1.519238519098E9,1.519238519938E9 +19,-8722982905601966219,479635942,1.519238547088E9,1.519238548018E9 +19,1318424142803243899,479635942,1.519238592418E9,1.519238594908E9 +19,-5438412136153030075,479635942,1.519238618369E9,1.519238622871E9 +19,2224270384256314041,479635942,1.519238633611E9,1.5192386391E9 +19,7723718644442095952,479635942,1.519238640001E9,1.519238961178E9 +19,2253797242199800066,479635942,1.519238992829E9,1.519238993728E9 +19,2049656652565732174,479635942,1.519239006448E9,1.519239201535E9 +19,2471398529509504796,479635942,1.519239468568E9,1.519239469468E9 +19,-8854767250839283109,479635942,1.519239495718E9,1.519239507447E9 +19,-6026046154867841437,479635942,1.519239536157E9,1.519239544917E9 +19,625738784370450155,479635942,1.519239549836E9,1.519239550587E9 +19,4203402333149566388,479635942,1.519239574888E9,1.519239582476E9 +19,-6499400431325262116,479635942,1.519239619556E9,1.519239629577E9 +19,-6132583495483120942,479635942,1.519239717594E9,1.519239719395E9 +19,-1490272467932344596,479635942,1.519239733825E9,1.519239765445E9 +19,-1656999970416748654,479635942,1.519239768864E9,1.519239792204E9 +19,-8520777268265145208,479635942,1.519239822084E9,1.519239890604E9 +19,1301007286925811684,479635942,1.519239913763E9,1.519239923603E9 +19,9199763139909967687,479635942,1.519239938393E9,1.519239982072E9 +19,6111448393157487946,479635942,1.519240000494E9,1.519240090043E9 +19,7290323716025233615,479635942,1.519240103662E9,1.519240221806E9 +19,5033702741749148784,479635942,1.519240227117E9,1.519240249256E9 +19,6308521703074585928,479635942,1.519240279526E9,1.519240329865E9 +19,5194732260996399601,479635942,1.519240464231E9,1.5192404777E9 +19,-7585094175769183574,479635942,1.519240618159E9,1.519240619062E9 +19,-8066897750035161654,479635942,1.519240648939E9,1.519240651939E9 +19,-4674137383446323737,479635942,1.519240816998E9,1.519240817898E9 +19,7875866929159281894,479635942,1.51924089701E9,1.51924089776E9 +48,4790704554089194909,479635942,1.502305287918E9,1.502305288729E9 +48,6212635237664191872,479635942,1.502305398986E9,1.502305400789E9 +48,8399905263604661135,479635942,1.5023054033890002E9,1.502305405069E9 +48,-3112090855975957995,479635942,1.502305406838E9,1.502305408519E9 +48,-2318641406649556887,479635942,1.502305429519E9,1.5023054318890002E9 +48,-9190940846721587034,479635942,1.502305471459E9,1.502305472359E9 +48,-3930128046966389082,479635942,1.502305475119E9,1.502305476019E9 +48,-3941886940628148436,479635942,1.5023055156790001E9,1.50230551748E9 +48,-4107281696769170658,479635942,1.502305534638E9,1.502305536286E9 +48,-6043645318129225075,479635942,1.502306144763E9,1.502306147265E9 +48,3244416218092439856,479635942,1.502306157165E9,1.502306163875E9 +48,6735411992212048803,479635942,1.502306180458E9,1.502306239474E9 +48,559154123080819694,479635942,1.5023062420310001E9,1.502306288754E9 +48,-8066955475557413138,479635942,1.50230653504E9,1.502306576786E9 +48,-5885406572919342504,479635942,1.5023065850900002E9,1.502306627756E9 +48,-162065876543376615,479635942,1.5023066372410002E9,1.502307029142E9 +48,8933180889007863511,479635942,1.502307325484E9,1.502307327134E9 +48,-1626320486275157895,479635942,1.502307363546E9,1.502307365194E9 +48,7208890045493190891,479635942,1.502307366872E9,1.50230736855E9 +48,-1797910056301520170,479635942,1.5023073868660002E9,1.50230739352E9 +48,4483959113167145503,479635942,1.502307401879E9,1.502307404382E9 +48,-2505442633121216769,479635942,1.502307471259E9,1.502307472964E9 +48,-5102107702007524451,479635942,1.502307484598E9,1.502307485421E9 +48,6450116291038204849,479635942,1.5023074995010002E9,1.502307500326E9 +48,7131347818050049092,479635942,1.5023075842020001E9,1.502307585027E9 +48,-9000065078716718490,479635942,1.502307587558E9,1.502307588382E9 +48,3271519467239371525,479635942,1.5023076101620002E9,1.502307610987E9 +48,2524730172130113058,479635942,1.50230761349E9,1.50230761514E9 +48,-7526758433824278794,479635942,1.502307620997E9,1.5023076218500001E9 +48,8487159644906055738,479635942,1.502307658453E9,1.502307659277E9 +79,4313940813830172671,479635942,1.5245133684290001E9,1.524513369269E9 +79,-1574674127851301219,479635942,1.5245133851390002E9,1.524513386069E9 +79,3885380142608347575,479635942,1.524513413118E9,1.524513415068E9 +79,6082956534531698008,479635942,1.524513420767E9,1.524513422657E9 +79,-529538213786321798,479635942,1.524513554598E9,1.5245135557970002E9 +79,-5233143905061212353,479635942,1.524513690227E9,1.524513691397E9 +79,-6749936408276765452,479635942,1.524513698506E9,1.524513699588E9 +79,4195997416838376465,479635942,1.524513702948E9,1.524513710957E9 +79,1395027177858466145,479635942,1.524513906708E9,1.524513907877E9 +79,4329072157274374587,479635942,1.524513961278E9,1.524513963708E9 +79,9104944897137045253,479635942,1.5245139715970001E9,1.524513974958E9 +79,-7937042204976751146,479635942,1.524514008017E9,1.524514010358E9 +79,-5359913606374734079,479635942,1.524514011767E9,1.524514025928E9 +79,1470764958350755913,479635942,1.5245140493270001E9,1.5245140507680001E9 +79,-8816464584560565024,479635942,1.524514061027E9,1.524514062437E9 +79,7031661784234920102,479635942,1.524514065046E9,1.524514066457E9 +79,7630475052421829992,479635942,1.5245141082480001E9,1.524514111458E9 +79,4459805701445750788,479635942,1.524514146947E9,1.5245141482970002E9 +79,1018930148492736803,479635942,1.524514150937E9,1.524514153157E9 +79,-3012724119970807671,479635942,1.5245141591890001E9,1.524514161618E9 +79,-6091802513688418518,479635942,1.524514185857E9,1.524514188198E9 +79,391085927997469060,479635942,1.524514201398E9,1.524514202537E9 +79,4026496079609271954,479635942,1.524514270997E9,1.524514271987E9 +19,-4547642813019348592,627460548,1.519238634431E9,1.519238968431E9 +19,5325245226839771994,627460548,1.519238983431E9,1.519239214431E9 +19,973218997822966150,627460548,1.519239620431E9,1.519239633431E9 +19,628300341692610971,627460548,1.519239667431E9,1.519239802431E9 +19,3014920702555012859,627460548,1.519239811431E9,1.519239896431E9 +19,-2621136301447011161,627460548,1.519239899431E9,1.519240352431E9 +19,7975499753437858593,627460548,1.519240363431E9,1.519240365431E9 +19,-3189134808378560287,627460548,1.519240424431E9,1.519240452431E9 +19,8600203470179719851,627460548,1.519240472431E9,1.519240602431E9 +19,3770848338282036760,627460548,1.519240676431E9,1.519240682431E9 +48,-3834210982116192238,627460548,1.502306145236E9,1.502306308236E9 +48,-6350312161582800364,627460548,1.502306351236E9,1.502306387236E9 +48,-8840098135087323120,627460548,1.502306388236E9,1.502306390236E9 +48,3427549274498046150,627460548,1.502306529236E9,1.502307069236E9 +28,1040427654212385429,1201859611,1.521789223098E9,1.521792477381E9 +29,6556782270497265025,1201859611,1.519801439524E9,1.519804840842E9 +33,-6056317502954263346,1201859611,1.501924860614E9,1.501929162822E9 +30,-4509912318961261997,1201859611,1.521702888744E9,1.5217061028439999E9 +59,4526313175717176312,1201859611,1.501094993184E9,1.501098434005E9 +69,5297223516241122749,1201859611,1.499677025521E9,1.4996782686339998E9 +76,1604686477683752455,1201859611,1.519679166069E9,1.519680393604E9 +11,-5926799641916171529,1201859611,1.500055626984E9,1.500061931105E9 +49,-8089365359392949673,1201859611,1.518804886421E9,1.518807260224E9 +65,7663622432945194351,1201859611,1.499761469866E9,1.499763686776E9 +9,7816983797231647252,1201859611,1.500880944319E9,1.500885608209E9 +32,-5227152884695581212,1201859611,1.519282265154E9,1.519285345753E9 +14,2702796889550818087,1201859611,1.501611902218E9,1.50161679806E9 +17,1920899555557485333,1201859611,1.499238990444E9,1.499242935794E9 +63,-1533397159073443657,1201859611,1.521534483703E9,1.521536109483E9 +35,-5091309530965523951,1201859611,1.519975368235E9,1.519978307823E9 +3,3286614479096879038,1201859611,1.5012633016019998E9,1.5012697935319998E9 +78,-6903129361326617772,1201859611,1.519408682288E9,1.519409544651E9 +45,4031524100617037594,1201859611,1.502352322421E9,1.502355083889E9 +81,-3039017337926577879,1201859611,1.500020387605E9,1.500021118887E9 +4,3413059497672580978,1201859611,1.521442341766E9,1.5214483285210001E9 +19,107421361993047462,1201859611,1.519237249431E9,1.519241038728E9 +23,1667169207552354980,1201859611,1.502091434075E9,1.502096130444E9 +61,-6074962741691029584,1201859611,1.502391863903E9,1.5023941796430001E9 +6,5601914189163822671,1201859611,1.501486121017E9,1.501491202042E9 +80,-8519964041684473957,1201859611,1.519154607775E9,1.51915581276E9 +24,7437298780708400991,1201859611,1.502263499684E9,1.502266791831E9 +46,-6047084444995931845,1201859611,1.518867417239E9,1.518869908982E9 +38,8006099507947146869,1201859611,1.522048625131E9,1.522051483289E9 +13,-1944483825207215539,1201859611,1.521659356202E9,1.521664064724E9 +20,3772502504273691038,1201859611,1.49984414566E9,1.499847846184E9 +39,1077981989772730875,1201859611,1.501872789635E9,1.501875581284E9 +58,1432421368744511818,1201859611,1.519404898117E9,1.51940681295E9 +70,382192189422054743,1201859611,1.501576972043E9,1.50157821128E9 +2,7921822167645060769,1201859611,1.524466721245E9,1.5244732439699998E9 +12,621520344043114077,1201859611,1.518937687489E9,1.5189428261990001E9 +42,-4744849287861989057,1201859611,1.519370038173E9,1.519372747634E9 +48,2089968467991060156,1201859611,1.502305247236E9,1.5023078405010002E9 +26,8588031391057209555,1201859611,1.521825843374E9,1.5218295814350002E9 +67,-2034556668057200826,1201859611,1.501189878386E9,1.501191326419E9 +73,4038311380879008479,1201859611,1.519628083501E9,1.5196290640549998E9 +22,1702604805711203895,1201859611,1.499780494611E9,1.499784016244E9 +36,-4482700421175446431,1201859611,1.5198367056390002E9,1.5198396267280002E9 +43,-5059295208201598568,1201859611,1.518591995006E9,1.5185946256460001E9 +66,6831185462073925864,1201859611,1.519719456367E9,1.5197209466030002E9 +40,8695525273439978119,1201859611,1.499886014457E9,1.4998891463E9 +75,5110935768283636594,1201859611,1.499974132752E9,1.4999754008790002E9 +27,-3534261932286880816,1201859611,1.4994121920089998E9,1.4994154300719998E9 +55,-7312033798960504347,1201859611,1.502177604786E9,1.502180609284E9 +79,2185843916018887178,1201859611,1.524513306088E9,1.524514433208E9 +50,4815143990666075450,1201859611,1.521744131314E9,1.5217470064229999E9 +7,-7434725489921472031,1201859611,1.499367268538E9,1.4993722404810002E9 +47,3287404548624903503,1201859611,1.499798046275E9,1.499800963408E9 +71,5850834331784891980,1201859611,1.5012309768E9,1.501232161654E9 +51,-8939688255858788198,1201859611,1.502132725389E9,1.502136639393E9 +56,-1655462257548164667,1201859611,1.502217242236E9,1.5022195563930001E9 +53,-8118175245509467954,1201859611,1.518629438223E9,1.518632591188E9 +74,-2523754915779770005,1201859611,1.521487415459E9,1.521488712033E9 +77,8292766365109962602,1201859611,1.501530768092E9,1.501532001474E9 +8,-6047692687641618500,1201859611,1.519629355513E9,1.519634174599E9 +34,-5864392879735263686,1201859611,1.520447895177E9,1.520451442709E9 +25,-5797038905919203719,1201859611,1.50165899742E9,1.501662280055E9 +31,27763026888340976,1201859611,1.5204926438980002E9,1.5204962007880003E9 +62,2045378005814092654,1201859611,1.52208759315E9,1.5220894282940001E9 +15,8620646447947530699,1201859611,1.50105479387E9,1.5010588258569999E9 +54,3329842588530743954,1201859611,1.501789556646E9,1.5017919091299999E9 +5,3570466563200151918,1201859611,1.499929241381E9,1.499934640063E9 +1,-928762887014768240,1201859611,1.5223419101579998E9,1.5223499941319997E9 +10,799052670452099784,1201859611,1.519924977475E9,1.519929815329E9 +37,-3015666225396535697,1201859611,1.520406325296E9,1.520409425879E9 +68,-4052763906460934747,1201859611,1.519201828118E9,1.519203136445E9 +57,8939056937015267775,1201859611,1.5192422426949997E9,1.5192442915019996E9 +64,1978039981849347272,1201859611,1.499330278298E9,1.4993318242410002E9 +16,-5204063793680029428,1201859611,1.519755823446E9,1.519760198065E9 +18,7002524279394511806,1201859611,1.5203611051940002E9,1.5203654402490003E9 +60,1782504894750799124,1201859611,1.51940682201E9,1.519408675632E9 +72,-4361505612007328068,1201859611,1.501145006228E9,1.5011461628769999E9 +44,819277571598892579,1201859611,1.499282239418E9,1.499285561123E9 +21,2586283319979737404,1201859611,1.518876947209E9,1.5188804750140002E9 +41,8437372657831499562,1201859611,1.519887529198E9,1.5198902487549999E9 +52,1261300119102051947,1201859611,1.499455466949E9,1.4994579101969998E9 diff --git a/tests/e2e/expected/pushdown/histogram_fact.csv b/tests/e2e/expected/pushdown/histogram_fact.csv new file mode 100644 index 00000000..7b2aa217 --- /dev/null +++ b/tests/e2e/expected/pushdown/histogram_fact.csv @@ -0,0 +1,811 @@ +container_id,visual_id,event_id,bin_id,hist_value,lower_bound,upper_bound,bin_name +58,1650601208,479635942,0,0.23099994659423828,0.0,20.0,0.0-20.0 +58,1650601208,479635942,1,17.36300015449524,20.0,40.0,20.0-40.0 +58,1650601208,479635942,2,7.54699969291687,40.0,60.0,40.0-60.0 +58,1650601208,479635942,3,0.31100010871887207,60.0,80.0,60.0-80.0 +58,1650601208,479635942,4,2.447999954223633,80.0,100.0,80.0-100.0 +58,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +58,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +58,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +58,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +58,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +21,1650601208,479635942,0,7.312999486923218,0.0,20.0,0.0-20.0 +21,1650601208,479635942,1,39.688000202178955,20.0,40.0,20.0-40.0 +21,1650601208,479635942,2,198.11000037193298,40.0,60.0,40.0-60.0 +21,1650601208,479635942,3,70.43300008773804,60.0,80.0,60.0-80.0 +21,1650601208,479635942,4,8.516999959945679,80.0,100.0,80.0-100.0 +21,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +21,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +21,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +21,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +21,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +71,1650601208,479635942,0,6.758000135421753,0.0,20.0,0.0-20.0 +71,1650601208,479635942,1,19.253000259399414,20.0,40.0,20.0-40.0 +71,1650601208,479635942,2,9.220000267028809,40.0,60.0,40.0-60.0 +71,1650601208,479635942,3,11.587000131607056,60.0,80.0,60.0-80.0 +71,1650601208,479635942,4,3.30299973487854,80.0,100.0,80.0-100.0 +71,1650601208,479635942,5,0.7760000228881836,100.0,120.0,100.0-120.0 +71,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +71,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +71,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +71,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +2,1650601208,479635942,0,5.552999973297119,0.0,20.0,0.0-20.0 +2,1650601208,479635942,1,39.76800012588501,20.0,40.0,20.0-40.0 +2,1650601208,479635942,2,15.734000205993652,40.0,60.0,40.0-60.0 +2,1650601208,479635942,3,18.09100031852722,60.0,80.0,60.0-80.0 +2,1650601208,479635942,4,53.58200001716614,80.0,100.0,80.0-100.0 +2,1650601208,479635942,5,102.90800023078918,100.0,120.0,100.0-120.0 +2,1650601208,479635942,6,850.6170001029968,120.0,140.0,120.0-140.0 +2,1650601208,479635942,7,143.0,140.0,160.0,140.0-160.0 +2,1650601208,479635942,8,34.0,160.0,180.0,160.0-180.0 +2,1650601208,479635942,9,15.0,180.0,200.0,180.0-200.0 +75,1650601208,479635942,0,11.133000135421753,0.0,20.0,0.0-20.0 +75,1650601208,479635942,1,15.557999849319458,20.0,40.0,20.0-40.0 +75,1650601208,479635942,2,16.977999925613403,40.0,60.0,40.0-60.0 +75,1650601208,479635942,3,0.9600000381469727,60.0,80.0,60.0-80.0 +75,1650601208,479635942,4,0.0,80.0,100.0,80.0-100.0 +75,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +75,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +75,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +75,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +75,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +13,1650601208,479635942,0,3.309000253677368,0.0,20.0,0.0-20.0 +13,1650601208,479635942,1,30.49999976158142,20.0,40.0,20.0-40.0 +13,1650601208,479635942,2,15.688999891281128,40.0,60.0,40.0-60.0 +13,1650601208,479635942,3,10.263000011444092,60.0,80.0,60.0-80.0 +13,1650601208,479635942,4,0.26399993896484375,80.0,100.0,80.0-100.0 +13,1650601208,479635942,5,68.17199993133545,100.0,120.0,100.0-120.0 +13,1650601208,479635942,6,1340.5370004177094,120.0,140.0,120.0-140.0 +13,1650601208,479635942,7,195.0,140.0,160.0,140.0-160.0 +13,1650601208,479635942,8,88.0,160.0,180.0,160.0-180.0 +13,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +65,1650601208,479635942,0,5.704999685287476,0.0,20.0,0.0-20.0 +65,1650601208,479635942,1,20.86199951171875,20.0,40.0,20.0-40.0 +65,1650601208,479635942,2,16.904999494552612,40.0,60.0,40.0-60.0 +65,1650601208,479635942,3,5.159999847412109,60.0,80.0,60.0-80.0 +65,1650601208,479635942,4,5.632999897003174,80.0,100.0,80.0-100.0 +65,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +65,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +65,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +65,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +65,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +5,1650601208,479635942,0,0.0,0.0,20.0,0.0-20.0 +5,1650601208,479635942,1,58.30999946594238,20.0,40.0,20.0-40.0 +5,1650601208,479635942,2,17.987999200820923,40.0,60.0,40.0-60.0 +5,1650601208,479635942,3,12.355000257492065,60.0,80.0,60.0-80.0 +5,1650601208,479635942,4,12.01200008392334,80.0,100.0,80.0-100.0 +5,1650601208,479635942,5,68.18899941444397,100.0,120.0,100.0-120.0 +5,1650601208,479635942,6,836.606999874115,120.0,140.0,120.0-140.0 +5,1650601208,479635942,7,138.0,140.0,160.0,140.0-160.0 +5,1650601208,479635942,8,115.0,160.0,180.0,160.0-180.0 +5,1650601208,479635942,9,85.0,180.0,200.0,180.0-200.0 +45,1650601208,479635942,0,0.75,0.0,20.0,0.0-20.0 +45,1650601208,479635942,1,48.87399911880493,20.0,40.0,20.0-40.0 +45,1650601208,479635942,2,48.92199969291687,40.0,60.0,40.0-60.0 +45,1650601208,479635942,3,1.7999999523162842,60.0,80.0,60.0-80.0 +45,1650601208,479635942,4,0.0,80.0,100.0,80.0-100.0 +45,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +45,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +45,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +45,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +45,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +7,1650601208,479635942,0,1.5900001525878906,0.0,20.0,0.0-20.0 +7,1650601208,479635942,1,4.076000213623047,20.0,40.0,20.0-40.0 +7,1650601208,479635942,2,17.454000234603882,40.0,60.0,40.0-60.0 +7,1650601208,479635942,3,3.0820000171661377,60.0,80.0,60.0-80.0 +7,1650601208,479635942,4,4.050999879837036,80.0,100.0,80.0-100.0 +7,1650601208,479635942,5,72.08199954032898,100.0,120.0,100.0-120.0 +7,1650601208,479635942,6,1025.4879999160767,120.0,140.0,120.0-140.0 +7,1650601208,479635942,7,256.0,140.0,160.0,140.0-160.0 +7,1650601208,479635942,8,23.0,160.0,180.0,160.0-180.0 +7,1650601208,479635942,9,7.0,180.0,200.0,180.0-200.0 +41,1650601208,479635942,0,6.815000057220459,0.0,20.0,0.0-20.0 +41,1650601208,479635942,1,22.197999954223633,20.0,40.0,20.0-40.0 +41,1650601208,479635942,2,23.501999616622925,40.0,60.0,40.0-60.0 +41,1650601208,479635942,3,7.4629998207092285,60.0,80.0,60.0-80.0 +41,1650601208,479635942,4,3.1670000553131104,80.0,100.0,80.0-100.0 +41,1650601208,479635942,5,17.20400047302246,100.0,120.0,100.0-120.0 +41,1650601208,479635942,6,254.64599990844727,120.0,140.0,120.0-140.0 +41,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +41,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +41,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +72,1650601208,479635942,0,3.739000082015991,0.0,20.0,0.0-20.0 +72,1650601208,479635942,1,15.936999559402466,20.0,40.0,20.0-40.0 +72,1650601208,479635942,2,29.878000020980835,40.0,60.0,40.0-60.0 +72,1650601208,479635942,3,7.961999893188477,60.0,80.0,60.0-80.0 +72,1650601208,479635942,4,3.7730000019073486,80.0,100.0,80.0-100.0 +72,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +72,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +72,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +72,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +72,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +63,1650601208,479635942,0,2.9769999980926514,0.0,20.0,0.0-20.0 +63,1650601208,479635942,1,5.459000110626221,20.0,40.0,20.0-40.0 +63,1650601208,479635942,2,17.04200005531311,40.0,60.0,40.0-60.0 +63,1650601208,479635942,3,12.180999994277954,60.0,80.0,60.0-80.0 +63,1650601208,479635942,4,0.8999998569488525,80.0,100.0,80.0-100.0 +63,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +63,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +63,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +63,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +63,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +64,1650601208,479635942,0,2.2839996814727783,0.0,20.0,0.0-20.0 +64,1650601208,479635942,1,8.54800009727478,20.0,40.0,20.0-40.0 +64,1650601208,479635942,2,10.621000289916992,40.0,60.0,40.0-60.0 +64,1650601208,479635942,3,5.288000106811523,60.0,80.0,60.0-80.0 +64,1650601208,479635942,4,4.488000154495239,80.0,100.0,80.0-100.0 +64,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +64,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +64,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +64,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +64,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +66,1650601208,479635942,0,4.3500001430511475,0.0,20.0,0.0-20.0 +66,1650601208,479635942,1,12.407999753952026,20.0,40.0,20.0-40.0 +66,1650601208,479635942,2,10.442999839782715,40.0,60.0,40.0-60.0 +66,1650601208,479635942,3,6.661999940872192,60.0,80.0,60.0-80.0 +66,1650601208,479635942,4,4.061000108718872,80.0,100.0,80.0-100.0 +66,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +66,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +66,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +66,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +66,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +1,1650601208,479635942,0,17.065999746322632,0.0,20.0,0.0-20.0 +1,1650601208,479635942,1,27.56600069999695,20.0,40.0,20.0-40.0 +1,1650601208,479635942,2,35.28600001335144,40.0,60.0,40.0-60.0 +1,1650601208,479635942,3,20.41099977493286,60.0,80.0,60.0-80.0 +1,1650601208,479635942,4,33.45799970626831,80.0,100.0,80.0-100.0 +1,1650601208,479635942,5,49.44500017166138,100.0,120.0,100.0-120.0 +1,1650601208,479635942,6,732.9229998588562,120.0,140.0,120.0-140.0 +1,1650601208,479635942,7,88.0,140.0,160.0,140.0-160.0 +1,1650601208,479635942,8,64.0,160.0,180.0,160.0-180.0 +1,1650601208,479635942,9,50.0,180.0,200.0,180.0-200.0 +15,1650601208,479635942,0,15.610000133514404,0.0,20.0,0.0-20.0 +15,1650601208,479635942,1,38.424999475479126,20.0,40.0,20.0-40.0 +15,1650601208,479635942,2,30.39199995994568,40.0,60.0,40.0-60.0 +15,1650601208,479635942,3,12.213000059127808,60.0,80.0,60.0-80.0 +15,1650601208,479635942,4,9.17799997329712,80.0,100.0,80.0-100.0 +15,1650601208,479635942,5,14.23799991607666,100.0,120.0,100.0-120.0 +15,1650601208,479635942,6,149.02300000190735,120.0,140.0,120.0-140.0 +15,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +15,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +15,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +77,1650601208,479635942,0,3.374000072479248,0.0,20.0,0.0-20.0 +77,1650601208,479635942,1,10.215000629425049,20.0,40.0,20.0-40.0 +77,1650601208,479635942,2,10.065999984741211,40.0,60.0,40.0-60.0 +77,1650601208,479635942,3,5.822000026702881,60.0,80.0,60.0-80.0 +77,1650601208,479635942,4,6.808000087738037,80.0,100.0,80.0-100.0 +77,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +77,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +77,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +77,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +77,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +3,1650601208,479635942,0,12.914000034332275,0.0,20.0,0.0-20.0 +3,1650601208,479635942,1,23.630000591278076,20.0,40.0,20.0-40.0 +3,1650601208,479635942,2,25.780999660491943,40.0,60.0,40.0-60.0 +3,1650601208,479635942,3,9.639999866485596,60.0,80.0,60.0-80.0 +3,1650601208,479635942,4,10.754999876022339,80.0,100.0,80.0-100.0 +3,1650601208,479635942,5,47.10400056838989,100.0,120.0,100.0-120.0 +3,1650601208,479635942,6,770.1849999427795,120.0,140.0,120.0-140.0 +3,1650601208,479635942,7,133.0,140.0,160.0,140.0-160.0 +3,1650601208,479635942,8,64.0,160.0,180.0,160.0-180.0 +3,1650601208,479635942,9,9.0,180.0,200.0,180.0-200.0 +37,1650601208,479635942,0,8.831999778747559,0.0,20.0,0.0-20.0 +37,1650601208,479635942,1,31.012999773025513,20.0,40.0,20.0-40.0 +37,1650601208,479635942,2,26.063000202178955,40.0,60.0,40.0-60.0 +37,1650601208,479635942,3,12.044999837875366,60.0,80.0,60.0-80.0 +37,1650601208,479635942,4,6.138999938964844,80.0,100.0,80.0-100.0 +37,1650601208,479635942,5,17.294999837875366,100.0,120.0,100.0-120.0 +37,1650601208,479635942,6,476.46999979019165,120.0,140.0,120.0-140.0 +37,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +37,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +37,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +27,1650601208,479635942,0,6.152000188827515,0.0,20.0,0.0-20.0 +27,1650601208,479635942,1,5.763999938964844,20.0,40.0,20.0-40.0 +27,1650601208,479635942,2,14.504999876022339,40.0,60.0,40.0-60.0 +27,1650601208,479635942,3,4.658000230789185,60.0,80.0,60.0-80.0 +27,1650601208,479635942,4,2.986999988555908,80.0,100.0,80.0-100.0 +27,1650601208,479635942,5,28.151000261306763,100.0,120.0,100.0-120.0 +27,1650601208,479635942,6,291.1559998989105,120.0,140.0,120.0-140.0 +27,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +27,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +27,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +57,1650601208,479635942,0,0.748999834060669,0.0,20.0,0.0-20.0 +57,1650601208,479635942,1,7.174000024795532,20.0,40.0,20.0-40.0 +57,1650601208,479635942,2,9.626000165939331,40.0,60.0,40.0-60.0 +57,1650601208,479635942,3,8.250999927520752,60.0,80.0,60.0-80.0 +57,1650601208,479635942,4,16.186000108718872,80.0,100.0,80.0-100.0 +57,1650601208,479635942,5,20.353999614715576,100.0,120.0,100.0-120.0 +57,1650601208,479635942,6,494.6379997730255,120.0,140.0,120.0-140.0 +57,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +57,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +57,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +24,1650601208,479635942,0,7.280000448226929,0.0,20.0,0.0-20.0 +24,1650601208,479635942,1,27.253000020980835,20.0,40.0,20.0-40.0 +24,1650601208,479635942,2,30.388999938964844,40.0,60.0,40.0-60.0 +24,1650601208,479635942,3,6.756000280380249,60.0,80.0,60.0-80.0 +24,1650601208,479635942,4,5.3409998416900635,80.0,100.0,80.0-100.0 +24,1650601208,479635942,5,9.391999959945679,100.0,120.0,100.0-120.0 +24,1650601208,479635942,6,296.80799984931946,120.0,140.0,120.0-140.0 +24,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +24,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +24,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +52,1650601208,479635942,0,1.6480000019073486,0.0,20.0,0.0-20.0 +52,1650601208,479635942,1,13.079999923706055,20.0,40.0,20.0-40.0 +52,1650601208,479635942,2,17.981000185012817,40.0,60.0,40.0-60.0 +52,1650601208,479635942,3,5.105000019073486,60.0,80.0,60.0-80.0 +52,1650601208,479635942,4,5.0,80.0,100.0,80.0-100.0 +52,1650601208,479635942,5,14.773999691009521,100.0,120.0,100.0-120.0 +52,1650601208,479635942,6,269.58200001716614,120.0,140.0,120.0-140.0 +52,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +52,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +52,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +28,1650601208,479635942,0,10.282000303268433,0.0,20.0,0.0-20.0 +28,1650601208,479635942,1,35.23700022697449,20.0,40.0,20.0-40.0 +28,1650601208,479635942,2,31.35200023651123,40.0,60.0,40.0-60.0 +28,1650601208,479635942,3,6.669000148773193,60.0,80.0,60.0-80.0 +28,1650601208,479635942,4,5.691999912261963,80.0,100.0,80.0-100.0 +28,1650601208,479635942,5,24.655999660491943,100.0,120.0,100.0-120.0 +28,1650601208,479635942,6,262.2939999103546,120.0,140.0,120.0-140.0 +28,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +28,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +28,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +54,1650601208,479635942,0,3.561000108718872,0.0,20.0,0.0-20.0 +54,1650601208,479635942,1,9.060999870300293,20.0,40.0,20.0-40.0 +54,1650601208,479635942,2,9.157999992370605,40.0,60.0,40.0-60.0 +54,1650601208,479635942,3,0.9579999446868896,60.0,80.0,60.0-80.0 +54,1650601208,479635942,4,0.0,80.0,100.0,80.0-100.0 +54,1650601208,479635942,5,24.14199995994568,100.0,120.0,100.0-120.0 +54,1650601208,479635942,6,491.4690001010895,120.0,140.0,120.0-140.0 +54,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +54,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +54,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +39,1650601208,479635942,0,8.365000247955322,0.0,20.0,0.0-20.0 +39,1650601208,479635942,1,17.322999954223633,20.0,40.0,20.0-40.0 +39,1650601208,479635942,2,16.073999881744385,40.0,60.0,40.0-60.0 +39,1650601208,479635942,3,8.408999681472778,60.0,80.0,60.0-80.0 +39,1650601208,479635942,4,8.45300006866455,80.0,100.0,80.0-100.0 +39,1650601208,479635942,5,13.613999843597412,100.0,120.0,100.0-120.0 +39,1650601208,479635942,6,497.2000000476837,120.0,140.0,120.0-140.0 +39,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +39,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +39,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +35,1650601208,479635942,0,4.420000076293945,0.0,20.0,0.0-20.0 +35,1650601208,479635942,1,22.999000072479248,20.0,40.0,20.0-40.0 +35,1650601208,479635942,2,42.980000019073486,40.0,60.0,40.0-60.0 +35,1650601208,479635942,3,10.475000381469727,60.0,80.0,60.0-80.0 +35,1650601208,479635942,4,20.78100037574768,80.0,100.0,80.0-100.0 +35,1650601208,479635942,5,9.062000036239624,100.0,120.0,100.0-120.0 +35,1650601208,479635942,6,349.4390001296997,120.0,140.0,120.0-140.0 +35,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +35,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +35,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +43,1650601208,479635942,0,5.408000469207764,0.0,20.0,0.0-20.0 +43,1650601208,479635942,1,17.420000553131104,20.0,40.0,20.0-40.0 +43,1650601208,479635942,2,12.46899962425232,40.0,60.0,40.0-60.0 +43,1650601208,479635942,3,2.246000051498413,60.0,80.0,60.0-80.0 +43,1650601208,479635942,4,0.0,80.0,100.0,80.0-100.0 +43,1650601208,479635942,5,21.176999807357788,100.0,120.0,100.0-120.0 +43,1650601208,479635942,6,327.8110001087189,120.0,140.0,120.0-140.0 +43,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +43,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +43,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +49,1650601208,479635942,0,1.2899999618530273,0.0,20.0,0.0-20.0 +49,1650601208,479635942,1,5.510999917984009,20.0,40.0,20.0-40.0 +49,1650601208,479635942,2,8.889999866485596,40.0,60.0,40.0-60.0 +49,1650601208,479635942,3,10.263000011444092,60.0,80.0,60.0-80.0 +49,1650601208,479635942,4,8.491999864578247,80.0,100.0,80.0-100.0 +49,1650601208,479635942,5,8.203999757766724,100.0,120.0,100.0-120.0 +49,1650601208,479635942,6,92.0,120.0,140.0,120.0-140.0 +49,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +49,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +49,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +22,1650601208,479635942,0,5.926999807357788,0.0,20.0,0.0-20.0 +22,1650601208,479635942,1,21.940999746322632,20.0,40.0,20.0-40.0 +22,1650601208,479635942,2,46.76200008392334,40.0,60.0,40.0-60.0 +22,1650601208,479635942,3,46.16399955749512,60.0,80.0,60.0-80.0 +22,1650601208,479635942,4,37.27300024032593,80.0,100.0,80.0-100.0 +22,1650601208,479635942,5,33.235000133514404,100.0,120.0,100.0-120.0 +22,1650601208,479635942,6,356.1919996738434,120.0,140.0,120.0-140.0 +22,1650601208,479635942,7,85.0,140.0,160.0,140.0-160.0 +22,1650601208,479635942,8,32.0,160.0,180.0,160.0-180.0 +22,1650601208,479635942,9,22.0,180.0,200.0,180.0-200.0 +76,1650601208,479635942,0,1.9129998683929443,0.0,20.0,0.0-20.0 +76,1650601208,479635942,1,10.884999752044678,20.0,40.0,20.0-40.0 +76,1650601208,479635942,2,15.609000444412231,40.0,60.0,40.0-60.0 +76,1650601208,479635942,3,10.48099970817566,60.0,80.0,60.0-80.0 +76,1650601208,479635942,4,2.2669999599456787,80.0,100.0,80.0-100.0 +76,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +76,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +76,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +76,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +76,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +16,1650601208,479635942,0,3.705000162124634,0.0,20.0,0.0-20.0 +16,1650601208,479635942,1,18.13099980354309,20.0,40.0,20.0-40.0 +16,1650601208,479635942,2,51.05400037765503,40.0,60.0,40.0-60.0 +16,1650601208,479635942,3,11.666000127792358,60.0,80.0,60.0-80.0 +16,1650601208,479635942,4,10.354000091552734,80.0,100.0,80.0-100.0 +16,1650601208,479635942,5,34.42799949645996,100.0,120.0,100.0-120.0 +16,1650601208,479635942,6,1141.1679999828339,120.0,140.0,120.0-140.0 +16,1650601208,479635942,7,234.0,140.0,160.0,140.0-160.0 +16,1650601208,479635942,8,90.0,160.0,180.0,160.0-180.0 +16,1650601208,479635942,9,25.0,180.0,200.0,180.0-200.0 +70,1650601208,479635942,0,3.2849998474121094,0.0,20.0,0.0-20.0 +70,1650601208,479635942,1,13.48900032043457,20.0,40.0,20.0-40.0 +70,1650601208,479635942,2,4.489999771118164,40.0,60.0,40.0-60.0 +70,1650601208,479635942,3,0.0989999771118164,60.0,80.0,60.0-80.0 +70,1650601208,479635942,4,1.7320001125335693,80.0,100.0,80.0-100.0 +70,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +70,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +70,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +70,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +70,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +4,1650601208,479635942,0,18.390999794006348,0.0,20.0,0.0-20.0 +4,1650601208,479635942,1,69.01699995994568,20.0,40.0,20.0-40.0 +4,1650601208,479635942,2,42.043999671936035,40.0,60.0,40.0-60.0 +4,1650601208,479635942,3,13.719000339508057,60.0,80.0,60.0-80.0 +4,1650601208,479635942,4,62.8050000667572,80.0,100.0,80.0-100.0 +4,1650601208,479635942,5,52.789000034332275,100.0,120.0,100.0-120.0 +4,1650601208,479635942,6,860.7999999523163,120.0,140.0,120.0-140.0 +4,1650601208,479635942,7,213.0,140.0,160.0,140.0-160.0 +4,1650601208,479635942,8,87.0,160.0,180.0,160.0-180.0 +4,1650601208,479635942,9,51.0,180.0,200.0,180.0-200.0 +8,1650601208,479635942,0,7.2659995555877686,0.0,20.0,0.0-20.0 +8,1650601208,479635942,1,21.407999992370605,20.0,40.0,20.0-40.0 +8,1650601208,479635942,2,11.783000230789185,40.0,60.0,40.0-60.0 +8,1650601208,479635942,3,15.637000322341919,60.0,80.0,60.0-80.0 +8,1650601208,479635942,4,38.78800010681152,80.0,100.0,80.0-100.0 +8,1650601208,479635942,5,71.13999962806702,100.0,120.0,100.0-120.0 +8,1650601208,479635942,6,842.5719997882843,120.0,140.0,120.0-140.0 +8,1650601208,479635942,7,138.0,140.0,160.0,140.0-160.0 +8,1650601208,479635942,8,58.0,160.0,180.0,160.0-180.0 +8,1650601208,479635942,9,36.0,180.0,200.0,180.0-200.0 +20,1650601208,479635942,0,8.585999965667725,0.0,20.0,0.0-20.0 +20,1650601208,479635942,1,19.19599986076355,20.0,40.0,20.0-40.0 +20,1650601208,479635942,2,18.27999973297119,40.0,60.0,40.0-60.0 +20,1650601208,479635942,3,9.07699990272522,60.0,80.0,60.0-80.0 +20,1650601208,479635942,4,10.724000215530396,80.0,100.0,80.0-100.0 +20,1650601208,479635942,5,29.31999969482422,100.0,120.0,100.0-120.0 +20,1650601208,479635942,6,306.6889998912811,120.0,140.0,120.0-140.0 +20,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +20,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +20,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +44,1650601208,479635942,0,2.114000082015991,0.0,20.0,0.0-20.0 +44,1650601208,479635942,1,12.960999727249146,20.0,40.0,20.0-40.0 +44,1650601208,479635942,2,29.532000064849854,40.0,60.0,40.0-60.0 +44,1650601208,479635942,3,5.055999994277954,60.0,80.0,60.0-80.0 +44,1650601208,479635942,4,3.4509999752044678,80.0,100.0,80.0-100.0 +44,1650601208,479635942,5,22.24899935722351,100.0,120.0,100.0-120.0 +44,1650601208,479635942,6,475.2669997215271,120.0,140.0,120.0-140.0 +44,1650601208,479635942,7,130.0,140.0,160.0,140.0-160.0 +44,1650601208,479635942,8,150.0,160.0,180.0,160.0-180.0 +44,1650601208,479635942,9,53.0,180.0,200.0,180.0-200.0 +30,1650601208,479635942,0,6.010999917984009,0.0,20.0,0.0-20.0 +30,1650601208,479635942,1,37.361000776290894,20.0,40.0,20.0-40.0 +30,1650601208,479635942,2,37.32800030708313,40.0,60.0,40.0-60.0 +30,1650601208,479635942,3,2.7860000133514404,60.0,80.0,60.0-80.0 +30,1650601208,479635942,4,3.692999839782715,80.0,100.0,80.0-100.0 +30,1650601208,479635942,5,1.7960000038146973,100.0,120.0,100.0-120.0 +30,1650601208,479635942,6,232.7720000743866,120.0,140.0,120.0-140.0 +30,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +30,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +30,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +50,1650601208,479635942,0,10.335999965667725,0.0,20.0,0.0-20.0 +50,1650601208,479635942,1,28.96899962425232,20.0,40.0,20.0-40.0 +50,1650601208,479635942,2,24.430999755859375,40.0,60.0,40.0-60.0 +50,1650601208,479635942,3,5.9659998416900635,60.0,80.0,60.0-80.0 +50,1650601208,479635942,4,5.861000061035156,80.0,100.0,80.0-100.0 +50,1650601208,479635942,5,5.640999794006348,100.0,120.0,100.0-120.0 +50,1650601208,479635942,6,166.0,120.0,140.0,120.0-140.0 +50,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +50,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +50,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +80,1650601208,479635942,0,4.133999824523926,0.0,20.0,0.0-20.0 +80,1650601208,479635942,1,24.025999784469604,20.0,40.0,20.0-40.0 +80,1650601208,479635942,2,25.842000484466553,40.0,60.0,40.0-60.0 +80,1650601208,479635942,3,4.501000165939331,60.0,80.0,60.0-80.0 +80,1650601208,479635942,4,6.442999839782715,80.0,100.0,80.0-100.0 +80,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +80,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +80,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +80,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +80,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +33,1650601208,479635942,0,10.035000085830688,0.0,20.0,0.0-20.0 +33,1650601208,479635942,1,41.073999881744385,20.0,40.0,20.0-40.0 +33,1650601208,479635942,2,41.460999727249146,40.0,60.0,40.0-60.0 +33,1650601208,479635942,3,25.85599994659424,60.0,80.0,60.0-80.0 +33,1650601208,479635942,4,52.42799973487854,80.0,100.0,80.0-100.0 +33,1650601208,479635942,5,374.3180003166199,100.0,120.0,100.0-120.0 +33,1650601208,479635942,6,399.98800015449524,120.0,140.0,120.0-140.0 +33,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +33,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +33,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +10,1650601208,479635942,0,9.16100025177002,0.0,20.0,0.0-20.0 +10,1650601208,479635942,1,30.361000061035156,20.0,40.0,20.0-40.0 +10,1650601208,479635942,2,11.082000255584717,40.0,60.0,40.0-60.0 +10,1650601208,479635942,3,4.306999921798706,60.0,80.0,60.0-80.0 +10,1650601208,479635942,4,2.4040000438690186,80.0,100.0,80.0-100.0 +10,1650601208,479635942,5,186.60700011253357,100.0,120.0,100.0-120.0 +10,1650601208,479635942,6,261.4119999408722,120.0,140.0,120.0-140.0 +10,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +10,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +10,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +40,1650601208,479635942,0,6.553999662399292,0.0,20.0,0.0-20.0 +40,1650601208,479635942,1,21.205999851226807,20.0,40.0,20.0-40.0 +40,1650601208,479635942,2,19.22100019454956,40.0,60.0,40.0-60.0 +40,1650601208,479635942,3,12.73099970817566,60.0,80.0,60.0-80.0 +40,1650601208,479635942,4,5.411999940872192,80.0,100.0,80.0-100.0 +40,1650601208,479635942,5,18.870999813079834,100.0,120.0,100.0-120.0 +40,1650601208,479635942,6,166.01900005340576,120.0,140.0,120.0-140.0 +40,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +40,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +40,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +46,1650601208,479635942,0,6.091999769210815,0.0,20.0,0.0-20.0 +46,1650601208,479635942,1,5.6419997215271,20.0,40.0,20.0-40.0 +46,1650601208,479635942,2,15.571000099182129,40.0,60.0,40.0-60.0 +46,1650601208,479635942,3,8.923999786376953,60.0,80.0,60.0-80.0 +46,1650601208,479635942,4,18.31600022315979,80.0,100.0,80.0-100.0 +46,1650601208,479635942,5,9.361999988555908,100.0,120.0,100.0-120.0 +46,1650601208,479635942,6,286.8710000514984,120.0,140.0,120.0-140.0 +46,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +46,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +46,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +53,1650601208,479635942,0,9.881999731063843,0.0,20.0,0.0-20.0 +53,1650601208,479635942,1,14.146000385284424,20.0,40.0,20.0-40.0 +53,1650601208,479635942,2,15.546000003814697,40.0,60.0,40.0-60.0 +53,1650601208,479635942,3,0.6959998607635498,60.0,80.0,60.0-80.0 +53,1650601208,479635942,4,1.937999963760376,80.0,100.0,80.0-100.0 +53,1650601208,479635942,5,13.47599983215332,100.0,120.0,100.0-120.0 +53,1650601208,479635942,6,254.0,120.0,140.0,120.0-140.0 +53,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +53,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +53,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +34,1650601208,479635942,0,5.322999715805054,0.0,20.0,0.0-20.0 +34,1650601208,479635942,1,21.000999689102173,20.0,40.0,20.0-40.0 +34,1650601208,479635942,2,17.105000257492065,40.0,60.0,40.0-60.0 +34,1650601208,479635942,3,7.132999897003174,60.0,80.0,60.0-80.0 +34,1650601208,479635942,4,11.398000001907349,80.0,100.0,80.0-100.0 +34,1650601208,479635942,5,30.367000102996826,100.0,120.0,100.0-120.0 +34,1650601208,479635942,6,549.6099998950958,120.0,140.0,120.0-140.0 +34,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +34,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +34,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +17,1650601208,479635942,0,9.160999536514282,0.0,20.0,0.0-20.0 +17,1650601208,479635942,1,16.122000217437744,20.0,40.0,20.0-40.0 +17,1650601208,479635942,2,17.057000160217285,40.0,60.0,40.0-60.0 +17,1650601208,479635942,3,5.045000314712524,60.0,80.0,60.0-80.0 +17,1650601208,479635942,4,4.797000169754028,80.0,100.0,80.0-100.0 +17,1650601208,479635942,5,9.59500002861023,100.0,120.0,100.0-120.0 +17,1650601208,479635942,6,194.6470000743866,120.0,140.0,120.0-140.0 +17,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +17,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +17,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +11,1650601208,479635942,0,7.879000186920166,0.0,20.0,0.0-20.0 +11,1650601208,479635942,1,37.40599989891052,20.0,40.0,20.0-40.0 +11,1650601208,479635942,2,25.96399974822998,40.0,60.0,40.0-60.0 +11,1650601208,479635942,3,13.407999753952026,60.0,80.0,60.0-80.0 +11,1650601208,479635942,4,16.212000131607056,80.0,100.0,80.0-100.0 +11,1650601208,479635942,5,52.43899965286255,100.0,120.0,100.0-120.0 +11,1650601208,479635942,6,1057.5130002498627,120.0,140.0,120.0-140.0 +11,1650601208,479635942,7,149.0,140.0,160.0,140.0-160.0 +11,1650601208,479635942,8,40.0,160.0,180.0,160.0-180.0 +11,1650601208,479635942,9,5.0,180.0,200.0,180.0-200.0 +12,1650601208,479635942,0,10.884999752044678,0.0,20.0,0.0-20.0 +12,1650601208,479635942,1,25.68499994277954,20.0,40.0,20.0-40.0 +12,1650601208,479635942,2,8.270999908447266,40.0,60.0,40.0-60.0 +12,1650601208,479635942,3,11.29800009727478,60.0,80.0,60.0-80.0 +12,1650601208,479635942,4,33.06700015068054,80.0,100.0,80.0-100.0 +12,1650601208,479635942,5,65.29999995231628,100.0,120.0,100.0-120.0 +12,1650601208,479635942,6,974.7320001125336,120.0,140.0,120.0-140.0 +12,1650601208,479635942,7,151.0,140.0,160.0,140.0-160.0 +12,1650601208,479635942,8,134.0,160.0,180.0,160.0-180.0 +12,1650601208,479635942,9,53.0,180.0,200.0,180.0-200.0 +81,1650601208,479635942,0,0.0,0.0,20.0,0.0-20.0 +81,1650601208,479635942,1,5.164000034332275,20.0,40.0,20.0-40.0 +81,1650601208,479635942,2,20.323000192642212,40.0,60.0,40.0-60.0 +81,1650601208,479635942,3,20.067999839782715,60.0,80.0,60.0-80.0 +81,1650601208,479635942,4,3.0439999103546143,80.0,100.0,80.0-100.0 +81,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +81,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +81,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +81,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +81,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +25,1650601208,479635942,0,6.118000030517578,0.0,20.0,0.0-20.0 +25,1650601208,479635942,1,29.918999910354614,20.0,40.0,20.0-40.0 +25,1650601208,479635942,2,9.591000318527222,40.0,60.0,40.0-60.0 +25,1650601208,479635942,3,6.742000102996826,60.0,80.0,60.0-80.0 +25,1650601208,479635942,4,7.473999977111816,80.0,100.0,80.0-100.0 +25,1650601208,479635942,5,14.598999977111816,100.0,120.0,100.0-120.0 +25,1650601208,479635942,6,338.90899991989136,120.0,140.0,120.0-140.0 +25,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +25,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +25,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +9,1650601208,479635942,0,0.75,0.0,20.0,0.0-20.0 +9,1650601208,479635942,1,18.699000358581543,20.0,40.0,20.0-40.0 +9,1650601208,479635942,2,28.75600028038025,40.0,60.0,40.0-60.0 +9,1650601208,479635942,3,19.00499987602234,60.0,80.0,60.0-80.0 +9,1650601208,479635942,4,26.515999794006348,80.0,100.0,80.0-100.0 +9,1650601208,479635942,5,43.538999795913696,100.0,120.0,100.0-120.0 +9,1650601208,479635942,6,782.3210000991821,120.0,140.0,120.0-140.0 +9,1650601208,479635942,7,173.0,140.0,160.0,140.0-160.0 +9,1650601208,479635942,8,85.0,160.0,180.0,160.0-180.0 +9,1650601208,479635942,9,24.0,180.0,200.0,180.0-200.0 +38,1650601208,479635942,0,5.002999782562256,0.0,20.0,0.0-20.0 +38,1650601208,479635942,1,14.337000370025635,20.0,40.0,20.0-40.0 +38,1650601208,479635942,2,4.3329997062683105,40.0,60.0,40.0-60.0 +38,1650601208,479635942,3,0.0,60.0,80.0,60.0-80.0 +38,1650601208,479635942,4,1.0320000648498535,80.0,100.0,80.0-100.0 +38,1650601208,479635942,5,28.73099994659424,100.0,120.0,100.0-120.0 +38,1650601208,479635942,6,343.4079999923706,120.0,140.0,120.0-140.0 +38,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +38,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +38,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +14,1650601208,479635942,0,5.801000118255615,0.0,20.0,0.0-20.0 +14,1650601208,479635942,1,22.826000690460205,20.0,40.0,20.0-40.0 +14,1650601208,479635942,2,22.742000102996826,40.0,60.0,40.0-60.0 +14,1650601208,479635942,3,6.872999906539917,60.0,80.0,60.0-80.0 +14,1650601208,479635942,4,19.932000160217285,80.0,100.0,80.0-100.0 +14,1650601208,479635942,5,45.425999879837036,100.0,120.0,100.0-120.0 +14,1650601208,479635942,6,1009.1250002384186,120.0,140.0,120.0-140.0 +14,1650601208,479635942,7,196.0,140.0,160.0,140.0-160.0 +14,1650601208,479635942,8,78.0,160.0,180.0,160.0-180.0 +14,1650601208,479635942,9,75.0,180.0,200.0,180.0-200.0 +29,1650601208,479635942,0,5.468000173568726,0.0,20.0,0.0-20.0 +29,1650601208,479635942,1,28.407000064849854,20.0,40.0,20.0-40.0 +29,1650601208,479635942,2,17.767000198364258,40.0,60.0,40.0-60.0 +29,1650601208,479635942,3,1.4659998416900635,60.0,80.0,60.0-80.0 +29,1650601208,479635942,4,0.9079999923706055,80.0,100.0,80.0-100.0 +29,1650601208,479635942,5,44.509000062942505,100.0,120.0,100.0-120.0 +29,1650601208,479635942,6,300.03399991989136,120.0,140.0,120.0-140.0 +29,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +29,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +29,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +61,1650601208,479635942,0,1.1099998950958252,0.0,20.0,0.0-20.0 +61,1650601208,479635942,1,9.28600001335144,20.0,40.0,20.0-40.0 +61,1650601208,479635942,2,5.766999959945679,40.0,60.0,40.0-60.0 +61,1650601208,479635942,3,3.621000051498413,60.0,80.0,60.0-80.0 +61,1650601208,479635942,4,0.5829999446868896,80.0,100.0,80.0-100.0 +61,1650601208,479635942,5,19.401000022888184,100.0,120.0,100.0-120.0 +61,1650601208,479635942,6,505.2630000114441,120.0,140.0,120.0-140.0 +61,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +61,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +61,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +68,1650601208,479635942,0,4.630000114440918,0.0,20.0,0.0-20.0 +68,1650601208,479635942,1,11.36900019645691,20.0,40.0,20.0-40.0 +68,1650601208,479635942,2,6.5929999351501465,40.0,60.0,40.0-60.0 +68,1650601208,479635942,3,6.558000087738037,60.0,80.0,60.0-80.0 +68,1650601208,479635942,4,11.668999910354614,80.0,100.0,80.0-100.0 +68,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +68,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +68,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +68,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +68,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +51,1650601208,479635942,0,3.254999876022339,0.0,20.0,0.0-20.0 +51,1650601208,479635942,1,12.04800009727478,20.0,40.0,20.0-40.0 +51,1650601208,479635942,2,15.393000364303589,40.0,60.0,40.0-60.0 +51,1650601208,479635942,3,3.812000274658203,60.0,80.0,60.0-80.0 +51,1650601208,479635942,4,0.0,80.0,100.0,80.0-100.0 +51,1650601208,479635942,5,32.21999979019165,100.0,120.0,100.0-120.0 +51,1650601208,479635942,6,1226.6640002727509,120.0,140.0,120.0-140.0 +51,1650601208,479635942,7,170.0,140.0,160.0,140.0-160.0 +51,1650601208,479635942,8,136.0,160.0,180.0,160.0-180.0 +51,1650601208,479635942,9,121.0,180.0,200.0,180.0-200.0 +23,1650601208,479635942,0,11.47700047492981,0.0,20.0,0.0-20.0 +23,1650601208,479635942,1,19.835000038146973,20.0,40.0,20.0-40.0 +23,1650601208,479635942,2,11.426000118255615,40.0,60.0,40.0-60.0 +23,1650601208,479635942,3,9.200999975204468,60.0,80.0,60.0-80.0 +23,1650601208,479635942,4,36.223000049591064,80.0,100.0,80.0-100.0 +23,1650601208,479635942,5,43.32000017166138,100.0,120.0,100.0-120.0 +23,1650601208,479635942,6,804.4109997749329,120.0,140.0,120.0-140.0 +23,1650601208,479635942,7,231.0,140.0,160.0,140.0-160.0 +23,1650601208,479635942,8,100.0,160.0,180.0,160.0-180.0 +23,1650601208,479635942,9,38.0,180.0,200.0,180.0-200.0 +36,1650601208,479635942,0,7.728999853134155,0.0,20.0,0.0-20.0 +36,1650601208,479635942,1,13.174999952316284,20.0,40.0,20.0-40.0 +36,1650601208,479635942,2,10.326000213623047,40.0,60.0,40.0-60.0 +36,1650601208,479635942,3,3.003000020980835,60.0,80.0,60.0-80.0 +36,1650601208,479635942,4,6.536999702453613,80.0,100.0,80.0-100.0 +36,1650601208,479635942,5,6.004999876022339,100.0,120.0,100.0-120.0 +36,1650601208,479635942,6,265.28200006484985,120.0,140.0,120.0-140.0 +36,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +36,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +36,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +60,1650601208,479635942,0,2.7950000762939453,0.0,20.0,0.0-20.0 +60,1650601208,479635942,1,10.498999834060669,20.0,40.0,20.0-40.0 +60,1650601208,479635942,2,29.247999668121338,40.0,60.0,40.0-60.0 +60,1650601208,479635942,3,15.557000398635864,60.0,80.0,60.0-80.0 +60,1650601208,479635942,4,5.699000120162964,80.0,100.0,80.0-100.0 +60,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +60,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +60,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +60,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +60,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +18,1650601208,479635942,0,4.121000051498413,0.0,20.0,0.0-20.0 +18,1650601208,479635942,1,5.961999893188477,20.0,40.0,20.0-40.0 +18,1650601208,479635942,2,24.38099980354309,40.0,60.0,40.0-60.0 +18,1650601208,479635942,3,4.855000257492065,60.0,80.0,60.0-80.0 +18,1650601208,479635942,4,2.5339999198913574,80.0,100.0,80.0-100.0 +18,1650601208,479635942,5,64.44199967384338,100.0,120.0,100.0-120.0 +18,1650601208,479635942,6,1509.6249997615814,120.0,140.0,120.0-140.0 +18,1650601208,479635942,7,198.0,140.0,160.0,140.0-160.0 +18,1650601208,479635942,8,60.0,160.0,180.0,160.0-180.0 +18,1650601208,479635942,9,25.0,180.0,200.0,180.0-200.0 +32,1650601208,479635942,0,5.072999715805054,0.0,20.0,0.0-20.0 +32,1650601208,479635942,1,29.634999752044678,20.0,40.0,20.0-40.0 +32,1650601208,479635942,2,19.76199984550476,40.0,60.0,40.0-60.0 +32,1650601208,479635942,3,6.398000240325928,60.0,80.0,60.0-80.0 +32,1650601208,479635942,4,12.368999719619751,80.0,100.0,80.0-100.0 +32,1650601208,479635942,5,40.537999868392944,100.0,120.0,100.0-120.0 +32,1650601208,479635942,6,262.5460000038147,120.0,140.0,120.0-140.0 +32,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +32,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +32,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +42,1650601208,479635942,0,4.313999891281128,0.0,20.0,0.0-20.0 +42,1650601208,479635942,1,23.26799988746643,20.0,40.0,20.0-40.0 +42,1650601208,479635942,2,16.91800022125244,40.0,60.0,40.0-60.0 +42,1650601208,479635942,3,8.259999990463257,60.0,80.0,60.0-80.0 +42,1650601208,479635942,4,14.244999885559082,80.0,100.0,80.0-100.0 +42,1650601208,479635942,5,47.08099985122681,100.0,120.0,100.0-120.0 +42,1650601208,479635942,6,376.23800015449524,120.0,140.0,120.0-140.0 +42,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +42,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +42,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +55,1650601208,479635942,0,4.744000196456909,0.0,20.0,0.0-20.0 +55,1650601208,479635942,1,21.726999521255493,20.0,40.0,20.0-40.0 +55,1650601208,479635942,2,6.817000150680542,40.0,60.0,40.0-60.0 +55,1650601208,479635942,3,3.1489996910095215,60.0,80.0,60.0-80.0 +55,1650601208,479635942,4,9.75,80.0,100.0,80.0-100.0 +55,1650601208,479635942,5,11.67799997329712,100.0,120.0,100.0-120.0 +55,1650601208,479635942,6,312.9119999408722,120.0,140.0,120.0-140.0 +55,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +55,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +55,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +67,1650601208,479635942,0,6.002000093460083,0.0,20.0,0.0-20.0 +67,1650601208,479635942,1,7.947999715805054,20.0,40.0,20.0-40.0 +67,1650601208,479635942,2,33.82499957084656,40.0,60.0,40.0-60.0 +67,1650601208,479635942,3,16.243000030517578,60.0,80.0,60.0-80.0 +67,1650601208,479635942,4,2.634000062942505,80.0,100.0,80.0-100.0 +67,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +67,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +67,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +67,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +67,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +69,1650601208,479635942,0,8.27899980545044,0.0,20.0,0.0-20.0 +69,1650601208,479635942,1,8.6419997215271,20.0,40.0,20.0-40.0 +69,1650601208,479635942,2,7.4670000076293945,40.0,60.0,40.0-60.0 +69,1650601208,479635942,3,0.0,60.0,80.0,60.0-80.0 +69,1650601208,479635942,4,0.0,80.0,100.0,80.0-100.0 +69,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +69,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +69,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +69,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +69,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +59,1650601208,479635942,0,4.7699995040893555,0.0,20.0,0.0-20.0 +59,1650601208,479635942,1,48.91099977493286,20.0,40.0,20.0-40.0 +59,1650601208,479635942,2,36.63100028038025,40.0,60.0,40.0-60.0 +59,1650601208,479635942,3,32.36099982261658,60.0,80.0,60.0-80.0 +59,1650601208,479635942,4,38.46499967575073,80.0,100.0,80.0-100.0 +59,1650601208,479635942,5,101.41899991035461,100.0,120.0,100.0-120.0 +59,1650601208,479635942,6,562.0110001564026,120.0,140.0,120.0-140.0 +59,1650601208,479635942,7,26.0,140.0,160.0,140.0-160.0 +59,1650601208,479635942,8,19.0,160.0,180.0,160.0-180.0 +59,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +26,1650601208,479635942,0,15.731999635696411,0.0,20.0,0.0-20.0 +26,1650601208,479635942,1,19.463000059127808,20.0,40.0,20.0-40.0 +26,1650601208,479635942,2,49.53899931907654,40.0,60.0,40.0-60.0 +26,1650601208,479635942,3,17.043999910354614,60.0,80.0,60.0-80.0 +26,1650601208,479635942,4,27.563000440597534,80.0,100.0,80.0-100.0 +26,1650601208,479635942,5,2.25,100.0,120.0,100.0-120.0 +26,1650601208,479635942,6,38.789000034332275,120.0,140.0,120.0-140.0 +26,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +26,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +26,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +47,1650601208,479635942,0,2.9720001220703125,0.0,20.0,0.0-20.0 +47,1650601208,479635942,1,22.3510000705719,20.0,40.0,20.0-40.0 +47,1650601208,479635942,2,21.10200047492981,40.0,60.0,40.0-60.0 +47,1650601208,479635942,3,7.428999662399292,60.0,80.0,60.0-80.0 +47,1650601208,479635942,4,4.352999925613403,80.0,100.0,80.0-100.0 +47,1650601208,479635942,5,61.835999488830566,100.0,120.0,100.0-120.0 +47,1650601208,479635942,6,285.79299998283386,120.0,140.0,120.0-140.0 +47,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +47,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +47,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +74,1650601208,479635942,0,8.037999629974365,0.0,20.0,0.0-20.0 +74,1650601208,479635942,1,13.637999773025513,20.0,40.0,20.0-40.0 +74,1650601208,479635942,2,14.28600025177002,40.0,60.0,40.0-60.0 +74,1650601208,479635942,3,6.2930004596710205,60.0,80.0,60.0-80.0 +74,1650601208,479635942,4,2.2950000762939453,80.0,100.0,80.0-100.0 +74,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +74,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +74,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +74,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +74,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +62,1650601208,479635942,0,2.2869999408721924,0.0,20.0,0.0-20.0 +62,1650601208,479635942,1,9.260000228881836,20.0,40.0,20.0-40.0 +62,1650601208,479635942,2,13.741999864578247,40.0,60.0,40.0-60.0 +62,1650601208,479635942,3,2.553999900817871,60.0,80.0,60.0-80.0 +62,1650601208,479635942,4,0.0,80.0,100.0,80.0-100.0 +62,1650601208,479635942,5,4.011999845504761,100.0,120.0,100.0-120.0 +62,1650601208,479635942,6,333.25099992752075,120.0,140.0,120.0-140.0 +62,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +62,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +62,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +31,1650601208,479635942,0,8.723000288009644,0.0,20.0,0.0-20.0 +31,1650601208,479635942,1,26.126999616622925,20.0,40.0,20.0-40.0 +31,1650601208,479635942,2,28.31600022315979,40.0,60.0,40.0-60.0 +31,1650601208,479635942,3,10.780999898910522,60.0,80.0,60.0-80.0 +31,1650601208,479635942,4,13.184000015258789,80.0,100.0,80.0-100.0 +31,1650601208,479635942,5,7.829999685287476,100.0,120.0,100.0-120.0 +31,1650601208,479635942,6,366.47199988365173,120.0,140.0,120.0-140.0 +31,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +31,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +31,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +56,1650601208,479635942,0,0.8810000419616699,0.0,20.0,0.0-20.0 +56,1650601208,479635942,1,9.948999881744385,20.0,40.0,20.0-40.0 +56,1650601208,479635942,2,29.597999811172485,40.0,60.0,40.0-60.0 +56,1650601208,479635942,3,4.230999946594238,60.0,80.0,60.0-80.0 +56,1650601208,479635942,4,10.319999694824219,80.0,100.0,80.0-100.0 +56,1650601208,479635942,5,32.77300024032593,100.0,120.0,100.0-120.0 +56,1650601208,479635942,6,365.38800024986267,120.0,140.0,120.0-140.0 +56,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +56,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +56,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +73,1650601208,479635942,0,1.3550000190734863,0.0,20.0,0.0-20.0 +73,1650601208,479635942,1,10.442999601364136,20.0,40.0,20.0-40.0 +73,1650601208,479635942,2,2.88700008392334,40.0,60.0,40.0-60.0 +73,1650601208,479635942,3,1.9110000133514404,60.0,80.0,60.0-80.0 +73,1650601208,479635942,4,1.8510000705718994,80.0,100.0,80.0-100.0 +73,1650601208,479635942,5,3.1599998474121094,100.0,120.0,100.0-120.0 +73,1650601208,479635942,6,99.08299994468689,120.0,140.0,120.0-140.0 +73,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +73,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +73,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +6,1650601208,479635942,0,10.128000259399414,0.0,20.0,0.0-20.0 +6,1650601208,479635942,1,21.66099977493286,20.0,40.0,20.0-40.0 +6,1650601208,479635942,2,13.740999698638916,40.0,60.0,40.0-60.0 +6,1650601208,479635942,3,1.6570000648498535,60.0,80.0,60.0-80.0 +6,1650601208,479635942,4,4.210999965667725,80.0,100.0,80.0-100.0 +6,1650601208,479635942,5,56.05999994277954,100.0,120.0,100.0-120.0 +6,1650601208,479635942,6,891.0009999275208,120.0,140.0,120.0-140.0 +6,1650601208,479635942,7,197.0,140.0,160.0,140.0-160.0 +6,1650601208,479635942,8,164.0,160.0,180.0,160.0-180.0 +6,1650601208,479635942,9,45.0,180.0,200.0,180.0-200.0 +78,1650601208,479635942,0,3.1459999084472656,0.0,20.0,0.0-20.0 +78,1650601208,479635942,1,9.855000019073486,20.0,40.0,20.0-40.0 +78,1650601208,479635942,2,11.586000204086304,40.0,60.0,40.0-60.0 +78,1650601208,479635942,3,11.25,60.0,80.0,60.0-80.0 +78,1650601208,479635942,4,11.0239999294281,80.0,100.0,80.0-100.0 +78,1650601208,479635942,5,5.151000022888184,100.0,120.0,100.0-120.0 +78,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +78,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +78,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +78,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +19,1650601208,479635942,0,8.42300009727478,0.0,20.0,0.0-20.0 +19,1650601208,479635942,1,37.29699969291687,20.0,40.0,20.0-40.0 +19,1650601208,479635942,2,15.55299997329712,40.0,60.0,40.0-60.0 +19,1650601208,479635942,3,11.168000221252441,60.0,80.0,60.0-80.0 +19,1650601208,479635942,4,14.953999996185303,80.0,100.0,80.0-100.0 +19,1650601208,479635942,5,46.43299961090088,100.0,120.0,100.0-120.0 +19,1650601208,479635942,6,709.7850000858307,120.0,140.0,120.0-140.0 +19,1650601208,479635942,7,212.0,140.0,160.0,140.0-160.0 +19,1650601208,479635942,8,27.0,160.0,180.0,160.0-180.0 +19,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +48,1650601208,479635942,0,5.446999549865723,0.0,20.0,0.0-20.0 +48,1650601208,479635942,1,16.72699999809265,20.0,40.0,20.0-40.0 +48,1650601208,479635942,2,13.656999588012695,40.0,60.0,40.0-60.0 +48,1650601208,479635942,3,1.0499999523162842,60.0,80.0,60.0-80.0 +48,1650601208,479635942,4,0.47300004959106445,80.0,100.0,80.0-100.0 +48,1650601208,479635942,5,19.126999855041504,100.0,120.0,100.0-120.0 +48,1650601208,479635942,6,571.6639997959137,120.0,140.0,120.0-140.0 +48,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +48,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +48,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 +79,1650601208,479635942,0,9.5600004196167,0.0,20.0,0.0-20.0 +79,1650601208,479635942,1,13.09499979019165,20.0,40.0,20.0-40.0 +79,1650601208,479635942,2,31.70799946784973,40.0,60.0,40.0-60.0 +79,1650601208,479635942,3,3.321000099182129,60.0,80.0,60.0-80.0 +79,1650601208,479635942,4,0.7890000343322754,80.0,100.0,80.0-100.0 +79,1650601208,479635942,5,0.0,100.0,120.0,100.0-120.0 +79,1650601208,479635942,6,0.0,120.0,140.0,120.0-140.0 +79,1650601208,479635942,7,0.0,140.0,160.0,140.0-160.0 +79,1650601208,479635942,8,0.0,160.0,180.0,160.0-180.0 +79,1650601208,479635942,9,0.0,180.0,200.0,180.0-200.0 diff --git a/tests/e2e/expected/pushdown/stats_aggregator_fact.csv b/tests/e2e/expected/pushdown/stats_aggregator_fact.csv new file mode 100644 index 00000000..c5651fd7 --- /dev/null +++ b/tests/e2e/expected/pushdown/stats_aggregator_fact.csv @@ -0,0 +1,973 @@ +container_id,visual_id,channel_name,event_id,event_instance_id,aggregation_label,statistic_value +58,430930200,coolant_1s,1201859611,1432421368744511818,min,11.0 +58,430930200,coolant_1s,1201859611,1432421368744511818,mean,71.05970651180458 +58,430930200,coolant_1s,1201859611,1432421368744511818,max,95.0 +58,430930200,speed_1s_diff,1201859611,1432421368744511818,min,-13.0 +58,430930200,speed_1s_diff,1201859611,1432421368744511818,mean,0.0 +58,430930200,speed_1s_diff,1201859611,1432421368744511818,max,13.0 +58,1374207915,Engine RPM,1201859611,1432421368744511818,min,0.0 +58,1374207915,Engine RPM,1201859611,1432421368744511818,mean,1211.9540284966436 +58,1374207915,Engine RPM,1201859611,1432421368744511818,max,2441.0 +58,1374207915,rpm_1s,1201859611,1432421368744511818,min,0.0 +58,1374207915,rpm_1s,1201859611,1432421368744511818,mean,1211.838531101375 +58,1374207915,rpm_1s,1201859611,1432421368744511818,max,2441.0 +21,430930200,coolant_1s,1201859611,2586283319979737404,min,41.0 +21,430930200,coolant_1s,1201859611,2586283319979737404,mean,81.94178674855078 +21,430930200,coolant_1s,1201859611,2586283319979737404,max,93.0 +21,430930200,speed_1s_diff,1201859611,2586283319979737404,min,-24.0 +21,430930200,speed_1s_diff,1201859611,2586283319979737404,mean,0.0 +21,430930200,speed_1s_diff,1201859611,2586283319979737404,max,17.0 +71,430930200,coolant_1s,1201859611,5850834331784891980,min,24.0 +71,430930200,coolant_1s,1201859611,5850834331784891980,mean,73.02575676121619 +71,430930200,coolant_1s,1201859611,5850834331784891980,max,95.0 +71,430930200,speed_1s_diff,1201859611,5850834331784891980,min,-14.0 +71,430930200,speed_1s_diff,1201859611,5850834331784891980,mean,0.0 +71,430930200,speed_1s_diff,1201859611,5850834331784891980,max,15.0 +21,1374207915,Engine RPM,1201859611,2586283319979737404,min,0.0 +21,1374207915,Engine RPM,1201859611,2586283319979737404,mean,1484.8370648465818 +21,1374207915,Engine RPM,1201859611,2586283319979737404,max,4315.0 +21,1374207915,rpm_1s,1201859611,2586283319979737404,min,0.0 +21,1374207915,rpm_1s,1201859611,2586283319979737404,mean,1485.8446271713678 +21,1374207915,rpm_1s,1201859611,2586283319979737404,max,4315.0 +71,1374207915,Engine RPM,1201859611,5850834331784891980,min,0.0 +71,1374207915,Engine RPM,1201859611,5850834331784891980,mean,1210.6967274379303 +71,1374207915,Engine RPM,1201859611,5850834331784891980,max,2766.0 +71,1374207915,rpm_1s,1201859611,5850834331784891980,min,0.0 +71,1374207915,rpm_1s,1201859611,5850834331784891980,mean,1212.7424171754622 +71,1374207915,rpm_1s,1201859611,5850834331784891980,max,2766.0 +2,430930200,coolant_1s,1201859611,7921822167645060769,min,17.0 +2,430930200,coolant_1s,1201859611,7921822167645060769,mean,86.48472072621517 +2,430930200,coolant_1s,1201859611,7921822167645060769,max,98.0 +2,430930200,speed_1s_diff,1201859611,7921822167645060769,min,-14.0 +2,430930200,speed_1s_diff,1201859611,7921822167645060769,mean,0.0 +2,430930200,speed_1s_diff,1201859611,7921822167645060769,max,16.0 +75,430930200,coolant_1s,1201859611,5110935768283636594,min,30.0 +75,430930200,coolant_1s,1201859611,5110935768283636594,mean,72.03738821251518 +75,430930200,coolant_1s,1201859611,5110935768283636594,max,94.0 +75,430930200,speed_1s_diff,1201859611,5110935768283636594,min,-13.0 +75,430930200,speed_1s_diff,1201859611,5110935768283636594,mean,0.0 +75,430930200,speed_1s_diff,1201859611,5110935768283636594,max,14.0 +2,1374207915,Engine RPM,1201859611,7921822167645060769,min,0.0 +2,1374207915,Engine RPM,1201859611,7921822167645060769,mean,1429.4126314544558 +2,1374207915,Engine RPM,1201859611,7921822167645060769,max,3255.0 +2,1374207915,rpm_1s,1201859611,7921822167645060769,min,0.0 +2,1374207915,rpm_1s,1201859611,7921822167645060769,mean,1429.1606230628358 +2,1374207915,rpm_1s,1201859611,7921822167645060769,max,3254.0 +75,1374207915,Engine RPM,1201859611,5110935768283636594,min,0.0 +75,1374207915,Engine RPM,1201859611,5110935768283636594,mean,1186.5843996568703 +75,1374207915,Engine RPM,1201859611,5110935768283636594,max,2634.0 +75,1374207915,rpm_1s,1201859611,5110935768283636594,min,0.0 +75,1374207915,rpm_1s,1201859611,5110935768283636594,mean,1183.672643902938 +75,1374207915,rpm_1s,1201859611,5110935768283636594,max,2634.0 +13,430930200,coolant_1s,1201859611,-1944483825207215539,min,17.0 +13,430930200,coolant_1s,1201859611,-1944483825207215539,mean,80.0719329761365 +13,430930200,coolant_1s,1201859611,-1944483825207215539,max,94.0 +13,430930200,speed_1s_diff,1201859611,-1944483825207215539,min,-18.0 +13,430930200,speed_1s_diff,1201859611,-1944483825207215539,mean,0.0 +13,430930200,speed_1s_diff,1201859611,-1944483825207215539,max,14.0 +65,430930200,coolant_1s,1201859611,7663622432945194351,min,24.0 +65,430930200,coolant_1s,1201859611,7663622432945194351,mean,76.26011430381257 +65,430930200,coolant_1s,1201859611,7663622432945194351,max,94.0 +65,430930200,speed_1s_diff,1201859611,7663622432945194351,min,-25.0 +65,430930200,speed_1s_diff,1201859611,7663622432945194351,mean,0.005864017934646281 +65,430930200,speed_1s_diff,1201859611,7663622432945194351,max,14.0 +13,1374207915,Engine RPM,1201859611,-1944483825207215539,min,0.0 +13,1374207915,Engine RPM,1201859611,-1944483825207215539,mean,1658.5566166308397 +13,1374207915,Engine RPM,1201859611,-1944483825207215539,max,3008.0 +13,1374207915,rpm_1s,1201859611,-1944483825207215539,min,0.0 +13,1374207915,rpm_1s,1201859611,-1944483825207215539,mean,1658.472083582725 +13,1374207915,rpm_1s,1201859611,-1944483825207215539,max,3008.0 +65,1374207915,Engine RPM,1201859611,7663622432945194351,min,0.0 +65,1374207915,Engine RPM,1201859611,7663622432945194351,mean,1033.478161341088 +65,1374207915,Engine RPM,1201859611,7663622432945194351,max,2707.0 +65,1374207915,rpm_1s,1201859611,7663622432945194351,min,0.0 +65,1374207915,rpm_1s,1201859611,7663622432945194351,mean,1032.6033623438395 +65,1374207915,rpm_1s,1201859611,7663622432945194351,max,2631.0 +5,430930200,coolant_1s,1201859611,3570466563200151918,min,30.0 +5,430930200,coolant_1s,1201859611,3570466563200151918,mean,86.50359106161959 +5,430930200,coolant_1s,1201859611,3570466563200151918,max,93.0 +5,430930200,speed_1s_diff,1201859611,3570466563200151918,min,-49.0 +5,430930200,speed_1s_diff,1201859611,3570466563200151918,mean,0.0 +5,430930200,speed_1s_diff,1201859611,3570466563200151918,max,39.0 +45,430930200,coolant_1s,1201859611,4031524100617037594,min,89.0 +45,430930200,coolant_1s,1201859611,4031524100617037594,mean,90.3648305900915 +45,430930200,coolant_1s,1201859611,4031524100617037594,max,93.0 +45,430930200,speed_1s_diff,1201859611,4031524100617037594,min,-10.0 +45,430930200,speed_1s_diff,1201859611,4031524100617037594,mean,-0.024986709967894028 +45,430930200,speed_1s_diff,1201859611,4031524100617037594,max,11.0 +5,1374207915,Engine RPM,1201859611,3570466563200151918,min,0.0 +5,1374207915,Engine RPM,1201859611,3570466563200151918,mean,1490.0271707300046 +5,1374207915,Engine RPM,1201859611,3570466563200151918,max,3658.0 +5,1374207915,rpm_1s,1201859611,3570466563200151918,min,0.0 +5,1374207915,rpm_1s,1201859611,3570466563200151918,mean,1490.4425669184475 +5,1374207915,rpm_1s,1201859611,3570466563200151918,max,3646.0 +45,1374207915,Engine RPM,1201859611,4031524100617037594,min,0.0 +45,1374207915,Engine RPM,1201859611,4031524100617037594,mean,1373.587975935938 +45,1374207915,Engine RPM,1201859611,4031524100617037594,max,2373.0 +45,1374207915,rpm_1s,1201859611,4031524100617037594,min,0.0 +45,1374207915,rpm_1s,1201859611,4031524100617037594,mean,1373.0976292449066 +45,1374207915,rpm_1s,1201859611,4031524100617037594,max,2373.0 +7,430930200,coolant_1s,1201859611,-7434725489921472031,min,30.0 +7,430930200,coolant_1s,1201859611,-7434725489921472031,mean,86.36500257550907 +7,430930200,coolant_1s,1201859611,-7434725489921472031,max,96.0 +7,430930200,speed_1s_diff,1201859611,-7434725489921472031,min,-13.0 +7,430930200,speed_1s_diff,1201859611,-7434725489921472031,mean,0.0 +7,430930200,speed_1s_diff,1201859611,-7434725489921472031,max,13.0 +7,1374207915,Engine RPM,1201859611,-7434725489921472031,min,0.0 +7,1374207915,Engine RPM,1201859611,-7434725489921472031,mean,1589.8300959432972 +7,1374207915,Engine RPM,1201859611,-7434725489921472031,max,3072.0 +7,1374207915,rpm_1s,1201859611,-7434725489921472031,min,0.0 +7,1374207915,rpm_1s,1201859611,-7434725489921472031,mean,1589.9044574597033 +7,1374207915,rpm_1s,1201859611,-7434725489921472031,max,3072.0 +41,430930200,coolant_1s,1201859611,8437372657831499562,min,7.0 +41,430930200,coolant_1s,1201859611,8437372657831499562,mean,75.75952443698638 +41,430930200,coolant_1s,1201859611,8437372657831499562,max,95.0 +41,430930200,speed_1s_diff,1201859611,8437372657831499562,min,-14.0 +41,430930200,speed_1s_diff,1201859611,8437372657831499562,mean,0.0 +41,430930200,speed_1s_diff,1201859611,8437372657831499562,max,17.0 +41,1374207915,Engine RPM,1201859611,8437372657831499562,min,0.0 +41,1374207915,Engine RPM,1201859611,8437372657831499562,mean,1577.3415777230712 +41,1374207915,Engine RPM,1201859611,8437372657831499562,max,2764.0 +41,1374207915,rpm_1s,1201859611,8437372657831499562,min,0.0 +41,1374207915,rpm_1s,1201859611,8437372657831499562,mean,1577.8877736555137 +41,1374207915,rpm_1s,1201859611,8437372657831499562,max,2764.0 +72,430930200,coolant_1s,1201859611,-4361505612007328068,min,22.0 +72,430930200,coolant_1s,1201859611,-4361505612007328068,mean,71.81384326508437 +72,430930200,coolant_1s,1201859611,-4361505612007328068,max,94.0 +72,430930200,speed_1s_diff,1201859611,-4361505612007328068,min,-13.0 +72,430930200,speed_1s_diff,1201859611,-4361505612007328068,mean,0.0 +72,430930200,speed_1s_diff,1201859611,-4361505612007328068,max,17.0 +72,1374207915,Engine RPM,1201859611,-4361505612007328068,min,0.0 +72,1374207915,Engine RPM,1201859611,-4361505612007328068,mean,1238.9722056881737 +72,1374207915,Engine RPM,1201859611,-4361505612007328068,max,2747.0 +72,1374207915,rpm_1s,1201859611,-4361505612007328068,min,0.0 +72,1374207915,rpm_1s,1201859611,-4361505612007328068,mean,1237.592792664496 +72,1374207915,rpm_1s,1201859611,-4361505612007328068,max,2747.0 +63,430930200,coolant_1s,1201859611,-1533397159073443657,min,2.0 +63,430930200,coolant_1s,1201859611,-1533397159073443657,mean,51.822878863979106 +63,430930200,coolant_1s,1201859611,-1533397159073443657,max,70.0 +63,430930200,speed_1s_diff,1201859611,-1533397159073443657,min,-13.0 +63,430930200,speed_1s_diff,1201859611,-1533397159073443657,mean,0.0 +63,430930200,speed_1s_diff,1201859611,-1533397159073443657,max,15.0 +64,430930200,coolant_1s,1201859611,1978039981849347272,min,28.0 +64,430930200,coolant_1s,1201859611,1978039981849347272,mean,75.63724535841438 +64,430930200,coolant_1s,1201859611,1978039981849347272,max,94.0 +64,430930200,speed_1s_diff,1201859611,1978039981849347272,min,-18.0 +64,430930200,speed_1s_diff,1201859611,1978039981849347272,mean,0.0 +64,430930200,speed_1s_diff,1201859611,1978039981849347272,max,16.0 +63,1374207915,Engine RPM,1201859611,-1533397159073443657,min,0.0 +63,1374207915,Engine RPM,1201859611,-1533397159073443657,mean,1174.1847038505005 +63,1374207915,Engine RPM,1201859611,-1533397159073443657,max,2580.0 +63,1374207915,rpm_1s,1201859611,-1533397159073443657,min,0.0 +63,1374207915,rpm_1s,1201859611,-1533397159073443657,mean,1174.0833446165725 +63,1374207915,rpm_1s,1201859611,-1533397159073443657,max,2396.0 +64,1374207915,Engine RPM,1201859611,1978039981849347272,min,0.0 +64,1374207915,Engine RPM,1201859611,1978039981849347272,mean,1053.9283989931587 +64,1374207915,Engine RPM,1201859611,1978039981849347272,max,2979.0 +64,1374207915,rpm_1s,1201859611,1978039981849347272,min,0.0 +64,1374207915,rpm_1s,1201859611,1978039981849347272,mean,1054.365356277456 +64,1374207915,rpm_1s,1201859611,1978039981849347272,max,2964.0 +66,430930200,coolant_1s,1201859611,6831185462073925864,min,-1.0 +66,430930200,coolant_1s,1201859611,6831185462073925864,mean,45.183517242335085 +66,430930200,coolant_1s,1201859611,6831185462073925864,max,64.0 +66,430930200,speed_1s_diff,1201859611,6831185462073925864,min,-13.0 +66,430930200,speed_1s_diff,1201859611,6831185462073925864,mean,0.0 +66,430930200,speed_1s_diff,1201859611,6831185462073925864,max,15.0 +66,1374207915,Engine RPM,1201859611,6831185462073925864,min,0.0 +66,1374207915,Engine RPM,1201859611,6831185462073925864,mean,1308.5081461022414 +66,1374207915,Engine RPM,1201859611,6831185462073925864,max,2577.0 +66,1374207915,rpm_1s,1201859611,6831185462073925864,min,0.0 +66,1374207915,rpm_1s,1201859611,6831185462073925864,mean,1308.4926628904345 +66,1374207915,rpm_1s,1201859611,6831185462073925864,max,2577.0 +1,430930200,coolant_1s,1201859611,-928762887014768240,min,20.0 +1,430930200,coolant_1s,1201859611,-928762887014768240,mean,81.47659307165077 +1,430930200,coolant_1s,1201859611,-928762887014768240,max,93.0 +1,430930200,speed_1s_diff,1201859611,-928762887014768240,min,-20.0 +1,430930200,speed_1s_diff,1201859611,-928762887014768240,mean,1.237015359033603E-4 +1,430930200,speed_1s_diff,1201859611,-928762887014768240,max,16.0 +15,430930200,coolant_1s,1201859611,8620646447947530699,min,29.0 +15,430930200,coolant_1s,1201859611,8620646447947530699,mean,86.09125401444594 +15,430930200,coolant_1s,1201859611,8620646447947530699,max,100.0 +15,430930200,speed_1s_diff,1201859611,8620646447947530699,min,-15.0 +15,430930200,speed_1s_diff,1201859611,8620646447947530699,mean,0.0 +15,430930200,speed_1s_diff,1201859611,8620646447947530699,max,18.0 +77,430930200,coolant_1s,1201859611,8292766365109962602,min,30.0 +77,430930200,coolant_1s,1201859611,8292766365109962602,mean,71.54822269117298 +77,430930200,coolant_1s,1201859611,8292766365109962602,max,95.0 +77,430930200,speed_1s_diff,1201859611,8292766365109962602,min,-16.0 +77,430930200,speed_1s_diff,1201859611,8292766365109962602,mean,0.0 +77,430930200,speed_1s_diff,1201859611,8292766365109962602,max,12.0 +1,1374207915,Engine RPM,1201859611,-928762887014768240,min,0.0 +1,1374207915,Engine RPM,1201859611,-928762887014768240,mean,1303.5914744669135 +1,1374207915,Engine RPM,1201859611,-928762887014768240,max,3430.0 +1,1374207915,rpm_1s,1201859611,-928762887014768240,min,0.0 +1,1374207915,rpm_1s,1201859611,-928762887014768240,mean,1304.1977853971118 +1,1374207915,rpm_1s,1201859611,-928762887014768240,max,3430.0 +15,1374207915,Engine RPM,1201859611,8620646447947530699,min,0.0 +15,1374207915,Engine RPM,1201859611,8620646447947530699,mean,1402.273463882204 +15,1374207915,Engine RPM,1201859611,8620646447947530699,max,2882.0 +15,1374207915,rpm_1s,1201859611,8620646447947530699,min,0.0 +15,1374207915,rpm_1s,1201859611,8620646447947530699,mean,1402.6216503190205 +15,1374207915,rpm_1s,1201859611,8620646447947530699,max,2882.0 +77,1374207915,Engine RPM,1201859611,8292766365109962602,min,0.0 +77,1374207915,Engine RPM,1201859611,8292766365109962602,mean,1277.184755053257 +77,1374207915,Engine RPM,1201859611,8292766365109962602,max,2312.0 +77,1374207915,rpm_1s,1201859611,8292766365109962602,min,0.0 +77,1374207915,rpm_1s,1201859611,8292766365109962602,mean,1278.857758598333 +77,1374207915,rpm_1s,1201859611,8292766365109962602,max,2312.0 +3,430930200,coolant_1s,1201859611,3286614479096879038,min,31.0 +3,430930200,coolant_1s,1201859611,3286614479096879038,mean,86.7964519026567 +3,430930200,coolant_1s,1201859611,3286614479096879038,max,101.0 +3,430930200,speed_1s_diff,1201859611,3286614479096879038,min,-16.0 +3,430930200,speed_1s_diff,1201859611,3286614479096879038,mean,0.0 +3,430930200,speed_1s_diff,1201859611,3286614479096879038,max,16.0 +37,430930200,coolant_1s,1201859611,-3015666225396535697,min,15.0 +37,430930200,coolant_1s,1201859611,-3015666225396535697,mean,78.68194046069054 +37,430930200,coolant_1s,1201859611,-3015666225396535697,max,94.0 +37,430930200,speed_1s_diff,1201859611,-3015666225396535697,min,-20.0 +37,430930200,speed_1s_diff,1201859611,-3015666225396535697,mean,0.0 +37,430930200,speed_1s_diff,1201859611,-3015666225396535697,max,15.0 +3,1374207915,Engine RPM,1201859611,3286614479096879038,min,0.0 +3,1374207915,Engine RPM,1201859611,3286614479096879038,mean,1433.0548539779463 +3,1374207915,Engine RPM,1201859611,3286614479096879038,max,3111.0 +3,1374207915,rpm_1s,1201859611,3286614479096879038,min,0.0 +3,1374207915,rpm_1s,1201859611,3286614479096879038,mean,1432.434283788608 +3,1374207915,rpm_1s,1201859611,3286614479096879038,max,3111.0 +37,1374207915,Engine RPM,1201859611,-3015666225396535697,min,0.0 +37,1374207915,Engine RPM,1201859611,-3015666225396535697,mean,1537.2965390817742 +37,1374207915,Engine RPM,1201859611,-3015666225396535697,max,2991.0 +37,1374207915,rpm_1s,1201859611,-3015666225396535697,min,0.0 +37,1374207915,rpm_1s,1201859611,-3015666225396535697,mean,1537.6658722062787 +37,1374207915,rpm_1s,1201859611,-3015666225396535697,max,2991.0 +27,430930200,coolant_1s,1201859611,-3534261932286880816,min,31.0 +27,430930200,coolant_1s,1201859611,-3534261932286880816,mean,84.65791740302966 +27,430930200,coolant_1s,1201859611,-3534261932286880816,max,94.0 +27,430930200,speed_1s_diff,1201859611,-3534261932286880816,min,-14.0 +27,430930200,speed_1s_diff,1201859611,-3534261932286880816,mean,0.0 +27,430930200,speed_1s_diff,1201859611,-3534261932286880816,max,12.0 +57,430930200,coolant_1s,1201859611,8939056937015267775,min,73.0 +57,430930200,coolant_1s,1201859611,8939056937015267775,mean,88.20734359065447 +57,430930200,coolant_1s,1201859611,8939056937015267775,max,92.0 +57,430930200,speed_1s_diff,1201859611,8939056937015267775,min,-13.0 +57,430930200,speed_1s_diff,1201859611,8939056937015267775,mean,0.0 +57,430930200,speed_1s_diff,1201859611,8939056937015267775,max,12.0 +27,1374207915,Engine RPM,1201859611,-3534261932286880816,min,0.0 +27,1374207915,Engine RPM,1201859611,-3534261932286880816,mean,1441.383705808705 +27,1374207915,Engine RPM,1201859611,-3534261932286880816,max,2501.0 +27,1374207915,rpm_1s,1201859611,-3534261932286880816,min,0.0 +27,1374207915,rpm_1s,1201859611,-3534261932286880816,mean,1441.9025204368031 +27,1374207915,rpm_1s,1201859611,-3534261932286880816,max,2501.0 +57,1374207915,Engine RPM,1201859611,8939056937015267775,min,0.0 +57,1374207915,Engine RPM,1201859611,8939056937015267775,mean,1606.2501516080329 +57,1374207915,Engine RPM,1201859611,8939056937015267775,max,2830.0 +57,1374207915,rpm_1s,1201859611,8939056937015267775,min,0.0 +57,1374207915,rpm_1s,1201859611,8939056937015267775,mean,1605.4117230493096 +57,1374207915,rpm_1s,1201859611,8939056937015267775,max,2830.0 +24,430930200,coolant_1s,1201859611,7437298780708400991,min,29.0 +24,430930200,coolant_1s,1201859611,7437298780708400991,mean,84.96984794432757 +24,430930200,coolant_1s,1201859611,7437298780708400991,max,94.0 +24,430930200,speed_1s_diff,1201859611,7437298780708400991,min,-19.0 +24,430930200,speed_1s_diff,1201859611,7437298780708400991,mean,0.0 +24,430930200,speed_1s_diff,1201859611,7437298780708400991,max,16.0 +24,1374207915,Engine RPM,1201859611,7437298780708400991,min,0.0 +24,1374207915,Engine RPM,1201859611,7437298780708400991,mean,1468.2751083580695 +24,1374207915,Engine RPM,1201859611,7437298780708400991,max,2606.0 +24,1374207915,rpm_1s,1201859611,7437298780708400991,min,0.0 +24,1374207915,rpm_1s,1201859611,7437298780708400991,mean,1468.2773554513326 +24,1374207915,rpm_1s,1201859611,7437298780708400991,max,2529.0 +52,430930200,coolant_1s,1201859611,1261300119102051947,min,64.0 +52,430930200,coolant_1s,1201859611,1261300119102051947,mean,89.34073331886282 +52,430930200,coolant_1s,1201859611,1261300119102051947,max,93.0 +52,430930200,speed_1s_diff,1201859611,1261300119102051947,min,-19.0 +52,430930200,speed_1s_diff,1201859611,1261300119102051947,mean,0.0 +52,430930200,speed_1s_diff,1201859611,1261300119102051947,max,13.0 +52,1374207915,Engine RPM,1201859611,1261300119102051947,min,0.0 +52,1374207915,Engine RPM,1201859611,1261300119102051947,mean,1549.1329341290623 +52,1374207915,Engine RPM,1201859611,1261300119102051947,max,2645.0 +52,1374207915,rpm_1s,1201859611,1261300119102051947,min,0.0 +52,1374207915,rpm_1s,1201859611,1261300119102051947,mean,1548.1622332550762 +52,1374207915,rpm_1s,1201859611,1261300119102051947,max,2644.0 +28,430930200,coolant_1s,1201859611,1040427654212385429,min,16.0 +28,430930200,coolant_1s,1201859611,1040427654212385429,mean,80.05025592424866 +28,430930200,coolant_1s,1201859611,1040427654212385429,max,94.0 +28,430930200,speed_1s_diff,1201859611,1040427654212385429,min,-33.0 +28,430930200,speed_1s_diff,1201859611,1040427654212385429,mean,0.0 +28,430930200,speed_1s_diff,1201859611,1040427654212385429,max,15.0 +28,1374207915,Engine RPM,1201859611,1040427654212385429,min,0.0 +28,1374207915,Engine RPM,1201859611,1040427654212385429,mean,1501.7751038300937 +28,1374207915,Engine RPM,1201859611,1040427654212385429,max,2592.0 +28,1374207915,rpm_1s,1201859611,1040427654212385429,min,0.0 +28,1374207915,rpm_1s,1201859611,1040427654212385429,mean,1502.2266075213695 +28,1374207915,rpm_1s,1201859611,1040427654212385429,max,2592.0 +54,430930200,coolant_1s,1201859611,3329842588530743954,min,67.0 +54,430930200,coolant_1s,1201859611,3329842588530743954,mean,89.93323482747094 +54,430930200,coolant_1s,1201859611,3329842588530743954,max,94.0 +54,430930200,speed_1s_diff,1201859611,3329842588530743954,min,-12.0 +54,430930200,speed_1s_diff,1201859611,3329842588530743954,mean,0.0 +54,430930200,speed_1s_diff,1201859611,3329842588530743954,max,14.0 +54,1374207915,Engine RPM,1201859611,3329842588530743954,min,0.0 +54,1374207915,Engine RPM,1201859611,3329842588530743954,mean,1568.8600029955448 +54,1374207915,Engine RPM,1201859611,3329842588530743954,max,2892.0 +54,1374207915,rpm_1s,1201859611,3329842588530743954,min,0.0 +54,1374207915,rpm_1s,1201859611,3329842588530743954,mean,1568.6361157110307 +54,1374207915,rpm_1s,1201859611,3329842588530743954,max,2892.0 +39,430930200,coolant_1s,1201859611,1077981989772730875,min,55.0 +39,430930200,coolant_1s,1201859611,1077981989772730875,mean,88.40019071146253 +39,430930200,coolant_1s,1201859611,1077981989772730875,max,96.0 +39,430930200,speed_1s_diff,1201859611,1077981989772730875,min,-16.0 +39,430930200,speed_1s_diff,1201859611,1077981989772730875,mean,0.0 +39,430930200,speed_1s_diff,1201859611,1077981989772730875,max,16.0 +39,1374207915,Engine RPM,1201859611,1077981989772730875,min,0.0 +39,1374207915,Engine RPM,1201859611,1077981989772730875,mean,1546.1728841483266 +39,1374207915,Engine RPM,1201859611,1077981989772730875,max,3788.0 +39,1374207915,rpm_1s,1201859611,1077981989772730875,min,0.0 +39,1374207915,rpm_1s,1201859611,1077981989772730875,mean,1545.3243398661664 +39,1374207915,rpm_1s,1201859611,1077981989772730875,max,3511.0 +35,430930200,coolant_1s,1201859611,-5091309530965523951,min,9.0 +35,430930200,coolant_1s,1201859611,-5091309530965523951,mean,77.25033440079919 +35,430930200,coolant_1s,1201859611,-5091309530965523951,max,95.0 +35,430930200,speed_1s_diff,1201859611,-5091309530965523951,min,-25.0 +35,430930200,speed_1s_diff,1201859611,-5091309530965523951,mean,0.0 +35,430930200,speed_1s_diff,1201859611,-5091309530965523951,max,14.0 +35,1374207915,Engine RPM,1201859611,-5091309530965523951,min,0.0 +35,1374207915,Engine RPM,1201859611,-5091309530965523951,mean,1562.233448947786 +35,1374207915,Engine RPM,1201859611,-5091309530965523951,max,2759.0 +35,1374207915,rpm_1s,1201859611,-5091309530965523951,min,0.0 +35,1374207915,rpm_1s,1201859611,-5091309530965523951,mean,1563.170036056927 +35,1374207915,rpm_1s,1201859611,-5091309530965523951,max,2759.0 +43,430930200,coolant_1s,1201859611,-5059295208201598568,min,12.0 +43,430930200,coolant_1s,1201859611,-5059295208201598568,mean,75.0400358853205 +43,430930200,coolant_1s,1201859611,-5059295208201598568,max,94.0 +43,430930200,speed_1s_diff,1201859611,-5059295208201598568,min,-18.0 +43,430930200,speed_1s_diff,1201859611,-5059295208201598568,mean,0.0 +43,430930200,speed_1s_diff,1201859611,-5059295208201598568,max,19.0 +43,1374207915,Engine RPM,1201859611,-5059295208201598568,min,0.0 +43,1374207915,Engine RPM,1201859611,-5059295208201598568,mean,1613.6854438659657 +43,1374207915,Engine RPM,1201859611,-5059295208201598568,max,2792.0 +43,1374207915,rpm_1s,1201859611,-5059295208201598568,min,0.0 +43,1374207915,rpm_1s,1201859611,-5059295208201598568,mean,1614.2016543166471 +43,1374207915,rpm_1s,1201859611,-5059295208201598568,max,2792.0 +49,430930200,coolant_1s,1201859611,-8089365359392949673,min,17.0 +49,430930200,coolant_1s,1201859611,-8089365359392949673,mean,79.0921866724866 +49,430930200,coolant_1s,1201859611,-8089365359392949673,max,94.0 +49,430930200,speed_1s_diff,1201859611,-8089365359392949673,min,-16.0 +49,430930200,speed_1s_diff,1201859611,-8089365359392949673,mean,0.0 +49,430930200,speed_1s_diff,1201859611,-8089365359392949673,max,18.0 +49,1374207915,Engine RPM,1201859611,-8089365359392949673,min,262.0 +49,1374207915,Engine RPM,1201859611,-8089365359392949673,mean,1587.8939076350948 +49,1374207915,Engine RPM,1201859611,-8089365359392949673,max,3136.0 +49,1374207915,rpm_1s,1201859611,-8089365359392949673,min,767.0 +49,1374207915,rpm_1s,1201859611,-8089365359392949673,mean,1588.1983050075885 +49,1374207915,rpm_1s,1201859611,-8089365359392949673,max,3136.0 +22,430930200,coolant_1s,1201859611,1702604805711203895,min,40.0 +22,430930200,coolant_1s,1201859611,1702604805711203895,mean,86.71665559683699 +22,430930200,coolant_1s,1201859611,1702604805711203895,max,93.0 +22,430930200,speed_1s_diff,1201859611,1702604805711203895,min,-14.0 +22,430930200,speed_1s_diff,1201859611,1702604805711203895,mean,0.0 +22,430930200,speed_1s_diff,1201859611,1702604805711203895,max,18.0 +76,430930200,coolant_1s,1201859611,1604686477683752455,min,11.0 +76,430930200,coolant_1s,1201859611,1604686477683752455,mean,57.1490059364652 +76,430930200,coolant_1s,1201859611,1604686477683752455,max,84.0 +76,430930200,speed_1s_diff,1201859611,1604686477683752455,min,-11.0 +76,430930200,speed_1s_diff,1201859611,1604686477683752455,mean,0.0 +76,430930200,speed_1s_diff,1201859611,1604686477683752455,max,12.0 +22,1374207915,Engine RPM,1201859611,1702604805711203895,min,0.0 +22,1374207915,Engine RPM,1201859611,1702604805711203895,mean,1585.0838331225893 +22,1374207915,Engine RPM,1201859611,1702604805711203895,max,3563.0 +22,1374207915,rpm_1s,1201859611,1702604805711203895,min,0.0 +22,1374207915,rpm_1s,1201859611,1702604805711203895,mean,1584.5281453411126 +22,1374207915,rpm_1s,1201859611,1702604805711203895,max,3563.0 +76,1374207915,Engine RPM,1201859611,1604686477683752455,min,0.0 +76,1374207915,Engine RPM,1201859611,1604686477683752455,mean,1273.3780449260523 +76,1374207915,Engine RPM,1201859611,1604686477683752455,max,2590.0 +76,1374207915,rpm_1s,1201859611,1604686477683752455,min,0.0 +76,1374207915,rpm_1s,1201859611,1604686477683752455,mean,1273.3005575325442 +76,1374207915,rpm_1s,1201859611,1604686477683752455,max,2590.0 +16,430930200,coolant_1s,1201859611,-5204063793680029428,min,12.0 +16,430930200,coolant_1s,1201859611,-5204063793680029428,mean,79.50146401315618 +16,430930200,coolant_1s,1201859611,-5204063793680029428,max,94.0 +16,430930200,speed_1s_diff,1201859611,-5204063793680029428,min,-17.0 +16,430930200,speed_1s_diff,1201859611,-5204063793680029428,mean,0.0 +16,430930200,speed_1s_diff,1201859611,-5204063793680029428,max,14.0 +70,430930200,coolant_1s,1201859611,382192189422054743,min,25.0 +70,430930200,coolant_1s,1201859611,382192189422054743,mean,72.15030700325742 +70,430930200,coolant_1s,1201859611,382192189422054743,max,94.0 +70,430930200,speed_1s_diff,1201859611,382192189422054743,min,-14.0 +70,430930200,speed_1s_diff,1201859611,382192189422054743,mean,0.0 +70,430930200,speed_1s_diff,1201859611,382192189422054743,max,13.0 +16,1374207915,Engine RPM,1201859611,-5204063793680029428,min,0.0 +16,1374207915,Engine RPM,1201859611,-5204063793680029428,mean,1755.3705995771268 +16,1374207915,Engine RPM,1201859611,-5204063793680029428,max,3177.0 +16,1374207915,rpm_1s,1201859611,-5204063793680029428,min,0.0 +16,1374207915,rpm_1s,1201859611,-5204063793680029428,mean,1754.9468463514531 +16,1374207915,rpm_1s,1201859611,-5204063793680029428,max,3177.0 +70,1374207915,Engine RPM,1201859611,382192189422054743,min,0.0 +70,1374207915,Engine RPM,1201859611,382192189422054743,mean,1159.3155368583996 +70,1374207915,Engine RPM,1201859611,382192189422054743,max,2534.0 +70,1374207915,rpm_1s,1201859611,382192189422054743,min,0.0 +70,1374207915,rpm_1s,1201859611,382192189422054743,mean,1156.745666899119 +70,1374207915,rpm_1s,1201859611,382192189422054743,max,2534.0 +4,430930200,coolant_1s,1201859611,3413059497672580978,min,12.0 +4,430930200,coolant_1s,1201859611,3413059497672580978,mean,84.76381528899793 +4,430930200,coolant_1s,1201859611,3413059497672580978,max,95.0 +4,430930200,speed_1s_diff,1201859611,3413059497672580978,min,-15.0 +4,430930200,speed_1s_diff,1201859611,3413059497672580978,mean,0.0 +4,430930200,speed_1s_diff,1201859611,3413059497672580978,max,16.0 +8,430930200,coolant_1s,1201859611,-6047692687641618500,min,65.0 +8,430930200,coolant_1s,1201859611,-6047692687641618500,mean,86.09475987773212 +8,430930200,coolant_1s,1201859611,-6047692687641618500,max,93.0 +8,430930200,speed_1s_diff,1201859611,-6047692687641618500,min,-15.0 +8,430930200,speed_1s_diff,1201859611,-6047692687641618500,mean,0.0 +8,430930200,speed_1s_diff,1201859611,-6047692687641618500,max,13.0 +4,1374207915,Engine RPM,1201859611,3413059497672580978,min,0.0 +4,1374207915,Engine RPM,1201859611,3413059497672580978,mean,1526.1111291013062 +4,1374207915,Engine RPM,1201859611,3413059497672580978,max,3347.0 +4,1374207915,rpm_1s,1201859611,3413059497672580978,min,0.0 +4,1374207915,rpm_1s,1201859611,3413059497672580978,mean,1526.3905646579901 +4,1374207915,rpm_1s,1201859611,3413059497672580978,max,3347.0 +8,1374207915,Engine RPM,1201859611,-6047692687641618500,min,0.0 +8,1374207915,Engine RPM,1201859611,-6047692687641618500,mean,1569.5588696551204 +8,1374207915,Engine RPM,1201859611,-6047692687641618500,max,3385.0 +8,1374207915,rpm_1s,1201859611,-6047692687641618500,min,0.0 +8,1374207915,rpm_1s,1201859611,-6047692687641618500,mean,1570.0512537118273 +8,1374207915,rpm_1s,1201859611,-6047692687641618500,max,3385.0 +20,430930200,coolant_1s,1201859611,3772502504273691038,min,30.0 +20,430930200,coolant_1s,1201859611,3772502504273691038,mean,84.58550302593561 +20,430930200,coolant_1s,1201859611,3772502504273691038,max,94.0 +20,430930200,speed_1s_diff,1201859611,3772502504273691038,min,-17.0 +20,430930200,speed_1s_diff,1201859611,3772502504273691038,mean,0.0 +20,430930200,speed_1s_diff,1201859611,3772502504273691038,max,14.0 +20,1374207915,Engine RPM,1201859611,3772502504273691038,min,0.0 +20,1374207915,Engine RPM,1201859611,3772502504273691038,mean,1415.7485190357797 +20,1374207915,Engine RPM,1201859611,3772502504273691038,max,2556.0 +20,1374207915,rpm_1s,1201859611,3772502504273691038,min,0.0 +20,1374207915,rpm_1s,1201859611,3772502504273691038,mean,1414.2253118880474 +20,1374207915,rpm_1s,1201859611,3772502504273691038,max,2556.0 +44,430930200,coolant_1s,1201859611,819277571598892579,min,31.0 +44,430930200,coolant_1s,1201859611,819277571598892579,mean,85.31638420615018 +44,430930200,coolant_1s,1201859611,819277571598892579,max,97.0 +44,430930200,speed_1s_diff,1201859611,819277571598892579,min,-19.0 +44,430930200,speed_1s_diff,1201859611,819277571598892579,mean,0.0 +44,430930200,speed_1s_diff,1201859611,819277571598892579,max,17.0 +44,1374207915,Engine RPM,1201859611,819277571598892579,min,0.0 +44,1374207915,Engine RPM,1201859611,819277571598892579,mean,1664.4286443199032 +44,1374207915,Engine RPM,1201859611,819277571598892579,max,3328.0 +44,1374207915,rpm_1s,1201859611,819277571598892579,min,0.0 +44,1374207915,rpm_1s,1201859611,819277571598892579,mean,1665.364066064984 +44,1374207915,rpm_1s,1201859611,819277571598892579,max,3328.0 +30,430930200,coolant_1s,1201859611,-4509912318961261997,min,34.0 +30,430930200,coolant_1s,1201859611,-4509912318961261997,mean,83.29358140675642 +30,430930200,coolant_1s,1201859611,-4509912318961261997,max,94.0 +30,430930200,speed_1s_diff,1201859611,-4509912318961261997,min,-12.0 +30,430930200,speed_1s_diff,1201859611,-4509912318961261997,mean,0.0 +30,430930200,speed_1s_diff,1201859611,-4509912318961261997,max,17.0 +50,430930200,coolant_1s,1201859611,4815143990666075450,min,12.0 +50,430930200,coolant_1s,1201859611,4815143990666075450,mean,79.43062367363801 +50,430930200,coolant_1s,1201859611,4815143990666075450,max,94.0 +50,430930200,speed_1s_diff,1201859611,4815143990666075450,min,-17.0 +50,430930200,speed_1s_diff,1201859611,4815143990666075450,mean,0.0 +50,430930200,speed_1s_diff,1201859611,4815143990666075450,max,22.0 +80,430930200,coolant_1s,1201859611,-8519964041684473957,min,16.0 +80,430930200,coolant_1s,1201859611,-8519964041684473957,mean,62.131615743914985 +80,430930200,coolant_1s,1201859611,-8519964041684473957,max,91.0 +80,430930200,speed_1s_diff,1201859611,-8519964041684473957,min,-19.0 +80,430930200,speed_1s_diff,1201859611,-8519964041684473957,mean,0.0 +80,430930200,speed_1s_diff,1201859611,-8519964041684473957,max,16.0 +30,1374207915,Engine RPM,1201859611,-4509912318961261997,min,0.0 +30,1374207915,Engine RPM,1201859611,-4509912318961261997,mean,1467.0242777097128 +30,1374207915,Engine RPM,1201859611,-4509912318961261997,max,2775.0 +30,1374207915,rpm_1s,1201859611,-4509912318961261997,min,0.0 +30,1374207915,rpm_1s,1201859611,-4509912318961261997,mean,1466.814784883101 +30,1374207915,rpm_1s,1201859611,-4509912318961261997,max,2775.0 +50,1374207915,Engine RPM,1201859611,4815143990666075450,min,259.0 +50,1374207915,Engine RPM,1201859611,4815143990666075450,mean,1558.7114812813386 +50,1374207915,Engine RPM,1201859611,4815143990666075450,max,2820.0 +50,1374207915,rpm_1s,1201859611,4815143990666075450,min,259.0 +50,1374207915,rpm_1s,1201859611,4815143990666075450,mean,1558.8581966952029 +50,1374207915,rpm_1s,1201859611,4815143990666075450,max,2820.0 +80,1374207915,Engine RPM,1201859611,-8519964041684473957,min,761.0 +80,1374207915,Engine RPM,1201859611,-8519964041684473957,mean,1354.3557869387535 +80,1374207915,Engine RPM,1201859611,-8519964041684473957,max,2785.0 +80,1374207915,rpm_1s,1201859611,-8519964041684473957,min,761.0 +80,1374207915,rpm_1s,1201859611,-8519964041684473957,mean,1354.5041639900755 +80,1374207915,rpm_1s,1201859611,-8519964041684473957,max,2785.0 +33,430930200,coolant_1s,1201859611,-6056317502954263346,min,75.0 +33,430930200,coolant_1s,1201859611,-6056317502954263346,mean,90.50106921824994 +33,430930200,coolant_1s,1201859611,-6056317502954263346,max,103.0 +33,430930200,speed_1s_diff,1201859611,-6056317502954263346,min,-12.0 +33,430930200,speed_1s_diff,1201859611,-6056317502954263346,mean,-0.0037190205587934637 +33,430930200,speed_1s_diff,1201859611,-6056317502954263346,max,13.0 +33,1374207915,Engine RPM,1201859611,-6056317502954263346,min,0.0 +33,1374207915,Engine RPM,1201859611,-6056317502954263346,mean,1602.7588354916938 +33,1374207915,Engine RPM,1201859611,-6056317502954263346,max,3200.0 +33,1374207915,rpm_1s,1201859611,-6056317502954263346,min,0.0 +33,1374207915,rpm_1s,1201859611,-6056317502954263346,mean,1602.8919513063895 +33,1374207915,rpm_1s,1201859611,-6056317502954263346,max,3200.0 +10,430930200,coolant_1s,1201859611,799052670452099784,min,8.0 +10,430930200,coolant_1s,1201859611,799052670452099784,mean,74.14057927361539 +10,430930200,coolant_1s,1201859611,799052670452099784,max,93.0 +10,430930200,speed_1s_diff,1201859611,799052670452099784,min,-19.0 +10,430930200,speed_1s_diff,1201859611,799052670452099784,mean,0.0 +10,430930200,speed_1s_diff,1201859611,799052670452099784,max,16.0 +10,1374207915,Engine RPM,1201859611,799052670452099784,min,0.0 +10,1374207915,Engine RPM,1201859611,799052670452099784,mean,1314.130454751977 +10,1374207915,Engine RPM,1201859611,799052670452099784,max,2545.0 +10,1374207915,rpm_1s,1201859611,799052670452099784,min,0.0 +10,1374207915,rpm_1s,1201859611,799052670452099784,mean,1313.5844632662936 +10,1374207915,rpm_1s,1201859611,799052670452099784,max,2545.0 +40,430930200,coolant_1s,1201859611,8695525273439978119,min,29.0 +40,430930200,coolant_1s,1201859611,8695525273439978119,mean,84.2353432147225 +40,430930200,coolant_1s,1201859611,8695525273439978119,max,94.0 +40,430930200,speed_1s_diff,1201859611,8695525273439978119,min,-14.0 +40,430930200,speed_1s_diff,1201859611,8695525273439978119,mean,0.0 +40,430930200,speed_1s_diff,1201859611,8695525273439978119,max,15.0 +46,430930200,coolant_1s,1201859611,-6047084444995931845,min,11.0 +46,430930200,coolant_1s,1201859611,-6047084444995931845,mean,77.08755838797703 +46,430930200,coolant_1s,1201859611,-6047084444995931845,max,94.0 +46,430930200,speed_1s_diff,1201859611,-6047084444995931845,min,-16.0 +46,430930200,speed_1s_diff,1201859611,-6047084444995931845,mean,0.0 +46,430930200,speed_1s_diff,1201859611,-6047084444995931845,max,16.0 +40,1374207915,Engine RPM,1201859611,8695525273439978119,min,0.0 +40,1374207915,Engine RPM,1201859611,8695525273439978119,mean,1467.4526332701093 +40,1374207915,Engine RPM,1201859611,8695525273439978119,max,2880.0 +40,1374207915,rpm_1s,1201859611,8695525273439978119,min,0.0 +40,1374207915,rpm_1s,1201859611,8695525273439978119,mean,1466.7077663342445 +40,1374207915,rpm_1s,1201859611,8695525273439978119,max,2880.0 +46,1374207915,Engine RPM,1201859611,-6047084444995931845,min,0.0 +46,1374207915,Engine RPM,1201859611,-6047084444995931845,mean,1544.2393300147194 +46,1374207915,Engine RPM,1201859611,-6047084444995931845,max,2886.0 +46,1374207915,rpm_1s,1201859611,-6047084444995931845,min,0.0 +46,1374207915,rpm_1s,1201859611,-6047084444995931845,mean,1544.5573050577018 +46,1374207915,rpm_1s,1201859611,-6047084444995931845,max,2886.0 +53,430930200,coolant_1s,1201859611,-8118175245509467954,min,40.0 +53,430930200,coolant_1s,1201859611,-8118175245509467954,mean,80.60631342221673 +53,430930200,coolant_1s,1201859611,-8118175245509467954,max,94.0 +53,430930200,speed_1s_diff,1201859611,-8118175245509467954,min,-33.0 +53,430930200,speed_1s_diff,1201859611,-8118175245509467954,mean,0.0 +53,430930200,speed_1s_diff,1201859611,-8118175245509467954,max,43.0 +53,1374207915,Engine RPM,1201859611,-8118175245509467954,min,0.0 +53,1374207915,Engine RPM,1201859611,-8118175245509467954,mean,1498.354045653113 +53,1374207915,Engine RPM,1201859611,-8118175245509467954,max,2616.0 +53,1374207915,rpm_1s,1201859611,-8118175245509467954,min,0.0 +53,1374207915,rpm_1s,1201859611,-8118175245509467954,mean,1498.9662048460405 +53,1374207915,rpm_1s,1201859611,-8118175245509467954,max,2616.0 +34,430930200,coolant_1s,1201859611,-5864392879735263686,min,14.0 +34,430930200,coolant_1s,1201859611,-5864392879735263686,mean,74.21999745200145 +34,430930200,coolant_1s,1201859611,-5864392879735263686,max,94.0 +34,430930200,speed_1s_diff,1201859611,-5864392879735263686,min,-15.0 +34,430930200,speed_1s_diff,1201859611,-5864392879735263686,mean,0.0 +34,430930200,speed_1s_diff,1201859611,-5864392879735263686,max,17.0 +34,1374207915,Engine RPM,1201859611,-5864392879735263686,min,0.0 +34,1374207915,Engine RPM,1201859611,-5864392879735263686,mean,1384.0831067538634 +34,1374207915,Engine RPM,1201859611,-5864392879735263686,max,2975.0 +34,1374207915,rpm_1s,1201859611,-5864392879735263686,min,0.0 +34,1374207915,rpm_1s,1201859611,-5864392879735263686,mean,1384.2135636709313 +34,1374207915,rpm_1s,1201859611,-5864392879735263686,max,2975.0 +17,430930200,coolant_1s,1201859611,1920899555557485333,min,29.0 +17,430930200,coolant_1s,1201859611,1920899555557485333,mean,84.91004600337592 +17,430930200,coolant_1s,1201859611,1920899555557485333,max,94.0 +17,430930200,speed_1s_diff,1201859611,1920899555557485333,min,-13.0 +17,430930200,speed_1s_diff,1201859611,1920899555557485333,mean,0.0 +17,430930200,speed_1s_diff,1201859611,1920899555557485333,max,16.0 +17,1374207915,Engine RPM,1201859611,1920899555557485333,min,0.0 +17,1374207915,Engine RPM,1201859611,1920899555557485333,mean,1377.4672276716858 +17,1374207915,Engine RPM,1201859611,1920899555557485333,max,2689.0 +17,1374207915,rpm_1s,1201859611,1920899555557485333,min,0.0 +17,1374207915,rpm_1s,1201859611,1920899555557485333,mean,1377.5334634684446 +17,1374207915,rpm_1s,1201859611,1920899555557485333,max,2689.0 +11,430930200,coolant_1s,1201859611,-5926799641916171529,min,30.0 +11,430930200,coolant_1s,1201859611,-5926799641916171529,mean,86.09463730157664 +11,430930200,coolant_1s,1201859611,-5926799641916171529,max,93.0 +11,430930200,speed_1s_diff,1201859611,-5926799641916171529,min,-17.0 +11,430930200,speed_1s_diff,1201859611,-5926799641916171529,mean,0.0 +11,430930200,speed_1s_diff,1201859611,-5926799641916171529,max,24.0 +11,1374207915,Engine RPM,1201859611,-5926799641916171529,min,0.0 +11,1374207915,Engine RPM,1201859611,-5926799641916171529,mean,1487.563622831777 +11,1374207915,Engine RPM,1201859611,-5926799641916171529,max,3096.0 +11,1374207915,rpm_1s,1201859611,-5926799641916171529,min,0.0 +11,1374207915,rpm_1s,1201859611,-5926799641916171529,mean,1488.538408599783 +11,1374207915,rpm_1s,1201859611,-5926799641916171529,max,3096.0 +12,430930200,coolant_1s,1201859611,621520344043114077,min,8.0 +12,430930200,coolant_1s,1201859611,621520344043114077,mean,82.9118961763229 +12,430930200,coolant_1s,1201859611,621520344043114077,max,95.0 +12,430930200,speed_1s_diff,1201859611,621520344043114077,min,-106.0 +12,430930200,speed_1s_diff,1201859611,621520344043114077,mean,0.0 +12,430930200,speed_1s_diff,1201859611,621520344043114077,max,16.0 +12,1374207915,Engine RPM,1201859611,621520344043114077,min,0.0 +12,1374207915,Engine RPM,1201859611,621520344043114077,mean,1735.6059370257688 +12,1374207915,Engine RPM,1201859611,621520344043114077,max,3272.0 +12,1374207915,rpm_1s,1201859611,621520344043114077,min,0.0 +12,1374207915,rpm_1s,1201859611,621520344043114077,mean,1735.2661037425933 +12,1374207915,rpm_1s,1201859611,621520344043114077,max,3272.0 +81,430930200,coolant_1s,1201859611,-3039017337926577879,min,22.0 +81,430930200,coolant_1s,1201859611,-3039017337926577879,mean,53.439409149490345 +81,430930200,coolant_1s,1201859611,-3039017337926577879,max,79.0 +81,430930200,speed_1s_diff,1201859611,-3039017337926577879,min,-26.0 +81,430930200,speed_1s_diff,1201859611,-3039017337926577879,mean,0.04375876884315798 +81,430930200,speed_1s_diff,1201859611,-3039017337926577879,max,11.0 +81,1374207915,Engine RPM,1201859611,-3039017337926577879,min,0.0 +81,1374207915,Engine RPM,1201859611,-3039017337926577879,mean,1418.3154289942524 +81,1374207915,Engine RPM,1201859611,-3039017337926577879,max,2562.0 +81,1374207915,rpm_1s,1201859611,-3039017337926577879,min,0.0 +81,1374207915,rpm_1s,1201859611,-3039017337926577879,mean,1418.4741891692327 +81,1374207915,rpm_1s,1201859611,-3039017337926577879,max,2551.0 +25,430930200,coolant_1s,1201859611,-5797038905919203719,min,33.0 +25,430930200,coolant_1s,1201859611,-5797038905919203719,mean,85.00035794413705 +25,430930200,coolant_1s,1201859611,-5797038905919203719,max,94.0 +25,430930200,speed_1s_diff,1201859611,-5797038905919203719,min,-20.0 +25,430930200,speed_1s_diff,1201859611,-5797038905919203719,mean,0.0 +25,430930200,speed_1s_diff,1201859611,-5797038905919203719,max,12.0 +25,1374207915,Engine RPM,1201859611,-5797038905919203719,min,0.0 +25,1374207915,Engine RPM,1201859611,-5797038905919203719,mean,1410.768188471251 +25,1374207915,Engine RPM,1201859611,-5797038905919203719,max,2472.0 +25,1374207915,rpm_1s,1201859611,-5797038905919203719,min,0.0 +25,1374207915,rpm_1s,1201859611,-5797038905919203719,mean,1409.4206712007287 +25,1374207915,rpm_1s,1201859611,-5797038905919203719,max,2472.0 +9,430930200,coolant_1s,1201859611,7816983797231647252,min,42.0 +9,430930200,coolant_1s,1201859611,7816983797231647252,mean,88.33248425676416 +9,430930200,coolant_1s,1201859611,7816983797231647252,max,96.0 +9,430930200,speed_1s_diff,1201859611,7816983797231647252,min,-12.0 +9,430930200,speed_1s_diff,1201859611,7816983797231647252,mean,0.0 +9,430930200,speed_1s_diff,1201859611,7816983797231647252,max,9.0 +9,1374207915,Engine RPM,1201859611,7816983797231647252,min,0.0 +9,1374207915,Engine RPM,1201859611,7816983797231647252,mean,1632.3920343462057 +9,1374207915,Engine RPM,1201859611,7816983797231647252,max,3220.0 +9,1374207915,rpm_1s,1201859611,7816983797231647252,min,0.0 +9,1374207915,rpm_1s,1201859611,7816983797231647252,mean,1631.8401591437218 +9,1374207915,rpm_1s,1201859611,7816983797231647252,max,3220.0 +38,430930200,coolant_1s,1201859611,8006099507947146869,min,9.0 +38,430930200,coolant_1s,1201859611,8006099507947146869,mean,77.4673975336255 +38,430930200,coolant_1s,1201859611,8006099507947146869,max,95.0 +38,430930200,speed_1s_diff,1201859611,8006099507947146869,min,-13.0 +38,430930200,speed_1s_diff,1201859611,8006099507947146869,mean,0.0 +38,430930200,speed_1s_diff,1201859611,8006099507947146869,max,14.0 +38,1374207915,Engine RPM,1201859611,8006099507947146869,min,0.0 +38,1374207915,Engine RPM,1201859611,8006099507947146869,mean,1514.527505517681 +38,1374207915,Engine RPM,1201859611,8006099507947146869,max,2737.0 +38,1374207915,rpm_1s,1201859611,8006099507947146869,min,0.0 +38,1374207915,rpm_1s,1201859611,8006099507947146869,mean,1514.4074666291426 +38,1374207915,rpm_1s,1201859611,8006099507947146869,max,2737.0 +14,430930200,coolant_1s,1201859611,2702796889550818087,min,31.0 +14,430930200,coolant_1s,1201859611,2702796889550818087,mean,85.51840970358967 +14,430930200,coolant_1s,1201859611,2702796889550818087,max,93.0 +14,430930200,speed_1s_diff,1201859611,2702796889550818087,min,-17.0 +14,430930200,speed_1s_diff,1201859611,2702796889550818087,mean,0.0 +14,430930200,speed_1s_diff,1201859611,2702796889550818087,max,22.0 +29,430930200,coolant_1s,1201859611,6556782270497265025,min,9.0 +29,430930200,coolant_1s,1201859611,6556782270497265025,mean,75.96046944173662 +29,430930200,coolant_1s,1201859611,6556782270497265025,max,95.0 +29,430930200,speed_1s_diff,1201859611,6556782270497265025,min,-16.0 +29,430930200,speed_1s_diff,1201859611,6556782270497265025,mean,0.0 +29,430930200,speed_1s_diff,1201859611,6556782270497265025,max,17.0 +14,1374207915,Engine RPM,1201859611,2702796889550818087,min,0.0 +14,1374207915,Engine RPM,1201859611,2702796889550818087,mean,1605.0366029386216 +14,1374207915,Engine RPM,1201859611,2702796889550818087,max,3406.0 +14,1374207915,rpm_1s,1201859611,2702796889550818087,min,0.0 +14,1374207915,rpm_1s,1201859611,2702796889550818087,mean,1605.6637174961218 +14,1374207915,rpm_1s,1201859611,2702796889550818087,max,3406.0 +29,1374207915,Engine RPM,1201859611,6556782270497265025,min,0.0 +29,1374207915,Engine RPM,1201859611,6556782270497265025,mean,1476.2099141015954 +29,1374207915,Engine RPM,1201859611,6556782270497265025,max,2787.0 +29,1374207915,rpm_1s,1201859611,6556782270497265025,min,0.0 +29,1374207915,rpm_1s,1201859611,6556782270497265025,mean,1476.2314320344917 +29,1374207915,rpm_1s,1201859611,6556782270497265025,max,2787.0 +61,430930200,coolant_1s,1201859611,-6074962741691029584,min,51.0 +61,430930200,coolant_1s,1201859611,-6074962741691029584,mean,87.06961921460437 +61,430930200,coolant_1s,1201859611,-6074962741691029584,max,95.0 +61,430930200,speed_1s_diff,1201859611,-6074962741691029584,min,-11.0 +61,430930200,speed_1s_diff,1201859611,-6074962741691029584,mean,8.636548144402064E-4 +61,430930200,speed_1s_diff,1201859611,-6074962741691029584,max,12.0 +68,430930200,coolant_1s,1201859611,-4052763906460934747,min,10.0 +68,430930200,coolant_1s,1201859611,-4052763906460934747,mean,52.49620163606516 +68,430930200,coolant_1s,1201859611,-4052763906460934747,max,74.0 +68,430930200,speed_1s_diff,1201859611,-4052763906460934747,min,-16.0 +68,430930200,speed_1s_diff,1201859611,-4052763906460934747,mean,0.0 +68,430930200,speed_1s_diff,1201859611,-4052763906460934747,max,13.0 +61,1374207915,Engine RPM,1201859611,-6074962741691029584,min,0.0 +61,1374207915,Engine RPM,1201859611,-6074962741691029584,mean,1618.4896166180724 +61,1374207915,Engine RPM,1201859611,-6074962741691029584,max,2544.0 +61,1374207915,rpm_1s,1201859611,-6074962741691029584,min,0.0 +61,1374207915,rpm_1s,1201859611,-6074962741691029584,mean,1618.8330209751616 +61,1374207915,rpm_1s,1201859611,-6074962741691029584,max,2544.0 +68,1374207915,Engine RPM,1201859611,-4052763906460934747,min,0.0 +68,1374207915,Engine RPM,1201859611,-4052763906460934747,mean,1295.0187685856379 +68,1374207915,Engine RPM,1201859611,-4052763906460934747,max,2818.0 +68,1374207915,rpm_1s,1201859611,-4052763906460934747,min,0.0 +68,1374207915,rpm_1s,1201859611,-4052763906460934747,mean,1292.8835138849922 +68,1374207915,rpm_1s,1201859611,-4052763906460934747,max,2594.0 +51,430930200,coolant_1s,1201859611,-8939688255858788198,min,31.0 +51,430930200,coolant_1s,1201859611,-8939688255858788198,mean,85.44686515384528 +51,430930200,coolant_1s,1201859611,-8939688255858788198,max,94.0 +51,430930200,speed_1s_diff,1201859611,-8939688255858788198,min,-20.0 +51,430930200,speed_1s_diff,1201859611,-8939688255858788198,mean,0.0 +51,430930200,speed_1s_diff,1201859611,-8939688255858788198,max,17.0 +51,1374207915,Engine RPM,1201859611,-8939688255858788198,min,0.0 +51,1374207915,Engine RPM,1201859611,-8939688255858788198,mean,1804.0610418580354 +51,1374207915,Engine RPM,1201859611,-8939688255858788198,max,3682.0 +51,1374207915,rpm_1s,1201859611,-8939688255858788198,min,0.0 +51,1374207915,rpm_1s,1201859611,-8939688255858788198,mean,1803.5733463865897 +51,1374207915,rpm_1s,1201859611,-8939688255858788198,max,3682.0 +23,430930200,coolant_1s,1201859611,1667169207552354980,min,26.0 +23,430930200,coolant_1s,1201859611,1667169207552354980,mean,85.88982616138162 +23,430930200,coolant_1s,1201859611,1667169207552354980,max,94.0 +23,430930200,speed_1s_diff,1201859611,1667169207552354980,min,-27.0 +23,430930200,speed_1s_diff,1201859611,1667169207552354980,mean,0.0 +23,430930200,speed_1s_diff,1201859611,1667169207552354980,max,16.0 +23,1374207915,Engine RPM,1201859611,1667169207552354980,min,0.0 +23,1374207915,Engine RPM,1201859611,1667169207552354980,mean,1646.6120441591759 +23,1374207915,Engine RPM,1201859611,1667169207552354980,max,3497.0 +23,1374207915,rpm_1s,1201859611,1667169207552354980,min,0.0 +23,1374207915,rpm_1s,1201859611,1667169207552354980,mean,1646.1574882712498 +23,1374207915,rpm_1s,1201859611,1667169207552354980,max,3497.0 +36,430930200,coolant_1s,1201859611,-4482700421175446431,min,5.0 +36,430930200,coolant_1s,1201859611,-4482700421175446431,mean,76.04038870429954 +36,430930200,coolant_1s,1201859611,-4482700421175446431,max,95.0 +36,430930200,speed_1s_diff,1201859611,-4482700421175446431,min,-18.0 +36,430930200,speed_1s_diff,1201859611,-4482700421175446431,mean,0.0 +36,430930200,speed_1s_diff,1201859611,-4482700421175446431,max,13.0 +36,1374207915,Engine RPM,1201859611,-4482700421175446431,min,0.0 +36,1374207915,Engine RPM,1201859611,-4482700421175446431,mean,1543.0496063472635 +36,1374207915,Engine RPM,1201859611,-4482700421175446431,max,2557.0 +36,1374207915,rpm_1s,1201859611,-4482700421175446431,min,0.0 +36,1374207915,rpm_1s,1201859611,-4482700421175446431,mean,1542.1204170821707 +36,1374207915,rpm_1s,1201859611,-4482700421175446431,max,2557.0 +60,430930200,coolant_1s,1201859611,1782504894750799124,min,87.0 +60,430930200,coolant_1s,1201859611,1782504894750799124,mean,89.50172041549492 +60,430930200,coolant_1s,1201859611,1782504894750799124,max,91.0 +60,430930200,speed_1s_diff,1201859611,1782504894750799124,min,-16.0 +60,430930200,speed_1s_diff,1201859611,1782504894750799124,mean,0.0 +60,430930200,speed_1s_diff,1201859611,1782504894750799124,max,16.0 +60,1374207915,Engine RPM,1201859611,1782504894750799124,min,353.0 +60,1374207915,Engine RPM,1201859611,1782504894750799124,mean,1471.711980551247 +60,1374207915,Engine RPM,1201859611,1782504894750799124,max,2923.0 +60,1374207915,rpm_1s,1201859611,1782504894750799124,min,353.0 +60,1374207915,rpm_1s,1201859611,1782504894750799124,mean,1472.4990132958542 +60,1374207915,rpm_1s,1201859611,1782504894750799124,max,2923.0 +18,430930200,coolant_1s,1201859611,7002524279394511806,min,17.0 +18,430930200,coolant_1s,1201859611,7002524279394511806,mean,80.69768318984516 +18,430930200,coolant_1s,1201859611,7002524279394511806,max,94.0 +18,430930200,speed_1s_diff,1201859611,7002524279394511806,min,-15.0 +18,430930200,speed_1s_diff,1201859611,7002524279394511806,mean,0.0 +18,430930200,speed_1s_diff,1201859611,7002524279394511806,max,16.0 +32,430930200,coolant_1s,1201859611,-5227152884695581212,min,13.0 +32,430930200,coolant_1s,1201859611,-5227152884695581212,mean,78.1569788861685 +32,430930200,coolant_1s,1201859611,-5227152884695581212,max,94.0 +32,430930200,speed_1s_diff,1201859611,-5227152884695581212,min,-16.0 +32,430930200,speed_1s_diff,1201859611,-5227152884695581212,mean,0.0 +32,430930200,speed_1s_diff,1201859611,-5227152884695581212,max,11.0 +18,1374207915,Engine RPM,1201859611,7002524279394511806,min,0.0 +18,1374207915,Engine RPM,1201859611,7002524279394511806,mean,1747.0170084475576 +18,1374207915,Engine RPM,1201859611,7002524279394511806,max,3359.0 +18,1374207915,rpm_1s,1201859611,7002524279394511806,min,0.0 +18,1374207915,rpm_1s,1201859611,7002524279394511806,mean,1746.8726163625854 +18,1374207915,rpm_1s,1201859611,7002524279394511806,max,3359.0 +32,1374207915,Engine RPM,1201859611,-5227152884695581212,min,0.0 +32,1374207915,Engine RPM,1201859611,-5227152884695581212,mean,1547.4348167386645 +32,1374207915,Engine RPM,1201859611,-5227152884695581212,max,2584.0 +32,1374207915,rpm_1s,1201859611,-5227152884695581212,min,0.0 +32,1374207915,rpm_1s,1201859611,-5227152884695581212,mean,1547.6998655206403 +32,1374207915,rpm_1s,1201859611,-5227152884695581212,max,2584.0 +42,430930200,coolant_1s,1201859611,-4744849287861989057,min,12.0 +42,430930200,coolant_1s,1201859611,-4744849287861989057,mean,76.7494228555472 +42,430930200,coolant_1s,1201859611,-4744849287861989057,max,94.0 +42,430930200,speed_1s_diff,1201859611,-4744849287861989057,min,-18.0 +42,430930200,speed_1s_diff,1201859611,-4744849287861989057,mean,0.0 +42,430930200,speed_1s_diff,1201859611,-4744849287861989057,max,18.0 +55,430930200,coolant_1s,1201859611,-7312033798960504347,min,34.0 +55,430930200,coolant_1s,1201859611,-7312033798960504347,mean,84.60425601861019 +55,430930200,coolant_1s,1201859611,-7312033798960504347,max,94.0 +55,430930200,speed_1s_diff,1201859611,-7312033798960504347,min,-16.0 +55,430930200,speed_1s_diff,1201859611,-7312033798960504347,mean,0.0 +55,430930200,speed_1s_diff,1201859611,-7312033798960504347,max,16.0 +42,1374207915,Engine RPM,1201859611,-4744849287861989057,min,0.0 +42,1374207915,Engine RPM,1201859611,-4744849287861989057,mean,1597.5412463324378 +42,1374207915,Engine RPM,1201859611,-4744849287861989057,max,3019.0 +42,1374207915,rpm_1s,1201859611,-4744849287861989057,min,0.0 +42,1374207915,rpm_1s,1201859611,-4744849287861989057,mean,1598.1903238423918 +42,1374207915,rpm_1s,1201859611,-4744849287861989057,max,3019.0 +55,1374207915,Engine RPM,1201859611,-7312033798960504347,min,0.0 +55,1374207915,Engine RPM,1201859611,-7312033798960504347,mean,1492.36312314672 +55,1374207915,Engine RPM,1201859611,-7312033798960504347,max,2472.0 +55,1374207915,rpm_1s,1201859611,-7312033798960504347,min,0.0 +55,1374207915,rpm_1s,1201859611,-7312033798960504347,mean,1491.5621365257193 +55,1374207915,rpm_1s,1201859611,-7312033798960504347,max,2472.0 +67,430930200,coolant_1s,1201859611,-2034556668057200826,min,27.0 +67,430930200,coolant_1s,1201859611,-2034556668057200826,mean,69.42530660544675 +67,430930200,coolant_1s,1201859611,-2034556668057200826,max,95.0 +67,430930200,speed_1s_diff,1201859611,-2034556668057200826,min,-12.0 +67,430930200,speed_1s_diff,1201859611,-2034556668057200826,mean,0.0 +67,430930200,speed_1s_diff,1201859611,-2034556668057200826,max,13.0 +69,430930200,coolant_1s,1201859611,5297223516241122749,min,90.0 +69,430930200,coolant_1s,1201859611,5297223516241122749,mean,90.62423930893844 +69,430930200,coolant_1s,1201859611,5297223516241122749,max,92.0 +69,430930200,speed_1s_diff,1201859611,5297223516241122749,min,-14.0 +69,430930200,speed_1s_diff,1201859611,5297223516241122749,mean,0.0 +69,430930200,speed_1s_diff,1201859611,5297223516241122749,max,13.0 +67,1374207915,Engine RPM,1201859611,-2034556668057200826,min,0.0 +67,1374207915,Engine RPM,1201859611,-2034556668057200826,mean,1114.4268945866636 +67,1374207915,Engine RPM,1201859611,-2034556668057200826,max,2616.0 +67,1374207915,rpm_1s,1201859611,-2034556668057200826,min,0.0 +67,1374207915,rpm_1s,1201859611,-2034556668057200826,mean,1114.4915454302397 +67,1374207915,rpm_1s,1201859611,-2034556668057200826,max,2616.0 +69,1374207915,Engine RPM,1201859611,5297223516241122749,min,0.0 +69,1374207915,Engine RPM,1201859611,5297223516241122749,mean,936.3941858379201 +69,1374207915,Engine RPM,1201859611,5297223516241122749,max,3109.0 +69,1374207915,rpm_1s,1201859611,5297223516241122749,min,0.0 +69,1374207915,rpm_1s,1201859611,5297223516241122749,mean,935.9267717744602 +69,1374207915,rpm_1s,1201859611,5297223516241122749,max,3109.0 +59,430930200,coolant_1s,1201859611,4526313175717176312,min,25.0 +59,430930200,coolant_1s,1201859611,4526313175717176312,mean,86.59994838482683 +59,430930200,coolant_1s,1201859611,4526313175717176312,max,101.0 +59,430930200,speed_1s_diff,1201859611,4526313175717176312,min,-33.0 +59,430930200,speed_1s_diff,1201859611,4526313175717176312,mean,0.0 +59,430930200,speed_1s_diff,1201859611,4526313175717176312,max,22.0 +59,1374207915,Engine RPM,1201859611,4526313175717176312,min,0.0 +59,1374207915,Engine RPM,1201859611,4526313175717176312,mean,1651.2476025409273 +59,1374207915,Engine RPM,1201859611,4526313175717176312,max,2868.0 +59,1374207915,rpm_1s,1201859611,4526313175717176312,min,0.0 +59,1374207915,rpm_1s,1201859611,4526313175717176312,mean,1650.9548130846458 +59,1374207915,rpm_1s,1201859611,4526313175717176312,max,2868.0 +26,430930200,coolant_1s,1201859611,8588031391057209555,min,14.0 +26,430930200,coolant_1s,1201859611,8588031391057209555,mean,80.88859170596483 +26,430930200,coolant_1s,1201859611,8588031391057209555,max,94.0 +26,430930200,speed_1s_diff,1201859611,8588031391057209555,min,-16.0 +26,430930200,speed_1s_diff,1201859611,8588031391057209555,mean,0.0 +26,430930200,speed_1s_diff,1201859611,8588031391057209555,max,17.0 +26,1374207915,Engine RPM,1201859611,8588031391057209555,min,0.0 +26,1374207915,Engine RPM,1201859611,8588031391057209555,mean,1426.7536469896415 +26,1374207915,Engine RPM,1201859611,8588031391057209555,max,3404.0 +26,1374207915,rpm_1s,1201859611,8588031391057209555,min,0.0 +26,1374207915,rpm_1s,1201859611,8588031391057209555,mean,1426.1991845250093 +26,1374207915,rpm_1s,1201859611,8588031391057209555,max,3404.0 +47,430930200,coolant_1s,1201859611,3287404548624903503,min,54.0 +47,430930200,coolant_1s,1201859611,3287404548624903503,mean,88.95273887062815 +47,430930200,coolant_1s,1201859611,3287404548624903503,max,94.0 +47,430930200,speed_1s_diff,1201859611,3287404548624903503,min,-15.0 +47,430930200,speed_1s_diff,1201859611,3287404548624903503,mean,0.0 +47,430930200,speed_1s_diff,1201859611,3287404548624903503,max,15.0 +74,430930200,coolant_1s,1201859611,-2523754915779770005,min,18.0 +74,430930200,coolant_1s,1201859611,-2523754915779770005,mean,59.68428026208551 +74,430930200,coolant_1s,1201859611,-2523754915779770005,max,95.0 +74,430930200,speed_1s_diff,1201859611,-2523754915779770005,min,-15.0 +74,430930200,speed_1s_diff,1201859611,-2523754915779770005,mean,0.0 +74,430930200,speed_1s_diff,1201859611,-2523754915779770005,max,13.0 +47,1374207915,Engine RPM,1201859611,3287404548624903503,min,0.0 +47,1374207915,Engine RPM,1201859611,3287404548624903503,mean,1507.952652274933 +47,1374207915,Engine RPM,1201859611,3287404548624903503,max,2910.0 +47,1374207915,rpm_1s,1201859611,3287404548624903503,min,0.0 +47,1374207915,rpm_1s,1201859611,3287404548624903503,mean,1507.281984084302 +47,1374207915,rpm_1s,1201859611,3287404548624903503,max,2910.0 +74,1374207915,Engine RPM,1201859611,-2523754915779770005,min,270.0 +74,1374207915,Engine RPM,1201859611,-2523754915779770005,mean,1330.4312155224095 +74,1374207915,Engine RPM,1201859611,-2523754915779770005,max,2400.0 +74,1374207915,rpm_1s,1201859611,-2523754915779770005,min,753.0 +74,1374207915,rpm_1s,1201859611,-2523754915779770005,mean,1331.2984095495528 +74,1374207915,rpm_1s,1201859611,-2523754915779770005,max,2400.0 +62,430930200,coolant_1s,1201859611,2045378005814092654,min,88.0 +62,430930200,coolant_1s,1201859611,2045378005814092654,mean,89.70193074767752 +62,430930200,coolant_1s,1201859611,2045378005814092654,max,91.0 +62,430930200,speed_1s_diff,1201859611,2045378005814092654,min,-17.0 +62,430930200,speed_1s_diff,1201859611,2045378005814092654,mean,-0.009263578225744286 +62,430930200,speed_1s_diff,1201859611,2045378005814092654,max,14.0 +62,1374207915,Engine RPM,1201859611,2045378005814092654,min,0.0 +62,1374207915,Engine RPM,1201859611,2045378005814092654,mean,1369.4898685597188 +62,1374207915,Engine RPM,1201859611,2045378005814092654,max,2902.0 +62,1374207915,rpm_1s,1201859611,2045378005814092654,min,0.0 +62,1374207915,rpm_1s,1201859611,2045378005814092654,mean,1369.6446403953091 +62,1374207915,rpm_1s,1201859611,2045378005814092654,max,2902.0 +31,430930200,coolant_1s,1201859611,27763026888340976,min,14.0 +31,430930200,coolant_1s,1201859611,27763026888340976,mean,79.9971604410171 +31,430930200,coolant_1s,1201859611,27763026888340976,max,94.0 +31,430930200,speed_1s_diff,1201859611,27763026888340976,min,-14.0 +31,430930200,speed_1s_diff,1201859611,27763026888340976,mean,0.0 +31,430930200,speed_1s_diff,1201859611,27763026888340976,max,17.0 +31,1374207915,Engine RPM,1201859611,27763026888340976,min,0.0 +31,1374207915,Engine RPM,1201859611,27763026888340976,mean,1450.6490898227182 +31,1374207915,Engine RPM,1201859611,27763026888340976,max,2665.0 +31,1374207915,rpm_1s,1201859611,27763026888340976,min,0.0 +31,1374207915,rpm_1s,1201859611,27763026888340976,mean,1450.1808124311776 +31,1374207915,rpm_1s,1201859611,27763026888340976,max,2665.0 +56,430930200,coolant_1s,1201859611,-1655462257548164667,min,62.0 +56,430930200,coolant_1s,1201859611,-1655462257548164667,mean,89.53733216897544 +56,430930200,coolant_1s,1201859611,-1655462257548164667,max,95.0 +56,430930200,speed_1s_diff,1201859611,-1655462257548164667,min,-13.0 +56,430930200,speed_1s_diff,1201859611,-1655462257548164667,mean,-0.017717034755572354 +56,430930200,speed_1s_diff,1201859611,-1655462257548164667,max,14.0 +56,1374207915,Engine RPM,1201859611,-1655462257548164667,min,0.0 +56,1374207915,Engine RPM,1201859611,-1655462257548164667,mean,1593.7827630223187 +56,1374207915,Engine RPM,1201859611,-1655462257548164667,max,2718.0 +56,1374207915,rpm_1s,1201859611,-1655462257548164667,min,0.0 +56,1374207915,rpm_1s,1201859611,-1655462257548164667,mean,1593.3649536052717 +56,1374207915,rpm_1s,1201859611,-1655462257548164667,max,2718.0 +73,430930200,coolant_1s,1201859611,4038311380879008479,min,4.0 +73,430930200,coolant_1s,1201859611,4038311380879008479,mean,46.54092278189158 +73,430930200,coolant_1s,1201859611,4038311380879008479,max,72.0 +73,430930200,speed_1s_diff,1201859611,4038311380879008479,min,-10.0 +73,430930200,speed_1s_diff,1201859611,4038311380879008479,mean,0.0 +73,430930200,speed_1s_diff,1201859611,4038311380879008479,max,14.0 +73,1374207915,Engine RPM,1201859611,4038311380879008479,min,0.0 +73,1374207915,Engine RPM,1201859611,4038311380879008479,mean,1448.0903827211644 +73,1374207915,Engine RPM,1201859611,4038311380879008479,max,2539.0 +73,1374207915,rpm_1s,1201859611,4038311380879008479,min,0.0 +73,1374207915,rpm_1s,1201859611,4038311380879008479,mean,1450.9755159680149 +73,1374207915,rpm_1s,1201859611,4038311380879008479,max,2539.0 +6,430930200,coolant_1s,1201859611,5601914189163822671,min,25.0 +6,430930200,coolant_1s,1201859611,5601914189163822671,mean,85.90773515201593 +6,430930200,coolant_1s,1201859611,5601914189163822671,max,94.0 +6,430930200,speed_1s_diff,1201859611,5601914189163822671,min,-18.0 +6,430930200,speed_1s_diff,1201859611,5601914189163822671,mean,0.0 +6,430930200,speed_1s_diff,1201859611,5601914189163822671,max,15.0 +78,430930200,coolant_1s,1201859611,-6903129361326617772,min,88.0 +78,430930200,coolant_1s,1201859611,-6903129361326617772,mean,89.44107063957185 +78,430930200,coolant_1s,1201859611,-6903129361326617772,max,91.0 +78,430930200,speed_1s_diff,1201859611,-6903129361326617772,min,-15.0 +78,430930200,speed_1s_diff,1201859611,-6903129361326617772,mean,0.0 +78,430930200,speed_1s_diff,1201859611,-6903129361326617772,max,19.0 +6,1374207915,Engine RPM,1201859611,5601914189163822671,min,0.0 +6,1374207915,Engine RPM,1201859611,5601914189163822671,mean,1605.4123428562618 +6,1374207915,Engine RPM,1201859611,5601914189163822671,max,3333.0 +6,1374207915,rpm_1s,1201859611,5601914189163822671,min,0.0 +6,1374207915,rpm_1s,1201859611,5601914189163822671,mean,1605.9475991382276 +6,1374207915,rpm_1s,1201859611,5601914189163822671,max,3333.0 +78,1374207915,Engine RPM,1201859611,-6903129361326617772,min,0.0 +78,1374207915,Engine RPM,1201859611,-6903129361326617772,mean,1503.6662265415732 +78,1374207915,Engine RPM,1201859611,-6903129361326617772,max,3632.0 +78,1374207915,rpm_1s,1201859611,-6903129361326617772,min,0.0 +78,1374207915,rpm_1s,1201859611,-6903129361326617772,mean,1500.6144988381545 +78,1374207915,rpm_1s,1201859611,-6903129361326617772,max,3632.0 +19,430930200,coolant_1s,1201859611,107421361993047462,min,16.0 +19,430930200,coolant_1s,1201859611,107421361993047462,mean,77.2196362542339 +19,430930200,coolant_1s,1201859611,107421361993047462,max,93.0 +19,430930200,speed_1s_diff,1201859611,107421361993047462,min,-11.0 +19,430930200,speed_1s_diff,1201859611,107421361993047462,mean,0.0 +19,430930200,speed_1s_diff,1201859611,107421361993047462,max,12.0 +48,430930200,coolant_1s,1201859611,2089968467991060156,min,37.0 +48,430930200,coolant_1s,1201859611,2089968467991060156,mean,85.42180610184076 +48,430930200,coolant_1s,1201859611,2089968467991060156,max,94.0 +48,430930200,speed_1s_diff,1201859611,2089968467991060156,min,-16.0 +48,430930200,speed_1s_diff,1201859611,2089968467991060156,mean,0.0 +48,430930200,speed_1s_diff,1201859611,2089968467991060156,max,17.0 +79,430930200,coolant_1s,1201859611,2185843916018887178,min,24.0 +79,430930200,coolant_1s,1201859611,2185843916018887178,mean,67.72829867036053 +79,430930200,coolant_1s,1201859611,2185843916018887178,max,95.0 +79,430930200,speed_1s_diff,1201859611,2185843916018887178,min,-12.0 +79,430930200,speed_1s_diff,1201859611,2185843916018887178,mean,0.0 +79,430930200,speed_1s_diff,1201859611,2185843916018887178,max,13.0 +19,1374207915,Engine RPM,1201859611,107421361993047462,min,0.0 +19,1374207915,Engine RPM,1201859611,107421361993047462,mean,1597.5680194141225 +19,1374207915,Engine RPM,1201859611,107421361993047462,max,2889.0 +19,1374207915,rpm_1s,1201859611,107421361993047462,min,0.0 +19,1374207915,rpm_1s,1201859611,107421361993047462,mean,1597.4993485791213 +19,1374207915,rpm_1s,1201859611,107421361993047462,max,2889.0 +48,1374207915,Engine RPM,1201859611,2089968467991060156,min,0.0 +48,1374207915,Engine RPM,1201859611,2089968467991060156,mean,1603.6036940271517 +48,1374207915,Engine RPM,1201859611,2089968467991060156,max,2721.0 +48,1374207915,rpm_1s,1201859611,2089968467991060156,min,0.0 +48,1374207915,rpm_1s,1201859611,2089968467991060156,mean,1603.127672977594 +48,1374207915,rpm_1s,1201859611,2089968467991060156,max,2721.0 +79,1374207915,Engine RPM,1201859611,2185843916018887178,min,0.0 +79,1374207915,Engine RPM,1201859611,2185843916018887178,mean,1261.5219812119237 +79,1374207915,Engine RPM,1201859611,2185843916018887178,max,2835.0 +79,1374207915,rpm_1s,1201859611,2185843916018887178,min,0.0 +79,1374207915,rpm_1s,1201859611,2185843916018887178,mean,1261.13286969096 +79,1374207915,rpm_1s,1201859611,2185843916018887178,max,2589.0 diff --git a/tests/impulse_query_engine/unit/analyze/query/optimizer/__init__.py b/tests/impulse_query_engine/unit/analyze/query/optimizer/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/impulse_query_engine/unit/analyze/query/optimizer/pipeline_helpers.py b/tests/impulse_query_engine/unit/analyze/query/optimizer/pipeline_helpers.py new file mode 100644 index 00000000..79709027 --- /dev/null +++ b/tests/impulse_query_engine/unit/analyze/query/optimizer/pipeline_helpers.py @@ -0,0 +1,222 @@ +"""Helpers for pushdown pipeline tests: a runner and test-only rules.""" + +import contextlib +from collections.abc import Callable +from unittest import mock + +import numpy as np +import pyspark.sql.functions as F +import pyspark.sql.types as T +from pyspark.sql import Column, DataFrame + +from impulse_query_engine.analyze.metadata.time_series_expression import ( + SeriesType, + TimeSeriesOp, + TimeSeriesSelector, +) +from impulse_query_engine.analyze.query.optimizer import ( + VARIANT_COL, + VARIANTS_COL, + Pushdown, + PushdownPlan, + PushdownRule, + SeriesContext, + apply_pushdowns, + pipeline, +) +from impulse_query_engine.analyze.query.solvers.solver_config import SolverConfig +from impulse_query_engine.model.series.sample_series import SampleSeries + +CONFIG = SolverConfig() + + +@contextlib.contextmanager +def chunk_rows(n: int | None): + """Run the pipeline with *n* rows per chunk (the default when ``None``).""" + if n is None: + yield + return + with mock.patch.object(pipeline, "CHUNK_ROWS", n): + yield + + +def channel_rows(spark, series_by_key, time_type=T.LongType()) -> tuple[DataFrame, DataFrame]: + """Channel data and the variants of each channel, as ``apply_pushdowns`` takes them. + + *series_by_key* maps ``(cid, ch, variant)`` to ``(tstarts, tends, values)``; all + variants of a channel have the same samples, as in a solve. + """ + by_channel: dict[tuple, tuple] = {} + for (cid, ch, vid), samples in series_by_key.items(): + known, vids = by_channel.setdefault((cid, ch), (samples, [])) + assert known is samples or known == samples, f"variants of {(cid, ch)} differ" + vids.append(vid) + schema = T.StructType( + [ + T.StructField("container_id", T.LongType()), + T.StructField("channel_id", T.LongType()), + T.StructField("tstart", time_type), + T.StructField("tend", time_type), + T.StructField("value", T.DoubleType()), + ] + ) + cast = int if isinstance(time_type, T.IntegralType) else float + + def to_time(t): + return None if t is None else cast(t) + + rows = [ + (cid, ch, to_time(ts), to_time(te), None if v is None else float(v)) + for (cid, ch), ((tstarts, tends, values), _) in by_channel.items() + for ts, te, v in zip(tstarts, tends, values, strict=True) + ] + variants = T.StructType( + [ + T.StructField("container_id", T.LongType()), + T.StructField("channel_id", T.LongType()), + T.StructField(VARIANTS_COL, T.ArrayType(T.IntegerType())), + ] + ) + return spark.createDataFrame(rows, schema=schema), spark.createDataFrame( + [(cid, ch, sorted(vids)) for (cid, ch), (_, vids) in by_channel.items()], variants + ) + + +def plan_for(pushdowns: dict[int, Pushdown]) -> PushdownPlan: + """A plan computing *pushdowns* as the given variants.""" + rules = tuple(dict.fromkeys(p.rule for p in pushdowns.values())) + return PushdownPlan( + rules=rules, + variant_ids={p.variant_key: vid for vid, p in pushdowns.items()}, + pushdowns_by_variant=dict(pushdowns), + ) + + +def run( + spark, + series_by_key, + pushdowns: dict[int, Pushdown], + time_type=T.LongType(), + chunk_size: int | None = None, + include_raw: bool = True, +) -> dict[tuple, SampleSeries]: + """Run *pushdowns* on the series; ``{(cid, ch, variant): SampleSeries}`` like the cache.""" + df, variants = channel_rows(spark, series_by_key, time_type) + with chunk_rows(chunk_size): + result = apply_pushdowns( + df, variants, plan_for(pushdowns), CONFIG, include_raw=include_raw + ) + expected = [*df.schema.fields, T.StructField(VARIANT_COL, T.IntegerType())] + assert [(f.name, f.dataType) for f in result.schema] == [ + (f.name, f.dataType) for f in expected + ] + pdf = result.toPandas() + out = {} + for key, group in pdf.groupby(["container_id", "channel_id", VARIANT_COL]): + group = group.sort_values(["tstart", "tend"], kind="stable") + out[tuple(int(k) for k in key)] = SampleSeries( + group["tstart"], group["tend"], group["value"].astype(np.float64) + ) + return out + + +def random_series(rng, n, nan_tend=False): + """Samples with gaps, zero-duration samples and NaN values, in series order. + + With *nan_tend*, some samples have a NaN ``tend`` (use a double time column). + """ + tstarts, tends, values = [], [], [] + t, after_point = 0, False + for _ in range(n): + if rng.random() < 0.15: + t += int(rng.integers(1, 6)) + duration = 0 if not after_point and rng.random() < 0.1 else int(rng.integers(1, 5)) + tstarts.append(t) + nan = nan_tend and duration > 0 and rng.random() < 0.1 + tends.append(np.nan if nan else t + duration) + values.append(np.nan if rng.random() < 0.1 else float(rng.integers(0, 4))) + t, after_point = t + duration, duration == 0 + return tstarts, tends, values + + +def _selector_op(node, operand, operation: str) -> TimeSeriesSelector | None: + """The continuous selector of ``selector.()``, or ``None``.""" + if ( + type(node) is not TimeSeriesOp + or node.optype != "cls" + or node.operation != operation + or len(node.args) != 1 + or node.kwargs + ): + return None + selector = operand(node.args[0]) + if not isinstance(selector, TimeSeriesSelector): + return None + return selector if selector.series_type is SeriesType.CONTINUOUS else None + + +class DiffRule(PushdownRule): + """Test-only ``channel.diff()``: the value change within a block, 0 at block starts.""" + + name = "diff" + + def match(self, node, operand): + selector = _selector_op(node, operand, "diff") + if selector is None: + return None + return Pushdown(rule=self, selector=selector, params=(), consumed=(selector,)) + + def row_columns(self, ctx: SeriesContext): + return {"diff": F.when(ctx.block.starts, F.lit(0.0)).otherwise(ctx.value - ctx.prev.value)} + + def emit(self, ctx: SeriesContext) -> Column: + return F.array(ctx.element(value=ctx.col("diff"))) + + +class OffsetRule(PushdownRule): + """Test-only per-sample rule adding its ``offset`` parameter to every value.""" + + name = "offset" + ordered = False + + def match(self, node, operand): + return None + + def emit(self, ctx: SeriesContext) -> Column: + return F.array(ctx.element(value=ctx.value + ctx.param("offset"))) + + +class ProbeRule(PushdownRule): + """Test-only rule emitting every sample with ``probe(ctx)`` as its value.""" + + match = OffsetRule.match + + def __init__( + self, + name: str, + probe: Callable[[SeriesContext], Column], + runs: dict | None = None, + ): + self.name = name + self.probe = probe + self._runs = runs or {} + + def runs(self): + return self._runs + + def emit(self, ctx: SeriesContext) -> Column: + return F.array(ctx.element(value=self.probe(ctx))) + + def pushdown(self, selector: TimeSeriesSelector) -> Pushdown: + return Pushdown(rule=self, selector=selector, params=(), consumed=(selector,)) + + +def top_level_items(text: str) -> list[str]: + """The comma-separated items of *text*, not splitting inside parentheses.""" + items, depth, start = [], 0, 0 + for i, char in enumerate(text): + depth += {"(": 1, ")": -1}.get(char, 0) + if char == "," and depth == 0: + items.append(text[start:i].strip()) + start = i + 1 + return [*items, text[start:].strip()] diff --git a/tests/impulse_query_engine/unit/analyze/query/optimizer/pipeline_test.py b/tests/impulse_query_engine/unit/analyze/query/optimizer/pipeline_test.py new file mode 100644 index 00000000..2fc7efa3 --- /dev/null +++ b/tests/impulse_query_engine/unit/analyze/query/optimizer/pipeline_test.py @@ -0,0 +1,412 @@ +# pylint: disable=missing-function-docstring +"""The pushdown pipeline's services match a straightforward per-series reference. + +Probe rules emit one service column per sample; the series are cut into chunks of +a few samples, so every service has to carry information across chunk boundaries. +""" + +import re + +import numpy as np +import numpy.testing as nptest +import pyspark.sql.functions as F +import pyspark.sql.types as T +import pytest + +from impulse_query_engine.analyze.metadata.tag_expression import TagSelector +from impulse_query_engine.analyze.metadata.time_series_expression import TimeSeriesSelector +from impulse_query_engine.analyze.query.optimizer import ( + VARIANT_COL, + VARIANTS_COL, + Classifier, + Pushdown, + SeriesPipeline, + apply_pushdowns, +) +from impulse_query_engine.model.series.sample_series import SampleSeries +from tests.conftest import spark +from tests.impulse_query_engine.unit.analyze.query.optimizer.pipeline_helpers import ( + CONFIG, + DiffRule, + OffsetRule, + ProbeRule, + channel_rows, + chunk_rows, + plan_for, + random_series, + run, + top_level_items, +) + +_X = TimeSeriesSelector(TagSelector("channel_name") == "x") + + +def _same_value(prev, cur) -> bool: + # Spark compares NaN as equal to NaN. + return prev == cur or (np.isnan(prev) and np.isnan(cur)) + + +def _value_change(prev, cur): + return prev.value.isNull() | (prev.value != cur.value) + + +def _runs(breaks: list[bool], tstarts: list) -> dict[str, list]: + ids = np.cumsum(breaks).tolist() + starts, start = [], None + for brk, ts in zip(breaks, tstarts, strict=True): + start = ts if brk else start + starts.append(start) + return { + "starts": [float(b) for b in breaks], + "id": ids, + "start": starts, + "next_starts": [float(b) for b in breaks[1:]] + [None], + "is_last": [float(i == ids[-1]) for i in ids], + } + + +def _reference(tstarts, tends, values) -> dict[str, list]: + """Every probed service for one series, computed sample by sample.""" + n = len(tstarts) + block_breaks = [i == 0 or tstarts[i] - tends[i - 1] != 0 for i in range(n)] + level_breaks = [i == 0 or not _same_value(values[i - 1], values[i]) for i in range(n)] + block = _runs(block_breaks, tstarts) + block_end = [] + for i in range(n): + members = [tends[j] for j in range(n) if block["id"][j] == block["id"][i]] + finite = [t for t in members if not np.isnan(t)] + block_end.append(max(finite) if finite else None) + return { + "prev_tstart": [None, *tstarts[:-1]], + "prev_tend": [None, *tends[:-1]], + "prev_value": [None, *values[:-1]], + "next_tstart": [*tstarts[1:], None], + "next_value": [*values[1:], None], + "n": [n] * n, + "last_tstart": [tstarts[-1]] * n, + "last_tend": [tends[-1]] * n, + **{f"block_{name}": column for name, column in block.items()}, + "block_end": block_end, + **{f"level_{name}": column for name, column in _runs(level_breaks, tstarts).items()}, + } + + +_PROBES = { + "prev_tstart": lambda ctx: ctx.prev.tstart, + "prev_tend": lambda ctx: ctx.prev.tend, + "prev_value": lambda ctx: ctx.prev.value, + "next_tstart": lambda ctx: ctx.next.tstart, + "next_value": lambda ctx: ctx.next.value, + "n": lambda ctx: ctx.n, + "last_tstart": lambda ctx: ctx.last.tstart, + "last_tend": lambda ctx: ctx.last.tend, + "block_starts": lambda ctx: ctx.block.starts, + "block_id": lambda ctx: ctx.block.id, + "block_start": lambda ctx: ctx.block.start, + "block_next_starts": lambda ctx: ctx.block.next_starts, + "block_is_last": lambda ctx: ctx.block.is_last, + "block_end": lambda ctx: ctx.block_end, + "level_starts": lambda ctx: ctx.run("level").starts, + "level_id": lambda ctx: ctx.run("level").id, + "level_start": lambda ctx: ctx.run("level").start, + "level_next_starts": lambda ctx: ctx.run("level").next_starts, + "level_is_last": lambda ctx: ctx.run("level").is_last, +} + + +def _probe_pushdowns() -> dict[int, Pushdown]: + probes = {} + for vid, (name, probe) in enumerate(_PROBES.items(), start=1): + runs = {"level": _value_change} if name.startswith("level_") else None + probes[vid] = ProbeRule(f"probe_{name}", probe, runs).pushdown(_X) + return probes + + +def _as_float(column: list) -> np.ndarray: + return np.array([np.nan if v is None else float(v) for v in column]) + + +@pytest.mark.parametrize( + ("time_type", "nan_tend"), [(T.LongType(), False), (T.DoubleType(), True)] +) +@pytest.mark.parametrize("chunk_size", [1, 2, 3, 7, None]) +def test_services_across_chunk_boundaries(spark, chunk_size, time_type, nan_tend): + rng = np.random.default_rng(17) + pushdowns = _probe_pushdowns() + series, expected = {}, {} + for cid, n in enumerate([1, 2, 5, 40, 90]): + samples = random_series(rng, n, nan_tend=nan_tend) + reference = _reference(*samples) + for vid, pushdown in pushdowns.items(): + series[(cid, 1, vid)] = samples + expected[(cid, 1, vid)] = ( + samples, + reference[pushdown.rule.name.removeprefix("probe_")], + ) + actual = run(spark, series, pushdowns, time_type=time_type, chunk_size=chunk_size) + assert actual.keys() == expected.keys() + for key, ((tstarts, tends, _), column) in expected.items(): + name = pushdowns[key[2]].rule.name + nptest.assert_array_equal(actual[key].tstarts, tstarts, err_msg=name) + nptest.assert_array_equal(actual[key].tends, tends, err_msg=name) + nptest.assert_array_equal(actual[key].values, _as_float(column), err_msg=name) + + +def test_diff_matches_sample_series(spark): + rng = np.random.default_rng(23) + series, expected = {}, {} + for cid, n in enumerate([1, 2, 30, 70]): + samples = random_series(rng, n) + series[(cid, 1, 1)] = samples + expected[(cid, 1, 1)] = SampleSeries(*samples).diff() + pushdowns = {1: Classifier([DiffRule()])(_X.diff())} + for chunk_size in (1, 3, None): + actual = run(spark, series, pushdowns, chunk_size=chunk_size) + for key, exp in expected.items(): + nptest.assert_array_equal(actual[key].tstarts, exp.tstarts) + nptest.assert_array_equal(actual[key].tends, exp.tends) + nptest.assert_array_equal(actual[key].values, exp.values) + + +_OFFSET = OffsetRule() + + +def _offset(value: float) -> Pushdown: + return Pushdown(rule=_OFFSET, selector=_X, params=(("offset", value),), consumed=(_X,)) + + +def test_per_sample_rules_share_the_row_branch_with_raw_rows(spark): + samples = ([0, 1, 5], [1, 2, 6], [1.0, 2.0, 4.0]) + series = {(1, 1, 0): samples, (1, 1, 1): samples, (1, 1, 2): samples, (2, 1, 3): samples} + pushdowns = {1: _offset(10.0), 2: _offset(0.5), 3: Classifier([DiffRule()])(_X.diff())} + actual = run(spark, series, pushdowns, chunk_size=2) + nptest.assert_array_equal(actual[(1, 1, 0)].values, [1.0, 2.0, 4.0]) + nptest.assert_array_equal(actual[(1, 1, 1)].values, [11.0, 12.0, 14.0]) + nptest.assert_array_equal(actual[(1, 1, 2)].values, [1.5, 2.5, 4.5]) + nptest.assert_array_equal(actual[(2, 1, 3)].values, [0.0, 1.0, 0.0]) + nptest.assert_array_equal(actual[(1, 1, 1)].tstarts, [0, 1, 5]) + + +def test_per_sample_rules_skip_the_series_scan(spark): + df, variants = channel_rows(spark, {(1, 1, 1): ([0, 1], [1, 2], [1.0, 2.0])}) + result = apply_pushdowns(df, variants, plan_for({1: _offset(1.0)}), CONFIG, include_raw=False) + plan = result._jdf.queryExecution().optimizedPlan().toString() + assert "_pd_chunk" not in plan + assert "Window" not in plan + assert "Aggregate" not in plan + + +def test_raw_rows_only_when_asked(spark): + samples = ([0, 1], [1, 2], [1.0, 2.0]) + series = {(1, 1, 0): samples, (1, 1, 1): samples} + pushdowns = {1: Classifier([DiffRule()])(_X.diff())} + assert set(run(spark, series, pushdowns)) == {(1, 1, 0), (1, 1, 1)} + assert set(run(spark, series, pushdowns, include_raw=False)) == {(1, 1, 1)} + + +def test_ordered_services_are_refused_to_per_sample_rules(spark): + class _Neighbourly(OffsetRule): + name = "neighbourly" + + def emit(self, ctx): + return F.array(ctx.element(value=ctx.prev.value)) + + df, variants = channel_rows(spark, {(1, 1, 1): ([0], [1], [1.0])}) + pushdown = Pushdown(rule=_Neighbourly(), selector=_X, params=(), consumed=(_X,)) + with pytest.raises(TypeError, match="needs an ordered rule"): + apply_pushdowns(df, variants, plan_for({1: pushdown}), CONFIG) + + +def test_block_end_is_only_available_to_kept_samples(spark): + rule = ProbeRule("early_block_end", lambda ctx: ctx.value) + rule.row_columns = lambda ctx: {"end": ctx.block_end} + df, variants = channel_rows(spark, {(1, 1, 1): ([0], [1], [1.0])}) + with pytest.raises(TypeError, match="kept samples"): + apply_pushdowns(df, variants, plan_for({1: rule.pushdown(_X)}), CONFIG) + + +def test_one_shuffle_of_the_samples(spark): + rng = np.random.default_rng(3) + series = {(cid, 1, 1): random_series(rng, 30) for cid in range(3)} + df, variants = channel_rows(spark, series) + plan = plan_for({1: Classifier([DiffRule()])(_X.diff())}) + result = apply_pushdowns(df, variants, plan, CONFIG, include_raw=False) + result.collect() + final = _final_plan(result) + exchanges = _shuffles(final) + key = "container_id, channel_id" + samples = [e for e in exchanges if "_pd_chunk" in e] + assert len(samples) == 1 and samples[0].startswith(f"hashpartitioning({key}, _pd_chunk") + # The others are the per-channel boundaries and chunk summaries. + assert all(e.startswith(f"hashpartitioning({key}") for e in exchanges) + # One full read, shared by the chunk pass and its summaries, and the boundaries. + assert len(_channel_data_scans(final)) == 2 + + +def test_chunks_scale_with_the_variants_of_a_channel(spark): + # Both variants of channel (1, 1) fan out per chunk, so it gets twice as many chunks + # to keep the rows per chunk; each of its samples enters the chunk shuffle once. + samples = (list(range(12)), list(range(1, 13)), [1.0] * 12) + df, variants = channel_rows( + spark, {(1, 1, 1): samples, (1, 1, 2): samples, (2, 1, 1): samples} + ) + diff, probe = DiffRule(), ProbeRule("probe_value", lambda ctx: ctx.value) + rules = [(diff, {1: Classifier([diff])(_X.diff())}), (probe, {2: probe.pushdown(_X)})] + with chunk_rows(4): + chunks = SeriesPipeline(rules, CONFIG)._chunked(df, variants) + per_channel = chunks.groupBy("container_id", "channel_id").agg( + F.countDistinct("_pd_chunk").alias("chunks"), F.count(F.lit(1)).alias("samples") + ) + counts = {(r[0], r[1]): (r["chunks"], r["samples"]) for r in per_channel.collect()} + assert counts == {(1, 1): (6, 12), (2, 1): (3, 12)} + + +def test_samples_are_shuffled_once_per_channel(spark): + samples = random_series(np.random.default_rng(11), 40) + # Channel (1, 1) ships two ordered variants, channel (2, 1) its raw rows. + series = {(1, 1, 1): samples, (1, 1, 2): samples, (2, 1, 0): samples} + df, variants = channel_rows(spark, series) + probe = ProbeRule("probe_value", lambda ctx: ctx.value) + plan = plan_for({1: Classifier([DiffRule()])(_X.diff()), 2: probe.pushdown(_X)}) + result = apply_pushdowns(df, variants, plan, CONFIG) + result.collect() + final = _final_plan(result) + # The variants fan out after the chunk shuffle, which no further shuffle follows. + assert all(VARIANT_COL not in e for e in _shuffles(final)) + lines = final.splitlines() + shuffle = next( + i for i, line in enumerate(lines) if re.search(r"- Exchange \w+\(.*_pd_chunk", line) + ) + assert not any("Generate" in line for line in _subtree(lines, shuffle)) + # One broadcast of the variants for the boundaries and the fan-out, one of their keys + # for the full read, one for the raw rows. + assert len([s for s in _scans(final) if VARIANTS_COL in s]) == 3 + + +def test_samples_and_summaries_see_the_same_chunk_shuffle(spark): + # The samples and their chunk summaries both read the chunk shuffle, which they share + # only if their plans below it are equal. Window inputs are computed before the + # shuffle and pruned per consumer, so the shuffle carries the raw columns only; the + # summaries are joined on guarded keys, so no runtime filter built from them can + # reach the samples' scan. + samples = random_series(np.random.default_rng(5), 30) + df, variants = channel_rows(spark, {(1, 1, 1): samples}) + plan = plan_for({1: Classifier([DiffRule()])(_X.diff())}) + result = apply_pushdowns(df, variants, plan, CONFIG, include_raw=False) + result.collect() + lines = _final_plan(result).splitlines() + shuffle = next( + i for i, line in enumerate(lines) if re.search(r"- Exchange \w+\(.*_pd_chunk", line) + ) + projection = re.search(r"Project \[(.*)\]$", lines[shuffle + 1]).group(1) + columns = [re.sub(r"#\d+L?", "", c.split(" AS ")[-1]) for c in top_level_items(projection)] + assert columns == ["container_id", "channel_id", "tstart", "tend", "value", "_pd_chunk"] + optimized = re.sub(r"#\d+L?", "", result._jdf.queryExecution().optimizedPlan().toString()) + for key in ("container_id", "channel_id", "_pd_chunk"): + guarded = f"CASE WHEN (SPARK_PARTITION_ID() >= 0) THEN {key} END AS _pd_join_{key}" + assert guarded in optimized + + +def test_raw_rows_are_joined_for_raw_channels_only(spark): + # The row branch broadcasts only the channels shipping raw rows, so the pushed + # channel's samples are dropped at the join instead of being exploded and filtered. + samples = random_series(np.random.default_rng(13), 20) + series = {(1, 1, 1): samples, (1, 1, 2): samples, (2, 1, 0): samples} + df, variants = channel_rows(spark, series) + plan = plan_for({1: Classifier([DiffRule()])(_X.diff()), 2: _offset(1.0)}) + result = apply_pushdowns(df, variants, plan, CONFIG) + optimized = re.sub(r"#\d+L?", "", result._jdf.queryExecution().optimizedPlan().toString()) + shipping = r"size\(filter\(__impulse_variants, lambdafunction\(lambda \w+ IN \(0,2\).*\) > 0" + assert re.search(shipping, optimized) + + +def _final_plan(df) -> str: + """The final physical plan of *df*, which must have been collected.""" + return df._jdf.queryExecution().executedPlan().toString().split("== Initial Plan ==")[0] + + +def _shuffles(final: str) -> list[str]: + """Partitionings of the shuffles (not broadcasts), each listed once even when reused.""" + return [re.sub(r"#\d+L?", "", e) for e in re.findall(r"(?<=- )Exchange (\w+\([^)]*\))", final)] + + +def _scans(final: str) -> list[str]: + """Output columns of each in-memory scan in the plan.""" + return re.findall(r"Scan ExistingRDD\[([^\]]*)\]", final) + + +def _channel_data_scans(final: str) -> list[str]: + """Scans of the channel data, not of the variants of each channel.""" + return [s for s in _scans(final) if VARIANTS_COL not in s] + + +def _subtree(lines: list[str], i: int) -> list[str]: + """The plan lines below the node on line *i*.""" + + def depth(line: str) -> int: + return len(line) - len(line.lstrip(" :+-")) + + below = [] + for line in lines[i + 1 :]: + if depth(line) <= depth(lines[i]): + break + below.append(line) + return below + + +def _values_by_tend(df) -> dict: + return {row["tend"]: row["value"] for row in df.collect()} + + +@pytest.mark.parametrize("chunk_size", [1, 2, None]) +def test_null_and_nan_tstarts_keep_their_place_in_the_series(spark, chunk_size): + # Spark orders a null tstart first and a NaN tstart last; their chunks must too. + nan = float("nan") + samples = ([None, 0.0, 1.0, 2.0, 3.0, 4.0, nan], [0.5, 1, 2, 3, 4, 5, 5.5], [1.0] * 7) + df, variants = channel_rows( + spark, {(1, 1, 1): samples, (1, 1, 2): samples}, time_type=T.DoubleType() + ) + prev = ProbeRule("prev_start", lambda ctx: ctx.prev.tstart).pushdown(_X) + nxt = ProbeRule("next_start", lambda ctx: ctx.next.tstart).pushdown(_X) + with chunk_rows(chunk_size): + result = apply_pushdowns( + df, variants, plan_for({1: prev, 2: nxt}), CONFIG, include_raw=False + ) + by_variant = {v: _values_by_tend(result.where(F.col(VARIANT_COL) == v)) for v in (1, 2)} + expected_prev = {0.5: None, 1.0: None, 2.0: 0.0, 3.0: 1.0, 4.0: 2.0, 5.0: 3.0, 5.5: 4.0} + expected_next = {0.5: 0.0, 1.0: 1.0, 2.0: 2.0, 3.0: 3.0, 4.0: 4.0, 5.0: nan, 5.5: None} + for actual, expected in ((by_variant[1], expected_prev), (by_variant[2], expected_next)): + assert actual.keys() == expected.keys() + nptest.assert_array_equal(_as_float(actual.values()), _as_float(expected.values())) + + +def test_series_without_a_finite_tend_is_still_chunked(spark): + nan = float("nan") + samples = ([0.0, 1.0, 2.0, 3.0, 4.0, 5.0], [nan] * 6, [1.0] * 6) + df, variants = channel_rows(spark, {(1, 1, 1): samples}, time_type=T.DoubleType()) + rule = DiffRule() + with chunk_rows(2): + pipeline = SeriesPipeline([(rule, {1: Classifier([rule])(_X.diff())})], CONFIG) + chunks = pipeline._chunked(df, variants) + assert chunks.select("_pd_chunk").distinct().count() == 3 + + +def test_rule_names_cannot_collide_with_pipeline_columns(spark): + # The rule column "ts" of a rule named "prev" once overwrote the pipeline's prev tstart. + rule = ProbeRule("prev", lambda ctx: ctx.prev.tstart) + rule.row_columns = lambda ctx: {"ts": ctx.value * 100} + samples = ([0, 1, 2], [1, 2, 3], [1.0, 2.0, 3.0]) + actual = run(spark, {(1, 1, 1): samples}, {1: rule.pushdown(_X)}, chunk_size=1) + nptest.assert_array_equal(actual[(1, 1, 1)].values, [np.nan, 0.0, 1.0]) + + +@pytest.mark.parametrize("hook", ["row_columns", "runs"]) +def test_invalid_rule_column_and_run_names_are_rejected(spark, hook): + rule = ProbeRule("bad_names", lambda ctx: ctx.value) + if hook == "row_columns": + rule.row_columns = lambda ctx: {"Bad__name": ctx.value} + else: + rule._runs = {"Bad__name": _value_change} + df, variants = channel_rows(spark, {(1, 1, 1): ([0], [1], [1.0])}) + with pytest.raises(ValueError, match="name 'Bad__name'"): + apply_pushdowns(df, variants, plan_for({1: rule.pushdown(_X)}), CONFIG) diff --git a/tests/impulse_query_engine/unit/analyze/query/optimizer/plan_test.py b/tests/impulse_query_engine/unit/analyze/query/optimizer/plan_test.py new file mode 100644 index 00000000..ad001174 --- /dev/null +++ b/tests/impulse_query_engine/unit/analyze/query/optimizer/plan_test.py @@ -0,0 +1,119 @@ +# pylint: disable=missing-function-docstring +"""PushdownPlan: channel variant tagging and native calculated-channel targets.""" + +import pyspark.sql.functions as F +import pyspark.sql.types as T +import pytest + +from impulse_query_engine.analyze.metadata.tag_expression import TagSelector +from impulse_query_engine.analyze.metadata.time_series_expression import TimeSeriesSelector +from impulse_query_engine.analyze.query.channels.calculated_channel import CalculatedChannel +from impulse_query_engine.analyze.query.optimizer import ( + PushdownContext, + PushdownPlan, + QueryOptimizer, + ResamplePushdownRule, +) +from tests.conftest import spark + +_LONG = PushdownContext(T.LongType(), T.LongType()) + + +def _channel(name, **kwargs): + return TimeSeriesSelector(TagSelector("channel_name") == name, **kwargs) + + +def _cc(expr, name): + return CalculatedChannel(expr, {"channel_name": name}) + + +@pytest.fixture +def selectors(): + return {"pushed": _channel("rpm"), "pushed_2": _channel("spd"), "raw": _channel("temp")} + + +def test_channel_variants(spark, selectors): + pushed, pushed_2, raw = selectors["pushed"], selectors["pushed_2"], selectors["raw"] + plan = QueryOptimizer().optimize( + [pushed.resample(1e6), pushed_2.resample(5e5), raw.max()], _LONG + ) + pushed_variant = plan.variants_by_selector[pushed.selector_id] + pushed_2_variant = plan.variants_by_selector[pushed_2.selector_id] + cases = { + "none": ([], [0]), + "null": ([None], [0]), + "raw": ([raw.selector_id], [0]), + "pushed": ([pushed.selector_id], list(pushed_variant)), + "raw_and_pushed": ([raw.selector_id, pushed.selector_id], [0]), + "pushed_and_null": ([pushed.selector_id, None], list(pushed_variant)), + "pushed_twice": ([pushed.selector_id, pushed.selector_id], list(pushed_variant)), + "two_pushed": ( + [pushed.selector_id, pushed_2.selector_id], + list(pushed_variant + pushed_2_variant), + ), + } + df = spark.createDataFrame( + [(name, ids) for name, (ids, _) in cases.items()], + schema="name string, selector_ids array", + ) + actual = { + r["name"]: r["variants"] + for r in df.select( + "name", plan.channel_variants(F.col("selector_ids")).alias("variants") + ).collect() + } + assert actual == {name: expected for name, (_, expected) in cases.items()} + + +def test_channel_selector_ids(spark): + df = spark.createDataFrame( + [(0, [7, None, 7, 3]), (1, []), (2, None)], schema="i int, selector_ids array" + ) + result = df.select("i", PushdownPlan.channel_selector_ids("selector_ids").alias("ids")) + assert result.schema["ids"].dataType.elementType == T.LongType() + assert {r["i"]: r["ids"] for r in result.collect()} == {0: [7, 3], 1: [], 2: None} + + +class TestNativeTargets: + def test_whole_expression_pushdowns(self, selectors): + rpm, spd = selectors["pushed"], selectors["pushed_2"] + aliased = _channel("vehicle_speed", uses_alias=True) + channels = [ + _cc(rpm.resample(1e6), "rpm_1s"), + _cc(rpm.resample(1e6), "rpm_1s_copy"), + _cc(spd.resample(5e5), "spd"), + _cc(aliased.resample(1e6), "vehicle_speed"), + ] + plan = QueryOptimizer().optimize(channels, _LONG) + v_1s = plan.variant_ids[("resample", (("sample_rate", 1e6),))] + v_half = plan.variant_ids[("resample", (("sample_rate", 5e5),))] + assert plan.native_targets(channels) == { + v_1s: { + rpm.selector_id: [ + (channels[0].channel_id, False), + (channels[1].channel_id, False), + ], + aliased.selector_id: [(channels[3].channel_id, True)], + }, + v_half: {spd.selector_id: [(channels[2].channel_id, False)]}, + } + + def test_needs_every_selection_to_be_a_whole_pushdown(self, selectors): + rpm, spd = selectors["pushed"], selectors["pushed_2"] + native = _cc(rpm.resample(1e6), "rpm_1s") + for other in (_cc(spd.resample(1e6) * 2, "spd_x2"), spd.resample(1e6)): + selections = [native, other] + plan = QueryOptimizer().optimize(selections, _LONG) + assert not plan.is_empty + assert plan.native_targets(selections) is None + assert QueryOptimizer().optimize([native], _LONG).native_targets([]) is None + + def test_rule_with_custom_decode_is_not_native(self, selectors): + class _CustomDecode(ResamplePushdownRule): + def decode(self, rows, pushdown, cache): + return super().decode(rows, pushdown, cache) + + channels = [_cc(selectors["pushed"].resample(1e6), "rpm_1s")] + plan = QueryOptimizer([_CustomDecode()]).optimize(channels, _LONG) + assert not plan.is_empty + assert plan.native_targets(channels) is None diff --git a/tests/impulse_query_engine/unit/analyze/query/optimizer/query_optimizer_test.py b/tests/impulse_query_engine/unit/analyze/query/optimizer/query_optimizer_test.py new file mode 100644 index 00000000..79db1458 --- /dev/null +++ b/tests/impulse_query_engine/unit/analyze/query/optimizer/query_optimizer_test.py @@ -0,0 +1,409 @@ +# pylint: disable=missing-function-docstring +"""Planning tests for the rule-based pushdown optimizer (no data involved).""" + +import math +import pickle + +import pyspark.sql.types as T +import pytest +from pyspark import cloudpickle + +from impulse_query_engine.analyze.metadata.tag_expression import TagSelector +from impulse_query_engine.analyze.metadata.time_series_expression import ( + SeriesType, + TimeSeriesExpression, + TimeSeriesOp, + TimeSeriesSelector, +) +from impulse_query_engine.analyze.query.aggregations.histogram import HistogramDuration +from impulse_query_engine.analyze.query.aggregations.stats_aggregator import StatsAggregator +from impulse_query_engine.analyze.query.channels.calculated_channel import CalculatedChannel +from impulse_query_engine.analyze.query.events.sequence_of_events_expression import ( + SequenceOfEventsExpression, +) +from impulse_query_engine.analyze.query.optimizer import ( + Pushdown, + PushdownContext, + PushdownPlan, + QueryOptimizer, + ResamplePushdownRule, +) +from impulse_query_engine.analyze.query.optimizer.traversal import Classifier, discover +from impulse_query_engine.model.series.intervals import Intervals + +_LONG = PushdownContext(T.LongType(), T.LongType()) +_DOUBLE = PushdownContext(T.DoubleType(), T.DoubleType()) + + +def _channel(name: str, **kwargs) -> TimeSeriesSelector: + return TimeSeriesSelector(TagSelector("channel_name") == name, **kwargs) + + +def _plan(*selections, context=_LONG, rules=None): + return QueryOptimizer(rules).optimize(selections, context) + + +@pytest.fixture +def rpm(): + return _channel("rpm") + + +@pytest.fixture +def spd(): + return _channel("spd") + + +class TestPolicy: + def test_resample_only_selection_is_pushed(self, rpm): + plan = _plan(rpm.resample(1e6)) + assert not plan.is_empty + assert plan.pushed_selector_ids == {rpm.selector_id} + assert plan.raw_selector_ids == frozenset() + assert [p.describe() for p in plan.pushdowns_by_variant.values()] == [ + "resample(sample_rate=1000000.0)" + ] + + def test_raw_use_in_another_selection_blocks_pushdown(self, rpm): + assert _plan(rpm.resample(1e6), rpm.max()).is_empty + + def test_raw_use_in_the_same_expression_blocks_pushdown(self, rpm): + assert _plan(rpm.resample(1e6) + rpm).is_empty + + def test_other_channels_keep_raw_data(self, rpm, spd): + plan = _plan(rpm.resample(1e6), spd.max(), spd.resample(1e6)) + assert plan.pushed_selector_ids == {rpm.selector_id} + assert plan.raw_selector_ids == {spd.selector_id} + + def test_two_rates_give_two_variants(self, rpm): + plan = _plan(rpm.resample(1e6), rpm.resample(1e5)) + assert set(plan.variant_ids.values()) == {1, 2} + assert plan.variants_by_selector == {rpm.selector_id: (1, 2)} + + def test_equivalent_calls_share_one_variant(self, rpm): + plan = _plan(rpm.resample(1e6), rpm.resample(sample_rate=1_000_000)) + assert plan.variants_by_selector == {rpm.selector_id: (1,)} + + def test_equal_selectors_built_separately_are_one_selector(self): + plan = _plan(_channel("rpm").resample(1e6), _channel("rpm").max()) + assert plan.is_empty + + def test_node_shared_across_selections_and_branches(self, rpm): + resampled = rpm.resample(1e6) + plan = _plan(resampled + resampled, resampled.cumtrapz()) + assert plan.pushed_selector_ids == {rpm.selector_id} + + def test_non_expression_selection_gives_empty_plan(self, rpm): + assert _plan(rpm.resample(1e6), object()).is_empty + + +class TestDiscovery: + def test_inside_udf_argument(self, rpm): + # TimeSeriesUDF stores its first operand in ``optype`` too. + plan = _plan(rpm.resample(1e6).apply(lambda s: s)) + assert plan.pushed_selector_ids == {rpm.selector_id} + + def test_inside_calculated_channel(self, rpm): + plan = _plan(CalculatedChannel(rpm.resample(1e6) * 2, {"channel_name": "rpm_1hz"})) + assert plan.pushed_selector_ids == {rpm.selector_id} + + def test_inside_stats_aggregator_and_event(self, rpm, spd): + event = spd.resample(1e6) > 50 + stats = StatsAggregator( + input_expressions=[rpm.resample(1e6)], statistics=["max"], event_expression=event + ) + plan = _plan(stats, event) + assert plan.pushed_selector_ids == {rpm.selector_id, spd.selector_id} + + def test_inside_histogram_and_sequence_of_events(self, rpm, spd): + plan = _plan( + HistogramDuration(rpm.resample(1e6), bins=[0, 1000, 2000]), + SequenceOfEventsExpression([spd.resample(1e6) > 10, spd.resample(1e6) > 20]), + ) + assert plan.pushed_selector_ids == {rpm.selector_id, spd.selector_id} + + def test_aliased_selector(self): + alias = _channel("vehicle_speed", uses_alias=True) + assert _plan(alias.resample(1e6)).pushed_selector_ids == {alias.selector_id} + + def test_walk_disagreeing_with_get_selectors_keeps_raw_data(self, rpm): + # One selection over-counts a pushed subtree, another hides a raw use; the + # totals would balance, so only the per-selection guard keeps rpm raw. + resampled = rpm.resample(1e6) + over_counted = _Opaque({"a": resampled, "b": resampled}, reported=[rpm]) + hidden_raw_use = _Opaque({"a": rpm.resample(1e6)}, reported=[rpm, rpm]) + assert _plan(over_counted, hidden_raw_use).is_empty + + +class _MatchesAnyNode(ResamplePushdownRule): + """Claims every non-selector node that reads exactly one selector, of any type.""" + + name = "any_node" + + def match(self, node, operand): + if isinstance(node, TimeSeriesSelector) or not isinstance(node, TimeSeriesExpression): + return None + selectors = node.get_selectors() + if len(selectors) != 1: + return None + return Pushdown(rule=self, selector=selectors[0], params=(), consumed=tuple(selectors)) + + def supports(self, pushdown, context): + return True + + +class TestOnlyOperationsArePushedDown: + # Only TimeSeriesOp.build serves precomputed results; other node types must + # keep their raw rows. + def test_aggregation_node_is_not_a_candidate(self, rpm): + found, leaves = discover( + HistogramDuration(rpm, bins=[0, 1000]), Classifier([_MatchesAnyNode()]) + ) + assert found == [] + assert [s.selector_id for s in leaves] == [rpm.selector_id] + + def test_udf_node_is_not_a_candidate_but_its_arguments_are(self, rpm): + found, _ = discover(rpm.resample(1e6).apply(lambda s: s), Classifier([_MatchesAnyNode()])) + assert [node.operation for node, _ in found] == ["resample"] + + def test_lookup_ignores_other_node_types(self, rpm): + rule = _MatchesAnyNode() + plan = QueryOptimizer([rule]).optimize([rpm.resample(1e6)], _LONG) + assert plan.lookup(rpm.resample(1e6)) is not None + assert plan.lookup(HistogramDuration(rpm, bins=[0, 1000])) is None + + +class _OnPushedOperand(ResamplePushdownRule): + """Matches ``x.cumtrapz()`` on a pushed ``x``, recording the operands it sees.""" + + def __init__(self, name="on_pushed", chained=False, supported=True): + self.name = name + self.chained = chained + self.supported = supported + self.seen = [] + + def match(self, node, operand): + if type(node) is not TimeSeriesOp or node.operation != "cumtrapz": + return None + inner = operand(node.args[0]) + self.seen.append(inner) + if not isinstance(inner, Pushdown): + return None + return Pushdown( + rule=self, + selector=inner.selector, + params=(("inner", inner.variant_key),), + consumed=inner.consumed, + source=inner if self.chained else None, + ) + + def supports(self, pushdown, context): + return self.supported + + +_RESAMPLE = "resample(sample_rate=1000000.0)" + + +def _described(plan): + return [p.describe() for p in plan.pushdowns_by_variant.values()] + + +class TestBottomUpClassification: + def test_rules_see_their_operands_classification(self, rpm): + rule = _OnPushedOperand() + classify = Classifier([rule, ResamplePushdownRule()]) + for node in (rpm.cumtrapz(), rpm.resample(1e6).cumtrapz(), (rpm * 2).cumtrapz()): + classify(node) + channel, pushed, evaluated_in_pandas = rule.seen + assert channel is rpm + assert pushed.describe() == _RESAMPLE + assert evaluated_in_pandas is None + + def test_chained_pushdown_is_not_planned_but_its_operand_is(self, rpm): + expr = rpm.resample(1e6).cumtrapz() + plan = _plan(expr, rules=[_OnPushedOperand(chained=True), ResamplePushdownRule()]) + assert _described(plan) == [_RESAMPLE] + assert plan.pushed_selector_ids == {rpm.selector_id} + assert plan.lookup(expr) is None + assert plan.lookup(expr.args[0])[1] == 1 + + def test_absorbing_rule_is_planned_as_one_variant(self, rpm): + expr = rpm.resample(1e6).cumtrapz() + plan = _plan(expr, rules=[_OnPushedOperand(), ResamplePushdownRule()]) + (pushdown,) = plan.pushdowns_by_variant.values() + assert pushdown.rule.name == "on_pushed" and pushdown.source is None + assert pushdown.consumed == (rpm,) + hit, variant = plan.lookup(expr) + assert hit.variant_key == pushdown.variant_key and variant == 1 + assert plan.lookup(expr.args[0]) is None + + def test_unsupported_parent_falls_back_to_its_pushed_operand(self, rpm): + expr = rpm.resample(1e6).cumtrapz() + plan = _plan(expr, rules=[_OnPushedOperand(supported=False), ResamplePushdownRule()]) + assert _described(plan) == [_RESAMPLE] + assert plan.lookup(expr) is None + assert plan.lookup(expr.args[0])[1] == 1 + + def test_first_matching_rule_decides(self, rpm): + # The UDF classifies without knowing what is supported, so an unsupported + # first rule must not hand the node to the next one. + expr = rpm.resample(1e6).cumtrapz() + rules = [ + _OnPushedOperand("unsupported", supported=False), + _OnPushedOperand("supported"), + ResamplePushdownRule(), + ] + plan = _plan(expr, rules=rules) + assert _described(plan) == [_RESAMPLE] + assert plan.lookup(expr) is None + assert plan.lookup(expr.args[0])[1] == 1 + + def test_shared_nodes_are_matched_once(self, rpm): + matched = [] + + class _Recording(ResamplePushdownRule): + def match(self, node, operand): + matched.append(node) + return super().match(node, operand) + + resampled = rpm.resample(1e6) + plan = _plan(resampled + resampled, resampled.cumtrapz(), rules=[_Recording()]) + assert len(matched) == len({id(node) for node in matched}) == 3 + assert plan.pushed_selector_ids == {rpm.selector_id} + + def test_chained_pushdowns_are_their_own_variant_and_cannot_be_planned(self, rpm): + inner = Classifier([ResamplePushdownRule()])(rpm.resample(1e6)) + chained = Pushdown( + rule=inner.rule, selector=rpm, params=inner.params, consumed=(rpm,), source=inner + ) + assert inner.variant_key == ("resample", (("sample_rate", 1e6),)) + assert chained.variant_key != inner.variant_key + assert chained.describe() == f"{_RESAMPLE} on {_RESAMPLE}" + with pytest.raises(ValueError, match="has a source"): + PushdownPlan.build([inner.rule], [chained], [rpm.selector_id]) + + +class TestRuleNames: + @pytest.mark.parametrize("name", ["Bad", "a__b", "_x", "x_", "1x", ""]) + def test_invalid_rule_names_are_rejected(self, name): + rule = type("_Named", (ResamplePushdownRule,), {"name": name})() + with pytest.raises(ValueError, match="rule name"): + QueryOptimizer([rule]) + + def test_rule_names_must_be_unique(self): + with pytest.raises(ValueError, match="unique"): + QueryOptimizer([ResamplePushdownRule(), ResamplePushdownRule()]) + + def test_plan_rejects_two_rules_sharing_a_name(self, rpm, spd): + first, second = ResamplePushdownRule(), ResamplePushdownRule() + plan = PushdownPlan.build( + (first, second), + [Classifier([first])(rpm.resample(1e6)), Classifier([second])(spd.resample(2e6))], + [rpm.selector_id, spd.selector_id], + ) + with pytest.raises(ValueError, match="two different pushdown rules"): + plan.variants_by_rule() + + +class _Opaque(TimeSeriesExpression): + """Node whose attributes disagree with the selectors it reports.""" + + def __init__(self, attributes, reported): + super().__init__() + self.__dict__.update(attributes) + self._reported = lambda: list(reported) # not visible to the walk + + def build(self, cache): + return None + + def get_selectors(self): + return self._reported() + + def get_required_tag_exprs(self): + return set() + + def required_tags(self): + return set() + + def get_selector_expr(self): + return None + + def __str__(self): + return "_Opaque" + + +class TestResampleRule: + @pytest.mark.parametrize( + "make", + [ + lambda c: (c * 2).resample(1e6), + lambda c: c.resample(True), + lambda c: c.resample(0), + lambda c: c.resample(-1e6), + lambda c: c.resample(math.nan), + lambda c: c.resample(math.inf), + lambda c: c.resample(c), + lambda c: c.resample(1e6, 2), + lambda c: c.resample(1e6, sample_rate=1e6), + lambda c: c.resample(rate=1e6), + lambda c: c.rolling_average(1e6), + ], + ) + def test_unsupported_calls_are_not_matched(self, rpm, make): + assert Classifier([ResamplePushdownRule()])(make(rpm)) is None + + def test_default_rate(self, rpm): + pushdown = Classifier([ResamplePushdownRule()])(rpm.resample()) + assert pushdown.params == (("sample_rate", 1.0),) + assert pushdown.selector is rpm + + def test_points_in_time_selector_is_not_matched(self): + poi = _channel("dtc", series_type=SeriesType.POINTS_IN_TIME) + assert Classifier([ResamplePushdownRule()])(poi.resample(1e6)) is None + + def test_fractional_rate_needs_double_time_columns(self, rpm): + assert _plan(rpm.resample(0.5), context=_LONG).is_empty + assert not _plan(rpm.resample(0.5), context=_DOUBLE).is_empty + + @pytest.mark.parametrize( + "time_type", [T.FloatType(), T.DecimalType(20, 0), T.TimestampType(), T.StringType()] + ) + def test_unsupported_time_column_types(self, rpm, time_type): + assert _plan(rpm.resample(1e6), context=PushdownContext(time_type, time_type)).is_empty + + +class TestResultTypeCheck: + def test_misdeclared_result_type_is_dropped(self, rpm): + class _IntervalsResample(ResamplePushdownRule): + def result_type(self, pushdown): + return Intervals + + assert _plan(rpm.resample(1e6), rules=[_IntervalsResample()]).is_empty + + def test_failing_type_inference_drops_candidate(self, rpm): + class _Exploding(ResamplePushdownRule): + def match(self, node, operand): + pushdown = super().match(node, operand) + if pushdown is not None: + node.evaluation_type = _raise + return pushdown + + def _raise(): + raise RuntimeError("boom") + + assert _plan(rpm.resample(1e6), rules=[_Exploding()]).is_empty + + +class TestPlanInExecutor: + @pytest.mark.parametrize("dumps", [pickle.dumps, cloudpickle.dumps]) + def test_plan_and_selections_survive_pickling(self, rpm, spd, dumps): + selections = [rpm.resample(1e6).cumtrapz(), spd.resample(1e6), spd.max()] + plan = _plan(*selections) + plan2, selections2 = pickle.loads(dumps((plan, selections))) + + pushed = selections2[0].args[0] + pushdown, variant = plan2.lookup(pushed) + assert variant == 1 + assert pushdown.selector.selector_id == rpm.selector_id + # spd is needed raw by spd.max(), so its resample is evaluated in pandas. + assert plan2.lookup(selections2[1]) is None + assert plan2.lookup(selections2[2]) is None diff --git a/tests/impulse_query_engine/unit/analyze/query/optimizer/resample_rule_test.py b/tests/impulse_query_engine/unit/analyze/query/optimizer/resample_rule_test.py new file mode 100644 index 00000000..f0e2c941 --- /dev/null +++ b/tests/impulse_query_engine/unit/analyze/query/optimizer/resample_rule_test.py @@ -0,0 +1,371 @@ +# pylint: disable=missing-function-docstring +"""Parity of the Spark ``resample`` pushdown with ``SampleSeries.resample``.""" + +import re + +import numpy as np +import numpy.testing as nptest +import pyspark.sql.types as T +import pytest + +from impulse_query_engine.analyze.metadata.tag_expression import TagSelector +from impulse_query_engine.analyze.metadata.time_series_expression import TimeSeriesSelector +from impulse_query_engine.analyze.query.optimizer import ( + VARIANT_COL, + Classifier, + ResamplePushdownRule, +) +from impulse_query_engine.model.series.sample_series import SampleSeries +from tests.conftest import spark +from tests.impulse_query_engine.unit.analyze.query.optimizer.pipeline_helpers import ( + CONFIG, + DiffRule, + channel_rows, + plan_for, + random_series, + run, + top_level_items, +) +from impulse_query_engine.analyze.query.optimizer import apply_pushdowns + +_RULE = ResamplePushdownRule() +_X = TimeSeriesSelector(TagSelector("channel_name") == "x") + + +def _pushdown(rate): + return Classifier([_RULE])(_X.resample(rate)) + + +def _resample_in_spark(spark, series_by_key, rates, time_type=T.LongType(), chunk_rows=None): + """Resample ``{(cid, ch, variant): (tstarts, tends, values)}`` at ``{variant: rate}``. + + Variant 0 rows are raw rows. Returns ``{(cid, ch, variant): SampleSeries}`` + built like the pandas cache does. + """ + pushdowns = {vid: _pushdown(rate) for vid, rate in rates.items()} + return run(spark, series_by_key, pushdowns, time_type=time_type, chunk_size=chunk_rows) + + +def _expected(series, rate): + tstarts, tends, values = series + return SampleSeries(tstarts, tends, [np.nan if v is None else v for v in values]).resample( + rate + ) + + +def _assert_same(actual, expected): + nptest.assert_array_equal(actual.tstarts, expected.tstarts) + nptest.assert_array_equal(actual.tends, expected.tends) + nptest.assert_array_equal(actual.values, expected.values) + + +def _where_lt(tstarts, tends, values, threshold): + s = SampleSeries(tstarts, tends, values) + w = s.where(s < threshold) + return list(w.tstarts), list(w.tends), list(w.values) + + +# Inputs of the SampleSeries.resample unit tests plus further edge cases, as +# (tstarts, tends, values, rate). Integral, so exact for LongType columns. +_CASES = { + "two_samples": ([0, 1], [1, 3], [1, 2], 1), + "long_first_sample": ([0, 4, 5], [4, 5, 6], [1, 3, 2], 1), + "uneven": ([0, 4, 6], [4, 6, 7], [1, 3, 2], 1), + "gap": ([0, 1, 6], [1, 3, 7], [1, 2, 4], 1), + "where_gap": (*_where_lt([0, 1, 2, 3, 4, 5], [1, 2, 3, 4, 5, 10], [5, 5, 5, 4, 5, 1], 5), 1), + "where_gap_rate_2": ( + *_where_lt([0, 1, 2, 3, 4, 5, 10], [1, 2, 3, 4, 5, 10, 11], [5, 5, 5, 4, 5, 1, 2], 5), + 2, + ), + "trailing_point": ([0, 4, 5, 6], [4, 5, 6, 6], [1, 3, 2, 3], 1), + "where_trailing_point": ( + *_where_lt([0, 1, 2, 3, 4, 5, 10], [1, 2, 3, 4, 5, 10, 10], [5, 5, 5, 4, 5, 1, 2], 6), + 1, + ), + "interior_point": ([0, 2, 3], [1, 2, 4], [10, 20, 30], 1), + "interior_and_trailing_point": ([0, 2, 3], [1, 2, 3], [10, 20, 30], 1), + "isolated_trailing_point": ([0, 1, 5], [1, 2, 5], [1, 2, 3], 1), + "single_sample": ([5], [7], [1.0], 1), + "single_point": ([5], [5], [1.0], 1), + "nan_and_null_values": ([0, 1, 2, 3], [1, 2, 3, 4], [1.0, np.nan, None, 4.0], 1), + "grid_off_sample_boundaries": ([0, 3, 7, 12], [3, 7, 12, 20], [1, 2, 3, 4], 4), + "rate_beyond_block": ([0, 1, 2], [1, 2, 3], [1, 2, 3], 10), + "grid_on_sample_starts": (list(range(10)), list(range(1, 11)), list(range(10)), 3), +} + + +@pytest.mark.parametrize("name", list(_CASES)) +def test_resample_matches_sample_series(spark, name): + tstarts, tends, values, rate = _CASES[name] + actual = _resample_in_spark(spark, {(1, 1, 1): (tstarts, tends, values)}, {1: rate}) + _assert_same(actual[(1, 1, 1)], _expected((tstarts, tends, values), rate)) + + +# Few samples per chunk put chunk boundaries at gaps, inside buckets and next to +# zero-duration samples, with empty chunks in between. +@pytest.mark.parametrize("chunk_rows", [None, 1, 2, 3]) +def test_all_cases_in_one_pass_and_raw_rows_unchanged(spark, chunk_rows): + series = {} + expected = {} + for i, (tstarts, tends, values, _rate) in enumerate(_CASES.values()): + # rate 1 -> variant 1, rate 2 -> variant 2; containers keep channels apart. + for vid, r in ((1, 1), (2, 2)): + series[(i, 7, vid)] = (tstarts, tends, values) + expected[(i, 7, vid)] = _expected((tstarts, tends, values), r) + # Raw rows (variant 0) pass through untouched. + series[(i, 7, 0)] = (tstarts, tends, values) + expected[(i, 7, 0)] = SampleSeries( + tstarts, tends, [np.nan if v is None else v for v in values] + ) + actual = _resample_in_spark(spark, series, {1: 1, 2: 2}, chunk_rows=chunk_rows) + assert actual.keys() == expected.keys() + for key, exp in expected.items(): + _assert_same(actual[key], exp) + + +@pytest.mark.parametrize("chunk_rows", [None, 7]) +def test_ns_epoch_longs_above_2_pow_53(spark, chunk_rows): + rng = np.random.default_rng(7) + base = 1_700_000_000_123_456_789 + steps = rng.integers(9_000_000, 11_000_000, size=500) # ~10 ms, jittered + times = base + np.concatenate([[0], np.cumsum(steps)]) + tstarts, tends = times[:-1].tolist(), times[1:].tolist() + values = rng.normal(size=500).tolist() + rate = 100_000_000 # 100 ms + actual = _resample_in_spark( + spark, {(1, 1, 1): (tstarts, tends, values)}, {1: rate}, chunk_rows=chunk_rows + ) + expected = _expected((tstarts, tends, values), rate) + assert len(expected) < len(tstarts) / 5 + _assert_same(actual[(1, 1, 1)], expected) + + +def test_only_linear_window_frames(spark): + # Spark recomputes a ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING frame for every + # row (quadratic in the series length), so the rule must not use one. + plan = _optimized_plan(spark) + assert "specifiedwindowframe(RowFrame, currentrow$(), unboundedfollowing$())" not in plan + assert "specifiedwindowframe(RowFrame, unboundedpreceding$(), currentrow$())" in plan + + +def test_keep_filter_does_not_inline_the_rule_columns(spark): + # Spark pushes filters through deterministic projections by substituting column + # definitions; resample's grid-index columns refer to each other several times, so an + # inlined keep condition would grow to about 100k characters. + df, variants = channel_rows( + spark, {(1, 1, 1): ([0, 1], [1, 2], [1.0, 2.0]), (1, 1, 2): ([0, 1], [1, 2], [1.0, 2.0])} + ) + plan = plan_for({1: _pushdown(1), 2: _pushdown(2)}) + result = apply_pushdowns(df, variants, plan, CONFIG, include_raw=False) + optimized = result._jdf.queryExecution().optimizedPlan().toString() + conditions = re.findall(r"Filter (.*)", optimized) + assert conditions and not any("raise_error" in c for c in conditions) + assert max(len(c) for c in conditions) < 1_000 + + +def test_random_series_across_chunk_sizes(spark): + rng = np.random.default_rng(3) + rates = {1: 1, 2: 3, 3: 7} + series, expected = {}, {} + for cid, n in enumerate([1, 2, 40, 60, 60, 80]): + tstarts, tends, values = random_series(rng, n) + for vid, rate in rates.items(): + series[(cid, 1, vid)] = (tstarts, tends, values) + expected[(cid, 1, vid)] = _expected((tstarts, tends, values), rate) + series[(cid, 1, 0)] = (tstarts, tends, values) + expected[(cid, 1, 0)] = SampleSeries(tstarts, tends, values) + assert sum(len(e) for e in expected.values()) > 0 + for chunk_rows in (1, 2, 3, 5, 8, 13): + actual = _resample_in_spark(spark, series, rates, chunk_rows=chunk_rows) + assert actual.keys() == expected.keys() + for key, exp in expected.items(): + _assert_same(actual[key], exp) + + +def _optimized_plan(spark) -> str: + df, variants = channel_rows(spark, {(1, 1, 1): ([0, 1], [1, 2], [1.0, 2.0])}) + result = apply_pushdowns(df, variants, plan_for({1: _pushdown(1)}), CONFIG, include_raw=False) + return result._jdf.queryExecution().optimizedPlan().toString() + + +def test_windows_over_samples_are_chunked(spark): + # Spark buffers a whole window partition, so no window over the samples may be + # partitioned by the series alone; only the chunk summaries are. + plan = _optimized_plan(spark) + specs = re.findall(r"windowspecdefinition\((.*?), specifiedwindowframe", plan) + channel_key = ["container_id", "channel_id"] + seen = set() + for spec in specs: + items = top_level_items(re.sub(r"#\d+L?", "", spec)) + partition = [item for item in items if not item.endswith(("NULLS FIRST", "NULLS LAST"))] + order = [item.split(" ")[0] for item in items[len(partition) :]] + seen.add((tuple(partition), tuple(order))) + if partition == channel_key: + assert order == ["_pd_chunk"] + else: + # Per chunk of a channel; after the fan-out, per chunk of a series. + assert partition in ( + [*channel_key, "_pd_chunk"], + [*channel_key, VARIANT_COL, "_pd_chunk"], + ) + assert (tuple([*channel_key, "_pd_chunk"]), ("tstart", "tend", "value")) in seen + assert any(partition == tuple(channel_key) for partition, _ in seen) + # Photon runs window aggregates over structs row-based. + assert not re.search(r"(max|min)\(struct\(", plan) + + +def test_resample_and_diff_in_one_pass(spark): + rng = np.random.default_rng(5) + series, expected = {}, {} + for cid in range(3): + samples = random_series(rng, 50) + series[(cid, 1, 1)] = series[(cid, 1, 2)] = samples + expected[(cid, 1, 1)] = _expected(samples, 3) + expected[(cid, 1, 2)] = SampleSeries(*samples).diff() + pushdowns = {1: _pushdown(3), 2: Classifier([DiffRule()])(_X.diff())} + for chunk_size in (2, 7): + actual = run(spark, series, pushdowns, chunk_size=chunk_size) + assert actual.keys() == expected.keys() + for key, exp in expected.items(): + _assert_same(actual[key], exp) + + +@pytest.mark.parametrize("chunk_rows", [None, 7]) +def test_ns_epoch_grid_on_sample_starts(spark, chunk_rows): + # Every fifth sample starts exactly on a grid point, the case where the bucket + # index estimate needs its correction step. + base = 1_700_000_000_123_456_789 + times = [base + i * 10_000_000 for i in range(301)] + tstarts, tends = times[:-1], times[1:] + values = [float(i) for i in range(300)] + rate = 50_000_000 + actual = _resample_in_spark( + spark, {(1, 1, 1): (tstarts, tends, values)}, {1: rate}, chunk_rows=chunk_rows + ) + _assert_same(actual[(1, 1, 1)], _expected((tstarts, tends, values), rate)) + + +@pytest.mark.parametrize("chunk_rows", [None, 2]) +def test_integer_time_columns(spark, chunk_rows): + tstarts, tends, values, rate = _CASES["where_gap_rate_2"] + actual = _resample_in_spark( + spark, + {(1, 1, 1): (tstarts, tends, values)}, + {1: rate}, + time_type=T.IntegerType(), + chunk_rows=chunk_rows, + ) + _assert_same(actual[(1, 1, 1)], _expected((tstarts, tends, values), rate)) + + +@pytest.mark.parametrize("chunk_rows", [None, 2]) +def test_seconds_as_double_with_binary_exact_rate(spark, chunk_rows): + tstarts = [10.0, 10.25, 11.0, 12.5, 14.0] + tends = [10.25, 11.0, 12.5, 14.0, 14.0] + values = [1.0, 2.0, 3.0, 4.0, 5.0] + actual = _resample_in_spark( + spark, + {(1, 1, 1): (tstarts, tends, values)}, + {1: 0.75}, + time_type=T.DoubleType(), + chunk_rows=chunk_rows, + ) + _assert_same(actual[(1, 1, 1)], _expected((tstarts, tends, values), 0.75)) + + +@pytest.mark.parametrize("chunk_rows", [None, 7]) +def test_seconds_as_double_with_fractional_rate(spark, chunk_rows): + # 0.1 is not binary-exact: grid points may differ from numpy by one ulp where + # numpy fuses the multiply-add. Sample boundaries stay clear of the grid, so the + # values must match exactly. + rng = np.random.default_rng(11) + times = 1234.5678 + np.concatenate([[0.0], np.cumsum(rng.uniform(0.031, 0.037, 400))]) + tstarts, tends = times[:-1].tolist(), times[1:].tolist() + values = rng.normal(size=400).tolist() + actual = _resample_in_spark( + spark, + {(1, 1, 1): (tstarts, tends, values)}, + {1: 0.1}, + time_type=T.DoubleType(), + chunk_rows=chunk_rows, + )[(1, 1, 1)] + expected = _expected((tstarts, tends, values), 0.1) + _assert_close(actual, expected) + + +def _assert_close(actual, expected): + """Equal values; grid times within one ulp, where numpy may fuse the multiply-add.""" + assert len(actual) == len(expected) + nptest.assert_array_max_ulp(actual.tstarts, expected.tstarts, maxulp=1) + nptest.assert_array_max_ulp(actual.tends, expected.tends, maxulp=1) + nptest.assert_array_equal(actual.values, expected.values) + + +def _as_seconds(series): + """*series* on an irregular seconds-as-double clock: times are not binary-exact.""" + tstarts, tends, values = series + clock = [1234.5678 + t * 0.0371 for t in tstarts], [1234.5678 + t * 0.0371 for t in tends] + return (*clock, values) + + +@pytest.mark.parametrize("chunk_rows", [None, 1, 3, 8]) +def test_random_double_time_series(spark, chunk_rows): + # Gaps, zero-duration samples and NaN values on a clock that is not binary-exact, + # at fractional rates below and above the sample spacing. + rng = np.random.default_rng(17) + rates = {1: 0.1, 2: 0.25, 3: 0.6} + series, expected = {}, {} + for cid, n in enumerate([1, 2, 40, 90, 150]): + samples = _as_seconds(random_series(rng, n)) + for vid, rate in rates.items(): + series[(cid, 1, vid)] = samples + expected[(cid, 1, vid)] = _expected(samples, rate) + assert sum(len(e) for e in expected.values()) > 300 + actual = _resample_in_spark( + spark, series, rates, time_type=T.DoubleType(), chunk_rows=chunk_rows + ) + assert actual.keys() == expected.keys() + for key, exp in expected.items(): + _assert_close(actual[key], exp) + + +@pytest.mark.filterwarnings("ignore:All-NaN slice encountered:RuntimeWarning") +@pytest.mark.parametrize("chunk_rows", [None, 2, 7]) +def test_nan_tends(spark, chunk_rows): + # A NaN tend ends its block, and the block ends at its largest finite tend, so the + # sample with the NaN tend adds no grid points of its own. + nan = float("nan") + series = { + (0, 1, 1): ([0.0, 1.0, 2.0, 3.0], [1.0, nan, 3.0, 4.0], [1.0, 2.0, 3.0, 4.0]), + (1, 1, 1): ([0.0, 1.0, 2.0], [1.0, 2.0, nan], [1.0, 2.0, 3.0]), + } + rng = np.random.default_rng(23) + while len(series) < 12: + tstarts, tends, values = random_series(rng, int(rng.integers(2, 60)), nan_tend=True) + samples = ([float(t) for t in tstarts], tends, values) + try: + _expected(samples, 1) + except ValueError: + # A block without a finite tend has no end (see the next test). + continue + if any(np.isnan(tends)): + series[(len(series), 1, 1)] = samples + for key in list(series): + series[(key[0], 1, 2)] = series[key] + actual = _resample_in_spark( + spark, series, {1: 1, 2: 3}, time_type=T.DoubleType(), chunk_rows=chunk_rows + ) + for (cid, ch, vid), samples in series.items(): + _assert_same(actual[(cid, ch, vid)], _expected(samples, {1: 1, 2: 3}[vid])) + + +@pytest.mark.filterwarnings("ignore:All-NaN slice encountered:RuntimeWarning") +def test_block_without_a_finite_tend_fails_like_pandas(spark): + # A sample with a NaN tend after a block break forms a block without an end; the + # pandas evaluation fails on it, and so must the pushdown instead of guessing one. + nan = float("nan") + samples = ([0.0, 1.0, 2.0], [nan, 2.0, 3.0], [1.0, 2.0, 3.0]) + with pytest.raises(ValueError): + _expected(samples, 1) + with pytest.raises(Exception, match="resample:"): + _resample_in_spark(spark, {(1, 1, 1): samples}, {1: 1}, time_type=T.DoubleType()) diff --git a/tests/impulse_query_engine/unit/analyze/query/solvers/default_solver_channel_resolution_test.py b/tests/impulse_query_engine/unit/analyze/query/solvers/default_solver_channel_resolution_test.py new file mode 100644 index 00000000..f6e8ac22 --- /dev/null +++ b/tests/impulse_query_engine/unit/analyze/query/solvers/default_solver_channel_resolution_test.py @@ -0,0 +1,176 @@ +# pylint: disable=missing-function-docstring +"""DefaultSolver channel resolution when several channels of a container match a selection. + +A direct selection keeps every matching channel. An alias resolves to one channel per +container: the best alias priority, then the lowest ``channel_id``. +""" + +import os + +import numpy as np +import pandas as pd +import pyspark.sql.functions as F +import pytest + +from impulse_query_engine.analyze.metadata.tag_expression import TagSelector +from impulse_query_engine.analyze.metadata.time_series_expression import TimeSeriesSelector +from impulse_query_engine.analyze.query.solvers.default_solver import DefaultSolver +from impulse_query_engine.analyze.query.solvers.solver_config import ( + ChannelMappingConfig, + SolverConfig, + TableConfig, +) +from impulse_query_engine.measurement_db import MeasurementDB, MeasurementDBConfig +from tests.conftest import spark + +_ALIAS_SCHEMA = "spark_catalog.silver_key_value_store_alias" +# unit="C" matches the ambient and intake temperatures: channels 6 and 9 in containers 1 +# and 2, channels 2 and 8 in container 3. +_CELSIUS_CHANNELS = {1: [6, 9], 2: [6, 9], 3: [2, 8]} + + +def _alias_config() -> SolverConfig: + return SolverConfig( + project_id="SAMPLE_PROJECT", + container_metrics=TableConfig(column_name_mapping={"project": "project_id"}), + channel_mapping=ChannelMappingConfig(filters={"toolbox_id": "container_concept"}), + ) + + +def _resolved(spark, solver, query, selector) -> dict: + """``{container_id: [channel_id, ...]}`` the filter pipeline resolves *selector* to.""" + matches = query.select(selector)._run_filter_pipeline(spark, solver, None) + rows = matches.where( + F.exists("selector_ids", lambda sid: sid.cast("long") == selector.selector_id) + ).collect() + resolved: dict = {} + for row in rows: + resolved.setdefault(row["container_id"], []).append(row["channel_id"]) + return {cid: sorted(channels) for cid, channels in resolved.items()} + + +def _alias_tables(spark) -> dict: + names = ( + "container_tags", + "container_metrics", + "channel_metrics", + "channels", + "channel_mapping", + "channel_tags", + ) + return {name: spark.read.table(f"{_ALIAS_SCHEMA}.{name}") for name in names} + + +def _alias_db(tables, ws, with_channel_tags=False) -> MeasurementDB: + tables = dict(tables) + if not with_channel_tags: + tables.pop("channel_tags") + cfg = MeasurementDBConfig.for_debug(tables) + cfg.channel_mapping_table = "channel_mapping" + return MeasurementDB(cfg, ws=ws) + + +def _with_channel(channel_metrics, container_id, channel_id, **columns): + """*channel_metrics* plus a copy of container 1's Engine RPM row as a new channel.""" + template = channel_metrics.where( + (F.col("container_id") == 1) & (F.col("channel_name") == "Engine RPM") + ) + row = template.withColumn("container_id", F.lit(container_id).cast("long")).withColumn( + "channel_id", F.lit(channel_id).cast(channel_metrics.schema["channel_id"].dataType) + ) + for name, value in columns.items(): + row = row.withColumn(name, F.lit(value)) + return channel_metrics.unionByName(row.select(*channel_metrics.columns)) + + +class TestDirectSelectors: + def test_wide_mode_keeps_every_matching_channel(self, spark, basic_narrow_db): + q = basic_narrow_db.query + celsius = q.channel(unit="C") + assert _resolved(spark, DefaultSolver(spark), q, celsius) == _CELSIUS_CHANNELS + + def test_wide_mode_solves_on_one_of_the_matching_channels(self, spark, basic_narrow_db): + q = basic_narrow_db.query + result = q.select(q.channel(unit="C").max().alias("t_max")).solve( + spark, solver=DefaultSolver(spark) + ) + actual = {r["container_id"]: r["t_max"] for r in result.collect()} + + base_path = os.path.dirname(os.path.abspath(__file__)) + base_path = base_path[: base_path.find("tests")] + data = pd.read_csv(f"{base_path}/tests/unit/data/basic_narrow_csv/channel_data.csv") + assert actual.keys() == _CELSIUS_CHANNELS.keys() + for cid, channels in _CELSIUS_CHANNELS.items(): + maxima = [ + np.nanmax(data[(data.container_id == cid) & (data.channel_id == ch)].value) + for ch in channels + ] + assert np.isfinite(actual[cid]) + assert any(actual[cid] == pytest.approx(m) for m in maxima) + + def test_eav_mode_keeps_every_matching_channel( + self, spark, setup_key_value_store_alias_db, mock_workspace_client + ): + # Re-tag channel 9 of container 1 and channels 2 and 8 of container 3 as + # "Engine RPM", next to channel 5 of containers 1 and 2. + tables = _alias_tables(spark) + tags = tables["channel_tags"] + retagged = [(1, 9), (3, 2), (3, 8)] + keep = ~F.lit(False) + for cid, ch in retagged: + keep = keep & ~((F.col("container_id") == cid) & (F.col("channel_id") == ch)) + extra = spark.createDataFrame( + [(cid, ch, "channel_name", "Engine RPM") for cid, ch in retagged], + schema=tags.schema, + ) + tables["channel_tags"] = tags.where(keep).unionByName(extra) + db = _alias_db(tables, mock_workspace_client, with_channel_tags=True) + + q = db.query + rpm = q.channel(channel_name="Engine RPM") + assert _resolved(spark, DefaultSolver(spark, config=_alias_config()), q, rpm) == { + 1: [5, 9], + 2: [5], + 3: [2, 8], + } + + +class TestAliasedSelectors: + @pytest.mark.parametrize(("extra_channel", "expected"), [(2, 2), (8, 5)]) + def test_priority_tie_keeps_lowest_channel_id( + self, spark, setup_key_value_store_alias_db, mock_workspace_client, extra_channel, expected + ): + # Container 1 gets a second engine_speed candidate (EngSpd); both candidates + # have no priority, so the lower channel_id wins. + tables = _alias_tables(spark) + tables["channel_metrics"] = _with_channel( + tables["channel_metrics"], + 1, + extra_channel, + channel_name="EngSpd", + data_key="ProjSpecREC_10Hz", + ) + db = _alias_db(tables, mock_workspace_client) + q = db.query + engine_speed = q.channel_with_alias(channel_alias="engine_speed") + solver = DefaultSolver(spark, config=_alias_config()) + assert _resolved(spark, solver, q, engine_speed)[1] == [expected] + + def test_selector_matching_several_aliases_resolves_each_alias( + self, spark, key_value_store_alias_db + ): + q = key_value_store_alias_db.query + solver = DefaultSolver(spark, config=_alias_config()) + expected: dict = {} + for alias in ("engine_speed", "vehicle_speed"): + one = _resolved(spark, solver, q, q.channel_with_alias(channel_alias=alias)) + for cid, channels in one.items(): + expected.setdefault(cid, set()).update(channels) + both = TimeSeriesSelector( + (TagSelector("channel_alias") == "engine_speed") + | (TagSelector("channel_alias") == "vehicle_speed"), + uses_alias=True, + ) + resolved = _resolved(spark, solver, q, both) + assert resolved == {cid: sorted(channels) for cid, channels in expected.items()} + assert any(len(channels) == 2 for channels in resolved.values()) diff --git a/tests/impulse_query_engine/unit/analyze/query/solvers/default_solver_native_calculated_channels_test.py b/tests/impulse_query_engine/unit/analyze/query/solvers/default_solver_native_calculated_channels_test.py new file mode 100644 index 00000000..81e331b4 --- /dev/null +++ b/tests/impulse_query_engine/unit/analyze/query/solvers/default_solver_native_calculated_channels_test.py @@ -0,0 +1,338 @@ +# pylint: disable=missing-function-docstring +"""Calculated channels that are pushdowns as a whole are solved without the pandas UDF. + +Each case solves the same calculated channels with pushdown off (pandas UDF) and on +(native when every channel qualifies) and requires identical rows, types, nulls and +NaNs. +""" + +import pandas as pd +import pyspark.sql.functions as F +import pytest +from pyspark.sql import DataFrame + +from impulse_query_engine.analyze.query.channels.calculated_channel import CalculatedChannel +from impulse_query_engine.analyze.query.optimizer import ResamplePushdownRule, query_optimizer +from impulse_query_engine.analyze.query.solvers.default_solver import DefaultSolver +from impulse_query_engine.analyze.query.solvers.solver_config import ( + ChannelMappingConfig, + SolverConfig, + TableConfig, +) +from impulse_query_engine.measurement_db import MeasurementDB, MeasurementDBConfig +from tests.conftest import spark +from tests.impulse_query_engine.unit.analyze.query.optimizer.pipeline_helpers import DiffRule +from tests.impulse_query_engine.unit.analyze.query.solvers.default_solver_pushdown_test import ( + PUSHED_SCANS, + SAMPLE_SHUFFLE, + channel_data_scans, + container_shuffles_of_channel_data, + shuffles, + small_chunks, +) + +RATE = 1e7 # 10 s in µs; the fixture channels are sampled roughly every 0.9 s +_KVS_SCHEMA = "spark_catalog.silver_key_value_store" + + +def _kvs_config() -> SolverConfig: + return SolverConfig( + project_id="SAMPLE_PROJECT", + container_tags=TableConfig(column_name_mapping={"element_id": "key"}), + container_metrics=TableConfig(column_name_mapping={"project": "project_id"}), + channel_mapping=ChannelMappingConfig(), + ) + + +def _unit_conversion_config() -> SolverConfig: + return SolverConfig( + project_id="SAMPLE_PROJECT", + container_metrics=TableConfig(column_name_mapping={"project": "project_id"}), + channel_mapping=ChannelMappingConfig(filters={"toolbox_id": "container_concept"}), + ) + + +@pytest.fixture +def udf_calls(monkeypatch) -> list: + """Number of grouped-map UDF runs, per solve.""" + calls: list[int] = [] + original = DefaultSolver._apply_grouped_map + + def _counting(self, *args, **kwargs): + calls[-1] += 1 + return original(self, *args, **kwargs) + + monkeypatch.setattr(DefaultSolver, "_apply_grouped_map", _counting) + return calls + + +def _solve(spark, db, make_channels, enable_pushdown, udf_calls=None, **solver_kwargs): + if udf_calls is not None: + udf_calls.append(0) + q = db.query + solver = DefaultSolver(spark, enable_pushdown=enable_pushdown, **solver_kwargs) + return q.select(*make_channels(q)).solve_calculated_channels(spark, solver=solver) + + +def _nan_and_null_counts(df: DataFrame) -> tuple: + return tuple( + df.select( + F.sum(F.isnan("value").cast("int")), F.sum(F.col("value").isNull().cast("int")) + ).first() + ) + + +def _rows(df: DataFrame) -> pd.DataFrame: + pdf = df.toPandas() + pdf["identity"] = pdf["identity"].map(lambda m: tuple(sorted(m.items()))) + return pdf.sort_values(["container_id", "channel_id", "tstart", "tend"]).reset_index(drop=True) + + +def _assert_same(native: DataFrame, udf: DataFrame) -> pd.DataFrame: + assert [(f.name, f.dataType) for f in native.schema] == [ + (f.name, f.dataType) for f in udf.schema + ] + assert _nan_and_null_counts(native) == _nan_and_null_counts(udf) + expected = _rows(udf) + assert len(expected) > 0 + pd.testing.assert_frame_equal(_rows(native), expected, check_exact=True) + return expected + + +def _solve_both(spark, db, make_channels, udf_calls, **solver_kwargs) -> pd.DataFrame: + udf = _solve(spark, db, make_channels, False, udf_calls, **solver_kwargs) + native = _solve(spark, db, make_channels, True, udf_calls, **solver_kwargs) + return _assert_same(native, udf), native + + +@pytest.mark.usefixtures("small_chunks") +class TestNativeCalculatedChannels: + def test_resampled_channels_skip_the_udf(self, spark, key_value_store_db, udf_calls): + def channels(q): + return [ + CalculatedChannel( + q.channel(channel_name="Engine RPM").resample(RATE), {"channel_name": "rpm"} + ), + CalculatedChannel( + q.channel(channel_name="Vehicle Speed Sensor").resample(RATE / 2), + {"channel_name": "speed", "data_key": "CALC"}, + ), + ] + + rows, native = _solve_both( + spark, key_value_store_db, channels, udf_calls, config=_kvs_config() + ) + assert udf_calls == [1, 0] + assert rows["value"].gt(0).any() + assert rows["channel_id"].nunique() == 2 + plan = native._jdf.queryExecution().executedPlan().toString() + assert "FlatMapGroupsInPandas" not in plan + assert channel_data_scans(native) == PUSHED_SCANS + # The samples are shuffled by series and chunk, never by container. + assert len([e for e in shuffles(native) if e.startswith(SAMPLE_SHUFFLE)]) == 1 + assert container_shuffles_of_channel_data(native) == 0 + + def test_two_rules_share_the_reads(self, spark, key_value_store_db, udf_calls, monkeypatch): + monkeypatch.setattr( + query_optimizer, "default_rules", lambda: (ResamplePushdownRule(), DiffRule()) + ) + + def channels(q): + return [ + CalculatedChannel( + q.channel(channel_name="Engine RPM").resample(RATE), {"channel_name": "rpm"} + ), + CalculatedChannel( + q.channel(channel_name="Vehicle Speed Sensor").diff(), + {"channel_name": "speed_diff"}, + ), + ] + + rows, native = _solve_both( + spark, key_value_store_db, channels, udf_calls, config=_kvs_config() + ) + assert udf_calls == [1, 0] + assert rows["channel_id"].nunique() == 2 + assert rows["value"].ne(0).any() + assert channel_data_scans(native) == PUSHED_SCANS + assert len([e for e in shuffles(native) if e.startswith(SAMPLE_SHUFFLE)]) == 1 + + def test_same_expression_under_two_identities_and_two_rates( + self, spark, key_value_store_db, udf_calls + ): + def channels(q): + rpm = q.channel(channel_name="Engine RPM") + return [ + CalculatedChannel(rpm.resample(RATE), {"channel_name": "rpm_a"}), + CalculatedChannel(rpm.resample(RATE), {"channel_name": "rpm_b"}), + CalculatedChannel(rpm.resample(RATE / 4), {"channel_name": "rpm_c"}), + ] + + rows, _ = _solve_both(spark, key_value_store_db, channels, udf_calls, config=_kvs_config()) + assert udf_calls == [1, 0] + assert rows["channel_id"].nunique() == 3 + + def test_mixed_selection_uses_the_udf(self, spark, key_value_store_db, udf_calls): + def channels(q): + rpm = q.channel(channel_name="Engine RPM") + return [ + CalculatedChannel(rpm.resample(RATE), {"channel_name": "rpm"}), + CalculatedChannel(rpm.resample(RATE) * 2, {"channel_name": "rpm_x2"}), + ] + + _solve_both(spark, key_value_store_db, channels, udf_calls, config=_kvs_config()) + assert udf_calls == [1, 1] + + def test_aliased_and_direct_selection_of_one_channel_with_unit_conversion( + self, spark, key_value_store_unit_conversion_db, udf_calls + ): + # vehicle_speed resolves to the "Vehicle Speed Sensor" channel; only the + # aliased selection converts its values. + def channels(q): + return [ + CalculatedChannel( + q.channel_with_alias(channel_alias="vehicle_speed").resample(RATE), + {"channel_name": "speed_converted"}, + ), + CalculatedChannel( + q.channel(channel_name="Vehicle Speed Sensor").resample(RATE), + {"channel_name": "speed_raw"}, + ), + ] + + rows, _ = _solve_both( + spark, + key_value_store_unit_conversion_db, + channels, + udf_calls, + config=_unit_conversion_config(), + ) + assert udf_calls == [1, 0] + by_channel = rows.groupby("channel_id")["value"].sum() + assert len(by_channel) == 2 and by_channel.iloc[0] != by_channel.iloc[1] + + def test_nan_conversion_factor_leaves_values_unconverted( + self, spark, unit_conversion_dataframes, mock_workspace_client, udf_calls + ): + # vehicle_speed converts km/h to m/s; a NaN km/h factor must be skipped like + # the pandas evaluation does, not turn every value into null. + tables = dict(unit_conversion_dataframes) + tables["unit_conversion"] = tables["unit_conversion"].withColumn( + "conversion_factor", + F.when(F.col("unit") == "km/h", F.lit(float("nan"))).otherwise( + F.col("conversion_factor").cast("double") + ), + ) + cfg = MeasurementDBConfig.for_debug(tables) + cfg.channel_mapping_table = "channel_mapping" + cfg.unit_conversion_table = "unit_conversion" + db = MeasurementDB(cfg, ws=mock_workspace_client) + + def channels(q): + return [ + CalculatedChannel( + q.channel_with_alias(channel_alias="vehicle_speed").resample(RATE), + {"channel_name": "speed_aliased"}, + ), + CalculatedChannel( + q.channel(channel_name="Vehicle Speed Sensor").resample(RATE), + {"channel_name": "speed_raw"}, + ), + ] + + rows, _ = _solve_both(spark, db, channels, udf_calls, config=_unit_conversion_config()) + assert udf_calls == [1, 0] + aliased, raw = ( + rows[rows["identity"] == (("channel_name", name),)] + for name in ("speed_aliased", "speed_raw") + ) + same_samples = aliased.merge(raw, on=["container_id", "tstart", "tend"]) + assert len(same_samples) > 0 and same_samples["value_x"].notna().all() + pd.testing.assert_series_equal( + same_samples["value_x"], same_samples["value_y"], check_names=False + ) + + def test_raw_data(self, spark, setup_raw_channels_db, mock_workspace_client, udf_calls): + tables = { + name: spark.read.table(f"spark_catalog.silver_raw.{name}") + for name in ("container_metrics", "channel_metrics", "channels") + } + db = MeasurementDB(MeasurementDBConfig.for_debug(tables), ws=mock_workspace_client) + + def channels(q): + rpm = q.channel(channel_name="Engine RPM") + return [CalculatedChannel(rpm.resample(2e6), {"channel_name": "rpm_2s"})] + + _solve_both(spark, db, channels, udf_calls, is_raw_data=True) + assert udf_calls == [1, 0] + + def test_ns_timestamps_nan_values_and_a_single_sample( + self, spark, setup_key_value_store_db, mock_workspace_client, udf_calls + ): + # Timestamps beyond 2^53 (not exact in float64), NaN values, and container 3's + # Engine RPM reduced to a single sample (passed through by resample). + tables = { + name: spark.read.table(f"{_KVS_SCHEMA}.{name}") + for name in ("container_tags", "container_metrics", "channel_metrics", "channels") + } + data = tables["channels"] + rpm_3 = (F.col("container_id") == 3) & (F.col("channel_id") == 5) + first_3 = data.where(rpm_3).agg(F.min("tstart")).first()[0] + rpm_1 = (F.col("container_id") == 1) & (F.col("channel_id") == 5) + first_1 = data.where(rpm_1).agg(F.min("tstart")).first()[0] + tables["channels"] = ( + data.where(~rpm_3 | (F.col("tstart") == first_3)) + .withColumn("tstart", F.col("tstart") * 1000 + 1) + .withColumn("tend", F.col("tend") * 1000 + 1) + .withColumn( + "value", + # tstart is already scaled here; every sample but the first becomes NaN. + F.when( + rpm_1 & (F.col("tstart") > first_1 * 1000 + 1), F.lit(float("nan")) + ).otherwise(F.col("value").cast("double")), + ) + ) + db = MeasurementDB(MeasurementDBConfig.for_debug(tables), ws=mock_workspace_client) + + def channels(q): + rpm = q.channel(channel_name="Engine RPM") + return [CalculatedChannel(rpm.resample(RATE * 1000), {"channel_name": "rpm_ns"})] + + rows, native = _solve_both(spark, db, channels, udf_calls, config=_kvs_config()) + assert udf_calls == [1, 0] + assert rows["tstart"].max() > 2**53 + assert len(rows[rows.container_id == 3]) == 1 + assert _nan_and_null_counts(native)[1] > 0 + + +@pytest.mark.parametrize("channel_id_type", ["short", "string"]) +@pytest.mark.parametrize("enable_pushdown", [False, True]) +def test_channel_id_must_be_integer_or_long( + spark, + setup_key_value_store_db, + mock_workspace_client, + monkeypatch, + channel_id_type, + enable_pushdown, +): + def _prelude(*args, **kwargs): + raise AssertionError("the channel data was prepared before the channel_id check") + + # The check reads only the schema, before the prelude runs its first Spark job. + monkeypatch.setattr(DefaultSolver, "_prepare_channels_join", _prelude) + tables = { + name: spark.read.table(f"{_KVS_SCHEMA}.{name}") + for name in ("container_tags", "container_metrics", "channel_metrics", "channels") + } + for name in ("channel_metrics", "channels"): + tables[name] = tables[name].withColumn( + "channel_id", F.col("channel_id").cast(channel_id_type) + ) + db = MeasurementDB(MeasurementDBConfig.for_debug(tables), ws=mock_workspace_client) + + def channels(q): + rpm = q.channel(channel_name="Engine RPM") + return [CalculatedChannel(rpm.resample(RATE), {"channel_name": "rpm"})] + + with pytest.raises(ValueError, match="integer or long channel_id"): + _solve(spark, db, channels, enable_pushdown, config=_kvs_config()) diff --git a/tests/impulse_query_engine/unit/analyze/query/solvers/default_solver_pushdown_test.py b/tests/impulse_query_engine/unit/analyze/query/solvers/default_solver_pushdown_test.py new file mode 100644 index 00000000..246e84a6 --- /dev/null +++ b/tests/impulse_query_engine/unit/analyze/query/solvers/default_solver_pushdown_test.py @@ -0,0 +1,439 @@ +# pylint: disable=missing-function-docstring +"""DefaultSolver with ``enable_pushdown``: identical results, fewer rows into the UDF.""" + +import re +from collections import Counter + +import numpy as np +import numpy.testing as nptest +import pandas as pd +import pyspark.sql.functions as F +import pytest +from pyspark.sql import SparkSession + +from impulse_query_engine.analyze.metadata.tag_expression import TagSelector +from impulse_query_engine.analyze.metadata.time_series_expression import TimeSeriesSelector +from impulse_query_engine.analyze.query.channels.calculated_channel import CalculatedChannel +from impulse_query_engine.analyze.query.optimizer import ( + RAW_VARIANT, + VARIANT_COL, + Classifier, + PushdownPlan, + QueryOptimizer, + ResamplePushdownRule, + pipeline, +) +from impulse_query_engine.analyze.query.solvers.default_solver import ( + DefaultSolver, + TimeSeriesCache, +) +from impulse_query_engine.analyze.query.solvers.solver_config import ( + ChannelMappingConfig, + SolverConfig, + TableConfig, +) +from impulse_query_engine.measurement_db import MeasurementDB, MeasurementDBConfig +from impulse_query_engine.model.series.intervals import Intervals +from impulse_query_engine.model.series.sample_series import SampleSeries +from tests.conftest import spark + +RATE = 1e7 # 10 s; the fixture channels are sampled roughly every 0.9 s + + +def _kvs_config() -> SolverConfig: + return SolverConfig( + project_id="SAMPLE_PROJECT", + container_tags=TableConfig(column_name_mapping={"element_id": "key"}), + container_metrics=TableConfig(column_name_mapping={"project": "project_id"}), + channel_mapping=ChannelMappingConfig(), + ) + + +def _unit_conversion_config() -> SolverConfig: + return SolverConfig( + project_id="SAMPLE_PROJECT", + container_metrics=TableConfig(column_name_mapping={"project": "project_id"}), + channel_mapping=ChannelMappingConfig(filters={"toolbox_id": "container_concept"}), + ) + + +@pytest.fixture +def small_chunks(monkeypatch): + """Run the pushdown pipeline in chunks of a few samples, so channels span many chunks.""" + monkeypatch.setattr(pipeline, "CHUNK_ROWS", 7) + + +def _final_plan(df) -> str: + """The final physical plan of *df*, which must have been executed (e.g. collected).""" + return df._jdf.queryExecution().executedPlan().toString().split("== Initial Plan ==")[0] + + +def channel_data_scans(df) -> list[str]: + """Columns of each channels-table scan in the final plan of the executed *df*.""" + return sorted( + re.sub(r"#\d+L?", "", columns) + for columns in re.findall(r"FileScan parquet \S+\.channels\[([^\]]*)\]", _final_plan(df)) + ) + + +def shuffles(df) -> list[str]: + """Partitionings of the shuffles in the final plan of the executed *df* (reuses once).""" + return [ + re.sub(r"#\d+L?", "", e) + for e in re.findall(r"(?<=- )Exchange (\w+\([^)]*\))", _final_plan(df)) + ] + + +def container_shuffles_of_channel_data(df) -> int: + """Shuffles by container alone that read the channels table, in the executed *df*.""" + lines = _final_plan(df).splitlines() + + def depth(line: str) -> int: + return len(line) - len(line.lstrip(" :+-")) + + count = 0 + for i, line in enumerate(lines): + if re.search(r"- Exchange hashpartitioning\(container_id#\d+L?, \d+\)", line): + subtree = [] + for below in lines[i + 1 :]: + if depth(below) <= depth(line): + break + subtree.append(below) + count += any(re.search(r"FileScan parquet \S+\.channels\[", b) for b in subtree) + return count + + +#: Pushed channels: one full read, and one narrow read for the series boundaries. +PUSHED_SCANS = [ + "container_id,channel_id,tstart,tend", + "container_id,channel_id,tstart,tend,value", +] +#: Channels also used raw add one read. +MIXED_SCANS = sorted([*PUSHED_SCANS, "container_id,channel_id,tstart,tend,value"]) +#: The one shuffle of the pushed samples: by channel and chunk, once per sample. +SAMPLE_SHUFFLE = "hashpartitioning(container_id, channel_id, _pd_chunk" + + +@pytest.fixture +def udf_rows(monkeypatch): + """Rows reaching the grouped-map UDF, per solve: ``{(channel_id, variant): count}``.""" + recorded: list[Counter] = [] + original = DefaultSolver._apply_grouped_map + + def _recording(self, joined_df, *args, **kwargs): + has_variants = VARIANT_COL in joined_df.columns + cols = ["channel_id", VARIANT_COL] if has_variants else ["channel_id"] + counts = Counter() + for row in joined_df.groupBy(*cols).count().collect(): + variant = row[VARIANT_COL] if has_variants else None + counts[(row["channel_id"], variant)] += row["count"] + recorded.append(counts) + return original(self, joined_df, *args, **kwargs) + + monkeypatch.setattr(DefaultSolver, "_apply_grouped_map", _recording) + return recorded + + +def _solve_both(spark, db, make_selections, **solver_kwargs) -> tuple[pd.DataFrame, pd.DataFrame]: + """``toPandas`` results with pushdown off and on, sorted by container.""" + results = [] + for enable_pushdown in (False, True): + solver = DefaultSolver(spark, enable_pushdown=enable_pushdown, **solver_kwargs) + query = db.query + pdf = query.select(*make_selections(query)).toPandas(spark, solver=solver) + results.append(pdf.sort_values("container_id").reset_index(drop=True)) + return results[0], results[1] + + +def _assert_same(actual, expected): + assert list(actual.columns) == list(expected.columns) + assert len(actual) == len(expected) > 0 + for col in expected.columns: + for a, e in zip(actual[col], expected[col], strict=True): + if isinstance(e, SampleSeries): + assert isinstance(a, SampleSeries) + nptest.assert_array_equal(a.tstarts, e.tstarts) + nptest.assert_array_equal(a.tends, e.tends) + nptest.assert_array_equal(a.values, e.values) + else: + nptest.assert_equal(a, e) + + +def _derived_rows(pdf: pd.DataFrame, col: str) -> int: + return sum(len(s) for s in pdf[col]) + + +@pytest.mark.usefixtures("small_chunks") +class TestIdenticalResults: + def test_resample_only(self, spark: SparkSession, key_value_store_db, udf_rows): + off, on = _solve_both( + spark, + key_value_store_db, + lambda q: [q.channel(channel_name="Engine RPM").resample(RATE).alias("rpm")], + config=_kvs_config(), + ) + _assert_same(on, off) + assert all(np.isfinite(s.values).all() and len(s) > 1 for s in on["rpm"]) + + rows_off, rows_on = udf_rows + assert set(rows_on) == {(5, 1)} + assert rows_on[(5, 1)] == _derived_rows(off, "rpm") < rows_off[(5, None)] / 5 + + def test_pushed_channels_are_read_twice_and_shuffled_by_chunk( + self, spark: SparkSession, key_value_store_db + ): + q = key_value_store_db.query + result = q.select(q.channel(channel_name="Engine RPM").resample(RATE).alias("rpm")).solve( + spark, solver=DefaultSolver(spark, config=_kvs_config(), enable_pushdown=True) + ) + assert len(result.collect()) > 0 # executes result's own plan, which the checks read + assert channel_data_scans(result) == PUSHED_SCANS + samples = [e for e in shuffles(result) if "_pd_chunk" in e] + assert len(samples) == 1 and samples[0].startswith(SAMPLE_SHUFFLE) + # The grouped-map UDF still groups the shipped rows by container. + assert container_shuffles_of_channel_data(result) == 1 + + def test_channels_used_raw_add_one_read(self, spark: SparkSession, key_value_store_db): + q = key_value_store_db.query + result = q.select( + q.channel(channel_name="Engine RPM").resample(RATE).alias("rpm"), + q.channel(channel_name="Vehicle Speed Sensor").max().alias("speed_max"), + ).solve(spark, solver=DefaultSolver(spark, config=_kvs_config(), enable_pushdown=True)) + rows = result.collect() + assert rows and all(row["speed_max"] > 0 for row in rows) + assert channel_data_scans(result) == MIXED_SCANS + + def test_raw_use_of_the_same_channel_ships_raw_rows_only( + self, spark: SparkSession, key_value_store_db, udf_rows + ): + def selections(q): + rpm = q.channel(channel_name="Engine RPM") + return [rpm.resample(RATE).alias("rpm_10s"), rpm.max().alias("rpm_max")] + + off, on = _solve_both(spark, key_value_store_db, selections, config=_kvs_config()) + _assert_same(on, off) + rows_off, rows_on = udf_rows + # Nothing is pushed down, so the frame keeps today's shape. + assert rows_on == rows_off + + def test_mixed_channels(self, spark: SparkSession, key_value_store_db, udf_rows): + def selections(q): + rpm = q.channel(channel_name="Engine RPM") + spd = q.channel(channel_name="Vehicle Speed Sensor") + return [ + rpm.resample(RATE).alias("rpm_10s"), + spd.max().alias("spd_max"), + spd.resample(RATE).alias("spd_10s"), + ] + + off, on = _solve_both(spark, key_value_store_db, selections, config=_kvs_config()) + _assert_same(on, off) + rows_off, rows_on = udf_rows + assert rows_on == { + (7, RAW_VARIANT): rows_off[(7, None)], + (5, 1): _derived_rows(off, "rpm_10s"), + } + + def test_two_rates_and_nested_ops(self, spark: SparkSession, key_value_store_db, udf_rows): + def selections(q): + rpm = q.channel(channel_name="Engine RPM") + return [ + rpm.resample(RATE).cumtrapz().alias("rpm_integral"), + (rpm.resample(RATE / 2) > 1500).alias("rpm_high"), + rpm.resample(RATE / 2).mean().alias("rpm_mean"), + ] + + off, on = _solve_both(spark, key_value_store_db, selections, config=_kvs_config()) + _assert_same(on, off) + assert any(len(intervals) > 0 for intervals in on["rpm_high"]) + _, rows_on = udf_rows + assert set(rows_on) == {(5, 1), (5, 2)} + + def test_aliased_selector_with_unit_conversion( + self, spark: SparkSession, key_value_store_unit_conversion_db, udf_rows + ): + def selections(q): + speed = q.channel_with_alias(channel_alias="vehicle_speed") + return [speed.resample(RATE).alias("speed_10s")] + + off, on = _solve_both( + spark, key_value_store_unit_conversion_db, selections, config=_unit_conversion_config() + ) + _assert_same(on, off) + _, rows_on = udf_rows + assert {variant for _, variant in rows_on} == {1} + + def test_alongside_points_in_time_selection( + self, spark: SparkSession, basic_narrow_db, udf_rows + ): + def selections(q): + amb = q.channel(channel_name="Ambient Air Temperature") + dtc_count = q.poi_channel(channel_name="DTC_count") + rpm = q.channel(channel_name="Engine RPM") + return [ + amb.where(dtc_count.to_points_in_time()).alias("sampled"), + rpm.resample(RATE).alias("rpm_10s"), + ] + + off, on = _solve_both(spark, basic_narrow_db, selections) + _assert_same(on, off) + _, rows_on = udf_rows + assert rows_on[(91, RAW_VARIANT)] == 3 # POI rows keep the raw variant + assert {variant for (ch, variant) in rows_on if ch == 5} == {1} + + def test_raw_data_with_trailing_point( + self, spark: SparkSession, setup_raw_channels_db, mock_workspace_client + ): + tables = { + name: spark.read.table(f"spark_catalog.silver_raw.{name}") + for name in ("container_metrics", "channel_metrics", "channels") + } + db = MeasurementDB(MeasurementDBConfig.for_debug(tables), ws=mock_workspace_client) + off, on = _solve_both( + spark, + db, + lambda q: [q.channel(channel_name="Engine RPM").resample(2e6).alias("rpm")], + is_raw_data=True, + ) + _assert_same(on, off) + rpm = on["rpm"].iloc[0] + assert rpm.tstarts[-1] == rpm.tends[-1] # the series ends on a point + + @pytest.mark.parametrize("channel_id_type", ["int", "string"]) + def test_channel_id_types( + self, + spark: SparkSession, + setup_key_value_store_db, + mock_workspace_client, + udf_rows, + channel_id_type, + ): + tables = { + name: spark.read.table(f"spark_catalog.silver_key_value_store.{name}") + for name in ("container_tags", "container_metrics", "channel_metrics", "channels") + } + for name in ("channel_metrics", "channels"): + tables[name] = tables[name].withColumn( + "channel_id", F.col("channel_id").cast(channel_id_type) + ) + db = MeasurementDB(MeasurementDBConfig.for_debug(tables), ws=mock_workspace_client) + off, on = _solve_both( + spark, + db, + lambda q: [q.channel(channel_name="Engine RPM").resample(RATE).alias("rpm")], + config=_kvs_config(), + ) + _assert_same(on, off) + assert all(np.isfinite(s.values).all() and len(s) > 1 for s in on["rpm"]) + _, rows_on = udf_rows + assert {variant for _, variant in rows_on} == {1} + + +class TestCalculatedChannels: + def test_calculated_channel_with_resample( + self, spark: SparkSession, key_value_store_db, udf_rows + ): + results = [] + for enable_pushdown in (False, True): + q = key_value_store_db.query + rpm = q.channel(channel_name="Engine RPM") + channel = CalculatedChannel(rpm.resample(RATE) * 2, {"channel_name": "rpm_x2"}) + solver = DefaultSolver(spark, config=_kvs_config(), enable_pushdown=enable_pushdown) + df = q.select(channel).solve_calculated_channels(spark, solver=solver) + results.append( + df.drop("identity") + .toPandas() + .sort_values(["container_id", "tstart"]) + .reset_index(drop=True) + ) + off, on = results + assert len(off) > 0 + assert off["value"].gt(0).any() + pd.testing.assert_frame_equal(on, off) + assert set(udf_rows[1]) == {(5, 1)} + + +class TestTimeSeriesCacheVariants: + """Channel 5 carries raw rows (variant 0) and ``resample(10)`` rows (variant 1).""" + + @staticmethod + def _cache(rule, node) -> TimeSeriesCache: + pushdown = Classifier([rule])(node) + plan = PushdownPlan.build([rule], [pushdown], [pushdown.selector.selector_id]) + pdf = pd.DataFrame( + { + "container_id": [1] * 5, + "channel_id": [5] * 5, + VARIANT_COL: [1, 1, 0, 0, 0], + "tstart": [0, 10, 0, 5, 10], + "tend": [10, 20, 5, 10, 20], + "value": [1.0, 3.0, 1.0, 2.0, 3.0], + "conversion_factor": [2.0] * 5, + "selector_ids": [[pushdown.selector.selector_id], None, None, None, None], + } + ) + col_map = { + "cid": "container_id", + "ch": "channel_id", + "ts": "tstart", + "te": "tend", + "val": "value", + "conv": "conversion_factor", + } + return TimeSeriesCache(pdf, col_map=col_map, plan=plan) + + def test_raw_and_derived_rows_are_served_separately(self): + node = _rpm().resample(10) + cache = self._cache(ResamplePushdownRule(), node) + assert VARIANT_COL not in cache.mdf.columns + nptest.assert_array_equal(cache.load_blob(1, 5).tstarts, [0, 5, 10]) + derived = node.build(cache) + nptest.assert_array_equal(derived.tstarts, [0, 10]) + nptest.assert_array_equal(derived.values, [1.0, 3.0]) + + def test_conversion_factor_applies_to_aliased_derived_rows(self): + node = _rpm(uses_alias=True).resample(10) + cache = self._cache(ResamplePushdownRule(), node) + nptest.assert_array_equal(node.build(cache).values, [2.0, 6.0]) + + def test_falls_back_without_derived_rows(self): + rpm = _rpm() + cache = self._cache(ResamplePushdownRule(), rpm.resample(10)) + other_rate = rpm.resample(5) # not part of the plan + assert cache.materialized(other_rate) is None + nptest.assert_array_equal(other_rate.build(cache).tstarts, [0, 5, 10, 15]) + + def test_rule_decides_the_result_type(self): + class _IntervalsRule(ResamplePushdownRule): + def decode(self, rows, pushdown, cache): + return Intervals( + rows[cache.tstart_col].to_numpy(), rows[cache.tend_col].to_numpy() + ) + + node = _rpm().resample(10) + result = self._cache(_IntervalsRule(), node).materialized(node) + assert isinstance(result, Intervals) + nptest.assert_array_equal(result.tstarts, [0, 10]) + + def test_each_node_is_classified_once_per_container(self): + matched = [] + + class _Recording(ResamplePushdownRule): + def match(self, node, operand): + matched.append(node) + return super().match(node, operand) + + resampled = _rpm().resample(10) + cache = self._cache(_Recording(), resampled) + matched.clear() + integral = resampled.cumtrapz().build(cache) + # The root and the served resample, each matched once across both lookups. + assert len(matched) == 2 and len({id(node) for node in matched}) == 2 + nptest.assert_array_equal(integral.tstarts, [0, 10]) + + def test_optimizer_default_rules(self): + assert [type(r) for r in QueryOptimizer().rules] == [ResamplePushdownRule] + + +def _rpm(uses_alias: bool = False) -> TimeSeriesSelector: + return TimeSeriesSelector(TagSelector("channel_name") == "rpm", uses_alias=uses_alias) diff --git a/tests/impulse_reporting/integration/calculated_channel_test.py b/tests/impulse_reporting/integration/calculated_channel_test.py index 5a426e8e..44ba2eb2 100644 --- a/tests/impulse_reporting/integration/calculated_channel_test.py +++ b/tests/impulse_reporting/integration/calculated_channel_test.py @@ -9,6 +9,7 @@ from unittest.mock import create_autospec +import pandas as pd import pyspark.sql.functions as F import pyspark.sql.types as T import pytest @@ -614,3 +615,51 @@ def test_channel_metrics_custom_kpis(spark): assert "max" in metrics.columns assert "mean" not in metrics.columns assert "duration" not in metrics.columns + + +def test_resampled_channel_facts_are_identical_with_pushdown(spark, monkeypatch): + """A resample-only calculated channel is solved in Spark without the pandas UDF; + its gold facts equal the ones written without pushdown.""" + from impulse_query_engine.analyze.query.solvers.default_solver import DefaultSolver + + udf_runs = [] + original = DefaultSolver._apply_grouped_map + + def _counting(self, *args, **kwargs): + udf_runs[-1] += 1 + return original(self, *args, **kwargs) + + monkeypatch.setattr(DefaultSolver, "_apply_grouped_map", _counting) + facts = [] + for enable_pushdown in (False, True): + udf_runs.append(0) + config = _config() + config.query_engine = QueryEngine(enable_pushdown=enable_pushdown) + report = Report( + name="calc_channel_report", + spark=spark, + workspace_client=create_autospec(WorkspaceClient), + config=dict(config), + ) + q = report.get_db().query + report.add_calculated_channel( + CalculatedChannel( + name="speed_10s", + expr=q.channel(channel_name="Vehicle Speed Sensor").resample(1e7), + identity={"channel_name": "speed_10s", "data_key": "CALC"}, + ) + ) + report.determine_report() + report.persist_results() + facts.append( + spark.read.table(_FACT) + .drop("_created_at") + .toPandas() + .sort_values(["container_id", "channel_id", "tstart"]) + .reset_index(drop=True) + ) + off, on = facts + assert udf_runs == [1, 0] + assert len(off) > 0 + assert off["value"].gt(0).any() + pd.testing.assert_frame_equal(on, off, check_exact=True) diff --git a/tests/impulse_reporting/unit/config/config_parser_test.py b/tests/impulse_reporting/unit/config/config_parser_test.py index b935f0d0..287dc83b 100644 --- a/tests/impulse_reporting/unit/config/config_parser_test.py +++ b/tests/impulse_reporting/unit/config/config_parser_test.py @@ -532,6 +532,18 @@ def test_impulse_config_from_dict_no_query_engine_provided(): assert config.query_engine.solver == Solvers.DEFAULT_SOLVER +@pytest.mark.parametrize( + ("query_engine", "expected"), [({}, False), ({"enable_pushdown": True}, True)] +) +def test_impulse_config_enable_pushdown(query_engine, expected): + """Spark pushdown is opt-in.""" + config_json = { + **impulse_config_JSON, + "query_engine": {"solver": "DefaultSolver", **query_engine}, + } + assert ImpulseConfig.model_validate(config_json).query_engine.enable_pushdown is expected + + def test_impulse_config_from_dict_no_measurement_dim_provided(): """Test ImpulseConfig with no measurement dimension info provided.""" config_json = impulse_config_JSON.copy() diff --git a/tests/impulse_reporting/unit/core/report_test.py b/tests/impulse_reporting/unit/core/report_test.py index 696125d4..a60493af 100644 --- a/tests/impulse_reporting/unit/core/report_test.py +++ b/tests/impulse_reporting/unit/core/report_test.py @@ -314,6 +314,25 @@ def test_create_solver_key_value_store_with_solver_config(): assert solver.config.project_id_col == "project_id" +@pytest.mark.parametrize("enable_pushdown", [False, True]) +def test_create_solver_forwards_enable_pushdown(enable_pushdown): + """``query_engine.enable_pushdown`` decides whether the solver gets an optimizer.""" + config = { + **DUMMY_KEY_VALUE_STORE_CONFIG, + "query_engine": { + **DUMMY_KEY_VALUE_STORE_CONFIG["query_engine"], + "enable_pushdown": enable_pushdown, + }, + } + report = Report( + name="test_report", + spark=None, + workspace_client=create_autospec(WorkspaceClient), + config=config, + ) + assert (report.get_solver().optimizer is not None) is enable_pushdown + + def test_create_solver_delta_default_config(): """DeltaSolver created without solver_config gets a default SolverConfig (no renames).""" from impulse_query_engine.analyze.query.solvers.default_solver import DefaultSolver