Skip to content

Commit 4e4d5cd

Browse files
vitskovclaude
andcommitted
Add comprehensive functionality and testing enhancements
- Implement add_empty_word() method with proper attribute management - Add post-installation verification system with regression testing - Create reference data framework for computational validation - Include test files in package distribution - Fix circular imports and empty code edge cases - Add comprehensive documentation and changelog 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent d50c2a1 commit 4e4d5cd

10 files changed

Lines changed: 646 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# Changelog
2+
3+
All notable changes to the combinatorial_codes package are documented in this file.
4+
5+
## [0.2.0] - 2025-01-26
6+
7+
### Added
8+
- **`add_empty_word()` method**: New method in `CombinatorialCode` class to add the empty word (empty set) to a combinatorial code
9+
- Handles both empty codes and codes with existing words
10+
- Properly updates all code attributes (`unique_sizes`, `indices_by_size`, `min_size`, etc.)
11+
- Returns boolean indicating if empty word was added or already existed
12+
13+
- **Post-installation verification system**: Automatic verification runs during `pip install`
14+
- New `install_verification.py` module with comprehensive regression tests
15+
- `verify_installation()` function for manual verification
16+
- Tests package imports, C extension status, and computational correctness
17+
- Validates against reference data for "random example 32"
18+
- Performance benchmarking of key operations
19+
20+
- **Regression testing framework**:
21+
- New `correct_examples.py` with reference results for "random example 32"
22+
- `RANDOM_EXAMPLE_32_INTERSECTIONS`: 494 intersection values
23+
- `RANDOM_EXAMPLE_32_OBSTRUCTIONS`: (False, 426) obstruction results
24+
- Unit tests in `test_combinatorial_codes.py` validate against reference data
25+
26+
- **Enhanced package distribution**:
27+
- Test files now included in pip installations via `MANIFEST.in`
28+
- `CustomInstall` class in `setup.py` runs post-installation verification
29+
- Package data includes `tests/*.py` and `pytest.ini`
30+
31+
### Fixed
32+
- **Empty code initialization**: Fixed missing attributes in truly empty codes
33+
- `unique_sizes` and `indices_by_size` now properly initialized
34+
- `min_size` set to `float('inf')` for empty codes instead of 0
35+
36+
- **`has_empty_set()` false positives**: Added `n_words > 0` check to prevent false positives when code is completely empty
37+
38+
- **Circular import in examples.py**: Changed from absolute import `from combinatorial_codes import CombinatorialCode` to relative import `from .codes import CombinatorialCode`
39+
40+
### Enhanced
41+
- **`has_empty_set()` method**: Improved logic to handle edge cases correctly
42+
- **Test coverage**: Added comprehensive tests for `add_empty_word()` method with 5 test cases:
43+
- Empty code initialization
44+
- Adding to existing code without empty set
45+
- Attempting to add when empty set already exists
46+
- Attribute verification after addition
47+
- Integration with other methods
48+
49+
- **README.md**: Added Installation Verification section documenting the new `verify_installation()` functionality
50+
51+
### Technical Details
52+
- Fixed attribute management in empty code edge cases
53+
- Improved error handling in post-installation verification
54+
- Enhanced package metadata and distribution configuration
55+
- Added comprehensive regression testing for computational correctness
56+
57+
### Performance
58+
- Verification typically completes in 4-6 seconds
59+
- C extension detection and performance validation included
60+
- Reference computation times: ~2 seconds for intersections, ~2.5 seconds for obstructions
61+
62+
### Dependencies
63+
No changes to core dependencies. Test dependencies managed via `extras_require`.
64+
65+
---
66+
67+
## Previous Versions
68+
- [0.1.x] - Initial releases with basic combinatorial codes functionality

MANIFEST.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ include setup.py
66
# Include C source files
77
recursive-include src/combinatorial_codes *.c *.h
88

