Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f82415b
build: Phase A — v4.0.0 build system cleanup
OldCrow Jun 6, 2026
6ceb3cc
build: Phase A follow-up — remove all remaining Catalina/LLVM artefacts
OldCrow Jun 6, 2026
597be26
feat: Phase B+C — parameterised emission interface and CRTP distribut…
OldCrow Jun 6, 2026
ddeeb5a
feat: Phase D — BasicHmm<Obs> template HMM core
OldCrow Jun 6, 2026
0086235
merge: pull v3.8.0 improvements into v4 development branch
OldCrow Jun 7, 2026
e2751a9
feat: Phase E — multivariate observation types and HmmMV alias
OldCrow Jun 7, 2026
f05f8bd
feat: Phase F — Cholesky factorization and derived linalg helpers
OldCrow Jun 7, 2026
1d5008a
feat: Phase G — three multivariate emission distributions
OldCrow Jun 8, 2026
2504ff7
Improve AGENTS.md for arbitrary-user clarity
OldCrow Jun 9, 2026
55f7b72
feat: Phase H (part 1) — BasicCalculator<Obs> + BasicTrainer<Obs> tem…
OldCrow Jun 9, 2026
519c5b2
refactor: BasicCalculator<Obs> stores observation sequence by const r…
OldCrow Jun 9, 2026
3d7fd03
feat: Phase H (part 2) — BasicForwardBackwardCalculator<Obs> + BasicB…
OldCrow Jun 10, 2026
d0a832b
feat: Phase H (complete) — BasicViterbiCalculator, MapBWT, ViterbiTra…
OldCrow Jun 10, 2026
e492fd0
refactor: code quality pass — resolve CCN violations, extract helpers
OldCrow Jun 10, 2026
a700608
style: naming convention, [[nodiscard]], and Doxygen fixes in Phase H…
OldCrow Jun 10, 2026
8d74dca
feat: Phase I — multivariate HMM JSON serialization/deserialization
OldCrow Jun 10, 2026
a4397a3
feat: Phase J + K — model selection MV counts and comprehensive MV te…
OldCrow Jun 10, 2026
efeab5c
docs: Phase L — CHANGELOG v4.0.0, MIGRATION.md, CONTRIBUTING.md, .md …
OldCrow Jun 11, 2026
161a1b1
Phase L: add MV distribution setters; fix elk_mv_example; add mv_regi…
OldCrow Jun 11, 2026
b816717
Add MV distribution setters; update CMakeLists and README for mv_regi…
OldCrow Jun 11, 2026
89e083b
mv_regime_example: add real SPY+QQQ data path + hmmlearn reference co…
OldCrow Jun 11, 2026
c380bea
elk_mv_example: simplify to single IndependentComponents model + move…
OldCrow Jun 11, 2026
43f5d96
Dead-code fixes, setter tests, CHANGELOG update
OldCrow Jun 11, 2026
7d053e0
Address audit findings: M1/M2 alloc, L1 nodiscard, L2 EStepBuffers, L…
OldCrow Jun 11, 2026
b87b809
Fix clang-tidy bugprone findings: throwing-static-init, implicit-wide…
OldCrow Jun 12, 2026
606fbeb
Fix bugprone-crtp-constructor-accessibility: move DistributionBase sp…
OldCrow Jun 12, 2026
7bd3a36
Add zeek_anomaly_poc: CTU-13 Neris botnet MV HMM anomaly detection POC
OldCrow Jun 12, 2026
35b53fa
Merge origin/main: keep v4 AGENTS.md (Catalina dropped, MV docs correct)
OldCrow Jun 12, 2026
a816df7
style: clang-format all C++ files changed vs main
OldCrow Jun 12, 2026
5e79bfe
ci: use Ninja generator on Windows (compatible with ilammy/msvc-dev-cmd)
OldCrow Jun 12, 2026
d068e6d
fix: add missing #include <array> for MSVC compatibility
OldCrow Jun 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 4 additions & 44 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,9 @@ jobs:
if: matrix.msvc
run: |
cmake -B build `
-G "Visual Studio 17 2022" -A x64 `
-G "Ninja" `
-DCMAKE_C_COMPILER=cl `
-DCMAKE_CXX_COMPILER=cl `
-DBUILD_SHARED_LIBS=OFF `
-DBUILD_EXAMPLES=OFF `
-DBUILD_TOOLS=OFF `
Expand All @@ -94,52 +96,10 @@ jobs:
run: cmake --build build --config Release --parallel

