diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b53943..c5778c3 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.8) +project(COSC-4P80-Assignment-3 VERSION 0.0.9) include(FetchContent) option(ENABLE_ADDRSAN "Enable the address sanitizer" OFF) diff --git a/include/assign3/functions.h b/include/assign3/functions.h index 5fe6327..507d50c 100644 --- a/include/assign3/functions.h +++ b/include/assign3/functions.h @@ -20,6 +20,7 @@ #define COSC_4P80_ASSIGNMENT_3_FUNCTIONS_H #include +#include namespace assign3 { @@ -39,6 +40,16 @@ namespace assign3 [[nodiscard]] Scalar call(Scalar dist, Scalar r) const final; }; + struct distance_function_t + { + [[nodiscard]] virtual Scalar distance(blt::span x, blt::span y) const = 0; + }; + + struct euclidean_distance_function_t : public distance_function_t + { + [[nodiscard]] Scalar distance(blt::span x, blt::span y) const final; + }; + } #endif //COSC_4P80_ASSIGNMENT_3_FUNCTIONS_H diff --git a/include/assign3/ui.h b/include/assign3/ui.h new file mode 100644 index 0000000..163929e --- /dev/null +++ b/include/assign3/ui.h @@ -0,0 +1,26 @@ +#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_UI_H +#define COSC_4P80_ASSIGNMENT_3_UI_H + +namespace assign3 { + +} + +#endif //COSC_4P80_ASSIGNMENT_3_UI_H diff --git a/src/functions.cpp b/src/functions.cpp index 25dc50a..d29e19f 100644 --- a/src/functions.cpp +++ b/src/functions.cpp @@ -17,6 +17,7 @@ */ #include #include +#include "blt/iterator/zip.h" namespace assign3 { @@ -26,4 +27,15 @@ namespace assign3 auto dist_sq = dist * dist; return std::exp(-r * dist_sq); } + + Scalar euclidean_distance_function_t::distance(blt::span x, blt::span y) const + { + Scalar dist = 0; + for (auto [a, b] : blt::in_pairs(x, y)) + { + auto d = a - b; + dist += d * d; + } + return std::sqrt(dist); + } } \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 8f202aa..72d0b1d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -21,9 +21,26 @@ blt::gfx::first_person_camera_2d camera; blt::size_t som_width = 7; blt::size_t som_height = 7; -blt::size_t max_epochs = 100; +blt::size_t max_epochs = 1000; Scalar initial_learn_rate = 0.1; +int currently_selected_network = 0; +std::vector map_files_names; + +float neuron_scale = 35; +float draw_width = neuron_scale * static_cast(som_width); +float draw_height = neuron_scale * static_cast(som_height); + +void generate_network(int selection) +{ + som = std::make_unique(files[selection].normalize(), som_width, som_height, max_epochs); +} + +const char* get_selection_string(void*, int selection) +{ + return map_files_names[selection].c_str(); +} + void init(const blt::gfx::window_data&) { using namespace blt::gfx; @@ -33,15 +50,16 @@ void init(const blt::gfx::window_data&) resources.load_resources(); renderer_2d.create(); - som = std::make_unique( - *std::find_if(files.begin(), files.end(), [](const data_file_t& v) { return v.data_points.begin()->bins.size() == 32; }), - som_width, som_height, max_epochs); + for (const auto& data : files) + map_files_names.emplace_back(std::to_string(data.data_points.begin()->bins.size())); + + generate_network(currently_selected_network); } -void update(const blt::gfx::window_data& data) +void update(const blt::gfx::window_data& window_data) { using namespace blt::gfx; - global_matrices.update_perspectives(data.width, data.height, 90, 0.1, 2000); + global_matrices.update_perspectives(window_data.width, window_data.height, 90, 0.1, 2000); camera.update(); camera.update_view(global_matrices); @@ -49,6 +67,10 @@ void update(const blt::gfx::window_data& data) if (ImGui::Begin("Controls")) { + ImGui::Text("Network Select"); + if (ImGui::ListBox("##Network Select", ¤tly_selected_network, get_selection_string, nullptr, static_cast(map_files_names.size()))) + generate_network(currently_selected_network); + if (ImGui::Button("Run Epoch")) { static gaussian_function_t func; @@ -71,8 +93,8 @@ void update(const blt::gfx::window_data& data) activations.clear(); activations.resize(som->get_array().get_map().size()); - auto& meow = *std::find_if(files.begin(), files.end(), [](const data_file_t& v) { return v.data_points.begin()->bins.size() == 32; }); - for (auto& v : meow.data_points) + auto current_data_file = files[currently_selected_network].normalize(); + for (auto& v : current_data_file.data_points) { auto nearest = som->get_closest_neuron(v.bins); activations[nearest] += v.is_bad ? -1 : 1; @@ -83,20 +105,58 @@ void update(const blt::gfx::window_data& data) for (auto [i, v] : blt::enumerate(som->get_array().get_map())) { - float scale = 35; - auto activation = activations[i]; - blt::vec4 color = blt::make_color(1,1,1); + blt::vec4 color = blt::make_color(1, 1, 1); if (activation > 0) color = blt::make_color(0, static_cast(activation) / static_cast(max), 0); else if (activation < 0) color = blt::make_color(std::abs(static_cast(activation) / static_cast(min)), 0, 0); - renderer_2d.drawPointInternal(color, point2d_t{v.get_x() * scale + scale, v.get_y() * scale + scale, scale}); + renderer_2d.drawPointInternal(color, + point2d_t{v.get_x() * neuron_scale + neuron_scale, v.get_y() * neuron_scale + neuron_scale, neuron_scale}); } - renderer_2d.render(data.width, data.height); + static std::vector closest_type; + closest_type.clear(); + closest_type.resize(som->get_array().get_map().size()); + + for (auto [i, v] : blt::enumerate(som->get_array().get_map())) + { + Scalar lowest_distance = std::numeric_limits::max(); + bool is_bad = false; + for (const auto& data : current_data_file.data_points) + { + auto dist = v.dist(data.bins); + if (dist < lowest_distance) + { + lowest_distance = dist; + is_bad = data.is_bad; + } + } +// BLT_TRACE(is_bad ? -lowest_distance : lowest_distance); + closest_type[i] = is_bad ? -lowest_distance : lowest_distance; + } + + auto min_dist = *std::min_element(closest_type.begin(), closest_type.end()); + auto max_dist = *std::max_element(closest_type.begin(), closest_type.end()); + + for (auto [i, v] : blt::enumerate(som->get_array().get_map())) + { + auto type = closest_type[i]; + + blt::vec4 color = blt::make_color(1, 1, 1); + if (type >= 0) + color = blt::make_color(0, 1 - (type / max_dist) + 0.1f, 0); + else if (type < 0) + color = blt::make_color(1 - (type / min_dist) + 0.1f, 0, 0); + + renderer_2d.drawPointInternal(color, + point2d_t{draw_width + neuron_scale * 2 + v.get_x() * neuron_scale + neuron_scale, + v.get_y() * neuron_scale + neuron_scale, neuron_scale}); + } + + renderer_2d.render(window_data.width, window_data.height); } void destroy(const blt::gfx::window_data&) diff --git a/src/neuron.cpp b/src/neuron.cpp index 62912a8..c8390f5 100644 --- a/src/neuron.cpp +++ b/src/neuron.cpp @@ -49,19 +49,13 @@ namespace assign3 Scalar neuron_t::dist(const std::vector& X) const { - Scalar dist = 0; - for (auto [v, x] : blt::zip(data, X)) - { - auto loc = (v - x); - dist += loc * loc; - } - return std::sqrt(dist); + euclidean_distance_function_t dist_func; + return dist_func.distance(data, X); } Scalar neuron_t::distance(const neuron_t& n1, const neuron_t& n2) { - auto dx = n1.get_x() - n2.get_x(); - auto dy = n1.get_y() - n2.get_y(); - return std::sqrt(dx * dx + dy * dy); + euclidean_distance_function_t dist_func; + return dist_func.distance({n1.get_x(), n1.get_y()}, {n2.get_x(), n2.get_y()}); } } \ No newline at end of file