def test_simulate_state_dependent_model_for_negative_and_0_rates():
    """
    Test that an error is raised when rates are negative or 0
    """
    with pytest.raises(ValueError):
        simulate_model(
            lambda_2=0.15,
            lambda_1=0.2,
            mu={(i, j): -0.05 for i in range(10) for j in range(10)},
            num_of_servers=8,
            threshold=4,
            system_capacity=10,
            buffer_capacity=3,
        )

    with pytest.raises(ValueError):
        simulate_model(
            lambda_2=0.15,
            lambda_1=0.2,
            mu={(i, j): 0 for i in range(10) for j in range(10)},
            num_of_servers=8,
            threshold=4,
            system_capacity=10,
            buffer_capacity=3,
        )
def test_compare_state_server_dependent_model_with_normal_property_based(
    lambda_2, lambda_1, mu, num_of_servers
):
    """
    Property based test for the simulation when using both state and server
    dependent rates. For different values of lambda_1, lambda_2, mu and
    num_of_servers checks that the simulation outputs the same results when:
        - mu = mu
        - mu = {k: {(i,j): mu}} -> dictionary of dictionaries
    """
    simulation = simulate_model(
        lambda_2=lambda_2,
        lambda_1=lambda_1,
        mu=mu,
        num_of_servers=num_of_servers,
        threshold=4,
        seed_num=0,
        runtime=100,
    )

    rates = {}
    for server in range(1, num_of_servers + 1):
        rates[server] = {(u, v): mu for u in range(10) for v in range(10)}
    server_dependent_simulation = simulate_model(
        lambda_2=lambda_2,
        lambda_1=lambda_1,
        mu=rates,
        num_of_servers=num_of_servers,
        threshold=4,
        seed_num=0,
        runtime=100,
    )

    assert round(
        sum([w.waiting_time for w in simulation.get_all_records()]),
        NUMBER_OF_DIGITS_TO_ROUND,
    ) == round(
        sum([w.waiting_time for w in server_dependent_simulation.get_all_records()]),
        NUMBER_OF_DIGITS_TO_ROUND,
    )
    assert round(
        sum([b.time_blocked for b in simulation.get_all_records()]),
        NUMBER_OF_DIGITS_TO_ROUND,
    ) == round(
        sum([b.time_blocked for b in server_dependent_simulation.get_all_records()]),
        NUMBER_OF_DIGITS_TO_ROUND,
    )
    assert round(
        sum([s.service_time for s in simulation.get_all_records()]),
        NUMBER_OF_DIGITS_TO_ROUND,
    ) == round(
        sum([s.service_time for s in server_dependent_simulation.get_all_records()]),
        NUMBER_OF_DIGITS_TO_ROUND,
    )
def test_compare_server_dependent_model_with_non_state_dependent_property_based(
    lambda_2, lambda_1, mu, num_of_servers
):
    """
    Property based test for the simulation with server dependent rates. For
    different values of lambda_1, lambda_2, mu and num_of_servers checks
    that the simulation outputs the same results when:
        - mu = mu
        - mu = {k: mu for k in range(1, num_of_servers + 1)}
    """
    simulation = simulate_model(
        lambda_2=lambda_2,
        lambda_1=lambda_1,
        mu=mu,
        num_of_servers=num_of_servers,
        threshold=4,
        seed_num=0,
        runtime=100,
    )

    server_dependent_simulation = simulate_model(
        lambda_2=lambda_2,
        lambda_1=lambda_1,
        mu={k: mu for k in range(1, num_of_servers + 1)},
        num_of_servers=num_of_servers,
        threshold=4,
        seed_num=0,
        runtime=100,
    )

    assert round(
        sum([w.waiting_time for w in simulation.get_all_records()]),
        NUMBER_OF_DIGITS_TO_ROUND,
    ) == round(
        sum([w.waiting_time for w in server_dependent_simulation.get_all_records()]),
        NUMBER_OF_DIGITS_TO_ROUND,
    )
    assert round(
        sum([b.time_blocked for b in simulation.get_all_records()]),
        NUMBER_OF_DIGITS_TO_ROUND,
    ) == round(
        sum([b.time_blocked for b in server_dependent_simulation.get_all_records()]),
        NUMBER_OF_DIGITS_TO_ROUND,
    )
    assert round(
        sum([s.service_time for s in simulation.get_all_records()]),
        NUMBER_OF_DIGITS_TO_ROUND,
    ) == round(
        sum([s.service_time for s in server_dependent_simulation.get_all_records()]),
        NUMBER_OF_DIGITS_TO_ROUND,
    )
