Source code for ppbcc.code_complexity.halstead
"""Computation of the Halstead complexity measures.
Given the operator/operand multisets produced by
:mod:`ppbcc.code_complexity.classification`, this module derives the classic
Halstead metrics (Maurice Halstead, *Elements of Software Science*, 1977):
=========================== ==================================================
Measure Formula
=========================== ==================================================
Vocabulary ``n`` ``n1 + n2``
Length ``N`` ``N1 + N2``
Calculated length ``N^`` ``n1*log2(n1) + n2*log2(n2)``
Volume ``V`` ``N * log2(n)``
Difficulty ``D`` ``(n1 / 2) * (N2 / n2)``
Effort ``E`` ``D * V``
Time ``T`` ``E / 18`` seconds
Delivered bugs ``B`` ``V / 3000``
Program level ``L`` ``1 / D``
Language level ``lambda`` ``L^2 * V``
=========================== ==================================================
with ``n1``/``n2`` the number of distinct operators/operands and ``N1``/``N2``
their total number of occurrences.
"""
from __future__ import annotations
import math
from collections import Counter
from dataclasses import dataclass
[docs]
@dataclass(frozen=True)
class HalsteadMetrics:
"""The Halstead base counts and derived measures of one program.
Attributes:
distinct_operators: Number of distinct operators ``n1``.
distinct_operands: Number of distinct operands ``n2``.
total_operators: Total operator occurrences ``N1``.
total_operands: Total operand occurrences ``N2``.
"""
distinct_operators: int
distinct_operands: int
total_operators: int
total_operands: int
[docs]
@classmethod
def from_counts(cls, operators: Counter[str], operands: Counter[str]) -> "HalsteadMetrics":
"""Builds the metrics from operator/operand multisets.
Args:
operators: Operator occurrences keyed by operator text.
operands: Operand occurrences keyed by operand text.
Returns:
The corresponding :class:`HalsteadMetrics`.
"""
return cls(
distinct_operators=len(operators),
distinct_operands=len(operands),
total_operators=sum(operators.values()),
total_operands=sum(operands.values()),
)
@property
def vocabulary(self) -> int:
"""Program vocabulary ``n = n1 + n2``."""
return self.distinct_operators + self.distinct_operands
@property
def length(self) -> int:
"""Program length ``N = N1 + N2``."""
return self.total_operators + self.total_operands
@property
def calculated_length(self) -> float:
"""Estimated program length ``N^ = n1*log2(n1) + n2*log2(n2)``."""
result = 0.0
for distinct in (self.distinct_operators, self.distinct_operands):
if distinct > 0:
result += distinct * math.log2(distinct)
return result
@property
def volume(self) -> float:
"""Program volume ``V = N * log2(n)`` in bits."""
if self.vocabulary == 0:
return 0.0
return self.length * math.log2(self.vocabulary)
@property
def difficulty(self) -> float:
"""Program difficulty ``D = (n1 / 2) * (N2 / n2)``."""
if self.distinct_operands == 0:
return 0.0
return (self.distinct_operators / 2.0) * (self.total_operands / self.distinct_operands)
@property
def effort(self) -> float:
"""Programming effort ``E = D * V`` in elementary mental discriminations."""
return self.difficulty * self.volume
@property
def time_seconds(self) -> float:
"""Estimated implementation time ``T = E / 18`` in seconds."""
return self.effort / 18.0
@property
def delivered_bugs(self) -> float:
"""Estimated number of delivered bugs ``B = V / 3000``."""
return self.volume / 3000.0
@property
def program_level(self) -> float:
"""Program level ``L = 1 / D`` (1 is the most abstract program)."""
if self.difficulty == 0.0:
return 0.0
return 1.0 / self.difficulty
@property
def language_level(self) -> float:
"""Language level ``lambda = L^2 * V``."""
return self.program_level**2 * self.volume
[docs]
def as_dict(self) -> dict[str, int | float]:
"""Serialises all base counts and derived measures.
Returns:
Mapping from metric name (matching the CSV column names) to its
value, in a stable order.
"""
return {
"distinct_operators": self.distinct_operators,
"distinct_operands": self.distinct_operands,
"total_operators": self.total_operators,
"total_operands": self.total_operands,
"vocabulary": self.vocabulary,
"length": self.length,
"calculated_length": self.calculated_length,
"volume": self.volume,
"difficulty": self.difficulty,
"effort": self.effort,
"time_seconds": self.time_seconds,
"delivered_bugs": self.delivered_bugs,
"program_level": self.program_level,
"language_level": self.language_level,
}