# -------------------------------------------------------------------------
# Test — 36/36 tests pass on all platforms
# Test — 41/41 tests pass on all platforms
# -------------------------------------------------------------------------
- name: Test
run: ctest --test-dir build -C Release --output-on-failure --timeout 60 -LE "known_broken|benchmark"
catalina-guard:
name: Catalina guard (configure-only)
runs-on: macos-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4

- name: Catalina-safe configure should pass
shell: bash
run: |
./scripts/configure_catalina.sh build-catalina-guard-pass \
-DBUILD_TESTS=OFF \
-DBUILD_EXAMPLES=OFF \
-DBUILD_TOOLS=OFF \
-DBUILD_BENCHMARKS=OFF

- name: Catalina contaminated configure should fail
shell: bash
run: |
set +e
CXXFLAGS="-isystem /usr/local/opt/llvm/include/c++/v1" \
cmake -S . -B build-catalina-guard-fail \
-DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 \
-DBUILD_TESTS=OFF \
-DBUILD_EXAMPLES=OFF \
-DBUILD_TOOLS=OFF \
-DBUILD_BENCHMARKS=OFF \
> build-catalina-guard-fail.log 2>&1
status=$?
set -e

if [ "$status" -eq 0 ]; then
echo "Expected configure to fail for Catalina Homebrew libc++ contamination."
cat build-catalina-guard-fail.log
exit 1
fi

if ! grep -Fq "Detected Homebrew LLVM/libc++ hints on macOS Catalina" build-catalina-guard-fail.log; then
echo "Expected Catalina guard diagnostic was not found."
cat build-catalina-guard-fail.log
exit 1
fi

# ---------------------------------------------------------------------------
# Quality jobs — ubuntu only; run independently of the build matrix.
Expand Down
12 changes: 4 additions & 8 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,12 +64,8 @@ cmake --build build

VS 2022 Build Tools or full VS is sufficient. Install from https://aka.ms/vs/17/release/vs_buildtools.exe, or `winget install Microsoft.VisualStudio.2022.BuildTools`, or `choco install visualstudio2022buildtools`. GTest is fetched automatically via `FetchContent`; no vcpkg needed.

### macOS Catalina (10.15)

libhmm has no dedicated Catalina build script. The standard `cmake --preset release` command applies, but note:
- Avoid Homebrew LLVM/libc++ on Catalina unless specifically troubleshooting; use the system AppleClang toolchain.
- To bypass the Homebrew libc++ compatibility guard for troubleshooting only, pass `-DLIBHMM_ALLOW_UNSUPPORTED_CATALINA_HOMEBREW_LIBCXX=ON`.
- Background on the ABI issue: see `../libstats/docs/BUILD_SYSTEM_GUIDE.md` (available only if the sibling `libstats` repo is checked out alongside `libhmm`).
> macOS 13 (Ventura) is the minimum supported version in v4. macOS 12 and earlier are not
> supported; use v3.8.0 or fork. See MIGRATION.md.

Build options: `BUILD_EXAMPLES`, `BUILD_TESTS`, `BUILD_TOOLS` (all `ON` by default), `BUILD_BENCHMARKS` (`OFF`), `ENABLE_CLANG_TIDY` (`OFF`).

