Phasor docs

Phasor docs

Task-oriented notes with complete programs. Each example was compiled and run against the published package, and the output shown is what it printed.

Reading a Pleroma

A store is built item by item with Add, which takes a signed count, and Remove erases one copy. The key view keeps exactly the items with distinct keys up to KeyCapacity and refuses past that. Every other view reads its own slice of the array and stays exact at any load, within the field's range.

TryKeys returns the key, total count and the flag word, arrangement or tick when there is one per key. TryItems returns whole items when each key has one value per face factor. Both return false past capacity, never a wrong answer.

Flags, Near, Nearest
Flags returns the exact count at every flag word. Near(x, r) counts items whose flags differ from x in at most r bits, and Nearest(x) returns the nearest stored words with their distances.
Arrangements, SignBalance
Counts per arrangement of up to 5 things, and the even-minus-odd balance.
Times, Tick, Diffuse
Counts per tick. Tick ages every item, and Diffuse runs a lazy random walk over time on every item at once. Time views then return TimeScale times the exact expected counts.
JointCounts
Set JointFaces, for example Flags and Time, and JointCounts returns the exact joint counts. The joint slice holds the product of the faces' sizes, so size it deliberately.
CountWhere, Compile
Count the items whose flag word satisfies any predicate with one inner product. Compile prepares a predicate for repeated queries, and CountWhereByTime and CountWhereByArrangement give the count per tick or per arrangement.
BitCounts, FlagParity, TryFindConflicts
Per-bit counts and the XOR of every stored word from a few cells, and the keys that carry more than one flag word or arrangement.

Bind two stores

store.Bind(other) binds every pair of items at once: keys add, flag words XOR, arrangements compose, ticks add, and counts multiply. The cost is one product per order representation, not per pair of items. Binding with a one-item store shifts the whole store on every face together, and binding with a larger store applies the mix of all those shifts.

C#
using Phasor;

var shape = new HologramShape { KeyCapacity = 8, FlagBits = 4, TimePeriod = 9 };
var store = new Pleroma(shape);
store.Add(new HologramItem(Key: 10, Flags: 0b0011, Time: 2), 3);
store.Add(new HologramItem(Key: 20, Flags: 0b0111, Time: 5), 1);

// Bind with a one-item store: every item moves by the same key, flags and time shift.
var shift = new Pleroma(shape);
shift.Add(new HologramItem(Key: 1, Flags: 0b0001, Time: 1));
store.Bind(shift);

if (store.TryItems(out var items))
    foreach (var (item, count) in items)
        Console.WriteLine($"{item.Key}: flags {item.Flags}, time {item.Time}, count {count}");
Output
11: flags 2, time 3, count 3
21: flags 6, time 6, count 1

Apply a weighted kernel in one pass

A PleromaKernel is a weighted sum of any number of flag flips, arrangement relabellings and time shifts, held as one harmonic factor per stored harmonic. store.Apply(kernel) gives exactly the store you would get by moving every item by every element and adding the copies, at a cost that does not depend on how many elements the kernel holds. Kernels add and compose, and Diffuse is the two-element case.

The example applies one identity plus two copies of an item that has a flag flipped and a tick added, to three items. Every flag and time count changes at once.

C#
using Phasor;

var store = new Pleroma(new HologramShape
{
    KeyCapacity = 4,
    FlagBits = 3,
    TimePeriod = 7,
    JointFaces = HologramFaces.Flags | HologramFaces.Time,
});
store.Add(new HologramItem(Key: 10, Flags: 0b001, Time: 0), 3);
store.Add(new HologramItem(Key: 20, Flags: 0b010, Time: 2), 2);
store.Add(new HologramItem(Key: 30, Flags: 0b100, Time: 4), 1);
Console.WriteLine("before flags " + string.Join(" ", store.Flags()));
Console.WriteLine("before times " + string.Join(" ", store.Times()));

// One kernel: 1x identity plus 2x (flip flag bit 0 and age one tick), applied to every item in one pass.
var kernel = new PleromaKernel(store.Shape);
kernel.Add(weight: 1L);
kernel.Add(flagMask: 0b001, timeShift: 1, weight: 2L);
store.Apply(kernel);

