diff --git a/__pycache__/calculator.cpython-313.pyc b/__pycache__/calculator.cpython-313.pyc new file mode 100644 index 0000000..969ff6b 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..6195042 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..e02fa06 --- /dev/null +++ b/complex.py @@ -0,0 +1,172 @@ +# complex.py +import cmath +from calculator import Calculator +import re + + +def _complex_str_to_tuple(c_str): + try: + c = complex(c_str) + except ValueError as exc: + raise ValueError(f"Invalid complex number: {c_str}") from exc + return (float(c.real), float(c.imag)) + +def _format_complex(c): + """ + Helper function to format a Python complex object back into a + clean 'a+bj' string without parentheses to match lab requirements. + """ + res = str(c).replace(' ', '') + if res.startswith('(') and res.endswith(')'): + res = res[1:-1] + return res + +def parse_complex_string(expression): + """ + Parses an input string like '(3+2j) * (5+3j)'. + Extracts the two complex numbers and the mathematical operator. + """ + # Regex to capture everything inside the parentheses and the operator between them + pattern = r'\s*\(([^)]+)\)\s*([+\-*/])\s*\(([^)]+)\)\s*' + match = re.match(pattern, expression) + + if not match: + raise ValueError("Invalid complex expression format. Expected: '(a+bj) op (c+dj)'") + + c1_str, operator, c2_str = match.groups() + + # Remove any internal spaces (e.g., '3 + 2j' becomes '3+2j') + return c1_str.replace(' ', ''), operator, c2_str.replace(' ', '') + +def add_complex(c1, c2): + """ + Member 2: Handles the addition of two complex numbers[cite: 120]. + """ + + if not isinstance(c1, tuple) or not isinstance(c2, tuple): + raise TypeError("Inputs must be tuples") + if len(c1) != 2 or len(c2) != 2: + raise TypeError("Tuples must have exactly 2 elements") + if not (isinstance(c1[0], (int, float)) and isinstance(c1[1], (int, float)) and + isinstance(c2[0], (int, float)) and isinstance(c2[1], (int, float))): + raise TypeError("Tuple elements must be numeric") + + calc = Calculator() + a, b = c1 + c, d = c2 + + real_part = calc.add(a, c) + imag_part = calc.add(b, d) + + return (real_part, imag_part) + +def subtract_complex(c1, c2): + """ + Member 2: Handles the subtraction of c2 from c1[cite: 120]. + """ + if not isinstance(c1, tuple) or not isinstance(c2, tuple): + raise TypeError("Inputs must be tuples") + if len(c1) != 2 or len(c2) != 2: + raise TypeError("Tuples must have exactly 2 elements") + if not (isinstance(c1[0], (int, float)) and isinstance(c1[1], (int, float)) and + isinstance(c2[0], (int, float)) and isinstance(c2[1], (int, float))): + raise TypeError("Tuple elements must be numeric") + + calc = Calculator() + a, b = c1 + c, d = c2 + + real_part = calc.subtract(a, c) + imag_part = calc.subtract(b, d) + + return (real_part, imag_part) + +def multiply_complex(c1, c2): + """ + Member 3: Handles the multiplication of two complex numbers[cite: 120]. + """ + if not isinstance(c1, tuple) or not isinstance(c2, tuple): + raise TypeError("Inputs must be tuples") + if len(c1) != 2 or len(c2) != 2: + raise TypeError("Tuples must have exactly 2 elements") + if not (isinstance(c1[0], (int, float)) and isinstance(c1[1], (int, float)) and + isinstance(c2[0], (int, float)) and isinstance(c2[1], (int, float))): + raise TypeError("Tuple elements must be numeric") + + calc = Calculator() + a, b = c1 + c, d = c2 + + real_part = calc.subtract(calc.multiply(a, c), calc.multiply(b, d)) + imag_part = calc.add(calc.multiply(a, d), calc.multiply(b, c)) + + return (real_part, imag_part) + +def divide_complex(c1, c2): + """ + Member 3: Handles the division of c1 by c2[cite: 120]. + Must include error handling for division by zero. + """ + if not isinstance(c1, tuple) or not isinstance(c2, tuple): + raise TypeError("Inputs must be tuples") + if len(c1) != 2 or len(c2) != 2: + raise TypeError("Tuples must have exactly 2 elements") + if not (isinstance(c1[0], (int, float)) and isinstance(c1[1], (int, float)) and + isinstance(c2[0], (int, float)) and isinstance(c2[1], (int, float))): + raise TypeError("Tuple elements must be numeric") + + calc = Calculator() + a, b = c1 + c, d = c2 + + denominator = calc.add(calc.multiply(c, c), calc.multiply(d, d)) + if denominator == 0: + raise ValueError("Division by zero in complex division") + + real_num = calc.add(calc.multiply(a, c), calc.multiply(b, d)) + real_part = calc.divide(real_num, denominator) + + imag_num = calc.subtract(calc.multiply(b, c), calc.multiply(a, d)) + imag_part = calc.divide(imag_num, denominator) + + return (real_part, imag_part) + +def compute_magnitude(c): + """ + Member 4: Calculates the magnitude of a single complex number[cite: 122]. + """ + z = complex(c.replace(" ", "")) + return abs(z) + +def compute_phase(c): + """ + Member 4: Calculates the phase (angle) of a single complex number[cite: 122]. + """ + z = complex(c.replace(" ", "")) + return cmath.phase(z) + +def evaluate_complex_expression(expression): + """ + Evaluates a full complex expression string like: + '(1+2j) * (3+4j)' + """ + + c1_str, op, c2_str = parse_complex_string(expression) + c1 = _complex_str_to_tuple(c1_str) + c2 = _complex_str_to_tuple(c2_str) + + if op == '+': + return add_complex(c1, c2) + + elif op == '-': + return subtract_complex(c1, c2) + + elif op == '*': + return multiply_complex(c1, c2) + + elif op == '/': + return divide_complex(c1, c2) + + else: + raise ValueError("Unsupported operation") + diff --git a/test_complex.py b/test_complex.py new file mode 100644 index 0000000..8a0199f --- /dev/null +++ b/test_complex.py @@ -0,0 +1,143 @@ +# test_complex.py +import unittest +from complex import ( + parse_complex_string, add_complex, subtract_complex, + multiply_complex, divide_complex, compute_magnitude, + compute_phase, evaluate_complex_expression +) +import math +class TestComplexArithmetic(unittest.TestCase): + + # --- Parser Tests --- + def test_parse_complex_string_valid(self): + c1, op, c2 = parse_complex_string('(3 + 2j) * (5+ 3j)') + self.assertEqual(c1, '3+2j') + self.assertEqual(op, '*') + self.assertEqual(c2, '5+3j') + + def test_parse_complex_string_invalid(self): + # Boundary Condition: Missing brackets should raise a ValueError + with self.assertRaises(ValueError): + parse_complex_string('3+2j * 5+3j') + + # --- Member 2 Tests --- + def test_add_complex(self): + # Test normal addition and negative addition + self.assertEqual(add_complex((3, 2), (5, 3)), (8, 5)) + self.assertEqual(add_complex((0, 0), (5, 3)), (5, 3)) + self.assertEqual(add_complex((1, 0), (0, 1)), (1, 1)) + self.assertEqual(add_complex((2, -3), (-1, 4)), (1, 1)) + + # Boundary cases + self.assertEqual(add_complex((0, 0), (0, 0)), (0, 0)) + self.assertEqual(add_complex((999999, 0), (0, 999999)), (999999, 999999)) + self.assertEqual(add_complex((-1, -1), (-1, -1)), (-2, -2)) + + # Invalid input handling + with self.assertRaises(TypeError): + add_complex((1,), (2, 3)) # Too few elements + with self.assertRaises(TypeError): + add_complex((1, 2, 3), (2, 3)) # Too many elements + with self.assertRaises(TypeError): + add_complex((1, 'a'), (2, 3)) # Non-numeric + with self.assertRaises(TypeError): + add_complex('not a tuple', (2, 3)) + + def test_subtract_complex(self): + # Test normal subtraction + self.assertEqual(subtract_complex((5, 3), (3, 2)), (2, 1)) + self.assertEqual(subtract_complex((0, 0), (5, 3)), (-5, -3)) + self.assertEqual(subtract_complex((1, 0), (0, 1)), (1, -1)) + self.assertEqual(subtract_complex((2, -3), (-1, 4)), (3, -7)) + + # Boundary cases + self.assertEqual(subtract_complex((0, 0), (0, 0)), (0, 0)) + self.assertEqual(subtract_complex((999999, 0), (0, 999999)), (999999, -999999)) + self.assertEqual(subtract_complex((-1, -1), (-1, -1)), (0, 0)) + + # Invalid input handling + with self.assertRaises(TypeError): + subtract_complex((1,), (2, 3)) # Too few elements + with self.assertRaises(TypeError): + subtract_complex((1, 2, 3), (2, 3)) # Too many elements + with self.assertRaises(TypeError): + subtract_complex((1, 'a'), (2, 3)) # Non-numeric + with self.assertRaises(TypeError): + subtract_complex('not a tuple', (2, 3)) + + + # --- Member 3 Tests --- + + def test_multiply_complex(self): + # Normal cases + self.assertEqual(multiply_complex((3, 2), (5, 3)), (9, 19)) # (3+2j)*(5+3j) = (3*5-2*3, 3*3+2*5) = (15-6, 9+10) = (9, 19) + self.assertEqual(multiply_complex((0, 0), (5, 3)), (0, 0)) # Zero times anything + self.assertEqual(multiply_complex((1, 0), (0, 1)), (0, 1)) # (1+0j)*(0+1j) = (0, 1) + self.assertEqual(multiply_complex((2, -3), (-1, 4)), (10, 11)) # (2-3j)*(-1+4j) = (2*-1-(-3)*4, 2*4+(-3)*-1) = (-2+12, 8+3) = (10, 11) + + # Boundary cases + self.assertEqual(multiply_complex((0, 0), (0, 0)), (0, 0)) # Both zero + self.assertEqual(multiply_complex((999999, 0), (0, 999999)), (0, 999999*999999)) + self.assertEqual(multiply_complex((-1, -1), (-1, -1)), (0, 2)) + + # Invalid input handling + with self.assertRaises(TypeError): + multiply_complex((1,), (2, 3)) # Too few elements + with self.assertRaises(TypeError): + multiply_complex((1, 2, 3), (2, 3)) # Too many elements + with self.assertRaises(TypeError): + multiply_complex((1, 'a'), (2, 3)) # Non-numeric + with self.assertRaises(TypeError): + multiply_complex('not a tuple', (2, 3)) + + + def test_divide_complex(self): + # Normal cases + self.assertEqual(divide_complex((3, 2), (5, 3)), (0.6176470588235294, 0.029411764705882353)) + self.assertEqual(divide_complex((0, 0), (5, 3)), (0.0, 0.0)) + self.assertEqual(divide_complex((1, 0), (1, 0)), (1.0, 0.0)) + self.assertEqual(divide_complex((2, -3), (-1, 4)), (-0.8235294117647058, -0.29411764705882354)) + + # Boundary cases + self.assertEqual(divide_complex((0, 0), (0, 1)), (0.0, 0.0)) + self.assertEqual(divide_complex((999999, 0), (0, 999999)), (0.0, -1.0)) + self.assertEqual(divide_complex((-1, -1), (-1, -1)), (1.0, 0.0)) + + # Invalid input handling + with self.assertRaises(TypeError): + divide_complex((1,), (2, 3)) # Too few elements + with self.assertRaises(TypeError): + divide_complex((1, 2, 3), (2, 3)) # Too many elements + with self.assertRaises(TypeError): + divide_complex((1, 'a'), (2, 3)) # Non-numeric + with self.assertRaises(TypeError): + divide_complex('not a tuple', (2, 3)) + + + def test_divide_complex_by_zero(self): + # Test division by zero exception handling + with self.assertRaises(ValueError): + divide_complex((1, 2), (0, 0)) + + # --- Member 4 Tests --- + def test_compute_magnitude(self): + # Test magnitude calculations + self.assertAlmostEqual(compute_magnitude('3+4j'), 5.0) + self.assertAlmostEqual(compute_magnitude('1+0j'), 1.0) + self.assertAlmostEqual(compute_magnitude('0+2j'), 2.0) + + + def test_compute_phase(self): + # Test phase calculations (check against known angles) + self.assertAlmostEqual(compute_phase('1+0j'), 0.0) + self.assertAlmostEqual(compute_phase('0+1j'), math.pi/2) + self.assertAlmostEqual(compute_phase('-1+0j'), math.pi) + + + # --- Member 5 Tests (Integration) --- + def test_evaluate_complex_expression(self): + # Test the full pipeline from raw string to final output + pass + +if __name__ == "__main__": + unittest.main() \ No newline at end of file