Expand Down Expand Up @@ -161,7 +157,7 @@ SIMD compile flags (`LIBHMM_BEST_SIMD_FLAGS` = `-march=native` on GCC/Clang, CPU
There are two tiers of SIMD implementation:

- **Tier 2 (explicit intrinsics)**: `GaussianDistribution` and `ExponentialDistribution`. Dispatch chain: AVX-512 → AVX/AVX2 → SSE2 → NEON → scalar. `ForwardBackwardCalculator` and `BaumWelchTrainer` also have explicit recurrence kernels via `TranscendentalKernels`.
- **Tier 1 (compiler auto-vectorization)**: The other 14 distributions implement concrete non-virtual `getBatchLogProbabilities` loops. Whether the compiler emits vector instructions depends on the loop body and compiler; tier 1 is reliable "well-shaped scalar code" rather than guaranteed SIMD.
- **Tier 1 (compiler auto-vectorization)**: The other 14 scalar distributions implement concrete non-virtual `getBatchLogProbabilities` loops — reliable "well-shaped scalar code" rather than guaranteed SIMD. MV distributions (`DiagonalGaussian`, `FullCovGaussian`, `IndependentComponents`) call `getLogProbability(row_view(obs, t))` per timestep rather than a batch interface and are not in `LIBHMM_SIMD_SOURCES`.

`getBatchLogProbabilities(std::span<const double> obs, std::span<double> out)` is the SIMD interface: calculators call it once per state per `compute()` and consume a flat row-major buffer of log-emission values.

Expand Down Expand Up @@ -189,7 +185,7 @@ if (sumW < precision::ZERO || std::isnan(sumW)) return;

### I/O

JSON (`save_json` / `load_json`) is the recommended format—exact IEEE 754 round-trip, no external dependencies. Legacy XML support (`XMLFileReader` / `XMLFileWriter`) is retained for reading existing `.xml` files but deprecated for new code. Reference HMM files in both formats live in `samples/`.
JSON is the recommended format—exact IEEE 754 round-trip, no external dependencies. Scalar: `save_json`/`load_json`. MV: `save_json_mv`/`load_json_mv` (v4 schema with `obs_type: "multivariate"`). Legacy XML (`XMLFileReader`/`XMLFileWriter`) is scalar-only and deprecated; retained for reading existing `.xml` files. Reference HMM files live in `samples/`.

## Coding conventions

Expand Down
116 changes: 116 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,122 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [4.0.0] - 2026-06-11

Multivariate HMM support and full C++20 type system modernisation. 47/47 tests pass.

### Added — Multivariate emissions (v4)

- **`BasicHmm<Obs>`** template (`include/libhmm/basic_hmm.h`): parameterises the HMM core
on observation type. `using Hmm = BasicHmm<double>` preserves the v3 API unchanged;
`using HmmMV = BasicHmm<ObservationVectorView>` is the new multivariate alias.
- **`BasicEmissionDistribution<Obs>`** template (`include/libhmm/distributions/basic_emission_distribution.h`):
virtual interface parameterised on observation type. Scalar alias
`using EmissionDistribution = BasicEmissionDistribution<double>` unchanged.
- **`DistributionBase<Derived, Obs>`** CRTP base: provides thread-safe cache management,
default batch loop, and automatic `clone()`. Existing distributions updated to
`public DistributionBase<Derived>` (scalar default). See MIGRATION.md.
- **Three multivariate distributions** (`Obs = ObservationVectorView`):
- `DiagonalGaussianDistribution` — D means + D variances, uncorrelated features.
- `FullCovarianceGaussianDistribution` — D×D covariance with Cholesky caching and
regularisation, weighted MLE (Baum-Welch M-step).
- `IndependentComponentsDistribution` — D independent scalar emissions of any type.
- **Cholesky factorisation utilities** (`include/libhmm/linalg/cholesky.h`): `factorize`,
`log_det`, `inv_quad_form`, `solve_lower`. No external dependency.
- **Multivariate observation types** in `linalg_types.h`: `ObservationMatrix` (row-major
T×D), `MultiObservationLists`, `ObservationVectorView = std::span<const double>`,
`row_view()` helper.
- **Template calculators and trainers** (scalar aliases unchanged):
`BasicForwardBackwardCalculator<Obs>`, `BasicViterbiCalculator<Obs>`,
`BasicBaumWelchTrainer<Obs>`, `BasicMapBaumWelchTrainer<Obs>`, `BasicViterbiTrainer<Obs>`.
Each has scalar (`.cpp`) and MV (`_mv.cpp`) explicit-instantiation TUs compiled with
`LIBHMM_BEST_SIMD_FLAGS`. MV path uses `if constexpr` emission dispatch.
- **`kmeans_init`** (`include/libhmm/training/kmeans_init.h`): Lloyd's k-means with
k-means++ seeding on all observation vectors; initialises MV HMM emission distributions
from cluster members. Standard pre-training step before `BaumWelchTrainer<OVV>`.
- **Multivariate JSON IO** (`to_json(HmmMV)`, `from_json_mv`, `save_json_mv`, `load_json_mv`):
v4 schema `{"libhmm_version":"4","obs_type":"multivariate","dimensions":D,...}`.
Full parameter serialisation for all three MV distributions. Scalar JSON unchanged.
- **`getDimension()` virtual** on `BasicEmissionDistribution`: returns 1 for scalar
distributions (default); MV distributions override to return D. Used by `to_json(HmmMV)`.
- **`count_free_parameters` template**: works for both `Hmm` and `HmmMV`.
- **`examples/mv_gaussian_example.cpp`**: self-contained 2D DiagonalGaussian MV HMM
demonstration (synthetic data, no download required).

### Added — Documentation

- **`MIGRATION.md`**: v3→v4 upgrade guide covering platform minimums, CRTP change,
`Hmm` alias, JSON IO, and new MV features.
- **`CONTRIBUTING.md`**: contribution guidelines including toolchain policy (no PRs
restoring pre-v4 compiler support) and code standards (CCN ≤ 10, Doxygen, etc.).

### Changed — Platform and toolchain

- **Minimum compilers raised**: Apple Clang 14+ (macOS 13+), GCC 12+, Clang 14+,
MSVC 2022 17.x (`/std:c++20`). Catalina and earlier macOS no longer supported.
CMake minimum deployment target set to `13.0` on Apple.
- **`LIBHMM_HAS_STD_RANGES` probe removed**: `std::ranges::transform` used
unconditionally; pre-Ventura fallback branches deleted.
- All Catalina-specific CMake guards, Homebrew libc++ contamination detection,
and `LIBHMM_ALLOW_UNSUPPORTED_CATALINA_HOMEBREW_LIBCXX` option removed.

### Added — New MV examples and scripts

- **`examples/elk_mv_example.cpp`**: validates the v4 `IndependentComponentsDistribution`
API against the moveHMM R reference (Gamma + von Mises, 725 observations). Output
includes a statistical justification that within-state r(log\_step, angle) ≈ −0.06
(zero), confirming the independence assumption is appropriate. Cross-references
mv\_regime\_example for covariance comparison on genuinely correlated data.
- **`examples/mv_regime_example.cpp`**: 3-state regime HMM comparing `DiagonalGaussian`
vs `FullCovarianceGaussian` on correlated two-sector returns. Loads real SPY + QQQ
monthly log-returns (2000–2022) if present; falls back to embedded synthetic DGP
(ρ = 0.60–0.85 per state). FullCovGaussian wins by >240 BIC units on real data;
within-state ρ = 0.83–0.92. Validated against hmmlearn 0.3.3 — Model B LLs agree
to < 0.1 nat.
- **`scripts/prepare_mv_regime_data.R`**: downloads SPY + QQQ monthly returns from Yahoo
Finance via quantmod; writes `/tmp/spy_qqq_monthly.csv`.
- **`scripts/verify_mv_regime.py`**: hmmlearn 0.3.3 reference fit (20 random restarts,
diagonal and full covariance) for direct comparison against mv\_regime\_example output.
- **`docs/Future_Performance_Work.md`**: replaces the outdated performance-optimisation
document with a concise, accurate future-work document reflecting current SIMD
architecture.

### Added — MV distribution setter API

- **`DiagonalGaussianDistribution`**: `setParameters(means, variances)`, `setMeans(means)`,
`setVariances(variances)` — consistent with the univariate setter pattern.
- **`FullCovarianceGaussianDistribution`**: `setMean(mean)`, `setCovariance(cov)`,
`setParameters(mean, cov)` — validate dimensions and recompute Cholesky factor.
- **`IndependentComponentsDistribution`**: `setComponent(d, ptr)` — replace a component
distribution after construction.

### Changed — Code quality

- All trainer/calculator `train()` functions refactored to CCN ≤ 10 via extracted
helpers: `accum_one_sequence`, `process_one_sequence`, `precompute_log_trans_flat`,
`seed_kmeanspp`, `lloyd_assign`, `lloyd_update`, `fit_clusters`.
- `FullCovarianceGaussianDistribution::fit()` refactored with extracted
`compute_mean`, `compute_cov`, `compute_weighted_mean`, `compute_weighted_cov`.
- `from_json`/`from_json_mv` refactored with `checked_size`, `build_hmm`,
`read_distribution_array` helpers; CCN 14 → 4.
- `fillLogEmissions` (non-static) renamed from `fill_log_emissions` for naming-convention
consistency (non-static methods use camelCase; static helpers use snake_case).
- `DiagonalGaussianDistribution::setParameters` and `setVariances`: `variances` parameter
changed from pass-by-value to `const std::vector<double>&` (never moved).

### Tests (47 total, up from 42)

- `test_mv_calculator`: `BasicForwardBackwardCalculator<OVV>`, `BasicViterbiCalculator<OVV>`.
- `test_mv_training`: `BasicBaumWelchTrainer<OVV>`, `BasicViterbiTrainer<OVV>`,
`BasicMapBaumWelchTrainer<OVV>`, `kmeans_init`.
- `test_hmm_json_mv`: MV JSON round-trip, file I/O, schema validation, error cases.
- `test_cholesky`: Cholesky factorisation with exact analytical reference values.
- `test_model_selection` expanded: MV distribution parameter counts, `count_free_parameters(HmmMV)`.
- `test_multivariate_distributions` expanded: 11 new setter tests covering `setParameters`,
`setMeans`, `setVariances`, `setMean`, `setCovariance`, and `setComponent`.

---

## [3.8.0] - 2026-06-07

Final v3.x release before v4.0.0. 42/42 tests pass.
Expand Down
Loading
Loading