def test_simulate_state_dependent_model_when_buffer_capacity_less_than_1():
    """
    Test that an error is raised when buffer_capacity is less than 1
    """
    with pytest.raises(ValueError):
        simulate_model(
            lambda_2=0.15,
            lambda_1=0.2,
            mu=None,
            num_of_servers=8,
            threshold=4,
            seed_num=0,
            system_capacity=10,
            buffer_capacity=0,
        )
def test_server_dependent_simulation_example_2():
    """
    Example 2 for server dependent simulation
    """
    server_dependent_simulation = simulate_model(
        lambda_1=10,
        lambda_2=20,
        mu={1: 1, 2: 1.5, 3: 2, 4: 2.5, 5: 3, 6: 3.5, 7: 4, 8: 4.5, 9: 5, 10: 5.5},
        num_of_servers=10,
        threshold=15,
        system_capacity=20,
        buffer_capacity=5,
        seed_num=0,
        runtime=100,
    )

    mean_wait = np.mean(
        [w.waiting_time for w in server_dependent_simulation.get_all_records()]
    )
    mean_block = np.mean(
        [b.time_blocked for b in server_dependent_simulation.get_all_records()]
    )
    mean_service = np.mean(
        [s.service_time for s in server_dependent_simulation.get_all_records()]
    )

    assert round(mean_wait, NUMBER_OF_DIGITS_TO_ROUND) == round(
        0.0504065681590924, NUMBER_OF_DIGITS_TO_ROUND
    )
    assert round(mean_block, NUMBER_OF_DIGITS_TO_ROUND) == round(
        0.016470674877055978, NUMBER_OF_DIGITS_TO_ROUND
    )
    assert round(mean_service, NUMBER_OF_DIGITS_TO_ROUND) == round(
        0.1931311883483223, NUMBER_OF_DIGITS_TO_ROUND
    )
def test_server_dependent_simulation_example_1():
    """
    Example 1 for server dependent simulation
    """
    server_dependent_simulation = simulate_model(
        lambda_1=4,
        lambda_2=2,
        mu={1: 1, 2: 1.5, 3: 2, 4: 2.5},
        num_of_servers=4,
        threshold=4,
        seed_num=0,
        runtime=100,
    )

    mean_wait = np.mean(
        [w.waiting_time for w in server_dependent_simulation.get_all_records()]
    )
    mean_block = np.mean(
        [b.time_blocked for b in server_dependent_simulation.get_all_records()]
    )
    mean_service = np.mean(
        [s.service_time for s in server_dependent_simulation.get_all_records()]
    )

    assert round(mean_wait, NUMBER_OF_DIGITS_TO_ROUND) == round(
        0.11021530720155796, NUMBER_OF_DIGITS_TO_ROUND
    )
    assert round(mean_block, NUMBER_OF_DIGITS_TO_ROUND) == round(
        0.49569480054255816, NUMBER_OF_DIGITS_TO_ROUND
    )
    assert round(mean_service, NUMBER_OF_DIGITS_TO_ROUND) == round(
        0.4326597402401585, NUMBER_OF_DIGITS_TO_ROUND
    )
