Uncertain

repository·main·Indexed 42 days ago

https://github.com/mattt/uncertain

A Swift library for uncertainty-aware programming that models noisy or incomplete data using probability distributions. It enables evidence-based programming through lazy computation graphs, hypothesis testing via Sequential Probability Ratio Test (SPRT), and functional operations like map, flatMap, and filter. The library includes built-in support for various distributions (Normal, Uniform, Poisson, etc.) and specialized integration for Core Location GPS data.

Tokens
2.4K
Snippets
7
Records
7
Agent score
4%

What's inside Uncertain

  1. How Uncertain<T> works: Evidence-based programming

    main

    Instead of treating data as exact values, Uncertain<T> treats data as probability distributions. This allows you to make decisions based on confidence levels rather than raw values.

    Key workflow:

    1. Wrap data: Convert exact values or sensor readings into Uncertain<T> instances.
    2. Build computation graphs: Use arithmetic, comparison, and logical operators to create complex expressions. These operations are lazy and do not perform sampling immediately.
    3. Evaluate evidence: Use methods like .probability(exceeds:) or .evaluateHypothesis(...) to trigger sampling and make decisions based on statistical confidence.
    import Uncertain
    import CoreLocation
    
    // 1. Wrap data
    let uncertainLocation = Uncertain<CLLocation>.from(location)
    let uncertainSpeed = uncertainLocation.speed
    
    // 2. Build computation graph (comparison returns Uncertain<Bool>)
    let isSpeeding = uncertainSpeed > speedLimit
    
    // 3. Evaluate evidence with confidence
    if isSpeeding.probability(exceeds: 0.95) {
        issueCitation()
    }
  2. Install Uncertain via Swift Package Manager

    main

    To use Uncertain<T> in your Swift project, add it as a dependency in your Package.swift file using the following configuration:

    dependencies: [
        .package(url: "https://github.com/mattt/Uncertain.git", from: "1.0.0")
    ]
  3. Perform statistical operations on Uncertain<T>

    main

    Once you have an Uncertain<T> instance, you can perform statistical analysis by specifying a sampleCount to drive the estimation:

    • expectedValue(sampleCount:): Returns the mean.
    • standardDeviation(sampleCount:): Returns the standard deviation.
    • confidenceInterval(probability, sampleCount:): Returns a tuple (lower, upper) representing the bounds.
    • skewness(sampleCount:) and kurtosis(sampleCount:): Returns the distribution's shape metrics.
    • cdf(at:sampleCount:): Returns the Cumulative Distribution Function value at a point.
    • mode(sampleCount:): Returns the most frequent value.
    • histogram(sampleCount:): Returns a dictionary mapping values to occurrence counts.
    • entropy(sampleCount:): Returns information entropy in bits.
    • logLikelihood(value, sampleCount:bandwidth:): Estimates log-likelihood using kernel density estimation.
    let normal = Uncertain<Double>.normal(mean: 50.0, standardDeviation: 10.0)
    
    let mean = normal.expectedValue(sampleCount: 1000)
    let (lower, upper) = normal.confidenceInterval(0.95, sampleCount: 1000)
    let probability = normal.cdf(at: 60.0, sampleCount: 1000)
  4. Perform hypothesis testing with evaluateHypothesis

    main

    For advanced decision-making, use evaluateHypothesis on an Uncertain<Bool> to perform a Sequential Probability Ratio Test (SPRT). This is more efficient than simple probability checks as it automatically determines the required sample size based on statistical significance.

    Parameters:

    • threshold: The probability threshold for the decision.
    • confidenceLevel: The desired confidence level.
    • maxSamples: Maximum number of samples to draw.
    • epsilon: A small value for numerical stability.
    • alpha: Type I error (false positive rate).
    • beta: Type II error (false negative rate).
    • batchSize: Number of samples to draw per iteration.
    let sensor = Uncertain<Double>.normal(mean: 25.0, standardDeviation: 3.0)
    let evidence = sensor > 30.0
    
    let result = evidence.evaluateHypothesis(
        threshold: 0.8,
        confidenceLevel: 0.99,
        maxSamples: 5000,
        epsilon: 0.05,
        alpha: 0.01,
        beta: 0.01,
        batchSize: 20
    )
    
    print("Decision: \(result.decision)")
    print("Observed probability: \(result.probability)")
    print("Samples used: \(result.samplesUsed)")
  5. Transform uncertain values with functional operations

    main

    You can transform the underlying values of an Uncertain<T> using standard functional programming patterns. Note that these operations build a computation graph and only execute during evaluation.

    • map: Transforms each sample in the distribution.
    • flatMap: Chains uncertain computations (e.g., using a sampled value to parameterize a new distribution).
    • filter: Uses rejection sampling to create a new distribution containing only values that satisfy a predicate.
    let temperature = Uncertain<Double>.normal(mean: 20.0, standardDeviation: 3.0)
    
    // Map: transform each sample
    let fahrenheit = temperature.map { $0 * 9/5 + 32 }
    
    // FlatMap: chain uncertain computations
    let adjusted = temperature.flatMap { temp in
        Uncertain<Double>.normal(mean: temp, standardDeviation: 1.0)
    }
    
    // Filter: rejection sampling
    let positive = temperature.filter { $0 > 0 }
  6. Use Core Location integration for GPS data

    main

    If your project imports CoreLocation, Uncertain provides specialized methods for handling geographic uncertainty:

    • Uncertain<CLLocationCoordinate2D>.coordinate(coordinate, accuracy:): Creates an uncertain coordinate with a given accuracy radius.
    • userLocation.distance(to: destination): Calculates the uncertain distance between two coordinates.
    • userLocation.bearing(to: destination): Calculates the uncertain bearing.
    • Uncertain<CLLocation>.from(clLocation): Converts a CLLocation object into an Uncertain<CLLocation>, automatically using its built-in accuracy. You can override this with Uncertain<CLLocation>.from(clLocation, horizontalAccuracy:) to be more conservative.
    import Uncertain
    import CoreLocation
    
    let userLocation = Uncertain<CLLocationCoordinate2D>.coordinate(
        CLLocationCoordinate2D(latitude: 37.7749, longitude: -122.4194),
        accuracy: 10.0
    )
    
    let destination = Uncertain<CLLocationCoordinate2D>.coordinate(
        CLLocationCoordinate2D(latitude: 37.7849, longitude: -122.4094),
        accuracy: 5.0
    )
    
    let distance = userLocation.distance(to: destination)
    if (distance < 100.0).probability(exceeds: 0.9) {
        print("User is likely at the destination")
    }
  7. Construct distributions

    main

    The library provides several built-in constructors for common probability distributions. Most are accessed via static methods on Uncertain<T>:

    • normal(mean:standardDeviation:): For measurement errors and natural phenomena.
    • uniform(min:max:): For values where all outcomes in a range are equally likely.
    • exponential(rate:): For modeling time between events.
    • kumaraswamy(a:b:): A continuous distribution on [0,1].
    • rayleigh(scale:): For modeling magnitudes (e.g., GPS distance uncertainty).
    • binomial(trials:probability:): For number of successes in fixed trials.
    • poisson(lambda:): For number of events in a fixed interval.
    • bernoulli(probability:): For single trials with two outcomes.
    • categorical([Key: Probability]): For discrete outcomes.
    • empirical(observedData): Creates a distribution by sampling from provided data.
    • mixture(of:weights:): Combines multiple distributions.
    • point(value): Represents a certain, non-probabilistic value.
    • Custom Sampler: Initialize with a closure Uncertain<T> { ... } that returns a random value.
    let normal = Uncertain<Double>.normal(mean: 0.0, standardDeviation: 1.0)
    let uniform = Uncertain<Double>.uniform(min: 0.0, max: 1.0)
    let categorical = Uncertain<String>.categorical(["red": 0.3, "blue": 0.7])
    let certain = Uncertain<Double>.point(42.0)
    
    // Custom sampler
    let custom = Uncertain<Double> {
        return myRandomValue()
    }