Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@ In this case, `hll` takes a `lg_k` which describes the size of register list.

**`--zipf-s`:** Some distribution requires parameter, like `zipf-s`.
`zipf-s` means skewness, a parameter for zipf distribution.
`--dataset pareto` instead takes `--pareto-alpha` (shape) and `--pareto-scale`
(minimum value, default 1.0); it is unbounded, so it needs `--dtype f64` (or
`i64`, which floors each draw) and ignores `--cardinality`.

**`--cardinality`:** Another parameter required by zipf distribution.
`cardinality` means the size of key-space of this synthesized zipf dataset.
Expand Down
12 changes: 10 additions & 2 deletions aqpbm-cli/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ pub struct SketchbenchArgs {
/// A *list* of specs is a multi-column stream, which only `hydra-*` targets take.
#[arg(long, help_heading = "Dataset")]
pub spec: Option<String>,
/// Inline shape: "uniform" or "zipf". Ignored when `--spec` is
/// Inline shape: "uniform", "zipf" or "pareto". Ignored when `--spec` is
/// set. Required unless `--spec` or `--list-impls` is given: a run either
/// names its shape or names its file, never falls back to one unasked.
#[arg(
Expand All @@ -106,7 +106,8 @@ pub struct SketchbenchArgs {
help_heading = "Dataset"
)]
pub size: Option<usize>,
/// Cardinality (uniform: max key; zipf: key-space size). Required unless
/// Cardinality (uniform: max key; zipf: key-space size; pareto: ignored,
/// it is unbounded). Required unless
/// `--spec` or `--list-impls` is given.
#[arg(
long,
Expand All @@ -118,6 +119,13 @@ pub struct SketchbenchArgs {
/// otherwise.
#[arg(long, required_if_eq("dataset", "zipf"), help_heading = "Dataset")]
pub zipf_s: Option<f64>,
/// Pareto shape `alpha`. Required when `--dataset pareto` is given;
/// ignored otherwise.
#[arg(long, required_if_eq("dataset", "pareto"), help_heading = "Dataset")]
pub pareto_alpha: Option<f64>,
/// Pareto scale (the minimum value). Only read by `--dataset pareto`.
#[arg(long, default_value_t = 1.0, help_heading = "Dataset")]
pub pareto_scale: f64,
/// Item type the value column is generated at: `i64`, `u64`, `f64` or
/// `string`
/// Required unless `--list-impls` is given: read regardless of `--spec`.
Expand Down
24 changes: 21 additions & 3 deletions aqpbm-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ use aqpbm_core::measure::MeasureConfig;
use aqpbm_core::measure::MIN_MERGE_SHARDS;
use aqpbm_core::metrics::{Metric, MetricsMask, Operation, OperationMask};
use aqpbm_datagen::{
ColumnSpec, DataDistribution, GeneratedTable, StringOpts, TableDescription, UniformParameter,
ZipfParameter, RULE_NONE,
ColumnSpec, DataDistribution, GeneratedTable, ParetoParameter, StringOpts, TableDescription,
UniformParameter, ZipfParameter, RULE_NONE,
};
use clap::Parser;
use sketch_bench::params::ParamSet;
Expand Down Expand Up @@ -205,7 +205,25 @@ fn dataset_spec(args: &SketchbenchArgs) -> Result<InputDataSetSpec> {
population_size: cardinality,
seed: args.seed,
}),
other => bail!("unknown dataset shape: {other} (expected uniform|zipf, or use --spec)"),
// Unbounded and continuous, so `--cardinality` has nothing to name. An
// f64 column keeps the draws as drawn; an i64 column floors them (every
// draw is >= scale > 0, so the renderer's truncation is a floor), which
// is what the i64-only exact quantile baseline needs.
"pareto" => {
if !matches!(args.dtype.as_deref(), Some("f64" | "i64")) {
bail!("--dataset pareto needs --dtype f64 or i64 (i64 floors each draw)");
}
DataDistribution::Pareto(ParetoParameter {
alpha: args
.pareto_alpha
.expect("clap requires --pareto-alpha when --dataset pareto"),
scale: args.pareto_scale,
seed: args.seed,
})
}
other => {
bail!("unknown dataset shape: {other} (expected uniform|zipf|pareto, or use --spec)")
}
};
// Only the rows that ingest text read this. Left `None` at the defaults
// so a run that did not ask for the axis keeps the descriptor — and so
Expand Down
79 changes: 79 additions & 0 deletions aqpbm-core/schema/merged_record.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,38 @@
],
"x-schema-version": 5,
"$defs": {
"BurstSpec": {
"type": "object",
"properties": {
"burst_intervals": {
"type": "integer",
"format": "uint",
"minimum": 0
},
"extra_fraction": {
"description": "Additional rows per selected interval, relative to its base size.",
"type": "number",
"format": "double"
},
"interval_rows": {
"type": "integer",
"format": "uint",
"minimum": 0
},
"seed": {
"type": "integer",
"format": "uint64",
"minimum": 0
}
},
"additionalProperties": false,
"required": [
"interval_rows",
"burst_intervals",
"extra_fraction",
"seed"
]
},
"ColumnSpec": {
"description": "One column of a [`crate::TableDescription`].",
"type": "object",
Expand Down Expand Up @@ -453,6 +485,19 @@
"required": [
"kind"
]
},
{
"type": "object",
"properties": {
"kind": {
"type": "string",
"const": "pareto"
}
},
"$ref": "#/$defs/ParetoParameter",
"required": [
"kind"
]
}
]
},
Expand Down Expand Up @@ -607,6 +652,30 @@
"seed"
]
},
"ParetoParameter": {
"description": "Pareto: heavy-tailed, `P(X > x) = (scale / x)^alpha` for `x >= scale`.\nUnbounded above, so like Normal it has no domain.",
"type": "object",
"properties": {
"alpha": {
"type": "number",
"format": "double"
},
"scale": {
"type": "number",
"format": "double"
},
"seed": {
"type": "integer",
"format": "uint64",
"minimum": 0
}
},
"required": [
"alpha",
"scale",
"seed"
]
},
"RunStats": {
"description": "Aggregate across N samples: mean / stddev / optional 95% CI, plus the raw\nsamples the aggregate was computed from.",
"type": "object",
Expand Down Expand Up @@ -781,6 +850,16 @@
"synthetic": {
"type": "object",
"properties": {
"burst": {
"anyOf": [
{
"$ref": "#/$defs/BurstSpec"
},
{
"type": "null"
}
]
},
"description": {
"$ref": "#/$defs/TableDescription"
}
Expand Down
7 changes: 7 additions & 0 deletions aqpbm-core/src/erp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,13 @@ fn distribution_descriptor(workload: &WorkloadDescription) -> serde_json::Value
("standard_deviation".into(), value.standard_deviation),
]),
),
DataDistribution::Pareto(value) => (
"pareto",
BTreeMap::from([
("alpha".into(), value.alpha),
("scale".into(), value.scale),
]),
),
};
Some(ErpDataShape {
cardinality,
Expand Down
4 changes: 2 additions & 2 deletions aqpbm-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ pub mod metrics;
pub use accuracy::GroundTruth;
pub use aqpbm_datagen::{
ColumnData, ColumnItem, ColumnSpec, DataDistribution, DataGenError, GeneratedTable,
NormalParameter, StringOpts, TableDescription, UniformParameter, ZipfParameter,
RULE_MONOTONIC_INCREASE, RULE_NONE,
NormalParameter, ParetoParameter, StringOpts, TableDescription, UniformParameter,
ZipfParameter, RULE_MONOTONIC_INCREASE, RULE_NONE,
};
pub use atomic_costs::{reduce_all, reduce_one, AtomicCostEntry, AtomicCostTable, SkipReason};
pub use benchmark_result::{
Expand Down
33 changes: 30 additions & 3 deletions aqpbm-datagen/src/dist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
//! fixes it — so two columns are independent unless given the same one. Every
//! distribution draws an `f64`, so [`crate::column`]'s pipeline is written once.

use rand_distr::{Distribution as _, Normal, Uniform, Zipf};
use rand_distr::{Distribution as _, Normal, Pareto, Uniform, Zipf};
use rand_xoshiro::Xoshiro256PlusPlus;
use serde::{Deserialize, Serialize};

Expand Down Expand Up @@ -40,6 +40,16 @@ pub struct NormalParameter {
pub seed: u64,
}

/// Pareto: heavy-tailed, `P(X > x) = (scale / x)^alpha` for `x >= scale`.
/// Unbounded above, so like Normal it has no domain.
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ParetoParameter {
pub alpha: f64,
pub scale: f64,
pub seed: u64,
}

/// A column's distribution. Serialises internally tagged, so a spec file reads
/// `distribution: {kind: zipf, skewness: 1.1, population_size: 200, seed: 1}`.
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
Expand All @@ -49,6 +59,7 @@ pub enum DataDistribution {
Zipf(ZipfParameter),
Uniform(UniformParameter),
Normal(NormalParameter),
Pareto(ParetoParameter),
}

/// The half-open span a bounded distribution draws over: the lowest value it can
Expand All @@ -68,6 +79,7 @@ impl DataDistribution {
DataDistribution::Zipf(_) => "zipf",
DataDistribution::Uniform(_) => "uniform",
DataDistribution::Normal(_) => "normal",
DataDistribution::Pareto(_) => "pareto",
}
}

Expand All @@ -77,10 +89,11 @@ impl DataDistribution {
DataDistribution::Zipf(p) => p.seed,
DataDistribution::Uniform(p) => p.seed,
DataDistribution::Normal(p) => p.seed,
DataDistribution::Pareto(p) => p.seed,
}
}

/// The bounded span, or `None` for Normal — which is unbounded, so a
/// The bounded span, or `None` for Normal and Pareto — which are unbounded, so a
/// `cardinality` over it would be a claim this crate cannot keep.
pub fn domain(&self) -> Option<Domain> {
match self {
Expand All @@ -93,7 +106,7 @@ impl DataDistribution {
lower: p.lower_bound,
size: (p.upper_bound - p.lower_bound) as u64,
}),
DataDistribution::Normal(_) => None,
DataDistribution::Normal(_) | DataDistribution::Pareto(_) => None,
}
}

