consider the code:
import simpy
import random
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from prettytable import PrettyTable

# Parameters
num_edge_nodes = 2
num_cloud_nodes = 4
edge_buffer_size = 10
cloud_buffer_size = 15

# service times
cloud_server_time = 10
A_edge_service_time = 4
A_cloud_service_time = 2
B_edge_sevice_time = 2
B_cloud_service_partialprocess = 4
B_cloud_service_fullprocess = 5
propagation_delay = 1

cloud_server_times = [8, 10, 12]  # Add a list of service times for each cloud server type
edge_server_times = [A_edge_service_time, B_edge_sevice_time]  # Add edge service times for easy indexing


arrival_rate = 0.8

edge_costs = [1, 2, 3]
cloud_costs = [2, 4, 6]
# Modify cloud_costs and edge_costs to support more configurations
cloud_costs = [cloud_cost * server_time for cloud_cost, server_time in zip(cloud_costs, cloud_server_times)]
edge_costs = [edge_cost * server_time for edge_cost, server_time in zip(edge_costs, edge_server_times)]


# Measurements
class Measure:
    def __init__(self, N_arr_a, N_arr_b, drop, total_queuing_delay, edge_cost, cloud_cost):
        self.N_arr_A = N_arr_a
        self.N_arr_B = N_arr_b
        self.drop = drop
        self.total_queuing_delay = total_queuing_delay
        self.edge_cost = edge_cost
        self.cloud_cost = cloud_cost

measurements = Measure(0, 0, 0, 0, 0, 0)

def packet_arrivals(env, micro_data_center, cloud_data_center, data, edge_buffer, cloud_buffer, f_value):
    packet_type_options = ['A', 'B']
    packet_id = 1

    while True:
        packet_type = random.choices(packet_type_options, weights=(1 - f_value, f_value))[0]

        if packet_type == 'A':
            data.N_arr_A += 1
        else:
            data.N_arr_B += 1

        arrival_time = env.now  # Adding current time stamp for calculating delay
        if len(micro_data_center.items) < edge_buffer:
            if packet_type == 'A':
                micro_data_center.put((packet_id, packet_type, A_edge_service_time, arrival_time))
            elif packet_type == 'B':
                micro_data_center.put((packet_id, packet_type, B_edge_sevice_time, arrival_time))
        else:
            if len(cloud_data_center.items) < cloud_buffer:
                if packet_type == 'A':
                    cloud_data_center.put((packet_id, packet_type, A_cloud_service_time + propagation_delay))
                elif packet_type == 'B':
                    cloud_data_center.put((packet_id, packet_type, B_cloud_service_fullprocess + propagation_delay))
            else:
                data.drop += 1

        yield env.timeout(random.expovariate(arrival_rate))
        packet_id += 1

def edge_node(env, micro_data_center, cloud_data_center, node_id, data, edge_cost, service_time):
    while True:
        packet_id, packet_type, packet_processing_time, arrival_time = yield micro_data_center.get()

        queuing_delay = env.now - arrival_time
        data.total_queuing_delay += queuing_delay
        data.edge_cost += edge_cost * packet_processing_time
        print(f"Edge Node {node_id} processed packet {packet_id} of type {packet_type} at time {env.now}")

        yield env.timeout(service_time)  # Use parametrized service time for delay
        if packet_type == 'B':
            yield cloud_data_center.put((packet_id, packet_type, B_cloud_service_partialprocess + propagation_delay))

def cloud_node(env, cloud_data_center, node_id, data, cloud_cost, service_time):
    while True:
        packet_id, packet_type, packet_processing_time = yield cloud_data_center.get()

        yield env.timeout(service_time)  # Use parametrized service time for delay
        data.cloud_cost += cloud_cost * packet_processing_time
        print(f"Cloud Node {node_id} processed {packet_type} packet {packet_id} (including propagation delay) at time {env.now}")

# Simulation setup
simtime = 10000
packet_loss_rates = []
f_list = np.linspace(0, 1, 5)

# In the simulation loop, run the simulation by varying the server types

combinations = []
for f in f_list:
    for i in range(len(edge_costs)):
        for j in range(len(cloud_costs)):
            # Create a new environment for each combination
            env = simpy.Environment()

            # Initialize queue and measurements
            micro_data_center = simpy.Store(env)
            cloud_data_center = simpy.Store(env)
            measurements = Measure(0, 0, 0, 0, 0, 0)

            # Configure the packet arrival function
            env.process(packet_arrivals(
                env, micro_data_center, cloud_data_center, measurements, edge_buffer_size, cloud_buffer_size, f))

            # Configure edge nodes with specific server types
            for node_id in range(num_edge_nodes):
                env.process(edge_node(env, micro_data_center, cloud_data_center, node_id + 1,
                                      measurements, edge_costs[i], edge_server_times[node_id % len(edge_server_times)]))

            # Configure cloud nodes with specific server types
            for node_id in range(num_cloud_nodes):
                env.process(cloud_node(env, cloud_data_center, node_id + 1,
                                       measurements, cloud_costs[j], cloud_server_times[node_id % len(cloud_server_times)]))

            # Run the simulation and gather results
            env.run(until=simtime)
            packet_loss_rate = measurements.drop / (measurements.N_arr_A + measurements.N_arr_B)

            # Append the current combination’s cost and packet loss rate to the results
            combinations.append({
                "edge_cost": edge_costs[i],
                "cloud_cost": cloud_costs[j],
                "f": f,
                "total_cost": measurements.edge_cost + measurements.cloud_cost,
                "queuing_delay": measurements.total_queuing_delay,
                "packet_loss_rate": packet_loss_rate
            })

df = pd.DataFrame(combinations)
print(combinations)  # Print the list of combinations and their corresponding costs, queuing delays, and packet drop rates


gimme the code modifications needed to perform the following task:
Task:
Set a value of f < 0.5 and define a
desired threshold on the maximum operational cost.
• Identify the best combination of server types allowing to reduce the cost below
the desired threshold.
• Does this combination allow to respect the constraint on the maximum queuing delay, i.e. Tq, set in Task 3 for type A packets?