From ed9d6a724cf7b687a7b7e43cce6eef9621574e4b Mon Sep 17 00:00:00 2001 From: Apoorva Appadoo Srinivas Date: Fri, 23 Feb 2024 17:43:40 +0100 Subject: [PATCH 1/2] rename tings --- solver/board/board.cpp | 65 ++++++++++++++++++++++---------------- solver/board/board.h | 9 ++++-- tests/test_board.cpp | 63 ++++++++++++++---------------------- tests/test_solve_board.cpp | 28 +++++++--------- 4 files changed, 79 insertions(+), 86 deletions(-) diff --git a/solver/board/board.cpp b/solver/board/board.cpp index 6101322..495fecb 100644 --- a/solver/board/board.cpp +++ b/solver/board/board.cpp @@ -12,30 +12,44 @@ Board create_board(int size) { // function to create an empty board with the given size and fill it with empty pieces // a board is a 2D array of pieces - Board board(size, std::vector(size, {EMPTY, 0, 0})); + Board board = { + std::vector(size * size, {EMPTY, 0, 0}), + static_cast(size) + }; return board; } +size_t get_1d_board_index(const Board &board, Index index) { + // function to get the 1D index of a 2D index + return index.second * board.size + index.first; +} + +Index get_2d_board_index(const Board &board, int index) { + // function to get the 2D index of a 1D index + return {index % board.size, index / board.size}; +} + void place_piece(Board &board, const RotatedPiece &piece, Index index) { // function to place a piece on the board at the given position // the piece is rotated and placed on the board - board[index.second][index.first] = piece; + board.board[get_1d_board_index(board, index)] = piece; } + void remove_piece(Board &board, Index index) { // function to remove a piece from the board at the given position // the piece is replaced with an empty piece - board[index.second][index.first] = {EMPTY, 0, 0}; + board.board[get_1d_board_index(board, index)] = {EMPTY, 0, 0}; } Index get_next(const Board &board, Index index) { // function to get the next position on the board // the function updates the index to the next position index.first++; - if (index.first == board.size()) { + if (index.first == board.size) { index.first = 0; index.second++; } @@ -44,7 +58,7 @@ Index get_next(const Board &board, Index index) { bool is_end(const Board &board, Index index) { // function to check if the index is at the end of the board - return index.second == board.size(); + return get_1d_board_index(board, index) == board.board.size(); } std::string index_to_string(Index index) { @@ -55,15 +69,14 @@ std::string index_to_string(Index index) { std::vector board_to_string(const Board &board) { // function to print the board to the console std::vector board_lines; - for (const auto &row: board) { - std::vector row_lines = piece_to_string(rotate_piece_right(row[0].piece, row[0].rotation)); - for (size_t i = 1; i < row.size(); i++) { - const auto &piece = row[i].piece; - const auto &piece_lines = piece_to_string(rotate_piece_right(piece, row[i].rotation)); + for (int y = 0; y < board.size; ++y) { + std::vector row_lines = piece_to_string(apply_rotation(board.board[get_1d_board_index(board, {0,y})]) ); + for (int x = 1; x 0) { - neighbor.up = &board[index.second - 1][index.first]; + neighbor.up = &board.board[get_1d_board_index(board, {index.first, index.second - 1})]; } else { neighbor.up = nullptr; } - if (index.first < board.size() - 1) { - neighbor.right = &board[index.second][index.first + 1]; + if (index.first < board.size - 1) { + neighbor.right = &board.board[get_1d_board_index(board, {index.first + 1, index.second})]; } else { neighbor.right = nullptr; } - if (index.second < board.size() - 1) { - neighbor.down = &board[index.second + 1][index.first]; + if (index.second < board.size - 1) { + neighbor.down = &board.board[get_1d_board_index(board, {index.first, index.second + 1})]; } else { neighbor.down = nullptr; } if (index.first > 0) { - neighbor.left = &board[index.second][index.first - 1]; + neighbor.left = &board.board[get_1d_board_index(board, {index.first - 1, index.second})]; } else { neighbor.left = nullptr; } diff --git a/solver/board/board.h b/solver/board/board.h index 4d9b848..e1f560f 100644 --- a/solver/board/board.h +++ b/solver/board/board.h @@ -13,7 +13,11 @@ #include #include "../piece_search/piece_search.h" -using Board = std::vector>; +struct Board { + std::vector board; + size_t size; +}; + using BoardHash = unsigned long long; using Index = std::pair; @@ -51,5 +55,6 @@ void log_board(const Board &board, const std::string &description); void export_board(const Board &board); std::string export_board_to_csv_string(const Board &board); - +size_t get_1d_board_index(const Board &board, Index index); +Index get_2d_board_index(const Board &board, int index); #endif //ETERNITY2_BOARD_H \ No newline at end of file diff --git a/tests/test_board.cpp b/tests/test_board.cpp index a081885..1731240 100644 --- a/tests/test_board.cpp +++ b/tests/test_board.cpp @@ -5,27 +5,10 @@ #include "format/format.h" -TEST_CASE("Board Creation", "[create]") { - SECTION("Create 2x2 board") { - Board board = create_board(2); - REQUIRE(board.size() == 2); - REQUIRE(board[0].size() == 2); - REQUIRE(board[1].size() == 2); - log_board(board, "2x2 board"); - }SECTION("Create 3x3 board") { - Board board = create_board(3); - REQUIRE(board.size() == 3); - REQUIRE(board[0].size() == 3); - REQUIRE(board[1].size() == 3); - REQUIRE(board[2].size() == 3); - log_board(board, "3x3 board"); - }SECTION("Create 4x4 board") { +TEST_CASE("Board Creation", "[create]"){ + SECTION("Create 4x4 board") { Board board = create_board(4); - REQUIRE(board.size() == 4); - REQUIRE(board[0].size() == 4); - REQUIRE(board[1].size() == 4); - REQUIRE(board[2].size() == 4); - REQUIRE(board[3].size() == 4); + REQUIRE(board.board.size() == 4 * 4); log_board(board, "4x4 board"); } } @@ -37,24 +20,24 @@ TEST_CASE("Board place", "[place]") { RotatedPiece rotated_piece = {piece, 0}; place_piece(board, rotated_piece, {0, 0}); log_board(board, "2x2 board with piece"); - REQUIRE(board[0][0].piece == piece); - REQUIRE(board[0][0].rotation == 0); + REQUIRE(board.board[get_1d_board_index(board,{0,0})].piece == piece); + REQUIRE(board.board[get_1d_board_index(board,{0,0})].rotation == 0); }SECTION("place piece in 3x3 board") { Board board = create_board(3); Piece piece = make_piece(1, 2, 3, 4); RotatedPiece rotated_piece = {piece, 0}; place_piece(board, rotated_piece, {1, 1}); log_board(board, "3x3 board with piece"); - REQUIRE(board[1][1].piece == piece); - REQUIRE(board[1][1].rotation == 0); + REQUIRE(board.board[get_1d_board_index(board,{1,1})].piece == piece); + REQUIRE(board.board[get_1d_board_index(board,{1,1})].rotation == 0); }SECTION("place piece in 4x4 board") { Board board = create_board(4); Piece piece = make_piece(1, 2, 3, 4); RotatedPiece rotated_piece = {piece, 0}; place_piece(board, rotated_piece, {2, 2}); log_board(board, "4x4 board with piece"); - REQUIRE(board[2][2].piece == piece); - REQUIRE(board[2][2].rotation == 0); + REQUIRE(board.board[get_1d_board_index(board,{2,2})].piece == piece); + REQUIRE(board.board[get_1d_board_index(board,{2,2})].rotation == 0); } } @@ -67,8 +50,8 @@ TEST_CASE("Board remove", "[remove]") { log_board(board, "2x2 board with piece"); remove_piece(board, {0, 0}); log_board(board, "2x2 board without piece"); - REQUIRE(board[0][0].piece == 0); - REQUIRE(board[0][0].rotation == 0); + REQUIRE(board.board[get_1d_board_index(board,{0,0})].piece == 0); + REQUIRE(board.board[get_1d_board_index(board,{0,0})].rotation == 0); }SECTION("remove piece in 3x3 board") { Board board = create_board(3); Piece piece = make_piece(1, 2, 3, 4); @@ -77,8 +60,8 @@ TEST_CASE("Board remove", "[remove]") { log_board(board, "3x3 board with piece"); remove_piece(board, {1, 1}); log_board(board, "3x3 board without piece"); - REQUIRE(board[1][1].piece == 0); - REQUIRE(board[1][1].rotation == 0); + REQUIRE(board.board[get_1d_board_index(board,{1,1})].piece == 0); + REQUIRE(board.board[get_1d_board_index(board,{1,1})].rotation == 0); }SECTION("remove piece in 4x4 board") { Board board = create_board(4); Piece piece = make_piece(1, 2, 3, 4); @@ -87,8 +70,8 @@ TEST_CASE("Board remove", "[remove]") { log_board(board, "4x4 board with piece"); remove_piece(board, {2, 2}); log_board(board, "4x4 board without piece"); - REQUIRE(board[2][2].piece == 0); - REQUIRE(board[2][2].rotation == 0); + REQUIRE(board.board[get_1d_board_index(board,{2,2})].piece == 0); + REQUIRE(board.board[get_1d_board_index(board,{2,2})].rotation == 0); } } @@ -103,23 +86,23 @@ TEST_CASE("Board get_neighbors", "[neighbors]") { log_board(board, "2x2 board with pieces"); Neighbor neighbors = get_neighbors(board, {0, 0}); REQUIRE(neighbors.up == nullptr); - REQUIRE(neighbors.right == &board[0][1]); - REQUIRE(neighbors.down == &board[1][0]); + REQUIRE(neighbors.right == &board.board[get_1d_board_index(board,{1,0})]); + REQUIRE(neighbors.down == &board.board[get_1d_board_index(board,{0,1})]); REQUIRE(neighbors.left == nullptr); neighbors = get_neighbors(board, {1, 0}); REQUIRE(neighbors.up == nullptr); REQUIRE(neighbors.right == nullptr); - REQUIRE(neighbors.down == &board[1][1]); - REQUIRE(neighbors.left == &board[0][0]); + REQUIRE(neighbors.down == &board.board[get_1d_board_index(board,{1,1})]); + REQUIRE(neighbors.left == &board.board[get_1d_board_index(board,{0,0})]); neighbors = get_neighbors(board, {0, 1}); - REQUIRE(neighbors.up == &board[0][0]); - REQUIRE(neighbors.right == &board[1][1]); + REQUIRE(neighbors.up == &board.board[get_1d_board_index(board,{0,0})]); + REQUIRE(neighbors.right == &board.board[get_1d_board_index(board,{1,1})]); REQUIRE(neighbors.down == nullptr); REQUIRE(neighbors.left == nullptr); neighbors = get_neighbors(board, {1, 1}); - REQUIRE(neighbors.up == &board[0][1]); + REQUIRE(neighbors.up == &board.board[get_1d_board_index(board,{1,0})]); REQUIRE(neighbors.right == nullptr); REQUIRE(neighbors.down == nullptr); - REQUIRE(neighbors.left == &board[1][0]); + REQUIRE(neighbors.left == &board.board[get_1d_board_index(board,{0,1})]); } } diff --git a/tests/test_solve_board.cpp b/tests/test_solve_board.cpp index aaf2d0b..ceda574 100644 --- a/tests/test_solve_board.cpp +++ b/tests/test_solve_board.cpp @@ -39,11 +39,10 @@ TEST_CASE("Solve Board") { SharedData shared_data = {max_board, max_count, mutex, hashes}; solve_board(board, pieces, shared_data); log_board(board, "2x2 board solved"); - for (auto const &row: board) { - for (auto const &piece: row) { - REQUIRE(piece.piece != 0); - } + for (auto const &piece: board.board) { + REQUIRE(piece.piece != 0); } + } SECTION("Solve 3x3 board") { @@ -71,11 +70,10 @@ TEST_CASE("Solve Board") { SharedData shared_data = {max_board, max_count, mutex, hashes}; solve_board(board, pieces, shared_data); log_board(board, "3x3 board solved"); - for (auto const &row: board) { - for (auto const &piece: row) { - REQUIRE(piece.piece != 0); - } + for (auto const &piece: board.board) { + REQUIRE(piece.piece != 0); } + } SECTION("3x3 Complex") { @@ -123,11 +121,10 @@ TEST_CASE("Solve Board") { auto end = std::chrono::high_resolution_clock::now(); std::chrono::duration elapsed = end - start; log_board(board, format("3x3 board solved in {} seconds", elapsed.count())); - for (auto const &row: board) { - for (auto const &piece: row) { - REQUIRE(piece.piece != 0); - } + for (auto const &piece: board.board) { + REQUIRE(piece.piece != 0); } + } } @@ -149,12 +146,11 @@ TEST_CASE("Solve & Export Board") { SharedData shared_data = {max_board, max_count, mutex, hashes}; solve_board(board, pieces, shared_data); log_board(board, "2x2 board solved"); - for (auto const &row: board) { - for (auto const &piece: row) { + for (auto const &piece: board.board) { REQUIRE(piece.piece != 0); } - } - export_board(shared_data.max_board); + export_board(max_board); + } } From 7e51847a3e10312d9116fbf10944eabe4e1e1224 Mon Sep 17 00:00:00 2001 From: Ozeliurs Date: Fri, 23 Feb 2024 17:45:37 +0100 Subject: [PATCH 2/2] feat: add plotter --- gui/logger.py | 41 +++++++++++++++++++++++++++++++++++ gui/main.py | 35 ++++++++++++++++++++++-------- gui/solverlib/execution.py | 16 ++++++++++++-- gui/solverlib/solver.py | 20 +++++++++-------- gui/statiscticslib/grapher.py | 30 +++++++++++++++++++++++++ gui/statiscticslib/runner.py | 22 +++++++++++++++---- 6 files changed, 140 insertions(+), 24 deletions(-) create mode 100644 gui/logger.py diff --git a/gui/logger.py b/gui/logger.py new file mode 100644 index 0000000..2428b8b --- /dev/null +++ b/gui/logger.py @@ -0,0 +1,41 @@ +import logging +from pathlib import Path + + +class Logger: + logger: logging.Logger + log_folder = Path(__file__).parent / "logs" + + def __init__(self, clazz): + if not self.log_folder.exists(): + self.log_folder.mkdir() + + self.logger = logging.getLogger(type(clazz).__name__) + self.logger.setLevel(logging.DEBUG) + + self.formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + + self.stream_handler = logging.StreamHandler() + self.stream_handler.setLevel(logging.INFO) + self.stream_handler.setFormatter(self.formatter) + self.logger.addHandler(self.stream_handler) + + self.file_handler = logging.FileHandler(self.log_folder / f"{type(clazz).__name__}.log") + self.file_handler.setLevel(logging.DEBUG) + self.file_handler.setFormatter(self.formatter) + self.logger.addHandler(self.file_handler) + + def debug(self, message): + self.logger.debug(message) + + def info(self, message): + self.logger.info(message) + + def warning(self, message): + self.logger.warning(message) + + def error(self, message): + self.logger.error(message) + + def critical(self, message): + self.logger.critical(message) \ No newline at end of file diff --git a/gui/main.py b/gui/main.py index cb2ec9c..5b715f9 100644 --- a/gui/main.py +++ b/gui/main.py @@ -33,6 +33,15 @@ def generate_board(size: int, pattern_count: int) -> (str, Path, Path): return board.to_csv(), unsorted_board, board.image +def generate_statistics(start_size: int, end_size: int, start_pattern_count: int, end_pattern_count: int, timeout: float = 4, progress=gr.Progress(track_tqdm=True)) -> Path: + runner = Runner((start_size, end_size), (start_pattern_count, end_pattern_count), timeout=timeout) + + results = runner.solve_boards(progress) + + grapher = Grapher(results) + return grapher.plot_time_over_size_and_pattern_count_plotly() + + image_gen = gr.Interface( fn=board_to_image, inputs=["text", "file"], @@ -44,18 +53,26 @@ def generate_board(size: int, pattern_count: int) -> (str, Path, Path): inputs=[ gr.Slider(2, 32, 12, step=1, label="Puzzle Size"), gr.Slider(1, 128, 22, step=1, label="Pattern count"), - # gr.Dataset(components=["slider"], samples=[["4x4 (2)", "8x8 (4)", "14x14 (22)", "15x15 (22)", "16x16 (22)", "Eternity II (16x16 22)"]]) ], outputs=[gr.Textbox(label="Board Data", show_copy_button=True), "image", "image"] ) -interface = gr.TabbedInterface([board_gen, image_gen], ["Generate Board", "Generate Image"]) - -if __name__ == "__main__": - runner = Runner((8, 8), (2, 22)) +statistics_gen = gr.Interface( + fn=generate_statistics, + inputs=[ + gr.Slider(2, 32, 2, step=1, label="Minimum Size"), + gr.Slider(2, 32, 4, step=1, label="Maximum Size"), + gr.Slider(2, 32, 2, step=1, label="Minimum Pattern Count"), + gr.Slider(2, 32, 10, step=1, label="Maximum Pattern Count"), + gr.Slider(0.1, 60, 4, label="Timeout (seconds)") + ], + outputs=[ + gr.Plot(label="Time over Size and Pattern Count") + ] +) - results = runner.solve_boards() - grapher = Grapher(results) - grapher.plot_time_over_pattern_count() +interface = gr.TabbedInterface([board_gen, image_gen, statistics_gen], + ["Generate Board", "Generate Image", "Generate Statistics"]) - # interface.launch(share=True) +if __name__ == "__main__": + interface.launch(share=True) diff --git a/gui/solverlib/execution.py b/gui/solverlib/execution.py index 4d20113..899ba8f 100644 --- a/gui/solverlib/execution.py +++ b/gui/solverlib/execution.py @@ -1,15 +1,27 @@ +from logger import Logger + + class Execution: - def __init__(self, logs: str): + def __init__(self, logs: str, timeout: float = None): + self.logger = Logger(self) self.logs = logs self.time = 0 - self.parse_logs() + + if timeout: + self.time = timeout + else: + self.parse_logs() def parse_logs(self): # scrape metadata logs for line in self.logs.split("\n"): + self.logger.debug(f"Scraping line: {line}") if line.startswith("=time="): + self.logger.debug(f"Found time line: {line}") self.time = float(line.split("=")[2]) + self.logger.debug(f"Execution time: {self.time}") + def __repr__(self): return f""" Execution: diff --git a/gui/solverlib/solver.py b/gui/solverlib/solver.py index 0bc4a26..5eb91c5 100644 --- a/gui/solverlib/solver.py +++ b/gui/solverlib/solver.py @@ -1,40 +1,42 @@ import subprocess -import logging from pathlib import Path from eternitylib.board import Board +from logger import Logger from solverlib.execution import Execution class Solver: def __init__(self): # exe is in the same dir as this file + self.logger = Logger(self) self.exe_path = Path(__file__).parent / "e2solver" def link_solver(self, path: Path = Path(__file__).parent.parent.parent / "cmake-build-debug/solver/Solver"): # If file does not exist, raise an error if not path.exists(): + self.logger.error(f"Solver executable not found at {path}") raise FileNotFoundError(f"Solver executable not found at {path}") if self.exe_path.exists(): + self.logger.debug(f"Deleting existing solver at {path}") self.exe_path.unlink() + self.logger.debug(f"Symlinking {path} to {self.exe_path}.") self.exe_path.symlink_to(path) - def solve(self, board: Board, timeout: int = 60) -> Execution: + def solve(self, board: Board, timeout: float = 60) -> Execution: # Write the board to a file + self.logger.info(f"Writing board to tmp/board.{board.hash()}.txt.") board_path = Path(__file__).parent.parent / f"tmp/board.{board.hash()}.txt" board_path.parent.mkdir(parents=True, exist_ok=True) board_path.write_text(board.to_csv()) - # print info to log - logging. - # Run the solver try: - logging.info(f"Running solver with timeout of {timeout} seconds") + self.logger.info(f"Running solver with timeout of {timeout} seconds") result = subprocess.run([self.exe_path, str(board_path)], capture_output=True, timeout=timeout) - logging.info("Solver finished.") + self.logger.info(f"Solver returned with code {result.returncode}") except subprocess.TimeoutExpired: - logging.warning(f"Solver timed out after {timeout} seconds") - return Execution("") + self.logger.warning(f"Solver timed out after {timeout} seconds") + return Execution("", timeout=timeout) return Execution(result.stdout.decode("utf-8")) diff --git a/gui/statiscticslib/grapher.py b/gui/statiscticslib/grapher.py index 166e870..efa8f7f 100644 --- a/gui/statiscticslib/grapher.py +++ b/gui/statiscticslib/grapher.py @@ -1,6 +1,11 @@ +import random +import string +from pathlib import Path from typing import List import matplotlib.pyplot as plt +import pandas as pd +import plotly.graph_objects as go from statiscticslib.result import Result @@ -9,6 +14,15 @@ class Grapher: def __init__(self, data: List[Result]): self.data = data + @property + def df(self): + # Create a dataframe with board size as columns and pattern count as index and time as values + df = pd.DataFrame(columns=list(set([result.board.size for result in self.data])), index=list(set([result.board.pattern_count for result in self.data]))) + for result in self.data: + df[result.board.size][result.board.pattern_count] = result.execution.time + return df + + def plot_time_over_size(self): plt.plot([result.board.size for result in self.data], [result.execution.time for result in self.data]) plt.xlabel("Size") @@ -34,4 +48,20 @@ def plot_time_over_size_and_pattern_count(self): ax.set_ylabel("Pattern Count") ax.set_zlabel("Time") + rand_path = Path(__file__).parent.parent / "tmp" / f"{''.join(random.choices(string.ascii_lowercase, k=10))}.jpg" + plt.show() + plt.savefig(rand_path) + return rand_path + + def plot_time_over_size_and_pattern_count_plotly(self) -> str: + fig = go.Figure(data=[go.Surface(z=self.df.values)]) + + fig.update_layout(scene=dict( + xaxis_title='Size', + yaxis_title='Pattern Count', + zaxis_title='Time' + )) + + return fig + diff --git a/gui/statiscticslib/runner.py b/gui/statiscticslib/runner.py index 86791c3..d92ee9e 100644 --- a/gui/statiscticslib/runner.py +++ b/gui/statiscticslib/runner.py @@ -1,6 +1,10 @@ import itertools +import gradio as gr +from tqdm import tqdm + from eternitylib.board import Board +from logger import Logger from solverlib.solver import Solver from statiscticslib.result import Result @@ -11,30 +15,40 @@ class Runner: solver: Solver boards: list[Board] results: list[Result] + logger: Logger - def __init__(self, size_range=(4, 8), pattern_count_range=(2, 22)): + def __init__(self, size_range=(4, 8), pattern_count_range=(2, 22), timeout: float = 60): + self.logger = Logger(self) self.solver = Solver() self.boards = [] self.results = [] + self.timeout = timeout self.set_size_range(*size_range) self.set_pattern_count_range(*pattern_count_range) + self.logger.info( + f"New Runner of {size_range[0]}x{size_range[0]} to {size_range[1]}x{size_range[1]} size and from {pattern_count_range[0]} to {pattern_count_range[1]} patterns.") def generate_boards(self): + self.logger.info(f"Generating {len(list(itertools.product(self.sizes, self.pattern_counts)))} boards.") for size, pattern_count in itertools.product(self.sizes, self.pattern_counts): + self.logger.debug(f"Genrating {size}x{size} of {pattern_count} patterns.") self.boards.append(Board().generate(size, pattern_count)) def set_size_range(self, size_start, size_end): + self.logger.info(f"Setting runner size from {size_start} to {size_end}.") self.sizes = list(range(size_start, size_end + 1)) def set_pattern_count_range(self, pattern_count_start, pattern_count_end): + self.logger.info(f"Setting runner pattern count from {pattern_count_start} to {pattern_count_end}.") self.pattern_counts = list(range(pattern_count_start, pattern_count_end + 1)) - def solve_boards(self) -> list[Result]: + def solve_boards(self, progress=gr.Progress(track_tqdm=True)) -> list[Result]: if not self.boards: self.generate_boards() - for board in self.boards: - self.results.append(Result(board, self.solver.solve(board))) + for board in tqdm(self.boards): + self.logger.debug(f"Solving {board}.") + self.results.append(Result(board, self.solver.solve(board, timeout=self.timeout))) return self.results