Building Python Proficiency Through Equation Solving

Solving Linear and Quadratic Equations to Explore Core Python Concepts

This approach uses mathematical problem solving as a unifying thread to introduce foundational Python constructs—starting from basic syntax and progressing through functions, object-oriented design, error handling, and modularization.

Linear Equations in One Variable

from sympy import symbols, Eq, solve

x = symbols('x')
equation = Eq(3 * x - 7, 5)
solution = solve(equation, x)
print(f"Solution: {solution[0]}")  # Output: 4

Systems of Linear Equations

from sympy import symbols, Eq, solve

x, y = symbols('x y')
eq1 = Eq(2 * x + y, 9)
eq2 = Eq(x - y, 3)
sol = solve((eq1, eq2), (x, y))
print(f"Solution set: {sol}")  # Output: {x: 4, y: 1}

Quadratic Equations: Symbolic and Numeric Approaches

Using sympy for exact symbolic solutions:

import sympy as sp

x = sp.symbols('x')
expr = x**2 - 5*x + 6
roots = sp.solve(expr, x)
print(f"Roots: {roots}")  # Output: [2, 3]

Manual numeric implementation with full discriminant handling:

import math

def compute_roots(a: float, b: float, c: float) -> list:
    if a == 0:
        raise ValueError("Coefficient 'a' must be non-zero for a quadratic equation.")
    
    discriminant = b**2 - 4*a*c
    
    if discriminant > 0:
        sqrt_d = math.sqrt(discriminant)
        r1 = (-b + sqrt_d) / (2*a)
        r2 = (-b - sqrt_d) / (2*a)
        return [r1, r2]
    elif discriminant == 0:
        r = -b / (2*a)
        return [r]
    else:
        real = -b / (2*a)
        imag = math.sqrt(-discriminant) / (2*a)
        return [complex(real, imag), complex(real, -imag)]

print(compute_roots(1, -5, 6))   # [2.0, 3.0]
print(compute_roots(1, -2, 5))   # [(1+2j), (1-2j)]

Visualizing Quadratic Functions

import numpy as np
import matplotlib.pyplot as plt
import sympy as sp

# Define coefficients
a, b, c = 1, -5, 6

# Generate domain and compute range
x_vals = np.linspace(-1, 6, 200)
y_vals = a * x_vals**2 + b * x_vals + c

# Plot function
plt.figure(figsize=(8, 5))
plt.plot(x_vals, y_vals, label=f'$y = {a}x^2 + {b}x + {c}$', color='blue')
plt.axhline(0, color='gray', linewidth=0.8)
plt.axvline(0, color='gray', linewidth=0.8)
plt.grid(True, alpha=0.3)
plt.xlabel('x')
plt.ylabel('y')
plt.title('Quadratic Function and Its Real Roots')

# Compute and mark roots
x_sym = sp.symbols('x')
expr_sym = a * x_sym**2 + b * x_sym + c
roots_sym = sp.solve(expr_sym, x_sym)

for root in roots_sym:
    if root.is_real:
        plt.scatter(float(root), 0, color='red', zorder=5)

plt.legend()
plt.show()

Core Language Features Demonstrated Through Equation Solving

Variables and Data Types

Python variables act as dynamic containers. Coefficients (a, b, c) are assigned numeric values—integers or floats—and the complex type handles imaginary components naturally.

Control Flow

Conditional logic determines root behavior based on the discriminant:

if discriminant > 0:
    # Two distinct real roots
elif discriminant == 0:
    # One repeated real root
else:
    # Two complex conjugate roots

Functions

Encapsulating computation improves reusability and clarity:

def quadratic_solver(a: float, b: float, c: float) -> list:
    """Compute all roots of ax² + bx + c = 0."""
    # ... implementation as above ...
    return roots

Object-Oriented Design

A class-based solver separates concerns and supports extensibility:

class PolynomialSolver:
    def __init__(self, coeffs: list):
        self.coeffs = coeffs  # descending powers: [a, b, c] for ax²+bx+c
    
    def roots(self) -> list:
        if len(self.coeffs) == 3:
            a, b, c = self.coeffs
            return compute_roots(a, b, c)
        raise NotImplementedError("Only quadratic supported in this version.")

solver = PolynomialSolver([1, -5, 6])
print(solver.roots())

Error Handling

Robust code anticipatse invalid inputs:

def safe_quadratic_solver(a, b, c):
    try:
        return compute_roots(float(a), float(b), float(c))
    except (ValueError, TypeError) as e:
        print(f"Input error: {e}")
        return []
    except ZeroDivisionError:
        print("Invalid coefficient: 'a' cannot be zero.")
        return []

Modular Structure

Separating logic into modules promotes maintainability. A file algebra.py might contain:

# algebra.py
from typing import List, Union, Optional

def discriminant(a: float, b: float, c: float) -> float:
    return b**2 - 4*a*c

def solve_quadratic(a: float, b: float, c: float) -> List[Union[float, complex]]:
    # ... same logic ...

Then imported elsewhere:

from algebra import solve_quadratic
result = solve_quadratic(1, -5, 6)

File I/O Integration

Persisting results using context managers ensures safe rseource handling:

roots = solve_quadratic(1, -5, 6)
with open("solutions.txt", "w") as f:
    f.write(f"Roots: {roots}\n")

# Later, read back
with open("solutions.txt", "r") as f:
    print(f.read().strip())

Tags: python equation-solving Beginner programming-fundamentals Mathematics

Posted on Sat, 08 Aug 2026 16:55:39 +0000 by Knutty