Current section
Files
Jump to
Current section
Files
src/tastoids.gleam
//// Tastoids (& their 'Temperate' Algebra)
////
//// It is often said, one can't compare "apples" to "oranges",
//// but I daresay.. _perhaps you can?_
////
//// **Let me show you how.**
////
//// On their own, it is hard to figure how one might compare
//// apples to oranges, let alone deign to approach a calculus
//// of taste however, with _just enough_ structure, a **Taste**
//// can become a **Tastoid**, along with a curious and powerful
//// _Temperate_ Algebra.
////
//// > Not unlike a [_Tropical_ geometry](https://en.wikipedia.org/wiki/Tropical_geometry),
//// > where the notion of addition and multiplication are replaced by min(a,b) & add(a,b),
//// > I pose that a _Temperate Algebra_ is one whose typical notions of (+,×) are replaced
//// > with operations that effectively 'average' taste (with commutativity, distributivity
//// > and reversibility, no less!)
////
//// ### A Taste (t) Field
////
//// "Tastes", broadly, are any measurable/comparable sentiment about a thing.
//// Something with a unique direction and magnitude. _A vector!_
////
//// Consider if you will, then:
////
//// - The set of all Vectors are a field ℝⁿ (aka 𝕍).
//// - You may partition 𝕍 by some enumerable set of indices (as ⅈ <= countable ∞))
//// - The '[Algebraic extension](https://en.wikipedia.org/wiki/Algebraic_extension)'
//// of 𝕍/ⅈ, is itself a field (with Algebra)
////
//// > In the context of large-language- and embedding-models, this idea of
//// > mapping _things_ (text or otherwise) into a well-defined set of possible
//// > indices and probabilities is referred to as an '_embedding_'.
////
//// ### A Taste -> One Tastoid
////
//// We're almost there, I promise. Lets talk about plain numbers for a bit; say
//// I told you knew the average of some set of values was `42`. You also know
//// for a fact there was a 13 in there once, somewhere.
////
//// Knowing nothing else, how would you _un_-average 13 from 42?
////
//// _Were there two values that averaged to 42? Three? More?_
////
//// Without the _cardinality_ of the original sampling, its (absolutely) impossible to know.
//// But with it... say n=13, in which case, we can merely remove 1/13th of 13 (i.e. 1)
//// from our combined average to find out the average without that 13 was just 41.
////
//// Similarly, on their own Taste vectors _are_ comparable, even averagable in some sense,
//// but without a cardinality, their operations aren't quite _lined up_ to have solutions
//// to previously impossible questions become possible and yield seemingly 'free' results.
////
//// Attempting to put some formalism to the above,
////
//// - We can partition a taste-field further, by its cardinality k ∈ 𝕂 (𝕂 ~ ℝ x ℝ₄ ~ ℂ)
//// (As well as a 4-cycle of like -> dislike -> unlike -> undislike)
////
//// i.e. someone (or thing) expressing { like ( 1+i ) -> dislike (-1 + i)
//// 𝕒 taste (valued at weight w), k times -> un-like (-1 + -i) -> un-dislike (1 + -i) }
//// A Tastoid, can be arrived at by a person/subject, expressing a taste _once_ (tᵢ, k=1),
//// imbedding (sic) that individuals' sentiment in a univesal/possibly infinite set of all
//// the tastoid's that subject could express 𝕥ᵤ↪ᵢ ∈ 𝕋ᵤ
////
//// Then, we may define a handful a tiny, tidy, yet supremely powerful (σ/sigma-)
//// _Algebra_ of Taste_
////
//// - [ ] Brief introduction to operators
//
// ### Operations
//
// - [ ] Tidy this up with all the more recent changes; probably into a more formal
// 'proof'ing ground
//
// (A thorough reckoning of its axioms will take time, learning, and discourse with others;
// suffice to say I mean a formal category-theory 'Algebra' with several handy properties,
// notably invertible, self-integrating distillation of an 'average' taste!)
//
// - add(t¹, u¹) -> (t + u)¹ ('regular' properties of scalar vector addition)
// - scale(tⁿ, k) -> tⁿᵏ ('regular' properties of scalar vector multiplication)
// - blend(t¹, u¹)) -> (t + u)² ('tensor' product; associative, distributive, commutative, invertable)
// (equiv. to) ~> (½t + ½u)¹
// - squash(tᵏ, p) -> ||t||ₚ ( for p =0, yields the unipotent norm--i.e. every sparse tᵢ -> 1
// p==1, yields the normal tastoid (weighted power mean)
// p!=0, yields the p-norm of t with k=p )
import gleam/float
import gleam/int
import gleam/set.{type Set}
import tastoids/taste.{add, negate, scale}
import tastoids/tastoid.{type Tastoid, Tasteless, Tastoid}
/// Blend (**⩐**) two tastoids, producing a larger tastoid _congruent_ to
/// the weighted power mean, aka their average 'taste' (via `squash`).
///
/// See also `retract` - which yields u' which _unblends_ when blended,
pub fn blend(t: Tastoid(index), with u: Tastoid(index)) {
case t, u {
Tasteless, _ -> u
_, Tasteless -> t
Tastoid(t, k_t), Tastoid(u, k_u) -> add(t, u) |> Tastoid(int.add(k_t, k_u))
}
}
/// Return the inverse of a taste (over `blend`)
pub fn retract(taste: Tastoid(index)) -> Tastoid(index) {
case taste {
Tastoid(t, k) -> negate(t) |> Tastoid(int.negate(k))
Tasteless -> Tasteless
}
}
/// Unblend a Tastoid (the _divisor_), removing it `from`
/// another (the _numerator_).
///
/// nb. This works even if the `divisor` tastoid isn't in the `from` Tastoid.
pub fn less(
divisor: Tastoid(space),
from numerator: Tastoid(space),
) -> Tastoid(space) {
// In arithmetic, a/b = (1/b, or b^-1) x a
divisor |> retract |> blend(numerator)
}
/// Return the 'and'-product (**⩍**), multiplying tastes/cardinalities
/// between _left_ and _right_; effectively erasing unshared tastes.
pub fn both(
tastoid left: Tastoid(space),
and right: Tastoid(space),
) -> Tastoid(space) {
case left, right {
Tasteless, _ -> Tasteless
_, Tasteless -> Tasteless
Tastoid(t1, k1), Tastoid(t2, k2) -> {
let t = taste.multiply(t1, t2)
let k = int.multiply(k1, k2)
Tastoid(t, k)
}
}
}
/// **Experimental** Return the 'not-and'-product, letting
/// the maths take the wheel.
///
/// _Avery's guess is this will act like a harsh move away the
/// interesected indices, conversely strengthening everything else_
///
/// Bland : A, B -> (A ⩐ B) - (A ⩍ B)
pub fn bland(
left: Tastoid(space),
excluding right: Tastoid(space),
) -> Tastoid(space) {
let or = blend(left, with: right)
let and = both(left, and: right)
case or, and {
Tasteless, Tastoid(ands, k) -> Tastoid(taste.negate(ands), int.negate(k))
_, Tasteless -> or
Tastoid(ors, k_or), Tastoid(ands, k_and) -> {
let xor = taste.add(ors, taste.negate(ands))
Tastoid(xor, int.subtract(k_or, k_and))
}
}
}
/// Reduce k -> 1, yielding the 'average'/normalized tastoid of all
/// the tastoids blended/present
pub fn squash(to_norm tastoid: Tastoid(index)) {
case tastoid {
// Squashing a k=1 tastoid is the base case, so it returns unchanged.
Tastoid(_, 1) as t -> t
// A 0-strength taste squashes towards the emptiest taste (Null)
Tastoid(_, 0) -> Tasteless
// When k is non-zero, return the de-weighted norm of t (scale by 1 over k)
Tastoid(t, k) -> {
// A special coefficient that grounds t into k=1 when applied via scalar multiplication
let try_divide = float.divide(1.0, int.to_float(k))
case try_divide {
Ok(one_over_k) -> scale(t, by: one_over_k) |> Tastoid(1)
Error(_) -> Tasteless
}
}
Tasteless -> Tasteless
}
}
/// Blend, squashing, aka 'smash' as it is effectively the smash-product (⨳),
/// or t ⨳ u (see [Smash Product](https://ncatlab.org/nlab/show/smash+product))
pub fn smashing(t: Tastoid(index), with u: Tastoid(index)) -> Tastoid(index) {
blend(t, u) |> squash
}
/// Returns `True` iff Tastoids `t` & `u` are congruent (weakly equivalent)
pub fn equal(t: Tastoid(index), u: Tastoid(index)) -> Bool {
case t, u {
Tasteless, Tasteless -> True
_, Tasteless -> False
Tasteless, _ -> False
Tastoid(t, k_t), Tastoid(u, k_u) if k_t == k_u -> {
t == u
}
t, u -> equal(squash(t), squash(u))
}
}
/// Project a tastoid into a _Length-oid_ of sorts, condensing
/// it into a 1-d tastoid indexed by the set of its indexes,
/// and a value of its dimensions sum.
pub fn length(of tastoid: Tastoid(space)) -> Tastoid(Set(space)) {
case tastoid {
Tasteless -> Tasteless
Tastoid(t, k) -> t |> taste.condense |> Tastoid(k)
}
}
/// Returns a Tastoid's _scalar_ value length, discarding its cardinality
///
/// nb. This doesn't _need_ to be a `length(of: tastoid)` result, and in
/// fact they yield the same value over `distance(covered_by:)`
pub fn distance(covered_by tastoid: Tastoid(space)) -> Float {
case tastoid {
Tasteless -> 0.0
Tastoid(t, _k) -> taste.length(t)
}
}