Domain: Math — Global Statistics & Combinatorics

Problem:
The network $G=(V,E)$ with $7$ nodes indexed from $0$ to $6$. Two commodities $k=1,2$ represent different task flows using $G$.

| Commodity | Source Node | Sink Node | Demand ($f_k$) |
|--- | --- | --- | --- |
|$k=1$ | $s_1=0$ | $t_1=6$ | $D_1=6$ |
|$k=2$ | $s_2=1$ | $t_2=5$ | $D_2=4$ |

The number of edges activated across both the commodities cannot exceed $7$, where $y_{(i,j)}$ is an indicator function of edge activation.

$$\sum_{(i,j)\in E}y_{(i,j)}\le 7$$

Following table has the edge parameters:

| Edge $(i,j)$| Capacity $u(i,j)$ | Activation cost $F(i,j)$ | Variable cost $(c_1(i,j),c_2(i,j))$|
|--- | --- | --- | --- |
|$0\rightarrow 1$|$4$|$8$|$(2,\infty)$|
|$0\rightarrow 2$|$5$|$10$|$(3,\infty)$|
|$1\rightarrow 3$|$5$|$5$|$(1,4)$|
|$2\rightarrow 4$|$5$|$6$|$(4,2)$|
|$3\rightarrow 5$|$4$|$7$|$(2,1)$|
|$3\rightarrow 4$|$3$|$12$|$(5,5)$|
|$4\rightarrow 6$|$6$|$9$|$(1,\infty)$|
|$4\rightarrow 5$|$4$|$10$|$(3,2)$|
|$5\rightarrow 6$|$5$|$6$|$(2,\infty)$|
|$1\rightarrow 6$|$3$|$15$|$(4,\infty)$|

Minimize 
$$Z_{total}=\sum_{k=1}^2 \sum_{(i,j)\in E} c_k (i,j)\cdot f_k(i,j)+\sum_{(i,j)\in E}F(i,j)\cdot y_{i,j}$$

subject to:
1. Flow conservation per node $\nu$ and per commodity $k$
2. $f_1(i,j)+f_2(i,j)\leq u(i,j) \forall (i,j)\in E$
3. $y_{i,j}=1$ if any flow is activated on the edge $(i,j)$
4. $\sum_{(i,j)\in E} y_{i,j}\le 7$
5. $f_k(i,j)\geq 0, y_{i,j}\in\{0,1\}$

Provide the Python code required to solve the prompt in one cell. Implement and run any algorithm required to solve the problem, but provide the final answer.
State the final answer explicitly.

Answer: 122

Solution code:
import random
import math

# --- 1. Problem Definition ---
INF = 1000000
COMMODITIES = [1, 2]
DEMAND_DATA = {1: {'source': 0, 'sink': 6, 'demand': 6}, 2: {'source': 1, 'sink': 5, 'demand': 4}}
ACTIVATION_BUDGET = 7

network_data = {
    (0, 1): (4, 8, 2, INF), (0, 2): (5, 10, 3, INF), (1, 3): (5, 5, 1, 4),
    (2, 4): (5, 6, 4, 2), (3, 5): (4, 7, 2, 1), (3, 4): (3, 12, 5, 5),
    (4, 6): (6, 9, 1, INF), (4, 5): (4, 10, 3, 2), (5, 6): (5, 6, 2, INF),
    (1, 6): (3, 15, 4, INF),
}
ALL_EDGES = list(network_data.keys())

# Extract costs/caps
CAPACITIES = {e: data[0] for e, data in network_data.items()}
FIXED_COSTS = {e: data[1] for e, data in network_data.items()}
VAR_COSTS = {e: {k: data[k + 1] for k in COMMODITIES} for e, data in network_data.items()}

# --- 2. Helper Functions (Finding Variable Cost on Restricted Graph) ---

