///|
/// Mutable fixed locations. new borrows an array; from_view retains its stride.
pub struct FixedLocator {
priv locations : TickValues
priv mut nbins : Double?
}
///|
/// Retain source order, duplicates and nonfinite locations.
///
/// ```mbt check
/// test {
/// let positions = [4.0, 1.0, 0.0, -1.0, -4.0]
/// let locator = @ticker.FixedLocator::new(positions, nbins=2.0)
/// let ticks = locator.tick_values(100.0, -100.0)
/// assert_eq(ticks.to_array(), [0.0])
/// ticks.set(0, 0.5)
/// assert_eq(positions[2], 0.5)
/// }
/// ```
pub fn FixedLocator::new(
locations : Array[Double],
nbins? : Double,
) -> FixedLocator {
FixedLocator::from_view(TickValues::from_array(locations), nbins?)
}
///|
/// Borrow an existing view, retaining its base buffer, shape and signed stride.
pub fn FixedLocator::from_view(
locations : TickValues,
nbins? : Double,
) -> FixedLocator {
let nbins = nbins.map(value => if value < 2.0 { 2.0 } else { value })
{ locations, nbins, }
}
///|
/// Python-list-style construction, independent from the supplied array.
pub fn FixedLocator::from_list(
locations : Array[Double],
nbins? : Double,
) -> FixedLocator {
FixedLocator::new(locations.copy(), nbins?)
}
///|
/// Borrow the live locations buffer, corresponding to the source locs attribute.
pub fn FixedLocator::locations(self : FixedLocator) -> TickValues {
self.locations
}
///|
pub fn FixedLocator::nbins(self : FixedLocator) -> Double? {
self.nbins
}
///|
/// Omitted/None leaves nbins unchanged. Source validation is deferred to ticks.
pub fn FixedLocator::set_params(self : FixedLocator, nbins? : Double) -> Unit {
if nbins is Some(value) {
self.nbins = Some(value)
}
}
///|
/// Shallow source-style copy: share locations, independently replace nbins.
pub fn FixedLocator::copy(self : FixedLocator) -> FixedLocator {
{ locations: self.locations, nbins: self.nbins, }
}
///|
fn fixed_minimum_absolute(
locations : TickValues,
first : Int32,
stride : Int32,
) -> Double raise @core.DataError {
let mut smallest = Double::abs(locations.get(first))
let mut index = first
while index < locations.length() {
let candidate = Double::abs(locations.get(index))
if candidate.is_nan() || candidate < smallest {
smallest = candidate
}
if stride >= locations.length() - index {
break
}
index += stride
}
smallest
}
///|
fn fixed_integer_absolute(value : Int64) -> Int64 {
if value < 0L {
(0UL - value.reinterpret_as_uint64()).reinterpret_as_int64()
} else {
value
}
}
///|
// NumPy abs/min retains Int64 storage: abs(Int64.min) wraps to Int64.min.
// Comparing through Double would also collapse distinct integers above 2^53.
fn fixed_minimum_integer_absolute(
locations : TickValues,
first : Int32,
stride : Int32,
) -> Int64 raise @core.DataError {
let mut smallest = fixed_integer_absolute(locations.get_int64(first))
let mut index = first
while index < locations.length() {
let candidate = fixed_integer_absolute(locations.get_int64(index))
if candidate < smallest {
smallest = candidate
}
if stride >= locations.length() - index {
break
}
index += stride
}
smallest
}
///|
/// Ignore bounds and return a borrowed, possibly strided view.
pub fn FixedLocator::tick_values(
self : FixedLocator,
_lower : Double,
_upper : Double,
) -> TickValues raise @core.DataError {
guard self.nbins is Some(nbins) else { return self.locations }
if nbins == 0.0 {
raise @core.DataError::InvalidTicks(
message="fixed locator nbins divides by zero",
)
}
let stride = (Double::from_int(self.locations.length()) / nbins).ceil()
if stride.is_nan() || stride.is_inf() {
raise @core.DataError::InvalidTicks(
message="fixed locator subsampling stride is not an integer",
)
}
let stride = if stride < 1.0 { 1.0 } else { stride }
if stride > Double::from_int(self.locations.length()) && stride > 1.0 {
raise @core.DataError::InvalidTicks(
message="fixed locator empty subsample has no minimum",
)
}
let stride = stride.to_int()
let mut first = 0
if stride > 1 {
if self.locations.is_int64() {
let mut smallest = fixed_minimum_integer_absolute(
self.locations,
0,
stride,
)
for candidate in 1..= self.locations.length() {
0
} else {
1 + (self.locations.length() - 1 - first) / stride
}
self.locations.view(first, stride, length)
}
///|
/// Source __call__ without an axis binding: locations never depend on bounds.
pub fn FixedLocator::values(
self : FixedLocator,
) -> TickValues raise @core.DataError {
self.tick_values(0.0, 0.0)
}
///|
pub fn FixedLocator::nonsingular(
_self : FixedLocator,
lower : Double,
upper : Double,
) -> (Double, Double) {
locator_nonsingular(lower, upper, 0.05)
}
///|
pub fn FixedLocator::view_limits(
_self : FixedLocator,
lower : Double,
upper : Double,
) -> (Double, Double) {
locator_nonsingular(lower, upper, 0.001)
}