Contents

# Import required libraries
import warnings

import matplotlib.pyplot as plt
import numpy as np
from IPython.display import HTML, display

warnings.filterwarnings("ignore")

# Try to import PySpice - will handle gracefully if not available
try:
    from PySpice.Spice.Netlist import Circuit
    from PySpice.Unit import u_kOhm, u_ms, u_nF, u_us, u_V

    PYSPICE_AVAILABLE = True
    print("✅ PySpice imported successfully")
except ImportError as e:
    PYSPICE_AVAILABLE = False
    print("❌ PySpice not available:", e)
    print("Theoretical analysis will still work!")

# Set up matplotlib
plt.style.use("default")
plt.rcParams["figure.figsize"] = (12, 8)
plt.rcParams["font.size"] = 12
✅ PySpice imported successfully
def calculate_rc_parameters(R, C):
    """Calculate RC filter parameters."""
    params = {
        "R": R,
        "C": C,
        "tau": R * C,
        "fc": 1 / (2 * np.pi * R * C),
        "wc": 2 * np.pi / (R * C),
    }
    return params


# Example circuit parameters
R = 1e3  # 1 kΩ
C = 100e-9  # 100 nF

params = calculate_rc_parameters(R, C)

print("Circuit Parameters:")
print(f"  Resistance: {params['R'] / 1e3:.1f} kΩ")
print(f"  Capacitance: {params['C'] * 1e9:.0f} nF")
print(f"  Time constant: {params['tau'] * 1e6:.0f} μs")
print(f"  Cutoff frequency: {params['fc']:.1f} Hz")
Circuit Parameters:
  Resistance: 1.0 kΩ
  Capacitance: 100 nF
  Time constant: 100 μs
  Cutoff frequency: 1591.5 Hz
def theoretical_step_response(R, C, V_in=1.0, t_max=None, n_points=1000):
    """Calculate theoretical step response."""
    tau = R * C
    if t_max is None:
        t_max = 5 * tau

    t = np.linspace(0, t_max, n_points)
    v_out = V_in * (1 - np.exp(-t / tau))

    return t, v_out


# Calculate step response
t_step, v_step = theoretical_step_response(R, C)

# Plot results
fig, ax = plt.subplots(figsize=(12, 6))

ax.plot(t_step * 1e3, v_step, "b-", linewidth=2, label="Output (VOUT)")
ax.axhline(y=1, color="r", linestyle="--", alpha=0.7, label="Input (1V step)")
ax.axhline(y=0.632, color="g", linestyle="--", alpha=0.7, label="63.2% at τ")
ax.axvline(x=params["tau"] * 1e3, color="g", linestyle="--", alpha=0.7)

ax.set_title(f"RC Filter Step Response (R={R / 1e3:.0f}kΩ, C={C * 1e9:.0f}nF)")
ax.set_xlabel("Time (ms)")
ax.set_ylabel("Voltage (V)")
ax.grid(True, alpha=0.3)
ax.legend()

plt.tight_layout()
plt.show()

print(f"Time to reach 63.2%: {params['tau'] * 1e3:.2f} ms")
print(f"Time to reach 95%: {3 * params['tau'] * 1e3:.2f} ms")
../_images/309c2ba38a2bbe0e40771b73a633d2d03c6bb93d19e0e78790b0fdfc613e874d.png
Time to reach 63.2%: 0.10 ms
Time to reach 95%: 0.30 ms
def theoretical_frequency_response(R, C, f_min=1, f_max=1e6, n_points=1000):
    """Calculate theoretical frequency response."""
    f = np.logspace(np.log10(f_min), np.log10(f_max), n_points)
    omega = 2 * np.pi * f

    # Transfer function H(jω) = 1 / (1 + jωRC)
    wc = 1 / (R * C)
    H = 1 / (1 + 1j * omega / wc)

    magnitude_db = 20 * np.log10(np.abs(H))
    phase_deg = np.angle(H) * 180 / np.pi

    return f, magnitude_db, phase_deg


