Documentation
¶
Overview ¶
Package evocoupling computes evolutionary coupling between packages via co-change kernel correlation.
Index ¶
- Constants
- func CoOccurrences(seriesA, seriesB []bool) int
- func EffectiveSampleSize(events []time.Time, centerAt time.Time, sigma time.Duration) float64
- func Entropy(series []bool) float64
- func Gaussian(delta, sigma time.Duration) float64
- func MutualInformation(seriesA, seriesB []bool) float64
- func NMI(seriesA, seriesB []bool) float64
- func PearsonBinned(seriesA, seriesB []bool) float64
- type AdviseOptions
- type Advisory
- type AutoSigmaOptions
- type AutoSigmaResult
- type CouplingPair
- type KernelFunc
- type Options
- type PackageResolver
- type PresenceMatrix
- type TimedPackageSet
Constants ¶
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 ¶
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 ¶
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 ¶
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 MutualInformation ¶
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 ¶
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 ¶
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):
- required_ess := 1 / target_stddev²
- Build EvalPoints grid at quantiles of the event times — precision is evaluated where estimates are made, not where the calendar is empty.
- 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.
- Binary search σ in [1 min, min(window, cap)] for required ESS; converge to within 5% relative tolerance.
- 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 ¶
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 ¶
TimedPackageSet represents the packages touched by a single commit.