addernet 0.1.0__py3-none-any.whl

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.
addernet/__init__.py ADDED
@@ -0,0 +1,29 @@
1
+ import os
2
+ import sys
3
+ from pathlib import Path
4
+
5
+ _HERE = Path(__file__).parent
6
+
7
+ if sys.platform == "darwin":
8
+ _lib_hdc_name = "libaddernet_hdc.dylib"
9
+ _lib_addernet_name = "libaddernet.dylib"
10
+ elif sys.platform == "win32":
11
+ _lib_hdc_name = "addernet_hdc.dll"
12
+ _lib_addernet_name = "addernet.dll"
13
+ else:
14
+ _lib_hdc_name = "libaddernet_hdc.so"
15
+ _lib_addernet_name = "libaddernet.so"
16
+
17
+ _need_build = False
18
+ if not (_HERE / _lib_hdc_name).exists() or not (_HERE / _lib_addernet_name).exists():
19
+ _need_build = True
20
+
21
+ if _need_build:
22
+ from . import build_ext
23
+ build_ext.build()
24
+
25
+ from .addernet import AdderNetLayer
26
+ from .addernet_hdc import AdderNetHDC
27
+
28
+ __version__ = "0.1.0"
29
+ __all__ = ["AdderNetLayer", "AdderNetHDC"]
addernet/addernet.py ADDED
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ AdderNet Python Bindings — ctypes interface to libaddernet.so
4
+ ==============================================================
5
+
6
+ Usage:
7
+ from addernet import AdderNetLayer
8
+
9
+ layer = AdderNetLayer(size=256, bias=50, input_min=-50, input_max=200, lr=0.1)
10
+ layer.train(inputs, targets, epochs_raw=1000, epochs_expanded=4000)
11
+ result = layer.predict(37.0)
12
+ layer.save("model.bin")
13
+ """
14
+
15
+ import os
16
+ import ctypes
17
+ import numpy as np
18
+
19
+ # ---- Locate shared library ----
20
+
21
+ _HERE = os.path.dirname(os.path.abspath(__file__))
22
+ _LIB_NAMES = [
23
+ os.path.join(_HERE, "libaddernet.so"),
24
+ os.path.join(_HERE, "libaddernet.dylib"),
25
+ "libaddernet.so",
26
+ ]
27
+
28
+ _lib = None
29
+ for _name in _LIB_NAMES:
30
+ try:
31
+ _lib = ctypes.CDLL(_name)
32
+ break
33
+ except OSError:
34
+ continue
35
+
36
+ if _lib is None:
37
+ raise OSError(
38
+ "Cannot find libaddernet.so. "
39
+ "Build it first: cd addernet_lib && make"
40
+ )
41
+
42
+ # ---- Opaque pointer type ----
43
+
44
+ _AnLayerPtr = ctypes.c_void_p
45
+
46
+ # ---- Function signatures ----
47
+
48
+ _lib.an_layer_create.restype = _AnLayerPtr
49
+ _lib.an_layer_create.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_double]
50
+
51
+ _lib.an_layer_free.restype = None
52
+ _lib.an_layer_free.argtypes = [_AnLayerPtr]
53
+
54
+ _lib.an_train.restype = ctypes.c_int
55
+ _lib.an_train.argtypes = [
56
+ _AnLayerPtr,
57
+ ctypes.POINTER(ctypes.c_double),
58
+ ctypes.POINTER(ctypes.c_double),
59
+ ctypes.c_int,
60
+ ctypes.c_int,
61
+ ctypes.c_int,
62
+ ]
63
+
64
+ _lib.an_predict.restype = ctypes.c_double
65
+ _lib.an_predict.argtypes = [_AnLayerPtr, ctypes.c_double]
66
+
67
+ _lib.an_predict_batch.restype = ctypes.c_int
68
+ _lib.an_predict_batch.argtypes = [
69
+ _AnLayerPtr,
70
+ ctypes.POINTER(ctypes.c_double),
71
+ ctypes.POINTER(ctypes.c_double),
72
+ ctypes.c_int,
73
+ ]
74
+
75
+ _lib.an_save.restype = ctypes.c_int
76
+ _lib.an_save.argtypes = [_AnLayerPtr, ctypes.c_char_p]
77
+
78
+ _lib.an_load.restype = _AnLayerPtr
79
+ _lib.an_load.argtypes = [ctypes.c_char_p]
80
+
81
+ _lib.an_get_offset.restype = ctypes.c_int
82
+ _lib.an_get_offset.argtypes = [_AnLayerPtr, ctypes.POINTER(ctypes.c_double), ctypes.c_int]
83
+
84
+ _lib.an_get_size.restype = ctypes.c_int
85
+ _lib.an_get_size.argtypes = [_AnLayerPtr]
86
+
87
+ _lib.an_get_bias.restype = ctypes.c_int
88
+ _lib.an_get_bias.argtypes = [_AnLayerPtr]
89
+
90
+ _lib.an_get_input_min.restype = ctypes.c_int
91
+ _lib.an_get_input_min.argtypes = [_AnLayerPtr]
92
+
93
+ _lib.an_get_input_max.restype = ctypes.c_int
94
+ _lib.an_get_input_max.argtypes = [_AnLayerPtr]
95
+
96
+ _lib.an_get_lr.restype = ctypes.c_double
97
+ _lib.an_get_lr.argtypes = [_AnLayerPtr]
98
+
99
+
100
+ # ---- Python wrapper ----
101
+
102
+ class AdderNetLayer:
103
+ """
104
+ Python wrapper around the C an_layer struct.
105
+ Provides train / predict / predict_batch / save / load using numpy arrays.
106
+ """
107
+
108
+ def __init__(self, size=256, bias=50, input_min=-50, input_max=200, lr=0.1,
109
+ _ptr=None):
110
+ if _ptr is not None:
111
+ self._ptr = _ptr
112
+ else:
113
+ self._ptr = _lib.an_layer_create(size, bias, input_min, input_max, lr)
114
+ if not self._ptr:
115
+ raise MemoryError("an_layer_create failed")
116
+
117
+ def __del__(self):
118
+ if hasattr(self, "_ptr") and self._ptr:
119
+ _lib.an_layer_free(self._ptr)
120
+ self._ptr = None
121
+
122
+ def train(self, inputs, targets, epochs_raw=1000, epochs_expanded=4000):
123
+ """Train on input→target pairs. Accepts lists or numpy arrays."""
124
+ inputs = np.ascontiguousarray(inputs, dtype=np.float64)
125
+ targets = np.ascontiguousarray(targets, dtype=np.float64)
126
+ n = len(inputs)
127
+ if len(targets) != n:
128
+ raise ValueError("inputs and targets must have same length")
129
+ ret = _lib.an_train(
130
+ self._ptr,
131
+ inputs.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
132
+ targets.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
133
+ n, epochs_raw, epochs_expanded,
134
+ )
135
+ if ret != 0:
136
+ raise RuntimeError("an_train failed")
137
+
138
+ def predict(self, x):
139
+ """Single prediction. Returns float."""
140
+ return _lib.an_predict(self._ptr, float(x))
141
+
142
+ def predict_batch(self, inputs):
143
+ """Batch prediction. Accepts numpy array, returns numpy array."""
144
+ inputs = np.ascontiguousarray(inputs, dtype=np.float64)
145
+ n = len(inputs)
146
+ outputs = np.empty(n, dtype=np.float64)
147
+ _lib.an_predict_batch(
148
+ self._ptr,
149
+ inputs.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
150
+ outputs.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
151
+ n,
152
+ )
153
+ return outputs
154
+
155
+ def save(self, path):
156
+ """Save layer to binary file."""
157
+ ret = _lib.an_save(self._ptr, path.encode("utf-8"))
158
+ if ret != 0:
159
+ raise IOError(f"an_save failed: {path}")
160
+
161
+ @classmethod
162
+ def load(cls, path):
163
+ """Load layer from binary file. Returns new AdderNetLayer."""
164
+ ptr = _lib.an_load(path.encode("utf-8"))
165
+ if not ptr:
166
+ raise IOError(f"an_load failed: {path}")
167
+ return cls(_ptr=ptr)
168
+
169
+ @property
170
+ def offset_table(self):
171
+ """Return the offset table as a numpy array."""
172
+ n = _lib.an_get_size(self._ptr)
173
+ buf = np.empty(n, dtype=np.float64)
174
+ _lib.an_get_offset(
175
+ self._ptr,
176
+ buf.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
177
+ n,
178
+ )
179
+ return buf
180
+
181
+ @property
182
+ def size(self):
183
+ return _lib.an_get_size(self._ptr)
184
+
185
+ @property
186
+ def bias(self):
187
+ return _lib.an_get_bias(self._ptr)
188
+
189
+ @property
190
+ def input_min(self):
191
+ return _lib.an_get_input_min(self._ptr)
192
+
193
+ @property
194
+ def input_max(self):
195
+ return _lib.an_get_input_max(self._ptr)
196
+
197
+ @property
198
+ def lr(self):
199
+ return _lib.an_get_lr(self._ptr)
200
+
201
+ def __repr__(self):
202
+ return (f"AdderNetLayer(size={self.size}, bias={self.bias}, "
203
+ f"range=[{self.input_min},{self.input_max}], lr={self.lr})")
@@ -0,0 +1,280 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ AdderNet-HDC Python Bindings — ctypes interface to libaddernet_hdc.so
4
+ ======================================================================
5
+
6
+ Usage:
7
+ from addernet_hdc import AdderNetHDC
8
+
9
+ model = AdderNetHDC(n_vars=4, n_classes=3, table_size=256)
10
+ model.train(X, y)
11
+ pred = model.predict(x)
12
+ """
13
+
14
+ import os
15
+ import ctypes
16
+ import numpy as np
17
+
18
+ # ---- Locate shared library ----
19
+
20
+ _HERE = os.path.dirname(os.path.abspath(__file__))
21
+ _LIB_NAMES = [
22
+ os.path.join(_HERE, "libaddernet_hdc.so"),
23
+ os.path.join(_HERE, "libaddernet_hdc.dylib"),
24
+ "libaddernet_hdc.so",
25
+ ]
26
+
27
+ _lib = None
28
+ for _name in _LIB_NAMES:
29
+ try:
30
+ _lib = ctypes.CDLL(_name)
31
+ break
32
+ except OSError:
33
+ continue
34
+
35
+ if _lib is None:
36
+ raise OSError(
37
+ "Cannot find libaddernet_hdc.so. "
38
+ "Build it first: cd addernet_lib && make hdc"
39
+ )
40
+
41
+ # ---- Opaque pointer type ----
42
+
43
+ _AnHdcPtr = ctypes.c_void_p
44
+
45
+ # ---- Function signatures ----
46
+
47
+ _lib.an_hdc_create.restype = _AnHdcPtr
48
+ _lib.an_hdc_create.argtypes = [
49
+ ctypes.c_int, ctypes.c_int, ctypes.c_int,
50
+ ctypes.POINTER(ctypes.c_int),
51
+ ]
52
+
53
+ _lib.an_hdc_free.restype = None
54
+ _lib.an_hdc_free.argtypes = [_AnHdcPtr]
55
+
56
+ _lib.an_hdc_train.restype = None
57
+ _lib.an_hdc_train.argtypes = [
58
+ _AnHdcPtr,
59
+ ctypes.POINTER(ctypes.c_double),
60
+ ctypes.POINTER(ctypes.c_int),
61
+ ctypes.c_int,
62
+ ]
63
+
64
+ _lib.an_hdc_predict.restype = ctypes.c_int
65
+ _lib.an_hdc_predict.argtypes = [_AnHdcPtr, ctypes.POINTER(ctypes.c_double)]
66
+
67
+ _lib.an_hdc_predict_batch.restype = ctypes.c_int
68
+ _lib.an_hdc_predict_batch.argtypes = [
69
+ _AnHdcPtr,
70
+ ctypes.POINTER(ctypes.c_double),
71
+ ctypes.POINTER(ctypes.c_int),
72
+ ctypes.c_int,
73
+ ]
74
+
75
+ _lib.an_hdc_save.restype = ctypes.c_int
76
+ _lib.an_hdc_save.argtypes = [_AnHdcPtr, ctypes.c_char_p]
77
+
78
+ _lib.an_hdc_load.restype = _AnHdcPtr
79
+ _lib.an_hdc_load.argtypes = [ctypes.c_char_p]
80
+
81
+ _lib.hv_seed.restype = None
82
+ _lib.hv_seed.argtypes = [ctypes.c_uint]
83
+
84
+ # HDC primitive bindings (for direct hypervector manipulation)
85
+
86
+ _HV_WORDS = 157 # HDC_WORDS = ceil(10000/64)
87
+ _HV_t = ctypes.c_uint64 * _HV_WORDS
88
+
89
+ _lib.hv_bind.restype = None
90
+ _lib.hv_bind.argtypes = [_HV_t, _HV_t, _HV_t]
91
+
92
+ _lib.hv_bundle.restype = None
93
+ _lib.hv_bundle.argtypes = [_HV_t, ctypes.POINTER(_HV_t), ctypes.c_int]
94
+
95
+ _lib.hv_hamming.restype = ctypes.c_int
96
+ _lib.hv_hamming.argtypes = [_HV_t, _HV_t]
97
+
98
+ _lib.hv_similarity.restype = ctypes.c_float
99
+ _lib.hv_similarity.argtypes = [_HV_t, _HV_t]
100
+
101
+ _lib.hv_random.restype = None
102
+ _lib.hv_random.argtypes = [_HV_t]
103
+
104
+ _lib.hv_copy.restype = None
105
+ _lib.hv_copy.argtypes = [_HV_t, _HV_t]
106
+
107
+ _lib.hv_add_noise.restype = None
108
+ _lib.hv_add_noise.argtypes = [_HV_t, _HV_t, ctypes.c_float]
109
+
110
+
111
+ # ---- Python wrapper ----
112
+
113
+ class AdderNetHDC:
114
+ """
115
+ Multivariate classifier using AdderNet encoding + Hyperdimensional Computing.
116
+ Zero floating-point multiplication at inference.
117
+ """
118
+
119
+ def __init__(self, n_vars=1, n_classes=2, table_size=256, bias=None,
120
+ seed=42, _ptr=None):
121
+ """
122
+ Create a new model.
123
+
124
+ Args:
125
+ n_vars: number of input variables
126
+ n_classes: number of output classes
127
+ table_size: encoding table size per variable (power of 2)
128
+ bias: list of bias values per variable (default: table_size//2)
129
+ seed: random seed for reproducibility
130
+ """
131
+ if _ptr is not None:
132
+ self._ptr = _ptr
133
+ self._n_vars = n_vars
134
+ self._n_classes = n_classes
135
+ self._table_size = table_size
136
+ return
137
+
138
+ _lib.hv_seed(seed)
139
+
140
+ bias_arr = None
141
+ if bias is not None:
142
+ bias_arr = (ctypes.c_int * n_vars)(*bias)
143
+
144
+ self._ptr = _lib.an_hdc_create(n_vars, n_classes, table_size, bias_arr)
145
+ if not self._ptr:
146
+ raise MemoryError("an_hdc_create failed")
147
+ self._n_vars = n_vars
148
+ self._n_classes = n_classes
149
+ self._table_size = table_size
150
+
151
+ def __del__(self):
152
+ if hasattr(self, "_ptr") and self._ptr:
153
+ _lib.an_hdc_free(self._ptr)
154
+ self._ptr = None
155
+
156
+ def train(self, X, y):
157
+ """
158
+ Train the codebook from labeled data.
159
+
160
+ Args:
161
+ X: n_samples × n_vars array (list of lists or numpy array)
162
+ y: n_samples class labels (int, 0-indexed)
163
+ """
164
+ X = np.ascontiguousarray(X, dtype=np.float64)
165
+ y = np.ascontiguousarray(y, dtype=np.int32)
166
+
167
+ if X.ndim == 1:
168
+ X = X.reshape(-1, self._n_vars)
169
+
170
+ n = X.shape[0]
171
+ if len(y) != n:
172
+ raise ValueError(f"X has {n} samples but y has {len(y)}")
173
+
174
+ _lib.an_hdc_train(
175
+ self._ptr,
176
+ X.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
177
+ y.ctypes.data_as(ctypes.POINTER(ctypes.c_int)),
178
+ n,
179
+ )
180
+
181
+ def predict(self, x):
182
+ """
183
+ Classify one sample.
184
+
185
+ Args:
186
+ x: list or array of n_vars input values
187
+ Returns:
188
+ predicted class label (int)
189
+ """
190
+ x = np.ascontiguousarray(x, dtype=np.float64)
191
+ return _lib.an_hdc_predict(
192
+ self._ptr,
193
+ x.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
194
+ )
195
+
196
+ def predict_batch(self, X):
197
+ """
198
+ Classify multiple samples.
199
+
200
+ Args:
201
+ X: n_samples × n_vars array
202
+ Returns:
203
+ numpy array of predicted class labels
204
+ """
205
+ X = np.ascontiguousarray(X, dtype=np.float64)
206
+ if X.ndim == 1:
207
+ X = X.reshape(-1, self._n_vars)
208
+ n = X.shape[0]
209
+ outputs = np.empty(n, dtype=np.int32)
210
+ _lib.an_hdc_predict_batch(
211
+ self._ptr,
212
+ X.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
213
+ outputs.ctypes.data_as(ctypes.POINTER(ctypes.c_int)),
214
+ n,
215
+ )
216
+ return outputs
217
+
218
+ def save(self, path):
219
+ """Save model to binary file."""
220
+ ret = _lib.an_hdc_save(self._ptr, path.encode("utf-8"))
221
+ if ret != 0:
222
+ raise IOError(f"an_hdc_save failed: {path}")
223
+
224
+ @classmethod
225
+ def load(cls, path):
226
+ """Load model from binary file."""
227
+ ptr = _lib.an_hdc_load(path.encode("utf-8"))
228
+ if not ptr:
229
+ raise IOError(f"an_hdc_load failed: {path}")
230
+ return cls(_ptr=ptr)
231
+
232
+ @property
233
+ def n_vars(self):
234
+ return self._n_vars
235
+
236
+ @property
237
+ def n_classes(self):
238
+ return self._n_classes
239
+
240
+ @property
241
+ def table_size(self):
242
+ return self._table_size
243
+
244
+ @property
245
+ def codebook(self):
246
+ """
247
+ Return the trained codebook as a list of numpy uint64 arrays.
248
+ Each array has 157 words (10000 bits).
249
+
250
+ Requires the model to be trained first.
251
+ """
252
+ _HDC_WORDS = 157
253
+
254
+ # Read the codebook pointer from the an_hdc_model struct.
255
+ # On x86_64, the codebook hv_t* field is at byte offset 32
256
+ # (after n_vars, n_classes, table_size, table_mask, bias*, enc_table*).
257
+ buf = ctypes.string_at(self._ptr, 48) # read first 48 bytes of struct
258
+ cb_addr = int.from_bytes(buf[32:40], byteorder='little')
259
+ if cb_addr == 0:
260
+ raise RuntimeError("Model not trained yet (codebook is NULL)")
261
+
262
+ # Cast the address to a flat uint64 array
263
+ cb = ctypes.cast(
264
+ ctypes.c_void_p(cb_addr),
265
+ ctypes.POINTER(ctypes.c_uint64 * (_HDC_WORDS * self._n_classes))
266
+ )
267
+
268
+ result = []
269
+ flat = cb.contents # the full (HDC_WORDS * n_classes) uint64 array
270
+ for c in range(self._n_classes):
271
+ offset = c * _HDC_WORDS
272
+ hv = np.array([flat[offset + i] for i in range(_HDC_WORDS)],
273
+ dtype=np.uint64)
274
+ result.append(hv)
275
+ return result
276
+
277
+ def __repr__(self):
278
+ return (f"AdderNetHDC(n_vars={self._n_vars}, "
279
+ f"n_classes={self._n_classes}, "
280
+ f"table_size={self._table_size})")
addernet/build_ext.py ADDED
@@ -0,0 +1,42 @@
1
+ import subprocess
2
+ import os
3
+ import sys
4
+ from pathlib import Path
5
+
6
+ def build():
7
+ pkg_dir = Path(__file__).parent
8
+ src_dir = pkg_dir / "src"
9
+
10
+ if sys.platform == "darwin":
11
+ lib_name = "libaddernet_hdc.dylib"
12
+ lib_name_addernet = "libaddernet.dylib"
13
+ elif sys.platform == "win32":
14
+ lib_name = "addernet_hdc.dll"
15
+ lib_name_addernet = "addernet.dll"
16
+ else:
17
+ lib_name = "libaddernet_hdc.so"
18
+ lib_name_addernet = "libaddernet.so"
19
+
20
+ out_hdc = pkg_dir / lib_name
21
+ out_addernet = pkg_dir / lib_name_addernet
22
+
23
+ cmd_hdc = [
24
+ "gcc", "-O3", "-march=native", "-fPIC", "-shared",
25
+ str(src_dir / "addernet.c"),
26
+ str(src_dir / "hdc_core.c"),
27
+ str(src_dir / "addernet_hdc.c"),
28
+ "-o", str(out_hdc), "-lm"
29
+ ]
30
+ subprocess.run(cmd_hdc, check=True)
31
+
32
+ cmd_addernet = [
33
+ "gcc", "-O3", "-march=native", "-fPIC", "-shared",
34
+ str(src_dir / "addernet.c"),
35
+ "-o", str(out_addernet), "-lm"
36
+ ]
37
+ subprocess.run(cmd_addernet, check=True)
38
+
39
+ return str(out_hdc), str(out_addernet)
40
+
41
+ if __name__ == "__main__":
42
+ build()
Binary file
Binary file