Is this the same model. # --- System 1: RO with Retentate Recycle (Generic for any active contaminant) ---
def simulate_system1(q_drain, q_product, q_ro_feed_fixed, c_source_active, ro_rejection_active):
    """
    Simulates System 1: RO with retentate recycle, with a fixed RO feed flow rate.
    Calculates source flow, product concentration, and drain concentration for a single active contaminant.
    
    Args:
        q_drain (float): Drain flow rate (ml/min).
        q_product (float): Product flow rate (ml/min).
        q_ro_feed_fixed (float): Fixed RO feed flow rate (ml/min).
        c_source_active (float): Source concentration of the active contaminant.
        ro_rejection_active (float): RO rejection rate of the active contaminant (0-1).
        
    Returns:
        tuple: (q_source, c_product_active, c_drain_active, q_recycle)
               Returns None for any value if a mathematical singularity occurs.
    """
    q_source = q_product + q_drain  # Overall system flow balance

    q_ro_feed = q_ro_feed_fixed
    q_ro_retentate = q_ro_feed - q_product # Fixed RO retentate flow based on fixed RO feed and product

    # Calculate recycle flow based on fixed RO retentate and drain
    q_recycle_calc = q_ro_retentate - q_drain

    # Initialize concentration
    c_ro_feed_active = 0.0

    if q_recycle_calc <= 0:
        # Case A: No Recycle (or negative recycle, which means all retentate goes to drain)
        q_recycle = 0 # Actual recycle flow is zero
        c_ro_feed_active = c_source_active # RO feed concentration is source concentration
    else:
        # Case B: Recycle Occurs (q_recycle_calc > 0)
        q_recycle = q_recycle_calc # Actual recycle flow is the calculated positive value

        # Mass Balance for C_RO_Feed_Active
        numerator = q_source * c_source_active
        denominator = q_ro_feed - q_recycle * (q_ro_feed - q_product * (1 - ro_rejection_active)) / q_ro_retentate

        if denominator == 0:
            return None, None, None, None # Mathematical singularity

        c_ro_feed_active = numerator / denominator

    # Calculate concentrations based on RO Feed (common to both cases)
    c_product_active = c_ro_feed_active * (1 - ro_rejection_active)

    # Calculate retentate concentration from RO unit's mass balance
    if q_ro_retentate == 0:
        c_retentate_ro_active = c_ro_feed_active
    else:
        c_retentate_ro_active = (q_ro_feed * c_ro_feed_active - q_product * c_product_active) / q_ro_retentate

    c_drain_active = c_retentate_ro_active # The drain concentration is the retentate concentration

    return q_source, c_product_active, c_drain_active, q_recycle