Console.WriteLine("after flags  " + string.Join(" ", store.Flags()));
Console.WriteLine("after times  " + string.Join(" ", store.Times()));
Output
before flags 0 3 2 0 1 0 0 0
before times 3 0 2 0 1 0 0
after flags  6 3 2 4 1 2 0 0
after times  3 6 2 4 1 2 0

Bit counts and parity

BitCounts gives the exact count of items with each bit set, and FlagParity gives the XOR of every stored word, both from a handful of cells.

C#
using Phasor;

var store = new Pleroma(new HologramShape { FlagBits = 4, KeyCapacity = 8 });
store.Add(new HologramItem(Key: 1, Flags: 0b0011), 2);   // two copies
store.Add(new HologramItem(Key: 1, Flags: 0b0110), 1);   // one copy
store.Add(new HologramItem(Key: 2, Flags: 0b0001), 3);   // three copies

long[] bitCounts = store.BitCounts();                     // exact count of items with each bit set
uint parity = store.FlagParity();                         // XOR of every stored word
Console.WriteLine($"{bitCounts[0]} {bitCounts[1]} {bitCounts[2]} {bitCounts[3]} parity {parity}");
Output
5 3 1 0 parity 7

More operations

ScaleKeys, CombineCycles
With CompleteKeys a store keeps every harmonic of a small key cycle. ScaleKeys(u) relabels every key a to u a in place, and CombineCycles joins two stores on coprime cycles into one on their product, by the Chinese remainder theorem.
Frobenius, ShiftRegister
On a store built without per-bit item lines, Frobenius squares every flag word in GF(2^m) and ShiftRegister steps every word through a linear shift register, exactly, any number of steps in one pass.
Syndrome, IsIndependent, Sample
Count stored words per Hamming syndrome, test in exact integers whether joint counts are the product of their marginals, and draw one value of any view with probability count over total.
RelationFace, ChargeLattice
Store relations between a small set of keys and read a loop's winding, its frustration parity and the direction of its net circulation. Integer offsets on a periodic lattice give each cube an exact charge.
Eliminate
With one flag bit as a label, every stored word is an example, and Eliminate solves for the parity rule over GF(2) that fits every example, or reports that none does.
Dense pages
With CompleteKeys a store is a page of up to 4,096 slots in which every slot reads back exactly. FromCounts and AddCounts build a page from a count vector in one transform, CountWhereKey sums any function of the key in one read, and InnerProduct gives the exact equi-join sum of two pages.
Sparse pages
PleromaSparsePage keeps exact counts over keys anywhere in a huge key set in memory proportional to the keys present, with exact merges and draws.
Redundancy
ReservedKeys with Verify and Repair detect and rebuild lost harmonics, CheckPrime and VerifyCheck detect corruption of either copy, and PleromaParity rebuilds any one lost store of a group.
SQL-style compositions
Restrict, Support (and so COUNT DISTINCT), MapCounts, sums per group and joins are exact, built from the operations above.

CycleHologram: the keys-only case

CycleHologram is the Pleroma with keys only, kept as its own type. The phasor bundle, a sum of phasors, is a hologram: many items superposed in one fixed-size vector, read out approximately, with crosstalk that grows with load. CycleHologram is the same superposition on the discrete cycle of a finite field. Each symbol is an element of the cycle, the hologram holds power sums of the stored elements, and the readout is algebraic instead of statistical, so there is no similarity threshold and no rounding.

A hologram of capacity k holds 2k+1 eight-byte elements, and TryDecode returns every stored symbol with its signed count when at most k distinct symbols are stored. With k+1 distinct symbols decode always refuses. Every decode is re-encoded and must reproduce every stored element. Holograms add as a multiset union, subtract as an exact signed difference, and serialize with their field metadata.

Each replica in the second example holds 10,000 items and the hologram has capacity 4, so it finds what differs by exchanging one small hologram sized to the difference, not to the data. If the replicas differ in more than the capacity, TryDecode returns false, so size the capacity to the largest difference you expect.

