stanlogic 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: stanlogic
3
+ Version: 1.1.0
4
+ Summary: A Boolean simplification and inference library that integrates K-Map simplification and logical inference.
5
+ Author: Stan's Technologies
6
+ Author-email: Somtochukwu Stanislus Emeka-Onwuneme <somtochukwueo@outlook.com>
7
+ License: MIT NC
8
+ Keywords: boolean-algebra,kmap,logic-simplification,inference,symbolic-logic
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ Dynamic: author
15
+ Dynamic: requires-python
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "stanlogic"
7
+ version = "1.1.0"
8
+ authors = [
9
+ { name = "Somtochukwu Stanislus Emeka-Onwuneme", email = "somtochukwueo@outlook.com" }
10
+ ]
11
+ description = "A Boolean simplification and inference library that integrates K-Map simplification and logical inference."
12
+ readme = "README.md"
13
+ requires-python = ">=3.8"
14
+ license = { text = "MIT NC" }
15
+ keywords = ["boolean-algebra", "kmap", "logic-simplification", "inference", "symbolic-logic"]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "License :: OSI Approved :: MIT License",
19
+ "Operating System :: OS Independent",
20
+ ]
21
+
22
+ # Entry point for setuptools to find your code
23
+ [tool.setuptools.packages.find]
24
+ where = ["src"]
25
+
26
+ # pytest configuration
27
+ [tool.pytest.ini_options]
28
+ testpaths = ["tests"]
29
+ pythonpath = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,11 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="StanLogic",
5
+ version="1.0.0",
6
+ packages=find_packages(where="src"),
7
+ package_dir={"": "src"},
8
+ author="Stan's Technologies",
9
+ description="An advanced KMap solver and logic simplification engine",
10
+ python_requires=">=3.8",
11
+ )
@@ -0,0 +1,15 @@
1
+ Metadata-Version: 2.4
2
+ Name: stanlogic
3
+ Version: 1.1.0
4
+ Summary: A Boolean simplification and inference library that integrates K-Map simplification and logical inference.
5
+ Author: Stan's Technologies
6
+ Author-email: Somtochukwu Stanislus Emeka-Onwuneme <somtochukwueo@outlook.com>
7
+ License: MIT NC
8
+ Keywords: boolean-algebra,kmap,logic-simplification,inference,symbolic-logic
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ Dynamic: author
15
+ Dynamic: requires-python
@@ -0,0 +1,12 @@
1
+ pyproject.toml
2
+ setup.py
3
+ src/StanLogic.egg-info/PKG-INFO
4
+ src/StanLogic.egg-info/SOURCES.txt
5
+ src/StanLogic.egg-info/dependency_links.txt
6
+ src/StanLogic.egg-info/top_level.txt
7
+ src/stanlogic/__init__.py
8
+ src/stanlogic/kmapsolver.py
9
+ src/stanlogic.egg-info/PKG-INFO
10
+ src/stanlogic.egg-info/SOURCES.txt
11
+ src/stanlogic.egg-info/dependency_links.txt
12
+ src/stanlogic.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ stanlogic
@@ -0,0 +1,9 @@
1
+ # kmap_algorithm/__init__.py
2
+
3
+ from .kmapsolver import KMapSolver
4
+
5
+ __all__ = [
6
+ "KMapSolver",
7
+ ]
8
+
9
+ __version__ = "1.1.0"
@@ -0,0 +1,298 @@
1
+ """
2
+ stanclustlogic.py, library to easily solve 2-4 variable kmaps.
3
+
4
+ Developed from kmapsolver.py, kmap_solver_prototype.py
5
+ """
6
+
7
+ # Author: Somtochukwu Stanislus Emeka-Onwuneme
8
+ # Publication Date: 8th September, 2025
9
+ # Copyright © 2025 Somtochukwu Stanislus Emeka-Onwuneme
10
+ #---------------------------------------------------------
11
+
12
+ from collections import defaultdict
13
+
14
+ class KMapSolver:
15
+ def __init__(self, kmap, convention = "vranseic"):
16
+ """
17
+ Initialize with a given K-map.
18
+ kmap: 2D list with values 0, 1, or 'd'
19
+ """
20
+ self.kmap = kmap
21
+ self.num_rows = len(kmap)
22
+ self.num_cols = len(kmap[0])
23
+ self.convention = convention.lower().strip()
24
+
25
+ # Determine number of variables (2, 3, or 4)
26
+ size = self.num_rows * self.num_cols
27
+ if size == 4:
28
+ self.num_vars = 2
29
+ self.row_labels = ["0", "1"]
30
+ self.col_labels = ["0", "1"]
31
+ elif size == 8:
32
+ self.num_vars = 3
33
+ self.row_labels = ["0", "1"]
34
+ self.col_labels = ["00", "01", "11", "10"]
35
+ elif size == 16:
36
+ self.num_vars = 4
37
+ self.row_labels = ["00", "01", "11", "10"]
38
+ self.col_labels = ["00", "01", "11", "10"]
39
+ else:
40
+ raise ValueError("K-map must be 2x2 (2 vars), 2x4/4x2 (3 vars), or 4x4 (4 vars)")
41
+
42
+ # Group sizes (powers of 2 only)
43
+ self.group_sizes = [(1,1),(1,2),(2,1),(2,2),
44
+ (1,4),(4,1),(2,4),(4,2),(4,4)]
45
+
46
+ # ---- Optimizations: precompute mapping and prune group sizes ----
47
+ # keep only group sizes that actually fit the K-map
48
+ self.group_sizes = [(h,w) for (h,w) in self.group_sizes if h <= self.num_rows and w <= self.num_cols]
49
+
50
+ # Precompute cell index (minterm/maxterm index) and bit-strings for each cell.
51
+ # Use _cell_to_bits/_cell_to_term (methods are available on the instance)
52
+ self._cell_index = [[self._cell_to_term(r, c) for c in range(self.num_cols)] for r in range(self.num_rows)]
53
+ self._cell_bits = [[self._cell_to_bits(r, c) for c in range(self.num_cols)] for r in range(self.num_rows)]
54
+ # map minterm index -> (r, c)
55
+ self._index_to_rc = {self._cell_index[r][c]: (r, c) for r in range(self.num_rows) for c in range(self.num_cols)}
56
+
57
+ # ---------- Utility functions ---------- #
58
+ def _cell_to_term(self, r, c):
59
+ """Convert (row, col) to a minterm index."""
60
+ bits = self.col_labels[c] + self.row_labels[r]
61
+ return int(bits, 2)
62
+
63
+ # def _cell_to_maxterm(self, r, c):
64
+ # """Convert (row, col) to a maxterm index."""
65
+ # bits = self.col_labels[c] + self.row_labels[r]
66
+ # return int(bits, 2)
67
+
68
+ def _cell_to_bits(self, r, c):
69
+ """Return the bit string representing (row, col) based on the convention."""
70
+ if self.convention == "mano_kime":
71
+ # Mano-Kime → rows = x1x2, cols = x3x4
72
+ bits = self.row_labels[r] + self.col_labels[c]
73
+ else:
74
+ # Vranesic → rows = x3x4, cols = x1x2
75
+ bits = self.col_labels[c] + self.row_labels[r]
76
+ return bits
77
+
78
+
79
+ def _get_group_coords(self, r, c, h, w):
80
+ """Get wrapped coordinates for a group starting at (r,c)."""
81
+ return [((r+i) % self.num_rows, (c+j) % self.num_cols)
82
+ for i in range(h) for j in range(w)]
83
+
84
+ # ---------- Group finding ---------- #
85
+ def find_all_groups(self, allow_dontcare=False):
86
+ """Find all groups of 1’s (and optionally 'd'). Return integer bitmasks for groups."""
87
+ groups = set()
88
+ for r in range(self.num_rows):
89
+ for c in range(self.num_cols):
90
+ for h, w in self.group_sizes:
91
+ coords = self._get_group_coords(r, c, h, w)
92
+
93
+ if allow_dontcare:
94
+ if all(self.kmap[rr][cc] in (1, 'd') for rr, cc in coords) and \
95
+ any(self.kmap[rr][cc] == 1 for rr, cc in coords):
96
+ mask = 0
97
+ for rr, cc in coords:
98
+ mask |= 1 << self._cell_index[rr][cc]
99
+ groups.add(mask)
100
+ else:
101
+ if all(self.kmap[rr][cc] == 1 for rr, cc in coords):
102
+ mask = 0
103
+ for rr, cc in coords:
104
+ mask |= 1 << self._cell_index[rr][cc]
105
+ groups.add(mask)
106
+ return list(groups)
107
+
108
+ def find_all_groups_pos(self, allow_dontcare=False):
109
+ """Find all groups of 0's (and optionally 'd') for POS. Return integer bitmasks."""
110
+ groups = set()
111
+ for r in range(self.num_rows):
112
+ for c in range(self.num_cols):
113
+ for h, w in self.group_sizes:
114
+ coords = self._get_group_coords(r, c, h, w)
115
+
116
+ if allow_dontcare:
117
+ if all(self.kmap[rr][cc] in (0, 'd') for rr, cc in coords) and \
118
+ any(self.kmap[rr][cc] == 0 for rr, cc in coords):
119
+ mask = 0
120
+ for rr, cc in coords:
121
+ mask |= 1 << self._cell_index[rr][cc]
122
+ groups.add(mask)
123
+ else:
124
+ if all(self.kmap[rr][cc] == 0 for rr, cc in coords):
125
+ mask = 0
126
+ for rr, cc in coords:
127
+ mask |= 1 << self._cell_index[rr][cc]
128
+ groups.add(mask)
129
+ return list(groups)
130
+
131
+ def filter_prime_implicants(self, groups):
132
+ """Remove groups that are strict subsets of others. groups are integer masks."""
133
+ primes = []
134
+ # sort by bit_count descending to allow early pruning (larger groups first)
135
+ groups_sorted = sorted(groups, key=lambda g: g.bit_count(), reverse=True)
136
+ for i, g in enumerate(groups_sorted):
137
+ is_subset = False
138
+ for other in groups_sorted:
139
+ if other == g:
140
+ continue
141
+ # if g is subset of other
142
+ if (g & other) == g:
143
+ is_subset = True
144
+ break
145
+ if not is_subset:
146
+ primes.append(g)
147
+ return primes
148
+
149
+ # ---------- Boolean simplification ---------- #
150
+ def _simplify_group_bits(self, bits_list):
151
+ bits = list(bits_list[0])
152
+ for b in bits_list[1:]:
153
+ for i in range(self.num_vars):
154
+ if bits[i] != b[i]:
155
+ bits[i] = '-'
156
+
157
+ vars_ = [f"x{i+1}" for i in range(self.num_vars)]
158
+ term = []
159
+ for i, b in enumerate(bits):
160
+ if b == '0':
161
+ term.append(vars_[i] + "'")
162
+ elif b == '1':
163
+ term.append(vars_[i])
164
+ return "".join(term)
165
+
166
+ def _simplify_group_bits_pos(self, bits_list):
167
+ """Convert group bits to POS term."""
168
+ bits = list(bits_list[0])
169
+ for b in bits_list[1:]:
170
+ for i in range(self.num_vars):
171
+ if bits[i] != b[i]:
172
+ bits[i] = '-'
173
+
174
+ vars_ = [f"x{i+1}" for i in range(self.num_vars)]
175
+ term = []
176
+ for i, b in enumerate(bits):
177
+ if b == '1':
178
+ term.append(vars_[i] + "'")
179
+ elif b == '0':
180
+ term.append(vars_[i])
181
+ return "(" + " + ".join(term) + ")"
182
+
183
+ # ---------- Main minimization ---------- #
184
+ def minimize(self, form='sop'):
185
+ """
186
+ Minimize the K-map expression.
187
+ Args:
188
+ form: 'sop' for Sum of Products or 'pos' for Product of Sums
189
+ Returns:
190
+ tuple: (list of terms, string expression)
191
+ """
192
+ if form.lower() not in ['sop', 'pos']:
193
+ raise ValueError("form must be either 'sop' or 'pos'")
194
+
195
+ target_val = 0 if form.lower() == 'pos' else 1
196
+
197
+ # Choose appropriate group-finding and simplifier
198
+ if form.lower() == 'pos':
199
+ groups = self.find_all_groups_pos(allow_dontcare=True)
200
+ simplify_method = self._simplify_group_bits_pos
201
+ join_operator = " * "
202
+ else:
203
+ groups = self.find_all_groups(allow_dontcare=True)
204
+ simplify_method = self._simplify_group_bits
205
+ join_operator = " + "
206
+
207
+ # Step 2: reduce to prime implicants (groups are bitmasks)
208
+ prime_groups = self.filter_prime_implicants(groups)
209
+
210
+ # Step 3: compute terms and covers (use bitmasks for covers)
211
+ prime_terms = []
212
+ prime_covers = []
213
+ for gmask in prime_groups:
214
+ # collect only actual minterms (target_val) covered by this group
215
+ cover_mask = 0
216
+ bits_list = []
217
+ temp = gmask
218
+ while temp:
219
+ low = temp & -temp
220
+ idx = low.bit_length() - 1
221
+ temp -= low
222
+ r, c = self._index_to_rc[idx]
223
+ if self.kmap[r][c] == target_val:
224
+ cover_mask |= 1 << idx
225
+ # include bits for simplification (include don't-cares as they are part of the group)
226
+ bits_list.append(self._cell_bits[r][c])
227
+ if cover_mask == 0:
228
+ continue
229
+ term_str = simplify_method(bits_list)
230
+ prime_terms.append(term_str)
231
+ prime_covers.append(cover_mask)
232
+
233
+ # Step 4: build prime implicant chart using masks
234
+ minterm_to_primes = defaultdict(list)
235
+ all_minterms_mask = 0
236
+ for p_idx, cover in enumerate(prime_covers):
237
+ all_minterms_mask |= cover
238
+ temp = cover
239
+ while temp:
240
+ low = temp & -temp
241
+ idx = low.bit_length() - 1
242
+ temp -= low
243
+ minterm_to_primes[idx].append(p_idx)
244
+
245
+ # Step 5: essential primes
246
+ essential_indices = set()
247
+ for m, primes in minterm_to_primes.items():
248
+ if len(primes) == 1:
249
+ essential_indices.add(primes[0])
250
+
251
+ essential_terms = [prime_terms[i] for i in sorted(essential_indices)]
252
+
253
+ # Step 6: mark covered by essentials
254
+ covered_mask = 0
255
+ for i in essential_indices:
256
+ covered_mask |= prime_covers[i]
257
+
258
+ # Step 7: greedy covering (bitmask-based)
259
+ remaining_mask = all_minterms_mask & ~covered_mask
260
+ selected = set(essential_indices)
261
+ while remaining_mask:
262
+ best_idx, best_cover_count = None, -1
263
+ for idx in range(len(prime_covers)):
264
+ if idx in selected:
265
+ continue
266
+ cover = prime_covers[idx] & remaining_mask
267
+ count = cover.bit_count()
268
+ if count > best_cover_count:
269
+ best_cover_count = count
270
+ best_idx = idx
271
+ if best_idx is None or best_cover_count == 0:
272
+ break
273
+ selected.add(best_idx)
274
+ covered_mask |= prime_covers[best_idx]
275
+ remaining_mask = all_minterms_mask & ~covered_mask
276
+
277
+ # Step 8: remove redundant terms using masks
278
+ def covers_with_indices(indices):
279
+ mask = 0
280
+ for i in indices:
281
+ mask |= prime_covers[i]
282
+ return mask
283
+
284
+ chosen = set(selected)
285
+ for idx in list(sorted(chosen)):
286
+ trial = chosen - {idx}
287
+ if covers_with_indices(trial) == covers_with_indices(chosen):
288
+ chosen = trial
289
+
290
+ final_terms = [prime_terms[i] for i in sorted(chosen)]
291
+ return final_terms, join_operator.join(final_terms)
292
+
293
+ # ---------- Display ---------- #
294
+ def print_kmap(self):
295
+ """Pretty print the K-map with headers."""
296
+ print(" " + " ".join(self.col_labels))
297
+ for i, row in enumerate(self.kmap):
298
+ print(f"{self.row_labels[i]} " + " ".join(str(val) for val in row))