Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: LoopKit/LoopAlgorithm
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 84a099f
Choose a base ref
...
head repository: LoopKit/LoopAlgorithm
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 7d016fb
Choose a head ref
  • 19 commits
  • 49 files changed
  • 8 contributors

Commits on Aug 21, 2025

  1. Fix decoding of old AutomaticDoseRecommendation structures without ba…

    …salAdjustment (#20)
    ps2 authored Aug 21, 2025
    Configuration menu
    Copy the full SHA
    7ba61e1 View commit details
    Browse the repository at this point in the history

Commits on Aug 28, 2025

  1. Carb absorption model selection updates. (#21)

    * Add ability to specify carbAbsorptionModel in LoopPredictionInput and in encoded files
    
    * Make codable rep for CarbAbsorptionModel a string
    ps2 authored Aug 28, 2025
    Configuration menu
    Copy the full SHA
    29c7b52 View commit details
    Browse the repository at this point in the history

Commits on Oct 31, 2025

  1. LOOP-5502 Allow setting of max active insulin multiplier (#22)

    * Allow setting of max active insulin multiplier
    
    * Fix formatting for older xcode
    
    * Fix formatting for older xcode
    ps2 authored Oct 31, 2025
    Configuration menu
    Copy the full SHA
    89dd58a View commit details
    Browse the repository at this point in the history

Commits on Nov 13, 2025

  1. Mjc/has gradual transitions (#23)

    * Adds gradual transition validation for retrospective correction as a mitigation for spurious iCGM values
    
    * Add gradualTransitionsThreshold to AlgorithmInput and related structures
    
    Set RC transition check to only use the last 7 samples
    
    * Refactor conditions in momentum to include gradual transitions check
    
    * Changed default for gradualTransitionThreshold from 20 -> 40
    
    * fixed naming for gradualTransitionThreshold
    
    * Missed a name change on cleanup
    
    * Add tests
    
    * fix comma for older xcode
    
    * Update xcode
    
    * Update xcode version
    
    * Change resource class
    
    * Update simulator
    
    ---------
    
    Co-authored-by: Pete Schwamb <pete@schwamb.net>
    markjudeconnolly and ps2 authored Nov 13, 2025
    Configuration menu
    Copy the full SHA
    8093b57 View commit details
    Browse the repository at this point in the history

Commits on Nov 18, 2025

  1. Configuration menu
    Copy the full SHA
    13cb4b4 View commit details
    Browse the repository at this point in the history

Commits on Mar 11, 2026

  1. Configuration menu
    Copy the full SHA
    9e1ff9b View commit details
    Browse the repository at this point in the history
  2. formatting, typo

    PecaWolf committed Mar 11, 2026
    Configuration menu
    Copy the full SHA
    07afc01 View commit details
    Browse the repository at this point in the history

Commits on Mar 25, 2026

  1. force rebuild

    PecaWolf committed Mar 25, 2026
    Configuration menu
    Copy the full SHA
    1816673 View commit details
    Browse the repository at this point in the history
  2. Merge pull request #27 from tidepool-org/pd/NEMO-254/meal_bolus_no_is…

    …f_test
    
    Pd/nemo 254/meal bolus no isf test
    PecaWolf authored Mar 25, 2026
    Configuration menu
    Copy the full SHA
    bd1a879 View commit details
    Browse the repository at this point in the history

Commits on May 21, 2026

  1. Make decayEffect a continuous function of sample timestamp (#33)

    * Make decayEffect a continuous function of sample timestamp
    
    Reformulates decayEffect using a closed-form quadratic in time-since-sample
    rather than accumulating step-by-step from the floored simulation boundary.
    This makes the effect value at any future absolute timestamp independent of
    which delta-sized simulation bucket the sample's startDate falls into.
    
    For samples aligned to delta boundaries the two formulations are
    mathematically identical. For unaligned samples (the common case with real
    CGM streams) the new formulation removes a small discontinuity that the
    old code exhibited at bucket boundaries.
    
    Adds LoopMathTests covering continuity across a delta boundary. Existing
    fixture-calibrated tests are re-pinned to the new values; per-prediction
    drift is on the order of 0.1 mg/dL.
    
    Ports the LoopMath change from LoopKit/LoopKit#556 by Moti Nisenson-Ken to
    the LoopAlgorithm package, where decayEffect now lives.
    
    * Space to kick off tests
    
    ---------
    
    Co-authored-by: Pete Schwamb <pete@pete-mbp-eth.maplect.net>
    ps2 and Pete Schwamb authored May 21, 2026
    Configuration menu
    Copy the full SHA
    b05f72b View commit details
    Browse the repository at this point in the history
  2. Add unit tests for StandardRetrospectiveCorrection (#32)

    StandardRetrospectiveCorrection (the P-only retrospective correction
    controller) had no dedicated unit tests — its behavior was only
    covered transitively via the higher-level LoopAlgorithm tests.
    
    Adds 9 tests covering:
    - Recency gating: stale / nil / empty discrepancy lists clear the
      correction and return empty.
    - Total correction effect equals the latest (most-recent) discrepancy
      magnitude (Standard is P-only on .last).
    - Positive / negative discrepancies project glucose forward in the
      expected direction, with the last sample ≈ starting + discrepancy.
    - The first effect sample equals the starting glucose value at the
      starting date (correction hasn't yet had time to apply).
    - Only the latest discrepancy contributes — older entries are ignored
      (the key behavioral difference vs IntegralRC, which integrates them).
    - Short discrepancies are clamped to retrospectiveCorrectionGroupingInterval
      to prevent over-amplified velocity from very short windows.
    
    These tests will serve as a backstop while the active-insulin / EGP
    decomposition work modifies the glucose-effect computation upstream of
    the RC discrepancy calculation.
    
    Co-authored-by: LoopKit Developer <dev@loopkit.org>
    ps2 and loopkitdev authored May 21, 2026
    Configuration menu
    Copy the full SHA
    a1d0e57 View commit details
    Browse the repository at this point in the history
  3. Faster filterDateRange via binary search (#31)

    SampleValue.swift: add a filterDateRange overload for
    RandomAccessCollection where Element: TimelineValue, Index == Int.
    Returns the same result as the existing Sequence-based linear-filter
    implementation but uses two binary searches instead of a linear scan.
    Picks up automatically for Array-backed callers (which is every caller
    in this codebase via Swift protocol dispatch).
    
    Significant speedup for hot paths that call filterDateRange repeatedly
    on long schedules — for example, InsulinMath.glucoseEffectsMidAbsorptionISF
    and DoseMath.insulinCorrection when the sensitivity schedule has many
    segments. In a LoopEval 60-day per-step prediction sweep with a per-step
    ISF schedule, total sim wall-clock went from ~30 min to ~1 min (≈30×
    faster) with bit-identical output to the linear-filter path.
    
    Tests: FilterDateRangeTests.swift with 11 cases covering equivalence
    with the linear-filter reference: boundary cases (empty, both bounds
    nil, only start, only end), start-before-all, end-after-all, fully-
    outside, single-sample collections, exact-match-one-segment, and a
    100-iteration randomized fuzz over a 200-element contiguous schedule.
    ps2 authored May 21, 2026
    Configuration menu
    Copy the full SHA
    729e508 View commit details
    Browse the repository at this point in the history
  4. Add PrecomputedInsulinInput for efficient multi-step prediction sweep…

    …s + parallelize glucose-effects (#29)
    
    * Add PrecomputedInsulinInput for efficient multi-step prediction sweeps
    
    Introduces PrecomputedInsulinInput and a new generatePrediction overload
    that accepts pre-annotated dose data, enabling significant speedups for
    historical back-testing / evaluation sweeps.
    
    The key bottleneck in a dense prediction sweep is doses.annotated(with: basal),
    which is O(doses × basalSegments) and was called from scratch at every step.
    Between adjacent 5-min steps the dose list changes only at its edges; the
    annotation of every dose in the middle is identical.
    
    Changes:
    - Sources/LoopAlgorithm/Insulin/PrecomputedInsulinInput.swift (new)
      PrecomputedInsulinInput struct holding pre-annotated doses and an optional
      pre-built insulinEffects timeline. Includes a convenience .build() factory.
    
    - Sources/LoopAlgorithm/LoopAlgorithm.swift
      New generatePrediction(start:glucoseHistory:precomputedInsulin:carbEntries:
      sensitivity:carbRatio:...) overload. Skips annotated(with:) entirely;
      optionally skips glucoseEffects() when insulinEffects is pre-supplied.
    
    - Sources/LoopAlgorithm/Glucose/GlucoseEffect.swift
      Add Sendable conformance (struct with value-type fields, safe).
    
    - Tests/LoopAlgorithmTests/PrecomputedInsulinInputTests.swift (new)
      3 tests verifying the new overload produces output matching the standard
      path (bit-identical for annotation-only, count-identical + clinically
      equivalent for pre-built effects).
    
    Expected speedup for a 7-day sweep at 5-min step (~2016 calls):
      annotation bypass alone: ~40-60% wall-clock reduction
      + effects cache (fixed ISF): additional ~20-30%
    
    * Refactor PrecomputedInsulinInput for explicit ISF-sweep pattern
    
    Split the API into two explicit steps so ISF sweeps pay annotation cost
    exactly once across all multipliers:
    
      annotate(doses:basal:)          → ISF-independent, build once
      .withEffects(sensitivity:from:to:) → ISF-dependent, once per multiplier
    
    Correct ISF sweep pattern:
      let base = PrecomputedInsulinInput.annotate(doses: doses, basal: basal)
      for multiplier in isfMultipliers {
        let input = base.withEffects(sensitivity: scale(sensitivity, by: multiplier))
        // run ~2016 steps with input — no annotation, no per-step glucoseEffects
      }
    
    Cost breakdown for 10-multiplier × 7-day sweep (n≈2016 steps each):
      Before: annotated(with:) + glucoseEffects() called 20160× each
      After:  annotated(with:) called 1×, glucoseEffects() called 10×
    
    Also adds testISFSweepPattern verifying bit-identical output across
    multipliers [0.7, 0.8, ..., 1.3] vs the standard generatePrediction path.
    
    * Add sliced(from:to:) for per-step dose window slicing
    
    Enables EvalCore to slice pre-annotated doses to the per-step lookback
    window without re-annotating. Uses binary search on startDate + linear
    filter on endDate (arrays are ~100-200 entries, linear endDate scan
    is negligible).
    
    Also cleans up the unused private partition helper (now only used by sliced).
    
    * Expose dose-recommendation internals as public API
    
    Downstream callers (LoopEval bench engine) need to compute dose
    recommendations from a forecast without going through the full run() API,
    which re-computes insulin effects. Making insulinCorrection,
    recommendTempBasal, and recommendAutomaticDose public lets them do
    that efficiently using already-computed predictions.
    
    Enables delivery-based ODR/UDR metrics in LoopEval that compare the
    actual insulin Loop would deliver across two configurations.
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    
    * Parallelize glucose-effects accumulation in InsulinMath
    
    Replace the sequential reduce loop with DispatchQueue.concurrentPerform
    over per-step increments, then a final cumsum. Per-step contributions are
    independent until the final summation, so this scales with available cores.
    
    Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
    
    * chore: carry forward momentumVelocityMaximum param from eval/precomputed-insulin-effects
    
    ---------
    
    Co-authored-by: Bot <bot@macmini.maplect.net>
    Co-authored-by: LoopKit Developer <dev@loopkit.org>
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    4 people authored May 21, 2026
    Configuration menu
    Copy the full SHA
    6e4e315 View commit details
    Browse the repository at this point in the history
  5. Merge pull request #6 from tidepool-org/main

    Bring in Tidepool LoopAlgorithm updates
    ps2 authored May 21, 2026
    Configuration menu
    Copy the full SHA
    1ca8566 View commit details
    Browse the repository at this point in the history

Commits on May 24, 2026

  1. Assert filterDateRange input is sorted ascending

    The binary-search filterDateRange overload assumes its collection is sorted
    ascending by startDate, but returns silently-wrong results otherwise. Add a
    debug-only assert enforcing that contract (compiled out of release builds, so
    no runtime cost).
    
    This immediately surfaced an existing violation: testGlucoseEffectFromHistory
    built a `basal` schedule with segments out of order (and one with endDate
    before startDate, plus overlaps). Replace it with a well-formed sorted,
    contiguous, non-overlapping schedule, and regenerate the expected-effect
    fixture to match the corrected schedule.
    ps2 committed May 24, 2026
    Configuration menu
    Copy the full SHA
    8faf496 View commit details
    Browse the repository at this point in the history

Commits on May 25, 2026

  1. Merge pull request #7 from LoopKit/harden/filterdaterange-sorted-assert

    Assert filterDateRange input is sorted ascending
    ps2 authored May 25, 2026
    Configuration menu
    Copy the full SHA
    2f5c630 View commit details
    Browse the repository at this point in the history

Commits on Jul 15, 2026

  1. Fix delta-scale IOB ripple for basal segments longer than delta (#35)

    continuousDeliveryInsulinOnBoard quantized its integration bound to the
    delta grid (floor((time + delay) / delta) * delta), so a whole chunk was
    added discontinuously each time `time` crossed a delta boundary. This
    produced a delta-scale ripple in insulinOnBoard for any basal segment
    longer than one delta -- i.e. essentially every real temp basal / suspend
    (median ~10 min in real dose histories).
    
    Integrate the delivered fraction up to `time`, weighting a partial final
    chunk, and sample each chunk's remaining-effect at its midpoint. IOB is
    now continuous and matches a finely-subdivided equivalent delivery.
    
    Scoped to the insulinOnBoard path; glucoseEffect (and therefore dosing)
    is unchanged. Adds a regression test. All package tests pass.
    ps2 authored Jul 15, 2026
    Configuration menu
    Copy the full SHA
    aeffea8 View commit details
    Browse the repository at this point in the history

Commits on Jul 27, 2026

  1. Bound IntegralRC correction rate to a settings-free physiological cei…

    …ling (#37)
    
    The port from LoopKit to LoopAlgorithm dropped IntegralRetrospectiveCorrection's
    integral clamp — a safety bound on the wound-up integral term. Rather than restore
    the original clamp (which scaled the bound by ISF x basal and the target range), this
    bounds the RC correction RATE to a physiological ceiling (mg/dL/min), independent of
    user settings.
    
    Rationale: RC forecasts unmodeled physiology, whose plausible velocity does not depend
    on a person's insulin needs or target range. The original clamp's implied "plausible
    unmodeled velocity" varied ~11x across settings (0.55-4.9 mg/dL/min for ISF x basal of
    18-160) - trusting the same real carb rise for a high-need user while clamping it for a
    low-need one. The integral is already self-limiting (leaky integrator; converges to
    ~1.087 x discrepancy), so the clamp is a rare-event backstop for large or spurious
    discrepancies, and a fixed physiological rate ceiling is the right shape for that.
    
    Default 4 mg/dL/min (top of plausible sustained unmodeled velocity). On real IRC data
    (two datasets) this reproduces the deployed clamp's behavior - both near-inert in normal
    operation - without the settings coupling. Configurable via
    IntegralRetrospectiveCorrection(effectDuration:maxCorrectionVelocity:); nil disables.
    
    Tests: correction rate clamped to +/- the ceiling on a large windup, symmetric for
    negative discrepancies, and inert when the rate is below the ceiling.
    
    Co-authored-by: LoopKit Developer <dev@loopkit.org>
    ps2 and loopkitdev authored Jul 27, 2026
    Configuration menu
    Copy the full SHA
    925e672 View commit details
    Browse the repository at this point in the history
  2. Merge branch 'main' into main

    ps2 authored Jul 27, 2026
    Configuration menu
    Copy the full SHA
    7d016fb View commit details
    Browse the repository at this point in the history
Loading