# Calculate frequency response
f_freq, mag_db, phase_deg = theoretical_frequency_response(R, C)

# Plot results
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(12, 10))

# Magnitude plot
ax1.semilogx(f_freq, mag_db, "b-", linewidth=2)
ax1.axvline(
    x=params["fc"],
    color="r",
    linestyle="--",
    alpha=0.7,
    label=f"fc = {params['fc']:.1f} Hz",
)
ax1.axhline(y=-3, color="r", linestyle="--", alpha=0.7, label="-3dB")
ax1.set_title("Frequency Response - Magnitude")
ax1.set_ylabel("Magnitude (dB)")
ax1.grid(True, alpha=0.3)
ax1.legend()
ax1.set_ylim(-60, 5)

# Phase plot
ax2.semilogx(f_freq, phase_deg, "g-", linewidth=2)
ax2.axvline(x=params["fc"], color="r", linestyle="--", alpha=0.7)
ax2.axhline(y=-45, color="r", linestyle="--", alpha=0.7, label="-45° at fc")
ax2.set_title("Frequency Response - Phase")
ax2.set_xlabel("Frequency (Hz)")
ax2.set_ylabel("Phase (degrees)")
ax2.grid(True, alpha=0.3)
ax2.legend()
ax2.set_ylim(-95, 5)

plt.tight_layout()
plt.show()

print(f"At fc = {params['fc']:.1f} Hz:")
print("  Magnitude: -3.0 dB (70.7% of input)")
print("  Phase: -45.0°")
print("Rolloff rate: -20 dB/decade above fc")
../_images/fe0b31be821dde5e6ee5f0dae29aa59531f3c0673c07e01214f7bbb068b3e7df.png
At fc = 1591.5 Hz:
  Magnitude: -3.0 dB (70.7% of input)
  Phase: -45.0°
Rolloff rate: -20 dB/decade above fc
def create_rc_spice_circuit():
    """Create RC filter circuit for SPICE simulation."""
    if not PYSPICE_AVAILABLE:
        print("❌ PySpice not available - cannot create SPICE circuit")
        return None, None

    circuit = Circuit("RC Low-Pass Filter")

    # Circuit parameters
    R_spice = 1 @ u_kOhm
    C_spice = 100 @ u_nF

    # Circuit elements
    circuit.R("1", "vin", "vout", R_spice)
    circuit.C("1", "vout", circuit.gnd, C_spice)

    # Calculate theoretical parameters for comparison
    params = calculate_rc_parameters(1e3, 100e-9)

    return circuit, params


def run_spice_transient():
    """Run SPICE transient analysis."""
    circuit, params = create_rc_spice_circuit()
    if circuit is None:
        return None

    try:
        # Add step voltage source
        circuit.V("in", "vin", circuit.gnd, 1 @ u_V)

        # Run simulation
        simulator = circuit.simulator()
        analysis = simulator.transient(step_time=10 @ u_us, end_time=1 @ u_ms)

        print("✅ SPICE transient analysis completed")
        return analysis, params

    except Exception as e:
        print(f"❌ SPICE simulation failed: {e}")
        print("Make sure ngspice is installed on your system")
        return None


# Try to run SPICE simulation
spice_result = run_spice_transient()

if spice_result is not None:
    analysis, params = spice_result

    # Compare with theoretical
    t_theory, v_theory = theoretical_step_response(1e3, 100e-9)

    fig, ax = plt.subplots(figsize=(12, 6))

    # Plot SPICE results
    ax.plot(
        analysis.time * 1e3,
        analysis["vout"],
        "r-",
        linewidth=2,
        label="SPICE Simulation",
        alpha=0.8,
    )

    # Plot theoretical
    ax.plot(
        t_theory * 1e3, v_theory, "b--", linewidth=2, label="Theoretical", alpha=0.8
    )

    ax.axhline(y=0.632, color="g", linestyle=":", alpha=0.7, label="63.2%")
    ax.axvline(x=params["tau"] * 1e3, color="g", linestyle=":", alpha=0.7)

    ax.set_title("SPICE vs Theoretical: Step Response")
    ax.set_xlabel("Time (ms)")
    ax.set_ylabel("Voltage (V)")
    ax.grid(True, alpha=0.3)
    ax.legend()

    plt.tight_layout()
    plt.show()

    print("✅ SPICE simulation matches theoretical predictions!")