The decoder is the syndrome decoder of Reed-Solomon and BCH codes (Berlekamp-Massey), the construction behind the PinSketch set sketch and the minisketch library.

Exact counts
using Phasor;

var stock = new CycleHologram(capacity: 8);
stock.Add(CycleHologram.Symbol("apples"), 10);
stock.Add(42, 5);                                   // integer symbols are exact
stock.Add(CycleHologram.Symbol("oranges"), 3);

if (stock.TryDecode(out var items))
{
    foreach (var (symbol, count) in items.OrderBy(i => i.Item2))
        Console.WriteLine($"count {count}");        // every count is exact
}
Output
count 3
count 5
count 10
Replica reconciliation
using Phasor;

var a = new CycleHologram(capacity: 4);
var b = new CycleHologram(capacity: 4);
for (ulong id = 0; id < 10_000; id++) { a.Add(id); b.Add(id); }
a.Add(777_001);        // only on A
b.Add(777_002, 2);     // only on B, two copies

byte[] wire = a.Serialize();                   // what A sends
Console.WriteLine($"wire size {wire.Length} bytes");
var delta = CycleHologram.Deserialize(wire);
delta.Subtract(b);                             // B subtracts its own hologram: A minus B
if (delta.TryDecode(out var diff))
    foreach (var (symbol, count) in diff.OrderBy(d => d.Item1))
        Console.WriteLine($"{symbol}: {count}");
Output
wire size 192 bytes
777001: 1
777002: -2

The phasor codecs

Phasor gives you two operations. Encode turns a number into a face with its value as phase, and gives a symbol a salted hash-based random signature. Compose with bind (complex multiply per component) to build relationships, bundle (sum) to collect sets, and unbind (conjugate) to extract parts, and read out with correlation. On the linear band of a face, bind is addition. On the log band it is multiplication. There is no separate calculator: arithmetic is the composition.

PhasorCodec has a fixed width of 256 reals, with a frozen identity prefix and a learned orbital tail, and is built for neural models. HoloCodec has a configurable width, uses SIMD, has a planar layout and a memoized number cache, and is built for holographic storage and similarity search at scale. Do not mix faces between the two.

Encode, bind, bundle, unbind
using Phasor;

// Encode a number and some symbols
double[] five = PhasorCodec.Encode("5");
double[] three = PhasorCodec.Encode("3");
double[] age = PhasorCodec.Encode("age");
double[] name = PhasorCodec.Encode("name");
double[] hello = PhasorCodec.Encode("hello");

// Bind two numbers: adds them on the linear band
double[] fivePlusThree = PhasorCodec.Bind(five, three);
Console.WriteLine($"5 + 3 = {PhasorCodec.DecodeSum(fivePlusThree, max: 10)}");

// Bind each value to a role, bundle the pairs into one record, then unbind a role to read its value back
double[] record = PhasorCodec.Bundle(PhasorCodec.Bind(age, five), PhasorCodec.Bind(name, hello));
double[] ageField = PhasorCodec.Unbind(record, age);
Console.WriteLine($"age field matches 5 better than 3: {PhasorCodec.Correlate(ageField, five) > PhasorCodec.Correlate(ageField, three)}");

// For larger workloads and similarity search, HoloCodec is SIMD and configurable in width
var codec = new HoloCodec(dim: 1024);
double[] encoded = codec.Encode("hello");
Console.WriteLine($"HoloCodec self-similarity is positive: {HoloCodec.Dot(encoded, codec.Encode("hello")) > 0}");
Output
5 + 3 = 8
age field matches 5 better than 3: True
HoloCodec self-similarity is positive: True

Hologram: readable algebra with operators

Hologram wraps a phasor face with operators for readability and prototyping. It is a readonly struct around a double array, so it converts freely to and from arrays without copying.

