quanticsgrids user guide
This library provides utilities for working with functions on quantics grids and for converting between quantics indices, grid indices, and original coordinates.
quanticsgrids provides two main layers:
- Low-level discrete grids with integer coordinates
- High-level discretized grids over continuous domains
Most users will want the high-level interface built around
DiscretizedGrid. The following chapters mirror the Julia guide while using the
Rust API and Rust idioms.
Installation
This crate is not yet published on crates.io. For now, depend on the GitHub repository directly.
Add this to Cargo.toml:
[dependencies]
quanticsgrids = { git = "https://github.com/tensor4all/quanticsgrids-rs" }
Or use cargo add:
cargo add quanticsgrids --git https://github.com/tensor4all/quanticsgrids-rs
Definition
We first introduce a base-B representation with B = 2, 3, 4, ....
Throughout this guide, quantics digits and grid indices are 0-based
(0-indexed), matching the Rust convention. QuanticsGrids.jl is 1-based;
when porting Julia scripts, subtract 1 from grid indices and quantics
digits at the call boundary.
We represent a non-negative integer X >= 0 as
$$ X = \sum_{i=1}^{R} x_i B^{R-i}, $$
where each digit x_i satisfies 0 <= x_i < B and R is the number of
digits. In this crate, the base-B representation of X is stored as the
vector
$$ [x_1, \ldots, x_R]. $$
For multiple variables, the crate supports fused and interleaved unfolding
schemes. For three variables X, Y, and Z, suppose their base-B
representations are
$$ [x_1, \ldots, x_R], \quad [y_1, \ldots, y_R], \quad [z_1, \ldots, z_R]. $$
The interleaved representation is
$$ [x_1, y_1, z_1, x_2, y_2, z_2, \ldots, x_R, y_R, z_R]. $$
The fused representation is
$$ [\alpha_1, \alpha_2, \ldots, \alpha_R], $$
where
$$ \alpha_i = x_i + B y_i + B^2 z_i $$
and therefore
$$ 0 \le \alpha_i < B^3. $$
In fused ordering, the x digit runs fastest at each digit level. This
generalizes to any number of variables.
Discretized Grid
DiscretizedGrid discretizes a continuous d-dimensional domain and supports
conversions between:
- quantics indices
- grid indices
- original coordinates
Unlike the Julia package, the Rust API returns an error when original coordinates fall outside the configured domain.
Creating a one-dimensional grid
We can discretize the interval [0, 1) with R bits as follows:
use quanticsgrids::DiscretizedGrid;
fn main() -> quanticsgrids::Result<()> {
let r = 4;
let grid = DiscretizedGrid::builder(&[r])
.with_bounds(0.0, 1.0)
.build()?;
let quantics = vec![0; r];
let grididx = vec![0];
assert_eq!(grid.quantics_to_grididx(&quantics)?, grididx);
assert_eq!(grid.grididx_to_quantics(&grididx)?, quantics);
assert!((grid.grididx_to_origcoord(&grididx)?[0] - 0.0).abs() < 1e-12);
assert_eq!(grid.origcoord_to_grididx(&[0.0])?, grididx);
let quantics = vec![1; r];
let grididx = vec![2usize.pow(r as u32) - 1];
let x = 1.0 - 1.0 / 2.0_f64.powi(r as i32);
assert_eq!(grid.quantics_to_grididx(&quantics)?, grididx);
assert_eq!(grid.grididx_to_quantics(&grididx)?, quantics);
assert!((grid.quantics_to_origcoord(&quantics)?[0] - x).abs() < 1e-12);
assert!((grid.grididx_to_origcoord(&grididx)?[0] - x).abs() < 1e-12);
assert_eq!(grid.origcoord_to_grididx(&[x])?, grididx);
assert_eq!(grid.origcoord_to_quantics(&[x])?, quantics);
Ok(())
}
Supported resolutions
Coordinates are f64, so grid points must stay distinguishable: the rounding
error of a conversion is about one ulp of the bounds, and a grid whose step falls
below that limit cannot map coordinates to indices exactly. The limit therefore
depends on the bounds, not on R alone:
| Bounds | Highest supported R |
|---|---|
[0, 1) | 50 |
symmetric O(1), e.g. [-1.5, 1.5) | 51 |
[0.3, 10.3) | 50 |
[1e10, 1e10 + 1) | 17 |
[1e12, 1e12 + 1e6) | 30 |
build() returns QuanticsGridError::GridNotRepresentable above these, instead
of producing a grid whose conversions silently return a neighbouring index. The
check is conservative (step of at least four ulps of the bounds, plus the last
two grid points checked), so it can also reject a grid that would happen to be
exact. Within the accepted range,
origcoord_to_grididx(&grididx_to_origcoord(&[g])?) returns g exactly.
Mapping coordinates to indices
A coordinate c maps to the index round((c - lower_bound) / grid_step), clamped
to 0..=N-1; a normalized index exactly halfway between two indices rounds to
the larger one. The rounding applies to that computed ratio, so the result is not
promised to be the geometrically nearest emitted coordinate in every case.
The grid does not wrap around: in the half-open case a coordinate in the last step
below the upper bound keeps the last grid point. Coordinates outside the domain
([lower, upper), or [lower, upper] with include_endpoint(true)) return
QuanticsGridError::CoordinateOutOfBounds.
The Rust builder also supports including the upper endpoint:
use quanticsgrids::DiscretizedGrid;
fn main() -> quanticsgrids::Result<()> {
let r = 4;
let grid = DiscretizedGrid::builder(&[r])
.with_bounds(0.0, 1.0)
.include_endpoint(true)
.build()?;
assert!((grid.grididx_to_origcoord(&[0])?[0] - 0.0).abs() < 1e-12);
assert!((grid.grididx_to_origcoord(&[2usize.pow(r as u32) - 1])?[0] - 1.0).abs() < 1e-12);
Ok(())
}
Creating a d-dimensional grid
A d-dimensional grid is created in the same way by supplying one resolution
per dimension. You can choose either fused or interleaved unfolding.
Fused representation
use quanticsgrids::{DiscretizedGrid, UnfoldingScheme};
fn main() -> quanticsgrids::Result<()> {
let r = 4;
let grid = DiscretizedGrid::builder(&[r, r, r])
.with_lower_bound(&[0.0, 0.0, 0.0])
.with_upper_bound(&[1.0, 1.0, 1.0])
.with_unfolding_scheme(UnfoldingScheme::Fused)
.build()?;
let quantics = vec![0; r];
let grididx = vec![0, 0, 0];
assert_eq!(grid.quantics_to_grididx(&quantics)?, grididx);
assert_eq!(grid.grididx_to_quantics(&grididx)?, quantics);
assert_eq!(grid.origcoord_to_grididx(&[0.0, 0.0, 0.0])?, grididx);
let quantics = vec![0, 0, 0, 1];
let grididx = vec![1, 0, 0];
assert_eq!(grid.quantics_to_grididx(&quantics)?, grididx);
assert_eq!(grid.grididx_to_quantics(&grididx)?, quantics);
let coord = grid.quantics_to_origcoord(&quantics)?;
assert!((coord[0] - 1.0 / 16.0).abs() < 1e-12);
assert!((coord[1] - 0.0).abs() < 1e-12);
assert!((coord[2] - 0.0).abs() < 1e-12);
Ok(())
}
Incrementing the least significant fused index increments the x coordinate
first.
Interleaved representation
use quanticsgrids::{DiscretizedGrid, UnfoldingScheme};
fn main() -> quanticsgrids::Result<()> {
let r = 4;
let grid = DiscretizedGrid::builder(&[r, r, r])
.with_lower_bound(&[0.0, 0.0, 0.0])
.with_upper_bound(&[1.0, 1.0, 1.0])
.with_unfolding_scheme(UnfoldingScheme::Interleaved)
.build()?;
let quantics = vec![0; 3 * r];
let grididx = vec![0, 0, 0];
assert_eq!(grid.quantics_to_grididx(&quantics)?, grididx);
assert_eq!(grid.grididx_to_quantics(&grididx)?, quantics);
assert_eq!(grid.origcoord_to_grididx(&[0.0, 0.0, 0.0])?, grididx);
let quantics = vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0];
let grididx = vec![1, 0, 0];
assert_eq!(grid.quantics_to_grididx(&quantics)?, grididx);
assert_eq!(grid.grididx_to_quantics(&grididx)?, quantics);
let coord = grid.quantics_to_origcoord(&quantics)?;
assert!((coord[0] - 1.0 / 16.0).abs() < 1e-12);
assert!((coord[1] - 0.0).abs() < 1e-12);
assert!((coord[2] - 0.0).abs() < 1e-12);
Ok(())
}
Inherent Discrete Grid
InherentDiscreteGrid is the low-level grid type for discrete target spaces. It
has an interface parallel to DiscretizedGrid, but original coordinates are
integer-valued and are defined by an origin and a step size.
use quanticsgrids::InherentDiscreteGrid;
fn main() -> quanticsgrids::Result<()> {
let r = 4;
let grid = InherentDiscreteGrid::builder(&[r])
.with_origin(&[0])
.with_step(&[1])
.build()?;
let quantics = vec![0; r];
let grididx = vec![0];
let origcoord = vec![0];
assert_eq!(grid.quantics_to_grididx(&quantics)?, grididx);
assert_eq!(grid.grididx_to_quantics(&grididx)?, quantics);
assert_eq!(grid.grididx_to_origcoord(&grididx)?, origcoord);
assert_eq!(grid.origcoord_to_grididx(&origcoord)?, grididx);
assert_eq!(grid.origcoord_to_quantics(&origcoord)?, quantics);
let quantics = vec![1; r];
let grididx = vec![2usize.pow(r as u32) - 1];
let origcoord = vec![2_i64.pow(r as u32) - 1];
assert_eq!(grid.quantics_to_grididx(&quantics)?, grididx);
assert_eq!(grid.grididx_to_quantics(&grididx)?, quantics);
assert_eq!(grid.grididx_to_origcoord(&grididx)?, origcoord);
assert_eq!(grid.origcoord_to_grididx(&origcoord)?, grididx);
assert_eq!(grid.origcoord_to_quantics(&origcoord)?, quantics);
Ok(())
}
As with DiscretizedGrid, the Rust API returns an error when original
coordinates fall outside the representable range.
Use a Base Other Than 2
You can choose a base other than 2 when building a grid. The example below
uses base 10.
use quanticsgrids::InherentDiscreteGrid;
fn main() -> quanticsgrids::Result<()> {
let r = 4;
let base = 10;
let grid = InherentDiscreteGrid::builder(&[r])
.with_origin(&[0])
.with_step(&[1])
.with_base(base)
.build()?;
let quantics = vec![base - 1; r];
let grididx = vec![base.pow(r as u32) - 1];
let origcoord = vec![(base as i64).pow(r as u32) - 1];
assert_eq!(grid.quantics_to_grididx(&quantics)?, grididx);
assert_eq!(grid.grididx_to_quantics(&grididx)?, quantics);
assert_eq!(grid.grididx_to_origcoord(&grididx)?, origcoord);
assert_eq!(grid.origcoord_to_grididx(&origcoord)?, grididx);
assert_eq!(grid.origcoord_to_quantics(&origcoord)?, quantics);
Ok(())
}
Quantics Function
When working with algorithms that expect a function of quantics indices, you can
wrap a coordinate-space function with quantics_function.
use quanticsgrids::{DiscretizedGrid, quantics_function};
fn main() -> quanticsgrids::Result<()> {
let grid = DiscretizedGrid::builder(&[4, 4]).build()?;
let f = |coords: &[f64]| coords[0].sin() + coords[1];
let fq = quantics_function(&grid, f);
let quantics = grid.origcoord_to_quantics(&[0.0, 0.5])?;
let coords = grid.quantics_to_origcoord(&quantics)?;
let value = fq(&quantics)?;
assert!((value - (coords[0].sin() + coords[1])).abs() < 1e-12);
Ok(())
}
This is the Rust equivalent of first defining a function in the original coordinate space and then obtaining a quantics-index wrapper around it.