Help us shape the future of Go! Take the 2026 Go Developer Survey

evocoupling

package
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 30, 2026 License: MPL-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package evocoupling computes evolutionary coupling between packages via co-change kernel correlation.

Index

Constants

View Source
const (
	ReasonOK                  = "ok"
	ReasonInsufficientCommits = "insufficient_commits"
	ReasonSparseWindow        = "sparse_window"
	ReasonDensityFallback     = "density_fallback"
)

Reason codes for AutoSigmaResult.Reason. Stable strings — they appear in JSON output and downstream consumers may branch on them.

Variables

This section is empty.

Functions

func CoOccurrences

func CoOccurrences(seriesA, seriesB []bool) int

CoOccurrences returns n_11 — the number of bins where both sequences are true. Surfaced for the min-support filter in Analyze. Returns 0 when inputs are unequal-length (no signal is defensible).

func EffectiveSampleSize

func EffectiveSampleSize(
	events []time.Time,
	centerAt time.Time,
	sigma time.Duration,
) float64

EffectiveSampleSize returns the Gaussian-kernel effective sample size for the given event timestamps, kernel bandwidth, and centering time.

ESS = (Σ wᵢ)² / Σ wᵢ², where wᵢ = exp(-Δᵢ² / 2σ²) and Δᵢ = centerAt - eventᵢ.

Returns 0 when events is empty, sigma is non-positive, or no event lies within the kernel's effective support. Documented choice: ESS at σ=0 is the limit of a degenerate point-mass kernel and not meaningful for the downstream binary search; returning 0 keeps the search bounds well-defined.

Implementation note: events more than essTailCutoffSigmas × σ from centerAt are skipped as an O(N) optimization. The Gaussian falls off so fast that retaining them changes ESS by less than the 5% tolerance the auto-picker converges to.

func Entropy

func Entropy(series []bool) float64

Entropy returns Shannon entropy H(A) of a binary sequence in bits. Returns 0 for empty input or degenerate distributions (all-true or all-false), matching the convention that 0·log0 := 0.

func Gaussian

func Gaussian(delta, sigma time.Duration) float64

Gaussian kernel: exp(-(delta^2) / (2 * sigma^2)).

func MutualInformation

func MutualInformation(seriesA, seriesB []bool) float64

MutualInformation returns the plug-in Shannon mutual information between two equal-length binary sequences, in bits.

No bias correction is applied — this is the raw estimator. Use NMI for user-facing output; MutualInformation is exported for the conditional-MI v2 work and for tests that need the uncorrected baseline.

Returns 0 when len(a) == 0, when sequences differ in length (no signal is defensible from mismatched inputs), or when either marginal is degenerate (one outcome with probability 1).

func NMI

func NMI(seriesA, seriesB []bool) float64

NMI returns Miller-Madow-corrected normalized mutual information between two binary presence sequences. Result is in [0, 1], clipped to 0 when the bias correction exceeds the plug-in estimate.

Normalization is by min(H(A), H(B)) per the design doc — bounded [0, 1] and information-symmetric (perfect correlation and perfect anti- correlation both yield 1).

Returns NaN when:

  • inputs are unequal-length or shorter than miMinBins (the contingency is too sparse to learn from);
  • either marginal is degenerate (min(H(A), H(B)) == 0), so the ratio is undefined.

The NaN return is a deliberate signal to the caller — a degenerate or underpowered pair carries no information about coupling. Downstream callers test with math.IsNaN and surface low_support.

func PearsonBinned

func PearsonBinned(seriesA, seriesB []bool) float64

PearsonBinned returns the phi coefficient (Pearson correlation on two binary sequences). Result is in [-1, 1].

Returns NaN when inputs are unequal-length, shorter than miMinBins, or when either sequence is degenerate (variance = 0). NaN propagates the same low-support signal as NMI for caller-side handling.

Types

type AdviseOptions

type AdviseOptions struct {
	// MinCorrelation drops pairs below the threshold. Zero keeps every
	// pair the coupling analysis surfaced.
	MinCorrelation float64

	// IncludeLowSupport keeps pairs flagged LowSupport. Off by default:
	// young or sparse histories mark almost every pair low-support, and
	// advisories built on them are noise.
	IncludeLowSupport bool
}

AdviseOptions tunes the advisory pass.

type Advisory

type Advisory struct {
	Touched     string  `json:"touched"`
	Expected    string  `json:"expected"`
	Correlation float64 `json:"correlation"`
}