9+
# Include test files for regression testing
10+
recursive-include tests *.py
11+
include pytest.ini
12+
913
# Include data files if any
1014
recursive-include src/combinatorial_codes *.txt *.dat *.json
1115

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,21 @@ To verify installation and see basic usage:
3333
python getting_started.py
3434
```
3535

36-
> **Note**: Tests do **not** run automatically during installation. They must be run separately using the commands above.
36+
### Installation Verification
37+
The package includes automatic post-installation verification that runs regression tests during `pip install`. You can also manually verify your installation:
38+
39+
```python
40+
from combinatorial_codes import verify_installation
41+
verify_installation()
42+
```
43+
44+
This will run comprehensive tests including:
45+
- Package imports and C extension detection
46+
- Computational correctness using reference examples
47+
- Performance benchmarking of key operations
48+
- Verification of the `add_empty_word()` functionality
49+
50+
The verification typically takes 4-6 seconds and provides detailed feedback on your installation status.
3751

3852
## C Extension Compilation
3953

setup.py

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from setuptools import setup, find_packages, Extension
22
from setuptools.command.build_ext import build_ext
3+
from setuptools.command.install import install
34
import numpy
45
import sys
56
import platform
7+
import subprocess
68

79
class CustomBuildExt(build_ext):
810
"""Custom build_ext command that provides user feedback and handles cross-platform builds"""
@@ -49,6 +51,47 @@ def build_extensions(self):
4951

5052
print("="*60 + "\n")
5153

54+
class CustomInstall(install):
55+
"""Custom install command that runs post-installation verification"""
56+
57+
def run(self):
58+
# Run the normal installation
59+
super().run()
60+
61+
# Run post-installation verification
62+
try:
63+
# Import and run verification after installation
64+
print("\n" + "="*60)
65+
print("RUNNING POST-INSTALLATION VERIFICATION...")
66+
print("="*60)
67+
68+
# We need to use subprocess because the package might not be
69+
# importable in the current process during installation
70+
result = subprocess.run([
71+
sys.executable, "-c",
72+
"""
73+
import sys
74+
import os
75+
# Try to import and run verification
76+
try:
77+
from combinatorial_codes.install_verification import verify_installation
78+
verify_installation()
79+
except ImportError:
80+
print('⚠️ Could not run post-installation verification.')
81+
print(' Package may still be functional. Try running manually:')
82+
print(' python -c "from combinatorial_codes.install_verification import verify_installation; verify_installation()"')
83+
except Exception as e:
84+
print(f'⚠️ Post-installation verification encountered an error: {e}')
85+
print(' Package may still be functional.')
86+
"""
87+
], capture_output=False, text=True)
88+
89+
except Exception as e:
90+
print(f"\n⚠️ Could not run post-installation verification: {e}")
91+
print("The package should still be functional.")
92+
print("You can manually run verification with:")
93+
print("python -c \"from combinatorial_codes.install_verification import verify_installation; verify_installation()\"")
94+
5295
# Define the C extension with cross-platform handling
5396
def get_ext_modules():
5497
try:
@@ -87,7 +130,7 @@ def get_ext_modules():
87130
packages=find_packages(where="src"),
88131
package_dir={"": "src"},
89132
ext_modules=get_ext_modules(),
90-
cmdclass={'build_ext': CustomBuildExt},
133+
cmdclass={'build_ext': CustomBuildExt, 'install': CustomInstall},
91134
python_requires=">=3.11",
92135
install_requires=[
93136
"numba>=0.57.0", # Adjust version as needed
@@ -107,6 +150,10 @@ def get_ext_modules():
107150
"flake8",
108151
],
109152
},
153+
include_package_data=True,
154+
package_data={
155+
"": ["tests/*.py", "pytest.ini"],
156+
},
110157
classifiers=[
111158
"Programming Language :: Python :: 3.11",
112159
"License :: OSI Approved :: MIT License",

src/combinatorial_codes/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from .examples import example_dictionary
2929
from .examples import bernoulli_random_code
3030
from .status import check_c_extension_status, quick_status
31+
from .install_verification import verify_installation
3132
# Import translated_functions (C extension) if available
3233
try:
3334
from . import translated_functions
@@ -47,7 +48,7 @@
4748
"array_of_words_to_vectors_of_integers",
4849
"intersections_via_cliques",
4950
"intersections_inside_a_clique",
50-
"check_c_extension_status", "quick_status",
51+
"check_c_extension_status", "quick_status", "verify_installation",
5152
"translated_functions"
5253
]
5354

src/combinatorial_codes/codes.py

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,11 @@ def __init__(self, array_of_vectors: List[List[int]], method: str = "array_of_ve
120120
# No words were passed
121121
self.words = np.zeros(0,dtype=np.uint8)
122122
self.dtype=np.uint8
123-
self.sizes = []
123+
self.sizes = np.array([])
124+
self.unique_sizes = np.array([])
125+
self.indices_by_size = Dict.empty(key_type=types.int64, value_type=types.Array(types.int64, 1, 'C'))
124126
self.max_size = 0
125-
self.min_size = 0
127+
self.min_size = float('inf') # No words means no minimum size
126128
self.n_words = 0
127129
self.n_bits=0
128130
self.maximal_words= self.words
@@ -155,7 +157,7 @@ def __init__(self, array_of_vectors: List[List[int]], method: str = "array_of_ve
155157
self.min_size, self.max_size = self.unique_sizes.min(), self.unique_sizes.max()
156158
self.maximal_words= find_maximal_words(words, self.unique_sizes, indices_by_size,self.dtype)
157159
def has_empty_set(self):
158-
return bool(self.min_size==0)
160+
return bool(self.n_words > 0 and self.min_size==0)
159161
def has_full_set(self):
160162
return bool(self.max_size==self.n_bits)
161163
def simplicial_violators(self, enforce_maximal_word_limit: bool=True):
@@ -174,6 +176,76 @@ def __repr__(self):
174176
def show(self):
175177
print(self.__repr__())
176178

179+
def add_empty_word(self):
180+
"""Add the empty word (empty set) to the combinatorial code.
181+
182+
This method adds exactly one new word to the code: the empty set (word = 0).
183+
If the code already contains the empty word, no changes are made.
184+
If there were non-empty words in the code, the list of maximal words remains unchanged.
185+
All other attributes (indices_by_size, unique_sizes, sizes, min_size, n_words, etc.)
186+
are updated correctly.
187+
188+
Returns:
189+
bool: True if the empty word was added, False if it was already present
190+
"""
191+
# Check if empty word already exists
192+
if self.has_empty_set():
193+
return False
194+
195+
# Handle empty code case
196+
if self.n_words == 0:
197+
# If code was empty, initialize with just the empty word
198+
# Need to set a proper n_bits value - use default or 1
199+
if not hasattr(self, 'n_bits') or self.n_bits == 0:
200+
self.n_bits = 1 # Default minimum
201+
202+
self.dtype = WORD_TYPE # Use proper word type
203+
self.words = np.array([0], dtype=self.dtype)
204+
self.sizes = np.array([0])
205+
self.unique_sizes = np.array([0])
206+
self.indices_by_size = Dict.empty(key_type=types.int64, value_type=types.Array(types.int64, 1, 'C'))
207+
self.indices_by_size[0] = np.array([0])
208+
self.n_words = 1
209+
self.min_size = 0
210+
self.max_size = 0
211+
self.maximal_words = np.array([0], dtype=self.dtype)
212+
213+
# Initialize translation_dict for consistency
214+
self.translation_dict = {i: i for i in range(self.n_bits)}
215+
return True
216+
217+
# Add empty word to existing code
218+
empty_word = self.dtype(0) # Empty word is represented as 0
219+
220+
# Add to words array
221+
self.words = np.concatenate([np.array([empty_word], dtype=self.dtype), self.words])
222+
223+
# Add to sizes array
224+
self.sizes = np.concatenate([np.array([0]), self.sizes])
225+
226+
# Update n_words
227+
self.n_words += 1
228+
229+
# Update unique_sizes to include 0 if not already present
230+
if 0 not in self.unique_sizes:
231+
self.unique_sizes = np.concatenate([np.array([0]), self.unique_sizes])
232+
self.unique_sizes.sort()
233+
234+
# Rebuild indices_by_size
235+
indices_by_size = Dict.empty(key_type=types.int64, value_type=types.Array(types.int64, 1, 'C'))
236+
for (i, u) in enumerate(self.unique_sizes):
237+
indices_by_size[int(u)] = np.where(self.sizes == u)[0]
238+
self.indices_by_size = indices_by_size
239+
240+
# Update min_size (now 0 since we added empty word)
241+
self.min_size = 0
242+
# max_size remains unchanged
243+
244+
# Maximal words remain unchanged when adding empty word (empty word is never maximal if non-empty words exist)
245+
# No need to recalculate maximal_words since empty word cannot be maximal when other words exist
246+
247+
return True
248+
177249

178250

179251

0 commit comments

Comments
 (0)