diff --git a/extern/sptest.h b/extern/sptest.h new file mode 100644 index 0000000..a163fa9 --- /dev/null +++ b/extern/sptest.h @@ -0,0 +1,120 @@ + +/* spekin@2024 + Version 2024Dec10_v1.0 +*/ +#ifndef sptest_header +#define sptest_header +#include +#include +#include +#include +namespace sptest +{ + struct TestCase + { + std::string name; + std::function func; + }; + // Container for test cases + inline std::vector test_cases; +// Macros to define and register test cases +#define TEST_CASE(name) \ + void name(); \ + bool _##name##_registered = sptest::register_test(#name, name); \ + void name() + // Function to register a test case + inline bool register_test(const std::string &name, std::function func) + { + test_cases.push_back({name, func}); + return true; + } +#define EXPECT(condition) \ + do \ + { \ + sptest::tests_run++; \ + if (!(condition)) \ + { \ + std::cerr << COLOR_RED << "[FAIL] " << COLOR_RESET \ + << __FILE__ << ":" << __LINE__ << " Tested [" << #condition << "]" << std::endl; \ + sptest::tests_failed++; \ + } \ + else \ + { \ + std::cout << COLOR_GREEN << "[PASS] " << COLOR_RESET \ + << __FILE__ << ":" << __LINE__ << ": " << #condition << std::endl; \ + } \ + } while (0) +#define OK(msg) \ + do \ + { \ + sptest::tests_run++; \ + std::cout << "just passing ..." << msg; \ + } while (0) +#define EXPECT_TRUE(condition) \ + do \ + { \ + if (!(condition)) \ + { \ + std::cerr << "[FAIL] " << __FILE__ << ":" << __LINE__ << ": " << #condition << " is false" << std::endl; \ + } \ + else \ + { \ + std::cout << "[PASS] " << __FILE__ << ":" << __LINE__ << ": " << #condition << std::endl; \ + } \ + } while (0) +#define EXPECT_EQ(actual, expected) \ + do \ + { \ + if ((actual) != (expected)) \ + { \ + std::cerr << "[FAIL] " << __FILE__ << ":" << __LINE__ << ": Expected " << (expected) << ", got " << (actual) << std::endl; \ + } \ + else \ + { \ + std::cout << "[PASS] " << __FILE__ << ":" << __LINE__ << ": " << (actual) << " == " << (expected) << std::endl; \ + } \ + } while (0) +#define EXPECT_NEAR(actual, expected, tolerance) \ + do \ + { \ + sptest::tests_run++; \ + if (std::fabs((actual) - (expected)) > (tolerance)) \ + { \ + std::cerr << "[FAIL] " << __FILE__ << ":" << __LINE__ \ + << ": Expected " << (expected) << " but got " << (actual) \ + << " (Tolerance: " << (tolerance) << ")" << std::endl; \ + } \ + else \ + { \ + std::cout << "[PASS] " << __FILE__ << ":" << __LINE__ \ + << ": " << (actual) << " is within " << (tolerance) \ + << " of " << (expected) << std::endl; \ + } \ + } while (0) + inline void run_all_tests() + { + int tests_run = 0; + int tests_failed = 0; + for (const auto &test : test_cases) + { + std::cout << "\nRunning test: " << test.name << std::endl; + try + { + test.func(); + } + catch (const std::exception &e) + { + std::cerr << "[EXCEPTION] " << e.what() << std::endl; + tests_failed++; + } + tests_run++; + } + std::cout << "\nTests run: " << tests_run << std::endl; + std::cout << "Tests failed: " << tests_failed << std::endl; + if (tests_failed == 0) + { + std::cout << "All tests passed!" << std::endl; + } + } +} // namespace sptest +#endif // sptest_header \ No newline at end of file diff --git a/include/adam.py b/include/adam.py new file mode 100644 index 0000000..30136b3 --- /dev/null +++ b/include/adam.py @@ -0,0 +1,173 @@ + +r""" +--- +title: Adam Optimizer +summary: A simple PyTorch implementation/tutorial of Adam optimizer +--- +# Adam Optimizer +This is a [PyTorch](https://pytorch.org) implementation of popular optimizer *Adam* from paper + [Adam: A Method for Stochastic Optimization](https://arxiv.org/abs/1412.6980). +*Adam* update is, +\begin{align} +m_t &\leftarrow \beta_1 m_{t-1} + (1 - \beta_1) \cdot g_t \\ +v_t &\leftarrow \beta_2 v_{t-1} + (1 - \beta_2) \cdot g_t^2 \\ +\hat{m}_t &\leftarrow \frac{m_t}{1-\beta_1^t} \\ +\hat{v}_t &\leftarrow \frac{v_t}{1-\beta_2^t} \\ +\theta_t &\leftarrow \theta_{t-1} - \alpha \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} +\end{align} +where $\alpha$, $\beta_1$, $\beta_2$ and $\epsilon$ are scalar hyper parameters. +$m_t$ and $v_t$ are first and second order moments. +$\hat{m}_t$ and $\hat{v}_t$ are biased corrected moments. +$\epsilon$ is used as a fix for division by zero error, but also acts as a form of a hyper-parameter +that acts against variance in gradients. +Effective step taken assuming $\epsilon = 0$ is, +$$\Delta t = \alpha \cdot \frac{\hat{m}_t}{\hat{v}_t}$$ +This is bounded by, +$$\vert \Delta t \vert \le \alpha \cdot \frac{1 - \beta_1}{\sqrt{1-\beta_2}}$$ +when $1-\beta_1 \gt \sqrt{1-\beta_2}$ +and +$$\vert \Delta t\vert \le \alpha$$ +otherwise. +And in most common scenarios, +$$\vert \Delta t \vert \approx \alpha$$ +""" +import math +from typing import Dict, Any, Tuple, Optional +import torch +from labml import tracker +from torch import nn +from labml_nn.optimizers import GenericAdaptiveOptimizer, WeightDecay +class Adam(GenericAdaptiveOptimizer): + """ + ## Adam Optimizer + We extend the class `GenericAdaptiveOptimizer` defined in [`__init__.py`](index.html) + to implement the Adam optimizer. + """ + def __init__(self, params, + lr: float = 1e-3, betas: Tuple[float, float] = (0.9, 0.999), eps: float = 1e-16, + weight_decay: WeightDecay = WeightDecay(), + optimized_update: bool = True, + defaults: Optional[Dict[str, Any]] = None): + """ + ### Initialize the optimizer + * `params` is the list of parameters + * `lr` is the learning rate $\alpha$ + * `betas` is a tuple of ($\beta_1$, $\beta_2$) + * `eps` is $\hat{\epsilon}$ or $\epsilon$ based on `optimized_update` + * `weight_decay` is an instance of class `WeightDecay` defined in [`__init__.py`](index.html) + * `optimized_update` is a flag whether to optimize the bias correction of the second moment + by doing it after adding $\epsilon$ + * `defaults` is a dictionary of default for group values. + This is useful when you want to extend the class `Adam`. + """ + defaults = {} if defaults is None else defaults + defaults.update(weight_decay.defaults()) + super().__init__(params, defaults, lr, betas, eps) + self.weight_decay = weight_decay + self.optimized_update = optimized_update + def init_state(self, state: Dict[str, any], group: Dict[str, any], param: nn.Parameter): + """ + ### Initialize a parameter state + * `state` is the optimizer state of the parameter (tensor) + * `group` stores optimizer attributes of the parameter group + * `param` is the parameter tensor $\theta_{t-1}$ + """ + # This is the number of optimizer steps taken on the parameter, $t$ + state['step'] = 0 + # Exponential moving average of gradients, $m_t$ + state['exp_avg'] = torch.zeros_like(param, memory_format=torch.preserve_format) + # Exponential moving average of squared gradient values, $v_t$ + state['exp_avg_sq'] = torch.zeros_like(param, memory_format=torch.preserve_format) + def get_mv(self, state: Dict[str, Any], group: Dict[str, Any], grad: torch.Tensor): + """ + ### Calculate $m_t$ and and $v_t$ + * `state` is the optimizer state of the parameter (tensor) + * `group` stores optimizer attributes of the parameter group + * `grad` is the current gradient tensor $g_t$ for the parameter $\theta_{t-1}$ + """ + # Get $\beta_1$ and $\beta_2$ + beta1, beta2 = group['betas'] + # Get $m_{t-1}$ and $v_{t-1}$ + m, v = state['exp_avg'], state['exp_avg_sq'] + # In-place calculation of $m_t$ + # $$m_t \leftarrow \beta_1 m_{t-1} + (1 - \beta_1) \cdot g_t$$ + m.mul_(beta1).add_(grad, alpha=1 - beta1) + # In-place calculation of $v_t$ + # $$v_t \leftarrow \beta_2 v_{t-1} + (1 - \beta_2) \cdot g_t^2$$ + v.mul_(beta2).addcmul_(grad, grad, value=1 - beta2) + return m, v + def get_lr(self, state: Dict[str, any], group: Dict[str, any]): + """ + ### Get learning-rate + This returns the modified learning rate based on the state. + For *Adam* this is just the specified learning rate for the parameter group, + $\alpha$. + """ + return group['lr'] + def adam_update(self, state: Dict[str, any], group: Dict[str, any], param: torch.nn.Parameter, + m: torch.Tensor, v: torch.Tensor): + """ + ### Do the *Adam* parameter update + * `state` is the optimizer state of the parameter (tensor) + * `group` stores optimizer attributes of the parameter group + * `param` is the parameter tensor $\theta_{t-1}$ + * `m` and `v` are the uncorrected first and second moments $m_t$ and $v_t$. + This computes the following + \begin{align} + \theta_t &\leftarrow \theta_{t-1} - \alpha \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} + \end{align} + Since $\alpha$, $\beta_1$, $\beta_2$ and $\epsilon$ are scalars and others are tensors + we modify this calculation to optimize the computation. + \begin{align} + \theta_t &\leftarrow \theta_{t-1} - \alpha \cdot \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon} \\ + \theta_t &\leftarrow \theta_{t-1} - \alpha \cdot + \frac{m_t / (1-\beta_1^t)}{\sqrt{v_t/(1-\beta_2^t)} + \epsilon} \\ + \theta_t &\leftarrow \theta_{t-1} - \alpha \frac{\sqrt{1-\beta_2^t}}{1-\beta_1^t} \cdot + \frac{m_t}{\sqrt{v_t} + \hat{\epsilon}} \\ + \end{align} + where + $$\hat{\epsilon} = (1-\beta_2^t) \epsilon$$ + is what we should specify as the hyper-parameter. + """ + # Get $\beta_1$ and $\beta_2$ + beta1, beta2 = group['betas'] + # Bias correction term for $\hat{m}_t$, $1 - \beta_1^t$ + bias_correction1 = 1 - beta1 ** state['step'] + # Bias correction term for $\hat{v}_t$, $1 - \beta_2^t$ + bias_correction2 = 1 - beta2 ** state['step'] + # Get learning rate + lr = self.get_lr(state, group) + # Whether to optimize the computation + if self.optimized_update: + # $\sqrt{v_t} + \hat{\epsilon}$ + denominator = v.sqrt().add_(group['eps']) + # $\alpha \frac{\sqrt{1-\beta_2^t}}{1-\beta_1^t}$ + step_size = lr * math.sqrt(bias_correction2) / bias_correction1 + # $\theta_t \leftarrow \theta_{t-1} - \alpha \frac{\sqrt{1-\beta_2^t}}{1-\beta_1^t} \cdot + # \frac{m_t}{\sqrt{v_t} + \hat{\epsilon}}$ + param.data.addcdiv_(m, denominator, value=-step_size) + # Computation without optimization + else: + # $\frac{\sqrt{v_t}}{\sqrt{1-\beta_2^t}} + \epsilon$ + denominator = (v.sqrt() / math.sqrt(bias_correction2)).add_(group['eps']) + # $\frac{\alpha}{1-\beta_1^t}$ + step_size = lr / bias_correction1 + # $\theta_t \leftarrow \theta_{t-1} - \alpha \cdot + # \frac{\hat{m}_t}{\sqrt{\hat{v}_t} + \epsilon}$ + param.data.addcdiv_(m, denominator, value=-step_size) + def step_param(self, state: Dict[str, any], group: Dict[str, any], grad: torch.Tensor, param: torch.nn.Parameter): + """ + ### Take an update step for a given parameter tensor + * `state` is the optimizer state of the parameter (tensor) + * `group` stores optimizer attributes of the parameter group + * `grad` is the current gradient tensor $g_t$ for the parameter $\theta_{t-1}$ + * `param` is the parameter tensor $\theta_{t-1}$ + """ + # Calculate weight decay + grad = self.weight_decay(param, grad, group) + # Get $m_t$ and $v_t$ + m, v = self.get_mv(state, group, grad) + # Increment $t$ the number of optimizer steps + state['step'] += 1 + # Perform *Adam* update + self.adam_update(state, group, param, m, v) \ No newline at end of file diff --git a/include/dataframe.hpp b/include/dataframe.hpp index 062624d..792b920 100644 --- a/include/dataframe.hpp +++ b/include/dataframe.hpp @@ -27,10 +27,6 @@ * * * - - - - */ #pragma once #include @@ -59,7 +55,6 @@ #include // For std::shuffle #include // For std::default_random_engine #include // For seeding the random engine - namespace microgradCpp { class DataFrame; @@ -68,26 +63,21 @@ namespace microgradCpp using Column = std::vector; class DataFrame { - /* * * * @usage : - DataFrame df; df.from_csv("./data/wine.csv", true, ';'); - df.normalize(); df.normalize(Range(2,5)); // columns between 2 and 4 df.normalize(Range(5)); // first 5 columns {0,1,2,3,4 } - df.encode_column("quality"); // one hot encoding for column name 'quality' - df.encode_column( {"quality" } ); // one hot encoding for column name 'quality' + df.encode_column( columns ); // one hot encoding for column names df.encode_column(Range(5 ) ); // one hot encoding for first 5 columns df.print(); df.shuffle(); df.print(); - */ public: void from_csv(const std::string &filename, bool has_header = true, char delimiter = ','); @@ -113,13 +103,10 @@ namespace microgradCpp auto numbers = range.to_vector(); return this->slice(numbers, col_names, DEFAULT_INPLACE); } - DataFrame operator()(const Range &rows_, const Range &cols_) { auto numbers = rows_.to_vector(); - v_string cols_filtered = cols_.filter(column_order); - return this->slice(numbers, cols_filtered, DEFAULT_INPLACE); } DataFrame rows(const Range &range) @@ -435,7 +422,6 @@ namespace microgradCpp // Replace the original columns with shuffled columns columns = shuffled_columns; } - void normalize(const v_string &cols_given = {}) { v_string cols_to_normalize; @@ -569,21 +555,17 @@ namespace microgradCpp // Update the column type to double column_types[column_name] = typeid(double); } - void encode_column(const Range &range) { v_string columns_ = range.filter(column_order); for (const string x : columns_) encode_column(x); } - void encode_column(const v_string &columns_) { - for (const string x : columns_) encode_column(x); } - void print_encoding_map(const std::string &column_name) const { auto it = encoding_mappings.find(column_name); @@ -678,7 +660,6 @@ namespace microgradCpp column_order.push_back(name); column_types[name] = type; } - private: std::vector get_all_row_indices() const { diff --git a/include/dataframe_extended.hpp b/include/dataframe_extended.hpp new file mode 100644 index 0000000..a979a8b --- /dev/null +++ b/include/dataframe_extended.hpp @@ -0,0 +1,70 @@ + +/* + * evdscpp: An open-source data wrapper for accessing the EVDS API. + * Author: Sermet Pekin + * + * MIT License + * + * Copyright (c) 2024 Sermet Pekin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + * + */ +#pragma once +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // typeid +// #include "../extern/nlohmann/json.hpp" +#include +#include +#include +#include +#include "series.hpp" +#include "header.hpp" +#include "range.hpp" +#include "console_utils.hpp" +#include "types.hpp" +#include // For std::shuffle +#include // For std::default_random_engine +#include // For seeding the random engine +#include "dataframe.hpp" +namespace microgradCpp +{ + class DataFrameExtended : public DataFrame + { + public: + struct testMyStruct + { + int x, y; + }; + testMyStruct abcdefg{5, 6}; + }; +} \ No newline at end of file diff --git a/include/linear.hpp b/include/linear.hpp index 187d9ff..db5112b 100644 --- a/include/linear.hpp +++ b/include/linear.hpp @@ -28,10 +28,7 @@ THE SOFTWARE. #include #include #include -using namespace microgradCpp ; - - - +using namespace microgradCpp ; inline std::vector> softmaxLocal(const std::vector> &inputs) { // sum_exp = sum of exp(input) @@ -51,10 +48,6 @@ inline std::vector> softmaxLocal(const std::vector> &targets) { const int width = 15; // Width for each column - std::cout << "\n📊 Predictions vs Targets\n"; + std::cout << "\n📊 Predictions vs Targets\n"; std::cout << "=============================================\n"; std::cout << std::setw(width) << "Predictions" << std::setw(width) << "Targets" << "\n"; std::cout << "---------------------------------------------\n"; diff --git a/include/micrograd.hpp b/include/micrograd.hpp index b15ab5c..1b8dc60 100644 --- a/include/micrograd.hpp +++ b/include/micrograd.hpp @@ -26,6 +26,7 @@ THE SOFTWARE. #include "header.hpp" #include "series.hpp" #include "dataframe.hpp" +#include "dataframe_extended.hpp" #include "dataframe_utils.hpp" #include "sp_testing_utils.hpp" #include "train_eval.hpp" diff --git a/include/mlp.hpp b/include/mlp.hpp index 6ee32cc..f333a34 100644 --- a/include/mlp.hpp +++ b/include/mlp.hpp @@ -124,7 +124,6 @@ class MLP { /* TODO options object */ int _in_features; std::vector _layer_sizes; - public: std::vector> layers; int input_size() const diff --git a/include/types.hpp b/include/types.hpp index f4f73b4..ac5c9a5 100644 --- a/include/types.hpp +++ b/include/types.hpp @@ -32,20 +32,14 @@ namespace microgradCpp using string = std::string; using v_string = std::vector; using vv_string = std::vector>; - - using v_double = std::vector; using vv_double = std::vector>; - using vv_shared_Value = std::vector>>; using v_shared_Value = std::vector>; using shared_Value = std::shared_ptr; - using ColumnData = std::variant, std::vector>; using DatasetType = std::vector>, std::vector>>>; using ColRows = std::vector>>; - // microgradCpp namespace - } #endif // TYPES_HPP \ No newline at end of file diff --git a/test3.cpp b/test3.cpp new file mode 100644 index 0000000..ae1c675 --- /dev/null +++ b/test3.cpp @@ -0,0 +1,15 @@ + +#include "tests/wintests/simple2.h" +using namespace simple_test; +// Sample test case 1 +TEST_CASE(test_addition22) +{ + EXPECT_EQ(2 + 2, 4); + EXPECT_EQ(1 + 1, 2); +} +// Sample test case 2 +TEST_CASE(test_boolean22) +{ + EXPECT_TRUE(3 > 2); + EXPECT_TRUE(0 == 1); // This will fail +} \ No newline at end of file diff --git a/test4.cpp b/test4.cpp new file mode 100644 index 0000000..7c6c2f8 --- /dev/null +++ b/test4.cpp @@ -0,0 +1,24 @@ + +#include "sptest.h" +using namespace sptest; +// Sample test case 1 +TEST_CASE(test_addition22) +{ + EXPECT_EQ(28 + 2, 30); + EXPECT_EQ(1 + 1, 2); +} +// Sample test case 2 +TEST_CASE(test_boolean22) +{ + EXPECT_TRUE(3 > 2); + EXPECT_TRUE(0 == 1); // This will fail +} +using namespace sptest; +int main() +{ + sptest::run_all_tests(); + return 0; +} +/* +g++ test4.cpp test5.cpp -Iinclude -Iextern +*/ \ No newline at end of file diff --git a/test5.cpp b/test5.cpp new file mode 100644 index 0000000..0ed676d --- /dev/null +++ b/test5.cpp @@ -0,0 +1,15 @@ + +#include "sptest.h" +using namespace sptest; +// Sample test case 1 +TEST_CASE(test_addition51) +{ + EXPECT_EQ(28 + 2, 30); + EXPECT_EQ(1 + 1, 2); +} +// Sample test case 2 +TEST_CASE(test_boolean52) +{ + EXPECT_TRUE(3 > 2); + EXPECT_TRUE(0 == 1); // This will fail +} \ No newline at end of file diff --git a/tests/wintests/simple.h b/tests/wintests/simple.h new file mode 100644 index 0000000..6fd1ea9 --- /dev/null +++ b/tests/wintests/simple.h @@ -0,0 +1,72 @@ + +// simple_test.h +#ifndef SIMPLE_TEST_H +#define SIMPLE_TEST_H +#include +#include +namespace simple_test +{ + int tests_run = 0; + int tests_failed = 0; +// ANSI escape codes for colors +#define COLOR_RESET "\033[0m" +#define COLOR_GREEN "\033[32m" +#define COLOR_RED "\033[31m" +#define COLOR_YELLOW "\033[33m" +#define COLOR_CYAN "\033[36m" +// Macro for checking conditions +#define EXPECT(condition) \ + do \ + { \ + simple_test::tests_run++; \ + if (!(condition)) \ + { \ + std::cerr << COLOR_RED << "[FAIL] " << COLOR_RESET \ + << __FILE__ << ":" << __LINE__ << " Tested [" << #condition << "]" << std::endl; \ + simple_test::tests_failed++; \ + } \ + else \ + { \ + std::cout << COLOR_GREEN << "[PASS] " << COLOR_RESET \ + << __FILE__ << ":" << __LINE__ << ": " << #condition << std::endl; \ + } \ + } while (0) + // Summary function + inline void test_summary() + { + std::cout << COLOR_CYAN << "\nTests run: " << tests_run << COLOR_RESET << std::endl; + if (tests_failed > 0) + { + std::cerr << COLOR_RED << "Tests failed: " << tests_failed << COLOR_RESET << std::endl; + } + else + { + std::cout << COLOR_GREEN << "All tests passed!" << COLOR_RESET << std::endl; + } + } +// Macro for checking conditions +#define OK(msg) \ + do \ + { \ + simple_test::tests_run++; \ + std::cout << "just passing ..." << msg; \ + } while (0) +#define EXPECT_NEAR(actual, expected, tolerance) \ + do \ + { \ + simple_test::tests_run++; \ + if (std::fabs((actual) - (expected)) > (tolerance)) \ + { \ + std::cerr << "[FAIL] " << __FILE__ << ":" << __LINE__ \ + << ": Expected " << (expected) << " but got " << (actual) \ + << " (Tolerance: " << (tolerance) << ")" << std::endl; \ + } \ + else \ + { \ + std::cout << "[PASS] " << __FILE__ << ":" << __LINE__ \ + << ": " << (actual) << " is within " << (tolerance) \ + << " of " << (expected) << std::endl; \ + } \ + } while (0) +} +#endif // SIMPLE_TEST_H \ No newline at end of file diff --git a/tests/wintests/simple2.h b/tests/wintests/simple2.h new file mode 100644 index 0000000..85da858 --- /dev/null +++ b/tests/wintests/simple2.h @@ -0,0 +1,121 @@ + +#ifndef SIMPLE_TEST_H +#define SIMPLE_TEST_H +#include +#include +#include +#include +// Namespace to contain testing functionality +namespace simple_test +{ + // Struct to represent a single test case + struct TestCase + { + std::string name; + std::function func; + }; + // Container for test cases + std::vector test_cases; +// Macros to define and register test cases +#define TEST_CASE(name) \ + void name(); \ + bool _##name##_registered = simple_test::register_test(#name, name); \ + void name() + // Function to register a test case + inline bool register_test(const std::string &name, std::function func) + { + test_cases.push_back({name, func}); + return true; + } +#define EXPECT(condition) \ + do \ + { \ + simple_test::tests_run++; \ + if (!(condition)) \ + { \ + std::cerr << COLOR_RED << "[FAIL] " << COLOR_RESET \ + << __FILE__ << ":" << __LINE__ << " Tested [" << #condition << "]" << std::endl; \ + simple_test::tests_failed++; \ + } \ + else \ + { \ + std::cout << COLOR_GREEN << "[PASS] " << COLOR_RESET \ + << __FILE__ << ":" << __LINE__ << ": " << #condition << std::endl; \ + } \ + } while (0) +#define OK(msg) \ + do \ + { \ + simple_test::tests_run++; \ + std::cout << "just passing ..." << msg; \ + } while (0) +// Assertion macros +#define EXPECT_TRUE(condition) \ + do \ + { \ + if (!(condition)) \ + { \ + std::cerr << "[FAIL] " << __FILE__ << ":" << __LINE__ << ": " << #condition << " is false" << std::endl; \ + } \ + else \ + { \ + std::cout << "[PASS] " << __FILE__ << ":" << __LINE__ << ": " << #condition << std::endl; \ + } \ + } while (0) +#define EXPECT_EQ(actual, expected) \ + do \ + { \ + if ((actual) != (expected)) \ + { \ + std::cerr << "[FAIL] " << __FILE__ << ":" << __LINE__ << ": Expected " << (expected) << ", got " << (actual) << std::endl; \ + } \ + else \ + { \ + std::cout << "[PASS] " << __FILE__ << ":" << __LINE__ << ": " << (actual) << " == " << (expected) << std::endl; \ + } \ + } while (0) +#define EXPECT_NEAR(actual, expected, tolerance) \ + do \ + { \ + simple_test::tests_run++; \ + if (std::fabs((actual) - (expected)) > (tolerance)) \ + { \ + std::cerr << "[FAIL] " << __FILE__ << ":" << __LINE__ \ + << ": Expected " << (expected) << " but got " << (actual) \ + << " (Tolerance: " << (tolerance) << ")" << std::endl; \ + } \ + else \ + { \ + std::cout << "[PASS] " << __FILE__ << ":" << __LINE__ \ + << ": " << (actual) << " is within " << (tolerance) \ + << " of " << (expected) << std::endl; \ + } \ + } while (0) + // Function to run all registered tests + inline void run_all_tests() + { + int tests_run = 0; + int tests_failed = 0; + for (const auto &test : test_cases) + { + std::cout << "\nRunning test: " << test.name << std::endl; + try + { + test.func(); + } + catch (const std::exception &e) + { + std::cerr << "[EXCEPTION] " << e.what() << std::endl; + tests_failed++; + } + tests_run++; + } + std::cout << "\nTests run: " << tests_run << std::endl; + std::cout << "Tests failed: " << tests_failed << std::endl; + if (tests_failed == 0) + { + std::cout << "All tests passed!" << std::endl; + } + } +} // namespace simple_test +#endif // SIMPLE_TEST_H \ No newline at end of file diff --git a/tests/wintests/simpletest.cpp b/tests/wintests/simpletest.cpp new file mode 100644 index 0000000..e6b4608 --- /dev/null +++ b/tests/wintests/simpletest.cpp @@ -0,0 +1,21 @@ + +#include "simple.h" +void test_addition() +{ + EXPECT(2 + 2 == 4); + EXPECT(1 + 1 == 3); // This will fail +} +void test_subtraction() +{ + EXPECT(5 - 3 == 2); + EXPECT(10 - 5 == 5); +} +int main() +{ + std::cout << "Running tests...\n" + << std::endl; + test_addition(); + test_subtraction(); + simple_test::test_summary(); + return 0; +} \ No newline at end of file diff --git a/tests/wintests/test_main.cpp b/tests/wintests/test_main.cpp new file mode 100644 index 0000000..3b16793 --- /dev/null +++ b/tests/wintests/test_main.cpp @@ -0,0 +1,17 @@ + +#include "micrograd.hpp" +#include "value.hpp" +#include "mlp.hpp" +using namespace microgradCpp; +int main() +{ + // DatasetType dataset = get_iris(); + DataFrame df; + df.from_csv("./data/iris.csv"); + df.normalize(); + df.encode_column("variety"); + df.print(); + df.shuffle(); + df.print(); + return 0; +} \ No newline at end of file diff --git a/tests/wintests/test_softmax.cpp b/tests/wintests/test_softmax.cpp new file mode 100644 index 0000000..e34d076 --- /dev/null +++ b/tests/wintests/test_softmax.cpp @@ -0,0 +1,71 @@ + +#include +#include "micrograd.hpp" // Assuming Value class is defined in value.hpp +#include "types.hpp" // Assuming Value class is defined in value.hpp +#include +#include +#include +using namespace microgradCpp ; +// Alias for convenience +// using v_shared_Value = std::vector>; +// using shared_Value = std::shared_ptr; +// Forward declaration of softmaxLocal function +// v_shared_Value softmaxLocal(const v_shared_Value &inputs); +// // Helper function to create a shared_ptr +shared_Value make_value(double data) { + return std::make_shared(data); +} +// -------------------- TEST CASES -------------------- +// Test softmax on a simple set of inputs +TEST(SoftmaxTest, SimpleInputs) { + v_shared_Value inputs = {make_value(1.0), make_value(2.0), make_value(3.0)}; + auto outputs = softmaxLocal(inputs); + // Compute expected softmax values manually + double exp1 = std::exp(1.0); + double exp2 = std::exp(2.0); + double exp3 = std::exp(3.0); + double sum_exp = exp1 + exp2 + exp3; + EXPECT_NEAR(outputs[0]->data, exp1 / sum_exp, 1e-5); + EXPECT_NEAR(outputs[1]->data, exp2 / sum_exp, 1e-5); + EXPECT_NEAR(outputs[2]->data, exp3 / sum_exp, 1e-5); +} +// Test softmax with all equal inputs +TEST(SoftmaxTest, EqualInputs) { + v_shared_Value inputs = {make_value(1.0), make_value(1.0), make_value(1.0)}; + auto outputs = softmaxLocal(inputs); + // When all inputs are equal, softmax outputs should be uniform + double expected = 1.0 / 3.0; + EXPECT_NEAR(outputs[0]->data, expected, 1e-5); + EXPECT_NEAR(outputs[1]->data, expected, 1e-5); + EXPECT_NEAR(outputs[2]->data, expected, 1e-5); +} +// Test softmax with negative inputs +TEST(SoftmaxTest, NegativeInputs) { + v_shared_Value inputs = {make_value(-1.0), make_value(-2.0), make_value(-3.0)}; + auto outputs = softmaxLocal(inputs); + double exp1 = std::exp(-1.0); + double exp2 = std::exp(-2.0); + double exp3 = std::exp(-3.0); + double sum_exp = exp1 + exp2 + exp3; + EXPECT_NEAR(outputs[0]->data, exp1 / sum_exp, 1e-5); + EXPECT_NEAR(outputs[1]->data, exp2 / sum_exp, 1e-5); + EXPECT_NEAR(outputs[2]->data, exp3 / sum_exp, 1e-5); +} +// Test softmax with a single input +TEST(SoftmaxTest, SingleInput) { + v_shared_Value inputs = {make_value(5.0)}; + auto outputs = softmaxLocal(inputs); + EXPECT_NEAR(outputs[0]->data, 1.0, 1e-5); +} +//// Test softmax with large inputs to check for numerical stability +// TEST(SoftmaxTest, LargeInputs) { +// v_shared_Value inputs = {make_value(1000.0), make_value(1001.0), make_value(1002.0)}; +// auto outputs = softmaxLocal(inputs); +// double exp1 = std::exp(1000.0 - 1002.0); +// double exp2 = std::exp(1001.0 - 1002.0); +// double exp3 = std::exp(1002.0 - 1002.0); +// double sum_exp = exp1 + exp2 + exp3; +// EXPECT_NEAR(outputs[0]->data, exp1 / sum_exp, 1e-5); +// EXPECT_NEAR(outputs[1]->data, exp2 / sum_exp, 1e-5); +// EXPECT_NEAR(outputs[2]->data, exp3 / sum_exp, 1e-5); +// } \ No newline at end of file