Advisory flags a co-change expectation a change set did not meet: the touched package historically changes together with the expected one, and this time it did not. Purely informational — the review command renders advisories without affecting its exit code.

func Advise

func Advise(pairs []CouplingPair, touched map[string]bool, opts AdviseOptions) []Advisory

Advise compares a co-change model (pairs from Analyze) against the set of packages a change touched. For each qualifying pair with exactly one side touched, it emits an advisory for the untouched side. Output is sorted by correlation (descending), then by touched/expected name.

type AutoSigmaOptions

type AutoSigmaOptions struct {
	// TargetStddev is the desired correlation-estimator standard deviation.
	// Default 0.05 ("two decimal places of stability"). Drives required
	// ESS via required = 1 / TargetStddev². Smaller stddev → larger sigma.
	TargetStddev float64

	// CoveragePercentile selects which point on the ESS distribution
	// across the evaluation grid must meet required ESS. 50 = median
	// ("typical density"), 25 = stricter coverage ("honest in sparse
	// regions"). Range [1, 99]; default 50.
	CoveragePercentile float64

	// EvalPoints is the number of evaluation centerAt timestamps spread
	// uniformly across [WindowStart, WindowEnd]. Default 20; min 3 so the
	// percentile has at least a start/middle/end to interpolate.
	EvalPoints int

	// WindowStart and WindowEnd bound the analysis window. The binary
	// search picks σ in [1 minute, WindowEnd - WindowStart].
	WindowStart time.Time
	WindowEnd   time.Time
}

AutoSigmaOptions configures DeriveSigma. Zero values are replaced with defaults — passing AutoSigmaOptions{WindowStart: ..., WindowEnd: ...} is the common case.

type AutoSigmaResult

type AutoSigmaResult struct {
	Sigma             time.Duration
	RequiredESS       float64
	AchievedESSMedian float64
	AchievedESSP25    float64
	LowConfidence     bool
	Reason            string
	DensityCap        time.Duration
}

AutoSigmaResult is the picker's output. Sigma is always populated (the fallback path returns window/3); LowConfidence flags whether the chosen sigma actually achieves the target precision. DensityCap is the session-resolution ceiling applied to the sigma search (0 = uncapped — the cadence has no session structure).

func DeriveSigma

func DeriveSigma(events []time.Time, opts AutoSigmaOptions) AutoSigmaResult

DeriveSigma picks the smallest Gaussian-kernel bandwidth that delivers the user's target correlation precision across the analysis window.

Algorithm (per the sigma auto-picker design doc, density-aware revision):

  1. required_ess := 1 / target_stddev²
  2. Build EvalPoints grid at quantiles of the event times — precision is evaluated where estimates are made, not where the calendar is empty.
  3. Cap the search's upper bound at the session-resolution ceiling (densityCap): a σ wider than a work session blends distinct sessions into one blob, which on burst-cadence repos turns every pair into a precise estimate of a meaningless quantity.
  4. Binary search σ in [1 min, min(window, cap)] for required ESS; converge to within 5% relative tolerance.
  5. If required ESS is unreachable at the upper bound, retry with the ESS floor (30 ≈ stddev 0.18). If even that is unreachable but session structure exists, keep σ at the session ceiling — precision is lost either way, so resolution wins over a precise estimate of a smeared quantity. Both return reason density_fallback. Only cadence with no session structure falls back to σ = window/3 with insufficient_commits. All fallbacks mark LowConfidence.

type CouplingPair

type CouplingPair struct {
	PackageA      string   `json:"package_a"`
	PackageB      string   `json:"package_b"`
	Correlation   float64  `json:"correlation"`
	PearsonBinned *float64 `json:"pearson_binned,omitempty"`
	NMI           *float64 `json:"nmi,omitempty"`
	Divergence    *float64 `json:"divergence,omitempty"`
	LowSupport    bool     `json:"low_support,omitempty"`
}

CouplingPair represents the evolutionary coupling between two packages.

Correlation is the kernel-weighted Pearson on continuous event streams — the v1 metric, unchanged. The pointer-valued PearsonBinned, NMI, and Divergence fields are populated only when Options.MIEnabled is true, and are NaN-suppressed: a NaN result (degenerate marginal, < miMinBins bins) leaves the pointer nil so the JSON output omits the field rather than emitting a non-marshallable NaN. LowSupport flags n_11 below the configured minimum.

