Examples

Short, self-contained snippets for each part of silt’s API. Tensors default to CPU; the sparse and RNG sections require CUDA.

Tensor Construction

import numpy as np
import silt

s = silt.shape(512, 512)                      # up to 4 dimensions
t = silt.tensor(silt.float32, s)               # host defaults to CPU
t = silt.tensor(silt.float32, s, silt.gpu)     # explicit host

z = silt.zeros((256, 256))                     # convenience constructors
o = silt.ones((256, 256), dtype=silt.float64)
f = silt.full((256, 256), 3.5)
e = silt.empty((256, 256))                     # uninitialized
l = silt.like(t)                               # same shape/dtype/host as t

a = silt.arange(10)                            # 1D, 0..9
r = silt.linspace(0.0, 1.0, 100)               # 1D, 100 values in [0, 1]

n = silt.tensor.from_numpy(np.zeros((64, 64), dtype=np.float32))
# silt.tensor.from_torch(torch_tensor) works the same way

Device Placement & Interop

t = silt.zeros((512, 512))       # CPU by default

t.to_gpu()                       # move in place, no-op if already on GPU
t.to_cpu()
t.to(silt.gpu)                   # equivalent to to_gpu()

c = t.copy_to()                  # independent copy, same host as t
c_gpu = t.copy_to(silt.gpu)      # independent copy, explicit host

arr = t.numpy()                  # CPU-only, always copies
tens = t.torch()                 # either host, always copies

silt.synchronize()               # block for outstanding GPU work; raises on error

Common Operations

In-place ops carry a trailing underscore; each has a pure-Python out-of-place counterpart with the same name minus the underscore.

t = silt.zeros((4, 4))
silt.set_(t, 1.0)
silt.add_(t, 2.0)                # scalar
silt.multiply_(t, 3.0)
silt.divide_(t, 2.0)
silt.clamp_(t, 0.0, 1.0)         # float32 only
silt.minimum_(t, 0.5)
silt.maximum_(t, 0.2)

a = silt.ones((4, 4))
b = silt.full((4, 4), 10.0)
silt.add_(a, b)                  # tensor-tensor, same shape/dtype/host
silt.mix_(a, b, 0.25)            # lerp(a, b, 0.25), written into a

# out-of-place: input is left untouched, result is a new tensor
result = silt.add(a, b)
result = silt.multiply(a, 2.0)
result = silt.mix(a, b, 0.5)
result = silt.clamp(a, 0.0, 1.0)

Slicing & Views

Indexing a tensor returns a silt.view – a lightweight window into the same buffer. A trailing comma is required for a 1D index: t[a:b:c] alone passes a bare slice object, not a tuple.

t = silt.zeros((10,))
v = t[2:8:1,]                     # one slice spec per dimension
silt.set_(v, 1.0)                 # writes through to t

t2 = silt.zeros((8, 8))
row = t2[3, 0:8:1]                 # mixed single index + range
silt.set_(row, 5.0)

print(v.slice.offset, v.slice.stride, v.slice.extent)

Reductions

Whole-buffer reductions over dense tensors. min/max skip NaNs; the others do not.

t = silt.tensor.from_numpy(np.array([3.0, -2.0, 5.0], dtype=np.float32))

silt.sum(t)
silt.mean(t)
silt.min(t)
silt.max(t)
silt.var(t)
silt.std(t)
silt.argmin(t)                   # flat index of the first minimum
silt.argmax(t)

Sparse (Indexed) Operations

An index set is a 1D int64 tensor of flat indices, built by compaction on the GPU. Indexed ops read and write only the selected cells; everything else is left untouched.

s = silt.shape(64, 64)

idx = silt.index_radius(s, [32.0, 32.0], 10.0)        # circular region
idx = silt.index_box(s, [10.0, 10.0], [20.0, 20.0])   # axis-aligned box
idx = silt.index_polygon(s, [(0, 0), (10, 0), (10, 10), (0, 10)])

data = silt.zeros(s, host=silt.gpu)
idx = silt.index_range(data, 0.2, 0.8)                # value-based, closed interval
idx = silt.index_greater(data, 0.5)
idx = silt.index_lesser(data, 0.5)
idx = silt.index_match(data, 0.0)                     # exact value

sl = silt.slice(64, 64)
sl.index(0, 10, 1, 20)                                # rows 10..29
idx = silt.index_slice(sl)                            # slice -> index set
t = silt.zeros(s, host=silt.gpu)
other = silt.full(s, 2.0, host=silt.gpu)

silt.indexed_set(t, 1.0, idx)
silt.indexed_add_(t, 1.0, idx)          # scalar
silt.indexed_add_(t, other, idx)        # tensor-tensor
silt.indexed_multiply_(t, 2.0, idx)
silt.indexed_divide_(t, 2.0, idx)
silt.indexed_mix_(t, other, idx, 0.5)

silt.indexed_sum(t, idx)
silt.indexed_mean(t, idx)
silt.indexed_min(t, idx)
silt.indexed_max(t, idx)
silt.indexed_argmin(t, idx)             # flat index into t
silt.indexed_argmax(t, idx)

Random Number Generation

RNG tensors are GPU-only (curand runs on device).

rng = silt.tensor(silt.rng, silt.shape(4096), silt.gpu)
silt.seed(rng, 42, 0)                       # seed, offset

u = silt.sample_uniform(rng)                # [0, 1)
u2 = silt.sample_uniform(rng, -1.0, 1.0)
n = silt.sample_normal(rng)                 # mean 0, std 1
n2 = silt.sample_normal(rng, 5.0, 2.0)

rng2 = silt.rand(silt.shape(4096), seed_value=42)   # allocate + seed in one call