Bind is the operator *, per-component complex multiply. Bundle is +, a component-wise sum that merges holograms into one superposition. Unbind is /, multiplying by the conjugate. Correlate is a method that gives a dot product, and ~ flips the sign of the imaginary parts. Readout by correlation is approximate. For an exact readout use Pleroma, or CycleHologram for keys only.

Read those four numbers carefully, because the two fields behave differently. The name field separates completely: 128.59 for the right value against -1.96 for the wrong one. Two symbols get independent random signatures, so an unrelated symbol correlates at the noise floor, near zero.

The age field separates by less than 3x: 32.59 for 7 against 13.39 for 5. That is the codec telling the truth. A number is encoded as a phase proportional to its value, so 5 and 7 are similar faces and 5 and 500 are not. Numbers carry a metric and symbols do not. If you want numbers to behave like symbols, encode them with FromSymbol instead of FromNumber, and give up arithmetic to get it.

So correlation separates signal from noise for symbols, and separates signal from nearby signal only by distance for numbers. Design the readout accordingly: take an argmax over candidate values rather than a threshold, which is what DecodeSum and DecodeProduct do.

A record of role and value pairs
using Phasor;

var dim = 256;  // Hologram default dimension

// A record is a superposition of role-value pairs
var nameRole = Hologram.FromSymbol("name", dim);
var lily = Hologram.FromSymbol("Lily", dim);
var ageRole = Hologram.FromSymbol("age", dim);
var seven = Hologram.FromNumber(7, dim);
var record = Hologram.Zero(dim) + nameRole * lily + ageRole * seven;   // bind, then bundle

// Unbind a role and correlate with candidate values
var nameField = record / nameRole;
var ageField = record / ageRole;
Console.WriteLine($"Name field has Lily:  {nameField.Correlate(lily):F2}");
Console.WriteLine($"Name field has John:  {nameField.Correlate(Hologram.FromSymbol("John", dim)):F2}");
Console.WriteLine($"Age field has 7:  {ageField.Correlate(seven):F2}");
Console.WriteLine($"Age field has 5:  {ageField.Correlate(Hologram.FromNumber(5, dim)):F2}");
Output
Name field has Lily:  128.59
Name field has John:  -1.96
Age field has 7:  32.59
Age field has 5:  13.39

Arithmetic with numbers

When you bind two numbers their bands compose. The face carries both operations at once, and you read out the band you care about. Arithmetic is composition, not calculation.

The operators allocate a new array per operation. In tight loops or accumulation, use the in-place static methods BindInto, BundleInto and UnbindInto with a buffer you allocate once. At typical dimensions the allocating Bind operator is about 1.7 times slower than BindInto.

C#
using Phasor;

var dim = 256;

// Linear band: binding two numbers adds them
var sumEncoded = Hologram.FromNumber(3, dim) * Hologram.FromNumber(5, dim);
Console.WriteLine(PhasorCodec.DecodeSum(sumEncoded, max: 20));

// Log band: binding two numbers multiplies them
var productEncoded = Hologram.FromNumber(2, dim) * Hologram.FromNumber(4, dim);
Console.WriteLine(PhasorCodec.DecodeProduct(productEncoded, max: 16));

// In a loop, use the in-place methods: they reuse one buffer instead of allocating per call
double[] accumulator = new double[dim];
for (int i = 0; i < 100; i++)
    Hologram.BundleInto(accumulator, Hologram.FromNumber(i, dim).Reals);
Console.WriteLine($"accumulated {accumulator.Length} components");
Output
8
8
accumulated 256 components

Things to know

Layouts do not mix
Hologram uses the interleaved layout, the same as PhasorCodec. HoloCodec is planar, with all reals then all imaginary parts, and produces garbage if given an interleaved face.
Dimension floor
PhasorCodec requires an even dimension of at least 128: 64 frozen reals and at least 64 orbital. Odd dimensions align down, and anything below 128 throws an ArgumentException.
.Reals is live
The Reals property returns the internal array, not a copy, so changing it changes the hologram. Use it read-only or pass it to the in-place methods.
Small fields wrap
Every view's count must lie within half the field per prime. On a small test field, large kernel weights wrap, so compare cells there instead of counts.