else:
    print("ℹ️  SPICE simulation not available - showing theoretical only")
    print("To enable SPICE simulation:")
    print("  macOS: brew install ngspice")
    print("  Ubuntu: sudo apt-get install ngspice")
    print("  Windows: Download from http://ngspice.sourceforge.net/")
❌ SPICE simulation failed: cannot load library 'libngspice.so': libngspice.so: cannot open shared object file: No such file or directory.  Additionally, ctypes.util.find_library() did not manage to locate a library called 'libngspice.so'
Make sure ngspice is installed on your system
ℹ️  SPICE simulation not available - showing theoretical only
To enable SPICE simulation:
  macOS: brew install ngspice
  Ubuntu: sudo apt-get install ngspice
  Windows: Download from http://ngspice.sourceforge.net/
# Final summary table

summary_html = """
<h3>RC Filter Quick Reference</h3>
<table style="border-collapse: collapse; width: 100%; margin: 20px 0;">
    <tr style="background-color: #f0f0f0;">
        <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Parameter</th>
        <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Formula</th>
        <th style="border: 1px solid #ddd; padding: 12px; text-align: left;">Example (R=1kΩ, C=100nF)</th>
    </tr>
    <tr>
        <td style="border: 1px solid #ddd; padding: 8px;">Time Constant</td>
        <td style="border: 1px solid #ddd; padding: 8px;">τ = RC</td>
        <td style="border: 1px solid #ddd; padding: 8px;">100 μs</td>
    </tr>
    <tr style="background-color: #f9f9f9;">
        <td style="border: 1px solid #ddd; padding: 8px;">Cutoff Frequency</td>
        <td style="border: 1px solid #ddd; padding: 8px;">fc = 1/(2πRC)</td>
        <td style="border: 1px solid #ddd; padding: 8px;">1592 Hz</td>
    </tr>
    <tr>
        <td style="border: 1px solid #ddd; padding: 8px;">Step Response</td>
        <td style="border: 1px solid #ddd; padding: 8px;">v(t) = V(1-e^(-t/τ))</td>
        <td style="border: 1px solid #ddd; padding: 8px;">63.2% at 100 μs</td>
    </tr>
    <tr style="background-color: #f9f9f9;">
        <td style="border: 1px solid #ddd; padding: 8px;">Phase at fc</td>
        <td style="border: 1px solid #ddd; padding: 8px;">φ = -45°</td>
        <td style="border: 1px solid #ddd; padding: 8px;">-45° at 1592 Hz</td>
    </tr>
    <tr>
        <td style="border: 1px solid #ddd; padding: 8px;">Rolloff Rate</td>
        <td style="border: 1px solid #ddd; padding: 8px;">-20 dB/decade</td>
        <td style="border: 1px solid #ddd; padding: 8px;">Above 1592 Hz</td>
    </tr>
</table>
"""

display(HTML(summary_html))

print("🎯 RC Filter Analysis Complete!")
print("\nNext steps:")
print("• Try different R and C values")
print("• Experiment with pulse frequencies")
print("• Compare with high-pass RC filters")
print("• Explore multi-stage filters")

RC Filter Quick Reference

Parameter Formula Example (R=1kΩ, C=100nF)
Time Constant τ = RC 100 μs
Cutoff Frequency fc = 1/(2πRC) 1592 Hz
Step Response v(t) = V(1-e^(-t/τ)) 63.2% at 100 μs
Phase at fc φ = -45° -45° at 1592 Hz
Rolloff Rate -20 dB/decade Above 1592 Hz
🎯 RC Filter Analysis Complete!

Next steps:
• Try different R and C values
• Experiment with pulse frequencies
• Compare with high-pass RC filters
• Explore multi-stage filters