diff --git a/DesignNotes.md b/DesignNotes.md new file mode 100644 index 0000000..ae2a311 --- /dev/null +++ b/DesignNotes.md @@ -0,0 +1,27 @@ +# 📐 Design Notes: Complex Number Arithmetic Module +**Team:** Group 13 +**Target Branch:** `Group_B` + +--- + +## 1. Architectural Approach +* **Modularity:** The feature is logically encapsulated within an independent `ComplexCalculator` class. This ensures clean integration with the base system without accidentally altering the core `Calculator` class . +* **Separation of Concerns:** The module strictly divides responsibilities into three internal domains: string parsing, mathematical execution, and error management. + +## 2. Input Parsing Strategy +* **Constraint:** The system strictly requires mathematical inputs to be provided as continuous strings (e.g., `'(3+2j)*(5+3j)'`). +* **Solution:** We implemented Python's `re` (Regular Expressions) module to act as the extraction engine. + * *Pre-processing:* All arbitrary whitespaces are stripped to standardize the user's input. + * *Extraction:* The regex pattern `r'\(([^)]+)\)([\+\-\*\/])\(([^)]+)\)'` dynamically isolates the first operand, the arithmetic operator, and the second operand into exact capture groups. + +## 3. Mathematical Implementation +* **Object Casting:** Extracted string operands are safely cast directly to Python's native `complex()` objects, inherently supporting the required $a+bj$ mathematical representation. +***Core Arithmetic:** Standard Python overloaded operators (`+`, `-`, `*`, `/`) handle the required addition, subtraction, multiplication, and division operations. +* **Advanced Computations:** Standard mathematical libraries are utilized for complex plane metrics. + * **Magnitude:** Calculated using the built-in `abs(z)` function to efficiently compute $\sqrt{a^2 + b^2}$. + * **Phase:** Computed using `cmath.phase(z)` to guarantee accurate quadrant mapping and return the angle in radians. +* **Output Formatting:** Raw mathematical results are dynamically reformatted back into the strictly required `a+bj` string notation before being returned to the main process. + +## 4. Error Handling & Validation +* **Syntax Validation:** The regex parser acts as the first line of defense. Unmatched patterns or invalid characters raise an immediate `ValueError`. +* **Boundary Conditions:** A programmatic check intercepts mathematical impossibilities, such as dividing by `(0+0j)`, raising a descriptive exception to prevent catastrophic system crashes. \ No newline at end of file diff --git a/FinalReport.md b/FinalReport.md new file mode 100644 index 0000000..ce6d8fd --- /dev/null +++ b/FinalReport.md @@ -0,0 +1,57 @@ +# Software Engineering Laboratory: Final Project Report +**Project:** Extensible Calculator System - Complex Number Arithmetic +**Team:** Group 13 +**Target Integration Branch:** `Group_B` + +--- + +## 1. Introduction and Objective +The primary objective of this laboratory experiment was to gain practical experience with industry-style software development practices. This was achieved by applying all phases of the Software Development Life Cycle (SDLC) to a single, extensible Python-based calculator system. + +Our specific team, Group 13, was tasked with analyzing, designing, implementing, and testing the **Complex Number Arithmetic** extension. The goal was to build a modular feature that integrates seamlessly with the base calculator application while adhering to strict version control and testing protocols. + +## 2. Requirement Analysis +During the initial phase of the SDLC, we identified the specific functional and non-functional requirements for the complex number module. + +**Functional Requirements:** +* The module must accept user input exclusively as a continuous string (e.g., `'(3+2j)*(5+3j)'`). +* It must support the standard $a+bj$ mathematical representation. +* The system must perform standard arithmetic operations: addition, subtraction, multiplication, and division of complex numbers. +* The system must compute advanced metrics: magnitude and phase. + +**Non-Functional Requirements & Constraints:** +* Code must be highly modular and maintainable, isolating complex logic from the core `Calculator` class. +* All errors (like malformed strings or division by zero) must be handled gracefully without crashing the main application. + +## 3. System Architecture and Design +To fulfill the requirements, we designed an independent software component to handle all complex domain logic. + +* **Object-Oriented Encapsulation:** We designed a standalone `ComplexCalculator` class. This ensures that the base system remains untouched and adheres to the Open/Closed Principle (open for extension, closed for modification). +* **String Parsing Engine:** Because inputs are strict strings, we designed a parsing layer using Python's Regular Expressions (`re` module). We engineered the pattern `r'\(([^)]+)\)([\+\-\*\/])\(([^)]+)\)'` to dynamically isolate the first operand, the arithmetic operator, and the second operand, bypassing arbitrary whitespace issues. +* **Mathematical Delegation:** Rather than manually calculating complex arithmetic, the design leverages Python's native `complex()` casting capabilities and the standard `cmath` library to guarantee mathematical precision. + +## 4. Implementation Details +The implementation phase translated our design into functional Python code within the `complex.py` module. + +* **Arithmetic Execution:** Once the regex engine extracts the string components, they are cast to `complex` objects. Python's overloaded operators process the calculation, and a custom `_format_result` method reconstructs the output back into the required $a+bj$ string format. +* **Magnitude Computation:** Implemented using the built-in `abs(z)` function, which efficiently processes the underlying $\sqrt{a^2 + b^2}$ calculation. +* **Phase Computation:** Implemented using `cmath.phase(z)`, which safely maps the angle in radians across all four quadrants. +* **Exception Handling:** A robust `try-except` block is implemented to catch `ValueError` instances. If the regex fails to find a match, or if a zero-division occurs during evaluation, the module halts the calculation and returns a descriptive error string. + +## 5. Software Testing and Quality Assurance +Test-driven development practices were strictly followed. A comprehensive unit test suite was developed in `test_complex.py` using Python's native `unittest` framework to ensure high code quality and test coverage. + +The test suite validates the system against three core paradigms: +* **Normal Cases:** Tests verify standard execution for all operations (e.g., asserting that `(1+2j)+(2+3j)` successfully evaluates to `3.0+5.0j`). +* **Boundary Conditions:** Edge cases were tested, most notably asserting that evaluating `(5+5j)/(0+0j)` correctly raises a `ValueError` rather than triggering a system-level math fault. +* **Invalid Input Handling:** The parser's resilience was tested by injecting malformed strings (e.g., missing parentheses, alphabetic characters, and unsupported operators). The tests confirm that the system correctly identifies and rejects these inputs. + +## 6. Version Control and Configuration Management +To support concurrent development across 20 groups without code conflicts, strict Git configuration management protocols were followed. + +* **Repository Forking & Cloning:** The main project repository was forked to an isolated environment. As Group 13 falls into the second cohort, we specifically cloned and targeted the `Group_B` branch for our baseline code. +* **Feature Branching:** Development was conducted entirely within an isolated feature branch (`FeatureB_13`) to prevent contamination of the group's integration branch. +* **Integration and Merging:** Following successful local testing, code updates were committed incrementally. The final feature was pushed to the remote fork, and a Pull Request was generated targeting the main repository's `Group_B` branch, ensuring zero merge conflicts with other teams. + +## 7. Conclusion +Through this experiment, Group 13 successfully applied the complete Software Development Life Cycle. By conducting thorough requirement analysis, designing a modular regex-driven architecture, implementing robust unit tests, and adhering to strict Git branching strategies, we successfully delivered a highly functional and reliable Complex Number Arithmetic module for the extensible calculator system. \ No newline at end of file diff --git a/__pycache__/calculator.cpython-313.pyc b/__pycache__/calculator.cpython-313.pyc new file mode 100644 index 0000000..59dae2e Binary files /dev/null and b/__pycache__/calculator.cpython-313.pyc differ diff --git a/__pycache__/complex.cpython-313.pyc b/__pycache__/complex.cpython-313.pyc new file mode 100644 index 0000000..60cae4b Binary files /dev/null and b/__pycache__/complex.cpython-313.pyc differ diff --git a/complex.py b/complex.py new file mode 100644 index 0000000..9aa04a9 --- /dev/null +++ b/complex.py @@ -0,0 +1,81 @@ +import cmath +import re + +class ComplexCalculator: + def evaluate(self, expression: str) -> str: + """ + Parses and evaluates a complex number expression. + Example input: '(3+2j)*(5+3j)' + """ + # Remove all spaces so we don't have to worry about weird spacing + expr = expression.replace(" ", "") + + # Regex to match the arithmetic format: (a+bj) operator (c+dj) + # Group 1: First number, Group 2: Operator, Group 3: Second number + pattern = r'\(([^)]+)\)([\+\-\*\/])\(([^)]+)\)' + match = re.match(pattern, expr) + + if match: + c1_str = match.group(1) + operator = match.group(2) + c2_str = match.group(3) + return self._perform_arithmetic(c1_str, operator, c2_str) + + # Regex to handle Magnitude: e.g., 'mag(3+2j)' + mag_match = re.match(r'mag\(([^)]+)\)', expr) + if mag_match: + return self.magnitude(mag_match.group(1)) + + # Regex to handle Phase: e.g., 'phase(3+2j)' + phase_match = re.match(r'phase\(([^)]+)\)', expr) + if phase_match: + return self.phase(phase_match.group(1)) + + raise ValueError("Invalid complex number expression format.") + + def _perform_arithmetic(self, c1: str, operator: str, c2: str) -> str: + # Convert strings to actual complex numbers + try: + z1 = complex(c1) + z2 = complex(c2) + except ValueError: + raise ValueError("Invalid format. Use a+bj notation.") + + # Perform the actual math + if operator == '+': + result = z1 + z2 + elif operator == '-': + result = z1 - z2 + elif operator == '*': + result = z1 * z2 + elif operator == '/': + if z2 == 0: + raise ValueError("Division by zero") + result = z1 / z2 + else: + raise ValueError("Unknown operator") + + return self._format_result(result) + + def magnitude(self, c_str: str) -> str: + z = complex(c_str) + # Magnitude is calculated as sqrt(a^2 + b^2) + return str(abs(z)) + + def phase(self, c_str: str) -> str: + z = complex(c_str) + # Phase is calculated in radians + return str(cmath.phase(z)) + + def _format_result(self, z: complex) -> str: + """Formats the output back to a readable a+bj string.""" + # Ensure the imaginary part always has a + or - sign + sign = '+' if z.imag >= 0 else '-' + return f"{z.real}{sign}{abs(z.imag)}j" + +# # Example Usage (You can delete this part in your final file, it's just to show you how it works): +# if __name__ == "__main__": +# calc = ComplexCalculator() +# print(calc.evaluate("(3+2j)*(5+3j)")) # Output: 9.0+19.0j +# print(calc.evaluate("(1+2j)+(2+3j)")) # Output: 3.0+5.0j +# print(calc.evaluate("mag(3+4j)")) # Output: 5.0 \ No newline at end of file diff --git a/test_complex.py b/test_complex.py new file mode 100644 index 0000000..11b0372 --- /dev/null +++ b/test_complex.py @@ -0,0 +1,66 @@ +import unittest +import math +from complex import ComplexCalculator + +class TestComplexCalculator(unittest.TestCase): + + def setUp(self): + # This creates a new calculator object before each test runs + self.calc = ComplexCalculator() + + # --- NORMAL CASES --- + def test_addition(self): + # (1+2j) + (2+3j) = 3+5j + result = self.calc.evaluate("(1+2j)+(2+3j)") + self.assertEqual(result, "3.0+5.0j") + + def test_subtraction(self): + # (5+5j) - (2+1j) = 3+4j + result = self.calc.evaluate("(5+5j)-(2+1j)") + self.assertEqual(result, "3.0+4.0j") + + def test_multiplication(self): + # (1+2j) * (3+4j) = -5+10j + result = self.calc.evaluate("(1+2j)*(3+4j)") + self.assertEqual(result, "-5.0+10.0j") + + def test_division(self): + # (10+5j) / (2+0j) = 5+2.5j + result = self.calc.evaluate("(10+5j)/(2+0j)") + self.assertEqual(result, "5.0+2.5j") + + def test_magnitude(self): + # Magnitude of 3+4j is exactly 5.0 (Pythagorean triple: 3^2 + 4^2 = 5^2) + result = self.calc.evaluate("mag(3+4j)") + self.assertEqual(result, "5.0") + + def test_phase(self): + # Phase of 0+1j is exactly pi/2 (90 degrees) + result = self.calc.evaluate("phase(0+1j)") + # Since float comparison can be tricky, we round it for testing + self.assertEqual(round(float(result), 4), round(math.pi / 2, 4)) + + # --- BOUNDARY CONDITIONS & INVALID INPUTS --- + def test_divide_by_zero(self): + # The system must catch dividing by zero and raise a ValueError + with self.assertRaises(ValueError) as context: + self.calc.evaluate("(5+5j)/(0+0j)") + self.assertTrue("Division by zero" in str(context.exception)) + + def test_invalid_string_format(self): + # Testing what happens if the user types complete garbage + with self.assertRaises(ValueError): + self.calc.evaluate("hello_world") + + def test_missing_parentheses(self): + # The regex strictly expects parentheses. Missing them should fail. + with self.assertRaises(ValueError): + self.calc.evaluate("3+2j*5+3j") + + def test_invalid_operator(self): + # Testing an operator that isn't supported by the regex + with self.assertRaises(ValueError): + self.calc.evaluate("(3+2j)^(5+3j)") + +if __name__ == "__main__": + unittest.main() \ No newline at end of file