Description
balanced_allocation silently skips its balancing loop when source IDs are non-integer values (e.g. 0.1, 0.3, 0.7).
_allocate_biased casts the allocation array to float32 at line 202. The iteration loop then compares this float32 array against float64 source IDs at line 359:
weights = np.array([
float(np.sum(fric_weight[alloc == sid]))
for sid in source_ids
])
NumPy upcasts the float32 alloc to float64 for the comparison, but the round-trip float64 -> float32 -> float64 changes non-integer values:
>>> np.float64(np.float32(0.1)) == np.float64(0.1)
False
alloc == sid produces all False, every weight comes out 0, total == 0, and the loop breaks on the first iteration without ever adjusting biases.
_allocate_from_costs has the same float32 cast at line 161.
Reproduction
import numpy as np
import xarray as xr
from xrspatial.balanced_allocation import balanced_allocation
h, w = 12, 12
data = np.zeros((h, w), dtype=np.float64)
data[2, 6] = 0.1
data[9, 2] = 0.3
data[9, 10] = 0.7
raster = xr.DataArray(data, dims=['y', 'x'], attrs={'res': (1.0, 1.0)})
raster['y'] = np.arange(h, dtype=np.float64)
raster['x'] = np.arange(w, dtype=np.float64)
friction = raster.copy(data=np.ones((h, w)))
result = balanced_allocation(raster, friction, tolerance=0.15)
out = result.values
# Source 0.1 gets 45% instead of ~33%; source 0.7 gets 23% instead of ~33%
for sid in [0.1, 0.3, 0.7]:
n = np.sum(out == np.float32(sid))
print(f'{sid}: {n / np.sum(np.isfinite(out)):.1%}')
Expected behavior
Territories should balance to within the requested tolerance regardless of whether source IDs are integers.
Fix
Keep the allocation array as float64 through the iteration loop. Cast to float32 only at the final return. Affects _allocate_biased (line 202) and _allocate_from_costs (line 161).
Description
balanced_allocationsilently skips its balancing loop when source IDs are non-integer values (e.g. 0.1, 0.3, 0.7)._allocate_biasedcasts the allocation array tofloat32at line 202. The iteration loop then compares thisfloat32array againstfloat64source IDs at line 359:NumPy upcasts the
float32alloc tofloat64for the comparison, but the round-tripfloat64 -> float32 -> float64changes non-integer values:alloc == sidproduces allFalse, every weight comes out 0,total == 0, and the loop breaks on the first iteration without ever adjusting biases._allocate_from_costshas the same float32 cast at line 161.Reproduction
Expected behavior
Territories should balance to within the requested tolerance regardless of whether source IDs are integers.
Fix
Keep the allocation array as
float64through the iteration loop. Cast tofloat32only at the final return. Affects_allocate_biased(line 202) and_allocate_from_costs(line 161).