Expand Down Expand Up @@ -139,6 +152,18 @@ impl DataDistribution {
.map_err(|e| bad(format!("normal: {e}")))?,
))
}
DataDistribution::Pareto(p) => {
if !p.alpha.is_finite() || p.alpha <= 0.0 || !p.scale.is_finite() || p.scale <= 0.0
{
return Err(bad(format!(
"pareto: alpha and scale must be finite and > 0, got alpha {} scale {}",
p.alpha, p.scale
)));
}
Ok(Sampler::Pareto(
Pareto::new(p.scale, p.alpha).map_err(|e| bad(format!("pareto: {e}")))?,
))
}
}
}
}
Expand All @@ -149,6 +174,7 @@ pub enum Sampler {
Zipf(Zipf<f64>),
Uniform(Uniform<f64>),
Normal(Normal<f64>),
Pareto(Pareto<f64>),
}

impl Sampler {
Expand All @@ -158,6 +184,7 @@ impl Sampler {
Sampler::Zipf(d) => d.sample(rng),
Sampler::Uniform(d) => d.sample(rng),
Sampler::Normal(d) => d.sample(rng),
Sampler::Pareto(d) => d.sample(rng),
}
}
}
46 changes: 45 additions & 1 deletion aqpbm-datagen/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ pub mod value;
pub use burst::{inject_interval_bursts, BurstSpec};
pub use column::{ColumnSpec, DATA_TYPES};
pub use dist::{
DataDistribution, Domain, NormalParameter, Sampler, UniformParameter, ZipfParameter,
DataDistribution, Domain, NormalParameter, ParetoParameter, Sampler, UniformParameter,
ZipfParameter,
};
pub use error::DataGenError;
pub use rule::{RULE_MASK_ALL, RULE_MONOTONIC_INCREASE, RULE_NONE};
Expand Down Expand Up @@ -48,6 +49,10 @@ mod tests {
})
}