def find_min_var_cost_flow(active_edges):
    """
    HEURISTIC: Evaluates the variable cost for the given set of active_edges.
    
    Since solving the multi-commodity min-cost flow without an ILP solver is complex,
    this function uses a simplified, greedy flow-decomposition approach combined with
    Bellman-Ford-like path finding (only feasible for this simple implementation).
    NOTE: This is a complex subroutine and may only approximate the true Z_var.
    """
    
    total_var_cost = 0.0
    
    # 1. Initialize Residual Graph with active edge capacities
    residual_capacity = {e: CAPACITIES[e] for e in active_edges}
    
    for k in COMMODITIES:
        demand = DEMAND_DATA[k]['demand']
        
        while demand > 0:
            # 2. Find the shortest (cheapest variable cost) path using Bellman-Ford/Dijkstra
            
            # Simple Bellman-Ford for shortest path (using variable costs)
            distances = {node: float('inf') for node in NODES}
            paths = {node: None for node in NODES}
            distances[DEMAND_DATA[k]['source']] = 0
            
            # Since costs are non-negative (ignoring INF), Dijkstra is sufficient, but 
            # we use a relaxed approach due to time constraints in this explanation.
            for _ in range(len(NODES)): # Relax edges |V| times
                for (u, v) in active_edges:
                    cost_uv = VAR_COSTS[u, v][k]
                    
                    # Only relax if there is residual capacity and finite cost
                    if residual_capacity.get((u, v), 0) > 0 and cost_uv < INF:
                         if distances[u] + cost_uv < distances[v]:
                            distances[v] = distances[u] + cost_uv
                            paths[v] = u
            
            # 3. Augment Flow along the found path
            sink = DEMAND_DATA[k]['sink']
            if distances[sink] == float('inf'):
                # No path exists - solution is infeasible for this commodity
                return float('inf')

            path_flow = demand # Start with remaining demand
            path_cost = distances[sink]
            
            # Find bottleneck capacity
            curr = sink
            while curr != DEMAND_DATA[k]['source']:
                prev = paths[curr]
                path_flow = min(path_flow, residual_capacity.get((prev, curr), 0))
                if prev is None: # Should not happen if path exists
                    return float('inf')
                curr = prev

            # Augment flow and update demand/costs
            flow_amount = min(demand, path_flow)
            total_var_cost += flow_amount * path_cost
            demand -= flow_amount
            
            # Update residual capacity
            curr = sink
            while curr != DEMAND_DATA[k]['source']:
                prev = paths[curr]
                residual_capacity[(prev, curr)] -= flow_amount
                # Back-edge capacity update is ignored for simplicity here
                curr = prev
                
    return total_var_cost


def evaluate_solution(active_edges):
    """Calculates the total cost (Z_fixed + Z_var) for a given set of active edges."""
    
    if len(active_edges) != ACTIVATION_BUDGET:
        return float('inf')
        
    # Calculate Fixed Cost
    fixed_cost = sum(FIXED_COSTS[e] for e in active_edges)
    
    # Calculate Variable Cost (Complex step)
    variable_cost = find_min_var_cost_flow(active_edges)
    
    if variable_cost == float('inf'):
        return float('inf') # Infeasible
        
    return fixed_cost + variable_cost


# --- 3. Simulated Annealing Algorithm ---

def simulated_annealing_fcnf():
    """Implements the SA metaheuristic to find the best set of 7 activated edges."""

    # SA Parameters
    T_start = 100.0
    T_min = 0.01
    alpha = 0.99
    
    # 1. Generate initial solution (Randomly select 7 edges)
    current_solution = tuple(random.sample(ALL_EDGES, ACTIVATION_BUDGET))
    current_cost = evaluate_solution(current_solution)
    
    # Ensure starting solution is feasible (try up to 100 times)
    attempts = 0
    while current_cost == float('inf') and attempts < 100:
        current_solution = tuple(random.sample(ALL_EDGES, ACTIVATION_BUDGET))
        current_cost = evaluate_solution(current_solution)
        attempts += 1

    if current_cost == float('inf'):
        return "Failed to find an initial feasible solution.", None, None

    best_solution = current_solution
    best_cost = current_cost
    T = T_start
    
    # 2. Main SA Loop
    while T > T_min:
        # Generate neighbor: swap one active edge with one inactive edge
        active_set = set(current_solution)
        inactive_set = set(ALL_EDGES) - active_set
        
        edge_to_remove = random.choice(list(active_set))
        edge_to_add = random.choice(list(inactive_set))
        
        neighbor_solution = list(active_set - {edge_to_remove} | {edge_to_add})
        neighbor_cost = evaluate_solution(tuple(neighbor_solution))

        # Calculate acceptance criteria
        if neighbor_cost < current_cost:
            accept_prob = 1.0
        else:
            accept_prob = math.exp((current_cost - neighbor_cost) / T)

        # Accept the neighbor solution?
        if random.random() < accept_prob:
            current_solution = tuple(neighbor_solution)
            current_cost = neighbor_cost
            
            if current_cost < best_cost:
                best_cost = current_cost
                best_solution = current_solution

        # Cooling Schedule
        T *= alpha

    return "Heuristic Search Completed (Simulated Annealing)", best_solution, best_cost

# --- 4. Execution ---
NODES = list(range(7)) # Defined here for helper function scope
status, solution, cost = simulated_annealing_fcnf()
print("Optimal Cost: ",cost)