diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 4ed505ba5..20c2028d8 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -6,9 +6,6 @@ on: push: branches: ["develop"] - pull_request: - branches: ["main"] - workflow_dispatch: jobs: diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index d0d901173..fbb131cda 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -10,9 +10,6 @@ on: - "**.hpp" - "**.py" - "**.yml" - - pull_request: - branches: ["main"] jobs: build: runs-on: ${{ matrix.os }} @@ -26,12 +23,16 @@ jobs: # # To add more build types (Release, Debug, RelWithDebInfo, etc.) customize the build_type list. matrix: - os: [ubuntu-latest, windows-2019, macos-13, macos-14, macos-15] - build_type: [Release] + os: [ubuntu-latest, ubuntu-24.04-arm, windows-latest, windows-11-arm, macos-13, macos-14, macos-15] + build_type: [Debug, Release] c_compiler: [clang, cl] python_version: ['3.11', '3.12', '3.13'] include: - - os: windows-2019 + - os: windows-latest + c_compiler: cl + cpp_compiler: cl + cmake_extra_options: '' + - os: windows-11-arm c_compiler: cl cpp_compiler: cl cmake_extra_options: '' @@ -39,9 +40,10 @@ jobs: c_compiler: clang cpp_compiler: clang++ cmake_extra_options: '-GNinja' - # - os: macos-latest - # c_compiler: gcc - # cpp_compiler: g++ + - os: ubuntu-24.04-arm + c_compiler: clang + cpp_compiler: clang++ + cmake_extra_options: '' - os: macos-13 c_compiler: clang cpp_compiler: clang++ @@ -55,10 +57,14 @@ jobs: cpp_compiler: clang++ cmake_extra_options: '-GNinja' exclude: - - os: windows-2019 + - os: windows-latest + c_compiler: clang + - os: windows-11-arm c_compiler: clang - os: ubuntu-latest c_compiler: cl + - os: ubuntu-24.04-arm + c_compiler: cl - os: macos-13 c_compiler: cl - os: macos-14 @@ -79,17 +85,6 @@ jobs: echo "CMAKE_CXX_COMPILER=${{ matrix.cpp_compiler }}" >> "$GITHUB_ENV" echo "CMAKE_OPTIONS=${{ matrix.cmake_extra_options }}" >> "$GITHUB_ENV" - - name: Set up Homebrew - if: ${{ matrix.os == 'macos-13' || matrix.os == 'macos-14' || matrix.os == 'macos-15' || matrix.os == 'ubuntu-latest'}} - id: set-up-homebrew - uses: Homebrew/actions/setup-homebrew@master - - - name: Install LLVM - if: ${{ matrix.os == 'macos-13' || matrix.os == 'macos-14' || matrix.os == 'macos-15' || matrix.os == 'ubuntu-latest' }} - run: | - brew install llvm ninja - echo "PATH=$(brew --prefix llvm)/bin:$PATH" >> "$GITHUB_ENV" - - name: Set up Python uses: actions/setup-python@v5 with: @@ -108,6 +103,6 @@ jobs: uses: actions/upload-artifact@v4.3.1 with: # Artifact name - name: PyBuild ${{ matrix.os }}-cp${{ matrix.python_version }} + name: steppable-${{ matrix.os }}-cp${{ matrix.python_version }}-${{ matrix.build_type }} # A file, directory or wildcard pattern that describes what to upload path: ${{ steps.strings.outputs.py-build-output-dir }} diff --git a/.idea/.name b/.idea/.name index 27a177823..3d3b5b67d 100644 --- a/.idea/.name +++ b/.idea/.name @@ -1 +1 @@ -Steppable \ No newline at end of file +Steppable diff --git a/.idea/Steppable.iml b/.idea/Steppable.iml index ec24658e0..4fe0f9e44 100644 --- a/.idea/Steppable.iml +++ b/.idea/Steppable.iml @@ -5,11 +5,4 @@ - - - \ No newline at end of file diff --git a/.idea/dictionaries/project.xml b/.idea/dictionaries/project.xml new file mode 100644 index 000000000..5cd0ffb2d --- /dev/null +++ b/.idea/dictionaries/project.xml @@ -0,0 +1,7 @@ + + + + nwsoft + + + \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 253b7e58f..cf993c792 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -1,10 +1,35 @@ { "configurations": [ + { + "name": "Matrix::rref", + "type": "cppdbg", + "request": "launch", + "program": "${workspaceFolder}/build/src/matrix_ref", + "args": [], + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": true, + "MIMode": "lldb", + "internalConsoleOptions": "openOnSessionStart", + "preLaunchTask": "CMake: build", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + }, + { + "description": "Set Disassembly Flavor to Intel", + "text": "-gdb-set disassembly-flavor intel", + "ignoreFailures": true + } + ] + }, { "name": "Division", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/build/src/division", + "program": "${workspaceFolder}/build/src/calc_division", "args": [ "1", "2" @@ -14,6 +39,7 @@ "environment": [], "externalConsole": false, "MIMode": "lldb", + "internalConsoleOptions": "openOnSessionStart", "setupCommands": [ { "description": "Enable pretty-printing for gdb", @@ -31,13 +57,14 @@ "name": "Add", "type": "cppdbg", "request": "launch", - "program": "${workspaceFolder}/build/src/add", + "program": "${workspaceFolder}/build/src/calc_add", "args": [ "-5", "6" ], "cwd": "${workspaceFolder}/build", "preLaunchTask": "CMake: build", + "internalConsoleOptions": "openOnSessionStart", "setupCommands": [ { "description": "Enable pretty-printing for gdb", @@ -50,14 +77,15 @@ "type": "cppdbg", "request": "launch", "name": "Multiply", - "program": "${workspaceFolder}/build/src/multiply", + "program": "${workspaceFolder}/build/src/calc_multiply", "args": [ "56", "76", "+profile" ], "cwd": "${workspaceFolder}", - "preLaunchTask": "CMake: build" + "preLaunchTask": "CMake: build", + "internalConsoleOptions": "openOnSessionStart", } ] } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 957beba96..d49f8fc60 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,6 +20,8 @@ # SOFTWARE. # ##################################################################################################### +INCLUDE(cmake/build_special_hacks.cmake) + CMAKE_MINIMUM_REQUIRED(VERSION 3.20) PROJECT(Steppable) @@ -109,11 +111,11 @@ SET(COMPONENTS calc::root calc::subtract calc::trig -) + matrix::ref) # NEW_COMPONENT: PATCH Do NOT remove the previous comment. SET(TARGETS ${COMPONENTS} util) -SET(TEST_TARGETS_TEMP util fraction number factors format ${COMPONENTS}) +SET(TEST_TARGETS_TEMP util fraction number mat2d factors format ${COMPONENTS}) FOREACH(TEST_TARGET IN LISTS TEST_TARGETS_TEMP) SET(TARGET_NAME "test") @@ -131,7 +133,6 @@ ENDFOREACH() ADD_SUBDIRECTORY(src/) ADD_SUBDIRECTORY(lib/) ADD_SUBDIRECTORY(tests/) -ADD_SUBDIRECTORY(gui/) ADD_SUBDIRECTORY(include/) # The CMakeLists file there adds the include/ directory to everything FILE(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin) diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 3c7a58dcf..000000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,93 +0,0 @@ -trigger: - branches: - include: - - main - -jobs: - - job: BuildJobLinux - displayName: 'Build C++ Project (Linux)' - pool: - vmImage: 'ubuntu-latest' - steps: - - checkout: self - - - task: CMake@1 - displayName: 'Configure CMake (Linux)' - inputs: - cmakeArgs: '-B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=gcc-13 -D CMAKE_CXX_COMPILER=g++-13' - workingDirectory: '$(Build.SourcesDirectory)' - - - task: CMake@1 - displayName: 'Build C++ Project (Linux)' - inputs: - cmakeArgs: '--build .' - workingDirectory: '$(Build.SourcesDirectory)/build' - - - task: CmdLine@2 - displayName: 'Run Tests (Linux)' - inputs: - script: 'ctest --output-junit out-linux.xml' - workingDirectory: '$(Build.SourcesDirectory)/build/tests' - failOnStderr: true - - - task: PublishTestResults@2 - displayName: 'Publish Test Results (Linux)' - inputs: - testResultsFormat: 'JUnit' - testResultsFiles: '*.xml' - searchFolder: '$(Build.SourcesDirectory)/build/tests' - mergeTestResults: true - failTaskOnFailedTests: true - failTaskOnMissingResultsFile: true - testRunTitle: 'Test on Linux' - - - task: PublishPipelineArtifact@1 - displayName: 'Publish Build Artifact (Linux)' - inputs: - targetPath: '$(Build.SourcesDirectory)/build' - artifact: 'Build-Linux' - publishLocation: 'pipeline' - - - job: BuildJobWindows - displayName: 'Build C++ Project (Windows)' - pool: - vmImage: 'windows-latest' - steps: - - checkout: self - - - task: CMake@1 - displayName: 'Configure CMake (Windows)' - inputs: - cmakeArgs: '-B build -DCMAKE_BUILD_TYPE=Release' - workingDirectory: '$(Build.SourcesDirectory)' - - - task: CMake@1 - displayName: 'Build C++ Project (Windows)' - inputs: - cmakeArgs: '--build . --config Release' - workingDirectory: '$(Build.SourcesDirectory)/build' - - - task: CmdLine@2 - displayName: 'Run Tests (Windows)' - inputs: - script: 'ctest --output-junit out-windows.xml -C Release' - workingDirectory: '$(Build.SourcesDirectory)/build/tests' - failOnStderr: true - - - task: PublishTestResults@2 - displayName: 'Publish Test Results (Windows)' - inputs: - testResultsFormat: 'JUnit' - testResultsFiles: '*.xml' - searchFolder: '$(Build.SourcesDirectory)/build/tests' - mergeTestResults: true - failTaskOnFailedTests: true - failTaskOnMissingResultsFile: true - testRunTitle: 'Test on Windows' - - - task: PublishPipelineArtifact@1 - displayName: 'Publish Build Artifact (Windows)' - inputs: - targetPath: '$(Build.SourcesDirectory)/build' - artifact: 'Build-Windows' - publishLocation: 'pipeline' diff --git a/cmake/build_special_hacks.cmake b/cmake/build_special_hacks.cmake new file mode 100644 index 000000000..65fe19325 --- /dev/null +++ b/cmake/build_special_hacks.cmake @@ -0,0 +1,11 @@ +IF(STP_DEB_CALC_DIVISION_RESULT_INSPECT) + ADD_COMPILE_DEFINITIONS(STP_DEB_CALC_DIVISION_RESULT_INSPECT) +ENDIF() + +IF(STP_DEB_CALC_MULTIPLY_RESULT_INSPECT) + ADD_COMPILE_DEFINITIONS(STP_DEB_CALC_MULTIPLY_RESULT_INSPECT) +ENDIF() + +IF(STP_DEB_MATRIX_REF_RESULT_INSPECT) + ADD_COMPILE_DEFINITIONS(STP_DEB_MATRIX_REF_RESULT_INSPECT) +ENDIF() diff --git a/doxygen-awesome-css b/doxygen-awesome-css index 28ed396de..9760c3001 160000 --- a/doxygen-awesome-css +++ b/doxygen-awesome-css @@ -1 +1 @@ -Subproject commit 28ed396de19cd3d803bcb483dceefdb6d03b1b2b +Subproject commit 9760c30014131f4eacb8e96f15f3869c7bc5dd8c diff --git a/gui/CMakeLists.txt b/gui/CMakeLists.txt deleted file mode 100644 index 047c1e65a..000000000 --- a/gui/CMakeLists.txt +++ /dev/null @@ -1,35 +0,0 @@ -FUNCTION(ADD_GUI SOURCE) - GET_FILENAME_COMPONENT(NAME ${SOURCE} NAME_WE) - SET(NAME "gui_${NAME}") - - ADD_EXECUTABLE(${NAME} ${SOURCE}) - TARGET_LINK_LIBRARIES(${NAME} PRIVATE ${SDL2_LIBRARIES} ${OPENGL_LIBRARIES}) - TARGET_LINK_LIBRARIES(${NAME} PRIVATE imgui) - TARGET_INCLUDE_DIRECTORIES(${NAME} PRIVATE ${SDL2_INCLUDE_DIRS}) - TARGET_INCLUDE_DIRECTORIES(${NAME} PRIVATE "${STP_BASE_DIRECTORY}/include/imgui") - - # Link Steppable stuff - TARGET_LINK_LIBRARIES(${NAME} PRIVATE func impl) - TARGET_INCLUDE_DIRECTORIES(${NAME} PRIVATE ${STP_BASE_DIRECTORY}/include) - - if(FREETYPE_FOUND) - TARGET_LINK_LIBRARIES(${NAME} PRIVATE ${FREETYPE_LIBRARIES}) - TARGET_INCLUDE_DIRECTORIES(${NAME} PRIVATE ${FREETYPE_INCLUDE_DIRS}) - TARGET_COMPILE_DEFINITIONS(${NAME} PRIVATE IMGUI_ENABLE_FREETYPE) - TARGET_LINK_LIBRARIES(${NAME} PRIVATE imgui_freetype) - endif() -ENDFUNCTION() - -IF (STP_BUILD_GUI EQUAL 1) - # Find and link SDL2 and OpenGL - FIND_PACKAGE(SDL2 REQUIRED) - FIND_PACKAGE(OpenGL REQUIRED) - ADD_LIBRARY(impl STATIC impl/gui.cpp impl/window.cpp) - TARGET_LINK_LIBRARIES(impl PRIVATE ${SDL2_LIBRARIES} ${OPENGL_LIBRARIES} imgui) - TARGET_INCLUDE_DIRECTORIES(impl PRIVATE - ${SDL2_INCLUDE_DIRS} - "${STP_BASE_DIRECTORY}/include/imgui" - "${STP_BASE_DIRECTORY}/include") - - ADD_GUI(test.cpp) -ENDIF() diff --git a/gui/impl/gui.cpp b/gui/impl/gui.cpp deleted file mode 100644 index a37db77ab..000000000 --- a/gui/impl/gui.cpp +++ /dev/null @@ -1,163 +0,0 @@ -#include "gui.hpp" - -#include "output.hpp" - -#ifdef MACOSX - #include -#endif - -namespace steppable::gui::__internals -{ - - bool isDarkModeEnabled() - { -#ifdef MACOSX - bool isDarkMode = false; - CFPreferencesAppSynchronize(CFSTR("AppleInterfaceStyle")); - CFPropertyListRef value = CFPreferencesCopyAppValue(CFSTR("AppleInterfaceStyle"), kCFPreferencesAnyApplication); - if (value != nullptr) - { - const auto* interfaceStyle = static_cast(value); - if (CFStringCompare(interfaceStyle, CFSTR("Dark"), 0) == kCFCompareEqualTo) - isDarkMode = true; - CFRelease(value); - } - return isDarkMode; -#elif defined(LINUX) - return std::filesystem::exists("/usr/share/themes/Adwaita-dark/gtk-3.0"); -#elif defined(WINDOWS) - HKEY key; - if (RegOpenKeyExA(HKEY_CURRENT_USER, - "Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize", - 0, - KEY_QUERY_VALUE, - &key) == ERROR_SUCCESS) - { - DWORD value = 0; - DWORD size = sizeof(DWORD); - if (RegQueryValueExA(key, "AppsUseLightTheme", nullptr, nullptr, reinterpret_cast(&value), &size) == - ERROR_SUCCESS) - return value == 0; - } - return false; -#else - return false; -#endif - } - - void addFontIfExistent(const ImGuiIO* io, - const std::filesystem::path& path, - const ImFontConfig* config, - const ImWchar* ranges) noexcept - { - if (io->Fonts->Fonts.empty() and config->MergeMode) - config = nullptr; - if (std::filesystem::exists(path)) - { -#ifdef DEBUG - output::info("addIfExistent"s, "Added font {0}"s, { path }); -#endif - io->Fonts->AddFontFromFileTTF(path.c_str(), 15.0F, config, ranges); - } - } - - void loadFonts(const ImGuiIO* io) noexcept - { - ImFontConfig config; - config.MergeMode = true; -#ifdef WINDOWS - // WINDOWS fonts - // ------------- - // Chinese -> Microsoft YaHei - // Cyrillic -> Segoe UI -----------------+ - // Greek -> Segoe UI -----------------| - // Japanese -> Meiryo | - // Korean -> Malgun Gothic +--> Top-priority - // Thai -> Leelawadee | - // Vietnamese -> Segoe UI -----------------+ - - // Load top-priority fonts - addIfExistent(io, "C:/Windows/Fonts/segoeui.ttf", &config, io->Fonts->GetGlyphRangesCyrillic()); - addIfExistent(io, "C:/Windows/Fonts/segoeui.ttf", &config, io->Fonts->GetGlyphRangesDefault()); - addIfExistent(io, "C:/Windows/Fonts/segoeui.ttf", &config, io->Fonts->GetGlyphRangesGreek()); - addIfExistent(io, "C:/Windows/Fonts/segoeui.ttf", &config, io->Fonts->GetGlyphRangesVietnamese()); - - // Load Chinese fonts - addIfExistent(io, "C:/Windows/Fonts/msyh.ttc", &config, io->Fonts->GetGlyphRangesChineseFull()); - - // Load Japanese fonts - addIfExistent(io, "C:/Windows/Fonts/meiryo.ttc", &config, io->Fonts->GetGlyphRangesJapanese()); - - // Load Korean fonts - addIfExistent(io, "C:/Windows/Fonts/malgun.ttf", &config, io->Fonts->GetGlyphRangesKorean()); - - // Load Thai fonts - addIfExistent(io, "C:/Windows/Fonts/leelawad.ttf", &config, io->Fonts->GetGlyphRangesThai()); -#elif defined(MACOSX) - // MACOS fonts - // ------------- - // Chinese -> PingFang SC (*) - // Cyrillic -> SF Pro -----------------+ - // Greek -> SF Pro -----------------| - // Japanese -> Hiragino Sans | - // Korean -> Apple SD Gothic Neo +--> Top-priority - // Thai -> Thonburi | - // Vietnamese -> SF Pro -----------------+ - // - // (*) NOTE: PingFang may not be available on all systems, but STHeiti Medium is a good alternative. - - // Load top-priority fonts - addFontIfExistent(io, "/Library/Fonts/SF-Pro.ttf", &config, io->Fonts->GetGlyphRangesCyrillic()); - addFontIfExistent(io, "/Library/Fonts/SF-Pro.ttf", &config, io->Fonts->GetGlyphRangesDefault()); - addFontIfExistent(io, "/Library/Fonts/SF-Pro.ttf", &config, io->Fonts->GetGlyphRangesGreek()); - addFontIfExistent(io, "/Library/Fonts/SF-Pro.ttf", &config, io->Fonts->GetGlyphRangesVietnamese()); - - // Load Chinese fonts - addFontIfExistent(io, "/System/Library/Fonts/PingFang.ttc", &config, io->Fonts->GetGlyphRangesChineseFull()); - addFontIfExistent( - io, "/System/Library/Fonts/STHeiti Medium.ttc", &config, io->Fonts->GetGlyphRangesChineseFull()); - - // Load Japanese fonts - addFontIfExistent(io, "/System/Library/Fonts/Hiragino.ttc", &config, io->Fonts->GetGlyphRangesJapanese()); - - // Load Korean fonts - addFontIfExistent(io, "/System/Library/Fonts/AppleSDGothicNeo.ttc", &config, io->Fonts->GetGlyphRangesKorean()); - - // Load Thai fonts - addFontIfExistent(io, "/System/Library/Fonts/Thonburi.ttf", &config, io->Fonts->GetGlyphRangesThai()); - addFontIfExistent( - io, "/System/Library/Fonts/Supplemental/Ayuthaya.ttf", &config, io->Fonts->GetGlyphRangesThai()); -#elif defined(LINUX) - // LINUX fonts - // ------------- - // Chinese -> WenQuanYi Zen Hei - // Cyrillic -> DejaVu Sans -----------------+ - // Greek -> DejaVu Sans -----------------| - // Japanese -> Takao Gothic | - // Korean -> Nanum Gothic +--> Top-priority - // Thai -> Garuda | - // Vietnamese -> DejaVu Sans -----------------+ - - // Load top-priority fonts - addIfExistent(io, "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", &config, io->Fonts->GetGlyphRangesCyrillic()); - addIfExistent(io, "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", &config, io->Fonts->GetGlyphRangesDefault()); - addIfExistent(io, "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", &config, io->Fonts->GetGlyphRangesGreek()); - addIfExistent(io, "/usr/share/fonts/TTF/DejaVuSans-Bold.ttf", &config, io->Fonts->GetGlyphRangesVietnamese()); - - // Load Chinese fonts - addIfExistent(io, "/usr/share/fonts/TTF/wqy-zenhei.ttc", &config, io->Fonts->GetGlyphRangesChineseFull()); - - // Load Japanese fonts - addIfExistent(io, "/usr/share/fonts/TTF/takao-mincho.ttf", &config, io->Fonts->GetGlyphRangesJapanese()); - - // Load Korean fonts - addIfExistent(io, "/usr/share/fonts/TTF/NanumGothic.ttf", &config, io->Fonts->GetGlyphRangesKorean()); - - // Load Thai fonts - addIfExistent(io, "/usr/share/fonts/TTF/garuda.ttf", &config, io->Fonts->GetGlyphRangesThai()); -#endif - // Add the default font as well. - io->Fonts->AddFontDefault(&config); - io->Fonts->Build(); - } -} // namespace steppable::gui::__internals diff --git a/gui/impl/window.cpp b/gui/impl/window.cpp deleted file mode 100644 index 389ca9ad1..000000000 --- a/gui/impl/window.cpp +++ /dev/null @@ -1,143 +0,0 @@ -#include "backends/imgui_impl_opengl3.h" -#include "backends/imgui_impl_sdl2.h" -#include "gui.hpp" -#include "imgui.h" -#include "output.hpp" -#include "platform.hpp" - -#include -#include -#include - -using namespace steppable; -using namespace steppable::gui::__internals; -using namespace steppable::__internals::utils; - -namespace steppable::gui -{ - void runWindow(const std::string& name, const std::function& predicate) - { - // Setup SDL - if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0) - { - steppable::output::error("SDL_Init", std::string(SDL_GetError())); - programSafeExit(-1); - } - - // Decide GL+GLSL versions -#if defined(IMGUI_IMPL_OPENGL_ES2) - // GL ES 2.0 + GLSL 100 - const char* glsl_version = "#version 100"; - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); -#elif defined(__APPLE__) - // GL 3.2 Core + GLSL 150 - const char* glsl_version = "#version 150"; - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2); -#else - // GL 3.0 + GLSL 130 - const char* glsl_version = "#version 130"; - SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); - SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0); -#endif - - // From 2.0.18: Enable native IME. - SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1"); - - // Create window with graphics context - SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); - SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); - SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); - auto window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI); - SDL_Window* window = - SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags); - if (window == nullptr) - { - steppable::output::error("SDL_CreateWindow"s, std::string(SDL_GetError())); - programSafeExit(-1); - } - - SDL_GLContext gl_context = SDL_GL_CreateContext(window); - SDL_GL_MakeCurrent(window, gl_context); - SDL_GL_SetSwapInterval(1); // Enable vsync - - // Setup Dear ImGui context - IMGUI_CHECKVERSION(); - ImGui::CreateContext(); - ImGuiIO& io = ImGui::GetIO(); - io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls - io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; - - // Setup Platform/Renderer backends - ImGui_ImplSDL2_InitForOpenGL(window, gl_context); - ImGui_ImplOpenGL3_Init(glsl_version); - - bool done = false; - ImVec4 clear_color = ImVec4(0.22, 0.22, 0.22, 1.00); - std::array buf{}; - loadFonts(&io); - - while (not done) - { - // Poll and handle events (inputs, window resize, etc.) - // You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use - // your inputs. - // - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or - // clear/overwrite your copy of the mouse data. - // - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or - // clear/overwrite your copy of the keyboard data. Generally you may always pass all inputs to dear imgui, - // and hide them from your application based on those two flags. - SDL_Event event; - while (SDL_PollEvent(&event) != 0) - { - ImGui_ImplSDL2_ProcessEvent(&event); - if (event.type == SDL_QUIT) - done = true; - if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && - event.window.windowID == SDL_GetWindowID(window)) - done = true; - } - - // Start the Dear ImGui frame - ImGui_ImplOpenGL3_NewFrame(); - ImGui_ImplSDL2_NewFrame(); - ImGui::NewFrame(); - - predicate(); - - if (isDarkModeEnabled()) - { - glClearColor(0.22, 0.22, 0.22, 1.0); - ImGui::StyleColorsDark(); - } - else - { - glClearColor(0.95, 0.95, 0.95, 1.0); - ImGui::StyleColorsLight(); - } - - // Rendering - ImGui::Render(); - glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y); - glClear(GL_COLOR_BUFFER_BIT); - ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); - SDL_GL_SwapWindow(window); - } - - // Cleanup - ImGui_ImplOpenGL3_Shutdown(); - ImGui_ImplSDL2_Shutdown(); - ImGui::DestroyContext(); - - SDL_GL_DeleteContext(gl_context); - SDL_DestroyWindow(window); - SDL_Quit(); - } -} // namespace steppable::gui diff --git a/gui/test.cpp b/gui/test.cpp deleted file mode 100644 index 8a816ba60..000000000 --- a/gui/test.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include "gui.hpp" -#include "imgui.h" - -#include -#include - -// NOTE TO DEVELOPERS: This file is a test file for the GUI module of the Steppable library. -// When building the GUI part for the first time, this can come in handy. -// Run the executable to see if the GUI window works. - -using namespace steppable; -using namespace steppable::gui::__internals; - -void setUpContents() -{ - ImGui::Begin("Hello, world!"); - ImGui::Text("This is some useful text."); - ImGui::End(); -} - -int main() { gui::runWindow("", &setUpContents); } diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt index 6082f9837..630de4980 100644 --- a/include/CMakeLists.txt +++ b/include/CMakeLists.txt @@ -24,35 +24,3 @@ FOREACH(TARGET IN LISTS TEST_TARGETS) TARGET_INCLUDE_DIRECTORIES(${TARGET} PUBLIC ${STP_BASE_DIRECTORY}/include/) ENDFOREACH(TARGET) - -IF (STP_BUILD_GUI EQUAL 1) - FIND_PACKAGE(SDL2 REQUIRED) - ADD_LIBRARY(imgui - imgui/imgui.cpp - imgui/imgui_draw.cpp - imgui/imgui_widgets.cpp - imgui/imgui_tables.cpp - imgui/imgui_demo.cpp - imgui/backends/imgui_impl_sdl2.cpp - imgui/backends/imgui_impl_opengl3.cpp) - - # If we have freetype, we can use it to render text - FIND_PACKAGE(Freetype QUIET) - IF (FREETYPE_FOUND) - MESSAGE(STATUS "Found Freetype") - ADD_LIBRARY(imgui_freetype STATIC "${STP_BASE_DIRECTORY}/include/imgui/misc/freetype/imgui_freetype.cpp") - TARGET_INCLUDE_DIRECTORIES(imgui_freetype PRIVATE "${STP_BASE_DIRECTORY}/include/imgui") - TARGET_LINK_LIBRARIES(imgui_freetype PRIVATE ${FREETYPE_LIBRARIES}) - TARGET_LINK_LIBRARIES(imgui PRIVATE imgui_freetype) - TARGET_INCLUDE_DIRECTORIES(imgui_freetype PRIVATE ${FREETYPE_INCLUDE_DIRS}) - TARGET_COMPILE_DEFINITIONS(imgui PRIVATE IMGUI_ENABLE_FREETYPE) - ENDIF() - - IF (CMAKE_SYSTEM_NAME STREQUAL "Darwin") - TARGET_LINK_LIBRARIES(imgui PRIVATE "-framework CoreFoundation") - ENDIF() - - TARGET_LINK_LIBRARIES(imgui PRIVATE SDL2::SDL2) - TARGET_INCLUDE_DIRECTORIES(imgui PRIVATE ${SDL2_INCLUDE_DIRS}) - TARGET_INCLUDE_DIRECTORIES(imgui PRIVATE "${STP_BASE_DIRECTORY}/include/imgui") -ENDIF() diff --git a/include/fn/calc.hpp b/include/fn/calc.hpp index 3d0302bcb..0b4916d72 100644 --- a/include/fn/calc.hpp +++ b/include/fn/calc.hpp @@ -37,6 +37,8 @@ #include "fn/root.hpp" #include "output.hpp" +#include "steppable/number.hpp" +#include "types/result.hpp" #include #include @@ -44,8 +46,8 @@ using namespace std::literals; /** - * @namespace steppable::__internals - * @brief The namespace containing internal functions for the Steppable library. + * @namespace steppable::__internals::calc + * @brief The namespace containing number calculating functions for the Steppable library. */ namespace steppable::__internals::calc { @@ -188,6 +190,17 @@ namespace steppable::__internals::calc */ std::string subtract(const std::string& a, const std::string& b, int steps = 2, bool noMinus = false); + /** + * @brief Calculate the error between values a and b. + * @details Gets the absolute difference between a and b. + * + * @param a Value 1 + * @param b Value 2 + * + * @return The absolute value of the differnece between the values. + */ + types::Result error(const std::string& a, const std::string& b); + /** * @brief Takes the n-th root of a numer. * diff --git a/include/fn/root.hpp b/include/fn/root.hpp index 062be5e49..e4add3192 100644 --- a/include/fn/root.hpp +++ b/include/fn/root.hpp @@ -24,6 +24,10 @@ #include +/** + * @namespace steppable::__internals + * @brief The namespace containing internal functions for the Steppable library. + */ namespace steppable::__internals::calc { /** diff --git a/include/gui.hpp b/include/gui.hpp deleted file mode 100644 index 21a2cb34e..000000000 --- a/include/gui.hpp +++ /dev/null @@ -1,91 +0,0 @@ -/************************************************************************************************** - * Copyright (c) 2023-2025 NWSOFT * - * * - * 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 - -using namespace std::literals; - -/** - * @brief The namespace for the internal components of the GUI module of the Steppable library. - * @details This namespace contains the internal components of the GUI module of the Steppable library. - * For example, methods that gets the system fonts, checks if the dark mode is enabled, and loads the fonts. - * @note This namespace is not intended for use by the end user. - */ -namespace steppable::gui::__internals -{ - /** - * @brief Checks if the dark mode is enabled. - * @return True if the dark mode is enabled, false otherwise. - */ - bool isDarkModeEnabled(); - - /** - * Attempts to add a font to the application if it exists in the system. - * - * This method checks if a specified font is available on the system. If the font is found, - * it is added to the application's font resources, making it available for use within the application. - * If the font does not exist, the method will not perform any action or may log an error or warning, - * depending on implementation details. - * - * @param io A pointer to an ImGuiIO object to enable configuration. - * @param path The absolute path to the font file. - * @param config A pointer to an ImFontConfig object to enable font configuration. - * @param ranges A pointer to an array of ImWchar objects to enable character range configuration. - */ - void addFontIfExistent(const ImGuiIO* io, - const std::filesystem::path& path, - const ImFontConfig* config, - const ImWchar* ranges) noexcept; - - /** - * @brief Loads the fonts for the application. - * @details This method tries to find the system fonts that can display most character sets. - * @param io A pointer to an ImGuiIO object to enable configuration - */ - void loadFonts(const ImGuiIO* io) noexcept; -} // namespace steppable::gui::__internals - -/** - * @brief The namespace for the GUI components of the Steppable library. This is the main namespace for the GUI - * components. - */ -namespace steppable::gui -{ - /** - * @brief Runs the main window of the application. - * @details This method replaces the long ininitalization process of the application with a simple - * call to the runWindow method. This method initializes the SDL2 and OpenGL backends for the application, - * then runs the main window loop. The main window loop is responsible for rendering the application's - * contents and handling user input. - * - * @param name The name of the window. - * @param predicate The function that will be called to render the window. - */ - void runWindow(const std::string& name, const std::function& predicate); -} // namespace steppable::gui diff --git a/include/imgui b/include/imgui deleted file mode 160000 index dad58f2f6..000000000 --- a/include/imgui +++ /dev/null @@ -1 +0,0 @@ -Subproject commit dad58f2f6839e5fcadaf95b57cf6174ef0274e52 diff --git a/include/platform.hpp b/include/platform.hpp index aa8a63c73..36d6890f1 100644 --- a/include/platform.hpp +++ b/include/platform.hpp @@ -38,6 +38,8 @@ #include #include +#undef timeval + using namespace std::literals; /** @@ -58,6 +60,10 @@ namespace steppable::__internals::utils */ inline void programSafeExit(const int status) { +#ifdef STP_BINDINGS + return; +#endif + #ifdef MACOSX exit(status); // NOLINT(concurrency-mt-unsafe) #else diff --git a/include/rounding.hpp b/include/rounding.hpp index fb21088fc..21c7bcfde 100644 --- a/include/rounding.hpp +++ b/include/rounding.hpp @@ -21,6 +21,7 @@ **************************************************************************************************/ #pragma once +#include #include namespace steppable::__internals::numUtils @@ -46,9 +47,10 @@ namespace steppable::__internals::numUtils * * @param[in] _number The number to round. * @param[in] digits The number of decimal places to round to. + * @param[in] mode The mode of rounding. Defaults to rounding off. * @return The rounded number. */ - std::string roundOff(const std::string& _number, size_t digits = 0); + std::string roundOff(const std::string& _number, size_t digits = 0, Rounding mode = Rounding::ROUND_OFF); /** * @brief Move the decimal places of a number. diff --git a/include/fraction.hpp b/include/steppable/fraction.hpp similarity index 93% rename from include/fraction.hpp rename to include/steppable/fraction.hpp index 3b1c4a6a9..255ebab58 100644 --- a/include/fraction.hpp +++ b/include/steppable/fraction.hpp @@ -21,7 +21,7 @@ **************************************************************************************************/ /** - * @file fraction.hpp + * @file steppable/fraction.hpp * @brief This file contains the definition of the Fraction class, which represents a fraction in math. * * @author Andy Zhang @@ -30,7 +30,7 @@ #pragma once -#include "number.hpp" +#include "steppable/number.hpp" #include #include @@ -95,6 +95,13 @@ namespace steppable */ Fraction operator+(const Fraction& rhs) const; + /** + * @brief Unary plus operator. + * @details Does nothing. Simply returns a new instance of the fraction. + * @return A new instance of the fraction, with euqal value and equal in sign. + */ + Fraction operator+() const; + /** * @brief Subtracts a fraction from another fraction. * This function does it by doing a simple fraction subtraction and returns the difference. @@ -104,6 +111,13 @@ namespace steppable */ Fraction operator-(const Fraction& rhs) const; + /** + * @brief Unary minus operator. + * @details Converts the fraction to itself with the opposite sign. Returns a new instance of the fraction. + * @return A fraction equal in value but opposite in sign. + */ + Fraction operator-() const; + /** * @brief Multiplies two fractions together. * This function does it by doing a simple fraction multiplication and returns the sum. diff --git a/include/steppable/mat2d.hpp b/include/steppable/mat2d.hpp new file mode 100644 index 000000000..9d98a4b30 --- /dev/null +++ b/include/steppable/mat2d.hpp @@ -0,0 +1,460 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * 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. * + **************************************************************************************************/ + +/** + * @file mat2d.hpp + * @brief Defines methods and classes for 2D matrix manipulation. + * @author Andy Zhang + * @date 31 May 2025 + */ + +#pragma once + +#include "steppable/number.hpp" +#include "testing.hpp" +#include "types/point.hpp" + +#include +#include +#include + +namespace steppable +{ + /** + * @brief Alias for a 2D matrix represented as a vector of vectors. + * @tparam NumberT The type of the numbers in the matrix. + */ + template + using MatVec2D = std::vector>; + + namespace prettyPrint::printers + { + /** + * @brief Pretty prints a matrix. + * @param matrix The matrix to be pretty printed. + * @return A string representation of the matrix. + */ + std::string ppMatrix(const MatVec2D& matrix, int endRows = 0); + } // namespace prettyPrint::printers + + namespace __internals::symbols + { + constexpr std::string_view MATRIX_LEFT_TOP = "\u23A1"; + constexpr std::string_view MATRIX_LEFT_MIDDLE = "\u23A2"; + constexpr std::string_view MATRIX_LEFT_BOTTOM = "\u23A3"; + constexpr std::string_view MATRIX_RIGHT_TOP = "\u23A4"; + constexpr std::string_view MATRIX_RIGHT_MIDDLE = "\u23A5"; + constexpr std::string_view MATRIX_RIGHT_BOTTOM = "\u23A6"; + } // namespace __internals::symbols + + /** + * @class Matrix + * @brief Represents a mathematical matrix. + */ + class Matrix + { + size_t _cols; ///< The number of columns in the matrix. + size_t _rows; ///< The number of rows in the matrix. + size_t prec = 10; ///< Precision of numbers in the matrix. + MatVec2D data; ///< The data of the matrix. + + /** + * @brief Checks whether a point is inside the matrix. Errors and exits the program if not. + * @param point The point to check. + */ + void _checkIdxSanity(const YXPoint* point) const; + + /** + * @brief Checks whether the matrix data is correct in format. + * @details This method checks for non-uniform rows inside the matrix. + * + * @param data The matrix data vector. + */ + static void _checkDataSanity(const MatVec2D& data); + + /** + * @brief Rounds off all data inside a vector to a specified precision. + * @param data A double `std::vector` object containing matrix data. + * @param prec Precision of the matrix. + */ + static MatVec2D roundOffValues(const MatVec2D& data, size_t prec); + + public: + /** + * @brief Round off all values to a specified precision. + * @param prec Precision of the new matrix. + * @return A new instance of the current matrix, with values rounded to the desired precision. + */ + [[nodiscard]] Matrix roundOffValues(size_t prec) const; + + /** + * @brief Default constructor for the Matrix class. + */ + Matrix(); + + /** + * @brief Constructs a matrix with specified dimensions and an optional fill value. + * @param rows The number of rows. + * @param cols The number of columns. + * @param fill The value to fill the matrix with (default is "0"). + */ + Matrix(size_t rows, size_t cols, const Number& fill = Number("0")); + + /** + * @brief Constructs a matrix from a 2D vector of data. + * @param data The 2D vector representing the matrix data. + * @param prec Precision of the numbers. + */ + Matrix(const MatVec2D& data, size_t prec = 5); + + /** + * @brief Constructs a matrix from a 2D vector of C++ numbers. + * @param data The 2D vector representing the matrix data. + * @param prec Precision of the numbers. + * @tparam ValueT Type of value in the `data` parameter. + */ + template + Matrix(const MatVec2D& data, const size_t prec) : + _cols(data.front().size()), _rows(data.size()), prec(prec) + { + this->data = std::vector(_rows, std::vector(_cols, Number("0"))); + for (size_t i = 0; i < _rows; i++) + for (size_t j = 0; j < _cols; j++) + this->data[i][j] = Number(data[i][j], prec); + } + + /** + * @brief Converts the matrix to its reduced row echelon form. + * @return A new Matrix in reduced row echelon form. + */ + [[nodiscard]] Matrix rref() const; + + /** + * @brief Converts a matrix to row echelon form. + * @details Converts a matrix to row echelon form, with row elimation and swapping. + * @return A new Matrix in row echelon form. + */ + [[nodiscard]] Matrix ref() const; + + /** + * @brief Find the determinant of a matrix. + * @details Calculates the reduced echelon form of the matrix. + * @return A Number object representing the determinant. + */ + [[nodiscard]] Number det() const; + + /** + * @brief Presents the matrix as a string. + * @return A string representation of the matrix. + */ + [[nodiscard]] std::string present(int endRows = 0) const; + + /** + * @brief Creates a matrix filled with ones. + * @param rows The number of rows. + * @param cols The number of columns. + * @return A Matrix filled with ones. + */ + static Matrix ones(size_t rows, size_t cols); + + /** + * @brief Creates a matrix filled with zeros. + * @param rows The number of rows. + * @param cols The number of columns. + * @return A Matrix filled with zeros. + */ + static Matrix zeros(size_t rows, size_t cols); + + /** + * @brief Creates a diagnal matrix + * + * @param colsRows Number of columns and rows. + * @param fill Number to fill into the matrix. + * @return A diagnal matrix filled with the specified values. + */ + static Matrix diag(size_t colsRows, const Number& fill = 1); + + /** + * @brief Creates a diagnal matrix + * + * @param colsRows Number of columns and rows. + * @param fill Number to fill into the matrix. + * @tparam NumberT Type of the number. + * @return A diagnal matrix filled with the specified values. + */ + template + static Matrix diag(const size_t colsRows, const NumberT& fill = 0) + { + return diag(colsRows, Number(fill)); + } + + /** + * @brief Alias for the data `begin()` method to allow iterating over the matrix rows, + * @return The beginning of the matrix rows. + */ + auto begin() { return data.begin(); } + + /** + * @brief Alias for the data `end()` method to allow iterating over the matrix rows, + * @return The end of the matrix rows. + */ + auto end() { return data.end(); } + + /** + * @brief Calculates the rank of a matrix. + * @details Calculates the number of non-zero rows when the matrix is converted to row-echelon form. + * @return The rank of the matrix. + */ + [[nodiscard]] Number rank() const; + + /** + * @brief Transposes a matrix. + * @details Flips the rows and columns of the matrix and returns a new instance of the transposed matrix. + * @return An instance of the transposed matrix. + */ + [[nodiscard]] Matrix transpose() const; + + /** + * @brief Add a matrix to another matrix. + * @details Performs matrix addition, where corresponding elements in both matrices are added. + * + * @param rhs The other matrix. + * @return A new matrix with the addition result. + */ + Matrix operator+(const Matrix& rhs) const; + + /** + * @brief Adds the other matrix to current matrix and assigns result to this matrix. + * + * @details This operator performs in-place matrix addition, updating each element of the matrix by + * addition. + * + * @param rhs The other matrix. + * @return Instance of a new matrix after addition. + */ + Matrix operator+=(const Matrix& rhs); + + /** + * @brief Unary plus operator. + * @details Does nothing. Simply returns a new instance of the current matrix. + * @return A new instance of the current matrix. + */ + Matrix operator+() const; + + /** + * @brief Subtract a matrix from another matrix. + * @details Performs matrix subtraction, where corresponding elements in both matrices are subtracted. + * + * @param rhs The other matrix. + * @return A new matrix with the subtraction result. + */ + Matrix operator-(const Matrix& rhs) const; + + /** + * @brief Subtracts the other matrix from current matrix and assigns result to this matrix. + * + * @details This operator performs in-place matrix subtraction, updating each element of the matrix by + * subtraction. + * + * @param rhs The other matrix. + * @return Instance of a new matrix after subtraction. + */ + Matrix operator-=(const Matrix& rhs); + + /** + * @brief Unary minus operator. + * @details Converts the matrix to itself with all values being converted to the opposite sign. Returns a new + * instance of the matrix. + * @return A matrix with equal values in the opposite sign. + */ + Matrix operator-() const; + + /** + * @brief Scalar multiplication. + * @details Multiplies each element of the matrix by a scalar. + * + * @param rhs The scalar to multiply. + * @return A new matrix after the scalar multiplication + */ + Matrix operator*(const Number& rhs) const; + + /** + * @brief Multiplies the current matrix by a scalar value and assigns the result to this matrix. + * + * @details This operator performs in-place scalar multiplication, updating each element of the matrix + * by multiplying it with the provided scalar value. + * + * @param rhs The scalar value to multiply each element of the matrix by. + * @return Instance of a modified matrix after multiplication. + */ + Matrix operator*=(const Number& rhs); + + /** + * @brief Matrix multiplication. + * @details Multiplies a matrix by another matrix, returning the resulting matrix. + * + * @param rhs The other matrix to multiply. + * @return The new matrix after multiplying. + */ + Matrix operator*(const Matrix& rhs) const; + + /** + * @brief Multiplies this matrix by another matrix and assigns the result to this matrix. + * + * @details Performs in-place matrix multiplication with the given right-hand side (rhs) matrix. + * The current matrix is updated to be the product of itself and rhs. + * + * @param rhs The matrix to multiply with this matrix. + * @return The updated matrix after multiplication. + */ + Matrix operator*=(const Matrix& rhs); + + /** + * @brief Raises the current matrix to a certain power. + * @details Only positive integers (including zero) and the negative number -1 are allowed. Only square matrices + * are supported. The matrix is multiplied by itself for a specified number of times. + * + * @param times Times to raise the matrix to. + * @return A new matrix of the power result. + */ + Matrix operator^(const Number& times) const; + + /** + * @brief Raises the current matrix to a certain power, and assigns result to the current matrix. + * @details Only positive integers (including zero) and the negative number -1 are allowed. Only square matrices + * are supported. + * + * @param times Times to raise the matrix to. + * @return The current matrix. + */ + Matrix operator^=(const Number& times); + + /** + * @brief Join a matrix to the right of the current matrix. + * @details Joins a matrix to the right of the current matrix. Requires two matrices to have to same number of + * rows. + * + * @param rhs The other matrix to join. + * @return A new matrix where the two matrices are joined. + */ + Matrix operator<<(const Matrix& rhs) const; + + /** + * @brief Join a matrix to the right of the current matrix, then assign the result to the current one. + * @details Joins a matrix to the right of the current matrix. Requires two matrices to have to same number of + * rows. The result is assigned to the current matrix. + * + * @param rhs The other matrix to join. + * @return A new matrix where the two matrices are joined. + */ + Matrix operator<<=(const Matrix& rhs); + + /** + * @brief Join a matrix to the left of the current matrix. + * @details Joins a matrix to the left of the current matrix. Requires two matrices to have to same number of + * rows. + * + * @param rhs The other matrix to join. + * @return A new matrix where the two matrices are joined. + */ + Matrix operator>>(const Matrix& rhs) const; + + /** + * @brief Join a matrix to the left of the current matrix, then assign the result to the current one. + * @details Joins a matrix to the left of the current matrix. Requires two matrices to have to same number of + * rows. The result is assigned to the current matrix. + * + * @param rhs The other matrix to join. + * @return A new matrix where the two matrices are joined. + */ + Matrix operator>>=(const Matrix& rhs); + + /** + * @brief Test for equal matrices. + * @details If the current matrix is equal to the other matrix, i.e., equal in all of its values, returns True. + * + * @param rhs The other matrix. + * @return Whether the current matrix is equal to the other one. + */ + bool operator==(const Matrix& rhs) const; + + /** + * @brief Test for unequal matrices. + * @details If the current matrix is not equal to the other matrix, i.e., equal in all of its values, returns + * True. + * + * @param rhs The other matrix. + * @return Whether the current matrix is not equal to the other one. + */ + bool operator!=(const Matrix& rhs) const; + + /** + * @brief Gets the element at a point in the matrix. + * @details Tries to find an element at the index specified by the point. Prints out error and exits when the + * index does not exist. + * + * @param point The position of the element to find. + * @return A reference to the element at that position. + */ + Number& operator[](const YXPoint& point); + + /** + * @brief Accesses the matrix element at the specified YXPoint. + * + * This operator allows read-only access to the matrix element located at the given + * YXPoint coordinates. + * + * @param point The YXPoint specifying the (y, x) coordinates of the element. + * @return The value of the matrix element at the specified coordinates. + */ + Number operator[](const YXPoint& point) const; + + /** + * @brief Accesses the matrix element at the specified YXPoint. + * + * This operator allows read-only access to the matrix element located at the given + * YXPoint coordinates. + * + * @param point The YXPoint specifying the (y, x) coordinates of the element. + * @return The value of the matrix element at the specified coordinates. + */ + Matrix operator[](const YX2Points& point) const; + + /** + * @brief Get the number of rows in the matrix. + * @return The number of rows in the matrix. + */ + [[nodiscard]] size_t getRows() const { return _rows; } + + /** + * @brief Get the number of columns in the matrix. + * @return The number of columns in the matrix. + */ + [[nodiscard]] size_t getCols() const { return _cols; } + + /** + * @brief Get the data std::vector object from the matrix. + * @return A std::vector object containing all the matrix data. + */ + [[nodiscard]] MatVec2D getData() const { return data; } + }; +} // namespace steppable diff --git a/include/number.hpp b/include/steppable/number.hpp similarity index 72% rename from include/number.hpp rename to include/steppable/number.hpp index 2b3ef22b7..f2756e251 100644 --- a/include/number.hpp +++ b/include/steppable/number.hpp @@ -21,7 +21,7 @@ **************************************************************************************************/ /** - * @file number.hpp + * @file steppable/number.hpp * @brief Contains the definition of the Number class, which offers an API for arbitrary precision arithmetic. * * @author Andy Zhang @@ -30,7 +30,12 @@ #pragma once +#include "testing.hpp" +#include "util.hpp" + +#include #include +#include /** * @namespace steppable @@ -42,7 +47,7 @@ namespace steppable * @enum RoundingMode * @brief Specifies how Steppable should round the number in operations. */ - enum RoundingMode + enum class RoundingMode : std::uint8_t { /// @brief Use the higher precision whenever possible. USE_MAXIMUM_PREC = 0xFF, @@ -60,13 +65,19 @@ namespace steppable DISCARD_ALL_DECIMALS = 0x00 }; + enum class Rounding : std::uint8_t + { + ROUND_DOWN = 0x00, ///< Rounds the number down. + ROUND_UP = 0x01, ///< Rounds the number up. + ROUND_OFF = 0x02, ///< Rounds the number off. + }; + /** * @class Number * @brief Represents a number with arbitrary precision. It basically stores the value as a string. */ class Number { - private: /// @brief The value of the number. std::string value; @@ -74,17 +85,60 @@ namespace steppable size_t prec; /// @brief The rounding mode of the number. - RoundingMode mode = USE_CURRENT_PREC; + RoundingMode mode = RoundingMode::USE_CURRENT_PREC; + + template<__internals::utils::StringLiteral fnName> + [[nodiscard]] size_t determinePrec(const Number& rhs) const + { + size_t usePrec = 0; + if (mode == RoundingMode::USE_MAXIMUM_PREC) + usePrec = std::max(prec, rhs.prec); + else if (mode == RoundingMode::USE_MINIMUM_PREC) + usePrec = std::min(prec, rhs.prec); + else if (mode == RoundingMode::USE_CURRENT_PREC) + usePrec = prec; + else if (mode == RoundingMode::USE_OTHER_PREC) + usePrec = rhs.prec; + else if (mode == RoundingMode::DISCARD_ALL_DECIMALS) + usePrec = 0; + else + { + usePrec = 0; + output::warning(std::string(fnName.value), "Invalid precision specified"s); + } + + return usePrec; + } public: /// @brief The default constructor. Initializes the number with a value of 0. Number(); + Number(const Number& rhs); + /** * @brief Initializes a number with a specified value. * @note By default, the value is 0. */ - Number(std::string value = "0", size_t prec = 0, RoundingMode mode = USE_CURRENT_PREC); + Number(std::string value = "0", size_t prec = 10, RoundingMode mode = RoundingMode::USE_CURRENT_PREC); + + /** + * @brief Initializes a number with a C/C++ long double value. + * @note No matter how the number is specified, it will always be converted to a string for storage. + */ + template + Number(ValueT value, size_t prec = 10, RoundingMode mode = RoundingMode::USE_CURRENT_PREC) : + value(std::to_string(value)), prec(prec), mode(mode) + { + } + + void set(std::string newVal) { value = std::move(newVal); } + + void setPrec(size_t newPrec, RoundingMode mode = RoundingMode::USE_CURRENT_PREC) + { + this->mode = mode; + prec = newPrec; + } /** * @brief Adds two numbers together. @@ -114,6 +168,15 @@ namespace steppable */ Number operator/(const Number& rhs) const; + /** + * @brief Takes a modulus operation. + * @details Divides and takes the nearest quotient. + * + * @param rhs The other number. + * @return The modulus of the two numbers. + */ + [[nodiscard]] Number mod(const Number& rhs) const; + /** * @brief Calculates the remainder of two numbers. (Modulus) * @param rhs The number to divide. @@ -126,7 +189,7 @@ namespace steppable * @param rhs The power to raise the number to. * @return The result of the power operation. */ - Number operator^(const Number& rhs) const; + Number operator^(const Number& rhs); /** * @brief Adds the number to another number and assigns the result to the current number. @@ -224,10 +287,35 @@ namespace steppable */ Number operator--(); + /** + * @brief Unary minus operator. + * @details Converts the number to itself with the opposite sign. Returns a new instance of the number. + * @return A number equal in value but opposite in sign. + */ + Number operator-() const; + + /** + * @brief Unary plus operator. + * @details Does nothing. Simply returns a new instance of the number. + * @return A number equal in value and equal in sign. + */ + Number operator+() const; + /** * @brief Presents the number in a human-readable format. * @return The number as a string. */ [[nodiscard]] std::string present() const; }; + + /** + * @namespace steppable::literals + * @brief Literal suffixes for literals to be converted to Steppable objects. + */ + namespace literals + { + inline Number operator""_n(long double value) { return Number(value); } + + inline Number operator""_n(unsigned long long value) { return Number(value); } + } // namespace literals } // namespace steppable diff --git a/include/testing.hpp b/include/testing.hpp index e85934fee..8db028171 100644 --- a/include/testing.hpp +++ b/include/testing.hpp @@ -30,6 +30,9 @@ #pragma once +#include "format.hpp" +#include "types/concepts.hpp" + #include using namespace std::literals; @@ -101,7 +104,7 @@ namespace steppable::testing * @param[in] condition The condition to be checked. * @param[in] conditionName The name of the condition. */ - void assert(bool condition, const std::string& conditionName); + void _assertCondition(bool condition, const std::string& conditionName); std::string testCaseName; @@ -129,18 +132,70 @@ namespace steppable::testing void assertIsNotEqual(const std::string& a, const std::string& b); /** - * @brief Asserts that two integers are equal. - * @param[in] a The first integer. - * @param[in] b The second integer. + * @brief Asserts that two numeric values are equal. + * @param[in] a The first object. + * @param[in] b The second object. + */ + template + void assertIsEqual(ValueT a, ValueT b) + { + const std::string& conditionName = + __internals::format::format("Value {0} == {1}", { std::to_string(a), std::to_string(b) }); + _assertCondition(a == b, conditionName); + } + + /** + * @brief Asserts that two numeric values are nearly equal. + * @param[in] a The first object. + * @param[in] b The second object. + */ + template + void assertIsNearlyEqual(ValueT a, ValueT b) + { + const std::string& conditionName = + __internals::format::format("Value {0} ≈ {1}", { std::to_string(a), std::to_string(b) }); + // Take less than 10% error as equal + _assertCondition(abs(a - b) / a < 0.1, conditionName); + } + + /** + * @brief Asserts that two numeric values are not equal. + * @param[in] a The first object. + * @param[in] b The second object. + */ + template + void assertIsNotEqual(ValueT a, ValueT b) + { + const std::string& conditionName = + __internals::format::format("Value {0} != {1}", { std::to_string(a), std::to_string(b) }); + _assertCondition(a != b, conditionName); + } + + /** + * @brief Asserts that two objects with .present() method are equal. + * @param[in] a The first object. + * @param[in] b The second object. */ - void assertIsEqual(int a, int b); + template + void assertIsEqual(ValueT a, ValueT b) + { + const std::string& conditionName = + __internals::format::format("Object {0} == {1}", { a.present(), b.present() }); + _assertCondition(a == b, conditionName); + } /** - * @brief Asserts that two integers are not equal. - * @param[in] a The first integer. - * @param[in] b The second integer. + * @brief Asserts that two objects with .present() method are not equal. + * @param[in] a The first object. + * @param[in] b The second object. */ - void assertIsNotEqual(int a, int b); + template + void assertIsNotEqual(ValueT a, ValueT b) + { + const std::string& conditionName = + __internals::format::format("Object {0} != {1}", { a.present(), b.present() }); + _assertCondition(a != b, conditionName); + } /** * @brief Asserts that a boolean value is true. diff --git a/include/types/concepts.hpp b/include/types/concepts.hpp new file mode 100644 index 000000000..058c183fe --- /dev/null +++ b/include/types/concepts.hpp @@ -0,0 +1,46 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * 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. * + **************************************************************************************************/ + +/** + * @brief Defines concepts to be used for distinguishing types in the project. + * @author Andy Zhang + * @date 23 Jun 2025 + */ + +#include +#include + +/** + * @namespace steppable::concepts + * @brief Defines concepts to be used for distinguishing types in the project. + */ +namespace steppable::concepts +{ + template + concept Numeric = std::integral || std::floating_point; ///< Represents any C++ numeral literals. + + template + /// @brief Represents any object with a `.present()` method. + concept Presentable = requires(ObjectT object) { + { object.present() } -> std::same_as; + }; +} // namespace steppable::concepts diff --git a/include/types/data.hpp b/include/types/data.hpp index 724656aee..bfa7decf5 100644 --- a/include/types/data.hpp +++ b/include/types/data.hpp @@ -22,15 +22,24 @@ #pragma once -#include - #include "util.hpp" +#include +#include + using namespace std::literals; using namespace steppable::__internals::utils; namespace steppable { + /** + * @class Data + * @brief Represents data that is being passed through Steppable. + * @details This class contains a data value, and a name of the data. + * + * @tparam BaseT The type of the data. + * @tparam BaseTName A StringLiteral describing the type of the data. + */ template class Data { @@ -51,14 +60,14 @@ namespace steppable enum class _Weekday : std::uint8_t { - Sunday = 0, - Monday = 1, - Tuesday = 2, - Wednesday = 3, - Thursday = 4, - Friday = 5, - Saturday = 6, + Sunday = 0, ///< Sunday + Monday = 1, ///< Monday + Tuesday = 2, ///< Tuesday + Wednesday = 3, ///< Wednesday + Thursday = 4, ///< Thursday + Friday = 5, ///< Friday + Saturday = 6, ///< Saturday }; - using Weekday = Data<_Weekday, StringLiteral{"Weekday"}>; -} // namespace steppable \ No newline at end of file + using Weekday = Data<_Weekday, StringLiteral{ "Weekday" }>; +} // namespace steppable diff --git a/include/types/point.hpp b/include/types/point.hpp new file mode 100644 index 000000000..809e928b6 --- /dev/null +++ b/include/types/point.hpp @@ -0,0 +1,61 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * 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. * + **************************************************************************************************/ + +#include + +namespace steppable +{ + /** + * @struct XYPoint + * @brief A point object + * @details Represents a 2-D point on a 2D plane. + */ + struct XYPoint + { + size_t x = 0; ///< Coordinate x. + size_t y = 0; ///< Coordinate y. + }; + + /** + * @struct YXPoint + * @brief A point object + * @details Represents a 2-D point on a 2D plane. + */ + struct YXPoint + { + size_t y = 0; ///< Coordinate y. + size_t x = 0; ///< Coordinate x. + }; + + /** + * @struct YX2Points + * @brief A points object that represents two points + * @details Represents two 2-D points on a 2D plane. + */ + struct YX2Points + { + size_t y1 = 0; ///< Point 1 coordinate y. + size_t x1 = 0; ///< Point 1 coordinate x. + size_t y2 = 0; ///< Point 2 coordinate y. + size_t x2 = 0; ///< Point 2 coordinate x. + }; +} // namespace steppable diff --git a/include/types/result.hpp b/include/types/result.hpp index 28112c430..cec224b3e 100644 --- a/include/types/result.hpp +++ b/include/types/result.hpp @@ -22,6 +22,7 @@ #pragma once +#include #include #include #include @@ -43,9 +44,9 @@ namespace steppable::types */ enum class Status : std::uint8_t { - CALCULATED_SIMPLIFIED, - CALCULATED_UNSIMPLIFIED, - MATH_ERROR + CALCULATED_SIMPLIFIED, ///< The calculation is done by taking simplified steps. + CALCULATED_UNSIMPLIFIED, ///< The calculation is done. + MATH_ERROR ///< The calculation is not done because of an error. }; /** @@ -53,11 +54,11 @@ namespace steppable::types */ enum class StatusBool : std::uint8_t { - CALCULATED_SIMPLIFIED_YES, - CALCULATED_SIMPLIFIED_NO, - CALCULATED_UNSIMPLIFIED_YES, - CALCULATED_UNSIMPLIFIED_NO, - MATH_ERROR + CALCULATED_SIMPLIFIED_YES, ///< The calculation is done by taking simplified steps, the result it True. + CALCULATED_SIMPLIFIED_NO, ///< The calculation is done by taking simplified steps, the result it False. + CALCULATED_UNSIMPLIFIED_YES, ///< The calculation is done, the result it True. + CALCULATED_UNSIMPLIFIED_NO, ///< The calculation is done, the result it False. + MATH_ERROR ///< The calculation is not done because of an error. }; /** diff --git a/include/util.hpp b/include/util.hpp index b3bb8226f..b553f2da7 100644 --- a/include/util.hpp +++ b/include/util.hpp @@ -34,6 +34,10 @@ #pragma once +#ifdef WINDOWS + #include +#endif + #include "colors.hpp" #include "output.hpp" #include "platform.hpp" @@ -99,7 +103,6 @@ namespace steppable::__internals::utils #ifdef WINDOWS #include - #include /** * @brief Enables VT mode. @@ -293,6 +296,8 @@ namespace steppable::__internals::numUtils */ constexpr bool isZeroString(const std::string& string) { + if (string.empty()) + return true; return std::ranges::all_of(string, [](const char c) { return c == '0' or c == '.' or c == '-'; }); } diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 53b737e06..2c731b357 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -41,6 +41,6 @@ if(NOT ${STP_NO_BINDINGS}) # Only create the bindings if needed endif() set_target_properties(steppyble PROPERTIES POSITION_INDEPENDENT_CODE ON) target_link_libraries(steppyble PRIVATE func steppable) - target_compile_definitions(steppyble PRIVATE NO_MAIN) + target_compile_definitions(steppyble PRIVATE NO_MAIN STP_BINDINGS) target_include_directories(steppyble PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include) endif() diff --git a/lib/bindings.cpp b/lib/bindings.cpp index 3525f844e..9ae5948fb 100644 --- a/lib/bindings.cpp +++ b/lib/bindings.cpp @@ -20,9 +20,10 @@ * SOFTWARE. * **************************************************************************************************/ -#include "fn/calc.hpp" -#include "fraction.hpp" -#include "number.hpp" +#include "bindings/bindingsFraction.hpp" +#include "bindings/bindingsMat2d.hpp" +#include "bindings/bindingsNumber.hpp" +#include "bindings/fn/bindingsCalc.hpp" #include #include @@ -31,112 +32,15 @@ #include namespace nb = nanobind; -using namespace steppable::__internals::calc; using namespace nb::literals; NB_MODULE(steppyble, mod) // NOLINT { - auto internals = mod.def_submodule("_internals", "Internal functions."); - - nb::enum_(mod, "RoundingMode") - .value("USE_MAXIMUM_PREC", steppable::RoundingMode::USE_MAXIMUM_PREC) - .value("USE_MINIMUM_PREC", steppable::RoundingMode::USE_MINIMUM_PREC) - .value("USE_CURRENT_PREC", steppable::RoundingMode::USE_CURRENT_PREC) - .value("USE_OTHER_PREC", steppable::RoundingMode::USE_OTHER_PREC) - .value("DISCARD_ALL_DECIMALS", steppable::RoundingMode::DISCARD_ALL_DECIMALS); - - nb::class_(mod, "Number") - .def(nb::init(), - "value"_a = "0", - "prec"_a = 5, - "roundingMode"_a = steppable::USE_CURRENT_PREC) - .def(nb::self + nb::self, nb::rv_policy::automatic_reference) - .def(nb::self += nb::self, nb::rv_policy::automatic_reference) - .def(nb::self - nb::self, nb::rv_policy::automatic_reference) - .def(nb::self -= nb::self, nb::rv_policy::automatic_reference) - .def(nb::self * nb::self, nb::rv_policy::automatic_reference) - .def(nb::self *= nb::self, nb::rv_policy::automatic_reference) - .def(nb::self / nb::self, nb::rv_policy::automatic_reference) - .def(nb::self /= nb::self, nb::rv_policy::automatic_reference) - .def(nb::self % nb::self, nb::rv_policy::automatic_reference) - .def(nb::self %= nb::self, nb::rv_policy::automatic_reference) - .def("__pow__", &steppable::Number::operator^) - .def(nb::self == nb::self, nb::rv_policy::automatic_reference) - .def(nb::self != nb::self, nb::rv_policy::automatic_reference) - .def(nb::self < nb::self, nb::rv_policy::automatic_reference) - .def(nb::self > nb::self, nb::rv_policy::automatic_reference) - .def(nb::self <= nb::self, nb::rv_policy::automatic_reference) - .def(nb::self >= nb::self, nb::rv_policy::automatic_reference) - .def("__repr__", &steppable::Number::present); - - nb::class_(mod, "Fraction") - .def(nb::init(), "top"_a = "1", "bottom"_a = "1") - .def(nb::self + nb::self, nb::rv_policy::automatic_reference) - .def(nb::self += nb::self, nb::rv_policy::automatic_reference) - .def(nb::self - nb::self, nb::rv_policy::automatic_reference) - .def(nb::self -= nb::self, nb::rv_policy::automatic_reference) - .def(nb::self * nb::self, nb::rv_policy::automatic_reference) - .def(nb::self *= nb::self, nb::rv_policy::automatic_reference) - .def(nb::self / nb::self, nb::rv_policy::automatic_reference) - .def(nb::self /= nb::self, nb::rv_policy::automatic_reference) - .def("__pow__", &steppable::Fraction::operator^) - .def(nb::self == nb::self, nb::rv_policy::automatic_reference) - .def(nb::self != nb::self, nb::rv_policy::automatic_reference) - .def(nb::self < nb::self, nb::rv_policy::automatic_reference) - .def(nb::self > nb::self, nb::rv_policy::automatic_reference) - .def(nb::self <= nb::self, nb::rv_policy::automatic_reference) - .def(nb::self >= nb::self, nb::rv_policy::automatic_reference) - .def("__repr__", &steppable::Fraction::present); - mod.doc() = "The Python bindings for Steppable."; + steppable::__internals::bindings::bindingsNumber(mod); + steppable::__internals::bindings::bindingsFraction(mod); + steppable::__internals::bindings::bindingsMatrix(mod); - internals.def("abs", - &steppable::__internals::calc::abs, - "a"_a, - "steps"_a = 2, - "Internal function that takes the absolute value of a number."); - internals.def( - "add", - [](const std::string& a, const std::string& b, const int steps) { return add(a, b, steps); }, - "a"_a, - "b"_a, - "steps"_a = 2, - "Internal function that adds two numbers."); - internals.def( - "subtract", - [](const std::string& a, const std::string& b, const int steps) { return subtract(a, b, steps); }, - "a"_a, - "b"_a, - "steps"_a = 2, - "Internal function that subtracts two numbers."); - internals.def( - "multiply", - [](const std::string& a, const std::string& b, const int steps, const int decimals) { - return multiply(a, b, steps, decimals); - }, - "a"_a, - "b"_a, - "steps"_a = 2, - "decimals"_a = 8, - "Internal function that multiplies two numbers."); - internals.def( - "divide", - [](const std::string& a, const std::string& b, const int steps, const int decimals) { - return divide(a, b, steps, decimals); - }, - "a"_a, - "b"_a, - "steps"_a = 2, - "decimals"_a = 5, - "Internal function that divides two numbers."); - internals.def("divideWithQuotient", - ÷WithQuotient, - "Internal function that divides two numbers, but separating the quotient and remainder."); - internals.def("power", - &power, - "a"_a, - "raiseTo"_a, - "steps"_a = 2, - "decimals"_a = 8, - "Internal function raises a number to a power."); + // Internal functions + steppable::__internals::bindings::bindingsCalc(mod); } diff --git a/lib/bindings/bindingsFraction.hpp b/lib/bindings/bindingsFraction.hpp new file mode 100644 index 000000000..1e354c0a7 --- /dev/null +++ b/lib/bindings/bindingsFraction.hpp @@ -0,0 +1,61 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * 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 "steppable/fraction.hpp" + +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace steppable::__internals::bindings +{ + void bindingsFraction(nb::module_& mod) + { + nb::class_(mod, "Fraction") + .def(nb::init(), "top"_a = "1", "bottom"_a = "1") + .def(nb::self + nb::self, nb::rv_policy::automatic_reference) + .def(nb::self += nb::self, nb::rv_policy::automatic_reference) + .def(nb::self - nb::self, nb::rv_policy::automatic_reference) + .def("__neg__", [](steppable::Fraction& fraction) { return -fraction; }) + .def("__pos__", [](steppable::Fraction& fraction) { return +fraction; }) + .def(nb::self -= nb::self, nb::rv_policy::automatic_reference) + .def(nb::self * nb::self, nb::rv_policy::automatic_reference) + .def(nb::self *= nb::self, nb::rv_policy::automatic_reference) + .def(nb::self / nb::self, nb::rv_policy::automatic_reference) + .def(nb::self /= nb::self, nb::rv_policy::automatic_reference) + .def("__pow__", &steppable::Fraction::operator^) + .def(nb::self == nb::self, nb::rv_policy::automatic_reference) + .def(nb::self != nb::self, nb::rv_policy::automatic_reference) + .def(nb::self < nb::self, nb::rv_policy::automatic_reference) + .def(nb::self > nb::self, nb::rv_policy::automatic_reference) + .def(nb::self <= nb::self, nb::rv_policy::automatic_reference) + .def(nb::self >= nb::self, nb::rv_policy::automatic_reference) + .def("__repr__", &steppable::Fraction::present); + } +} // namespace steppable::__internals::bindings diff --git a/lib/bindings/bindingsMat2d.hpp b/lib/bindings/bindingsMat2d.hpp new file mode 100644 index 000000000..32933a4e8 --- /dev/null +++ b/lib/bindings/bindingsMat2d.hpp @@ -0,0 +1,88 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * 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 "steppable/mat2d.hpp" +#include "steppable/number.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace steppable::__internals::bindings +{ + void bindingsMatrix(nanobind::module_& mod) + { + nb::class_(mod, "Matrix") + .def(nb::init, size_t>(), "values"_a, "prec"_a = 5) + .def(nb::init, size_t>(), "values"_a, "prec"_a = 5) + .def("zeros", [](size_t rows, size_t cols) { return Matrix::zeros(rows, cols); }) + .def("diag", [](size_t rows_cols, const Number& fill = 1) { return Matrix::diag(rows_cols, fill); }) + .def("diag", [](size_t rows_cols, const double fill = 1) { return Matrix::diag(rows_cols, fill); }) + .def("ones", [](size_t rows, size_t cols) { return Matrix::ones(rows, cols); }) + .def("rref", &Matrix::rref) + .def("ref", &Matrix::ref) + .def("rank", &Matrix::rank) + .def("det", &Matrix::det) + .def("transpose", &Matrix::transpose) + .def(nb::self + nb::self, nb::rv_policy::automatic_reference) + .def(nb::self - nb::self, nb::rv_policy::automatic_reference) + .def(nb::self += nb::self, nb::rv_policy::automatic_reference) + .def(nb::self -= nb::self, nb::rv_policy::automatic_reference) + .def(nb::self << nb::self, nb::rv_policy::automatic_reference) + .def(nb::self >> nb::self, nb::rv_policy::automatic_reference) + .def(nb::self <<= nb::self, nb::rv_policy::automatic_reference) + .def(nb::self >>= nb::self, nb::rv_policy::automatic_reference) + .def(nb::self * nb::self, nb::rv_policy::automatic_reference) + .def(nb::self *= nb::self, nb::rv_policy::automatic_reference) + .def("__neg__", [](Matrix& matrix) { return -matrix; }) + .def("__pos__", [](Matrix& matrix) { return +matrix; }) + .def("__repr__", [](Matrix& matrix) { return matrix.present(0); }) + .def("__getitem__", + [](Matrix& matrix, std::array ij) { + auto [i, j] = ij; + return matrix[{ .y = i, .x = j }]; + }) + .def_prop_ro("rows", &Matrix::getRows, nb::rv_policy::copy) + .def_prop_ro("cols", &Matrix::getCols, nb::rv_policy::copy) + .def_prop_ro( + "dims", + [](Matrix& matrix) { return std::array{ matrix.getRows(), matrix.getCols() }; }, + nb::rv_policy::copy) + .def( + "__iter__", + [](Matrix& self) { + return nb::make_iterator(nb::type(), "MatrixIterator", self.begin(), self.end()); + }, + nb::keep_alive<0, 1>()); // Important for lifetime management + ; + } +} // namespace steppable::__internals::bindings diff --git a/lib/bindings/bindingsNumber.hpp b/lib/bindings/bindingsNumber.hpp new file mode 100644 index 000000000..09c15f330 --- /dev/null +++ b/lib/bindings/bindingsNumber.hpp @@ -0,0 +1,76 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * 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 "steppable/number.hpp" + +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace steppable::__internals::bindings +{ + void bindingsNumber(nanobind::module_& mod) + { + nb::enum_(mod, "RoundingMode") + .value("USE_MAXIMUM_PREC", steppable::RoundingMode::USE_MAXIMUM_PREC) + .value("USE_MINIMUM_PREC", steppable::RoundingMode::USE_MINIMUM_PREC) + .value("USE_CURRENT_PREC", steppable::RoundingMode::USE_CURRENT_PREC) + .value("USE_OTHER_PREC", steppable::RoundingMode::USE_OTHER_PREC) + .value("DISCARD_ALL_DECIMALS", steppable::RoundingMode::DISCARD_ALL_DECIMALS); + + nb::class_(mod, "Number") + .def(nb::init(), + "value"_a = "0", + "prec"_a = 5, + "roundingMode"_a = steppable::RoundingMode::USE_CURRENT_PREC) + .def(nb::init(), + "value"_a = 0, + "prec"_a = 5, + "roundingMode"_a = steppable::RoundingMode::USE_CURRENT_PREC) + .def(nb::self + nb::self, nb::rv_policy::automatic_reference) + .def(nb::self += nb::self, nb::rv_policy::automatic_reference) + .def(nb::self - nb::self, nb::rv_policy::automatic_reference) + .def("__neg__", [](steppable::Number& number) { return -number; }) + .def("__pos__", [](steppable::Number& number) { return +number; }) + .def(nb::self -= nb::self, nb::rv_policy::automatic_reference) + .def(nb::self * nb::self, nb::rv_policy::automatic_reference) + .def(nb::self *= nb::self, nb::rv_policy::automatic_reference) + .def(nb::self / nb::self, nb::rv_policy::automatic_reference) + .def(nb::self /= nb::self, nb::rv_policy::automatic_reference) + .def(nb::self % nb::self, nb::rv_policy::automatic_reference) + .def(nb::self %= nb::self, nb::rv_policy::automatic_reference) + .def("__pow__", &steppable::Number::operator^) + .def(nb::self == nb::self, nb::rv_policy::automatic_reference) + .def(nb::self != nb::self, nb::rv_policy::automatic_reference) + .def(nb::self < nb::self, nb::rv_policy::automatic_reference) + .def(nb::self > nb::self, nb::rv_policy::automatic_reference) + .def(nb::self <= nb::self, nb::rv_policy::automatic_reference) + .def(nb::self >= nb::self, nb::rv_policy::automatic_reference) + .def("__repr__", &steppable::Number::present); + } +} // namespace steppable::__internals::bindings diff --git a/lib/bindings/fn/bindingsCalc.hpp b/lib/bindings/fn/bindingsCalc.hpp new file mode 100644 index 000000000..5732424bc --- /dev/null +++ b/lib/bindings/fn/bindingsCalc.hpp @@ -0,0 +1,93 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * 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 "fn/calc.hpp" + +#include +#include +#include +#include +#include + +namespace nb = nanobind; +using namespace nb::literals; + +namespace steppable::__internals::bindings +{ + void bindingsCalc(nb::module_& mod) + { + using namespace steppable::__internals::calc; + + auto internals = mod.def_submodule("_internals", "Internal functions."); + internals.def("abs", + &steppable::__internals::calc::abs, + "a"_a, + "steps"_a = 2, + "Internal function that takes the absolute value of a number."); + internals.def( + "add", + [](const std::string& a, const std::string& b, const int steps) { return add(a, b, steps); }, + "a"_a, + "b"_a, + "steps"_a = 2, + "Internal function that adds two numbers."); + internals.def( + "subtract", + [](const std::string& a, const std::string& b, const int steps) { return subtract(a, b, steps); }, + "a"_a, + "b"_a, + "steps"_a = 2, + "Internal function that subtracts two numbers."); + internals.def( + "multiply", + [](const std::string& a, const std::string& b, const int steps, const int decimals) { + return multiply(a, b, steps, decimals); + }, + "a"_a, + "b"_a, + "steps"_a = 2, + "decimals"_a = 8, + "Internal function that multiplies two numbers."); + internals.def( + "divide", + [](const std::string& a, const std::string& b, const int steps, const int decimals) { + return divide(a, b, steps, decimals); + }, + "a"_a, + "b"_a, + "steps"_a = 2, + "decimals"_a = 5, + "Internal function that divides two numbers."); + internals.def("divide_with_quotient", + ÷WithQuotient, + "Internal function that divides two numbers, but separating the quotient and remainder."); + internals.def("power", + &power, + "a"_a, + "raiseTo"_a, + "steps"_a = 2, + "decimals"_a = 8, + "Internal function raises a number to a power."); + } +} // namespace steppable::__internals::bindings diff --git a/lib/paths.py b/lib/paths.py index e5cfa72cf..5522f61b3 100644 --- a/lib/paths.py +++ b/lib/paths.py @@ -25,6 +25,7 @@ """ from pathlib import Path + from lib.constants import WINDOWS # section Project paths diff --git a/lib/printing.py b/lib/printing.py index b0903977a..87fa93b14 100644 --- a/lib/printing.py +++ b/lib/printing.py @@ -20,8 +20,8 @@ # SOFTWARE. # ##################################################################################################### -import sys import os +import sys from lib.constants import WINDOWS diff --git a/pyproject.toml b/pyproject.toml index e2728bbd5..d95a9c4a8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,21 +3,21 @@ name = "steppable" version = "0.0.1" description = "A CAS built from scratch." authors = [ - { name = "Andy Zhang", email = "andy@nwsoft.tech" } + { name = "Andy Zhang", email = "z-c-ge@outlook.com" } ] license = {file = "LICENSE"} readme = "README.md" -requires-python = ">= 3.10" +requires-python = ">= 3.11" dynamic = ["classifiers", "optional-dependencies"] dependencies = [ - "matplotlib >= 3.9.0", - "black >= 24.4.2", - "isort >= 5.13.2", "setuptools >= 61.0", "nanobind >= 1.9.2", "pyyaml >= 6.0.1", + "matplotlib >= 3.9.0; 'Windows' not in platform_system", + "black >= 24.4.2", + "isort >= 5.13.2", ] [build-system] diff --git a/setup.py b/setup.py index 462309b18..77fab6c6e 100644 --- a/setup.py +++ b/setup.py @@ -161,38 +161,42 @@ def build_extension(self, ext: CMakeExtension) -> None: # The information here can also be placed in setup.cfg - better separation of # logic and declaration, and simpler if you include description/version in a file. -setup( - name="steppable", - version="0.0.1", - license="MIT", - author="Andy Zhang", - author_email="z-c-ge@outlook.com", - classifiers=[ - "Development Status :: 2 - Pre-Alpha", - "Environment :: Console", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: MIT License", - "Natural Language :: English", - "Operating System :: OS Independent", - "Programming Language :: C++", - "Programming Language :: Python", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Programming Language :: Python :: Implementation :: CPython", - "Topic :: Education", - "Topic :: Education :: Computer Aided Instruction (CAI)", - "Topic :: Scientific/Engineering :: Mathematics", - "Topic :: Software Development :: Libraries", - ], - description="Python bindings for Steppable.", - long_description="Python bindings for Steppable, written using nanobind.", - ext_modules=[CMakeExtension("steppyble")], - include_package_data=True, - packages=["steppyble"], - package_data={"steppyble": ["__init__.pyi", "fraction.pyi", "number.pyi"]}, - cmdclass={"build_ext": CMakeBuild}, - zip_safe=False, - extras_require={"test": ["pytest>=6.0"]}, - python_requires=">=3.10", -) +try: + setup( + name="steppable", + version="0.0.1", + author="Andy Zhang", + author_email="z-c-ge@outlook.com", + classifiers=[ + "Development Status :: 2 - Pre-Alpha", + "Environment :: Console", + "Intended Audience :: Science/Research", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: C++", + "Programming Language :: Python", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: Implementation :: CPython", + "Topic :: Education", + "Topic :: Education :: Computer Aided Instruction (CAI)", + "Topic :: Scientific/Engineering :: Mathematics", + "Topic :: Software Development :: Libraries", + ], + description="Python bindings for Steppable.", + long_description="Python bindings for Steppable, written using nanobind.", + ext_modules=[CMakeExtension("steppyble")], + include_package_data=True, + packages=["steppyble"], + package_data={"steppyble": ["__init__.pyi", "fraction.pyi", "number.pyi"]}, + cmdclass={"build_ext": CMakeBuild}, + zip_safe=False, + extras_require={"test": ["pytest>=6.0"]}, + license="MIT", + license_files=("LICENSE",), + python_requires=">=3.10", + ) +except Exception as e: + print(e) + exit(1) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 83496c2f6..112145fc5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -35,7 +35,7 @@ ADD_LIBRARY( ) SET_TARGET_PROPERTIES(util PROPERTIES POSITION_INDEPENDENT_CODE ON) -ADD_LIBRARY(steppable STATIC number.cpp fraction.cpp) +ADD_LIBRARY(steppable STATIC steppable/number.cpp steppable/fraction.cpp steppable/mat2d.cpp) SET_TARGET_PROPERTIES(steppable PROPERTIES POSITION_INDEPENDENT_CODE ON) SET(CALCULATOR_FILES) @@ -62,7 +62,12 @@ FOREACH(COMPONENT IN LISTS COMPONENTS) ENDFOREACH() # FUNC: Library containing all Steppable functions. -ADD_LIBRARY(func STATIC ${CALCULATOR_FILES} fraction.cpp rounding.cpp factors.cpp number.cpp) +ADD_LIBRARY(func STATIC ${CALCULATOR_FILES} + steppable/fraction.cpp + steppable/mat2d.cpp + steppable/number.cpp + rounding.cpp + factors.cpp) SET_TARGET_PROPERTIES(func PROPERTIES POSITION_INDEPENDENT_CODE ON) TARGET_INCLUDE_DIRECTORIES(steppable PRIVATE ${STP_BASE_DIRECTORY}/include/) diff --git a/src/calc/abs/abs.cpp b/src/calc/abs/abs.cpp index d230271e5..31c2155a5 100644 --- a/src/calc/abs/abs.cpp +++ b/src/calc/abs/abs.cpp @@ -30,14 +30,14 @@ #include "absReport.hpp" #include "argParse.hpp" +#include "fn/calc.hpp" #include "getString.hpp" +#include "steppable/number.hpp" +#include "types/result.hpp" #include "util.hpp" -#include #include -#include #include -#include using namespace steppable::__internals::utils; using namespace steppable::__internals::calc; diff --git a/src/calc/comparison/comparison.cpp b/src/calc/comparison/comparison.cpp index 959687a77..7cdde1a0a 100644 --- a/src/calc/comparison/comparison.cpp +++ b/src/calc/comparison/comparison.cpp @@ -31,6 +31,8 @@ #include "comparisonReport.hpp" #include "fn/calc.hpp" #include "getString.hpp" +#include "steppable/number.hpp" +#include "types/result.hpp" #include "util.hpp" #include diff --git a/src/calc/division/division.cpp b/src/calc/division/division.cpp index 0a5f75444..1df54e640 100644 --- a/src/calc/division/division.cpp +++ b/src/calc/division/division.cpp @@ -107,8 +107,8 @@ namespace steppable::__internals::calc return 1; // Step 2: Squeeze! - // Method: If number >= divisor -> return numberScale. - // Else -> return numberScale - 1. + // Method: If number >= divisor -> return diffScale + 1. + // Else -> return diffScale. if (compare(number, divisor, 0) != "0") return diffScale + 1; return diffScale; diff --git a/src/calc/division/divisionReport.cpp b/src/calc/division/divisionReport.cpp index 34ff77ff3..28cb473c2 100644 --- a/src/calc/division/divisionReport.cpp +++ b/src/calc/division/divisionReport.cpp @@ -74,7 +74,18 @@ std::string reportDivision(std::stringstream& tempFormattedAns, return formattedAns.str(); } if (resultIsNegative) + { +#if defined(STP_DEB_CALC_DIVISION_RESULT_INSPECT) && DEBUG + steppable::output::info("calc::division"s, + _number + " " + static_cast(DIVIDED_BY) + " " + _divisor + " = -" + ans); +#endif return "-"s + static_cast(ans); + } + +#if defined(STP_DEB_CALC_DIVISION_RESULT_INSPECT) && DEBUG + steppable::output::info("calc::division"s, + _number + " " + static_cast(DIVIDED_BY) + " " + _divisor + " = " + ans); +#endif return static_cast(ans); } diff --git a/src/calc/hyp/hyp.cpp b/src/calc/hyp/hyp.cpp index b9571088f..5ec72a013 100644 --- a/src/calc/hyp/hyp.cpp +++ b/src/calc/hyp/hyp.cpp @@ -54,8 +54,8 @@ namespace steppable::__internals::calc checkDecimalArg(&decimals); const auto& twoX = multiply(x, "2", 0, decimals + 2); - const auto& eTwoX = roundOff(power(static_cast(constants::E), twoX, 0), decimals + 2); - const auto& eX = roundOff(power(static_cast(constants::E), x, 0), decimals + 2); + const auto& eTwoX = exp(twoX, decimals + 2); + const auto& eX = exp(x, decimals + 2); const auto& numerator = subtract(eTwoX, "1", 0); const auto& denominator = multiply("2", eX, 0, decimals + 2); diff --git a/src/calc/multiply/multiply.cpp b/src/calc/multiply/multiply.cpp index cb79a98c1..567dc06d8 100644 --- a/src/calc/multiply/multiply.cpp +++ b/src/calc/multiply/multiply.cpp @@ -60,8 +60,6 @@ namespace steppable::__internals::calc resultIsNegative = false; // NOLINT(bugprone-branch-clone) else if (aIsNegative or bIsNegative) resultIsNegative = true; - else - resultIsNegative = false; auto [aInteger, aDecimal, bInteger, bDecimal] = splitNumberArray; std::stringstream out; @@ -74,20 +72,34 @@ namespace steppable::__internals::calc } // Multiplying by 1 gives the other number. - if (a == "1") + if (compare(a, "1", 0) == "2") { if (steps == 2) out << $("multiply", "36adc27f-07e8-4118-88ed-f148164044da", { a, b }) << "\n"; out << b; return out.str(); } - if (b == "1") + if (compare(b, "1", 0) == "2") { if (steps == 2) out << $("multiply", "36adc27f-07e8-4118-88ed-f148164044da", { b, a }) << "\n"; out << a; return out.str(); } + if (compare(a, "-1", 0) == "2") + { + if (steps == 2) + out << $("multiply", "36adc27f-07e8-4118-88ed-f148164044da", { a, b }) << "\n"; + out << standardizeNumber("-" + b); + return out.str(); + } + if (compare(b, "-1", 0) == "2") + { + if (steps == 2) + out << $("multiply", "36adc27f-07e8-4118-88ed-f148164044da", { b, a }) << "\n"; + out << standardizeNumber("-" + a); + return out.str(); + } // Multiplying by a power of ten means moving the decimal places. if (isPowerOfTen(a)) @@ -196,7 +208,8 @@ namespace steppable::__internals::calc prodDigitsOut, carries, resultIsNegative, - steps); + steps, + decimals); } } // namespace steppable::__internals::calc diff --git a/src/calc/multiply/multiplyReport.cpp b/src/calc/multiply/multiplyReport.cpp index 9a1cb3c7f..5b11988a8 100644 --- a/src/calc/multiply/multiplyReport.cpp +++ b/src/calc/multiply/multiplyReport.cpp @@ -31,6 +31,7 @@ #include "multiplyReport.hpp" +#include "output.hpp" #include "rounding.hpp" #include "symbols.hpp" #include "util.hpp" @@ -55,7 +56,8 @@ std::string reportMultiply(const std::string& a, const std::vector>& prodDigitsOut, const std::vector>& carries, const bool resultIsNegative, - const int steps) + const int steps, + const int decimals) { std::stringstream ss; @@ -117,7 +119,18 @@ std::string reportMultiply(const std::string& a, // Add the decimal point. auto places = aDecimal.length() + bDecimal.length(); out = moveDecimalPlaces(out, -static_cast(places)); + out = roundOff(out, decimals); + out = standardizeNumber(out); - ss << standardizeNumber(out); +#if defined(STP_DEB_CALC_MULTIPLY_RESULT_INSPECT) && DEBUG + std::stringstream debOut; + + debOut << a << " " << MULTIPLY << " " << b << " = "; + if (resultIsNegative) + debOut << "-"; + debOut << out; + steppable::output::info("calc::multiply"s, debOut.str()); +#endif + ss << out; return ss.str(); } diff --git a/src/calc/multiply/multiplyReport.hpp b/src/calc/multiply/multiplyReport.hpp index 1c5467d7b..d4c79efca 100644 --- a/src/calc/multiply/multiplyReport.hpp +++ b/src/calc/multiply/multiplyReport.hpp @@ -63,4 +63,5 @@ std::string reportMultiply(const std::string& a, const std::vector>& prodDigitsOut, const std::vector>& carries, bool resultIsNegative = false, - int steps = 2); + int steps = 2, + int decimals = 1); diff --git a/src/calc/power/power.cpp b/src/calc/power/power.cpp index b67f58ca2..b245e3cb4 100644 --- a/src/calc/power/power.cpp +++ b/src/calc/power/power.cpp @@ -30,7 +30,7 @@ #include "argParse.hpp" #include "constants.hpp" #include "fn/calc.hpp" -#include "fraction.hpp" +#include "steppable/fraction.hpp" #include "getString.hpp" #include "powerReport.hpp" #include "rounding.hpp" @@ -118,19 +118,35 @@ namespace steppable::__internals::calc return reportPower(number, raiseTo, numberTrailingZeros, negativePower, steps, decimals); } - std::string exp(const std::string& x, const size_t decimals) + std::string _exp(const std::string& x, const size_t decimals) // NOLINT(*-no-recursion) { - constexpr size_t iter = 50; + if (compare(x, "4", 0) == "1") + { + std::string halfX = divide(x, "2", 0, static_cast(decimals) + 2); + std::string result = _exp(halfX, decimals + 2); + return multiply(result, result, 0, static_cast(decimals + 2)); + } + std::string sum = "1"; std::string term = "1"; - for (size_t i = 1; i < iter; i++) + const auto errorStr = "0." + std::string(decimals + 1, '0') + "1"; + for (int i = 1;; i++) { std::string frac = divide(x, std::to_string(i), 0, static_cast(decimals) + 2); term = multiply(term, frac, 0, static_cast(decimals) + 2); + if (compare(term, errorStr, 0) != "1") + break; + sum = add(sum, term, 0); } return roundOff(sum, decimals); } + + std::string exp(const std::string& x, const size_t decimals) + { + const auto result = _exp(x, decimals + 2); + return roundOff(result, decimals); + } } // namespace steppable::__internals::calc #ifndef NO_MAIN diff --git a/src/calc/power/powerReport.cpp b/src/calc/power/powerReport.cpp index 53dc20dbb..84f0e7401 100644 --- a/src/calc/power/powerReport.cpp +++ b/src/calc/power/powerReport.cpp @@ -32,9 +32,9 @@ #include "powerReport.hpp" #include "fn/calc.hpp" -#include "fraction.hpp" #include "getString.hpp" #include "rounding.hpp" +#include "steppable/fraction.hpp" #include "symbols.hpp" #include "util.hpp" @@ -93,16 +93,16 @@ std::string reportPower(const std::string& _number, loop(raiseTo, [&](const auto& i) { if (not negativePower) - result = multiply(result, _number, 0, decimals + 1); + result = multiply(result, _number, 0, decimals + 2); else - result = divide("1", result, 0, decimals + 1); + result = divide("1", result, 0, decimals + 2); auto currentPower = add(i, "1", 0); if (steps == 2) { if (not negativePower) - ss << BECAUSE << " " << multiply(result, _number, 1) << '\n'; + ss << BECAUSE << " " << multiply(result, _number, 1, decimals + 2) << '\n'; else - ss << BECAUSE << " " << divide("1", result, 1) << '\n'; + ss << BECAUSE << " " << divide("1", result, 1, decimals + 2) << '\n'; if (negativePower) currentPower = "-" + currentPower; diff --git a/src/calc/power/powerReport.hpp b/src/calc/power/powerReport.hpp index b2dc42a23..860fb7c1f 100644 --- a/src/calc/power/powerReport.hpp +++ b/src/calc/power/powerReport.hpp @@ -27,7 +27,7 @@ */ #pragma once -#include "fraction.hpp" +#include "steppable/fraction.hpp" #include diff --git a/src/calc/root/root.cpp b/src/calc/root/root.cpp index 8887758e2..726fb5f92 100644 --- a/src/calc/root/root.cpp +++ b/src/calc/root/root.cpp @@ -33,7 +33,7 @@ #include "argParse.hpp" #include "factors.hpp" #include "fn/calc.hpp" -#include "fraction.hpp" +#include "steppable/fraction.hpp" #include "getString.hpp" #include "rootReport.hpp" #include "rounding.hpp" diff --git a/src/calc/root/rootReport.hpp b/src/calc/root/rootReport.hpp index ba1a07331..bfa3fe671 100644 --- a/src/calc/root/rootReport.hpp +++ b/src/calc/root/rootReport.hpp @@ -22,7 +22,7 @@ #pragma once -#include "fraction.hpp" +#include "steppable/fraction.hpp" #include diff --git a/src/calc/subtract/subtract.cpp b/src/calc/subtract/subtract.cpp index 910028423..c073a6707 100644 --- a/src/calc/subtract/subtract.cpp +++ b/src/calc/subtract/subtract.cpp @@ -32,8 +32,10 @@ #include "argParse.hpp" #include "fn/calc.hpp" #include "getString.hpp" +#include "steppable/number.hpp" #include "subtractReport.hpp" #include "symbols.hpp" +#include "types/result.hpp" #include "util.hpp" #include @@ -96,8 +98,8 @@ namespace steppable::__internals::calc if (bIsNegative) { if (steps == 2) - // Adding {0} and {1} since {1} is negative - std::cout << $("subtract", "063f0bd2-a4ca-4433-97c0-8baa73cd0e7c", { a, b.substr(1) }) << "\n"; + // Adding {0} and {1} since {2} is negative + std::cout << $("subtract", "063f0bd2-a4ca-4433-97c0-8baa73cd0e7c", { a, b.substr(1), b }) << "\n"; resultIsNegative = false; return add(a, b.substr(1), steps); } @@ -128,8 +130,8 @@ namespace steppable::__internals::calc std::ranges::reverse(aStr); std::ranges::reverse(bStr); - std::vector diffDigits(aStr.length(), 0); - std::vector borrows(aStr.length()); + std::vector diffDigits(aStr.length(), 0); + std::vector borrows(aStr.length(), 0); std::ranges::copy(aStr, borrows.begin()); std::ranges::for_each(borrows, [](int& c) { c -= '0'; }); for (int index = 0; index < aStr.length(); index++) @@ -185,6 +187,13 @@ namespace steppable::__internals::calc resultIsNegative, noMinus); } + + types::Result error(const std::string& a, const std::string& b) + { + auto difference = subtract(a, b, 0); + difference = abs(difference, 0); + return { { a, b }, { difference }, Number(difference), types::Status::CALCULATED_UNSIMPLIFIED }; + } } // namespace steppable::__internals::calc #ifndef NO_MAIN diff --git a/src/calc/subtract/subtractReport.cpp b/src/calc/subtract/subtractReport.cpp index d9472b0a7..983f1ea19 100644 --- a/src/calc/subtract/subtractReport.cpp +++ b/src/calc/subtract/subtractReport.cpp @@ -127,6 +127,11 @@ std::string reportSubtract(const std::string& aInteger, ansStream << outputChar; // No spaces } +#if defined(STP_DEB_CALC_SUBTRACT_RESULT_INSPECT) && DEBUG + auto bStr = bInteger + '.' + bDecimal; + steppable::output::info("calc::subtract"s, aStr + " - " + bStr + " = " + ansStream.str()); +#endif + ss << standardizeNumber(ansStream.str()); - return std::move(ss.str()); + return ss.str(); } diff --git a/src/getString.cpp b/src/getString.cpp index a09ee5656..d6ff236b0 100644 --- a/src/getString.cpp +++ b/src/getString.cpp @@ -87,24 +87,30 @@ namespace steppable::localization R"(^([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}) >> \"([^\"]+?)\"$)"); const auto& confDir = getConfDirectory(); - const auto& lang = getLanguage(); + std::string lang = getLanguage(); - const auto& langDir = confDir / "translations" / lang; - const auto& originFile = langDir / (origin + ".stp_localized"); + auto langDir = confDir / "translations" / lang; + auto originFile = langDir / (origin + ".stp_localized"); - if (not exists(originFile)) + // Since en-US is the default language, not having this language means the package is not properly installed + if (not exists(originFile) and lang == "en-US") { // If the file does not exist, we return the key as the string. Also, we log an error. - output::error("getString"s, "Cannot find the localization file: " + originFile.string()); + output::error("localization::getString"s, "Cannot find the localization file: " + originFile.string()); return "<"s + key + ">"s; // Since the key is in UUID format, we need to make it look like a placeholder. } + if (not exists(originFile)) + { + langDir = confDir / "translations" / "en-US"; + originFile = langDir / (origin + ".stp_localized"); + } // Read the file and get the string std::ifstream file(originFile); if (not file.is_open()) { // If we cannot open the file, we return the key as the string. Also, we log an error. - output::error("getString"s, "Cannot open the localization file: " + originFile.string()); + output::error("localization::getString"s, "Cannot open the localization file: " + originFile.string()); return "<"s + key + ">"s; // Since the key is in UUID format, we need to make it look like a placeholder. } @@ -134,7 +140,7 @@ namespace steppable::localization } else { - output::error("getString"s, + output::error("localization::getString"s, "Malformed line in localization file: " + originFile.string() + " -> " + line); break; } @@ -143,7 +149,7 @@ namespace steppable::localization // If we cannot find the string, we return the key as the string. Also, we log an error. if (translation.empty()) { - output::error("getString"s, "Cannot find the string for the key: " + key); + output::error("localization::getString"s, "Cannot find the string for the key: " + key); return "<" + key + ">"; // Since the key is in UUID format, we need to make it look like a placeholder. } return translation; diff --git a/src/matrix/ref/ref.cpp b/src/matrix/ref/ref.cpp new file mode 100644 index 000000000..205435ff7 --- /dev/null +++ b/src/matrix/ref/ref.cpp @@ -0,0 +1,55 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * 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. * + **************************************************************************************************/ + +/** + * @file ref.cpp + * @brief Desciption + * + * @author Andy Zhang + * @date 31st May 2025 + */ + +#include "refReport.hpp" +#include "steppable/mat2d.hpp" +#include "steppable/number.hpp" + +#include +#include + +namespace steppable::__internals::matrix +{ + std::string ref(/* Arguments... */) { return ""; } +} // namespace steppable::__internals::matrix + +int main() +{ + using namespace steppable; + std::vector> matrix = { { 2, 1, -1, 3, 2, 8 }, + { 1, -2, 1, 0, 2, -4 }, + { 3, 1, -3, 4, 1, 6 }, + { 1, 1, 1, 1, 1, 5 }, + { 2, -1, 2, -1, 3, 3 } }; + auto mat = steppable::Matrix(matrix); + mat = mat.rref(); + + std::cout << mat.present(1) << "\n"; +} diff --git a/src/matrix/ref/refReport.cpp b/src/matrix/ref/refReport.cpp new file mode 100644 index 000000000..e8defb66e --- /dev/null +++ b/src/matrix/ref/refReport.cpp @@ -0,0 +1,35 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * 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. * + **************************************************************************************************/ + +/** + * @file refReport.cpp + * @brief Desciption + * + * @author Andy Zhang + * @date 31st May 2025 + */ + +#include "refReport.hpp" + +#include + +std::string reportRef() { return ""; } diff --git a/src/matrix/ref/refReport.hpp b/src/matrix/ref/refReport.hpp new file mode 100644 index 000000000..f6099478b --- /dev/null +++ b/src/matrix/ref/refReport.hpp @@ -0,0 +1,33 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * 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. * + **************************************************************************************************/ + +/** + * @file refReport.hpp + * @brief Desciption + * + * @author Andy Zhang + * @date 31st May 2025 + */ + +#include + +std::string reportRef(); diff --git a/src/platform.cpp b/src/platform.cpp index 748f60a05..f4fe76740 100644 --- a/src/platform.cpp +++ b/src/platform.cpp @@ -61,9 +61,7 @@ namespace steppable::__internals::utils std::array errBuffer{}; const char* homeEnv = nullptr; int error = 0; - struct passwd pw - { - }; + struct passwd pw{}; struct passwd* result = nullptr; uid_t userId = getuid(); diff --git a/src/rounding.cpp b/src/rounding.cpp index 421f7dc2d..994b90b3e 100644 --- a/src/rounding.cpp +++ b/src/rounding.cpp @@ -57,14 +57,14 @@ namespace steppable::__internals::numUtils return integer; } - std::string roundOff(const std::string& _number, const size_t digits) + std::string roundOff(const std::string& _number, const size_t digits, Rounding mode) { auto number = _number; if (number.empty()) return "0"; if (number.find('.') == std::string::npos) return number; - auto splitNumberResult = splitNumber(number, "0", true, true, false, false); + auto splitNumberResult = splitNumber(number, "0", false, false, false, false); // Round off the number auto splitNumberArray = splitNumberResult.splitNumberArray; @@ -73,7 +73,7 @@ namespace steppable::__internals::numUtils bool isNegative = splitNumberResult.aIsNegative; if (decimal.length() < digits) - return _number; + decimal += std::string(digits - decimal.length(), '0'); // Preserve one digit after the rounded digit decimal = decimal.substr(0, digits + 1); @@ -85,8 +85,22 @@ namespace steppable::__internals::numUtils } auto newDecimal = decimal.substr(0, digits); std::ranges::reverse(newDecimal.begin(), newDecimal.end()); + bool condition = false; + switch (mode) + { + case Rounding::ROUND_OFF: + condition = decimal.back() >= '5'; + break; + case Rounding::ROUND_DOWN: + condition = false; + break; + case Rounding::ROUND_UP: + condition = true; + default: + break; + } - if (decimal.back() >= '5') + if (condition) { // Need to round up the digit for (size_t i = 0; i < newDecimal.length(); i++) @@ -107,18 +121,26 @@ namespace steppable::__internals::numUtils } } std::ranges::reverse(newDecimal.begin(), newDecimal.end()); + + std::string result; decimal = newDecimal.substr(0, digits); if (isNegative) integer = '-' + integer; if (decimal.empty() and digits > 0) - return integer + "." + std::string(digits, '0'); - if (decimal.empty()) - return integer; - return integer + "." + decimal; + result = integer + "." + std::string(digits, '0'); + else if (decimal.empty()) + result = integer; + else + result = integer + "." + decimal; + + result = simplifyPolarity(result); + return result; } std::string moveDecimalPlaces(const std::string& _number, const long places) { + if (_number.empty()) + return "0"; auto number = _number; // No change if (places == 0) diff --git a/src/fraction.cpp b/src/steppable/fraction.cpp similarity index 95% rename from src/fraction.cpp rename to src/steppable/fraction.cpp index 018b1f248..b329774c6 100644 --- a/src/fraction.cpp +++ b/src/steppable/fraction.cpp @@ -28,11 +28,11 @@ * @date 13th March 2024 */ -#include "fraction.hpp" +#include "steppable/fraction.hpp" #include "exceptions.hpp" #include "fn/calc.hpp" -#include "number.hpp" +#include "steppable/number.hpp" #include "symbols.hpp" #include "util.hpp" @@ -230,6 +230,16 @@ namespace steppable return compare(thisNewTop, otherNewTop, 0) != "0"; } + Fraction Fraction::operator+() const { return *this; } + + Fraction Fraction::operator-() const + { + auto newTop = "-" + top; + auto newFrac = Fraction(newTop, bottom); + newFrac.simplify(); + return newFrac; + } + void Fraction::reciprocal() { std::ranges::swap(top, bottom); @@ -239,6 +249,8 @@ namespace steppable void Fraction::simplify() { // Make sure the fraction does not contain decimal points. + top = standardizeNumber(top); + bottom = standardizeNumber(bottom); while (isDecimal(top) or isDecimal(bottom)) { top = multiply(top, "10", 0); diff --git a/src/steppable/mat2d.cpp b/src/steppable/mat2d.cpp new file mode 100644 index 000000000..a29dc0d4c --- /dev/null +++ b/src/steppable/mat2d.cpp @@ -0,0 +1,595 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * 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. * + **************************************************************************************************/ + +/** + * @file mat2d.cpp + * @brief Implements methods for matrix manipulation. + * @author Andy Zhang + * @date 31 May 2025 + */ + +#include "steppable/mat2d.hpp" + +#include "output.hpp" +#include "platform.hpp" +#include "rounding.hpp" +#include "steppable/number.hpp" +#include "symbols.hpp" +#include "util.hpp" + +#include +#include +#include +#include +#include +#include + +namespace steppable +{ + using namespace __internals; + using namespace __internals::numUtils; + + namespace prettyPrint::printers + { + std::string ppMatrix(const MatVec2D& matrix, const int endRows) + { + int maxLen = 0; + std::stringstream ss; + for (const auto& row : matrix) + { + for (const auto& val : row) + { + auto length = static_cast(val.present().length()); + maxLen = std::max(length, maxLen); + } + } + + size_t matrixRows = matrix.size(); + for (size_t rowIdx = 0; rowIdx < matrixRows; rowIdx++) + { + const auto& row = matrix[rowIdx]; + if (matrixRows == 1) + ss << "["; + else if (rowIdx == 0) + ss << symbols::MATRIX_LEFT_TOP; + else if (rowIdx == matrixRows - 1) + ss << symbols::MATRIX_LEFT_BOTTOM; + else + ss << symbols::MATRIX_LEFT_MIDDLE; + for (size_t valIdx = 0; valIdx < row.size(); valIdx++) + { + const auto& val = row[valIdx]; + if (valIdx + endRows == row.size()) + ss << symbols::MATRIX_LEFT_MIDDLE; + ss << std::right << std::setw(maxLen + 1) << val.present(); + ss << " "; + } + + if (matrixRows == 1) + ss << "]"; + else if (rowIdx == 0) + ss << symbols::MATRIX_RIGHT_TOP; + else if (rowIdx == matrixRows - 1) + ss << symbols::MATRIX_RIGHT_BOTTOM; + else + ss << symbols::MATRIX_RIGHT_MIDDLE; + ss << "\n"; + } + return ss.str(); + } + } // namespace prettyPrint::printers + + void Matrix::_checkDataSanity(const MatVec2D& data) + { + size_t size = data.front().size(); + for (const auto& row : data) + { + const size_t rowSize = row.size(); + if (rowSize != size) + { + output::error("Matrix::_checkDataSanity"s, "Matrix has non-uniform dimensions."s); + utils::programSafeExit(1); + } + size = rowSize; + } + } + + Matrix::Matrix() : data({ {} }) { _cols = _rows = 0; } + + Matrix::Matrix(const size_t rows, const size_t cols, const Number& fill) : + data(std::vector(rows, std::vector(cols, fill))), _cols(cols), _rows(rows) + { + data = roundOffValues(data, static_cast(prec)); + _checkDataSanity(data); + } + + Matrix::Matrix(const MatVec2D& data, const size_t prec) : + data(data), _cols(data.front().size()), _rows(data.size()), prec(prec) + { + for (auto& row : this->data) + for (auto& value : row) + value.setPrec(prec + 3, RoundingMode::USE_MAXIMUM_PREC); + } + + void Matrix::_checkIdxSanity(const YXPoint* point) const + { + const auto x = point->x; + const auto y = point->y; + + if (x > _cols) + { + output::error("Matrix::operator[]"s, + "Incorrect x parameter. {0} exceeds the number of columns in this matrix ({1})."s, + { std::to_string(x), std::to_string(_cols) }); + utils::programSafeExit(1); + } + if (y > _rows) + { + output::error("Matrix::operator[]"s, + "Incorrect y parameter. {0} exceeds the number of rows in this matrix ({1})."s, + { std::to_string(y), std::to_string(_rows) }); + utils::programSafeExit(1); + } + } + + MatVec2D Matrix::roundOffValues(const MatVec2D& _data, const size_t prec) + { + auto data = _data; + for (auto& row : data) + for (auto& val : row) + { + auto valueString = val.present(); + + if (valueString == "Indeterminate") + valueString = "0"; + else + { + valueString = roundOff(val.present(), prec); + valueString = standardizeNumber(valueString); + } + val.set(valueString); + } + return data; + } + + Matrix Matrix::roundOffValues(const size_t prec) const { return roundOffValues(data, prec); } + + Matrix Matrix::rref() const + { + // Adapted from https://stackoverflow.com/a/31761026/14868780 + auto matrix = data; + matrix = roundOffValues(matrix, static_cast(prec)); +#if defined(STP_DEB_MATRIX_REF_RESULT_INSPECT) && DEBUG + std::cout << prettyPrint::printers::ppMatrix(matrix, 1) << "\n"; +#endif + + for (size_t lead = 0; lead < _rows; lead++) + { + Number divisor("0", 30, RoundingMode::USE_MAXIMUM_PREC); + Number multiplier("0", 30, RoundingMode::USE_MAXIMUM_PREC); + for (size_t r = 0; r < _rows; r++) + { + divisor = matrix[lead][lead]; + multiplier = matrix[r][lead] / matrix[lead][lead]; + for (size_t c = 0; c < _cols; c++) + if (r == lead) + { +#if defined(STP_DEB_CALC_DIVISION_RESULT_INSPECT) && DEBUG + auto oldMatrixRC = matrix[r][c]; +#endif + + matrix[r][c] /= divisor; + +#if defined(STP_DEB_CALC_DIVISION_RESULT_INSPECT) && DEBUG + output::info("Matrix::rref"s, + oldMatrixRC.present() + " " + std::string(__internals::symbols::DIVIDED_BY) + " " + + divisor.present() + " = " + matrix[r][c].present()); +#endif + } + else + { +#if defined(STP_DEB_MATRIX_REF_RESULT_INSPECT) && DEBUG + auto oldMatrixRC = matrix[r][c]; + auto oldMatrixLeadC = matrix[lead][c]; +#endif + + auto multiplyResult = matrix[lead][c] * multiplier; + matrix[r][c] -= multiplyResult; + +#if defined(STP_DEB_MATRIX_REF_RESULT_INSPECT) && DEBUG + output::info("Matrix::rref"s, + oldMatrixRC.present() + " - " + oldMatrixLeadC.present() + " " + + std::string(__internals::symbols::MULTIPLY) + " " + multiplier.present()); + output::info("Matrix::rref"s, + " = " + oldMatrixRC.present() + " - " + multiplyResult.present()); + output::info("Matrix::rref"s, " = " + matrix[r][c].present()); +#endif + } + + matrix = roundOffValues(matrix, static_cast(prec) + 3); + } +#if defined(STP_DEB_MATRIX_REF_RESULT_INSPECT) && DEBUG + std::cout << prettyPrint::printers::ppMatrix(matrix, 1) << "\n"; +#endif + } + + matrix = roundOffValues(matrix, static_cast(prec)); + return { matrix, prec }; + } + + Matrix Matrix::ref() const + { + MatVec2D mat = data; + mat = roundOffValues(mat, static_cast(prec) + 3); + + for (int col = 0, row = 0; col < _cols && row < _rows; ++col) + { + // Find first non-zero in column col, at or below row + int sel = -1; + for (int i = row; i < _rows; ++i) + if (mat[i][col] != 0.0) + { + sel = i; + break; + } + if (sel == -1) + continue; // All zeros in this column + + if (sel != row) + std::swap(mat[row], mat[sel]); // Swap if needed + + // Eliminate below + for (int i = row + 1; i < _rows; ++i) + { + Number factor = mat[i][col] / mat[row][col]; + for (int j = col; j < _cols; ++j) + mat[i][j] -= factor * mat[row][j]; + } + ++row; + } + mat = roundOffValues(mat, static_cast(prec)); + return { mat, prec }; + } + + Number Matrix::det() const + { + if (_rows != _cols) + { + output::error("Matrix::det"s, "Matrix is not a square."s); + utils::programSafeExit(1); + } + int sign = 1; + Number determinant = 1; + MatVec2D mat = data; + mat = roundOffValues(mat, static_cast(prec) + 3); + + for (size_t col = 0, row = 0; col < _cols && row < _rows; ++col) + { + // Find first non-zero in column col, at or below row + size_t sel = -1; + for (size_t i = row; i < _rows; ++i) + if (mat[i][col] != 0.0) + { + sel = i; + break; + } + if (sel == -1) + continue; // All zeros in this column + + if (sel != row) + { + // Swap rows - determinant becomes negative + sign = -sign; + std::swap(mat[row], mat[sel]); + } + + // Eliminate below + for (size_t i = row + 1; i < _rows; ++i) + { + Number factor = mat[i][col] / mat[row][col]; + for (size_t j = col; j < _cols; ++j) + mat[i][j] -= factor * mat[row][j]; + } + ++row; + } + determinant *= sign; + mat = roundOffValues(mat, static_cast(prec)); + for (size_t i = 0; i < _cols; i++) + determinant *= mat[i][i]; + return determinant; + } + + Matrix Matrix::operator+(const Matrix& rhs) const + { + if (rhs._cols != _cols) + { + output::error("Matrix::operator+"s, + "Matrix dimensions mismatch. Expect {0} columns. Got {1} columns."s, + { std::to_string(_cols), std::to_string(rhs._cols) }); + utils::programSafeExit(1); + } + if (rhs._rows != _rows) + { + output::error("Matrix::operator+"s, + "Matrix dimensions mismatch. Expect {0} rows. Got {1} rows."s, + { std::to_string(_rows), std::to_string(rhs._rows) }); + utils::programSafeExit(1); + } + + Matrix output = Matrix::zeros(_cols, _rows); + + for (size_t i = 0; i < _rows; i++) + for (size_t j = 0; j < _cols; j++) + output.data[i][j] += rhs.data[i][j]; + return output; + } + + Matrix Matrix::operator+() const { return *this; } + + Matrix Matrix::operator+=(const Matrix& rhs) + { + *this = *this + rhs; + return *this; + } + + Matrix Matrix::operator-(const Matrix& rhs) const { return *this + -rhs; } + + Matrix Matrix::operator-() const + { + Matrix newMatrix = *this; + for (auto& row : newMatrix.data) + for (auto& value : row) + value = -value; + return newMatrix; + } + + Matrix Matrix::operator-=(const Matrix& rhs) + { + *this = *this - rhs; + return *this; + } + + Matrix Matrix::operator*(const Number& rhs) const + { + Matrix newMatrix = *this; + for (auto& row : newMatrix.data) + for (auto& value : row) + value *= rhs; + return newMatrix; + } + + Matrix Matrix::operator*(const Matrix& rhs) const + { + if (_cols != rhs._rows) + { + output::error("Matrix::operator*"s, "Incorrect matrix dimensions for multiplication."s); + // https://en.wikipedia.org/wiki/Matrix_multiplication + output::info("Matrix::operator*"s, + "For matrix multiplication, the number of columns in the first matrix must be equal to the " + "number of rows in the second matrix"s); + utils::programSafeExit(1); + } + Matrix matrix = Matrix::zeros(_rows, rhs._cols); + for (size_t j = 0; j < rhs._rows; j++) + for (size_t k = 0; k < _cols; k++) + for (size_t i = 0; i < _rows; i++) + matrix.data[i][j] += data[i][k] * rhs.data[k][j]; + return matrix; + } + + Matrix Matrix::operator<<(const Matrix& rhs) const + { + if (rhs._rows != _rows) + { + output::error("Matrix::operator<<"s, + "Incorrect RHS matrix dimensions. Expect {0} rows, got {1}"s, + { std::to_string(rhs._rows), std::to_string(rhs._rows) }); + utils::programSafeExit(1); + } + + auto matrix = Matrix(_rows, _cols + rhs._cols); + + // Copy current matrix. + for (size_t i = 0; i < _rows; i++) + for (size_t j = 0; j < _cols; j++) + matrix[{ .y = i, .x = j }] = data[i][j]; + + // Copy other matrix. + for (size_t i = 0; i < _rows; i++) + for (size_t j = 0; j < rhs._cols; j++) + matrix[{ .y = i, .x = j + _cols }] = rhs.data[i][j]; + + return matrix; + } + + Matrix Matrix::operator>>(const Matrix& rhs) const + { + if (rhs._rows != _rows) + { + output::error("Matrix::operator>>"s, + "Incorrect RHS matrix dimensions. Expect {0} rows, got {1}"s, + { std::to_string(rhs._rows), std::to_string(rhs._rows) }); + utils::programSafeExit(1); + } + + auto matrix = Matrix(_rows, _cols + rhs._cols); + + // Copy other matrix. + for (size_t i = 0; i < _rows; i++) + for (size_t j = 0; j < rhs._cols; j++) + matrix[{ .y = i, .x = j }] = rhs.data[i][j]; + + // Copy current matrix. + for (size_t i = 0; i < _rows; i++) + for (size_t j = 0; j < _cols; j++) + matrix[{ .y = i, .x = j + _cols }] = data[i][j]; + + return matrix; + } + + Matrix Matrix::operator<<=(const Matrix& rhs) + { + *this = *this << rhs; + return *this; + } + + Matrix Matrix::operator>>=(const Matrix& rhs) + { + *this = *this >> rhs; + return *this; + } + + Matrix Matrix::operator*=(const Number& rhs) + { + *this = *this * rhs; + return *this; + } + + Matrix Matrix::operator*=(const Matrix& rhs) + { + *this = *this * rhs; + return *this; + } + + Matrix Matrix::operator^(const Number& times) const + { + if (_rows != _cols) + { + output::error("Matrix::operator^"s, "Matrix is not square."s); + utils::programSafeExit(1); + } + + auto matrix = *this; + + // Take inverse of matrix + if (times == -1) + { + matrix <<= diag(_rows); + matrix = matrix.rref(); + matrix = matrix[{ .y1 = 0, .x1 = _rows, .y2 = _rows - 1, .x2 = _cols * 2 - 1 }]; + return matrix; + } + for (Number i = 0; i < times; ++i) + matrix *= matrix; + return matrix; + } + + std::string Matrix::present(const int endRows) const { return prettyPrint::printers::ppMatrix(data, endRows); } + + Matrix Matrix::ones(const size_t rows, const size_t cols) + { + auto matrix = Matrix(rows, cols, Number("1")); + return matrix; + } + + Matrix Matrix::zeros(const size_t rows, const size_t cols) + { + auto matrix = Matrix(rows, cols); + return matrix; + } + + Matrix Matrix::diag(const size_t colsRows, const Number& fill) + { + auto matrix = Matrix(colsRows, colsRows); + for (size_t i = 0; i < colsRows; i++) + matrix[{ .y = i, .x = i }] = fill; + return matrix; + } + + Number Matrix::rank() const + { + auto matrix = *this; + matrix = matrix.rref(); + +#if defined(STP_DEB_MATRIX_REF_RESULT_INSPECT) && DEBUG + std::cout << prettyPrint::printers::ppMatrix(matrix.data) << "\n"; +#endif + size_t rank = 0; + + for (const auto& row : matrix) + { + size_t count = 0; + for (const auto& val : row) + if (val != 0) + count++; + + if (count != 0) + rank++; + } + return { rank }; + } + + Matrix Matrix::transpose() const + { + Matrix matrix(_cols, _rows); + for (size_t i = 0; i < _cols; i++) + for (size_t j = 0; j < _rows; j++) + matrix[{ .y = j, .x = i }] = data[i][j]; + return matrix; + } + + bool Matrix::operator==(const Matrix& rhs) const { return data == rhs.data; } + + bool Matrix::operator!=(const Matrix& rhs) const { return not(*this == rhs); } + + Number& Matrix::operator[](const YXPoint& point) + { + const auto x = point.x; + const auto y = point.y; + + _checkIdxSanity(&point); + return data[y][x]; + } + + Number Matrix::operator[](const YXPoint& point) const + { + const auto x = point.x; + const auto y = point.y; + + _checkIdxSanity(&point); + return data[y][x]; + } + + Matrix Matrix::operator[](const YX2Points& point) const + { + auto [y1, x1, y2, x2] = point; + const auto startPoint = YXPoint{ .y = y1, .x = x1 }; + _checkIdxSanity(&startPoint); + + const auto endPoint = YXPoint{ .y = y2, .x = x2 }; + _checkIdxSanity(&endPoint); + + // Make sure the end dimensions are always greater + if (y2 < y1 or x2 < x1) + { + std::swap(y2, y1); + std::swap(x2, x1); + } + + auto matrix = Matrix(y2 - y1 + 1, x2 - x1 + 1); + + for (size_t i = y1; i <= y2; i++) + for (size_t j = x1; j <= x2; j++) + matrix[{ .y = i - y1, .x = j - x1 }] = data[i][j]; + return matrix; + } +} // namespace steppable diff --git a/src/number.cpp b/src/steppable/number.cpp similarity index 66% rename from src/number.cpp rename to src/steppable/number.cpp index 838f2e061..86f99dd38 100644 --- a/src/number.cpp +++ b/src/steppable/number.cpp @@ -28,10 +28,15 @@ * @date 1st April 2024 */ -#include "number.hpp" +#include "steppable/number.hpp" #include "fn/calc.hpp" #include "output.hpp" +#include "rounding.hpp" +#include "util.hpp" + +#include +#include #ifdef WINDOWS #undef max @@ -42,42 +47,48 @@ namespace steppable { using namespace steppable::__internals::calc; - Number::Number() : prec(8), value("0") {} + Number::Number() : value("0"), prec(8) {} + + Number::Number(const Number& rhs) : value(rhs.value), prec(rhs.prec) {} - Number::Number(std::string value, size_t prec, RoundingMode mode) : value(std::move(value)), prec(prec), mode(mode) + Number::Number(std::string value, const size_t prec, const RoundingMode mode) : + value(std::move(value)), prec(prec), mode(mode) { } - Number Number::operator+(const Number& rhs) const { return add(value, rhs.value, 0); } + Number Number::operator+(const Number& rhs) const { return Number(add(value, rhs.value, 0), prec, mode); } - Number Number::operator-(const Number& rhs) const { return subtract(value, rhs.value, 0); } + Number Number::operator-(const Number& rhs) const { return Number(subtract(value, rhs.value, 0), prec, mode); } - Number Number::operator*(const Number& rhs) const { return multiply(value, rhs.value, 0, static_cast(prec)); } + Number Number::operator*(const Number& rhs) const + { + const size_t usePrec = determinePrec<"operator*">(rhs); + const auto result = multiply(value, rhs.value, 0, static_cast(usePrec) + 2); + return Number{ __internals::numUtils::roundOff(result, usePrec), usePrec, mode }; + } Number Number::operator/(const Number& rhs) const { - size_t usePrec = 0; - if (mode == USE_MAXIMUM_PREC) - usePrec = std::max(prec, rhs.prec); - else if (mode == USE_MINIMUM_PREC) - usePrec = std::min(prec, rhs.prec); - else if (mode == USE_CURRENT_PREC) - usePrec = prec; - else if (mode == USE_OTHER_PREC) - usePrec = rhs.prec; - else if (mode == DISCARD_ALL_DECIMALS) - usePrec = 0; - else - { - usePrec = 0; - output::warning("Number::operator/"s, "Invalid precision specified"s); - } - return divide(value, rhs.value, 0, static_cast(usePrec)); + const size_t usePrec = determinePrec<"operator/">(rhs); + const auto result = divide(value, rhs.value, 0, static_cast(usePrec) + 2); + return Number{ __internals::numUtils::roundOff(result, usePrec), usePrec, mode }; } - Number Number::operator%(const Number& rhs) const { return divideWithQuotient(value, rhs.value).remainder; } + Number Number::operator%(const Number& rhs) const + { + return Number(divideWithQuotient(value, rhs.value).remainder, prec, mode); + } - Number Number::operator^(const Number& rhs) const { return power(value, rhs.value, 0, static_cast(prec)); } + Number Number::mod(const Number& rhs) const + { + return Number(divideWithQuotient(value, rhs.value).quotient, prec, mode); + } + + Number Number::operator^(const Number& rhs) + { + const size_t usePrec = determinePrec<"operator^">(rhs); + return Number(power(value, rhs.value, 0, static_cast(usePrec)), usePrec, mode); + } Number& Number::operator+=(const Number& rhs) { @@ -87,18 +98,21 @@ namespace steppable Number& Number::operator-=(const Number& rhs) { + prec = determinePrec<"operator-=">(rhs); *this = *this - rhs; return *this; } Number& Number::operator*=(const Number& rhs) { + prec = determinePrec<"operator*=">(rhs); *this = *this * rhs; return *this; } Number& Number::operator/=(const Number& rhs) { + prec = determinePrec<"operator/=">(rhs); *this = *this / rhs; return *this; } @@ -127,6 +141,16 @@ namespace steppable bool Number::operator>=(const Number& rhs) const { return compare(value, rhs.value, 0) != "0"; } + Number Number::operator-() const + { + auto newValue = "-" + value; + newValue = __internals::numUtils::standardizeNumber(newValue); + Number number(newValue, prec, mode); + return number; + } + + Number Number::operator+() const { return *this; } + Number Number::operator++() { *this += Number("1"); diff --git a/src/testing.cpp b/src/testing.cpp index 4ae92c3a1..2f435fbc3 100644 --- a/src/testing.cpp +++ b/src/testing.cpp @@ -35,7 +35,7 @@ namespace steppable::testing { TestCase::TestCase(std::string testCaseName) : testCaseName(std::move(testCaseName)) {} - void TestCase::assert(const bool condition, const std::string& conditionName) + void TestCase::_assertCondition(const bool condition, const std::string& conditionName) { if (condition) { @@ -51,39 +51,25 @@ namespace steppable::testing void TestCase::assertIsEqual(const std::string& a, const std::string& b) { const std::string& conditionName = format::format("String {0} == {1}", { a, b }); - assert(a == b, conditionName); + _assertCondition(a == b, conditionName); } void TestCase::assertIsNotEqual(const std::string& a, const std::string& b) { const std::string& conditionName = format::format("String {0} != {1}", { a, b }); - assert(a != b, conditionName); - } - - void TestCase::assertIsEqual(const int a, const int b) - { - const std::string& conditionName = - format::format("Integer {0} == {1}", { std::to_string(a), std::to_string(b) }); - assert(a == b, conditionName); - } - - void TestCase::assertIsNotEqual(const int a, const int b) - { - const std::string& conditionName = - format::format("Integer {0} != {1}", { std::to_string(a), std::to_string(b) }); - assert(a != b, conditionName); + _assertCondition(a != b, conditionName); } void TestCase::assertTrue(const bool value) { const std::string& conditionName = format::format("{0} is True", { std::to_string(static_cast(value)) }); - assert(value, conditionName); + _assertCondition(value, conditionName); } void TestCase::assertFalse(const bool value) { const std::string& conditionName = format::format("{0} is False", { std::to_string(static_cast(value)) }); - assert(not value, conditionName); + _assertCondition(not value, conditionName); } void TestCase::summarize() const diff --git a/src/util.cpp b/src/util.cpp index 1177a1fdf..c874ce77a 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -68,15 +68,21 @@ namespace steppable::__internals::numUtils std::string simplifyZeroPolarity(const std::string& string) { + if (string.empty()) + return "0"; // Check if the string is zero if (isZeroString(string)) return "0"; - return static_cast(string); + return string; } std::string simplifyPolarity(const std::string& _string) { - auto string = simplifyZeroPolarity(_string); + if (_string.empty()) + return "0"; + + auto string = _string; + string = simplifyZeroPolarity(string); while (string[0] == '-' and string[1] == '-') string = string.substr(2); return string; @@ -84,6 +90,9 @@ namespace steppable::__internals::numUtils std::string standardizeNumber(const std::string& _number) { + if (_number.empty()) + return "0"; + auto number = simplifyPolarity(_number); // Remove the trailing decimal point if (number.back() == '.') @@ -91,6 +100,8 @@ namespace steppable::__internals::numUtils if (number.empty()) return "0"; + if (number == "-") + return "-0"; if (number.front() == '+') number.erase(0, 1); if (number.front() == '.') @@ -112,23 +123,17 @@ namespace steppable::__internals::numUtils auto a = static_cast(_a); auto b = static_cast(_b); - if (properlyFormat) + if (a.front() == '-') { - a = simplifyPolarity(_a), b = simplifyPolarity(_b); - if (a.front() == '-') - { - aIsNegative = true; - if (not preserveNegative) - a.erase(a.begin()); - } - if (b.front() == '-') - { - bIsNegative = true; - if (not preserveNegative) - b.erase(b.begin()); - } - a = removeLeadingZeros(a); - b = removeLeadingZeros(b); + aIsNegative = true; + if (not preserveNegative) + a.erase(a.begin()); + } + if (b.front() == '-') + { + bIsNegative = true; + if (not preserveNegative) + b.erase(b.begin()); } const std::vector aParts = stringUtils::split(a, '.'); const std::vector bParts = stringUtils::split(b, '.'); @@ -276,7 +281,11 @@ namespace steppable::__internals::numUtils if (number.front() == '-') number = number.substr(1, number.length() - 1); // Remove negative sign as it does nothing here. if (isDecimal(number)) - return not std::ranges::any_of(number, [](const auto& c) { return c != '0' and c != '.' and c != '1'; }); + { + return not std::ranges::any_of(number.substr(0, number.length() - 1), [](const auto& c) { + return c != '0' and c != '.'; + }) and number.back() == '1'; + } if (number == "1") return true; // 1 is a power of 10. if (number.front() != '1') diff --git a/steppyble/__init__.pyi b/steppyble/__init__.pyi index a62e1732d..7877de5eb 100644 --- a/steppyble/__init__.pyi +++ b/steppyble/__init__.pyi @@ -20,6 +20,18 @@ # SOFTWARE. # ##################################################################################################### -from .number import * -from .fraction import * -from .rounding_mode import * +""" +The Steppable library Python bindings +===================================== +This library provides the Python bindings to C++ functions of Steppable. + +Steppable +--------- +This project aims to make a Computer Algebra System (CAS) from scratch, and without any external libraries. +See https://github.com/ZCG-coder/Steppable for the project. +""" + +from ._fraction import * +from ._matrix import * +from ._number import * +from ._rounding_mode import * diff --git a/steppyble/_fraction.pyi b/steppyble/_fraction.pyi new file mode 100644 index 000000000..291988337 --- /dev/null +++ b/steppyble/_fraction.pyi @@ -0,0 +1,326 @@ +##################################################################################################### +# Copyright (c) 2023-2025 NWSOFT # +# # +# 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. # +##################################################################################################### + +import steppyble + +class Fraction: + def __init__(self, top: str = "1", bottom: str = "1") -> None: + """ + Initializes a Fraction. + + Parameters + ---------- + top : str, optional + Numerator of the fraction (default is "1"). + bottom : str, optional + Denominator of the fraction (default is "1"). + """ + ... + + def __repr__(self) -> str: + """ + Returns the string representation of the fraction. + + Returns + ------- + str + String representation of the fraction. + """ + ... + + def __add__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction: + """ + Adds another fraction to this fraction. + + Parameters + ---------- + rhs : steppyble.Fraction + The fraction to add. + + Returns + ------- + steppyble.Fraction + The sum of the two fractions. + """ + ... + + def __iadd__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction: + """ + Adds another fraction to this fraction in-place. + + Parameters + ---------- + rhs : steppyble.Fraction + The fraction to add. + + Returns + ------- + steppyble.Fraction + The sum of the two fractions (in-place). + """ + ... + + def __sub__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction: + """ + Subtracts another fraction from this fraction. + + Parameters + ---------- + rhs : steppyble.Fraction + The fraction to subtract. + + Returns + ------- + steppyble.Fraction + The difference of the two fractions. + """ + ... + + def __isub__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction: + """ + Subtracts another fraction from this fraction in-place. + + Parameters + ---------- + rhs : steppyble.Fraction + The fraction to subtract. + + Returns + ------- + steppyble.Fraction + The difference of the two fractions (in-place). + """ + ... + + def __neg__(self, /) -> steppyble.Fraction: + """ + Returns the negation of the fraction. + + Returns + ------- + steppyble.Fraction + The negated fraction. + """ + ... + + def __pos__(self, /) -> steppyble.Fraction: + """ + Returns a copy of the fraction (unary plus). + + Returns + ------- + steppyble.Fraction + A copy of the fraction. + """ + ... + + def __mul__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction: + """ + Multiplies this fraction by another fraction. + + Parameters + ---------- + rhs : steppyble.Fraction + The fraction to multiply with. + + Returns + ------- + steppyble.Fraction + The product of the two fractions. + """ + ... + + def __imul__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction: + """ + Multiplies this fraction by another fraction in-place. + + Parameters + ---------- + rhs : steppyble.Fraction + The fraction to multiply with. + + Returns + ------- + steppyble.Fraction + The product of the two fractions (in-place). + """ + ... + + def __truediv__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction: + """ + Divides this fraction by another fraction. + + Parameters + ---------- + rhs : steppyble.Fraction + The fraction to divide by. + + Returns + ------- + steppyble.Fraction + The quotient of the two fractions. + """ + ... + + def __itruediv__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction: + """ + Divides this fraction by another fraction in-place. + + Parameters + ---------- + rhs : steppyble.Fraction + The fraction to divide by. + + Returns + ------- + steppyble.Fraction + The quotient of the two fractions (in-place). + """ + ... + + def __pow__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction: + """ + Raises this fraction to the power of another fraction. + + Parameters + ---------- + rhs : steppyble.Fraction + The exponent. + + Returns + ------- + steppyble.Fraction + The result of exponentiation. + """ + ... + + def __ipow__(self, rhs: steppyble.Fraction, /) -> steppyble.Fraction: + """ + Raises this fraction to the power of another fraction in-place. + + Parameters + ---------- + rhs : steppyble.Fraction + The exponent. + + Returns + ------- + steppyble.Fraction + The result of exponentiation (in-place). + """ + ... + + def __eq__(self, _: object, /) -> bool: + """ + Checks if this fraction is equal to another object. + + Parameters + ---------- + _ : object + The object to compare with. + + Returns + ------- + bool + True if equal, False otherwise. + """ + ... + + def __ne__(self, _: object, /) -> bool: + """ + Checks if this fraction is not equal to another object. + + Parameters + ---------- + _ : object + The object to compare with. + + Returns + ------- + bool + True if not equal, False otherwise. + """ + ... + + def __ge__(self, rhs: steppyble.Fraction, /) -> bool: + """ + Checks if this fraction is greater than or equal to another fraction. + + Parameters + ---------- + rhs : steppyble.Fraction + The fraction to compare with. + + Returns + ------- + bool + True if greater than or equal, False otherwise. + """ + ... + + def __gt__(self, rhs: steppyble.Fraction, /) -> bool: + """ + Checks if this fraction is greater than another fraction. + + Parameters + ---------- + rhs : steppyble.Fraction + The fraction to compare with. + + Returns + ------- + bool + True if greater, False otherwise. + """ + ... + + def __le__(self, rhs: steppyble.Fraction, /) -> bool: + """ + Checks if this fraction is less than or equal to another fraction. + + Parameters + ---------- + rhs : steppyble.Fraction + The fraction to compare with. + + Returns + ------- + bool + True if less than or equal, False otherwise. + """ + ... + + def __lt__(self, rhs: steppyble.Fraction, /) -> bool: + """ + Checks if this fraction is less than another fraction. + + Parameters + ---------- + rhs : steppyble.Fraction + The fraction to compare with. + + Returns + ------- + bool + True if less, False otherwise. + """ + ... diff --git a/steppyble/_gui/__init__.py b/steppyble/_gui/__init__.py new file mode 100644 index 000000000..ac262d814 --- /dev/null +++ b/steppyble/_gui/__init__.py @@ -0,0 +1,109 @@ +##################################################################################################### +# Copyright (c) 2023-2025 NWSOFT # +# # +# 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. # +##################################################################################################### + +import platform +import tkinter as tk +from tkinter import ttk + + +class MenuButton(ttk.Label): + def __init__(self, master, name: str): + super().__init__(master, takefocus=True, padding=(5, 0)) + self["text"] = name + self.bind("", self._open_popup) + self._menu = tk.Menu(self) + self._menu.bind("", lambda _: self._menu.grab_release()) + + def _open_popup(self, _): + try: + self._menu.tk_popup( + self.winfo_rootx(), self.winfo_rooty() + self.winfo_height() + ) + finally: + self._menu.grab_release() + + def add_command(self, *args, **kwargs): + self._menu.add_command(*args, **kwargs) + + def add_checkbutton(self, *args, **kwargs): + self._menu.add_checkbutton(*args, **kwargs) + + def add_cascade(self, *args, **kwargs): + self._menu.add_cascade(*args, **kwargs) + + def add_radiobutton(self, *args, **kwargs): + self._menu.add_radiobutton(*args, **kwargs) + + def add_separator(self, *args, **kwargs): + self._menu.add_separator(*args, **kwargs) + + def pack(self, *args, **kwargs): + kwargs["side"] = tk.LEFT + kwargs["ipadx"] = 10 + kwargs["ipady"] = 2 + super().pack(*args, **kwargs) + + +class Menu(ttk.Frame): + def __init__(self, master): + super().__init__(master) + + +class _MainWindow(tk.Tk): + def __init__( + self, + screenName: str | None = None, + baseName: str | None = None, + className: str = "Tk", + useTk: bool = True, + sync: bool = False, + use: str | None = None, + ) -> None: + super().__init__(screenName, baseName, className, useTk, sync, use) + self.title("Steppable") + + # Fix the title on macOS + if platform.system() == "Darwin": + _menu = tk.Menu() + _python_menu = tk.Menu(_menu, name="apple") + _menu.add_cascade(menu=_python_menu) + self["menu"] = _menu + _python_menu.destroy() + + menu_frame = Menu(self) + menu = MenuButton(menu_frame, name="FILE") + # Add options to the menu + menu.add_command(label="Option 1") + menu.add_command(label="Option 2") + menu.pack() + menu = MenuButton(menu_frame, name="FILE") + # Add options to the menu + menu.add_command(label="Option 1") + menu.add_command(label="Option 2") + menu.pack() + + menu_frame.pack(anchor=tk.W) + + +if __name__ == "__main__": + win = _MainWindow() + win.mainloop() diff --git a/steppyble/_matrix.pyi b/steppyble/_matrix.pyi new file mode 100644 index 000000000..ee9f676a9 --- /dev/null +++ b/steppyble/_matrix.pyi @@ -0,0 +1,571 @@ +##################################################################################################### +# Copyright (c) 2023-2025 NWSOFT # +# # +# 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. # +##################################################################################################### + +import typing + +import steppyble +import steppyble._types + +class Matrix: + """ + A Steppable matrix supporting arithmetic and matrix operations. + + Parameters + ---------- + values : Sequence[Sequence[steppyble.Number]] or Sequence[Sequence[float]] + The values of the matrix. + prec : int, optional + Precision of the values to be copied into the matrix (default is 5). + + Notes + ----- + This class mimics Python's number behavior for matrices, supporting addition, subtraction, multiplication, and more. + """ + + @typing.overload + def __init__( + self, + values: typing.Sequence[typing.Sequence[steppyble.Number]], + prec: int = 5, + ) -> None: + """ + Constructs a matrix with specified dimensions and an optional fill value. + + Parameters + ---------- + values + The values of the matrix. + prec + Precision of the values to be copied into the matrix. + """ + + ... + + @typing.overload + def __init__( + self, + values: typing.Sequence[typing.Sequence[float]], + prec: int = 5, + ) -> None: + """ + Constructs a matrix with specified dimensions and an optional fill value. + + Parameters + ---------- + values + The values of the matrix. + prec + Precision of the values to be copied into the matrix. + """ + ... + + def __repr__(self) -> str: + """ + Represents the values in the matrix in text form. + + Returns + ------- + str + A string representing the matrix. + """ + + ... + + def __lshift__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix: + """ + Joins a matrix to the right of the current matrix. + + Parameters + ---------- + matrix + The matrix to join. + + Return + ------ + steppyble.Matrix + The two matrices joined together. + """ + + ... + + def __ilshift__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix: + """ + Joins a matrix to the right of the current matrix, assigning the result to the current one. + + Parameters + ---------- + matrix + The matrix to join. + + Return + ------ + steppyble.Matrix + The two matrices joined together. + """ + + ... + + def __rshift__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix: + """ + Joins a matrix to the left of the current matrix. + + Parameters + ---------- + matrix + The matrix to join. + + Return + ------ + steppyble.Matrix + The two matrices joined together. + """ + + ... + + def __irshift__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix: + """ + Joins a matrix to the left of the current matrix, assigning the result to the current one. + + Parameters + ---------- + matrix + The matrix to join. + + Return + ------ + steppyble.Matrix + The two matrices joined together. + """ + ... + + def __add__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix: + """ + Adds another matrix to the current one. + + Parameters + ---------- + matrix + The other matrix. + + Returns + ------- + steppyble.Matrix + A new matrix where all elements are added together correspondingly. + """ + ... + + def __iadd__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix: + """ + Adds another matrix to the current one, assigning the result to the current one. + + Parameters + ---------- + matrix + The other matrix. + + Returns + ------- + steppyble.Matrix + A new matrix where all elements are added together correspondingly. + """ + ... + + def __sub__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix: + """ + Subtracts another matrix from the current one. + + Parameters + ---------- + matrix : steppyble.Matrix + The other matrix. + + Returns + ------- + steppyble.Matrix + A new matrix where all elements are subtracted correspondingly. + """ + ... + + def __neg__(self, /) -> steppyble.Matrix: + """ + Returns the negation of the matrix (element-wise negation). + + Returns + ------- + steppyble.Matrix + A new matrix with all elements negated. + """ + ... + + def __pos__(self, /) -> steppyble.Matrix: + """ + Returns a copy of the matrix (unary plus). + + Returns + ------- + steppyble.Matrix + A copy of the matrix. + """ + ... + + def __isub__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix: + """ + Subtracts another matrix from the current one, assigning the result to the current one. + + Parameters + ---------- + matrix : steppyble.Matrix + The other matrix. + + Returns + ------- + steppyble.Matrix + The updated matrix after subtraction. + """ + ... + + @typing.overload + def __mul__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix: + """ + Multiplies the current matrix by another matrix (matrix multiplication). + + Parameters + ---------- + matrix : steppyble.Matrix + The matrix to multiply with. + + Returns + ------- + steppyble.Matrix + The matrix product. + """ + ... + + @typing.overload + def __mul__(self, _: steppyble.Number, /) -> steppyble.Matrix: + """ + Multiplies the current matrix by a scalar. + + Parameters + ---------- + _ : steppyble.Number + The scalar to multiply with. + + Returns + ------- + steppyble.Matrix + The scaled matrix. + """ + ... + + @typing.overload + def __imul__(self, _: steppyble.Number, /) -> steppyble.Matrix: + """ + Multiplies the current matrix by a scalar in-place. + + Parameters + ---------- + _ : steppyble.Number + The scalar to multiply with. + + Returns + ------- + steppyble.Matrix + The scaled matrix (in-place). + """ + ... + + @typing.overload + def __imul__(self, matrix: steppyble.Matrix, /) -> steppyble.Matrix: + """ + Multiplies the current matrix by another matrix in-place (matrix multiplication). + + Parameters + ---------- + matrix : steppyble.Matrix + The matrix to multiply with. + + Returns + ------- + steppyble.Matrix + The matrix product (in-place). + """ + ... + + def __pow__(self, _: steppyble.Number, /) -> steppyble.Matrix: + """ + Raises the matrix to a given power. + + Parameters + ---------- + _ : steppyble.Number + The exponent. + + Returns + ------- + steppyble.Matrix + The matrix raised to the given power. + """ + ... + + def __ipow__(self, _: steppyble.Number, /) -> steppyble.Matrix: + """ + Raises the matrix to a given power in-place. + + Parameters + ---------- + _ : steppyble.Number + The exponent. + + Returns + ------- + steppyble.Matrix + The matrix raised to the given power (in-place). + """ + ... + + def __eq__(self, _: object, /) -> bool: + """ + Checks if two matrices are equal. + + Parameters + ---------- + _ : object + The object to compare with. + + Returns + ------- + bool + True if the matrices are equal, False otherwise. + """ + ... + + def __ne__(self, _: object, /) -> bool: + """ + Checks if two matrices are not equal. + + Parameters + ---------- + _ : object + The object to compare with. + + Returns + ------- + bool + True if the matrices are not equal, False otherwise. + """ + ... + + @staticmethod + def zeros(rows: int, cols: int) -> steppyble.Matrix: + """ + Returns a matrix filled with zeros. + + Parameters + ---------- + rows : int + Number of rows. + cols : int + Number of columns. + + Returns + ------- + steppyble.Matrix + A matrix of shape (rows, cols) filled with zeros. + """ + ... + + @typing.overload + @staticmethod + def diag( + rows_cols: int, fill: steppyble.Number = steppyble.Number("1") + ) -> steppyble.Matrix: + """ + Returns a diagonal matrix with a specified fill value (steppyble.Number). + + Parameters + ---------- + rows_cols : int + Number of rows and columns (matrix is square). + fill : steppyble.Number, optional + Value to fill the diagonal (default is steppyble.Number("1")). + + Returns + ------- + steppyble.Matrix + A diagonal matrix. + """ + ... + + @typing.overload + @staticmethod + def diag(rows_cols: int, fill: float = 1) -> steppyble.Matrix: + """ + Returns a diagonal matrix with a specified fill value (float). + + Parameters + ---------- + rows_cols : int + Number of rows and columns (matrix is square). + fill : float, optional + Value to fill the diagonal (default is 1). + + Returns + ------- + steppyble.Matrix + A diagonal matrix. + """ + ... + + @staticmethod + def ones(rows: int, cols: int) -> steppyble.Matrix: + """ + Returns a matrix filled with ones. + + Parameters + ---------- + rows : int + Number of rows. + cols : int + Number of columns. + + Returns + ------- + steppyble.Matrix + A matrix of shape (rows, cols) filled with ones. + """ + ... + + def rref(self) -> steppyble.Matrix: + """ + Returns the reduced row echelon form (RREF) of the matrix. + + Returns + ------- + steppyble.Matrix + The RREF of the matrix. + """ + ... + + def ref(self) -> steppyble.Matrix: + """ + Returns the row echelon form (REF) of the matrix. + + Returns + ------- + steppyble.Matrix + The REF of the matrix. + """ + ... + + def rank(self) -> steppyble.Number: + """ + Returns the rank of the matrix. + + Returns + ------- + steppyble.Number + The rank of the matrix. + """ + ... + + def det(self) -> steppyble.Matrix: + """ + Returns the determinant of the matrix. + + Returns + ------- + steppyble.Matrix + The determinant of the matrix. + """ + ... + + def transpose(self) -> steppyble.Matrix: + """ + Returns the transpose of the matrix. + + Returns + ------- + steppyble.Matrix + The transposed matrix. + """ + ... + + def __getitem__(self, indices: typing.Tuple[int]) -> steppyble.Matrix: + """ + Returns a submatrix or element at the specified indices. + + Parameters + ---------- + indices : Tuple[int] + Indices to access. + + Returns + ------- + steppyble.Matrix + The submatrix or element at the given indices. + """ + ... + + def rows(self) -> int: + """ + Returns the number of rows of the matrix. + + Returns + ------- + int + The number of rows. + """ + ... + + def cols(self) -> int: + """ + Returns the number of columns of the matrix. + + Returns + ------- + int + The number of columns. + """ + ... + + def dims(self) -> typing.Tuple[int, int]: + """ + Returns the dimensions of the matrix. + + Returns + ------- + typing.Tuple[int, int] + The dimensions of the matrix as a tuple (rows, cols). + """ + ... + + def __iter__(self) -> steppyble._types.MatrixIterator: + """ + Returns an iterator over the matrix rows. + + Returns + ------- + steppyble.Matrix + An iterator over the matrix rows. + """ + ... diff --git a/steppyble/_number.pyi b/steppyble/_number.pyi new file mode 100644 index 000000000..0c7fa4694 --- /dev/null +++ b/steppyble/_number.pyi @@ -0,0 +1,335 @@ +##################################################################################################### +# Copyright (c) 2023-2025 NWSOFT # +# # +# 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. # +##################################################################################################### + +import steppyble + +class Number: + """A Steppable number, which performs exactly like a Python number, allowing add, subtract, multiply, etc.""" + + def __init__( + self, + value: str = "0", + prec: int = 5, + roundingMode: steppyble.RoundingMode = steppyble.RoundingMode.USE_CURRENT_PREC, + ) -> None: + """ + Initializes a Steppable number. + + Parameters + ---------- + value : str, optional + The value to initialize the number with (default is "0"). + prec : int, optional + Precision of the number (default is 5). + roundingMode : steppyble.RoundingMode, optional + Rounding mode to use (default is steppyble.RoundingMode.USE_CURRENT_PREC). + """ + ... + + def __repr__(self) -> str: + """ + Returns the string representation of the number. + + Returns + ------- + str + String representation of the number. + """ + ... + + def __add__(self, rhs: steppyble.Number, /) -> steppyble.Number: + """ + Adds another number to this number. + + Parameters + ---------- + rhs : steppyble.Number + The number to add. + + Returns + ------- + steppyble.Number + The sum of the two numbers. + """ + ... + + def __iadd__(self, rhs: steppyble.Number, /) -> steppyble.Number: + """ + Adds another number to this number in-place. + + Parameters + ---------- + rhs : steppyble.Number + The number to add. + + Returns + ------- + steppyble.Number + The sum of the two numbers (in-place). + """ + ... + + def __sub__(self, rhs: steppyble.Number, /) -> steppyble.Number: + """ + Subtracts another number from this number. + + Parameters + ---------- + rhs : steppyble.Number + The number to subtract. + + Returns + ------- + steppyble.Number + The difference of the two numbers. + """ + ... + + def __neg__(self, /) -> steppyble.Number: + """ + Returns the negation of the number. + + Returns + ------- + steppyble.Number + The negated number. + """ + ... + + def __pos__(self, /) -> steppyble.Number: + """ + Returns a copy of the number (unary plus). + + Returns + ------- + steppyble.Number + A copy of the number. + """ + ... + + def __isub__(self, rhs: steppyble.Number, /) -> steppyble.Number: + """ + Subtracts another number from this number in-place. + + Parameters + ---------- + rhs : steppyble.Number + The number to subtract. + + Returns + ------- + steppyble.Number + The difference of the two numbers (in-place). + """ + ... + + def __mul__(self, rhs: steppyble.Number, /) -> steppyble.Number: + """ + Multiplies this number by another number. + + Parameters + ---------- + rhs : steppyble.Number + The number to multiply with. + + Returns + ------- + steppyble.Number + The product of the two numbers. + """ + ... + + def __imul__(self, rhs: steppyble.Number, /) -> steppyble.Number: + """ + Multiplies this number by another number in-place. + + Parameters + ---------- + rhs : steppyble.Number + The number to multiply with. + + Returns + ------- + steppyble.Number + The product of the two numbers (in-place). + """ + ... + + def __truediv__(self, rhs: steppyble.Number, /) -> steppyble.Number: + """ + Divides this number by another number. + + Parameters + ---------- + rhs : steppyble.Number + The number to divide by. + + Returns + ------- + steppyble.Number + The quotient of the two numbers. + """ + ... + + def __itruediv__(self, rhs: steppyble.Number, /) -> steppyble.Number: + """ + Divides this number by another number in-place. + + Parameters + ---------- + rhs : steppyble.Number + The number to divide by. + + Returns + ------- + steppyble.Number + The quotient of the two numbers (in-place). + """ + ... + + def __pow__(self, rhs: steppyble.Number, /) -> steppyble.Number: + """ + Raises this number to the power of another number. + + Parameters + ---------- + rhs : steppyble.Number + The exponent. + + Returns + ------- + steppyble.Number + The result of exponentiation. + """ + ... + + def __ipow__(self, rhs: steppyble.Number, /) -> steppyble.Number: + """ + Raises this number to the power of another number in-place. + + Parameters + ---------- + rhs : steppyble.Number + The exponent. + + Returns + ------- + steppyble.Number + The result of exponentiation (in-place). + """ + ... + + def __eq__(self, _: object, /) -> bool: + """ + Checks if this number is equal to another object. + + Parameters + ---------- + _ : object + The object to compare with. + + Returns + ------- + bool + True if equal, False otherwise. + """ + ... + + def __ne__(self, _: object, /) -> bool: + """ + Checks if this number is not equal to another object. + + Parameters + ---------- + _ : object + The object to compare with. + + Returns + ------- + bool + True if not equal, False otherwise. + """ + ... + + def __ge__(self, rhs: steppyble.Number, /) -> bool: + """ + Checks if this number is greater than or equal to another number. + + Parameters + ---------- + rhs : steppyble.Number + The number to compare with. + + Returns + ------- + bool + True if greater than or equal, False otherwise. + """ + ... + + def __gt__(self, rhs: steppyble.Number, /) -> bool: + """ + Checks if this number is greater than another number. + + Parameters + ---------- + rhs : steppyble.Number + The number to compare with. + + Returns + ------- + bool + True if greater, False otherwise. + """ + ... + + def __le__(self, rhs: steppyble.Number, /) -> bool: + """ + Checks if this number is less than or equal to another number. + + Parameters + ---------- + rhs : steppyble.Number + The number to compare with. + + Returns + ------- + bool + True if less than or equal, False otherwise. + """ + ... + + def __lt__(self, rhs: steppyble.Number, /) -> bool: + """ + Checks if this number is less than another number. + + Parameters + ---------- + rhs : steppyble.Number + The number to compare with. + + Returns + ------- + bool + True if less, False otherwise. + """ + ... diff --git a/steppyble/rounding_mode.pyi b/steppyble/_rounding_mode.pyi similarity index 99% rename from steppyble/rounding_mode.pyi rename to steppyble/_rounding_mode.pyi index 238532f34..33c7155c6 100644 --- a/steppyble/rounding_mode.pyi +++ b/steppyble/_rounding_mode.pyi @@ -22,7 +22,6 @@ import enum - class RoundingMode(enum.Enum): """Specifies how Steppable should round the number in operations.""" diff --git a/steppyble/fraction.pyi b/steppyble/_types.py similarity index 61% rename from steppyble/fraction.pyi rename to steppyble/_types.py index 489a420df..895dd2164 100644 --- a/steppyble/fraction.pyi +++ b/steppyble/_types.py @@ -20,60 +20,8 @@ # SOFTWARE. # ##################################################################################################### -import steppyble - - -class Fraction: - def __init__(self, top: str = "1", bottom: str = "1") -> None: - ... - - def __repr__(self) -> str: - ... - - def __add__(self, _: steppyble.Fraction, /) -> steppyble.Fraction: - ... - - def __iadd__(self, _: steppyble.Fraction, /) -> steppyble.Fraction: - ... - - def __sub__(self, _: steppyble.Fraction, /) -> steppyble.Fraction: - ... - - def __isub__(self, _: steppyble.Fraction, /) -> steppyble.Fraction: - ... - - def __mul__(self, _: steppyble.Fraction, /) -> steppyble.Fraction: - ... - - def __imul__(self, _: steppyble.Fraction, /) -> steppyble.Fraction: - ... +import typing - def __truediv__(self, _: steppyble.Fraction, /) -> steppyble.Fraction: - ... - - def __itruediv__(self, _: steppyble.Fraction, /) -> steppyble.Fraction: - ... - - def __pow__(self, _: steppyble.Fraction, /) -> steppyble.Fraction: - ... - - def __ipow__(self, _: steppyble.Fraction, /) -> steppyble.Fraction: - ... - - def __eq__(self, _: object, /) -> bool: - ... - - def __ne__(self, _: object, /) -> bool: - ... - - def __ge__(self, _: steppyble.Fraction, /) -> bool: - ... - - def __gt__(self, _: steppyble.Fraction, /) -> bool: - ... - - def __le__(self, _: steppyble.Fraction, /) -> bool: - ... +import steppyble - def __lt__(self, _: steppyble.Fraction, /) -> bool: - ... +MatrixIterator: typing.TypeAlias = typing.Iterator[typing.List[steppyble.Number]] diff --git a/steppyble/number.pyi b/steppyble/number.pyi deleted file mode 100644 index 3e2c8b5e9..000000000 --- a/steppyble/number.pyi +++ /dev/null @@ -1,86 +0,0 @@ -##################################################################################################### -# Copyright (c) 2023-2025 NWSOFT # -# # -# 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. # -##################################################################################################### - -import steppyble - - -class Number: - """A Steppable number, which performs exactly like a Python number, allowing add, subtract, multiply...""" - - def __init__( - self, - value: str = "0", - prec: int = 5, - roundingMode: steppyble.RoundingMode = steppyble.RoundingMode.USE_CURRENT_PREC, - ) -> None: - ... - - def __repr__(self) -> str: - ... - - def __add__(self, _: steppyble.Number, /) -> steppyble.Number: - ... - - def __iadd__(self, _: steppyble.Number, /) -> steppyble.Number: - ... - - def __sub__(self, _: steppyble.Number, /) -> steppyble.Number: - ... - - def __isub__(self, _: steppyble.Number, /) -> steppyble.Number: - ... - - def __mul__(self, _: steppyble.Number, /) -> steppyble.Number: - ... - - def __imul__(self, _: steppyble.Number, /) -> steppyble.Number: - ... - - def __truediv__(self, _: steppyble.Number, /) -> steppyble.Number: - ... - - def __itruediv__(self, _: steppyble.Number, /) -> steppyble.Number: - ... - - def __pow__(self, _: steppyble.Number, /) -> steppyble.Number: - ... - - def __ipow__(self, _: steppyble.Number, /) -> steppyble.Number: - ... - - def __eq__(self, _: object, /) -> bool: - ... - - def __ne__(self, _: object, /) -> bool: - ... - - def __ge__(self, _: steppyble.Number, /) -> bool: - ... - - def __gt__(self, _: steppyble.Number, /) -> bool: - ... - - def __le__(self, _: steppyble.Number, /) -> bool: - ... - - def __lt__(self, _: steppyble.Number, /) -> bool: - ... diff --git a/tests/testFraction.cpp b/tests/testFraction.cpp index 7ebbffa7e..d1799e275 100644 --- a/tests/testFraction.cpp +++ b/tests/testFraction.cpp @@ -21,8 +21,8 @@ **************************************************************************************************/ #include "colors.hpp" -#include "fraction.hpp" #include "output.hpp" +#include "steppable/fraction.hpp" #include "testing.hpp" #include "util.hpp" @@ -65,4 +65,14 @@ fraction.reciprocal(); _.assertIsEqual(fraction.present(), "4/1"); SECTION_END() +SECTION(Unary) +auto fraction = Fraction("1", "4"); +fraction = -fraction; +_.assertIsEqual(fraction.present(), "-1/4"); + +fraction = Fraction("-1", "4"); +fraction = -fraction; +_.assertIsEqual(fraction.present(), "1/4"); +SECTION_END() + TEST_END() diff --git a/tests/testHyp.cpp b/tests/testHyp.cpp index f57efded2..be0ff75e9 100644 --- a/tests/testHyp.cpp +++ b/tests/testHyp.cpp @@ -34,7 +34,7 @@ TEST_START() using namespace steppable::__internals::calc; SECTION(Test hyperbolic sine) -_.assertIsEqual(sinh("10", 3), "11013.233"); +_.assertIsEqual(sinh("10", 4), "11013.2329"); SECTION_END() SECTION(Test hyperbolic cosine) diff --git a/tests/testMat2d.cpp b/tests/testMat2d.cpp new file mode 100644 index 000000000..044834333 --- /dev/null +++ b/tests/testMat2d.cpp @@ -0,0 +1,153 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * 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. * + **************************************************************************************************/ + +#include "steppable/mat2d.hpp" +#include "steppable/number.hpp" +#include "testing.hpp" +#include "util.hpp" + +#include +#include + +TEST_START() +SECTION(Matrix multiplication) +steppable::Matrix mat1({ + { 1, 0, 1 }, + { 2, 1, 1 }, + { 0, 1, 1 }, + { 1, 1, 2 }, +}); +steppable::Matrix mat2({ + { 1, 2, 1 }, + { 2, 3, 1 }, + { 4, 2, 2 }, +}); +auto mat3 = mat1 * mat2; + +_.assertIsEqual(mat3, + steppable::Matrix({ + { 5, 4, 3 }, + { 8, 9, 5 }, + { 6, 5, 3 }, + { 11, 9, 6 }, + })); +SECTION_END() + +SECTION(Transpose) +steppable::Matrix mat2({ + { 1, 2, 1 }, + { 2, 3, 1 }, + { 4, 2, 2 }, +}); +// Two transposes results in the same matrix +// T +// ( T ) +// ( A ) = A +_.assertIsEqual(mat2.transpose().transpose(), mat2); +_.assertIsNotEqual(mat2.transpose(), mat2); +SECTION_END() + +SECTION(Determinant) +steppable::Matrix mat2({ + { 6, 90 }, + { 4892, 892 }, +}); +_.assertIsEqual(mat2.det(), steppable::Number(-434928)); +SECTION_END() + +SECTION(Matrix slicing) +steppable::Matrix matrix({ + { 5, 4, 3 }, + { 8, 9, 5 }, + { 6, 5, 3 }, + { 11, 9, 6 }, +}); +matrix = matrix[{ .y1 = 1, .x1 = 0, .y2 = 1, .x2 = 2 }]; +_.assertIsEqual(matrix, steppable::Matrix({ { 8, 9, 5 } })); +SECTION_END() + +SECTION(Matrix joining) +steppable::Matrix matrix1({ + { 5, 4, 3 }, + { 8, 9, 5 }, + { 6, 5, 3 }, +}); +steppable::Matrix matrix2({ + { 11, 9, 6 }, + { 8, 9, 5 }, + { 6, 5, 3 }, +}); +steppable::Matrix matrix3 = matrix1 << matrix2; +steppable::Matrix matrix4 = matrix1 >> matrix2; + +_.assertIsEqual(matrix3, + steppable::Matrix({ + { 5, 4, 3, 11, 9, 6 }, + { 8, 9, 5, 8, 9, 5 }, + { 6, 5, 3, 6, 5, 3 }, + })); + +_.assertIsEqual(matrix4, + steppable::Matrix({ + { 11, 9, 6, 5, 4, 3 }, + { 8, 9, 5, 8, 9, 5 }, + { 6, 5, 3, 6, 5, 3 }, + })); +SECTION_END() + +SECTION(Matrix Rank) +using namespace steppable::literals; + +steppable::Matrix matrix1({ + { 1, 2, 1 }, + { -2, -3, 1 }, + { 3, 5, 0 }, +}); +_.assertIsEqual(matrix1.rank(), 2_n); +SECTION_END() + +SECTION(Matrix inverse) +steppable::Matrix matrix1({ + { -1, 1.5 }, + { 1, -1 }, +}); +_.assertIsEqual((matrix1 ^ -1), + steppable::Matrix({ { + 2.0000000000, + 3.0000000000, + }, + { + 2.0000000000, + 2.0000000000, + } })); + +steppable::Matrix matrix2( + { + { 69, 420, 475 }, + { 589, 4795, 33 }, + { 52, 47.5, 20.2 }, + }, + 6); +auto test = (matrix2 ^ -1 ^ -1).roundOffValues(1); +_.assertIsEqual(test, matrix2); +SECTION_END() +TEST_END() diff --git a/tests/testNumber.cpp b/tests/testNumber.cpp index 301f2b697..0fa46e07a 100644 --- a/tests/testNumber.cpp +++ b/tests/testNumber.cpp @@ -21,8 +21,8 @@ **************************************************************************************************/ #include "colors.hpp" -#include "number.hpp" #include "output.hpp" +#include "steppable/number.hpp" #include "testing.hpp" #include "util.hpp" @@ -49,7 +49,7 @@ _.assertIsEqual((Number("456") * Number("123")).present(), "56088"); SECTION_END() SECTION(Test Division) -_.assertIsEqual((Number("123", 4, USE_CURRENT_PREC) / Number("456")).present(), "0.2697"); +_.assertIsEqual((Number("123", 4, RoundingMode::USE_CURRENT_PREC) / Number("456")).present(), "0.2697"); SECTION_END() SECTION(Test Remainder) @@ -69,4 +69,14 @@ _.assertTrue(Number("123") <= Number("456")); _.assertTrue(Number("456") >= Number("123")); SECTION_END() +SECTION(Test Increment and Decrement) +_.assertTrue(++Number("123") == Number("124")); +_.assertTrue(--Number("124") == Number("123")); +SECTION_END() + +SECTION(Test unary operators) +_.assertTrue(-Number("123") == Number("-123")); +_.assertTrue(+Number("123") == Number("123")); +SECTION_END() + TEST_END() diff --git a/tests/testRef.cpp b/tests/testRef.cpp new file mode 100644 index 000000000..e8cbaa081 --- /dev/null +++ b/tests/testRef.cpp @@ -0,0 +1,32 @@ +/************************************************************************************************** + * Copyright (c) 2023-2025 NWSOFT * + * * + * 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. * + **************************************************************************************************/ + +#include "colors.hpp" +#include "output.hpp" +#include "testing.hpp" +#include "util.hpp" + +#include +#include + +TEST_START() +TEST_END() diff --git a/tools/install.py b/tools/install.py index 2c01e217a..0a52de907 100644 --- a/tools/install.py +++ b/tools/install.py @@ -24,9 +24,10 @@ Installs the Steppable settings and resources to the user home. """ -from lib.paths import DEFAULT_CONFIG_DIR, CONFIG_DIR import shutil +from lib.paths import CONFIG_DIR, DEFAULT_CONFIG_DIR + def copy_resources() -> None: """ diff --git a/tools/new_component.py b/tools/new_component.py index acb34e3fe..fdbe297a5 100644 --- a/tools/new_component.py +++ b/tools/new_component.py @@ -23,8 +23,8 @@ import datetime from pathlib import Path -from lib.paths import PROJECT_PATH, SRC_DIR, TESTS_DIR from lib.getch import getch +from lib.paths import PROJECT_PATH, SRC_DIR, TESTS_DIR WELCOME_STRING = """\ WELCOME TO STEPPABLE! @@ -148,7 +148,7 @@ def make_dir(name: str, date: str, author: str) -> None: path: Path = SRC_DIR / origin / name if not path.is_dir(): - path.mkdir(exist_ok=False) + path.mkdir(exist_ok=True, parents=True) else: print( "COMPONENT EXISTS. Maybe you want to add your code to that component, or choose another name." @@ -191,13 +191,13 @@ def make_dir(name: str, date: str, author: str) -> None: #include "{name}Report.hpp" #include -namespace steppable::__internals::arithmetic -{BRACE} +namespace steppable::__internals::{origin} +{{ std::string {name}(/* Arguments... */) - {BRACE} + {{ // Your code here... - {END_BRACE} -{END_BRACE} // namespace steppable::__internals::arithmetic + }} +}} // namespace steppable::__internals::{origin} """ ) print(f"- Added {name}.cpp") @@ -239,9 +239,9 @@ def make_dir(name: str, date: str, author: str) -> None: #include std::string report{name.capitalize()}() -{BRACE} +{{ // Your code here... -{END_BRACE} +}} """ ) print(f"- Added {name}Report.cpp")