viva_math/attractor

Attractor dynamics for emotional states.

Based on Mehrabian’s PAD model (1996) and dynamical systems theory. Emotions form attractors in PAD space - stable states the system tends toward.

The 8 basic emotions correspond to the octants of the PAD cube:

References:

Types

An attractor in PAD space with a name and position.

pub type Attractor {
  Attractor(name: String, position: vector.Vec3)
}

Constructors

Result of attractor analysis.

pub type AttractorResult {
  AttractorResult(
    nearest: Attractor,
    distance: Float,
    weights: List(#(Attractor, Float)),
  )
}

Constructors

  • AttractorResult(
      nearest: Attractor,
      distance: Float,
      weights: List(#(Attractor, Float)),
    )

    Arguments

    nearest

    Nearest attractor

    distance

    Distance to nearest attractor

    weights

    All attractors with their influence weights

Values

pub fn analyze(
  point: vector.Vec3,
  attractors: List(Attractor),
  temperature: Float,
) -> Result(AttractorResult, Nil)

Comprehensive attractor analysis for a point.

pub fn attractor_pull(
  point: vector.Vec3,
  attractor: Attractor,
  strength: Float,
) -> vector.Vec3

Compute attractor pull - force vector toward nearest attractor.

The pull strength increases with distance from attractor (spring-like). strength parameter controls overall force magnitude.

pub fn basin_weights(
  point: vector.Vec3,
  attractors: List(Attractor),
  temperature: Float,
) -> List(#(Attractor, Float))

Calculate influence weights for all attractors using softmax of negative distances.

CORRECTED per DeepSeek R1 validation: w_i = exp(-γ × d_i) / Σ_j exp(-γ × d_j)

Where γ = 1/temperature (higher temp = softer weights, lower temp = sharper). This is more numerically stable than 1/d and matches Boltzmann distribution.

Closer attractors have higher weights. The temperature parameter controls how “sharp” the weighting is (lower temp = more weight on nearest).

pub fn blend_attractors(
  a: Attractor,
  b: Attractor,
  t: Float,
) -> Attractor

Interpolate between two attractors based on a blend factor.

t=0 gives first attractor, t=1 gives second.

pub fn classify_emotion(point: vector.Vec3) -> String

Classify emotional state by nearest attractor name.

pub fn create(
  name: String,
  pleasure: Float,
  arousal: Float,
  dominance: Float,
) -> Attractor

Create a custom attractor from name and PAD values.

pub fn dominant_dimension(attractor: Attractor) -> String

Get the dominant emotion component (P, A, or D) for an attractor.

pub fn emotional_attractors() -> List(Attractor)

The 8 basic emotional attractors (Mehrabian octants). Values from empirical research on emotion self-reports.

pub fn in_basin(
  point: vector.Vec3,
  attractor: Attractor,
  all: List(Attractor),
) -> Bool

Check if point is in basin of an attractor.

A point is “in” a basin if that attractor has the highest weight.

pub fn nearby_attractors(
  point: vector.Vec3,
  attractors: List(Attractor),
  radius: Float,
) -> List(Attractor)

Find all attractors within a given distance.

pub fn nearest(
  point: vector.Vec3,
  attractors: List(Attractor),
) -> Result(Attractor, Nil)

Find the nearest attractor to a given point.

pub fn ou_mean_reversion(
  current: vector.Vec3,
  attractor: vector.Vec3,
  theta: Float,
  dt: Float,
) -> vector.Vec3

Ornstein-Uhlenbeck mean reversion toward attractor.

dx = theta * (attractor - x) * dt

This is the deterministic part of O-U process. theta controls reversion speed (higher = faster return to attractor).

pub fn weighted_pull(
  point: vector.Vec3,
  attractors: List(Attractor),
  strength: Float,
  temperature: Float,
) -> vector.Vec3

Compute weighted pull from all attractors.

Each attractor pulls proportionally to its basin weight.

Search Document