[
  {
    "path": ".gitattributes",
    "content": "*.jinja linguist-detectable=false\n*.py linguist-detectable=true"
  },
  {
    "path": ".gitignore",
    "content": "venv\n.idea\ntests\nmicromlgen/__pycache__\nexamples"
  },
  {
    "path": "LICENSE.txt",
    "content": "MIT License\nCopyright (c) 2018 YOUR NAME\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "MANIFEST",
    "content": "# file GENERATED by distutils, do NOT edit\nsetup.cfg\nsetup.py\nmicromlgen/__init__.py\nmicromlgen/decisiontreeclassifier.py\nmicromlgen/decisiontreeregressor.py\nmicromlgen/gaussiannb.py\nmicromlgen/linear_regression.py\nmicromlgen/logisticregression.py\nmicromlgen/micromlgen.py\nmicromlgen/pca.py\nmicromlgen/platforms.py\nmicromlgen/principalfft.py\nmicromlgen/randomforestclassifier.py\nmicromlgen/randomforestregressor.py\nmicromlgen/rvm.py\nmicromlgen/sefr.py\nmicromlgen/svm.py\nmicromlgen/utils.py\nmicromlgen/wifiindoorpositioning.py\nmicromlgen/xgboost.py\nmicromlgen/templates/__init__.py\nmicromlgen/templates/_skeleton.jinja\nmicromlgen/templates/classmap.jinja\nmicromlgen/templates/dot.jinja\nmicromlgen/templates/testset.jinja\nmicromlgen/templates/trainset.jinja\nmicromlgen/templates/vote.jinja\nmicromlgen/templates/__pycache__/__init__.cpython-37.pyc\nmicromlgen/templates/decisiontree/__init__.py\nmicromlgen/templates/decisiontree/decisiontree.jinja\nmicromlgen/templates/decisiontree/decisiontree_regressor.jinja\nmicromlgen/templates/decisiontree/tree.jinja\nmicromlgen/templates/decisiontree/tree_regressor.jinja\nmicromlgen/templates/gaussiannb/__init__.py\nmicromlgen/templates/gaussiannb/gaussiannb.jinja\nmicromlgen/templates/gaussiannb/vote.jinja\nmicromlgen/templates/linearregression/__init__.py\nmicromlgen/templates/linearregression/linearregression.jinja\nmicromlgen/templates/logisticregression/__init__.py\nmicromlgen/templates/logisticregression/logisticregression.jinja\nmicromlgen/templates/logisticregression/vote.arduino.jinja\nmicromlgen/templates/logisticregression/vote.attiny.jinja\nmicromlgen/templates/pca/__init__.py\nmicromlgen/templates/pca/pca.jinja\nmicromlgen/templates/principalfft/__init__.py\nmicromlgen/templates/principalfft/lut.jinja\nmicromlgen/templates/principalfft/lut_bool.jinja\nmicromlgen/templates/principalfft/principalfft.jinja\nmicromlgen/templates/randomforest/__init__.py\nmicromlgen/templates/randomforest/randomforest.jinja\nmicromlgen/templates/randomforest/randomforest_regressor.jinja\nmicromlgen/templates/randomforest/tree.jinja\nmicromlgen/templates/randomforest/tree_regressor.jinja\nmicromlgen/templates/rvm/__init__.py\nmicromlgen/templates/rvm/rvm.jinja\nmicromlgen/templates/sefr/__init__.py\nmicromlgen/templates/sefr/dot.jinja\nmicromlgen/templates/sefr/sefr.jinja\nmicromlgen/templates/svm/__init__.py\nmicromlgen/templates/svm/svm.jinja\nmicromlgen/templates/svm/computations/class.jinja\nmicromlgen/templates/svm/computations/decisions.jinja\nmicromlgen/templates/svm/computations/votes.jinja\nmicromlgen/templates/svm/computations/kernel/arduino.jinja\nmicromlgen/templates/svm/computations/kernel/attiny.jinja\nmicromlgen/templates/svm/kernel/arduino.jinja\nmicromlgen/templates/svm/kernel/attiny.jinja\nmicromlgen/templates/svm/kernel/kernel.jinja\nmicromlgen/templates/wifiindoorpositioning/__init__.py\nmicromlgen/templates/wifiindoorpositioning/wifiindoorpositioning.jinja\nmicromlgen/templates/xgboost/__init__.py\nmicromlgen/templates/xgboost/tree.jinja\nmicromlgen/templates/xgboost/xgboost.jinja\n"
  },
  {
    "path": "README.md",
    "content": "# Introducing MicroML\n\nMicroML is an attempt to bring Machine Learning algorithms to microcontrollers.\nPlease refer to [this blog post](https://eloquentarduino.github.io/2019/11/you-can-run-machine-learning-on-arduino/)\nto an introduction to the topic.\n\n**This repository is archived because it does what it was meant to do: generate C++ code for the supported models. I'm focusing on a more comprehensive library ([https://github.com/eloquentarduino/tinyml4all-python/](https://github.com/eloquentarduino/tinyml4all-python/)), so this will not receive updates**.\n\n## Install\n\n`pip install micromlgen`\n\n## Supported classifiers\n\n`micromlgen` can port to plain C many types of classifiers:\n\n - DecisionTree\n - RandomForest\n - XGBoost\n - GaussianNB\n - Support Vector Machines (SVC and OneClassSVM)\n - Relevant Vector Machines (from `skbayes.rvm_ard_models` package)\n - SEFR\n - PCA\n\n```python\nfrom micromlgen import port\nfrom sklearn.svm import SVC\nfrom sklearn.datasets import load_iris\n\n\nif __name__ == '__main__':\n    iris = load_iris()\n    X = iris.data\n    y = iris.target\n    clf = SVC(kernel='linear').fit(X, y)\n    print(port(clf))\n```\n\nYou may pass a classmap to get readable class names in the ported code\n\n```python\nfrom micromlgen import port\nfrom sklearn.svm import SVC\nfrom sklearn.datasets import load_iris\n\n\nif __name__ == '__main__':\n    iris = load_iris()\n    X = iris.data\n    y = iris.target\n    clf = SVC(kernel='linear').fit(X, y)\n    print(port(clf, classmap={\n        0: 'setosa',\n        1: 'virginica',\n        2: 'versicolor'\n    }))\n```\n\n## PCA\n\nIt can export a PCA transformer.\n\n```python\nfrom sklearn.decomposition import PCA\nfrom sklearn.datasets import load_iris\nfrom micromlgen import port\n\nif __name__ == '__main__':\n    X = load_iris().data\n    pca = PCA(n_components=2, whiten=False).fit(X)\n    \n    print(port(pca))\n```\n\n## SEFR\n\nRead the post about [SEFR](https://eloquentarduino.github.io/2020/07/sefr-a-fast-linear-time-classifier-for-ultra-low-power-devices/).\n\n```shell script\npip install sefr\n```\n\n```python\nfrom sefr import SEFR\nfrom micromlgen import port\n\n\nclf = SEFR()\nclf.fit(X, y)\nprint(port(clf))\n```\n\n## DecisionTreeRegressor and RandomForestRegressor\n\n```bash\npip install micromlgen>=1.1.26\n```\n\n```python\nfrom sklearn.datasets import load_boston\nfrom sklearn.tree import DecisionTreeRegressor\nfrom sklearn.ensemble import RandomForestRegressor\nfrom micromlgen import port\n\n\nif __name__ == '__main__':\n    X, y = load_boston(return_X_y=True)\n    regr = DecisionTreeRegressor(max_depth=10, min_samples_leaf=5).fit(X, y)\n    regr = RandomForestRegressor(n_estimators=10, max_depth=10, min_samples_leaf=5).fit(X, y)\n    \n    with open('RandomForestRegressor.h', 'w') as file:\n        file.write(port(regr))\n```\n\n```cpp\n// Arduino sketch\n#include \"RandomForestRegressor.h\"\n\nEloquent::ML::Port::RandomForestRegressor regressor;\nfloat X[] = {...};\n\n\nvoid setup() {\n}\n\nvoid loop() {\n    float y_pred = regressor.predict(X);\n}\n```\n"
  },
  {
    "path": "micromlgen/__init__.py",
    "content": "import micromlgen.platforms as platforms\nfrom micromlgen.micromlgen import port\nfrom micromlgen.utils import port_testset, port_trainset\nfrom micromlgen.wifiindoorpositioning import port_wifi_indoor_positioning\n"
  },
  {
    "path": "micromlgen/decisiontreeclassifier.py",
    "content": "from micromlgen.utils import jinja, check_type\n\n\ndef is_decisiontree(clf):\n    \"\"\"Test if classifier can be ported\"\"\"\n    return check_type(clf, 'DecisionTreeClassifier')\n\n\ndef port_decisiontree(clf, **kwargs):\n    \"\"\"Port sklearn's DecisionTreeClassifier\"\"\"\n    return jinja('decisiontree/decisiontree.jinja', {\n        'left': clf.tree_.children_left,\n        'right': clf.tree_.children_right,\n        'features': clf.tree_.feature,\n        'thresholds': clf.tree_.threshold,\n        'classes': clf.tree_.value,\n        'i': 0\n    }, {\n        'classname': 'DecisionTree'\n    }, **kwargs)"
  },
  {
    "path": "micromlgen/decisiontreeregressor.py",
    "content": "from micromlgen.utils import jinja, check_type\n\n\ndef is_decisiontree_regressor(clf):\n    \"\"\"\n    Test if classifier can be ported\n    :param clf:\n    :return: bool\n    \"\"\"\n    return check_type(clf, 'DecisionTreeRegressor')\n\n\ndef port_decisiontree_regressor(clf, **kwargs):\n    \"\"\"\n    Port sklearn's DecisionTreeClassifier\n    :param clf:\n    :return: str ported classifier\n    \"\"\"\n    return jinja('decisiontree/decisiontree_regressor.jinja', {\n        'dtype': 'float',\n        'left': clf.tree_.children_left,\n        'right': clf.tree_.children_right,\n        'features': clf.tree_.feature,\n        'thresholds': clf.tree_.threshold,\n        'values': clf.tree_.value,\n        'i': 0\n    }, {\n        'classname': 'DecisionTreeRegressor'\n    }, **kwargs)"
  },
  {
    "path": "micromlgen/gaussiannb.py",
    "content": "from micromlgen.utils import jinja, check_type\n\n\ndef is_gaussiannb(clf):\n    \"\"\"Test if classifier can be ported\"\"\"\n    return check_type(clf, 'GaussianNB')\n\n\ndef port_gaussiannb(clf, **kwargs):\n    \"\"\"Port sklearn's GaussianNB\"\"\"\n    return jinja('gaussiannb/gaussiannb.jinja', {\n        'sigma': clf.sigma_,\n        'theta': clf.theta_,\n        'prior': clf.class_prior_,\n        'classes': clf.classes_,\n        'n_classes': len(clf.classes_)\n    }, {\n        'classname': 'GaussianNB'\n    }, **kwargs)"
  },
  {
    "path": "micromlgen/linear_regression.py",
    "content": "from micromlgen.utils import jinja, check_type\n\n\ndef is_linear_regression(clf):\n    \"\"\"Test if classifier can be ported\"\"\"\n    return check_type(clf, 'LinearRegression')\n\n\ndef port_linear_regression(clf, classname=None, **kwargs):\n    \"\"\"Port Linear Regression\"\"\"\n    return jinja('linearregression/linearregression.jinja', {\n        'coefs': clf.coef_,\n        'intercept': clf.intercept_,\n        'dimension': len(clf.coef_),\n        'dtype': 'float'\n    }, {\n        'classname': 'LinearRegression'\n    }, **kwargs)"
  },
  {
    "path": "micromlgen/logisticregression.py",
    "content": "from micromlgen.utils import jinja, check_type\n\n\ndef is_logisticregression(clf):\n    \"\"\"Test if classifier can be ported\"\"\"\n    return check_type(clf, 'LogisticRegression')\n\n\ndef port_logisticregression(clf, **kwargs):\n    \"\"\"Port sklearn's LogisticRegressionClassifier\"\"\"\n    return jinja('logisticregression/logisticregression.jinja', {\n        'weights': clf.coef_,\n        'intercept': clf.intercept_,\n        'classes': clf.classes_,\n        'n_classes': len(clf.classes_)\n    }, {\n        'classname': 'LogisticRegression'\n    }, **kwargs)"
  },
  {
    "path": "micromlgen/micromlgen.py",
    "content": "from micromlgen import platforms\nfrom micromlgen.svm import is_svm, port_svm\nfrom micromlgen.rvm import is_rvm, port_rvm\nfrom micromlgen.sefr import is_sefr, port_sefr\nfrom micromlgen.decisiontreeclassifier import is_decisiontree, port_decisiontree\nfrom micromlgen.decisiontreeregressor import is_decisiontree_regressor, port_decisiontree_regressor\nfrom micromlgen.randomforestclassifier import is_randomforest, port_randomforest\nfrom micromlgen.randomforestregressor import is_randomforest_regressor, port_randomforest_regressor\nfrom micromlgen.logisticregression import is_logisticregression, port_logisticregression\nfrom micromlgen.gaussiannb import is_gaussiannb, port_gaussiannb\nfrom micromlgen.pca import is_pca, port_pca\nfrom micromlgen.principalfft import is_principalfft, port_principalfft\nfrom micromlgen.linear_regression import is_linear_regression, port_linear_regression\nfrom micromlgen.xgboost import is_xgboost, port_xgboost\n\n\ndef port(\n        clf,\n        classname=None,\n        classmap=None,\n        platform=platforms.ARDUINO,\n        precision=None,\n        **kwargs):\n    \"\"\"Port a classifier to plain C++\"\"\"\n    assert platform in platforms.ALL, 'Unknown platform %s. Use one of %s' % (platform, ', '.join(platforms.ALL))\n\n    if is_svm(clf):\n        return port_svm(**locals())\n    elif is_rvm(clf):\n        return port_rvm(**locals())\n    elif is_sefr(clf):\n        return port_sefr(**locals())\n    elif is_decisiontree(clf):\n        return port_decisiontree(**locals())\n    elif is_randomforest(clf):\n        return port_randomforest(**locals())\n    elif is_logisticregression(clf):\n        return port_logisticregression(**locals())\n    elif is_gaussiannb(clf):\n        return port_gaussiannb(**locals())\n    elif is_pca(clf):\n        return port_pca(**locals())\n    elif is_principalfft(clf):\n        return port_principalfft(**locals(), **kwargs)\n    elif is_linear_regression(clf):\n        return port_linear_regression(**locals(), **kwargs)\n    elif is_xgboost(clf):\n        return port_xgboost(**locals(), **kwargs)\n    elif is_decisiontree_regressor(clf):\n        return port_decisiontree_regressor(**locals(), **kwargs)\n    elif is_randomforest_regressor(clf):\n        return port_randomforest_regressor(**locals(), **kwargs)\n    raise TypeError('clf MUST be one of %s' % ', '.join(platforms.ALLOWED_CLASSIFIERS))"
  },
  {
    "path": "micromlgen/pca.py",
    "content": "from micromlgen.utils import jinja, check_type\n\n\ndef is_pca(clf):\n    \"\"\"Test if classifier can be ported\"\"\"\n    return check_type(clf, 'PCA')\n\n\ndef port_pca(clf, **kwargs):\n    \"\"\"Port a PCA\"\"\"\n    return jinja('pca/pca.jinja', {\n        'arrays': {\n            'components': clf.components_,\n            'mean': clf.mean_\n        },\n    }, {\n        'classname': 'PCA'\n    }, **kwargs)"
  },
  {
    "path": "micromlgen/platforms.py",
    "content": "ARDUINO = 'arduino'\nATTINY = 'attiny'\nALL = [\n    ARDUINO,\n    ATTINY\n]\n\nALLOWED_CLASSIFIERS = [\n    'SVC',\n    'OneClassSVM',\n    'RVC',\n    'SEFR',\n    'DecisionTreeClassifier',\n    'DecisionTreeRegressor',\n    'RandomForestClassifier',\n    'RandomForestRegressor',\n    'GaussianNB',\n    'LogisticRegression',\n    'PCA',\n    'PrincipalFFT',\n    'LinearRegression',\n    'XGBClassifier'\n]"
  },
  {
    "path": "micromlgen/principalfft.py",
    "content": "from micromlgen.utils import jinja, check_type\nfrom math import pi\n\n\ndef is_principalfft(clf):\n    \"\"\"Test if classifier can be ported\"\"\"\n    return check_type(clf, 'PrincipalFFT') or check_type(clf, 'KFFT')\n\n\ndef port_principalfft(clf, optimize_sin=False, lookup_cos=None, lookup_sin=None, **kwargs):\n    \"\"\"Port PrincipalFFT classifier\"\"\"\n    return jinja('principalfft/principalfft.jinja', {\n        'fft': clf,\n        'PI': pi,\n        'size': len(clf.idx),\n        'optmize_sin': optimize_sin,\n        'lookup_cos': lookup_cos,\n        'lookup_sin': lookup_sin,\n    }, **kwargs)"
  },
  {
    "path": "micromlgen/randomforestclassifier.py",
    "content": "from micromlgen.utils import jinja, check_type\n\n\ndef is_randomforest(clf):\n    \"\"\"Test if classifier can be ported\"\"\"\n    return check_type(clf, 'RandomForestClassifier')\n\n\ndef port_randomforest(clf, **kwargs):\n    \"\"\"Port sklearn's RandomForestClassifier\"\"\"\n    return jinja('randomforest/randomforest.jinja', {\n        'n_classes': clf.n_classes_,\n        'trees': [{\n            'left': clf.tree_.children_left,\n            'right': clf.tree_.children_right,\n            'features': clf.tree_.feature,\n            'thresholds': clf.tree_.threshold,\n            'classes': clf.tree_.value,\n        } for clf in clf.estimators_]\n    }, {\n        'classname': 'RandomForest'\n    }, **kwargs)"
  },
  {
    "path": "micromlgen/randomforestregressor.py",
    "content": "from micromlgen.utils import jinja, check_type\n\n\ndef is_randomforest_regressor(clf):\n    \"\"\"\n    Test if classifier can be ported\n    \"\"\"\n    return check_type(clf, 'RandomForestRegressor')\n\n\ndef port_randomforest_regressor(clf, **kwargs):\n    \"\"\"\n    Port sklearn's RandomForestRegressor\n    \"\"\"\n    return jinja('randomforest/randomforest_regressor.jinja', {\n        'dtype': 'float',\n        'n_estimators': clf.n_estimators,\n        'trees': [{\n            'left': clf.tree_.children_left,\n            'right': clf.tree_.children_right,\n            'features': clf.tree_.feature,\n            'thresholds': clf.tree_.threshold,\n            'values': clf.tree_.value,\n        } for clf in clf.estimators_]\n    }, {\n        'classname': 'RandomForestRegressor'\n    }, **kwargs)"
  },
  {
    "path": "micromlgen/rvm.py",
    "content": "from micromlgen.utils import jinja, check_type\n\n\ndef is_rvm(clf):\n    \"\"\"Test if classifier can be ported\"\"\"\n    return check_type(clf, 'RVC')\n\n\ndef port_rvm(clf, **kwargs):\n    \"\"\"Port a RVM classifier\"\"\"\n    return jinja('rvm/rvm.jinja', {\n        'n_classes': len(clf.intercept_),\n        'kernel': {\n            'type': clf.kernel,\n            'gamma': clf.gamma,\n            'coef0': clf.coef0,\n            'degree': clf.degree\n        },\n        'sizes': {\n            'features': clf.relevant_vectors_[0].shape[1],\n        },\n        'arrays': {\n            'vectors': clf.relevant_vectors_,\n            'coefs': clf.coef_,\n            'actives': clf.active_,\n            'intercepts': clf.intercept_,\n            'mean': clf._x_mean,\n            'std': clf._x_std\n        },\n    }, {\n        'classname': 'RVC'\n    }, **kwargs)\n"
  },
  {
    "path": "micromlgen/sefr.py",
    "content": "from micromlgen.utils import jinja, check_type\n\n\ndef is_sefr(clf):\n    \"\"\"Test if classifier can be ported\"\"\"\n    return check_type(clf, 'SEFR')\n\n\ndef port_sefr(clf, classname=None, **kwargs):\n    \"\"\"Port SEFR classifier\"\"\"\n    return jinja('sefr/sefr.jinja', {\n        'weights': clf.weights,\n        'bias': clf.bias,\n        'dimension': len(clf.weights),\n    }, {\n        'classname': 'SEFR'\n    }, **kwargs)"
  },
  {
    "path": "micromlgen/svm.py",
    "content": "from micromlgen.utils import jinja, check_type\n\n\ndef is_svm(clf):\n    \"\"\"Test if classifier can be ported\"\"\"\n    return check_type(clf, 'SVC', 'OneClassSVM')\n\n\ndef port_svm(clf, **kwargs):\n    \"\"\"Port a SVC / OneClassSVC classifier\"\"\"\n    assert isinstance(clf.gamma, float), 'You probably didn\\'t set an explicit value for gamma: 0.001 is a good default'\n\n    support_v = clf.support_vectors_\n    n_classes = len(clf.n_support_)\n\n    return jinja('svm/svm.jinja', {\n        'kernel': {\n            'type': clf.kernel,\n            'gamma': clf.gamma,\n            'coef0': clf.coef0,\n            'degree': clf.degree\n        },\n        'sizes': {\n            'features': len(support_v[0]),\n            'vectors': len(support_v),\n            'classes': n_classes,\n            'decisions': n_classes * (n_classes - 1) // 2,\n            'supports': clf.n_support_\n        },\n        'arrays': {\n            'supports': support_v,\n            'intercepts': clf.intercept_,\n            'coefs': clf.dual_coef_\n        }\n    }, {\n        'classname': 'OneClassSVM' if check_type(clf, 'OneClassSVM') else 'SVM'\n    }, **kwargs)"
  },
  {
    "path": "micromlgen/templates/__init__.py",
    "content": "# here just to force pip to copy this folder"
  },
  {
    "path": "micromlgen/templates/_skeleton.jinja",
    "content": "#pragma once\n#include <cstdarg>\n\nnamespace Eloquent {\n    namespace ML {\n        namespace Port {\n            class {{ classname }} {\n                public:\n\n                    /**\n                     * Predict class for features vector\n                     */\n                    {{ dtype|default(\"int\", true) }} predict(float *x) {\n                        {% block predict %}{% endblock %}\n                    }\n\n                    {% include 'classmap.jinja' %}\n\n                    {% block public %}{% endblock %}\n\n                protected:\n\n                    {% block protected %}{% endblock %}\n            };\n        }\n    }\n}"
  },
  {
    "path": "micromlgen/templates/classmap.jinja",
    "content": "{% if classmap is not none %}\n\n/**\n * Predict readable class name\n */\nconst char* predictLabel(float *x) {\n    return idxToLabel(predict(x));\n}\n\n/**\n * Convert class idx to readable name\n */\nconst char* idxToLabel(uint8_t classIdx) {\n    switch (classIdx) {\n        {% for idx, name in classmap.items() %}\n            case {{ idx }}:\n                return \"{{ name }}\";\n        {% endfor %}\n        default:\n            return \"Houston we have a problem\";\n    }\n}\n\n{% endif %}"
  },
  {
    "path": "micromlgen/templates/decisiontree/__init__.py",
    "content": "# here just to force pip to copy this folder\n"
  },
  {
    "path": "micromlgen/templates/decisiontree/decisiontree.jinja",
    "content": "{% extends '_skeleton.jinja' %}\n\n{% block predict %}\n    {% include 'decisiontree/tree.jinja' %}\n{% endblock %}"
  },
  {
    "path": "micromlgen/templates/decisiontree/decisiontree_regressor.jinja",
    "content": "{% extends '_skeleton.jinja' %}\n\n{% block predict %}\n    {% include 'decisiontree/tree_regressor.jinja' %}\n{% endblock %}"
  },
  {
    "path": "micromlgen/templates/decisiontree/tree.jinja",
    "content": "{% if left[i] != right[i] %}\n    if (x[{{ features[i] }}] <= {{ thresholds[i] }}) {\n        {% with i = left[i] %}\n            {% include 'decisiontree/tree.jinja' %}\n        {% endwith %}\n    }\n    else {\n        {% with i = right[i] %}\n            {% include 'decisiontree/tree.jinja' %}\n        {% endwith %}\n    }\n{% else %}\n    return {{ classes[i].argmax() }};\n{% endif %}"
  },
  {
    "path": "micromlgen/templates/decisiontree/tree_regressor.jinja",
    "content": "{% if left[i] != right[i] %}\n    if (x[{{ features[i] }}] <= {{ thresholds[i] }}) {\n        {% with i = left[i] %}\n            {% include 'decisiontree/tree_regressor.jinja' %}\n        {% endwith %}\n    }\n    else {\n        {% with i = right[i] %}\n            {% include 'decisiontree/tree_regressor.jinja' %}\n        {% endwith %}\n    }\n{% else %}\n    return {{ values[i].mean() }}f;\n{% endif %}"
  },
  {
    "path": "micromlgen/templates/dot.jinja",
    "content": "{% if platform == 'attiny' %}\n    {% set signature = 'float w[%d]' % dimension  %}\n    {% set wi = 'w[i]' %}\n    {% set preamble = '' %}\n{% else %}\n    {% set signature = '...' %}\n    {% set wi = 'va_arg(w, double)' %}\n    {% set preamble = 'va_list w;\\nva_start(w, %d);' % dimension %}\n{% endif %}\n\n/**\n * Compute dot product\n */\nfloat dot(float *x, {{ signature }}) {\n    {{ preamble }}\n    float dot = 0.0;\n\n    for (uint16_t i = 0; i < {{ dimension }}; i++) {\n        const float wi = {{ wi }};\n        dot += {{ expr }};\n    }\n\n    return dot;\n}"
  },
  {
    "path": "micromlgen/templates/gaussiannb/__init__.py",
    "content": "# here just to force pip to copy this folder"
  },
  {
    "path": "micromlgen/templates/gaussiannb/gaussiannb.jinja",
    "content": "{% extends '_skeleton.jinja' %}\n\n{% block predict %}\n    float votes[{{ classes|length }}] = { 0.0f };\n\n    {% include 'gaussiannb/vote.jinja' %}\n    {% include 'vote.jinja' %}\n{% endblock %}\n\n{% block protected %}\n    /**\n     * Compute gaussian value\n     */\n    float gauss(float *x, float *theta, float *sigma) {\n        float gauss = 0.0f;\n\n        for (uint16_t i = 0; i < {{ theta[0]|length }}; i++) {\n            gauss += log(sigma[i]);\n            gauss += abs(x[i] - theta[i]) / sigma[i];\n        }\n\n        return gauss;\n    }\n{% endblock %}"
  },
  {
    "path": "micromlgen/templates/gaussiannb/vote.jinja",
    "content": "float theta[{{ theta[0]|length }}] = { 0 };\nfloat sigma[{{ sigma[0]|length }}] = { 0 };\n\n{% for i, (t, s) in f.enumerate(f.zip(theta, sigma)) %}\n    {% for j, tj in f.enumerate(t) %}theta[{{ j }}] = {{ f.round(tj) }}; {% endfor %}\n    {% for j, sj in f.enumerate(s) %}sigma[{{ j }}] = {{ f.round(sj) }}; {% endfor %}\n    votes[{{ i }}] = {{ f.round(prior[i]) }} - gauss(x, theta, sigma);\n{% endfor %}"
  },
  {
    "path": "micromlgen/templates/linearregression/__init__.py",
    "content": "# here just to force pip to copy this folder"
  },
  {
    "path": "micromlgen/templates/linearregression/linearregression.jinja",
    "content": "{% extends '_skeleton.jinja' %}\n\n{% block predict %}\n    return dot(x, {{ f.to_array(coefs) }}) + {{ intercept }};\n{% endblock %}\n\n{% block protected %}\n    {% with expr = 'x[i] * wi' %}\n        {% include 'dot.jinja' %}\n    {% endwith %}\n{% endblock %}"
  },
  {
    "path": "micromlgen/templates/logisticregression/__init__.py",
    "content": "# here just to force pip to copy this folder"
  },
  {
    "path": "micromlgen/templates/logisticregression/logisticregression.jinja",
    "content": "{% extends '_skeleton.jinja' %}\n\n{% block predict %}\n    float votes[{{ classes|length }}] = { {% for i, x in f.enumerate(intercept) %}{% if i > 0 %},{% endif %}{{ f.round(x) }} {% endfor %} };\n\n    {% include 'logisticregression/vote.%s.jinja' % platform %}\n    {% include 'vote.jinja' %}\n{% endblock %}\n\n{% block protected %}\n    {% with dimension = weights[0]|length, expr = 'x[i] * wi' %}\n        {% include 'dot.jinja' %}\n    {% endwith %}\n{% endblock %}"
  },
  {
    "path": "micromlgen/templates/logisticregression/vote.arduino.jinja",
    "content": "{% for i, w in f.enumerate(weights) %}\n    votes[{{ i }}] += dot(x, {% for j, wj in f.enumerate(w) %} {% if j > 0 %},{% endif %} {{ f.round(wj) }} {% endfor %});\n{% endfor %}"
  },
  {
    "path": "micromlgen/templates/logisticregression/vote.attiny.jinja",
    "content": "float w[{{ weights[0]|length }}] = { 0 };\n\n{% for i, w in f.enumerate(weights) %}\n    {% for j, wj in f.enumerate(w) %}w[{{ j }}] = {{ f.round(wj) }}; {% endfor %}\n    votes[{{ i }}] += dot(x, w);\n{% endfor %}"
  },
  {
    "path": "micromlgen/templates/pca/__init__.py",
    "content": "# here just to force pip to copy this folder"
  },
  {
    "path": "micromlgen/templates/pca/pca.jinja",
    "content": "#pragma once\n#include <cstdarg>\n\nnamespace Eloquent {\n    namespace ML {\n        namespace Port {\n\n            class {{ classname }} {\n                public:\n\n                    /**\n                     * Apply dimensionality reduction\n                     * @warn Will override the source vector if no dest provided!\n                     */\n                    void transform(float *x, float *dest = NULL) {\n                        static float u[{{ arrays.components|length }}] = { 0 };\n\n                        {% for i, component in f.enumerate(arrays.components) %}\n                        u[{{ i }}] = dot(x, {% for j, cj in f.enumerate(component) %} {% if j > 0 %},{% endif %} {{ f.round(cj) }} {% endfor %});\n                        {% endfor %}\n\n                        memcpy(dest != NULL ? dest : x, u, sizeof(float) * {{ arrays.components|length }});\n                    }\n\n                protected:\n\n                    /**\n                     * Compute dot product with varargs\n                     */\n                    float dot(float *x, ...) {\n                        va_list w;\n                        va_start(w, {{ arrays.components[0]|length }});\n\n                        static float mean[] = { {% for i, m in f.enumerate(arrays.mean) %}{% if i > 0 %},{% endif %} {{ f.round(m) }} {% endfor %} };\n                        float dot = 0.0;\n\n                        for (uint16_t i = 0; i < {{ arrays.components[0]|length }}; i++) {\n                            dot += (x[i] - mean[i]) * va_arg(w, double);\n                        }\n\n                        return dot;\n                    }\n            };\n        }\n    }\n}"
  },
  {
    "path": "micromlgen/templates/principalfft/__init__.py",
    "content": "# here just to force pip to copy this folder"
  },
  {
    "path": "micromlgen/templates/principalfft/lut.jinja",
    "content": "const float {{ op }}LUT[{{ size }}][{{ fft.original_size }}] = {\n    {% for i in range(0, size) %}\n        { {% for n in range(0, fft.original_size) %} {{ math[op](2 * PI / fft.original_size * fft.idx[i] * n) }}, {% endfor %} },\n    {% endfor %}\n};"
  },
  {
    "path": "micromlgen/templates/principalfft/lut_bool.jinja",
    "content": "const bool {{ op }}LUT[{{ size }}][{{ fft.original_size }}] = {\n    {% for i in range(0, size) %}\n        { {% for n in range(0, fft.original_size) %} {{ \"true\" if math[op](2 * PI / fft.original_size * fft.idx[i] * n) > 0 else \"false\" }}, {% endfor %} },\n    {% endfor %}\n};"
  },
  {
    "path": "micromlgen/templates/principalfft/principalfft.jinja",
    "content": "void principalFFT(float *features, float *fft) {\n    // apply principal FFT (naive implementation for the top N frequencies only)\n    const int topFrequencies[] = { {{ f.to_array(fft.idx, True) }} };\n\n    {% if lookup_cos %}\n        {% with op=\"cos\" %}\n            {% include \"principalfft/lut.jinja\" %}\n        {% endwith %}\n\n        {# sin lookup is available only if cos lookup is used #}\n        {% if lookup_sin %}\n            {% with op=\"sin\" %}\n                {% include \"principalfft/lut.jinja\" %}\n            {% endwith %}\n        {% else %}\n            {% with op=\"sin\" %}\n                {% include \"principalfft/lut_bool.jinja\" %}\n            {% endwith %}\n        {% endif %}\n    {% endif %}\n\n    for (int i = 0; i < {{ size }}; i++) {\n        const int k = topFrequencies[i];\n        {% if not lookup_cos %}\n            const float harmonic = {{ 2 * PI / fft.original_size }} * k;\n        {% endif %}\n        float re = 0;\n        float im = 0;\n\n        // optimized case\n        if (k == 0) {\n            for (int n = 0; n < {{ fft.original_size }}; n++) {\n                re += features[n];\n            }\n        }\n        else {\n            for (int n = 0; n < {{ fft.original_size }}; n++) {\n                {% if lookup_cos %}\n                    const float cos_n = cosLUT[i][n];\n\n                    {% if lookup_sin %}\n                        const float sin_n = sinLUT[i][n];\n                    {% else %}\n                        const float sin_n = sinLUT[i][n] * sqrt(1 - cos_n * cos_n);\n                    {% endif %}\n                {% else %}\n                    const float harmonicN = harmonic * n;\n                    const float cos_n = cos(harmonicN);\n                    const float sin_n = sin(harmonicN);\n                {% endif %}\n\n                re += features[n] * cos_n;\n                im -= features[n] * sin_n;\n            }\n        }\n\n        fft[i] = sqrt(re * re + im * im);\n    }\n}"
  },
  {
    "path": "micromlgen/templates/randomforest/__init__.py",
    "content": "# here just to force pip to copy this folder"
  },
  {
    "path": "micromlgen/templates/randomforest/randomforest.jinja",
    "content": "{% extends '_skeleton.jinja' %}\n\n{% block predict %}\n    uint8_t votes[{{ n_classes }}] = { 0 };\n\n    {% for k, tree in f.enumerate(trees) %}\n        {% with i = 0 %}\n            // tree #{{ k + 1 }}\n            {% include 'randomforest/tree.jinja' %}\n        {% endwith %}\n    {% endfor %}\n\n    {% include 'vote.jinja' %}\n{% endblock %}"
  },
  {
    "path": "micromlgen/templates/randomforest/randomforest_regressor.jinja",
    "content": "{% extends '_skeleton.jinja' %}\n\n{% block predict %}\n    float y_pred = 0;\n\n    {% for k, tree in f.enumerate(trees) %}\n        {% with i = 0 %}\n            // tree #{{ k + 1 }}\n            {% include 'randomforest/tree_regressor.jinja' %}\n        {% endwith %}\n    {% endfor %}\n\n    return y_pred / {{ n_estimators }};\n{% endblock %}"
  },
  {
    "path": "micromlgen/templates/randomforest/tree.jinja",
    "content": "{% if tree['left'][i] != tree['right'][i] %}\n    if (x[{{ tree['features'][i] }}] <= {{ tree['thresholds'][i] }}) {\n        {% with i = tree['left'][i] %}\n            {% include 'randomforest/tree.jinja' %}\n        {% endwith %}\n    }\n    else {\n        {% with i = tree['right'][i] %}\n            {% include 'randomforest/tree.jinja' %}\n        {% endwith %}\n    }\n{% else %}\n    votes[{{ tree['classes'][i].argmax() }}] += 1;\n{% endif %}"
  },
  {
    "path": "micromlgen/templates/randomforest/tree_regressor.jinja",
    "content": "{% if tree['left'][i] != tree['right'][i] %}\n    if (x[{{ tree['features'][i] }}] <= {{ tree['thresholds'][i] }}) {\n        {% with i = tree['left'][i] %}\n            {% include 'randomforest/tree_regressor.jinja' %}\n        {% endwith %}\n    }\n    else {\n        {% with i = tree['right'][i] %}\n            {% include 'randomforest/tree_regressor.jinja' %}\n        {% endwith %}\n    }\n{% else %}\n    y_pred += {{ tree['values'][i].mean() }};\n{% endif %}"
  },
  {
    "path": "micromlgen/templates/rvm/__init__.py",
    "content": "# here just to force pip to copy this folder"
  },
  {
    "path": "micromlgen/templates/rvm/rvm.jinja",
    "content": "{% extends '_skeleton.jinja' %}\n\n{% block predict %}\n    float votes[{{ arrays.vectors|length }}] = { 0 };\n    {% for i, (rv, cf, act, b) in f.enumerate(f.zip(arrays.vectors, arrays.coefs, arrays.actives, arrays.intercepts)) %}\n        {% if rv.shape[0] == 0 %}\n            votes[{{ i }}] = {{ b }};\n        {% else %}\n            votes[{{ i }}] = (compute_kernel(x, {% for vi in rv[0] %}{% if loop.index > 1 %},{% endif %} {{ f.round(vi) }}{% endfor %}) - {{ f.round(arrays.mean[act][0]) }} ) * {{ f.round(cf[act][0] / arrays.std[act][0]) }} + {{ f.round(b) }};\n        {% endif %}\n    {% endfor %}\n\n    {% include 'vote.jinja' %}\n{% endblock %}\n\n{% block protected %}\n    {% include 'svm/kernel/%s.jinja' % platform %}\n{% endblock %}"
  },
  {
    "path": "micromlgen/templates/sefr/__init__.py",
    "content": "# here just to force pip to copy this folder"
  },
  {
    "path": "micromlgen/templates/sefr/dot.jinja",
    "content": "/**\n * Compute dot product between features vector and classifier weights\n */\nfloat dot(float *x, ...) {\n    va_list w;\n    va_start(w, {{ weights|length }});\n\n    float kernel = 0.0;\n\n    for (uint16_t i = 0; i < {{ weights|length }}; i++) {\n        kernel += x[i] * va_arg(w, double);\n    }\n\n    return kernel;\n}"
  },
  {
    "path": "micromlgen/templates/sefr/sefr.jinja",
    "content": "{% extends '_skeleton.jinja' %}\n\n{% block predict %}\n    return dot(x, {% for i, wi in f.enumerate(weights) %} {% if i > 0 %},{% endif %} {{ f.round(wi) }} {% endfor %}) <= {{ bias }} ? 0 : 1;\n{% endblock %}\n\n{% block protected %}\n    {% with expr = 'x[i] * wi' %}\n        {% include 'dot.jinja' %}\n    {% endwith %}\n{% endblock %}"
  },
  {
    "path": "micromlgen/templates/svm/__init__.py",
    "content": "# here just to force pip to copy this folder"
  },
  {
    "path": "micromlgen/templates/svm/computations/class.jinja",
    "content": "int val = votes[0];\nint idx = 0;\n\nfor (int i = 1; i < {{ sizes.classes }}; i++) {\n    if (votes[i] > val) {\n        val = votes[i];\n        idx = i;\n    }\n}\n\nreturn idx;"
  },
  {
    "path": "micromlgen/templates/svm/computations/decisions.jinja",
    "content": "{% set helpers = {'ii': 0} %}\n\n{% for i in range(0, sizes.classes) %}\n    {% for j in range(i + 1, sizes.classes) %}\n        {% set start_i = sizes.supports[:i].sum() %}\n        {% set start_j = sizes.supports[:j].sum() %}\n        decisions[{{ helpers.ii }}] = {{ f.round(arrays.intercepts[helpers.ii]) }}\n        {% for k in range(start_i, start_i + sizes.supports[i]) %}\n            {% with coef=arrays.coefs[j-1][k] %}\n                {% if coef == 1 %}\n                    + kernels[{{ k }}]\n                {% elif coef == -1 %}\n                    - kernels[{{ k }}]\n                {% elif coef != 0 %}\n                    + kernels[{{ k }}] * {{ f.round(coef) }}\n                {% endif %}\n            {% endwith %}\n        {% endfor %}\n        {% for k in range(start_j, start_j + sizes.supports[j]) %}\n            {% with coef=arrays.coefs[i][k] %}\n                {% if coef == 1 %}\n                    + kernels[{{ k }}]\n                {% elif coef == -1 %}\n                    - kernels[{{ k }}]\n                {% elif coef %}\n                    + kernels[{{ k }}] * {{ f.round(coef) }}\n                {% endif %}\n            {% endwith %}\n        {% endfor %};\n        {% if helpers.update({'ii': helpers.ii + 1}) %}{% endif %}\n    {% endfor %}\n{% endfor %}\n"
  },
  {
    "path": "micromlgen/templates/svm/computations/kernel/arduino.jinja",
    "content": "{% for i, w in f.enumerate(arrays.supports) %}\n    kernels[{{ i }}] = compute_kernel(x, {% for j, wj in f.enumerate(w) %} {% if j > 0 %},{% endif %} {{ f.round(wj) }} {% endfor %});\n{% endfor %}"
  },
  {
    "path": "micromlgen/templates/svm/computations/kernel/attiny.jinja",
    "content": "float w[{{ arrays.supports[0]|length }}] = { 0 };\n\n{% for i, w in f.enumerate(arrays.supports) %}\n    {% for j, wj in f.enumerate(w) %}w[{{ j }}] = {{ f.round(wj) }}; {% endfor %}\n    kernels[{{ i }}] = compute_kernel(x, w);\n{% endfor %}"
  },
  {
    "path": "micromlgen/templates/svm/computations/votes.jinja",
    "content": "{% set helpers = {'ii': 0} %}\n\n{% for i in range(0, sizes.classes) %}\n  {% for j in range(i + 1, sizes.classes) %}\n        votes[decisions[{{ helpers.ii }}] > 0 ? {{ i }} : {{ j }}] += 1;\n        {% if helpers.update({'ii': helpers.ii + 1}) %}{% endif %}\n   {% endfor %}\n{% endfor %}"
  },
  {
    "path": "micromlgen/templates/svm/kernel/arduino.jinja",
    "content": "/**\n * Compute kernel between feature vector and support vector.\n * Kernel type: {{ kernel['type'] }}\n */\nfloat compute_kernel(float *x, ...) {\n    va_list w;\n    va_start(w, {{ sizes.features }});\n\n    {% with %}\n        {% set wi = 'va_arg(w, double)' %}\n        {% include 'svm/kernel/kernel.jinja' %}\n    {% endwith %}\n}"
  },
  {
    "path": "micromlgen/templates/svm/kernel/attiny.jinja",
    "content": "/**\n * Compute kernel between feature vector and support vector.\n * Kernel type: {{ kernel['type'] }}\n */\nfloat compute_kernel(float *x, float w[{{ sizes.features }}]) {\n    {% with %}\n        {% set wi = 'w[i]' %}\n        {% include 'svm/kernel/kernel.jinja' %}\n    {% endwith %}\n}"
  },
  {
    "path": "micromlgen/templates/svm/kernel/kernel.jinja",
    "content": "float kernel = 0.0;\n\nfor (uint16_t i = 0; i < {{ sizes.features }}; i++) {\n    {% if kernel['type'] in ['linear', 'poly', 'sigmoid'] %}\n        kernel += x[i] * {{ wi }};\n    {% elif kernel['type'] == 'rbf' %}\n        kernel += pow(x[i] - {{ wi }}, 2);\n    {% else %}\n        #error \"UNKNOWN KERNEL {{ kernel['type'] }}\";\n    {% endif %}\n}\n\n{% if kernel['type'] == 'poly' %}\n    return pow(({{ kernel['gamma'] }} * kernel) + {{ kernel['coef0'] }}, {{ kernel['degree'] }});\n{% elif kernel['type'] == 'rbf' %}\n    return exp(-{{ kernel['gamma'] }} * kernel);\n{% elif kernel['type'] == 'sigmoid' %}\n    return sigmoid(({{ kernel['gamma'] }} * kernel) + {{ kernel['coef0'] }});\n{% else %}\n    return kernel;\n{% endif %}"
  },
  {
    "path": "micromlgen/templates/svm/svm.jinja",
    "content": "{% extends '_skeleton.jinja' %}\n\n{% block predict %}\n\n    float kernels[{{ sizes.vectors }}] = { 0 };\n    {% if sizes.classes > 1 %}\n        float decisions[{{ sizes.decisions }}] = { 0 };\n        int votes[{{ sizes.classes }}] = { 0 };\n    {% endif %}\n\n    {% include 'svm/computations/kernel/%s.jinja' % platform %}\n\n    {% if sizes.classes == 1 %}\n        float decision = {{ f.round(arrays.intercepts[0]) }} - ({% for i, coef in f.enumerate(arrays.coefs[0]) %} + kernels[{{ i }}] {% if coef != 1 %}* {{ f.round(coef) }}{% endif %} {% endfor %});\n\n        return decision > 0 ? 0 : 1;\n\n    {% elif sizes.classes == 2 %}\n        float decision = {{ f.round(arrays.intercepts[0]) }};\n\n        decision = decision - ({% for i in range(0, sizes.supports[0]) %} + kernels[{{ i }}] * {{ f.round(arrays.coefs[0][i]) }} {% endfor %});\n        decision = decision - ({% for i in range(sizes.supports[0], sizes.supports[0] + sizes.supports[1]) %} + kernels[{{ i }}] * {{ f.round(arrays.coefs[0][i]) }} {% endfor %});\n\n        return decision > 0 ? 0 : 1;\n    {% else %}\n        {% include 'svm/computations/decisions.jinja' %}\n        {% include 'svm/computations/votes.jinja' %}\n        {% include 'svm/computations/class.jinja' %}\n    {% endif %}\n{% endblock %}\n\n{% block protected %}\n    {% include 'svm/kernel/%s.jinja' % platform %}\n{% endblock %}"
  },
  {
    "path": "micromlgen/templates/testset.jinja",
    "content": "#pragma once\n\nnamespace Eloquent {\n    namespace ML {\n        namespace Test {\n\n            /**\n             * A tailor made test set\n             */\n            class {{ classname }} {\n                public:\n                    {{ classname }}() :\n                        _x{  {% for x in X %}\n                                { {% for xi in x %} {% if loop.index > 1 %}, {% endif %} {{ f.round(xi) }} {% endfor %} },\n                            {% endfor %}\n                        },\n                        _y{  {% for yi in y %} {% if loop.index > 1 %}, {% endif %} {{ f.round(yi) }} {% endfor %}  },\n                        _tp(0),\n                        _tn(0),\n                        _fp(0),\n                        _fn(0)\n                    {}\n\n                    template<class Classifier>\n                    void test(Classifier clf) {\n                        for (uint16_t i = 0; i < {{ X|length }}; i++) {\n                            int predicted = clf.predict(_x[i]);\n                            int actual = _y[i];\n\n                            if (predicted > 0 && predicted == actual)       _tp++;\n                            else if (predicted > 0 && predicted != actual)  _fp++;\n                            else if (predicted <= 0 && predicted == actual) _tn++;\n                            else _fn++;\n                        }\n                    }\n\n                    String dump() {\n                      return String(\"Support:\\t\")\n                            + support()\n                            + \"\\nTP:\\t\"\n                            + _tp\n                            + \"\\nTN:\\t\"\n                            + _tn\n                            + \"\\nFP:\\t\"\n                            + _fp\n                            + \"\\nFN:\\t\"\n                            + _fn\n                            + \"\\nAccuracy:\\t\"\n                            + accuracy()\n                            + \"\\nPrecision:\\t\"\n                            + precision()\n                            + \"\\nRecall:\\t\"\n                            + recall()\n                            + \"\\nSpecificity:\\t\"\n                            + specificity()\n                          ;\n                    }\n\n                    float accuracy() {\n                        return (1.0f * _tp + _tn) / support();\n                    }\n\n                    float precision() {\n                        return (1.0f * _tp) / (_tp + _fp);\n                    }\n                    float recall() {\n                        return (1.0f * _tp) / (_tp + _fn);\n                    }\n\n                    float specificity() {\n                      return (1.0f * _tn) / (_tn + _fp);\n                    }\n\n                    uint16_t support() {\n                        return _tp + _tn + _fp + _fn;\n                    }\n\n                protected:\n                    float _x[{{ X|length }}][{{ X[0]|length }}];\n                    int _y[{{ X|length }}];\n                    uint16_t _tp, _fp, _tn, _fn;\n            };\n        }\n    }\n}"
  },
  {
    "path": "micromlgen/templates/trainset.jinja",
    "content": "#pragma once\n\nnamespace Eloquent {\n    namespace ML {\n        namespace Train {\n\n            /**\n             * A tailor made training set\n             */\n            class {{ classname }} {\n                public:\n                    {{ classname }}() :\n                        _x{  {% for x in X %}\n                                { {% for xi in x %} {% if loop.index > 1 %}, {% endif %} {{ f.round(xi) }} {% endfor %} },\n                            {% endfor %}\n                        },\n                        _y{  {% for yi in y %} {% if loop.index > 1 %}, {% endif %} {{ f.round(yi) }} {% endfor %}  }\n                    {}\n\n                    template<class Classifier>\n                    void fit(Classifier *clf) {\n                        clf->fit(_x, _y, {{ X|length }});\n                    }\n\n                protected:\n                    float _x[{{ X|length }}][{{ X[0]|length }}];\n                    int _y[{{ X|length }}];\n            };\n        }\n    }\n}"
  },
  {
    "path": "micromlgen/templates/vote.jinja",
    "content": "// return argmax of votes\nuint8_t classIdx = 0;\nfloat maxVotes = votes[0];\n\nfor (uint8_t i = 1; i < {{ n_classes }}; i++) {\n    if (votes[i] > maxVotes) {\n        classIdx = i;\n        maxVotes = votes[i];\n    }\n}\n\nreturn classIdx;"
  },
  {
    "path": "micromlgen/templates/wifiindoorpositioning/__init__.py",
    "content": "# here just to force pip to copy this folder"
  },
  {
    "path": "micromlgen/templates/wifiindoorpositioning/wifiindoorpositioning.jinja",
    "content": "#pragma once\n\nnamespace Eloquent {\n    namespace Projects {\n        class WifiIndoorPositioning {\n            public:\n                float features[{{ X[0]|length }}] = {0};\n\n                /**\n                 * Get feature vector\n                 */\n                float* scan() {\n                    uint8_t numNetworks = WiFi.scanNetworks();\n\n                    for (uint8_t i = 0; i < {{ X[0]|length }}; i++) {\n                        features[i] = 0;\n                    }\n\n                    for (uint8_t i = 0; i < numNetworks; i++) {\n                        int featureIdx = ssidToFeatureIdx(WiFi.SSID(i));\n\n                        if (featureIdx >= 0) {\n                            features[featureIdx] = WiFi.RSSI(i);\n                        }\n                    }\n\n                    return features;\n                }\n\n            protected:\n                /**\n                 * Convert SSID to featureIdx\n                 */\n                int ssidToFeatureIdx(String ssid) {\n                    {% for network, idx in networkmap.items() %}\n                    if (ssid.equals(\"{{ network }}\"))\n                        return {{ idx }};\n                    {% endfor %}\n\n                    return -1;\n                }\n        };\n    }\n}"
  },
  {
    "path": "micromlgen/templates/xgboost/__init__.py",
    "content": "# here just to force pip to copy this folder"
  },
  {
    "path": "micromlgen/templates/xgboost/tree.jinja",
    "content": "{% if tree['left'][i] != tree['right'][i] %}\n    if (x[{{ tree['features'][i] }}] <= {{ tree['thresholds'][i] }}) {\n        {% with i = tree['left'][i] %}\n            {% include 'xgboost/tree.jinja' %}\n        {% endwith %}\n    }\n    else {\n        {% with i = tree['right'][i] %}\n            {% include 'xgboost/tree.jinja' %}\n        {% endwith %}\n    }\n{% else %}\n    votes[{{ class_idx }}] += {{ tree['thresholds'][i] }};\n{% endif %}"
  },
  {
    "path": "micromlgen/templates/xgboost/xgboost.jinja",
    "content": "{% extends '_skeleton.jinja' %}\n\n{% block predict %}\n    float votes[{{ n_classes }}] = { 0.0f };\n\n    {% for k, tree in f.enumerate(trees) %}\n        {% with i = 0, class_idx = k % n_classes %}\n            // tree #{{ k + 1 }}\n            {% include 'xgboost/tree.jinja' %}\n        {% endwith %}\n    {% endfor %}\n\n    {% include 'vote.jinja' %}\n{% endblock %}"
  },
  {
    "path": "micromlgen/utils.py",
    "content": "import os\nimport re\nfrom math import sin, cos\nfrom collections.abc import Iterable\nfrom inspect import getmro\nfrom jinja2 import FileSystemLoader, Environment\n\n\ndef check_type(instance, *classes):\n    \"\"\"Check if object is instance of given class\"\"\"\n    for klass in classes:\n        if type(instance).__name__ == klass:\n            return True\n        for T in getmro(type(instance)):\n            if T.__name__ == klass:\n                return True\n    return False\n\n\ndef prettify(code):\n    \"\"\"A super simple code prettifier\"\"\"\n    pretty = []\n    indent = 0\n    for line in code.split('\\n'):\n        line = line.strip()\n        # skip empty lines\n        if len(line) == 0:\n            continue\n        # lower indentation on closing braces\n        if line[-1] == '}' or line == '};' or line == 'protected:':\n            indent -= 1\n        pretty.append(('    ' * indent) + line)\n        # increase indentation on opening braces\n        if line[-1] == '{' or line == 'public:' or line == 'protected:':\n            indent += 1\n    pretty = '\\n'.join(pretty)\n    # leave empty line before {return, for, if}\n    pretty = re.sub(r'([;])\\n(\\s*?)(for|return|if) ', lambda m: '%s\\n\\n%s%s ' % m.groups(), pretty)\n    # leave empty line after closing braces\n    pretty = re.sub(r'}\\n', '}\\n\\n', pretty)\n    # strip empty lines between closing braces (2 times)\n    pretty = re.sub(r'\\}\\n\\n(\\s*?)\\}', lambda m: '}\\n%s}' % m.groups(), pretty)\n    pretty = re.sub(r'\\}\\n\\n(\\s*?)\\}', lambda m: '}\\n%s}' % m.groups(), pretty)\n    # remove \",\" before \"}\"\n    pretty = re.sub(r',\\s*\\}', '}', pretty)\n    return pretty\n\n\ndef jinja(template_file, data, defaults=None, **kwargs):\n    \"\"\"Render Jinja template\"\"\"\n    dir_path = os.path.dirname(os.path.realpath(__file__))\n    loader = FileSystemLoader(dir_path + '/templates')\n    template = Environment(loader=loader).get_template(template_file)\n    data = {k: v for k, v in data.items() if v is not None}\n    kwargs = {k: v for k, v in kwargs.items() if v is not None}\n    precision = data.get('precision', 12) or 12\n    precision_fmt = '%.' + str(precision) + 'f'\n    if defaults is None:\n        defaults = {}\n    defaults.setdefault('platform', 'arduino')\n    defaults.setdefault('classmap', None)\n    defaults.update({\n        'f': {\n            'enumerate': enumerate,\n            'round': lambda x: round(x, precision),\n            'zip': zip,\n            'signed': lambda x: '' if x == 0 else '+' + str(x) if x >= 0 else x,\n            'to_array': lambda x, as_int=False: ', '.join([precision_fmt % xx if not as_int else str(xx) for xx in x])\n        },\n        'math': {\n            'cos': cos,\n            'sin': sin\n        }\n    })\n    data = {\n        **defaults,\n        **kwargs,\n        **data\n    }\n    code = template.render(data)\n    return prettify(code)\n\n\ndef port_trainset(X, y, classname='TrainSet'):\n    return jinja('trainset.jinja', locals())\n\n\ndef port_testset(X, y, classname='TestSet'):\n    return jinja('testset.jinja', locals())\n\n\ndef port_array(arr, precision=9):\n    \"\"\"\n    Convert array to C\n    :param arr: list|ndarray\n    :param precision: int how many decimal digits to print\n    :return: str C-array contents\n    \"\"\"\n    if not isinstance(arr, Iterable):\n        fmt = '%%.%df' % precision\n        return fmt % arr\n\n    return '{%s}' % (', '.join([port_array(x, precision) for x in arr]))"
  },
  {
    "path": "micromlgen/wifiindoorpositioning.py",
    "content": "import json\nimport numpy as np\nfrom micromlgen.utils import jinja\n\n\ndef parse_samples(samples, parser):\n    for line in samples.split('\\n'):\n        if '{' in line and '}' in line:\n            data = json.loads(line)\n            info = {k: v for k, v in data.items() if k.startswith('__')}\n            networks = {k: v for k, v in data.items() if not k.startswith('__')}\n            yield parser(info, networks)\n\n\ndef get_classmap(samples):\n    \"\"\"Get {location: classIdx} mapping\"\"\"\n\n    def parser(info, networks):\n        return info['__location']\n\n    locations = list(parse_samples(samples, parser))\n    return {location: i for i, location in enumerate(sorted(set(locations)))}\n\n\ndef get_networkmap(samples):\n    \"\"\"Get {network: featureIdx} mapping\"\"\"\n\n    def parser(info, networks):\n        return networks.keys()\n\n    networks = [list(x) for x in parse_samples(samples, parser)]\n    networks = [network for sub in networks for network in sub]\n    return {network: i for i, network in enumerate(sorted(set(networks)))}\n\n\ndef get_x(samples, networkmap):\n    \"\"\"Get features array\"\"\"\n\n    def parser(info, networks):\n        x = [0] * len(networkmap)\n        for network, rssi in networks.items():\n            x[networkmap.get(network)] = rssi\n        return x\n\n    return np.asarray(list(parse_samples(samples, parser)), dtype=np.int8)\n\n\ndef get_y(samples, classmap):\n    \"\"\"Get locationIdx array\"\"\"\n\n    def parser(info, networks):\n        location = info['__location']\n        assert location in classmap, 'Unknown location %s' % location\n        return classmap[location]\n\n    return np.asarray(list(parse_samples(samples, parser)))\n\n\ndef port_wifi_indoor_positioning(samples):\n    classmap = get_classmap(samples)\n    networkmap = get_networkmap(samples)\n    X = get_x(samples, networkmap)\n    y = get_y(samples, classmap)\n    # classmap is flipped wrt the format `port` expects: flip it\n    classmap = {v: k for k, v in classmap.items()}\n    return X, y, classmap, jinja('wifiindoorpositioning/wifiindoorpositioning.jinja', {\n        'X': X,\n        'networkmap': networkmap\n    })\n"
  },
  {
    "path": "micromlgen/xgboost.py",
    "content": "from micromlgen.utils import jinja, check_type\nfrom tempfile import NamedTemporaryFile\nimport json\n\n\ndef format_tree(tree):\n    \"\"\"\n    Format xgboost tree like a sklearn DecisionTree\n    :param tree:\n    :return:\n    \"\"\"\n    split_indices = tree['split_indices']\n    split_conditions = tree['split_conditions']\n    left_children = tree['left_children']\n    right_children = tree['right_children']\n    return {\n        'left': left_children,\n        'right': right_children,\n        'features': split_indices,\n        'thresholds': split_conditions\n    }\n\n\ndef is_xgboost(clf):\n    \"\"\"\n    Test if classifier can be ported\n    \"\"\"\n    return check_type(clf, 'XGBClassifier')\n\n\ndef port_xgboost(clf, tmp_file=None, **kwargs):\n    \"\"\"\n    Port a XGBoost classifier\n    @updated 1.1.28\n    :param clf:\n    :param tmp_file: if not None, use the given file as temporary export destination\n    \"\"\"\n    if tmp_file is None:\n        with NamedTemporaryFile('w+', suffix='.json', encoding='utf-8') as tmp:\n            clf.save_model(tmp.name)\n            tmp.seek(0)\n            decoded = json.load(tmp)\n    else:\n        clf.save_model(tmp_file)\n\n        with open(tmp_file, encoding='utf-8') as file:\n            decoded = json.load(file)\n\n    trees = [format_tree(tree) for tree in decoded['learner']['gradient_booster']['model']['trees']]\n\n    return jinja('xgboost/xgboost.jinja', {\n        'n_classes': int(decoded['learner']['learner_model_param']['num_class']),\n        'trees': trees,\n    }, {\n        'classname': 'XGBClassifier'\n    }, **kwargs)"
  },
  {
    "path": "publish",
    "content": "#!/bin/bash\n\ntemplates=$(python -c 'import glob; print([f.replace(\"micromlgen/\", \"\") for f in glob.glob(\"micromlgen/templates/**/*\", recursive=True)])')\ntemplates=$(printf '%s\\n' \"$templates\" | sed -e 's/[\\/&]/\\\\&/g')\nrm dist/micromlgen-${1}.tar.gz\ncp setup_template.py setup.py\nsed -i \"\" \"s/PACKAGE_DATA/$templates/\" setup.py\nsed -i \"\" \"s/VERSION/${1}/\" setup.py\npython setup.py sdist\ntwine upload -u eloquentarduino dist/micromlgen-${1}.tar.gz\ngit add .\ngit commit -m \"bump dist to ${1}\"\ngit push origin master -f"
  },
  {
    "path": "setup.cfg",
    "content": "[metadata]\ndescription-file = README.md"
  },
  {
    "path": "setup.py",
    "content": "from distutils.core import setup\n\n\nsetup(\n  name = 'micromlgen',\n  packages = ['micromlgen'],\n  version = '1.1.28',\n  license='MIT',\n  description = 'Generate C code for microcontrollers from Python\\'s sklearn classifiers',\n  author = 'Simone Salerno',\n  author_email = 'eloquentarduino@gmail.com',\n  url = 'https://github.com/eloquentarduino/micromlgen',\n  download_url = 'https://github.com/eloquentarduino/micromlgen/blob/master/dist/micromlgen-1.1.28.tar.gz?raw=true',\n  keywords = [\n    'ML',\n    'microcontrollers',\n    'sklearn',\n    'machine learning'\n  ],\n  install_requires=[\n    'jinja2',\n  ],\n  package_data={\n    'micromlgen': ['templates/pca', 'templates/linearregression', 'templates/wifiindoorpositioning', 'templates/vote.jinja', 'templates/dot.jinja', 'templates/gaussiannb', 'templates/rvm', 'templates/xgboost', 'templates/decisiontree', 'templates/randomforest', 'templates/__init__.py', 'templates/__pycache__', 'templates/_skeleton.jinja', 'templates/sefr', 'templates/svm', 'templates/logisticregression', 'templates/principalfft', 'templates/classmap.jinja', 'templates/trainset.jinja', 'templates/testset.jinja', 'templates/pca/__init__.py', 'templates/pca/pca.jinja', 'templates/linearregression/__init__.py', 'templates/linearregression/linearregression.jinja', 'templates/wifiindoorpositioning/__init__.py', 'templates/wifiindoorpositioning/wifiindoorpositioning.jinja', 'templates/gaussiannb/vote.jinja', 'templates/gaussiannb/gaussiannb.jinja', 'templates/gaussiannb/__init__.py', 'templates/rvm/__init__.py', 'templates/rvm/rvm.jinja', 'templates/xgboost/tree.jinja', 'templates/xgboost/__init__.py', 'templates/xgboost/xgboost.jinja', 'templates/decisiontree/tree_regressor.jinja', 'templates/decisiontree/tree.jinja', 'templates/decisiontree/decisiontree.jinja', 'templates/decisiontree/__init__.py', 'templates/decisiontree/decisiontree_regressor.jinja', 'templates/randomforest/randomforest_regressor.jinja', 'templates/randomforest/randomforest.jinja', 'templates/randomforest/tree_regressor.jinja', 'templates/randomforest/tree.jinja', 'templates/randomforest/__init__.py', 'templates/__pycache__/__init__.cpython-37.pyc', 'templates/sefr/sefr.jinja', 'templates/sefr/dot.jinja', 'templates/sefr/__init__.py', 'templates/svm/__init__.py', 'templates/svm/computations', 'templates/svm/svm.jinja', 'templates/svm/kernel', 'templates/svm/computations/class.jinja', 'templates/svm/computations/decisions.jinja', 'templates/svm/computations/votes.jinja', 'templates/svm/computations/kernel', 'templates/svm/computations/kernel/attiny.jinja', 'templates/svm/computations/kernel/arduino.jinja', 'templates/svm/kernel/attiny.jinja', 'templates/svm/kernel/arduino.jinja', 'templates/svm/kernel/kernel.jinja', 'templates/logisticregression/__init__.py', 'templates/logisticregression/vote.arduino.jinja', 'templates/logisticregression/logisticregression.jinja', 'templates/logisticregression/vote.attiny.jinja', 'templates/principalfft/lut.jinja', 'templates/principalfft/__init__.py', 'templates/principalfft/principalfft.jinja', 'templates/principalfft/lut_bool.jinja']\n  },\n  classifiers=[\n    'Development Status :: 5 - Production/Stable',\n    'Intended Audience :: Developers',\n    'Topic :: Software Development :: Code Generators',\n    'License :: OSI Approved :: MIT License',\n    'Programming Language :: Python :: 3',\n    'Programming Language :: Python :: 3.4',\n    'Programming Language :: Python :: 3.5',\n    'Programming Language :: Python :: 3.6',\n  ],\n)\n"
  },
  {
    "path": "setup_template.py",
    "content": "from distutils.core import setup\n\n\nsetup(\n  name = 'micromlgen',\n  packages = ['micromlgen'],\n  version = 'VERSION',\n  license='MIT',\n  description = 'Generate C code for microcontrollers from Python\\'s sklearn classifiers',\n  author = 'Simone Salerno',\n  author_email = 'eloquentarduino@gmail.com',\n  url = 'https://github.com/eloquentarduino/micromlgen',\n  download_url = 'https://github.com/eloquentarduino/micromlgen/blob/master/dist/micromlgen-VERSION.tar.gz?raw=true',\n  keywords = [\n    'ML',\n    'microcontrollers',\n    'sklearn',\n    'machine learning'\n  ],\n  install_requires=[\n    'jinja2',\n  ],\n  package_data={\n    'micromlgen': PACKAGE_DATA\n  },\n  classifiers=[\n    'Development Status :: 5 - Production/Stable',\n    'Intended Audience :: Developers',\n    'Topic :: Software Development :: Code Generators',\n    'License :: OSI Approved :: MIT License',\n    'Programming Language :: Python :: 3',\n    'Programming Language :: Python :: 3.4',\n    'Programming Language :: Python :: 3.5',\n    'Programming Language :: Python :: 3.6',\n  ],\n)\n"
  }
]