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(())
}