fn pareto(alpha: f64, scale: f64, seed: u64) -> DataDistribution {
DataDistribution::Pareto(ParetoParameter { alpha, scale, seed })
}

fn column(distribution: DataDistribution, data_type: &str) -> ColumnSpec {
ColumnSpec {
distribution,
Expand Down Expand Up @@ -126,6 +131,45 @@ mod tests {
assert!((mean - 1000.0).abs() < 0.2, "mean {mean} not near 1000");
}

/// The MLE of the shape, `n / sum(ln(x / scale))`, recovers `alpha` from a
/// large fixed-seed sample.
#[test]
fn pareto_mle_recovers_alpha() {
let (alpha, scale) = (1.5, 2.0);
let t = one(column(pareto(alpha, scale, 13), "f64"), 200_000)
.generate()
.unwrap();
let values = t.into_column(0).unwrap().into_f64().unwrap();
assert!(values.iter().all(|&x| x >= scale));
let log_sum: f64 = values.iter().map(|x| (x / scale).ln()).sum();
let alpha_hat = values.len() as f64 / log_sum;
assert!(
(alpha_hat - alpha).abs() / alpha < 0.05,
"alpha_hat {alpha_hat} not within 5% of {alpha}"
);
}

/// An i64 column is the f64 draws floored: every draw is >= scale > 0, so
/// the renderer's `as` truncation is a floor. The i64-only exact quantile
/// baseline scores a Pareto stream through this.
#[test]
fn pareto_i64_floors_the_f64_draws() {
let draw = |data_type| {
one(column(pareto(1.1, 1000.0, 7), data_type), 50_000)
.generate()
.unwrap()
.into_column(0)
.unwrap()
};
let floats = draw("f64").into_f64().unwrap();
let ints = draw("i64").into_i64().unwrap();
assert!(ints.iter().all(|&v| v >= 1000));
assert!(ints
.iter()
.zip(&floats)
.all(|(&i, f)| i == f.floor() as i64));
}

// ---------- rendering ----------

/// `data_type` picks the variant, and nothing else about the description
Expand Down
7 changes: 7 additions & 0 deletions docs/aqpbm-datagen.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ enum DataDistribution {
Zipf(ZipfParameter),
Uniform(UniformParameter),
Normal(NormalParameter),
Pareto(ParetoParameter),
}

struct ZipfParameter {
Expand All @@ -60,6 +61,12 @@ struct NormalParameter {
pub standard_deviation: f64,
pub seed: u64,
}

struct ParetoParameter {
pub alpha: f64, // shape
pub scale: f64, // minimum value
pub seed: u64,
}
```

`special_rule` bit map contains the following at this moment:
Expand Down
Loading
Loading