def get_mean_waits_of_current_threshold(
    lambda_2,
    lambda_1,
    mu,
    num_of_servers,
    threshold,
    seed_num,
    num_of_trials,
    runtime,
    target,
):
    """
    Calculates the mean proportion of times that satisfy the target of all trials
    for the current threshold iteration

    Returns
    -------
    float, float, float
        The mean waiting times for ambulance patients, other patients and all
        patients for a given threshold
    """
    current_ambulance_proportions = []
    current_other_proportions = []
    current_combined_proportions = []

    if seed_num == None:
        seed_num = random.random()

    for trial in range(num_of_trials):
        individuals = simulate_model(
            lambda_2, lambda_1, mu, num_of_servers, threshold, seed_num + trial, runtime
        ).get_all_individuals()
        (
            ambulance_waits,
            ambulance_target_waits,
            other_waits,
            other_target_waits,
        ) = get_target_proportions_of_current_trial(individuals, target)

        current_ambulance_proportions.append(
            (ambulance_target_waits / ambulance_waits) if ambulance_waits != 0 else 1
        )
        current_other_proportions.append(
            (other_target_waits / other_waits) if other_waits != 0 else 1
        )
        current_combined_proportions.append(
            (ambulance_target_waits + other_target_waits)
            / (ambulance_waits + other_waits)
            if (ambulance_waits + other_waits) != 0
            else 1
        )

    return (
        np.nanmean(current_ambulance_proportions),
        np.nanmean(current_other_proportions),
        np.nanmean(current_combined_proportions),
    )
def test_compare_state_dependent_model_with_non_state_dependent_property_based(
    lambda_2, lambda_1, mu, num_of_servers
):
    """
    Property based test with state dependent service rate. Ensures that for
    different values of lambda_1, lambda_2, mu and num_of_servers, the results
    of the state dependent and non-state dependent simulation are the same when
    the rates of the state-depndednt one are all set to `mu`
    """
    simulation = simulate_model(
        lambda_2=lambda_2,
        lambda_1=lambda_1,
        mu=mu,
        num_of_servers=num_of_servers,
        threshold=4,
        seed_num=0,
        runtime=100,
        system_capacity=10,
        buffer_capacity=10,
    )

    rates = {(i, j): mu for i in range(11) for j in range(11)}
    simulation_extension = simulate_model(
        lambda_2=lambda_2,
        lambda_1=lambda_1,
        mu=rates,
        num_of_servers=num_of_servers,
        threshold=4,
        seed_num=0,
        runtime=100,
        system_capacity=10,
        buffer_capacity=10,
    )
    assert sum([w.waiting_time for w in simulation.get_all_records()]) == sum(
        [w.waiting_time for w in simulation_extension.get_all_records()]
    )
    assert sum([b.time_blocked for b in simulation.get_all_records()]) == sum(
        [b.time_blocked for b in simulation_extension.get_all_records()]
    )
    assert sum([s.service_time for s in simulation.get_all_records()]) == sum(
        [s.service_time for s in simulation_extension.get_all_records()]
    )
def test_simulate_state_dependent_model_when_threshold_more_than_system_capacity():
    """
    Tests the following scenarios where specific cases occur:
        - when buffer_capacity is less than 1 -> an error is raised
        - when threshold is greater than system capacity the
          model forces threshold=system_capacity and buffer_capacity=1
    """
    sim_results_normal = []
    sim_results_forced = []
    for seed in range(5):
        simulation = simulate_model(
            lambda_2=0.15,
            lambda_1=0.2,
            mu={(i, j): 0.05 for i in range(10) for j in range(10)},
            num_of_servers=8,
            threshold=10,
            seed_num=seed,
            system_capacity=10,
            buffer_capacity=1,
            runtime=100,
        )
        rec = simulation.get_all_records()
        sim_results_normal.append(rec)

    for seed in range(5):
        simulation = simulate_model(
            lambda_2=0.15,
            lambda_1=0.2,
            mu={(i, j): 0.05 for i in range(10) for j in range(10)},
            num_of_servers=8,
            threshold=12,
            seed_num=seed,
            system_capacity=10,
            buffer_capacity=5,
            runtime=100,
        )
        rec = simulation.get_all_records()
        sim_results_forced.append(rec)
    assert sim_results_normal == sim_results_forced
def test_simulate_state_dependent_model_example_1():
    """
    Example 1 for the simulation with state dependent rates
    """
    rates = {
        (0, 0): 0.2,
        (0, 1): 0.5,
        (0, 2): 0.3,
        (0, 3): 0.2,
        (1, 3): 0.2,
        (0, 4): 0.2,
        (1, 4): 0.4,
    }
    simulation = simulate_model(
        lambda_2=0.15,
        lambda_1=0.2,
        mu=rates,
        num_of_servers=2,
        threshold=4,
        seed_num=0,
        runtime=100,
    )

    assert (
        round(
            sum([w.waiting_time for w in simulation.get_all_records()]),
            NUMBER_OF_DIGITS_TO_ROUND,
        )
        == round(69.05359560579672, NUMBER_OF_DIGITS_TO_ROUND)
    )
    assert (
        round(
            sum([b.time_blocked for b in simulation.get_all_records()]),
            NUMBER_OF_DIGITS_TO_ROUND,
        )
        == round(1.8837534828730575, NUMBER_OF_DIGITS_TO_ROUND)
    )
    assert (
        round(
            sum([s.service_time for s in simulation.get_all_records()]),
            NUMBER_OF_DIGITS_TO_ROUND,
        )
        == round(130.39705479506074, NUMBER_OF_DIGITS_TO_ROUND)
    )
def get_times_for_patients(
    lambda_2,
    lambda_1,
    mu,
    num_of_servers,
    threshold,
    seed_num,
    measurement_type,
    runtime,
):
    """Determines the appropriate times to be used set by the user

    Parameters
    ----------
    lambda_2 : [float]
    lambda_1 : [float]
    mu : [float]
    num_of_servers : [int]
    threshold : [int]
    seed_num : [float]
    measurement_type : [string]
    runtime: [float]

    Returns
    -------
    list, list, list
        Three lists that store the times of patients from the ambulance, other
        patients and patients still in system
    """
    individuals = simulate_model(
        lambda_2, lambda_1, mu, num_of_servers, threshold, seed_num, runtime
    ).get_all_individuals()

    if measurement_type == "w":
        times = get_waiting_times(individuals)
    elif measurement_type == "b":
        times = get_blocking_times(individuals)
    else:
        times = get_both_times(individuals)

    return [times[0], times[1], times[2]]
def test_simulate_state_dependent_model_example_2():
    """
    Example 2 for the simulation with state dependent rates
    """
    rates = {(i, j): 0.05 if i < 4 else 1 for i in range(10) for j in range(10)}
    simulation = simulate_model(
        lambda_2=0.1,
        lambda_1=0.5,
        mu=rates,
        num_of_servers=8,
        threshold=4,
        seed_num=0,
        runtime=100,
    )

    assert (
        round(
            sum([w.waiting_time for w in simulation.get_all_records()]),
            NUMBER_OF_DIGITS_TO_ROUND,
        )
        == round(20.192189452374485, NUMBER_OF_DIGITS_TO_ROUND)
    )
    assert (
        round(
            sum([b.time_blocked for b in simulation.get_all_records()]),
            NUMBER_OF_DIGITS_TO_ROUND,
        )
        == round(229.48684030917272, NUMBER_OF_DIGITS_TO_ROUND)
    )
    assert (
        round(
            sum([s.service_time for s in simulation.get_all_records()]),
            NUMBER_OF_DIGITS_TO_ROUND,
        )
        == round(497.47902606711347, NUMBER_OF_DIGITS_TO_ROUND)
    )