diff --git a/projects/computer-algebra/README.md b/projects/computer-algebra/README.md new file mode 100644 index 00000000..14c95d5c --- /dev/null +++ b/projects/computer-algebra/README.md @@ -0,0 +1,60 @@ +# Newton CAS Python Wrapper and GUI + +This project aims to provide a Python wrapper and GUI for the Newton API, a Computer Algebra +System (CAS) that allows users to perform various mathematical computations. The GUI is built using +[DearPyGui](https://github.com/hoffstadt/DearPyGui) and +[Newton API](https://github.com/aunyks/newton-api). + +## Features + +- **User-Friendly Interface:** The GUI provides an intuitive interface for users to interact with the Newton API + effortlessly. +- **Multiple Mathematical Operations:** Users can perform a variety of mathematical operations such as simplification, + factoring, differentiation, integration, finding zeroes, and more. +- **Real-Time Evaluation:** Expressions are evaluated in real-time, providing instant feedback to users. + +## Installation + +1. Clone the repository: + + ```bash + git clone https://github.com/Mrinank-Bhowmick/python-beginner-projects.git + ``` + +2. Navigate to the project directory: + + ```bash + cd python-beginner-projects/projects/computer-algebra + ``` + +3. Install dependencies using pip: + + ```bash + pip install -r requirements.txt + ``` + +## Usage + +1. Run the main script `main.py`: + + ```bash + python main.py + ``` + +2. The application window will appear, consisting of two sections: + - **Input Section:** Enter the mathematical expression you want to evaluate. + - **Output Section:** View the result of the evaluation. + +3. Choose the desired mathematical operation from the radio buttons. +4. Enter the expression in the input text box. + - See valid syntax from [Newton API](https://github.com/aunyks/newton-api). +5. Click the "Evaluate" button to perform the selected operation. +6. The result will be displayed in the output section. + +## Contact + +[GitHub Profile](https://github.com/ca20110820) + +## License + +[![License](https://img.shields.io/static/v1?label=Licence&message=GPL-3-0&color=blue)](https://opensource.org/license/GPL-3-0) diff --git a/projects/computer-algebra/command.py b/projects/computer-algebra/command.py new file mode 100644 index 00000000..d621739b --- /dev/null +++ b/projects/computer-algebra/command.py @@ -0,0 +1,24 @@ +from typing import Any +from abc import abstractmethod, ABC +from urllib.parse import quote + + +class CmdBase(ABC): + """Base class for all the CAS (Computer Algebra System) API Commands.""" + + def __init__(self, operation: str, base_url: str): + self.operation = operation + self.base_url = base_url + + @abstractmethod + def command(self, expr: str) -> Any: + """ + Command for sending request to Newton CAS API with a given expression string and returns the result from + the API response. + """ + pass + + @staticmethod + def url_encode(inp_str: str) -> str: + """Encode the input string to a URL-safe format.""" + return quote(inp_str) diff --git a/projects/computer-algebra/main.py b/projects/computer-algebra/main.py new file mode 100644 index 00000000..dc6c36d4 --- /dev/null +++ b/projects/computer-algebra/main.py @@ -0,0 +1,116 @@ +from newton_command import NEWTON_CMDS_DICT + +import dearpygui.dearpygui as dpg + + +def selection_cb(sender, app_data, user_data): + """Callback function for button selections in the message box.""" + if user_data[1]: + print("User selected 'Ok'") + else: + print("User selected 'Cancel'") + + # delete window + dpg.delete_item(user_data[0]) + + +def show_info(title, message, selection_callback): + """ + Display an information message box with title, message, and callback. + + References: + https://github.com/hoffstadt/DearPyGui/discussions/1002 + """ + + # guarantee these commands happen in the same frame + with dpg.mutex(): + viewport_width = dpg.get_viewport_client_width() + viewport_height = dpg.get_viewport_client_height() + + with dpg.window(tag='popup-window', label=title, modal=True, no_close=True) as modal_id: + dpg.add_text(message) + dpg.add_button(label="Ok", width=75, user_data=(modal_id, True), callback=selection_callback) + dpg.add_same_line() + dpg.add_button(label="Cancel", width=75, user_data=(modal_id, False), callback=selection_callback) + + # guarantee these commands happen in another frame + dpg.split_frame() + width = dpg.get_item_width(modal_id) + height = dpg.get_item_height(modal_id) + dpg.set_item_pos(modal_id, [viewport_width // 2 - width // 2, viewport_height // 2 - height // 2]) + + +# Callbacks and Helpers +def on_evaluate(sender, app_data, user_data): + """Callback function for the 'Evaluate' button.""" + # Get the Command + cmd = dpg.get_value('radio-cmds') + cmd_func = NEWTON_CMDS_DICT[cmd] + + # Get the Expression + expr = dpg.get_value('inp-expr') + + if expr.strip() in ['']: + show_info( + 'Error', + 'Please use valid mathematical expressions.', + selection_cb + ) + # Clear Expression + dpg.set_value('inp-expr', '') + return + + # Evaluate + response = cmd_func(expr) + result = response.result + + dpg.set_value('label-output', result) + + +dpg.create_context() +dpg.create_viewport(title='Computer Algebra', width=1300, height=750) + +with dpg.window(tag='inp-window', + label="Input", + pos=[0, 0], + autosize=True, + # width=1150, + # height=350, + no_collapse=True, + no_close=True, + ): + # Radio Button for Commands + dpg.add_radio_button( + horizontal=True, + tag='radio-cmds', + items=[cmd for cmd in NEWTON_CMDS_DICT.keys()] + ) + + # Text Area for Mathematical Expression + dpg.add_input_text( + tag='inp-expr', + width=int(1150 * 0.8), + ) + + # Button for Evaluating Command and Expression + dpg.add_button(label="Evaluate", callback=on_evaluate) + +with dpg.window(tag='out-window', + pos=[0, 100], + label="Output", + # width=700, + # height=350, + autosize=True, + no_collapse=True, + no_close=True, + ): + # Use Label for Output + dpg.add_text(tag='label-output', + label='Result', + show_label=True, + ) + +dpg.setup_dearpygui() +dpg.show_viewport() +dpg.start_dearpygui() +dpg.destroy_context() diff --git a/projects/computer-algebra/newton_command.py b/projects/computer-algebra/newton_command.py new file mode 100644 index 00000000..e38e1e88 --- /dev/null +++ b/projects/computer-algebra/newton_command.py @@ -0,0 +1,94 @@ +from dataclasses import dataclass + +import requests +from command import CmdBase + + +@dataclass(frozen=True) +class NewtonResponse: + """Newton API Response.""" + operation: str + expression: str + result: str + + +class NewtonCmdException(Exception): + """Base class for Newton Command Exceptions.""" + + +class NewtonCommand(CmdBase): + """Base class for all the Newton API Commands.""" + + def __init__(self, operation: str): + super().__init__(operation, 'https://newton.now.sh/api/v2') + + def command(self, expr: str) -> NewtonResponse: + """ + Command method for NewtonCommand class. + + Args: + expr (str): Mathematical expression to be evaluated. + + Returns: + NewtonResponse: Object containing the operation, expression, and result of the evaluated expression. + + Raises: + NewtonCmdException: If the HTTP request fails or returns a non-success status code, the exception is raised + with the error message. + """ + # Construct the Request URL + expr_encode = self.url_encode(expr) # URL Encode for Expression + request_url = f"{self.base_url}/{self.operation}/{expr_encode}" + + # Make the HTTP GET request + response = requests.get(request_url) + + # Check if the request was successful (status code 200) + if response.status_code == 200: + # Deserialize the JSON response into a dictionary + response_data = response.json() + + # Extract relevant data from the response + operation = response_data['operation'] + expression = response_data['expression'] + result = response_data['result'] + + # Create and return a NewtonResponse object + return NewtonResponse(operation=operation, expression=expression, result=result) + else: + raise NewtonCmdException(f'{response.text}') + + +newton_simplify = NewtonCommand('simplify').command +newton_factor = NewtonCommand('factor').command +newton_derive = NewtonCommand('derive').command +newton_integrate = NewtonCommand('integrate').command +newton_zeroes = NewtonCommand('zeroes').command +newton_tangent = NewtonCommand('tangent').command +newton_area = NewtonCommand('area').command +newton_cos = NewtonCommand('cos').command +newton_sin = NewtonCommand('sin').command +newton_tan = NewtonCommand('tan').command +newton_arc_cos = NewtonCommand('arccos').command +newton_arc_sin = NewtonCommand('arcsin').command +newton_arc_tan = NewtonCommand('arctan').command +newton_abs = NewtonCommand('abs').command +newton_log = NewtonCommand('log').command + +NEWTON_CMDS_DICT = { + 'simplify': newton_simplify, + 'factor': newton_factor, + 'derive': newton_derive, + 'integrate': newton_integrate, + 'zeroes': newton_zeroes, + 'tangent': newton_tangent, + 'area': newton_area, + 'cos': newton_cos, + 'sin': newton_sin, + 'tan': newton_tan, + 'arccos': newton_arc_cos, + 'arcsin': newton_arc_sin, + 'arctan': newton_arc_tan, + 'abs': newton_abs, + 'log': newton_log +} diff --git a/projects/computer-algebra/requirements.txt b/projects/computer-algebra/requirements.txt new file mode 100644 index 00000000..d9e5829e --- /dev/null +++ b/projects/computer-algebra/requirements.txt @@ -0,0 +1,2 @@ +requests==2.31.0 +dearpygui==1.11.1 \ No newline at end of file