raksa 0.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.
raksa-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Noc Lab & Muhammad Ikhwan Fathulloh
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
raksa-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.4
2
+ Name: raksa
3
+ Version: 0.1.0
4
+ Summary: MicroPython client library for In-situ Analytics and WebSocket communication with FastAPI
5
+ Author: Noc Lab
6
+ License: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: Implementation :: MicroPython
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Requires-Python: >=3.7
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Dynamic: license-file
14
+
15
+ # Raksa ๐Ÿ›ก๏ธ
16
+
17
+ **Raksa** (derived from Sanskrit/Indonesian word for *Protector* or *Guardian*) is a high-persistence, self-contained MicroPython library built for the **Noc Lab** TinyML ecosystem as part of the **'Tiny Chip, Big Brain'** workshop. It serves as an end-to-end *In-situ Analytics* gateway, securing stable asynchronous telemetry data stream from edge devices (ESP32/RP2040) to a FastAPI cloud backend.
18
+
19
+ Leveraging native compiler execution (`@micropython.native`) for fast on-device inference and memory consolidations (`gc.collect()`), Raksa protects MicroPython devices from heap fragmentation during high-velocity TinyML operations.
20
+
21
+ ---
22
+
23
+ ## Key Features
24
+ - ๐Ÿ”„ **Persistent WebSockets**: Pure python RFC 6455 client implementation utilizing raw asynchronous `uasyncio` streams without external footprint overhead.
25
+ - โšก **High-Speed In-situ Analytics**: Edge inference calculations optimized directly via `@micropython.native` decorators to ensure minimal loop latency.
26
+ - ๐Ÿงน **Heap Consolidation**: Garbage collection executed dynamically inside data sync cycles to prevent RAM fragmentation on constrained hardware.
27
+ - ๐Ÿ‡ฎ๐Ÿ‡ฉ **Nusantara Resiliency**: Promotes self-reliance, stability, and secure data guardianship for resource-constrained edge-to-cloud architectures.
28
+
29
+ ---
30
+
31
+ ## Installation via `mip`
32
+
33
+ ### 1. Using `mpremote` (Recommended)
34
+ Connect your microcontroller board to your pc via USB/Serial and run:
35
+ ```bash
36
+ mpremote mip install github:Muhammad-Ikhwan-Fathulloh/Raksa
37
+ ```
38
+
39
+ ### 2. Directly in MicroPython REPL
40
+ Ensure your microcontroller board has an active Wi-Fi connection, then run:
41
+ ```python
42
+ import mip
43
+ mip.install("github:Muhammad-Ikhwan-Fathulloh/Raksa")
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Minimalist Code Example (ESP32 / RP2040)
49
+
50
+ Below is an end-to-end usage code highlighting edge inference and data synchronization under 10 lines of functional code:
51
+
52
+ ```python
53
+ import uasyncio as asyncio
54
+ from raksa import RaksaClient
55
+
56
+ async def main():
57
+ client = RaksaClient("ws://192.168.1.100:8000/ws/telemetry")
58
+ model = ([[0.5, -0.2], [0.1, 0.9]], [0.1, 0.0]) # TinyML weights & biases
59
+ inputs = [0.8, 1.5] # Raw sensor variables
60
+
61
+ pred = client.infer(model, inputs)
62
+ await client.sync({"model": "NocML_V1", "features": inputs, "prediction": pred})
63
+
64
+ asyncio.run(main())
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Complete Examples & Machine Learning Features
70
+
71
+ Full production-ready examples are available under the [`examples/`](examples/) folder, covering both hardware-specific scenarios and internal, Scikit-Learn-compatible Machine Learning algorithms:
72
+
73
+ ### Hardware-Connected Scenarios
74
+ | File | Scenario | Sensors |
75
+ | ------------------------------------------------------- | ------------------------------------------ | -------------------------- |
76
+ | [`main_edge.py`](examples/main_edge.py) | Quick-start minimalist (< 10 lines) | โ€” |
77
+ | [`esp32_wifi_sensor.py`](examples/esp32_wifi_sensor.py) | ADC analog sensor + 3-class classification | LDR / MQ-x / Potentiometer |
78
+ | [`esp32_dht_monitor.py`](examples/esp32_dht_monitor.py) | Temperature & humidity anomaly detection | DHT11 / DHT22 |
79
+
80
+ ### On-Device Machine Learning (NocML Port)
81
+ | File | Algorithms Covered | API Classes |
82
+ | ------------------------------------------------------- | -------------------------------- | ------------------------------------------------------------------- |
83
+ | [`ml_preprocessing.py`](examples/ml_preprocessing.py) | Custom Data Scaling & Extensions | `MinMaxScaler`, `StandardScaler`, `PolynomialFeatures` |
84
+ | [`ml_classification.py`](examples/ml_classification.py) | Optimized Classifiers | `KNN`, `NaiveBayes`, `LogisticRegression`, `DecisionTreeClassifier` |
85
+ | [`ml_clustering.py`](examples/ml_clustering.py) | Unsupervised Grouping / Fit | `KMeans` |
86
+ | [`ml_forecasting.py`](examples/ml_forecasting.py) | Simple Time Series forecasting | `LinearForecaster` |
87
+
88
+ ---
89
+
90
+ ## Machine Learning Reference (NocML Equivalent)
91
+
92
+ Raksa bundles a highly optimized port of the **NocML** C++ library, utilizing `@micropython.native` compiled mathematical loops to secure lightning-fast predictions directly on MicroPython edge boards without external dependencies or heavy resource bloat.
93
+
94
+ ### Preprocessing
95
+ - **`MinMaxScaler(dims, min_vals, max_vals)`**: Rescales variables to a range `[0.0 - 1.0]`.
96
+ - **`StandardScaler(dims, means, stddevs)`**: Standardizes features using population mean and variance.
97
+ - **`PolynomialFeatures(degree)`**: Expands input dimension using combinations with replacement.
98
+
99
+ ### Classification & Clustering
100
+ - **`KNN(training_data, labels, num_samples, dims, k=3)`**: Traditional classification using Euclidean distances.
101
+ - **`NaiveBayes(num_classes, dims, means, vars, priors)`**: Gaussian probabilistic classification.
102
+ - **`LogisticRegression(dims, weights, bias)`**: Fast binary classification with `predict()` and `predict_proba()`.
103
+ - **`DecisionTreeClassifier(nodes, num_nodes)`**: Node list traverse trees supporting dictionary, tuple, or custom node structures.
104
+ - **`KMeans(k, dims, centroids)`**: Clusters features into centroids. Supports `run(data, num_samples)` for on-device fitting.
105
+
106
+ ### Forecasting
107
+ - **`LinearForecaster()`**: Computes simple linear regressive trends ($y=mx+c$) natively via `fit(x, y)` and `forecastNext()`.
108
+
109
+ ---
110
+
111
+ ## Acknowledgments & Credits
112
+
113
+ The machine learning capabilities contained in Raksa are ported from the [NocML C++ Library for Arduino](https://github.com/Nocturnailed-Community/NocML), developed by Muhammad Ikhwan Fathulloh. Special credits go to the original creators for providing the foundation of these resource-constrained edge execution algorithms.
114
+
115
+ ---
116
+
117
+ ## Licenses & Copyrights
118
+ Developed by **Noc Lab** for nurturing TinyML literacy on hardware developers. Licensed under the MIT License.
raksa-0.1.0/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # Raksa ๐Ÿ›ก๏ธ
2
+
3
+ **Raksa** (derived from Sanskrit/Indonesian word for *Protector* or *Guardian*) is a high-persistence, self-contained MicroPython library built for the **Noc Lab** TinyML ecosystem as part of the **'Tiny Chip, Big Brain'** workshop. It serves as an end-to-end *In-situ Analytics* gateway, securing stable asynchronous telemetry data stream from edge devices (ESP32/RP2040) to a FastAPI cloud backend.
4
+
5
+ Leveraging native compiler execution (`@micropython.native`) for fast on-device inference and memory consolidations (`gc.collect()`), Raksa protects MicroPython devices from heap fragmentation during high-velocity TinyML operations.
6
+
7
+ ---
8
+
9
+ ## Key Features
10
+ - ๐Ÿ”„ **Persistent WebSockets**: Pure python RFC 6455 client implementation utilizing raw asynchronous `uasyncio` streams without external footprint overhead.
11
+ - โšก **High-Speed In-situ Analytics**: Edge inference calculations optimized directly via `@micropython.native` decorators to ensure minimal loop latency.
12
+ - ๐Ÿงน **Heap Consolidation**: Garbage collection executed dynamically inside data sync cycles to prevent RAM fragmentation on constrained hardware.
13
+ - ๐Ÿ‡ฎ๐Ÿ‡ฉ **Nusantara Resiliency**: Promotes self-reliance, stability, and secure data guardianship for resource-constrained edge-to-cloud architectures.
14
+
15
+ ---
16
+
17
+ ## Installation via `mip`
18
+
19
+ ### 1. Using `mpremote` (Recommended)
20
+ Connect your microcontroller board to your pc via USB/Serial and run:
21
+ ```bash
22
+ mpremote mip install github:Muhammad-Ikhwan-Fathulloh/Raksa
23
+ ```
24
+
25
+ ### 2. Directly in MicroPython REPL
26
+ Ensure your microcontroller board has an active Wi-Fi connection, then run:
27
+ ```python
28
+ import mip
29
+ mip.install("github:Muhammad-Ikhwan-Fathulloh/Raksa")
30
+ ```
31
+
32
+ ---
33
+
34
+ ## Minimalist Code Example (ESP32 / RP2040)
35
+
36
+ Below is an end-to-end usage code highlighting edge inference and data synchronization under 10 lines of functional code:
37
+
38
+ ```python
39
+ import uasyncio as asyncio
40
+ from raksa import RaksaClient
41
+
42
+ async def main():
43
+ client = RaksaClient("ws://192.168.1.100:8000/ws/telemetry")
44
+ model = ([[0.5, -0.2], [0.1, 0.9]], [0.1, 0.0]) # TinyML weights & biases
45
+ inputs = [0.8, 1.5] # Raw sensor variables
46
+
47
+ pred = client.infer(model, inputs)
48
+ await client.sync({"model": "NocML_V1", "features": inputs, "prediction": pred})
49
+
50
+ asyncio.run(main())
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Complete Examples & Machine Learning Features
56
+
57
+ Full production-ready examples are available under the [`examples/`](examples/) folder, covering both hardware-specific scenarios and internal, Scikit-Learn-compatible Machine Learning algorithms:
58
+
59
+ ### Hardware-Connected Scenarios
60
+ | File | Scenario | Sensors |
61
+ | ------------------------------------------------------- | ------------------------------------------ | -------------------------- |
62
+ | [`main_edge.py`](examples/main_edge.py) | Quick-start minimalist (< 10 lines) | โ€” |
63
+ | [`esp32_wifi_sensor.py`](examples/esp32_wifi_sensor.py) | ADC analog sensor + 3-class classification | LDR / MQ-x / Potentiometer |
64
+ | [`esp32_dht_monitor.py`](examples/esp32_dht_monitor.py) | Temperature & humidity anomaly detection | DHT11 / DHT22 |
65
+
66
+ ### On-Device Machine Learning (NocML Port)
67
+ | File | Algorithms Covered | API Classes |
68
+ | ------------------------------------------------------- | -------------------------------- | ------------------------------------------------------------------- |
69
+ | [`ml_preprocessing.py`](examples/ml_preprocessing.py) | Custom Data Scaling & Extensions | `MinMaxScaler`, `StandardScaler`, `PolynomialFeatures` |
70
+ | [`ml_classification.py`](examples/ml_classification.py) | Optimized Classifiers | `KNN`, `NaiveBayes`, `LogisticRegression`, `DecisionTreeClassifier` |
71
+ | [`ml_clustering.py`](examples/ml_clustering.py) | Unsupervised Grouping / Fit | `KMeans` |
72
+ | [`ml_forecasting.py`](examples/ml_forecasting.py) | Simple Time Series forecasting | `LinearForecaster` |
73
+
74
+ ---
75
+
76
+ ## Machine Learning Reference (NocML Equivalent)
77
+
78
+ Raksa bundles a highly optimized port of the **NocML** C++ library, utilizing `@micropython.native` compiled mathematical loops to secure lightning-fast predictions directly on MicroPython edge boards without external dependencies or heavy resource bloat.
79
+
80
+ ### Preprocessing
81
+ - **`MinMaxScaler(dims, min_vals, max_vals)`**: Rescales variables to a range `[0.0 - 1.0]`.
82
+ - **`StandardScaler(dims, means, stddevs)`**: Standardizes features using population mean and variance.
83
+ - **`PolynomialFeatures(degree)`**: Expands input dimension using combinations with replacement.
84
+
85
+ ### Classification & Clustering
86
+ - **`KNN(training_data, labels, num_samples, dims, k=3)`**: Traditional classification using Euclidean distances.
87
+ - **`NaiveBayes(num_classes, dims, means, vars, priors)`**: Gaussian probabilistic classification.
88
+ - **`LogisticRegression(dims, weights, bias)`**: Fast binary classification with `predict()` and `predict_proba()`.
89
+ - **`DecisionTreeClassifier(nodes, num_nodes)`**: Node list traverse trees supporting dictionary, tuple, or custom node structures.
90
+ - **`KMeans(k, dims, centroids)`**: Clusters features into centroids. Supports `run(data, num_samples)` for on-device fitting.
91
+
92
+ ### Forecasting
93
+ - **`LinearForecaster()`**: Computes simple linear regressive trends ($y=mx+c$) natively via `fit(x, y)` and `forecastNext()`.
94
+
95
+ ---
96
+
97
+ ## Acknowledgments & Credits
98
+
99
+ The machine learning capabilities contained in Raksa are ported from the [NocML C++ Library for Arduino](https://github.com/Nocturnailed-Community/NocML), developed by Muhammad Ikhwan Fathulloh. Special credits go to the original creators for providing the foundation of these resource-constrained edge execution algorithms.
100
+
101
+ ---
102
+
103
+ ## Licenses & Copyrights
104
+ Developed by **Noc Lab** for nurturing TinyML literacy on hardware developers. Licensed under the MIT License.
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "raksa"
7
+ version = "0.1.0"
8
+ description = "MicroPython client library for In-situ Analytics and WebSocket communication with FastAPI"
9
+ readme = "README.md"
10
+ requires-python = ">=3.7"
11
+ license = {text = "MIT"}
12
+ authors = [
13
+ {name = "Noc Lab"}
14
+ ]
15
+ classifiers = [
16
+ "Programming Language :: Python :: 3",
17
+ "Programming Language :: Python :: Implementation :: MicroPython",
18
+ "License :: OSI Approved :: MIT License",
19
+ ]
20
+ dependencies = []
21
+
22
+ [tool.setuptools]
23
+ py-modules = ["raksa"]
@@ -0,0 +1,118 @@
1
+ Metadata-Version: 2.4
2
+ Name: raksa
3
+ Version: 0.1.0
4
+ Summary: MicroPython client library for In-situ Analytics and WebSocket communication with FastAPI
5
+ Author: Noc Lab
6
+ License: MIT
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: Implementation :: MicroPython
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Requires-Python: >=3.7
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Dynamic: license-file
14
+
15
+ # Raksa ๐Ÿ›ก๏ธ
16
+
17
+ **Raksa** (derived from Sanskrit/Indonesian word for *Protector* or *Guardian*) is a high-persistence, self-contained MicroPython library built for the **Noc Lab** TinyML ecosystem as part of the **'Tiny Chip, Big Brain'** workshop. It serves as an end-to-end *In-situ Analytics* gateway, securing stable asynchronous telemetry data stream from edge devices (ESP32/RP2040) to a FastAPI cloud backend.
18
+
19
+ Leveraging native compiler execution (`@micropython.native`) for fast on-device inference and memory consolidations (`gc.collect()`), Raksa protects MicroPython devices from heap fragmentation during high-velocity TinyML operations.
20
+
21
+ ---
22
+
23
+ ## Key Features
24
+ - ๐Ÿ”„ **Persistent WebSockets**: Pure python RFC 6455 client implementation utilizing raw asynchronous `uasyncio` streams without external footprint overhead.
25
+ - โšก **High-Speed In-situ Analytics**: Edge inference calculations optimized directly via `@micropython.native` decorators to ensure minimal loop latency.
26
+ - ๐Ÿงน **Heap Consolidation**: Garbage collection executed dynamically inside data sync cycles to prevent RAM fragmentation on constrained hardware.
27
+ - ๐Ÿ‡ฎ๐Ÿ‡ฉ **Nusantara Resiliency**: Promotes self-reliance, stability, and secure data guardianship for resource-constrained edge-to-cloud architectures.
28
+
29
+ ---
30
+
31
+ ## Installation via `mip`
32
+
33
+ ### 1. Using `mpremote` (Recommended)
34
+ Connect your microcontroller board to your pc via USB/Serial and run:
35
+ ```bash
36
+ mpremote mip install github:Muhammad-Ikhwan-Fathulloh/Raksa
37
+ ```
38
+
39
+ ### 2. Directly in MicroPython REPL
40
+ Ensure your microcontroller board has an active Wi-Fi connection, then run:
41
+ ```python
42
+ import mip
43
+ mip.install("github:Muhammad-Ikhwan-Fathulloh/Raksa")
44
+ ```
45
+
46
+ ---
47
+
48
+ ## Minimalist Code Example (ESP32 / RP2040)
49
+
50
+ Below is an end-to-end usage code highlighting edge inference and data synchronization under 10 lines of functional code:
51
+
52
+ ```python
53
+ import uasyncio as asyncio
54
+ from raksa import RaksaClient
55
+
56
+ async def main():
57
+ client = RaksaClient("ws://192.168.1.100:8000/ws/telemetry")
58
+ model = ([[0.5, -0.2], [0.1, 0.9]], [0.1, 0.0]) # TinyML weights & biases
59
+ inputs = [0.8, 1.5] # Raw sensor variables
60
+
61
+ pred = client.infer(model, inputs)
62
+ await client.sync({"model": "NocML_V1", "features": inputs, "prediction": pred})
63
+
64
+ asyncio.run(main())
65
+ ```
66
+
67
+ ---
68
+
69
+ ## Complete Examples & Machine Learning Features
70
+
71
+ Full production-ready examples are available under the [`examples/`](examples/) folder, covering both hardware-specific scenarios and internal, Scikit-Learn-compatible Machine Learning algorithms:
72
+
73
+ ### Hardware-Connected Scenarios
74
+ | File | Scenario | Sensors |
75
+ | ------------------------------------------------------- | ------------------------------------------ | -------------------------- |
76
+ | [`main_edge.py`](examples/main_edge.py) | Quick-start minimalist (< 10 lines) | โ€” |
77
+ | [`esp32_wifi_sensor.py`](examples/esp32_wifi_sensor.py) | ADC analog sensor + 3-class classification | LDR / MQ-x / Potentiometer |
78
+ | [`esp32_dht_monitor.py`](examples/esp32_dht_monitor.py) | Temperature & humidity anomaly detection | DHT11 / DHT22 |
79
+
80
+ ### On-Device Machine Learning (NocML Port)
81
+ | File | Algorithms Covered | API Classes |
82
+ | ------------------------------------------------------- | -------------------------------- | ------------------------------------------------------------------- |
83
+ | [`ml_preprocessing.py`](examples/ml_preprocessing.py) | Custom Data Scaling & Extensions | `MinMaxScaler`, `StandardScaler`, `PolynomialFeatures` |
84
+ | [`ml_classification.py`](examples/ml_classification.py) | Optimized Classifiers | `KNN`, `NaiveBayes`, `LogisticRegression`, `DecisionTreeClassifier` |
85
+ | [`ml_clustering.py`](examples/ml_clustering.py) | Unsupervised Grouping / Fit | `KMeans` |
86
+ | [`ml_forecasting.py`](examples/ml_forecasting.py) | Simple Time Series forecasting | `LinearForecaster` |
87
+
88
+ ---
89
+
90
+ ## Machine Learning Reference (NocML Equivalent)
91
+
92
+ Raksa bundles a highly optimized port of the **NocML** C++ library, utilizing `@micropython.native` compiled mathematical loops to secure lightning-fast predictions directly on MicroPython edge boards without external dependencies or heavy resource bloat.
93
+
94
+ ### Preprocessing
95
+ - **`MinMaxScaler(dims, min_vals, max_vals)`**: Rescales variables to a range `[0.0 - 1.0]`.
96
+ - **`StandardScaler(dims, means, stddevs)`**: Standardizes features using population mean and variance.
97
+ - **`PolynomialFeatures(degree)`**: Expands input dimension using combinations with replacement.
98
+
99
+ ### Classification & Clustering
100
+ - **`KNN(training_data, labels, num_samples, dims, k=3)`**: Traditional classification using Euclidean distances.
101
+ - **`NaiveBayes(num_classes, dims, means, vars, priors)`**: Gaussian probabilistic classification.
102
+ - **`LogisticRegression(dims, weights, bias)`**: Fast binary classification with `predict()` and `predict_proba()`.
103
+ - **`DecisionTreeClassifier(nodes, num_nodes)`**: Node list traverse trees supporting dictionary, tuple, or custom node structures.
104
+ - **`KMeans(k, dims, centroids)`**: Clusters features into centroids. Supports `run(data, num_samples)` for on-device fitting.
105
+
106
+ ### Forecasting
107
+ - **`LinearForecaster()`**: Computes simple linear regressive trends ($y=mx+c$) natively via `fit(x, y)` and `forecastNext()`.
108
+
109
+ ---
110
+
111
+ ## Acknowledgments & Credits
112
+
113
+ The machine learning capabilities contained in Raksa are ported from the [NocML C++ Library for Arduino](https://github.com/Nocturnailed-Community/NocML), developed by Muhammad Ikhwan Fathulloh. Special credits go to the original creators for providing the foundation of these resource-constrained edge execution algorithms.
114
+
115
+ ---
116
+
117
+ ## Licenses & Copyrights
118
+ Developed by **Noc Lab** for nurturing TinyML literacy on hardware developers. Licensed under the MIT License.
@@ -0,0 +1,8 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ raksa.py
5
+ raksa.egg-info/PKG-INFO
6
+ raksa.egg-info/SOURCES.txt
7
+ raksa.egg-info/dependency_links.txt
8
+ raksa.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ raksa
raksa-0.1.0/raksa.py ADDED
@@ -0,0 +1,733 @@
1
+ # raksa.py
2
+ # MicroPython Client Library for In-situ Analytics and WebSocket communication.
3
+ # Part of the 'Noc Lab' TinyML ecosystem for the 'Tiny Chip, Big Brain' workshop.
4
+ # Developed with Nusantara values: Raksa = Protection, Guard, Reliability.
5
+
6
+ import gc
7
+
8
+ try:
9
+ import ustruct as struct
10
+ except ImportError:
11
+ import struct
12
+
13
+ try:
14
+ import uasyncio as asyncio
15
+ except ImportError:
16
+ import asyncio
17
+
18
+ try:
19
+ import urandom
20
+ except ImportError:
21
+ import random as urandom
22
+
23
+ # Dual compatibility for CPython and MicroPython platforms
24
+ try:
25
+ import micropython
26
+ except ImportError:
27
+ class micropython:
28
+ @staticmethod
29
+ def native(f):
30
+ return f
31
+ @staticmethod
32
+ def viper(f):
33
+ return f
34
+
35
+ # Standard Base64 characters helper for pure Python/MicroPython compilation
36
+ _B64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
37
+
38
+ @micropython.native
39
+ def _b64encode(b: bytes) -> str:
40
+ """Encode bytes to base64 string without external dependencies."""
41
+ res = ""
42
+ n = len(b)
43
+ for i in range(0, n, 3):
44
+ chunk = b[i:i+3]
45
+ pad = 3 - len(chunk)
46
+ if pad == 1:
47
+ chunk = chunk + b'\x00'
48
+ elif pad == 2:
49
+ chunk = chunk + b'\x00\x00'
50
+
51
+ val = (chunk[0] << 16) | (chunk[1] << 8) | chunk[2]
52
+ res += _B64_CHARS[(val >> 18) & 63]
53
+ res += _B64_CHARS[(val >> 12) & 63]
54
+ res += _B64_CHARS[(val >> 6) & 63] if pad < 2 else "="
55
+ res += _B64_CHARS[val & 63] if pad < 1 else "="
56
+ return res
57
+
58
+
59
+ class RaksaClient:
60
+ """
61
+ RaksaClient: Handles persistent, asynchronous WebSocket connections on MicroPython.
62
+ Manages client handshake, data serialization, memory optimization, and fast In-situ Analytics.
63
+ """
64
+ def __init__(self, uri: str, reconnect_delay: int = 5):
65
+ self.uri = uri
66
+ self.reconnect_delay = reconnect_delay
67
+ self.host = ""
68
+ self.port = 80
69
+ self.path = "/"
70
+ self.ssl = False
71
+ self.reader = None
72
+ self.writer = None
73
+ self._connected = False
74
+ self._lock = asyncio.Lock()
75
+
76
+ self._parse_uri(uri)
77
+
78
+ def _parse_uri(self, uri: str):
79
+ """Parse incoming ws:// or wss:// link into connection details."""
80
+ if not (uri.startswith("ws://") or uri.startswith("wss://")):
81
+ raise ValueError("URL must start with ws:// or wss://")
82
+
83
+ ssl = uri.startswith("wss://")
84
+ self.ssl = ssl
85
+ self.port = 443 if ssl else 80
86
+
87
+ url_path = uri.split("://", 1)[1]
88
+ if "/" in url_path:
89
+ host_port, path = url_path.split("/", 1)
90
+ self.path = "/" + path
91
+ else:
92
+ host_port = url_path
93
+ self.path = "/"
94
+
95
+ if ":" in host_port:
96
+ host, port = host_port.split(":", 1)
97
+ self.host = host
98
+ self.port = int(port)
99
+ else:
100
+ self.host = host_port
101
+
102
+ async def connect(self) -> bool:
103
+ """Establish WebSocket connection with the cloud server."""
104
+ async with self._lock:
105
+ if self._connected:
106
+ return True
107
+
108
+ print(f"[RAKSA] Menghubungkan ke backend di {self.host}:{self.port} ...")
109
+ try:
110
+ # Async TCP socket connection
111
+ self.reader, self.writer = await asyncio.open_connection(self.host, self.port)
112
+
113
+ # Perform handshakes manually to keep codebase extremely tiny
114
+ # Generate random 16-byte base64 key
115
+ raw_key = bytes([urandom.getrandbits(8) for _ in range(16)])
116
+ ws_key = _b64encode(raw_key)
117
+
118
+ handshake = (
119
+ f"GET {self.path} HTTP/1.1\r\n"
120
+ f"Host: {self.host}:{self.port}\r\n"
121
+ "Upgrade: websocket\r\n"
122
+ "Connection: Upgrade\r\n"
123
+ f"Sec-WebSocket-Key: {ws_key}\r\n"
124
+ "Sec-WebSocket-Version: 13\r\n\r\n"
125
+ )
126
+
127
+ self.writer.write(handshake.encode("utf-8"))
128
+ await self.writer.drain()
129
+
130
+ # Verify switching protocols status line
131
+ line = await self.reader.readline()
132
+ if not line:
133
+ raise Exception("Koneksi ditutup secara tidak terduga oleh server.")
134
+
135
+ status_line = line.decode("utf-8")
136
+ if "101" not in status_line:
137
+ raise Exception(f"Gagal melakukan upgrade websocket: {status_line.strip()}")
138
+
139
+ # Consume response headers until double CR/LF
140
+ while True:
141
+ line = await self.reader.readline()
142
+ if line == b"\r\n" or not line:
143
+ break
144
+
145
+ self._connected = True
146
+ print("[RAKSA] Koneksi persisten berhasil dijalankan. Aliran data aman dan stabil.")
147
+ gc.collect()
148
+ return True
149
+ except Exception as e:
150
+ print(f"[RAKSA] Gagal menghubungkan: {e}")
151
+ self._connected = False
152
+ self.writer = None
153
+ self.reader = None
154
+ return False
155
+
156
+ async def sync(self, payload) -> bool:
157
+ """
158
+ Sends telemetry/sensor payloads asynchronously via WebSocket.
159
+ Invokes memory clean-up automatically in the cycles to avoid RAM fragmentation.
160
+ """
161
+ if not self._connected:
162
+ success = await self.connect()
163
+ if not success:
164
+ return False
165
+
166
+ # Parse dict to JSON string if applicable
167
+ if isinstance(payload, dict):
168
+ import json
169
+ try:
170
+ payload = json.dumps(payload)
171
+ except Exception as e:
172
+ print(f"[RAKSA] Kesalahan serialisasi JSON: {e}")
173
+ return False
174
+
175
+ if not isinstance(payload, str):
176
+ payload = str(payload)
177
+
178
+ async with self._lock:
179
+ try:
180
+ # WebSocket Frame specification
181
+ data_bytes = payload.encode("utf-8")
182
+ length = len(data_bytes)
183
+
184
+ header = bytearray([0x81]) # Text frame FIN
185
+
186
+ # Client to Server must be masked
187
+ if length <= 125:
188
+ header.append(0x80 | length)
189
+ elif length <= 65535:
190
+ header.append(0x80 | 126)
191
+ header.extend(struct.pack(">H", length))
192
+ else:
193
+ header.append(0x80 | 127)
194
+ header.extend(struct.pack(">Q", length))
195
+
196
+ # Generate random 4-byte mask
197
+ mask_key = bytes([urandom.getrandbits(8) for _ in range(4)])
198
+ header.extend(mask_key)
199
+
200
+ masked_payload = self._mask_payload(data_bytes, mask_key)
201
+
202
+ # Send frame header and payload
203
+ self.writer.write(header)
204
+ self.writer.write(masked_payload)
205
+ await self.writer.drain()
206
+
207
+ # Memory consolidation
208
+ masked_payload = None
209
+ header = None
210
+ gc.collect() # Force execution of gc to maintain clean heap
211
+ return True
212
+
213
+ except Exception as e:
214
+ print(f"[RAKSA] Pengiriman gagal: {e}. Mencoba memulihkan...")
215
+ self._connected = False
216
+ self.writer = None
217
+ self.reader = None
218
+ gc.collect()
219
+ return False
220
+
221
+ @micropython.native
222
+ def _mask_payload(self, data: bytes, mask: bytes) -> bytes:
223
+ """XOR mask payload for client packets. Native acceleration applied."""
224
+ n = len(data)
225
+ m = bytearray(n)
226
+ for i in range(n):
227
+ m[i] = data[i] ^ mask[i % 4]
228
+ return bytes(m)
229
+
230
+ @micropython.native
231
+ def infer(self, model, data) -> float:
232
+ """
233
+ Process In-situ Analytics of data relative to a local ML weight model.
234
+ Uses MicroPython `@micropython.native` decorator for maximum computation speed.
235
+
236
+ Args:
237
+ model: A model configuration, standard callable, or a tuple of (weights, biases) matrix.
238
+ data: Data feature vector input (list).
239
+
240
+ Returns:
241
+ Computed inference result.
242
+ """
243
+ # Feature Matrix Multiply Inference Optimization
244
+ # weights: list of float lists (outputs x inputs), biases: list of float values (outputs)
245
+ if isinstance(model, tuple) and len(model) == 2:
246
+ weights = model[0]
247
+ biases = model[1]
248
+ if isinstance(weights, list) and isinstance(biases, list) and isinstance(data, list):
249
+ n_out = len(weights)
250
+ n_in = len(data)
251
+
252
+ if n_out > 0 and len(weights[0]) == n_in:
253
+ results = [0.0] * n_out
254
+ for i in range(n_out):
255
+ row = weights[i]
256
+ val = biases[i]
257
+ for j in range(n_in):
258
+ val += row[j] * data[j]
259
+ # Apply ReLU Activation
260
+ if val < 0.0:
261
+ results[i] = 0.0
262
+ else:
263
+ results[i] = val
264
+ return results
265
+
266
+ # Standard model execution fallback checks
267
+ if hasattr(model, 'predict') and callable(model.predict):
268
+ return model.predict(data)
269
+
270
+ if callable(model):
271
+ return model(data)
272
+
273
+ # Simulative linear TinyML model inference calculation fallback
274
+ val_sum = 0.0
275
+ n_features = len(data)
276
+ for i in range(n_features):
277
+ val_sum += data[i] * 0.45
278
+ val_sum += 0.05
279
+ return val_sum
280
+
281
+ async def recv(self) -> str:
282
+ """
283
+ Receives a single WebSocket frame asynchronously and returns its text payload.
284
+ Handles standard WebSocket framing including extended 16-bit and 64-bit lengths.
285
+ """
286
+ if not self._connected:
287
+ raise Exception("[RAKSA] Client tidak terhubung.")
288
+
289
+ try:
290
+ # Read first 2 bytes (header)
291
+ header = await self.reader.readexactly(2)
292
+ opcode = header[0] & 0x0f
293
+ has_mask = header[1] & 0x80
294
+ length = header[1] & 0x7f
295
+
296
+ # Close frame or ping/pong handlers
297
+ if opcode == 0x08: # CLOSE
298
+ await self.close()
299
+ return ""
300
+ elif opcode == 0x09: # PING
301
+ # Send PONG frame
302
+ await self._send_pong()
303
+ return await self.recv()
304
+ elif opcode == 0x0a: # PONG
305
+ return await self.recv()
306
+
307
+ # Parse extended lengths
308
+ if length == 126:
309
+ ext_len_bytes = await self.reader.readexactly(2)
310
+ length = struct.unpack(">H", ext_len_bytes)[0]
311
+ elif length == 127:
312
+ ext_len_bytes = await self.reader.readexactly(8)
313
+ length = struct.unpack(">Q", ext_len_bytes)[0]
314
+
315
+ # Read mask key if server masked it (though servers shouldn't mask)
316
+ if has_mask:
317
+ mask_key = await self.reader.readexactly(4)
318
+
319
+ # Read payload body
320
+ body = await self.reader.readexactly(length)
321
+
322
+ if has_mask:
323
+ body = self._mask_payload(body, mask_key)
324
+
325
+ # Decode and execute GC
326
+ res = body.decode("utf-8")
327
+ body = None
328
+ gc.collect()
329
+ return res
330
+
331
+ except Exception as e:
332
+ print(f"[RAKSA] Koneksi terputus saat membaca data: {e}")
333
+ self._connected = False
334
+ self.writer = None
335
+ self.reader = None
336
+ gc.collect()
337
+ raise e
338
+
339
+ async def _send_pong(self):
340
+ """Send a WebSocket Pong frame in response to Ping."""
341
+ async with self._lock:
342
+ if self.writer:
343
+ try:
344
+ self.writer.write(b'\x8a\x00') # FIN + PONG, length 0
345
+ await self.writer.drain()
346
+ except:
347
+ pass
348
+
349
+ async def close(self):
350
+ """Close connection cleanly and run gc.collect."""
351
+ async with self._lock:
352
+ if self.writer:
353
+ try:
354
+ self.writer.close()
355
+ await self.writer.wait_closed()
356
+ except:
357
+ pass
358
+ self.writer = None
359
+ self.reader = None
360
+ self._connected = False
361
+ gc.collect()
362
+
363
+
364
+ # โ”€โ”€ Preprocessing & Machine Learning Features (NocML inspired) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
365
+
366
+ class MinMaxScaler:
367
+ def __init__(self, dims, min_vals, max_vals):
368
+ self.dims = dims
369
+ self.min_vals = list(min_vals)
370
+ self.max_vals = list(max_vals)
371
+ # Precompute denominators to avoid division by zero and speed up math
372
+ self.ranges = [1.0] * dims
373
+ for i in range(dims):
374
+ r = self.max_vals[i] - self.min_vals[i]
375
+ self.ranges[i] = r if r != 0.0 else 1.0
376
+
377
+ @micropython.native
378
+ def transform(self, x, out=None):
379
+ if out is None:
380
+ out = [0.0] * self.dims
381
+ for i in range(self.dims):
382
+ out[i] = (x[i] - self.min_vals[i]) / self.ranges[i]
383
+ return out
384
+
385
+
386
+ class StandardScaler:
387
+ def __init__(self, dims, means, stddevs):
388
+ self.dims = dims
389
+ self.means = list(means)
390
+ self.stddevs = list(stddevs)
391
+ # Avoid division by zero
392
+ self.scales = [1.0] * dims
393
+ for i in range(dims):
394
+ s = self.stddevs[i]
395
+ self.scales[i] = s if s != 0.0 else 1.0
396
+
397
+ @micropython.native
398
+ def transform(self, x, out=None):
399
+ if out is None:
400
+ out = [0.0] * self.dims
401
+ for i in range(self.dims):
402
+ out[i] = (x[i] - self.means[i]) / self.scales[i]
403
+ return out
404
+
405
+
406
+ class PolynomialFeatures:
407
+ def __init__(self, degree):
408
+ self.degree = degree
409
+ self._cache = {}
410
+
411
+ def _get_combinations(self, dims):
412
+ if dims in self._cache:
413
+ return self._cache[dims]
414
+
415
+ # Generate combinations with replacement manually without itertools
416
+ combos = []
417
+ for d in range(self.degree + 1):
418
+ combos.extend(self._cvw(list(range(dims)), d))
419
+ self._cache[dims] = combos
420
+ return combos
421
+
422
+ def _cvw(self, items, r):
423
+ # Helper for combinations with replacement
424
+ if r == 0:
425
+ return [()]
426
+ if not items:
427
+ return []
428
+ n = len(items)
429
+ indices = [0] * r
430
+ results = [tuple(items[i] for i in indices)]
431
+ while True:
432
+ for i in reversed(range(r)):
433
+ if indices[i] != n - 1:
434
+ break
435
+ else:
436
+ return results
437
+ indices[i:] = [indices[i] + 1] * (r - i)
438
+ results.append(tuple(items[k] for k in indices))
439
+
440
+ @micropython.native
441
+ def transform(self, x, dims=None, out=None):
442
+ if dims is None:
443
+ dims = len(x)
444
+ combos = self._get_combinations(dims)
445
+
446
+ n_out = len(combos)
447
+ if out is None:
448
+ out = [0.0] * n_out
449
+
450
+ for i in range(n_out):
451
+ combo = combos[i]
452
+ val = 1.0
453
+ for idx in combo:
454
+ val *= x[idx]
455
+ out[i] = val
456
+ return out
457
+
458
+
459
+ class LinearForecaster:
460
+ def __init__(self):
461
+ self.m = 0.0
462
+ self.c = 0.0
463
+ self.last_x = 0.0
464
+ self.step = 1.0
465
+
466
+ def fit(self, x, y):
467
+ n = len(x)
468
+ if n == 0:
469
+ return
470
+ sum_x = sum(x)
471
+ sum_y = sum(y)
472
+ mean_x = sum_x / n
473
+ mean_y = sum_y / n
474
+
475
+ num = 0.0
476
+ den = 0.0
477
+ for i in range(n):
478
+ dx = x[i] - mean_x
479
+ num += dx * (y[i] - mean_y)
480
+ den += dx * dx
481
+
482
+ if den != 0.0:
483
+ self.m = num / den
484
+ else:
485
+ self.m = 0.0
486
+ self.c = mean_y - self.m * mean_x
487
+
488
+ self.last_x = x[-1]
489
+ self.step = (x[-1] - x[0]) / (n - 1) if n > 1 else 1.0
490
+
491
+ def predict(self, x):
492
+ return self.m * x + self.c
493
+
494
+ def forecastNext(self):
495
+ return self.m * (self.last_x + self.step) + self.c
496
+
497
+
498
+ class KNN:
499
+ def __init__(self, training_data, labels, num_samples, dims, k=3):
500
+ self.training_data = list(training_data)
501
+ self.labels = list(labels)
502
+ self.num_samples = num_samples
503
+ self.dims = dims
504
+ self.k = k
505
+ self.is_flat = len(self.training_data) == num_samples * dims
506
+
507
+ @micropython.native
508
+ def predict(self, x):
509
+ dists = [0.0] * self.num_samples
510
+ if self.is_flat:
511
+ for i in range(self.num_samples):
512
+ d2 = 0.0
513
+ offset = i * self.dims
514
+ for j in range(self.dims):
515
+ diff = x[j] - self.training_data[offset + j]
516
+ d2 += diff * diff
517
+ dists[i] = d2
518
+ else:
519
+ for i in range(self.num_samples):
520
+ d2 = 0.0
521
+ row = self.training_data[i]
522
+ for j in range(self.dims):
523
+ diff = x[j] - row[j]
524
+ d2 += diff * diff
525
+ dists[i] = d2
526
+
527
+ pairs = []
528
+ for i in range(self.num_samples):
529
+ pairs.append((dists[i], self.labels[i]))
530
+ pairs.sort()
531
+
532
+ votes = {}
533
+ for i in range(self.k):
534
+ lbl = pairs[i][1]
535
+ votes[lbl] = votes.get(lbl, 0) + 1
536
+
537
+ max_votes = -1
538
+ winner = None
539
+ for lbl, count in votes.items():
540
+ if count > max_votes:
541
+ max_votes = count
542
+ winner = lbl
543
+ return winner
544
+
545
+
546
+ class NaiveBayes:
547
+ def __init__(self, num_classes, dims, means, vars, priors):
548
+ self.num_classes = num_classes
549
+ self.dims = dims
550
+ self.means = list(means)
551
+ self.vars = list(vars)
552
+ self.priors = list(priors)
553
+
554
+ @micropython.native
555
+ def predict(self, x):
556
+ best_class = 0
557
+ max_log_prob = -1e9
558
+ import math
559
+
560
+ for c in range(self.num_classes):
561
+ log_prob = math.log(self.priors[c])
562
+ for j in range(self.dims):
563
+ idx = c * self.dims + j
564
+ mean_val = self.means[idx]
565
+ var_val = self.vars[idx]
566
+ if var_val <= 0.0:
567
+ var_val = 1e-9
568
+ diff = x[j] - mean_val
569
+ log_prob += -0.5 * math.log(6.283185307179586 * var_val) - (diff * diff) / (2.0 * var_val)
570
+
571
+ if log_prob > max_log_prob:
572
+ max_log_prob = log_prob
573
+ best_class = c
574
+ return best_class
575
+
576
+
577
+ class LogisticRegression:
578
+ def __init__(self, dims, weights, bias):
579
+ self.dims = dims
580
+ self.weights = list(weights)
581
+ self.bias = bias
582
+
583
+ @micropython.native
584
+ def predict(self, x):
585
+ z = self.bias
586
+ for i in range(self.dims):
587
+ z += self.weights[i] * x[i]
588
+ return 1 if z >= 0.0 else 0
589
+
590
+ def predict_proba(self, x):
591
+ z = self.bias
592
+ for i in range(self.dims):
593
+ z += self.weights[i] * x[i]
594
+ import math
595
+ try:
596
+ return 1.0 / (1.0 + math.exp(-z))
597
+ except OverflowError:
598
+ return 0.0 if z < 0 else 1.0
599
+
600
+
601
+ class DecisionTreeClassifier:
602
+ def __init__(self, nodes, num_nodes=None):
603
+ self.nodes = list(nodes)
604
+ self.num_nodes = len(self.nodes) if num_nodes is None else num_nodes
605
+
606
+ @micropython.native
607
+ def predict(self, x):
608
+ curr_idx = 0
609
+ while curr_idx < self.num_nodes:
610
+ node = self.nodes[curr_idx]
611
+ if isinstance(node, dict):
612
+ feat = node.get("feature", -1)
613
+ if feat == -1: feat = node.get("feature_idx", -1)
614
+ threshold = node.get("threshold", 0.0)
615
+ left = node.get("left", -1)
616
+ if left == -1: left = node.get("left_child", -1)
617
+ right = node.get("right", -1)
618
+ if right == -1: right = node.get("right_child", -1)
619
+ val = node.get("value", -1)
620
+ elif isinstance(node, (list, tuple)):
621
+ feat = int(node[0])
622
+ threshold = float(node[1])
623
+ left = int(node[2])
624
+ right = int(node[3])
625
+ val = int(node[4])
626
+ else:
627
+ feat = getattr(node, "feature", -1)
628
+ if feat == -1: feat = getattr(node, "feature_idx", -1)
629
+ threshold = getattr(node, "threshold", 0.0)
630
+ left = getattr(node, "left", -1)
631
+ if left == -1: left = getattr(node, "left_child", -1)
632
+ right = getattr(node, "right", -1)
633
+ if right == -1: right = getattr(node, "right_child", -1)
634
+ val = getattr(node, "value", -1)
635
+
636
+ if feat < 0:
637
+ return val
638
+ if x[feat] <= threshold:
639
+ curr_idx = left
640
+ else:
641
+ curr_idx = right
642
+ return -1
643
+
644
+
645
+ class KMeans:
646
+ def __init__(self, k, dims, centroids=None):
647
+ self.k = k
648
+ self.dims = dims
649
+ if centroids is not None:
650
+ self.centroids = list(centroids)
651
+ self.is_flat = len(self.centroids) == k * dims
652
+ else:
653
+ self.centroids = [0.0] * (k * dims)
654
+ self.is_flat = True
655
+
656
+ @micropython.native
657
+ def predict(self, x):
658
+ best_k = 0
659
+ min_d2 = 1e9
660
+ for i in range(self.k):
661
+ d2 = 0.0
662
+ if self.is_flat:
663
+ offset = i * self.dims
664
+ for j in range(self.dims):
665
+ diff = x[j] - self.centroids[offset + j]
666
+ d2 += diff * diff
667
+ else:
668
+ row = self.centroids[i]
669
+ for j in range(self.dims):
670
+ diff = x[j] - row[j]
671
+ d2 += diff * diff
672
+ if d2 < min_d2:
673
+ min_d2 = d2
674
+ best_k = i
675
+ return best_k
676
+
677
+ def run(self, data, num_samples, assignments=None, max_iters=10):
678
+ data = list(data)
679
+ is_data_flat = len(data) == num_samples * self.dims
680
+ if assignments is None:
681
+ assignments = [0] * num_samples
682
+
683
+ total_zeros = True
684
+ for val in self.centroids:
685
+ if val != 0.0:
686
+ total_zeros = False
687
+ break
688
+ if total_zeros:
689
+ for i in range(self.k):
690
+ sample_idx = i % num_samples
691
+ if is_data_flat:
692
+ for j in range(self.dims):
693
+ self.centroids[i * self.dims + j] = data[sample_idx * self.dims + j]
694
+ else:
695
+ self.centroids[i] = list(data[sample_idx])
696
+
697
+ for _ in range(max_iters):
698
+ changed = False
699
+ for i in range(num_samples):
700
+ if is_data_flat:
701
+ x = data[i * self.dims : (i + 1) * self.dims]
702
+ else:
703
+ x = data[i]
704
+ old_k = assignments[i]
705
+ new_k = self.predict(x)
706
+ if old_k != new_k:
707
+ assignments[i] = new_k
708
+ changed = True
709
+ if not changed:
710
+ break
711
+
712
+ cnt = [0] * self.k
713
+ sums = [0.0] * (self.k * self.dims)
714
+ for i in range(num_samples):
715
+ cid = assignments[i]
716
+ cnt[cid] += 1
717
+ if is_data_flat:
718
+ for j in range(self.dims):
719
+ sums[cid * self.dims + j] += data[i * self.dims + j]
720
+ else:
721
+ for j in range(self.dims):
722
+ sums[cid * self.dims + j] += data[i][j]
723
+
724
+ for cid in range(self.k):
725
+ n_pts = cnt[cid]
726
+ if n_pts > 0:
727
+ for j in range(self.dims):
728
+ val = sums[cid * self.dims + j] / n_pts
729
+ if self.is_flat:
730
+ self.centroids[cid * self.dims + j] = val
731
+ else:
732
+ self.centroids[cid][j] = val
733
+ return assignments
raksa-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+