Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [3.5.0] - 2026-05-06

cppcheck quality pass and V2 numerical framework removal. 37/37 tests pass.

### Removed

- **V2 numerical safety framework** (`include/libhmm/math/numerical_stability.h`,
`src/common/numerical_stability.cpp`, `tests/common/test_numerical_stability.cpp`):
`NumericalSafety`, `ConvergenceDetector`, `AdaptivePrecision`, `ErrorRecovery`,
and `NumericalDiagnostics` were built for the pre-V3 scaled-calculator architecture
and had no callers since the log-space-only pivot in V3. cppcheck confirmed every
method was unreachable; dead callee chain back to the deleted scaled trainers.
Files and associated test removed. 37/38 → 37 tests.

## [3.4.0] - 2026-05-05

Code quality refactoring (Phases 1–3) and JSON serialization. 38/38 tests pass.
Expand Down
3 changes: 1 addition & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ if(APPLE AND NOT CMAKE_CXX_COMPILER)
endif()

project(libhmm
VERSION 3.4.0
VERSION 3.5.0
DESCRIPTION "Modern C++20 Hidden Markov Model Library"
LANGUAGES CXX
)
Expand Down Expand Up @@ -518,7 +518,6 @@ set(LIBHMM_SOURCES
src/hmm.cpp
src/common/common.cpp
src/common/string_tokenizer.cpp
src/common/numerical_stability.cpp
src/common/weighted_stats.cpp
src/performance/transcendental_kernels.cpp
src/distributions/distribution_base.cpp
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
[![C++20](https://img.shields.io/badge/C%2B%2B-20-blue.svg)](https://isocpp.org/std/the-standard)
[![CMake](https://img.shields.io/badge/CMake-3.20%2B-blue.svg)](https://cmake.org/)
[![License](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
[![Version](https://img.shields.io/badge/Version-3.4.0-brightgreen.svg)](https://github.com/OldCrow/libhmm/releases)
[![Tests](https://img.shields.io/badge/Tests-38%2F38_Passing-success.svg)](tests/)
[![Version](https://img.shields.io/badge/Version-3.5.0-brightgreen.svg)](https://github.com/OldCrow/libhmm/releases)
[![Tests](https://img.shields.io/badge/Tests-37%2F37_Passing-success.svg)](tests/)
[![SIMD](https://img.shields.io/badge/SIMD-AVX--512%2FAVX2%2FSSE2%2FNEON-blue.svg)](src/distributions/)
[![CI](https://github.com/OldCrow/libhmm/actions/workflows/ci.yml/badge.svg)](https://github.com/OldCrow/libhmm/actions)

Expand Down Expand Up @@ -148,7 +148,7 @@ libhmm/
│ ├── training/ # Layer 4: BaumWelch, Viterbi, SegmentalKMeans
│ └── io/ # JSON (hmm_json.h) + legacy XML I/O
├── src/ # Implementation (mirrors include/)
├── tests/ # 38-test GTest suite
├── tests/ # 37-test GTest suite
├── examples/ # 12 usage demonstrations
├── tools/ # simd_inspection, batch_performance, hmm_validator (.json/.xml)
├── samples/ # Reference HMM files (two_state_gaussian, casino) in JSON and XML
Expand Down
19 changes: 0 additions & 19 deletions include/libhmm/distributions/beta_distribution.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,25 +135,6 @@ class BetaDistribution : public DistributionBase {
void fit(std::span<const double> data, std::span<const double> weights) override;
[[nodiscard]] bool isDiscrete() const noexcept override { return false; }

/**
* Vectorized batch computation of PDF for multiple values.
* Optimized for processing many values efficiently with cache reuse.
*
* @param values Vector of input values
* @param results Output vector for results (will be resized if needed)
*/
void getProbabilityBatch(const std::vector<double> &values, std::vector<double> &results);

/**
* Vectorized batch computation of log PDF for multiple values.
* Optimized for processing many values efficiently with cache reuse.
*
* @param values Vector of input values
* @param results Output vector for results (will be resized if needed)
*/
void getLogProbabilityBatch(const std::vector<double> &values,
std::vector<double> &results) const;

/**
* Resets the distribution to default parameters (α = 1.0, β = 1.0).
* This corresponds to a uniform distribution on [0,1].
Expand Down
15 changes: 14 additions & 1 deletion include/libhmm/distributions/discrete_distribution.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,19 @@ class DiscreteDistribution : public DistributionBase {
throw std::invalid_argument("Probability value must be between 0 and 1");
}

/// Fills pdf_ with uniform probabilities 1/numSymbols_ and invalidates the cache.
///
/// This exists as a private non-virtual helper so the constructor can
/// perform the same initialisation as reset() without invoking a virtual
/// function during construction (where virtual dispatch is not active and
/// cppcheck correctly flags the call as misleading). reset() delegates
/// here so the logic lives in exactly one place.
void init_uniform() noexcept {
const double p = 1.0 / static_cast<double>(numSymbols_);
std::fill(pdf_.begin(), pdf_.end(), p);
invalidateCache();
}

public:
/**
* Constructs a Discrete distribution with given number of symbols.
Expand All @@ -124,7 +137,7 @@ class DiscreteDistribution : public DistributionBase {
explicit DiscreteDistribution(int symbols = 10)
: DistributionBase{}, numSymbols_{validateSymbols(symbols)}, pdf_(numSymbols_),
cachedSum_{1.0}, cachedEntropy_{0.0} {
reset();
init_uniform();
}

DiscreteDistribution(const DiscreteDistribution &other) = default;
Expand Down
2 changes: 1 addition & 1 deletion include/libhmm/distributions/distribution_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Reader;
*/
class DistributionBase : public EmissionDistribution {
public:
virtual ~DistributionBase() = default;
~DistributionBase() override = default;

DistributionBase();
DistributionBase(const DistributionBase &other);
Expand Down
2 changes: 1 addition & 1 deletion include/libhmm/distributions/gamma_distribution.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class GammaDistribution : public DistributionBase {
* Evaluates the LOWER INCOMPLETE gamma function at x
* Uses numerical approximation for computational efficiency
*/
double ligamma(double a, double x) noexcept;
static double ligamma(double a, double x) noexcept;

public:
/**
Expand Down
2 changes: 1 addition & 1 deletion include/libhmm/distributions/log_normal_distribution.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ class LogNormalDistribution : public DistributionBase {
* @param stdDev Standard deviation of ln(X) (must be positive and finite)
* @throws std::invalid_argument if parameters are invalid
*/
void validateParameters(double mean, double stdDev) const {
static void validateParameters(double mean, double stdDev) {
if (std::isnan(mean) || std::isinf(mean)) {
throw std::invalid_argument("Mean parameter must be a finite number");
}
Expand Down
4 changes: 2 additions & 2 deletions include/libhmm/distributions/negative_binomial_distribution.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class NegativeBinomialDistribution : public DistributionBase {
* @param p Success probability (must be in (0,1])
* @throws std::invalid_argument if parameters are invalid
*/
void validateParameters(double r, double p) const {
static void validateParameters(double r, double p) {
if (std::isnan(r) || std::isinf(r) || r <= 0.0) {
throw std::invalid_argument("Number of successes must be positive");
}
Expand Down Expand Up @@ -247,7 +247,7 @@ class NegativeBinomialDistribution : public DistributionBase {
* @param value The value at which to evaluate the CDF
* @return Cumulative probability P(X ≤ value)
*/
[[nodiscard]] double CDF(double value) const noexcept;
[[nodiscard]] double getCumulativeProbability(double value) const noexcept;

/**
* Gets the mode of the distribution.
Expand Down
2 changes: 1 addition & 1 deletion include/libhmm/distributions/pareto_distribution.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class ParetoDistribution : public DistributionBase {
* @param xm Scale parameter (must be positive and finite)
* @throws std::invalid_argument if parameters are invalid
*/
void validateParameters(double k, double xm) const {
static void validateParameters(double k, double xm) {
if (std::isnan(k) || std::isinf(k) || k <= 0.0) {
throw std::invalid_argument("Shape parameter k must be a positive finite number");
}
Expand Down
2 changes: 1 addition & 1 deletion include/libhmm/distributions/poisson_distribution.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class PoissonDistribution : public DistributionBase {
* @param k Value to validate
* @return true if k is a valid count, false otherwise
*/
bool isValidCount(double k) const noexcept {
static bool isValidCount(double k) noexcept {
return k >= 0.0 && std::floor(k) == k && std::isfinite(k);
}

Expand Down
2 changes: 1 addition & 1 deletion include/libhmm/distributions/weibull_distribution.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class WeibullDistribution : public DistributionBase {
* @param lambda Scale parameter (must be positive and finite)
* @throws std::invalid_argument if parameters are invalid
*/
void validateParameters(double k, double lambda) const {
static void validateParameters(double k, double lambda) {
if (std::isnan(k) || std::isinf(k) || k <= 0.0) {
throw std::invalid_argument("Shape parameter k must be a positive finite number");
}
Expand Down
2 changes: 1 addition & 1 deletion include/libhmm/io/xml_file_reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ class XMLFileReader {
* @return Loaded HMM object
* @throws std::runtime_error if deserialization fails
*/
Hmm readFromStream(std::ifstream &stream);
static Hmm readFromStream(std::ifstream &stream);
};

} // namespace libhmm
2 changes: 1 addition & 1 deletion include/libhmm/io/xml_file_writer.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class XMLFileWriter {
* @param stream Output stream
* @throws std::runtime_error if serialization fails
*/
void writeToStream(const Hmm &hmm, std::ofstream &stream);
static void writeToStream(const Hmm &hmm, std::ofstream &stream);
};

} // namespace libhmm
12 changes: 6 additions & 6 deletions include/libhmm/linalg/basic_matrix.h
Original file line number Diff line number Diff line change
Expand Up @@ -341,11 +341,11 @@ class BasicMatrix {
BasicVector<T> row_sums() const {
BasicVector<T> result(rows_);
for (size_type i = 0; i < rows_; ++i) {
T sum = T{};
T row_sum = T{};
for (size_type j = 0; j < cols_; ++j) {
sum += (*this)(i, j);
row_sum += (*this)(i, j);
}
result[i] = sum;
result[i] = row_sum;
}
return result;
}
Expand All @@ -357,11 +357,11 @@ class BasicMatrix {
BasicVector<T> column_sums() const {
BasicVector<T> result(cols_);
for (size_type j = 0; j < cols_; ++j) {
T sum = T{};
T col_sum = T{};
for (size_type i = 0; i < rows_; ++i) {
sum += (*this)(i, j);
col_sum += (*this)(i, j);
}
result[j] = sum;
result[j] = col_sum;
}
return result;
}
Expand Down
Loading
Loading