# --- System 2: MCDI before RO, with another MCDI on Retentate side (Generic for any active contaminant) ---
def simulate_system2(q_drain, q_product, q_ro_feed_fixed, c_source_active, ro_rejection_active,
                     mcdi1_reject_flow, mcdi2_reject_flow, mcdi1_efficiency_active, mcdi2_efficiency_active):
    """
    Simulates System 2 (MCDI before RO, with MCDI on Retentate side) for a single active contaminant.
    
    Args:
        q_drain (float): Total drain flow rate (ml/min).
        q_product (float): Product flow rate (ml/min).
        q_ro_feed_fixed (float): Fixed RO feed flow rate (ml/min).
        c_source_active (float): Source concentration of the active contaminant.
        ro_rejection_active (float): RO rejection rate of the active contaminant (0-1).
        mcdi1_reject_flow (float): Fixed reject flow for MCDI1 (ml/min).
        mcdi2_reject_flow (float): Fixed reject flow for MCDI2 (ml/min).
        mcdi1_efficiency_active (float): MCDI1 removal efficiency of the active contaminant (0-1).
        mcdi2_efficiency_active (float): MCDI2 removal efficiency of the active contaminant (0-1).
        
    Returns:
        list: A list of calculated values for the active contaminant. Returns a list of Nones if invalid flow rates or convergence issues occur.
              (q_source, c_product_active, c_drain_active, q_mcdi1_feed, q_mcdi2_product_flow_iter,
               c_mcdi1_feed_conc, c_mcdi1_reject_conc, c_mcdi2_reject_conc_calc,
               q_mcdi1_product_flow_iter, q_mcdi2_feed_iter, c_ro_feed_conc)
    """
    # Fixed MCDI reject flows
    q_mcdi1_reject = mcdi1_reject_flow
    q_mcdi2_reject = mcdi2_reject_flow

    # Calculate additional drain flow (remaining drain after fixed MCDI rejects)
    q_add_drain = q_drain - q_mcdi1_reject - q_mcdi2_reject
    if q_add_drain < 0:
        # print(f"DEBUG: System 2 returns None because q_add_drain ({q_add_drain}) < 0 for q_drain={q_drain}")
        return [None] * 11 # Return list of Nones for all expected outputs

    # --- Fixed RO Unit Flows ---
    q_ro_feed = q_ro_feed_fixed # Fixed total flow into RO
    q_ro_retentate = q_ro_feed - q_product
    if q_ro_retentate < 0:
        # print(f"DEBUG: System 2 returns None because q_ro_retentate ({q_ro_retentate}) < 0 for q_drain={q_drain}")
        return [None] * 11

    # --- Initial Flow Calculations for Iteration ---
    q_mcdi2_feed_iter = q_ro_retentate - q_add_drain
    if q_mcdi2_feed_iter < 0:
        # print(f"DEBUG: System 2 returns None because q_mcdi2_feed_iter ({q_mcdi2_feed_iter}) < 0 for q_drain={q_drain}")
        return [None] * 11
    q_mcdi2_product_flow_iter = q_mcdi2_feed_iter - q_mcdi2_reject
    if q_mcdi2_product_flow_iter < 0:
        # print(f"DEBUG: System 2 returns None because q_mcdi2_product_flow_iter ({q_mcdi2_product_flow_iter}) < 0 for q_drain={q_drain}")
        return [None] * 11

    q_mcdi1_product_flow_iter = q_ro_feed_fixed - q_mcdi2_product_flow_iter
    if q_mcdi1_product_flow_iter < 0:
        # print(f"DEBUG: System 2 returns None because q_mcdi1_product_flow_iter ({q_mcdi1_product_flow_iter}) < 0 for q_drain={q_drain}")
        return [None] * 11

    q_source = q_mcdi1_feed = q_mcdi1_product_flow_iter + q_mcdi1_reject # Overall source flow

    # --- Iterative Solver for Concentrations ---
    # Initial guess for the recycle stream (MCDI2 product)
    c_mcdi2_product_conc_iter = c_source_active * 0.1

    for iter_count in range(MAX_ITERATIONS):
        # 1. MCDI1 Calculations (Feed is fresh source)
        c_mcdi1_feed_conc = c_source_active
        c_mcdi1_product_conc = c_mcdi1_feed_conc * (1 - mcdi1_efficiency_active)
        if q_mcdi1_reject == 0:
            c_mcdi1_reject_conc = c_mcdi1_feed_conc
        else:
            c_mcdi1_reject_conc = (q_mcdi1_feed * c_mcdi1_feed_conc - q_mcdi1_product_flow_iter * c_mcdi1_product_conc) / q_mcdi1_reject

        # 2. RO Feed Mixing Point
        if q_ro_feed_fixed == 0:
            c_ro_feed_conc = 0
        else:
            c_ro_feed_conc = (q_mcdi1_product_flow_iter * c_mcdi1_product_conc + \
                              q_mcdi2_product_flow_iter * c_mcdi2_product_conc_iter) / q_ro_feed_fixed

        # 3. RO Unit Calculations
        c_product_calc = c_ro_feed_conc * (1 - ro_rejection_active)
        if q_ro_retentate == 0:
            c_ro_retentate_conc = c_ro_feed_conc
        else:
            c_ro_retentate_conc = (q_ro_feed * c_ro_feed_conc - q_product * c_product_calc) / q_ro_retentate

        # 4. MCDI2 Calculations (MCDI2 processes RO retentate)
        c_mcdi2_feed_conc = c_ro_retentate_conc
        c_mcdi2_product_conc_new_calc = c_mcdi2_feed_conc * (1 - mcdi2_efficiency_active)
        if q_mcdi2_reject == 0:
            c_mcdi2_reject_conc_calc = c_mcdi2_feed_conc
        else:
            c_mcdi2_reject_conc_calc = (q_mcdi2_feed_iter * c_mcdi2_feed_conc - q_mcdi2_product_flow_iter * c_mcdi2_product_conc_new_calc) / q_mcdi2_reject

        # Check for convergence
        converged = abs(c_mcdi2_product_conc_new_calc - c_mcdi2_product_conc_iter) < ITERATION_TOLERANCE

        if converged:
            break

        # Update recycle stream concentration for the next iteration
        c_mcdi2_product_conc_iter = c_mcdi2_product_conc_new_calc
    else:
        print(f"ERROR: System 2 iteration failed to converge for q_drain={q_drain}, C_SOURCE={c_source_active}, RO_REJ={ro_rejection_active} after {MAX_ITERATIONS} iterations.")
        return [None] * 11 # Did not converge

    # Calculate final total drain concentration
    total_in_drain = (q_mcdi1_reject * c_mcdi1_reject_conc +
                      q_mcdi2_reject * c_mcdi2_reject_conc_calc +
                      q_add_drain * c_ro_retentate_conc)

    if q_drain == 0:
        c_drain_calc = 0
    else:
        c_drain_calc = total_in_drain / q_drain

    # Return all calculated values
    return (q_source, c_product_calc, c_drain_calc, q_mcdi1_feed, q_mcdi2_product_flow_iter,
            c_mcdi1_feed_conc, c_mcdi1_reject_conc, c_mcdi2_reject_conc_calc,
            q_mcdi1_product_flow_iter, q_mcdi2_feed_iter, c_ro_feed_conc)