diff --git a/.gitea/workflows/embed.yaml b/.gitea/workflows/embed.yaml new file mode 100644 index 0000000..2a0d240 --- /dev/null +++ b/.gitea/workflows/embed.yaml @@ -0,0 +1,37 @@ +name: Run VectorLoader Script + +on: + push: + branches: + - main + +jobs: + Explore-Gitea-Actions: + runs-on: ubuntu-latest + container: catthehacker/ubuntu:act-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v3 + with: + python-version: '3.11.7' + + - name: Clone additional repository + run: | + git config --global credential.helper cache + git clone https://fabel:${{ secrets.CICD }}@gitea.fabelous.app/fabel/VectorLoader.git + + - name: Install dependencies + run: | + cd VectorLoader + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run vectorizing + env: + VECTORDB_TOKEN: ${{ secrets.VECTORDB_TOKEN }} + run: | + cd VectorLoader + python -m src.run --full diff --git a/.gitea/workflows/test.yaml b/.gitea/workflows/test.yaml new file mode 100644 index 0000000..4e8d7b4 --- /dev/null +++ b/.gitea/workflows/test.yaml @@ -0,0 +1,36 @@ +name: Gitea Actions For Fabelous-Math +run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀 +on: [push] + +jobs: + Explore-Gitea-Actions: + runs-on: ubuntu-latest + container: catthehacker/ubuntu:act-latest + steps: + - name: Check out repository code + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.11.7' + + - name: Cache pip and model + uses: actions/cache@v3 + with: + path: | + ~/.cache/pip + ./fabel + key: ${{ runner.os }}-pip-model-${{ hashFiles('requirements-dev.txt')}} + + restore-keys: | + ${{ runner.os }}-pip-model- + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + - name: Run tests + run: | + pip install -e . + pytest tests \ No newline at end of file diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..774906b --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,19 @@ +{ + "configurations": [ + { + "name": "Win32", + "includePath": [ + "${workspaceFolder}/**", + "C:/Users/Falko/AppData/Local/Programs/Python/Python311/include", + "C:/Users/Falko/AppData/Local/Programs/Python/Python311/Lib/site-packages/pybind11/include" + ], + "defines": [], + "windowsSdkVersion": "10.0.19041.0", + "compilerPath": "cl.exe", + "cStandard": "c17", + "cppStandard": "c++17", + "intelliSenseMode": "windows-msvc-x64" + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..5dc5c07 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,75 @@ +{ + "files.associations": { + "*.py": "python", + "algorithm": "cpp", + "array": "cpp", + "atomic": "cpp", + "bit": "cpp", + "cctype": "cpp", + "charconv": "cpp", + "chrono": "cpp", + "clocale": "cpp", + "cmath": "cpp", + "compare": "cpp", + "concepts": "cpp", + "cstddef": "cpp", + "cstdint": "cpp", + "cstdio": "cpp", + "cstdlib": "cpp", + "cstring": "cpp", + "ctime": "cpp", + "cwchar": "cpp", + "exception": "cpp", + "filesystem": "cpp", + "format": "cpp", + "forward_list": "cpp", + "functional": "cpp", + "initializer_list": "cpp", + "iomanip": "cpp", + "ios": "cpp", + "iosfwd": "cpp", + "istream": "cpp", + "iterator": "cpp", + "limits": "cpp", + "list": "cpp", + "locale": "cpp", + "memory": "cpp", + "mutex": "cpp", + "new": "cpp", + "optional": "cpp", + "ostream": "cpp", + "ratio": "cpp", + "sstream": "cpp", + "stdexcept": "cpp", + "stop_token": "cpp", + "streambuf": "cpp", + "string": "cpp", + "system_error": "cpp", + "thread": "cpp", + "tuple": "cpp", + "type_traits": "cpp", + "typeindex": "cpp", + "typeinfo": "cpp", + "unordered_map": "cpp", + "unordered_set": "cpp", + "utility": "cpp", + "vector": "cpp", + "xfacet": "cpp", + "xhash": "cpp", + "xiosbase": "cpp", + "xlocale": "cpp", + "xlocbuf": "cpp", + "xlocinfo": "cpp", + "xlocmes": "cpp", + "xlocmon": "cpp", + "xlocnum": "cpp", + "xloctime": "cpp", + "xmemory": "cpp", + "xstring": "cpp", + "xtr1common": "cpp", + "xutility": "cpp", + "fstream": "cpp", + "iostream": "cpp", + "codecvt": "cpp" + } +} \ No newline at end of file diff --git a/README.md b/README.md index 5ba9681..f7e1609 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,72 @@ -# fabelous-math +# Fabelous Math -Python addtional math libary \ No newline at end of file +Fabelous Math is a simple library designed to provide basic mathematical functions, saving you the trouble of writing these common utilities repeatedly. This library includes essential functions like checking if a number is even or odd. + +## Installation + +You can easily install `fabelous-math` using pip: + +```sh +pip install https://gitea.fabelous.app/Fabel/fabelous-math.git +``` + +## Usage + +### Python + +To use the functions provided by Fabelous Math in your Python code, you can import them as follows: + +```python +from fabelous_math import is_even, is_odd + +# Example usage: +number = 42 +print(f"Is {number} even? {is_even(number)}") +print(f"Is {number} odd? {is_odd(number)}") +``` + +## Functions + +### `is_even` + +Checks if a given number is even. + +- **Python:** + ```python + def is_even(number: int) -> bool: + pass + ``` + +- **C++:** + ```cpp + namespace simple_functions { + bool is_even(long long number); + } + ``` + +### `is_odd` + +Checks if a given number is odd. + +- **Python:** + ```python + def is_odd(number: int) -> bool: + pass + ``` + +- **C++:** + ```cpp + namespace simple_functions { + bool is_odd(long long number); + } + ``` +## Performance Comparison + +To understand the performance of `fabelous-math` functions, I conducted a series of tests comparing my methods with traditional modulo operations. Below are the results: + +### Low Numbers Performance: +![Low Numbers Performance](docs/low_numbers_comparison.png) + + +### High Numbers Performance: +![High Numbers Performance](docs/high_numbers_comparison.png) diff --git a/docs/high_numbers_comparison.png b/docs/high_numbers_comparison.png new file mode 100644 index 0000000..3fb7d96 Binary files /dev/null and b/docs/high_numbers_comparison.png differ diff --git a/docs/low_numbers_comparison.png b/docs/low_numbers_comparison.png new file mode 100644 index 0000000..55729bd Binary files /dev/null and b/docs/low_numbers_comparison.png differ diff --git a/example.py b/example.py new file mode 100644 index 0000000..c9b0ffc --- /dev/null +++ b/example.py @@ -0,0 +1,4 @@ +from fabelous_math import is_even, is_odd + +print(is_even(5)) +print(is_odd(19)) \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..91c3f0e --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,12 @@ +[build-system] +requires = ["setuptools>=42", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "fabelous_math" +version = "0.0.1" +description = "Math functions written in C++ for faster code" +authors = [ + {name = "Falko Habel", email = "falko.habel@fabelous.app"} +] +requires-python = ">=3.7" diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..5ee6477 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +testpaths = tests diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..0002b62 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,2 @@ +pytest +pytest-cpp \ No newline at end of file diff --git a/result.py b/result.py new file mode 100644 index 0000000..3dedc1a --- /dev/null +++ b/result.py @@ -0,0 +1,138 @@ +import timeit +import random +import statistics +import matplotlib.pyplot as plt +import numpy as np +from fabelous_math import is_even, is_odd + +def generate_test_numbers(count: int, min_val: int, max_val: int): + return random.sample(range(min_val, max_val), count) + +def run_benchmark(numbers, func, iterations=100): + times = [] + for _ in range(iterations): + start_time = timeit.default_timer() + for num in numbers: + func(num) + end_time = timeit.default_timer() + times.append(end_time - start_time) + return times + +def create_visualization(results, title): + # Prepare data + methods = list(results.keys()) + means = [statistics.mean(times) * 1000 for times in results.values()] # Convert to milliseconds + stds = [statistics.stdev(times) * 1000 for times in results.values()] # Convert to milliseconds + + # Create figure with two subplots + fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10), height_ratios=[2, 1]) + fig.suptitle(title, fontsize=14) + plt.subplots_adjust(top=0.9) # Adjust spacing for title + + # Bar plot + x = np.arange(len(methods)) + width = 0.35 + bars = ax1.bar(x, means, width, yerr=stds, capsize=5) + + # Customize bar plot + ax1.set_ylabel('Time (milliseconds)') + ax1.set_xticks(x) + ax1.set_xticklabels(methods, rotation=0) + ax1.grid(True, axis='y', linestyle='--', alpha=0.7) + + # Add value labels on bars + for bar in bars: + height = bar.get_height() + ax1.text(bar.get_x() + bar.get_width()/2., height, + f'{height:.3f}ms', + ha='center', va='bottom') + + # Create table + cell_text = [] + for method, times in results.items(): + mean_time = statistics.mean(times) * 1000 + std_dev = statistics.stdev(times) * 1000 + min_time = min(times) * 1000 + max_time = max(times) * 1000 + + cell_text.append([ + method, + f"{mean_time:.3f}", + f"{std_dev:.3f}", + f"{min_time:.3f}", + f"{max_time:.3f}" + ]) + + # Add table + ax2.axis('tight') + ax2.axis('off') + columns = ['Method', 'Mean (ms)', 'Std Dev (ms)', 'Min (ms)', 'Max (ms)'] + table = ax2.table(cellText=cell_text, + colLabels=columns, + loc='center', + cellLoc='center') + + # Adjust table appearance + table.auto_set_font_size(False) + table.set_fontsize(9) + table.scale(1.2, 1.5) + + plt.tight_layout() + return plt + +def main(): + # Test parameters + SAMPLE_SIZE = 5000 + LOW_RANGE = (1, 10000000) + HIGH_RANGE = (1000000000000, 1000000010000000) + ITERATIONS = 100 + + print(f"Running benchmarks with {SAMPLE_SIZE} numbers, {ITERATIONS} iterations each...") + + # Generate test numbers + low_numbers = generate_test_numbers(SAMPLE_SIZE, *LOW_RANGE) + high_numbers = generate_test_numbers(SAMPLE_SIZE, *HIGH_RANGE) + + # Run benchmarks for low numbers + print("\nTesting low numbers...") + low_results = { + 'Fabelous Even': run_benchmark(low_numbers, is_even, ITERATIONS), + 'Fabelous Odd': run_benchmark(low_numbers, is_odd, ITERATIONS), + 'Modulo Even': run_benchmark(low_numbers, lambda x: x % 2 == 0, ITERATIONS), + 'Modulo Odd': run_benchmark(low_numbers, lambda x: x % 2 == 1, ITERATIONS) + } + + # Run benchmarks for high numbers + print("Testing high numbers...") + high_results = { + 'Fabelous Even': run_benchmark(high_numbers, is_even, ITERATIONS), + 'Fabelous Odd': run_benchmark(high_numbers, is_odd, ITERATIONS), + 'Modulo Even': run_benchmark(high_numbers, lambda x: x % 2 == 0, ITERATIONS), + 'Modulo Odd': run_benchmark(high_numbers, lambda x: x % 2 == 1, ITERATIONS) + } + + # Create and save visualizations + print("\nGenerating visualizations...") + plt_low = create_visualization(low_results, 'Performance Comparison - Low Numbers') + plt_low.savefig('low_numbers_comparison.png') + plt_low.show() + + plt_high = create_visualization(high_results, 'Performance Comparison - High Numbers') + plt_high.savefig('high_numbers_comparison.png') + plt_high.show() + + # Print summary + print("\nSummary of Findings:") + print("-------------------") + print("1. Low Numbers Performance:") + for method, times in low_results.items(): + mean_time = statistics.mean(times) * 1000 + print(f" - {method}: {mean_time:.3f}ms average") + + print("\n2. High Numbers Performance:") + for method, times in high_results.items(): + mean_time = statistics.mean(times) * 1000 + print(f" - {method}: {mean_time:.3f}ms average") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c59254f --- /dev/null +++ b/setup.py @@ -0,0 +1,30 @@ +from setuptools import setup, Extension +import platform + +extra_compile_args = [] +extra_link_args = [] + +# Set C++17 flag based on the compiler +if platform.system() == "Windows": + extra_compile_args.append('/std:c++17') +else: + extra_compile_args.append('-std=c++17') + +module = Extension( + 'fabelous_math.simple_functions', + sources=[ + 'src/fabelous_math/cpp/functions/simple_functions.cpp', + 'src/fabelous_math/cpp/functions/bindings.cpp' + ], + include_dirs=['src/fabelous_math/include'], + extra_compile_args=extra_compile_args, + extra_link_args=extra_link_args, +) + +setup( + name='fabelous_math', + description='Math functions written in C++ for faster code', + ext_modules=[module], + author="Falko Habel", + author_email="falko.habel@fabelous.app" +) \ No newline at end of file diff --git a/src/fabelous_math/__init__.py b/src/fabelous_math/__init__.py new file mode 100644 index 0000000..40c1e47 --- /dev/null +++ b/src/fabelous_math/__init__.py @@ -0,0 +1,3 @@ +from fabelous_math.simple_functions import is_even, is_odd + +__all__ = ["is_even", "is_odd"] \ No newline at end of file diff --git a/src/fabelous_math/cpp/functions/bindings.cpp b/src/fabelous_math/cpp/functions/bindings.cpp new file mode 100644 index 0000000..aef16dc --- /dev/null +++ b/src/fabelous_math/cpp/functions/bindings.cpp @@ -0,0 +1,36 @@ +#include +#include "simple_functions.hpp" + +static PyObject* is_even_wrapper(PyObject* self, PyObject* args) { + long long number; + if (!PyArg_ParseTuple(args, "L", &number)) { + return NULL; + } + return PyBool_FromLong(simple_functions::is_even(number)); +} + +static PyObject* is_odd_wrapper(PyObject* self, PyObject* args) { + long long number; + if (!PyArg_ParseTuple(args, "L", &number)) { + return NULL; + } + return PyBool_FromLong(simple_functions::is_odd(number)); +} + +static PyMethodDef NumberUtilsMethods[] = { + {"is_even", is_even_wrapper, METH_VARARGS, "Check if a number is even"}, + {"is_odd", is_odd_wrapper, METH_VARARGS, "Check if a number is odd"}, + {NULL, NULL, 0, NULL} +}; + +static struct PyModuleDef number_utils_module = { + PyModuleDef_HEAD_INIT, + "simple_functions", + "Module for checking if numbers are even or odd", + -1, + NumberUtilsMethods +}; + +PyMODINIT_FUNC PyInit_simple_functions(void) { + return PyModule_Create(&number_utils_module); +} \ No newline at end of file diff --git a/src/fabelous_math/cpp/functions/simple_functions.cpp b/src/fabelous_math/cpp/functions/simple_functions.cpp new file mode 100644 index 0000000..4d5ecab --- /dev/null +++ b/src/fabelous_math/cpp/functions/simple_functions.cpp @@ -0,0 +1,11 @@ +#include "simple_functions.hpp" + +namespace simple_functions { + bool is_even(long long number) { + return (number & 1) == 0; + } + + bool is_odd(long long number) { + return (number & 1); + } +} \ No newline at end of file diff --git a/src/fabelous_math/include/simple_functions.hpp b/src/fabelous_math/include/simple_functions.hpp new file mode 100644 index 0000000..4678dd8 --- /dev/null +++ b/src/fabelous_math/include/simple_functions.hpp @@ -0,0 +1,6 @@ +#pragma once + +namespace simple_functions { + bool is_even(long long number); + bool is_odd(long long number); +} \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/functions/__init__.py b/tests/functions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/functions/test_simple_functions.py b/tests/functions/test_simple_functions.py new file mode 100644 index 0000000..1463747 --- /dev/null +++ b/tests/functions/test_simple_functions.py @@ -0,0 +1,62 @@ +import pytest +from fabelous_math import is_even, is_odd + +def test_is_even(): + # Test positive even numbers + assert is_even(0) == True + assert is_even(2) == True + assert is_even(4) == True + assert is_even(100) == True + + # Test positive odd numbers + assert is_even(1) == False + assert is_even(3) == False + assert is_even(99) == False + + # Test negative even numbers + assert is_even(-2) == True + assert is_even(-4) == True + assert is_even(-100) == True + + # Test negative odd numbers + assert is_even(-1) == False + assert is_even(-3) == False + assert is_even(-99) == False + + # Test large numbers + assert is_even(1000000) == True + assert is_even(-1000001) == False + +def test_is_odd(): + # Test positive odd numbers + assert is_odd(1) == True + assert is_odd(3) == True + assert is_odd(99) == True + + # Test positive even numbers + assert is_odd(0) == False + assert is_odd(2) == False + assert is_odd(4) == False + assert is_odd(100) == False + + # Test negative odd numbers + assert is_odd(-1) == True + assert is_odd(-3) == True + assert is_odd(-99) == True + + # Test negative even numbers + assert is_odd(-2) == False + assert is_odd(-4) == False + assert is_odd(-100) == False + + # Test large numbers + assert is_odd(1000001) == True + assert is_odd(-1000000) == False + +def test_is_even_is_odd_complementary(): + # Ensure is_even and is_odd are complementary for various numbers + test_numbers = [0, 1, -1, 2, -2, 99, -99, 1000000, -1000001] + + for num in test_numbers: + assert is_even(num) != is_odd(num), \ + f"Failed for number {num}: is_even and is_odd should be opposite" \ No newline at end of file