diff --git a/.idea/workspace (conflicted copy 2024-11-04 192927).xml b/.idea/workspace (conflicted copy 2024-11-04 192927).xml deleted file mode 100644 index 99fca5a..0000000 --- a/.idea/workspace (conflicted copy 2024-11-04 192927).xml +++ /dev/null @@ -1,164 +0,0 @@ - - - - - { - "useNewFormat": true -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - { - "associatedIndex": 7 -} - - - - { - "keyToString": { - "CMake Application.COSC-4P80-Assignment-3.executor": "Run", - "NIXITCH_NIXPKGS_CONFIG": "/etc/nix/nixpkgs-config.nix", - "NIXITCH_NIX_CONF_DIR": "", - "NIXITCH_NIX_OTHER_STORES": "", - "NIXITCH_NIX_PATH": "/home/brett/.nix-defexpr/channels:nixpkgs=/nix/var/nix/profiles/per-user/root/channels/nixos:nixos-config=/etc/nixos/configuration.nix:/nix/var/nix/profiles/per-user/root/channels", - "NIXITCH_NIX_PROFILES": "/run/current-system/sw /nix/var/nix/profiles/default /etc/profiles/per-user/brett /home/brett/.local/state/nix/profile /nix/profile /home/brett/.nix-profile", - "NIXITCH_NIX_REMOTE": "", - "NIXITCH_NIX_USER_PROFILE_DIR": "/nix/var/nix/profiles/per-user/brett", - "RunOnceActivity.ShowReadmeOnStart": "true", - "RunOnceActivity.cidr.known.project.marker": "true", - "RunOnceActivity.readMode.enableVisualFormatting": "true", - "cf.first.check.clang-format": "false", - "cidr.known.project.marker": "true", - "git-widget-placeholder": "main", - "last_opened_file_path": "/home/brett/Documents/Brock/CS 4P80/COSC-4P80-Assignment-3", - "node.js.detected.package.eslint": "true", - "node.js.detected.package.tslint": "true", - "node.js.selected.package.eslint": "(autodetect)", - "node.js.selected.package.tslint": "(autodetect)", - "nodejs_package_manager_path": "npm", - "settings.editor.selected.configurable": "CMakeSettings", - "vue.rearranger.settings.migration": "true" - } -} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1730483030448 - - - - - - - - - - - \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 5c94a9d..1363d70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.25) -project(COSC-4P80-Assignment-3 VERSION 0.0.5) +project(COSC-4P80-Assignment-3 VERSION 0.0.6) option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF) option(ENABLE_UBSAN "Enable the ub sanitizer" OFF) diff --git a/commit.py b/commit.py old mode 100644 new mode 100755 diff --git a/include/assign3/array.h b/include/assign3/array.h new file mode 100644 index 0000000..a19d0d0 --- /dev/null +++ b/include/assign3/array.h @@ -0,0 +1,70 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef COSC_4P80_ASSIGNMENT_3_ARRAY_H +#define COSC_4P80_ASSIGNMENT_3_ARRAY_H + +#include +#include + +namespace assign3 +{ + + class array_t + { + public: + explicit array_t(blt::size_t dimensions, blt::size_t width, blt::size_t height): + width(static_cast(width)), height(static_cast(height)) + { + for (blt::size_t i = 0; i < width; i++) + for (blt::size_t j = 0; j < height; j++) + map.emplace_back(dimensions, i, j); + } + + inline neuron_t& get(blt::size_t x, blt::size_t y) + { return map[y * width + x]; } + + [[nodiscard]] inline const neuron_t& get(blt::size_t x, blt::size_t y) const + { return map[y * width + x]; } + + [[nodiscard]] inline blt::size_t get_width() const + { return width; } + + [[nodiscard]] inline blt::size_t get_height() const + { return height; } + + [[nodiscard]] inline std::vector& get_map() + { return map; } + + [[nodiscard]] inline const std::vector& get_map() const + { return map; } + + private: + [[nodiscard]] inline blt::i64 wrap_width(blt::i64 x) const; + + [[nodiscard]] inline blt::i64 wrap_height(blt::i64 y) const; + + private: + blt::i64 width, height; + std::vector map; + }; + + +} + +#endif //COSC_4P80_ASSIGNMENT_3_ARRAY_H diff --git a/include/assign3/functions.h b/include/assign3/functions.h index 5848935..5fe6327 100644 --- a/include/assign3/functions.h +++ b/include/assign3/functions.h @@ -26,6 +26,11 @@ namespace assign3 struct topology_function_t { + /** + * @param dist input - usually the distance + * @param r time ratio - t / max_t + * @return basis results + */ [[nodiscard]] virtual Scalar call(Scalar dist, Scalar r) const = 0; }; diff --git a/include/assign3/fwdecl.h b/include/assign3/fwdecl.h index 7025d45..978d602 100644 --- a/include/assign3/fwdecl.h +++ b/include/assign3/fwdecl.h @@ -22,6 +22,11 @@ namespace assign3 { using Scalar = float; + + enum class shape + { + GRID, HONEYCOMB + }; } #endif //COSC_4P80_ASSIGNMENT_3_FWDECL_H diff --git a/include/assign3/neuron.h b/include/assign3/neuron.h index c14db39..54f4812 100644 --- a/include/assign3/neuron.h +++ b/include/assign3/neuron.h @@ -36,9 +36,11 @@ namespace assign3 neuron_t& randomize(blt::size_t seed); - neuron_t& update(const std::vector& new_data, const topology_function_t* basis_func, Scalar eta, Scalar r); + neuron_t& update(const std::vector& new_data, Scalar dist, Scalar eta); - static Scalar distance(const neuron_t& n1, const neuron_t& n2, Scalar time_ratio); + static Scalar distance(const neuron_t& n1, const neuron_t& n2); + + [[nodiscard]] Scalar dist(const std::vector& X) const; [[nodiscard]] inline const std::vector& get_data() const { return data; } @@ -48,9 +50,8 @@ namespace assign3 [[nodiscard]] inline Scalar get_y() const { return y_pos; } - private: - [[nodiscard]] Scalar dist(const std::vector& X) const; + private: Scalar x_pos, y_pos; std::vector data; }; diff --git a/include/assign3/som.h b/include/assign3/som.h new file mode 100644 index 0000000..70e89a7 --- /dev/null +++ b/include/assign3/som.h @@ -0,0 +1,56 @@ +#pragma once +/* + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef COSC_4P80_ASSIGNMENT_3_SOM_H +#define COSC_4P80_ASSIGNMENT_3_SOM_H + +#include +#include +#include + +namespace assign3 +{ + + class som_t + { + public: + som_t(const data_file_t& file, blt::size_t width, blt::size_t height, blt::size_t max_epochs); + + blt::size_t get_closest_neuron(const std::vector& data); + + void train_epoch(Scalar initial_learn_rate, topology_function_t* basis_func); + + [[nodiscard]] const array_t& get_array() const + { return array; } + + [[nodiscard]] blt::size_t get_current_epoch() const + { return current_epoch; } + + [[nodiscard]] blt::size_t get_max_epochs() const + { return max_epochs; } + + private: + array_t array; + data_file_t file; + blt::size_t current_epoch = 0; + blt::size_t max_epochs; + }; + +} + +#endif //COSC_4P80_ASSIGNMENT_3_SOM_H diff --git a/src/array.cpp b/src/array.cpp new file mode 100644 index 0000000..75559dc --- /dev/null +++ b/src/array.cpp @@ -0,0 +1,43 @@ +/* + * + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include + +namespace assign3 +{ + + blt::i64 array_t::wrap_height(blt::i64 y) const + { + if (y >= height) + return y - height; + else if (y < 0) + return height + y; + else + return y; + } + + blt::i64 array_t::wrap_width(blt::i64 x) const + { + if (x >= width) + return x - width; + else if (x < 0) + return width + x; + else + return x; + } +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index da971c0..84edc44 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -6,9 +6,13 @@ #include "blt/gfx/renderer/batch_2d_renderer.h" #include "blt/gfx/renderer/camera.h" #include +#include #include -std::vector files; +using namespace assign3; + +std::vector files; +std::unique_ptr som; blt::gfx::matrix_state_manager global_matrices; blt::gfx::resource_manager resources; @@ -23,16 +27,39 @@ void init(const blt::gfx::window_data&) global_matrices.create_internals(); resources.load_resources(); renderer_2d.create(); + + blt::size_t size = 5; + som = std::make_unique( + *std::find_if(files.begin(), files.end(), [](const data_file_t& v) { return v.data_points.begin()->bins.size() == 32; }), + size, size, 100); } void update(const blt::gfx::window_data& data) { + using namespace blt::gfx; global_matrices.update_perspectives(data.width, data.height, 90, 0.1, 2000); camera.update(); camera.update_view(global_matrices); global_matrices.update(); + if (ImGui::Begin("Controls")) + { + ImGui::Button("Run Epoch"); + if (ImGui::IsItemClicked()) + { + static gaussian_function_t func; + som->train_epoch(0.1, &func); + } + } + ImGui::End(); + + for (auto& v : som->get_array().get_map()) + { + float scale = 35; + renderer_2d.drawPointInternal(blt::make_color(1, 0, 0), point2d_t{v.get_x() * scale + scale, v.get_y() * scale + scale, scale}); + } + renderer_2d.render(data.width, data.height); } diff --git a/src/neuron.cpp b/src/neuron.cpp index 9de8933..8d264cb 100644 --- a/src/neuron.cpp +++ b/src/neuron.cpp @@ -32,11 +32,17 @@ namespace assign3 return *this; } - neuron_t& neuron_t::update(const std::vector& new_data, const topology_function_t* basis_func, Scalar eta, Scalar r) + neuron_t& neuron_t::update(const std::vector& new_data, Scalar dist, Scalar eta) { - auto d = dist(new_data); - for (auto& v : data) - v = eta * basis_func->call(d, r); + static thread_local std::vector diff; + diff.clear(); + + for (auto [x, v] : blt::in_pairs(new_data, data)) + diff.push_back(v - x); + + for (auto [v, d] : blt::in_pairs(data, diff)) + v += eta * dist * d; + return *this; } @@ -51,10 +57,10 @@ namespace assign3 return std::sqrt(dist); } - Scalar neuron_t::distance(const neuron_t& n1, const neuron_t& n2, Scalar time_ratio) + Scalar neuron_t::distance(const neuron_t& n1, const neuron_t& n2) { - auto dist = n1.dist(n2.data); - auto dist_sq = dist * dist; - return std::exp(-time_ratio * dist_sq); + auto dx = n1.get_x() - n2.get_x(); + auto dy = n1.get_y() - n2.get_y(); + return std::sqrt(dx * dx + dy * dy); } } \ No newline at end of file diff --git a/src/som.cpp b/src/som.cpp new file mode 100644 index 0000000..f14044a --- /dev/null +++ b/src/som.cpp @@ -0,0 +1,79 @@ +/* + * + * Copyright (C) 2024 Brett Terpstra + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include +#include +#include +#include + +namespace assign3 +{ + + som_t::som_t(const data_file_t& file, blt::size_t width, blt::size_t height, blt::size_t max_epochs): + array(file.data_points.begin()->bins.size(), width, height), file(file), max_epochs(max_epochs) + { + for (auto& v : array.get_map()) + v.randomize(std::random_device{}()); + } + + void som_t::train_epoch(Scalar initial_learn_rate, topology_function_t* basis_func) + { + blt::random::random_t rand{std::random_device{}()}; + std::shuffle(file.data_points.begin(), file.data_points.end(), rand); + + auto time_ratio = static_cast(current_epoch) / static_cast(max_epochs); + auto eta = initial_learn_rate * std::exp(-2 * time_ratio); + + for (auto& current_data : file.data_points) + { + auto v0_idx = get_closest_neuron(current_data.bins); + auto v0 = array.get_map()[v0_idx]; + v0.update(current_data.bins, 1, eta); + + for (auto [i, n] : blt::enumerate(array.get_map())) + { + if (i == v0_idx) + continue; + auto dist = basis_func->call(neuron_t::distance(v0, n), time_ratio); + n.update(current_data.bins, dist, eta); + } + } + + current_epoch++; + } + + blt::size_t som_t::get_closest_neuron(const std::vector& data) + { + blt::size_t index = 0; + Scalar distance = std::numeric_limits::max(); + + for (auto [i, d] : blt::enumerate(array.get_map())) + { + auto dist = d.dist(data); + if (dist < distance) + { + index = i; + distance = dist; + } + } + + return index; + } + + +} \ No newline at end of file