func Analyze

func Analyze(commits []TimedPackageSet, opts Options) []CouplingPair

Analyze computes evolutionary coupling between packages from commit history.

The kernel-weighted Pearson pass always runs. When opts.MIEnabled is set, an independent binned-MI pass runs in parallel (sigma drives both the kernel bandwidth and the bin width — apples-to-apples by design). The two passes are merged by package-pair key: a pair surfaced by either pass carries both metric families.

type KernelFunc

type KernelFunc func(delta, sigma time.Duration) float64

KernelFunc computes the weight for a time delta given a bandwidth (sigma). Both are durations. Returns a value in [0, 1] where 0 = no influence, 1 = maximum influence (delta = 0).

type Options

type Options struct {
	Sigma   time.Duration
	Kernel  KernelFunc
	MinCorr float64

	// MIEnabled toggles the binned mutual-information metrics. When false
	// (v1 default), Analyze emits only the existing Correlation column
	// and JSON output is byte-stable with pre-MI consumers.
	MIEnabled bool

	// MIMinSupport is the n_11 floor below which a pair is flagged
	// LowSupport. Zero is replaced with defaultMIMinSupport (3) so
	// `Options{MIEnabled: true}` is a sensible call.
	MIMinSupport int

	// WindowStart anchors the presence matrix; commits before it are
	// dropped from the MI computation. Zero value falls back to the
	// earliest commit time in the input — common-case ergonomics.
	WindowStart time.Time
}

Options configures evolutionary coupling analysis.

type PackageResolver

type PackageResolver struct {
	// contains filtered or unexported fields
}

PackageResolver maps file paths to package names via a prefix trie.

func NewPackageResolver

func NewPackageResolver(boundaries []analyzer.PackageBoundary) *PackageResolver

NewPackageResolver builds a resolver from analyzer boundaries.

func (*PackageResolver) Resolve

func (r *PackageResolver) Resolve(filePath string) []string

Resolve returns the package names for a file path. Returns the deepest matching prefix. Returns nil if no match.

func (*PackageResolver) ResolveCommit

func (r *PackageResolver) ResolveCommit(files []string) map[string]struct{}

ResolveCommit returns the set of packages touched by a commit's changed files.

type PresenceMatrix

type PresenceMatrix struct {
	// Bins lists bin start times in chronological order, length n_bins.
	Bins []time.Time
	// Packages lists package names in lexicographic order, length n_packages.
	Packages []string
	// Present is indexed [binIdx][pkgIdx]; true iff Packages[pkgIdx]
	// appeared in any commit during the bin [Bins[binIdx], Bins[binIdx]+binWidth).
	Present [][]bool
}

PresenceMatrix is a [n_bins][n_packages] binary matrix indicating which packages had at least one commit in each fixed-width time bin.

The matrix is binary: multiple commits in a bin collapse to a single presence flag. Frequency information is the job of the kernel-weighted Pearson pipeline; this representation feeds the mutual-information estimator, which only cares whether the event happened.

Shape is preserved for forward-compatibility with conditional mutual information (v2 of the MI extension): Bins + Packages exposed as the stable axes, Present accessed by [binIdx][pkgIdx] so a third conditioning axis can be added without refactor.

func BuildPresenceMatrix

func BuildPresenceMatrix(
	commits []TimedPackageSet,
	windowStart time.Time,
	binWidth time.Duration,
) *PresenceMatrix

BuildPresenceMatrix bins commits by binWidth into a PresenceMatrix.

windowStart anchors the first bin; subsequent bins start at windowStart + k·binWidth. A commit whose Time falls in [windowStart + k·binWidth, windowStart + (k+1)·binWidth) marks every package it touches as present in bin k. Multiple commits in the same bin collapse to a single presence flag.

The number of bins is derived from the commit time span: ceil((maxTime - windowStart + 1) / binWidth) bins, capacity for the latest commit inclusive. If commits is empty or binWidth is non-positive, returns a matrix with no bins and no packages.

Packages absent from every commit are still listed in Packages with all- false rows iff they appear in any commit; packages never touched are not represented at all. Edge commits at windowStart land in bin 0; commits before windowStart are dropped.

type TimedPackageSet

type TimedPackageSet struct {
	Time     time.Time
	Packages map[string]struct{}
}

TimedPackageSet represents the packages touched by a single commit.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL