def test_simple_reentrant_line_model_scaled_rates(self): """Example 4.2.3, Figure 2.9 from CTCN book.""" alpha1 = 1 mu1 = 2.1 mu2 = 1.1 mu3 = mu1 env = envex.simple_reentrant_line_model(alpha1=alpha1, mu1=mu1, mu2=mu2, mu3=mu3) max_mu = mu1 alpha1 /= (max_mu*(1 + env.job_generator.add_max_rate)) mu1 /= (max_mu*(1 + env.job_generator.add_max_rate)) mu2 /= (max_mu*(1 + env.job_generator.add_max_rate)) mu3 /= (max_mu*(1 + env.job_generator.add_max_rate)) # Compute load, workload and sensitivity vectors sorted by load load, workload_mat, nu = wl.compute_load_workload_matrix(env) # Theoretical results from computation by hand load_theory = alpha1 * np.array([1/mu1 + 1/mu3, 1/mu2]) workload_theory = np.array([[1./mu1 + 1./mu3, 1./mu3, 1./mu3], [1./mu2, 1./mu2, 0.]]) nu_theory = np.array([[1, 0], [0, 1]]) # Sort the theoretical results by load and keep only the first num_wl_vec sort_index = np.argsort(load_theory)[::-1] load_theory = load_theory[sort_index] workload_theory = workload_theory[sort_index, :] nu_theory = nu_theory[sort_index, :] # Compare results with theoretical results np.testing.assert_almost_equal(load, load_theory, decimal=6) np.testing.assert_almost_equal(workload_mat, workload_theory, decimal=6) np.testing.assert_almost_equal(nu, nu_theory, decimal=6)
def study_workload_space(env, num_wl_vec): """ Compute and returns the workload tuple, including the workload matrix and the load, all the c_bar vectors, and the intersection of the planes defined by the c_bar vectors. :param env: CRW environment object. :param num_wl_vec: Number of workload vectors. :return: (workload_tuple, workload_space, c_bar_vectors_vertexes, w_int) """ workload_tuple = wl.compute_load_workload_matrix(env, num_wl_vec=num_wl_vec) workload_space = wl.describe_workload_space_as_halfspaces( workload_tuple.workload_mat) min_drain_time, z = compute_minimal_draining_time_cvxpy( env.job_generator.demand_rate, env.constituency_matrix, 0, env.job_generator.buffer_processing_matrix) c_bar_vectors = utils.get_all_effective_cost_linear_vectors( workload_tuple.workload_mat, env.cost_per_buffer) int_c_bar_workload_space = find_intersection_level_set_with_workload_space( workload_tuple.workload_mat, c_bar_vectors) try: int_c_bar = find_intersection_c_bar_hyperplanes(c_bar_vectors) except: int_c_bar = [] return (workload_tuple, workload_space, min_drain_time, z, c_bar_vectors, int_c_bar_workload_space, int_c_bar)
def test_simple_reentrant_line_model(self): """Example 4.2.3, Figure 2.9 from CTCN book.""" alpha1 = 0.3 mu1 = 0.67 mu2 = 0.35 mu3 = 0.67 env = envex.simple_reentrant_line_model(alpha1=alpha1, mu1=mu1, mu2=mu2, mu3=mu3) # Compute load, workload and sensitivity vectors sorted by load load, workload_mat, nu = wl.compute_load_workload_matrix(env) # Theoretical results from computation by hand load_theory = np.array([alpha1/mu1 + alpha1/mu3, alpha1/mu2]) workload_theory = np.array([[1./mu1 + 1./mu3, 1./mu3, 1./mu3], [1./mu2, 1./mu2, 0.]]) nu_theory = np.array([[1, 0], [0, 1]]) # Sort the theoretical results by load and keep only the first num_wl_vec sort_index = np.argsort(load_theory)[::-1] load_theory = load_theory[sort_index] workload_theory = workload_theory[sort_index, :] nu_theory = nu_theory[sort_index, :] # Compare results with theoretical results np.testing.assert_almost_equal(load, load_theory, decimal=6) np.testing.assert_almost_equal(workload_mat, workload_theory, decimal=6) np.testing.assert_almost_equal(nu, nu_theory, decimal=6)
def test_zero_workloads_input(): cost_per_buffer = np.array([1.5, 1, 2])[:, None] env = examples.simple_reentrant_line_model(alpha1=0.33, mu1=0.68, mu2=0.35, mu3=0.68, cost_per_buffer=cost_per_buffer) num_wl_vec = 2 load, workload_mat, _ = wl.compute_load_workload_matrix(env, num_wl_vec) strategic_idling_params = StrategicIdlingParams() horizon = 100 si_object = StrategicIdlingCoreHorizon(workload_mat, load, cost_per_buffer, env.model_type, horizon, strategic_idling_params) si_gto_object = StrategicIdlingGTOHorizon(workload_mat, load, cost_per_buffer, env.model_type, horizon, strategic_idling_params) x_zero = np.array([[0], [0], [0]]) si_output = si_object.get_allowed_idling_directions(x_zero) assert np.all(si_output.w_star >= 0) si_output = si_gto_object.get_allowed_idling_directions(x_zero) assert np.all(si_output.w_star >= 0)
def get_environment(env_name: str, agent_name: str, episode_len_to_min_drain_time_ratio: float, terminal_discount_factor: float = 0.7, action_repetitions: int = 1, parallel_environments: int = 8, env_overload_params: Optional[Dict] = None, agent_params: Optional[Dict] = None, seed: Optional[int] = None) \ -> Tuple[TFPyEnvironment, float, float, int, Tuple[int, ...]]: """ Builds and initialises a TensorFlow environment implementation of the Single Server Queue. :param env_name: The name of the scenario to load. Must be in the list of implemented scenarios. :param agent_name: The name of the RL agent the environment is to be set up for. :param episode_len_to_min_drain_time_ratio: Maximum number of time steps per episode as a proportion of the minimal draining time. :param terminal_discount_factor: The discount applied to the final time step from which a per-step discount factor is calculated. :param action_repetitions: Number of time steps each selected action is repeated for. :param parallel_environments: Number of environments to run in parallel. :param env_overload_params: Dictionary of parameters to override the scenario defaults. :param agent_params: Optional dictionary of agent parameters the environment can be adapted for. :param seed: Random seed used to initialise the environment. :return: The environment wrapped and ready for TensorFlow Agents. """ # Handle some default argument clean up. if env_overload_params is None: env_overload_params = {} env = scenarios.load_scenario(env_name, seed, env_overload_params).env if np.all(env.state_initialiser.initial_state == 0): env.max_episode_length = 450 else: if env.state_initialiser.initial_state.ndim == 1: initial_state = env.state_initialiser.initial_state.reshape((-1, 1)) else: initial_state = env.state_initialiser.initial_state minimal_draining_time = compute_minimal_draining_time_from_env_cvxpy(initial_state, env) env.max_episode_length = int(episode_len_to_min_drain_time_ratio * minimal_draining_time) discount_factor = np.exp(np.log(terminal_discount_factor) / env.max_episode_length) load = np.max(compute_load_workload_matrix(env).load) max_ep_len = env.max_episode_length # Allow toggling of observation normalisation in the environment. # The typical behaviour for PPO is that PPO normalises observations internally as necessary so # normalisation in the environment is not necessary. if agent_name == 'ppo' and agent_params.get('normalize_observations', True): normalise_obs_in_env = False else: normalise_obs_in_env = True # Wrap and parallelise environment for tf agents. tf_env, action_dims = rl_env_from_snc_env(env, discount_factor, action_repetitions, parallel_environments, normalise_observations=normalise_obs_in_env) return tf_env, discount_factor, load, max_ep_len, action_dims
def test_return_workload_vectors_based_on_medium_load_threshold_simple_routing_model(): """Example 6.2.2. from CTCN online, Example 6.2.4 from printed version. Returns workload vectors that correspond with threshold > 0.6.""" alpha_r = 0.18 mu1 = 0.13 mu2 = 0.07 mu_r = 0.3 env = envex.simple_routing_model(alpha_r, mu1, mu2, mu_r) # Compute load, workload and sensitivity vectors sorted by load load_threshold = 0.6 load, workload, nu = wl.compute_load_workload_matrix(env, load_threshold=load_threshold) # Compare results with theoretical result from CTCN book for load >= value. load_theory = np.array([alpha_r / (mu1 + mu2), alpha_r / mu_r]) workload_theory = np.vstack((1 / (mu1 + mu2) * np.ones(env.num_buffers), [0, 0, 1/mu_r])) nu_theory = np.array([[mu1 / (mu1 + mu2), mu2 / (mu1 + mu2), 0], [0, 0, 1]]) np.testing.assert_almost_equal(load, load_theory, decimal=6) np.testing.assert_almost_equal(workload, workload_theory, decimal=6) np.testing.assert_almost_equal(nu, nu_theory, decimal=6)
def test_compute_network_load_and_workload_klimov_model(): """Example 4.2.1. Klimov model form CTCN online""" alpha1 = .1 alpha2 = .12 alpha3 = .13 alpha4 = .14 mu1 = 0.6 mu2 = 0.7 mu3 = 0.8 mu4 = 0.9 env = envex.klimov_model(alpha1, alpha2, alpha3, alpha4, mu1, mu2, mu3, mu4) # Compute load, workload and sensitivity vectors sorted by load load, workload, nu = wl.compute_load_workload_matrix(env) # Compute network load and associated workload and sensitivity vectors. Useful to validate that # we obtain the same results with two different methods. network_load, xi_bottleneck, nu_bottleneck, constraints \ = alt_methods_test.compute_network_load_and_bottleneck_workload(env) # Third method of computing the network load: network_load_bis = alt_methods_test.compute_network_load(env) # Compare the three methods of obtaining the network load. np.testing.assert_almost_equal(network_load, network_load_bis, decimal=6) np.testing.assert_almost_equal(network_load, load[0], decimal=6) np.testing.assert_almost_equal(xi_bottleneck.value, np.reshape(workload[0, :], (env.num_buffers, 1)), decimal=6) # Compare results with theoretical result from CTCN book. np.testing.assert_almost_equal(load, alpha1/mu1 + alpha2/mu2 + alpha3/mu3 + alpha4/mu4, decimal=6) np.testing.assert_almost_equal(workload, np.array([[1/mu1, 1/mu2, 1/mu3, 1/mu4]]), decimal=6)
def test_get_policy_examples_two_alternative_methods_multiple_horizons( ex_env, horizon): seed = 42 np.random.seed(seed) env = eval('examples.' + ex_env)(job_gen_seed=seed) kappa_w = 1e2 kappa = 1e2 safety_stocks_vec = 2 * np.ones((env.num_resources, 1)) policy_params = BigStepPenaltyPolicyParams('cvx.CPLEX', False, kappa_w, kappa) load, workload_mat, nu = workload.compute_load_workload_matrix(env) num_wl_vec = workload_mat.shape[0] k_idling_set = np.random.randint(0, workload_mat.shape[0], 1) draining_bottlenecks = set(np.random.randint(0, workload_mat.shape[0], 1)) state = 20 * np.ones((env.num_buffers, 1)) policy_cvx = BigStepPolicy(env.cost_per_buffer, env.constituency_matrix, env.job_generator.demand_rate, env.job_generator.buffer_processing_matrix, workload_mat, nu, env.list_boundary_constraint_matrices, env.ind_surplus_buffers, policy_params) kwargs = { 'safety_stocks_vec': safety_stocks_vec, 'k_idling_set': k_idling_set, 'draining_bottlenecks': draining_bottlenecks, 'horizon': horizon } z_star_cvx, opt_val_cvx = policy_cvx.get_policy(state, **kwargs) allowed_activities = get_allowed_activities(policy_cvx, num_wl_vec, k_idling_set) z_star_scipy, opt_val_scipy = policy_utils.feedback_policy_nonidling_penalty_scipy( state, env.cost_per_buffer, env.constituency_matrix, env.job_generator.buffer_processing_matrix, workload_mat, safety_stocks_vec, k_idling_set, draining_bottlenecks, kappa_w, env.list_boundary_constraint_matrices, allowed_activities, method='revised simplex', demand_rate=env.job_generator.demand_rate, horizon=horizon) # SciPy has safety stock constraints, while CVX has a safety stock penalty. opt_val_cvx = ( env.cost_per_buffer.T @ env.job_generator.buffer_processing_matrix @ z_star_cvx)[0] opt_val_scipy = ( env.cost_per_buffer.T @ env.job_generator.buffer_processing_matrix @ z_star_scipy[:, None])[0] np.testing.assert_allclose(opt_val_cvx, opt_val_scipy, rtol=5e-2)
def test_compute_network_load_and_workload_simple_reentrant_line_model(): """Example 4.2.3. Simple re-entrant line from CTCN online.""" alpha1 = 0.3 mu1 = 0.67 mu2 = 0.35 mu3 = 0.67 env = envex.simple_reentrant_line_model(alpha1, mu1, mu2, mu3) # Compute load, workload and sensitivity vectors sorted by load load, workload, nu = wl.compute_load_workload_matrix(env) # Compute network load and associated workload and sensitivity vectors. Useful to validate that # we obtain the same results with two different methods. network_load, xi_bottleneck, nu_bottleneck, constraints \ = alt_methods_test.compute_network_load_and_bottleneck_workload(env) # Third method of computing the network load: network_load_bis = alt_methods_test.compute_network_load(env) # Compare the three methods of obtaining the network load. np.testing.assert_almost_equal(network_load, network_load_bis, decimal=6) np.testing.assert_almost_equal(network_load, load[0], decimal=6) np.testing.assert_almost_equal(xi_bottleneck.value, np.reshape(workload[0, :], (env.num_buffers, 1)), decimal=6) # Compare results with theoretical result from CTCN book. load_theory = np.array([alpha1 * (1/mu1 + 1/mu3), alpha1/mu2]) workload_theory = np.vstack(([1/mu1 + 1/mu3, 1/mu3, 1/mu3], [1/mu2, 1/mu2, 0])) np.testing.assert_almost_equal(load, load_theory, decimal=6) np.testing.assert_almost_equal(workload, workload_theory, decimal=6)
def test_three_station_network_model(): """Example 5.3.2 from CTCN online (Example 5.3.5 from printed version). Figure 5.2.""" env = envex.three_station_network_model() # Compute load, workload and sensitivity vectors sorted by load load, workload, nu = wl.compute_load_workload_matrix(env) # Third method of computing the network load: network_load_bis = alt_methods_test.compute_network_load(env) # Compute network load and associated workload and sensitivity vectors. Useful to validate that # we obtain the same results with two different methods. network_load, xi_bottleneck, nu_bottleneck, constraints \ = alt_methods_test.compute_network_load_and_bottleneck_workload(env) workload_theory = np.array([[1, 1, 0, 1, 0, 1], [1, 0, 1, 1, 0, 1], [1, 0, 1, 0, 1, 1]]) # Due to numerical noise, different computers can obtain the barc vectors in different order. # So we will compare sets instead of ndarrays. np.around(workload, decimals=6, out=workload) np.around(workload_theory, decimals=6, out=workload_theory) workload_set = set(map(tuple, workload)) workload_theory_set = set(map(tuple, workload_theory)) # Compare the three methods of obtaining the network load. np.testing.assert_almost_equal(network_load, network_load_bis, decimal=6) np.testing.assert_almost_equal(network_load, load[0], decimal=6) # We don't compare the two methods of obtaining the bottleneck workload for zero demand. # Compare workload with theoretical result from CTCN book. assert workload_set == workload_theory_set
def test_compute_network_load_and_workload_dai_wang_model(): """Example 4.6.4 and Figure 4.15 from CTCN online ed.""" alpha1 = 0.2 mu1 = 0.66 mu2 = mu1 mu3 = 0.42 mu4 = mu3 mu5 = mu1 env = envex.dai_wang_model(alpha1=alpha1, mu1=mu1, mu2=mu2, mu3=mu3, mu4=mu4, mu5=mu5) load_theory = np.array([2 * alpha1/mu3, 3 * alpha1/mu1]) workload_theory = np.array([[1/mu3 + 1/mu4, 1/mu3 + 1/mu4, 1/mu3 + 1/mu4, 1/mu4, 0], [1/mu1 + 1/mu2 + 1/mu5, 1/mu2 + 1/mu5, 1/mu5, 1/mu5, 1/mu5]]) # Compute load, workload and sensitivity vectors sorted by load load, workload, nu = wl.compute_load_workload_matrix(env) # Compute network load and associated workload and sensitivity vectors. Useful to validate that # we obtain the same results with two different methods. network_load, xi_bottleneck, nu_bottleneck, constraints \ = alt_methods_test.compute_network_load_and_bottleneck_workload(env) # Third method of computing the network load: network_load_bis = alt_methods_test.compute_network_load(env) # Compare the three methods of obtaining the network load. np.testing.assert_almost_equal(network_load, network_load_bis, decimal=6) np.testing.assert_almost_equal(network_load, load[0], decimal=6) np.testing.assert_almost_equal(xi_bottleneck.value, np.reshape(workload[0, :], (env.num_buffers, 1)), decimal=6) # Compare results with theoretical result from CTCN book. np.testing.assert_almost_equal(load, load_theory, decimal=6) np.testing.assert_almost_equal(workload, workload_theory, decimal=6)
def test_compute_network_load_and_workload_ksrs_network_model_different_parameters(): """Trying different parameters for Example 4.2.4. KSRS model from CTCN online.""" alpha1 = 0.4 alpha3 = 0.2 mu1 = 0.7 mu2 = 0.75 mu3 = 0.8 mu4 = 0.8 env = envex.ksrs_network_model(alpha1, alpha3, mu1, mu2, mu3, mu4) # Compute load, workload and sensitivity vectors sorted by load load, workload, nu = wl.compute_load_workload_matrix(env) # Compute network load and associated workload and sensitivity vectors. Useful to validate that # we obtain the same results with two different methods. network_load, xi_bottleneck, nu_bottleneck, constraints \ = alt_methods_test.compute_network_load_and_bottleneck_workload(env) # Third method of computing the network load: network_load_bis = alt_methods_test.compute_network_load(env) workload_theory = np.array([[1 / mu1, 0, 1 / mu4, 1 / mu4], [1 / mu2, 1 / mu2, 1 / mu3, 0]]) # Due to numerical noise, different computers can obtain the barc vectors in different order. # So we will compare sets instead of ndarrays. np.around(workload, decimals=6, out=workload) np.around(workload_theory, decimals=6, out=workload_theory) workload_set = set(map(tuple, workload)) workload_theory_set = set(map(tuple, workload_theory)) assert workload_set == workload_theory_set # Compare the three methods of obtaining the network load. np.testing.assert_almost_equal(network_load, network_load_bis, decimal=6) np.testing.assert_almost_equal(network_load, load[0], decimal=6) # Compare the two methods of obtaining the bottleneck workload. np.testing.assert_almost_equal(xi_bottleneck.value, np.reshape(workload[0, :], (env.num_buffers, 1)), decimal=6)
def prepare_describe_workload_space_as_halfspaces_test(num_wl_vec=None): env = examples.simple_link_constrained_model( mu13=4, alpha1=4.8, mu12=2, mu25=2, mu32=3, mu34=1.7, mu35=2, mu45=1, mu5=5.2, cost_per_buffer=np.array([[1], [0.5], [4], [2], [4]])) workload_tuple = wl.compute_load_workload_matrix(env, num_wl_vec, use_cdd_for_vertices=True) workload_space = wl.describe_workload_space_as_halfspaces(workload_tuple.workload_mat) return workload_space, workload_tuple, env
def test_three_workload_vectors_in_complex_demand_driven_model(): """Example 7.2.3, Figure 7.5, from CTCN.""" params = {'d1': 19/75, 'd2': 19/75, 'mu1': 13/15, 'mu2': 26/15, 'mu3': 13/15, 'mu4': 26/15, 'mu5': 1, 'mu6': 2, 'mu7': 1, 'mu8': 2, 'mu9': 1, 'mu10a': 1/3, 'mu10b': 1/3, 'mu11': 1/2, 'mu12': 1/10, 'mud1': 100, 'mus1': 100, 'mud2': 100, 'mus2': 100} scaling_factor = 100 params = {k: v/scaling_factor for k, v in params.items()} env = envex.complex_demand_driven_model(**params) # Compute load, workload and sensitivity vectors sorted by load num_wl_vec = 4 load, workload, nu = wl.compute_load_workload_matrix(env, num_wl_vec) # Compare results with first two vectors given by from CTCN book. workload_book_t = np.array( [ [0, 0, 0], [0, 0, 0], [-0.58, -0.5, -3.03], [-1.15, -2, 0], [-1.15, 0, 0], [-0.58, 0, 0], [-1.73, -0.5, -3.03], [-0.58, -0.26, -3.03], [-1.15, -1, 0], [-0.58, -0.5, 0], [-0.58, -0.5, -3.03], [-0.58, -0.5, 0], [-2.02, -1.75, -3.79], [-1.73, -2, 0], [2.02, 1.75, 3.79], # Book says [2.01, 1.75, 3.79] [1.73, 2, 0] ] ) workload_book_t *= scaling_factor # network_load, xi_bottleneck, nu_bottleneck, constraints \ # = alt_methods_test.compute_network_load_and_bottleneck_workload(env) # We obtain 4 bottlenecks. But the compute_vertex routing is susceptible of numerical noise. # Check that they all have same network load equal to the one in the book: 0.95. np.testing.assert_almost_equal(load, 0.95) # Due to numerical noise, different computers can obtain the barc vectors in different order. # So we will compare sets instead of ndarrays. np.around(workload, out=workload) # We don't get similar but not exactly the same workload vectors as in the book. Most of the # values are equal, but there are some that are slightly different. I asked Sean and he said # that this example was done by his PhD student several years ago and that he cannot confirm # whether the values are correct. So we will compare only with the first workload vector, which # only differs from one of the computed workload vectors in the second decimal of one of the # values. equal = False for w in workload: equal |= np.allclose(workload_book_t.T[0, :], w) assert equal
def test_physical_resources_to_workload_simple_routing_model(): # For this model, there should be a pooled resource env = examples.simple_routing_model( alpha_r=0.2, mu1=0.13, mu2=0.07, mu_r=0.2, cost_per_buffer=np.ones((3, 1)), initial_state=(1, 1, 1), capacity=np.ones((3, 1)) * np.inf, job_conservation_flag=True, job_gen_seed=42) nu = wl.compute_load_workload_matrix(env=env, num_wl_vec=None, load_threshold=None, feasible_tol=1e-10).nu phys_resources_to_wl_index = validation_utils.workload_to_physical_resources_index(nu) assert np.all(np.array([[2], [0, 1], [1], [0]]) == phys_resources_to_wl_index)
def test_limited_workload_vectors_in_three_station_network_model(): """Example 5.3.2 from CTCN online (Example 5.3.5 from printed version). Figure 5.2. This test specifies that only one workload vector should be returned.""" env = envex.three_station_network_model() # Compute load, workload and sensitivity vectors sorted by load num_wl_vec = 1 load, workload, nu = wl.compute_load_workload_matrix(env, num_wl_vec) # Compare results with first vector given by from CTCN book. np.testing.assert_almost_equal(workload, np.array([[1, 1, 0, 1, 0, 1]]), decimal=6) # Compute load, workload and sensitivity vectors sorted by load num_wl_vec = 2 load, workload, nu = wl.compute_load_workload_matrix(env, num_wl_vec) # Compare results with first two vectors given by from CTCN book. np.testing.assert_almost_equal(workload, np.array([[1, 1, 0, 1, 0, 1], [1, 0, 1, 1, 0, 1]]), decimal=6)
def test_physical_resources_to_workload_index_simple_reentrant_line(): # For this model and these parameters, the second resource should have higher load and so should # correspond to the first workload env = examples.simple_reentrant_line_model( alpha1=9, mu1=22, mu2=10, mu3=22, cost_per_buffer=np.ones((3, 1)), initial_state=(0, 0, 0), capacity=np.ones((3, 1)) * np.inf, job_conservation_flag=True, job_gen_seed=42) nu = wl.compute_load_workload_matrix(env=env, num_wl_vec=None, load_threshold=None, feasible_tol=1e-10).nu phys_resources_to_wl_index = validation_utils.workload_to_physical_resources_index(nu) assert np.all(np.array([[1], [0]]) == phys_resources_to_wl_index)
def perform_test(alpha1, mu1, mu2, mu3, cost_per_buffer, num_wl_vec, w): """ Test for the simple re-entrant line (see example 4.2.3, Figure 2.9 from CTCN book) given parameters of the environment, the relaxation and a specific point w. """ assert num_wl_vec == 2 # The test is only implemented without relaxation env = examples.simple_reentrant_line_model( alpha1=alpha1, mu1=mu1, mu2=mu2, mu3=mu3, cost_per_buffer=cost_per_buffer) # We define the theoretical workload matrix and compare it to the one we compute in order to # find which relaxation we are doing # Theoretical workload matrix and load workload_mat_theory = np.array( [[1. / mu1 + 1. / mu3, 1. / mu3, 1. / mu3], [1. / mu2, 1. / mu2, 0.]]) load_theory = np.array([alpha1 / mu1 + alpha1 / mu3, alpha1 / mu2]) # Computed workload matrix (sorted by load) _, workload_mat, _ = wl.compute_load_workload_matrix(env, num_wl_vec) # Theoretical vertexes of the \bar{c} feasible region based on the dim of the relaxation vertexes_2d = np.array( [[ mu3 * cost_per_buffer[2], mu2 * cost_per_buffer[0] - cost_per_buffer[2] * mu2 * (mu3 / mu1 + 1.) ], [ mu1 * (cost_per_buffer[0] - cost_per_buffer[1]), mu2 * cost_per_buffer[1] + (mu1 * mu2 / mu3) * (cost_per_buffer[1] - cost_per_buffer[0]) ], [ mu3 * cost_per_buffer[2], mu2 * (cost_per_buffer[1] - cost_per_buffer[2]) ]]) # We select which vertexes are feasible based on the env parameters if mu1 * (cost_per_buffer[0] - cost_per_buffer[1]) <= mu3 * cost_per_buffer[2]: feasible_vertexes = vertexes_2d[[0, 1], :, :] else: feasible_vertexes = vertexes_2d[[2], :, :] # The theoretical \bar{c} vectors were computed for a specific order of the workload # vectors. So we compute sort_by_load_index to be able to reorder the theoretical # \bar{c} components based on the sort made by load sort_by_load_index = np.argsort(load_theory)[::-1] feasible_vertexes = feasible_vertexes[:, sort_by_load_index, :] # Compute the index of the theoretical vertex which satisfy the max max_vertex_index = np.argmax(np.dot(w.T, feasible_vertexes).flatten()) barc_theory = feasible_vertexes[max_vertex_index] barc, _, _ = alt_methods_test.compute_dual_effective_cost_cvxpy( w, workload_mat, cost_per_buffer, method='cvx.ECOS') np.testing.assert_almost_equal(barc, barc_theory, decimal=4)
def get_layered_policy_object_for_simple_link_constrained_model(env): _, workload_mat, nu = workload.compute_load_workload_matrix(env, 6) policy_params = BigStepLayeredPolicyParams('cvx.CPLEX') policy_obj = BigStepLayeredPolicy( env.cost_per_buffer, env.constituency_matrix, env.job_generator.demand_rate, env.job_generator.buffer_processing_matrix, env.workload_mat, env.nu, env.list_boundary_constraint_matrices, env.ind_surplus_buffers, policy_params) return policy_obj
def test_compute_network_load_and_workload_simple_routing_model(): """Example 6.2.2. from CTCN online, Example 6.2.4 from printed version.""" alpha_r = 0.18 mu1 = 0.13 mu2 = 0.07 mu_r = 0.3 env = envex.simple_routing_model(alpha_r, mu1, mu2, mu_r) # Compute load, workload and sensitivity vectors sorted by load load, workload, nu = wl.compute_load_workload_matrix(env) # Compute network load and associated workload and sensitivity vectors. Useful to validate that # we obtain the same results with two different methods. network_load, xi_bottleneck, nu_bottleneck, constraints \ = alt_methods_test.compute_network_load_and_bottleneck_workload(env) # Third method of computing the network load: network_load_bis = alt_methods_test.compute_network_load(env) # Compare the two methods of obtaining the network load, directly and as the highest load. np.testing.assert_almost_equal(network_load, network_load_bis, decimal=6) np.testing.assert_almost_equal(network_load, load[0], decimal=6) np.testing.assert_almost_equal(xi_bottleneck.value, np.reshape(workload[0, :], (env.num_buffers, 1)), decimal=6) # Compare results with theoretical result from CTCN book, for the 4 resources. load_theory = np.array([alpha_r / (mu1 + mu2), alpha_r / mu_r, 0, 0]) workload_theory = np.vstack((1 / (mu1 + mu2) * np.ones(env.num_buffers), [0, 0, 1/mu_r], [0, 1 / mu2, 0], [1/mu1, 0, 0])) nu_theory = np.array([[mu1 / (mu1 + mu2), mu2 / (mu1 + mu2), 0], [0, 0, 1], [0, 1, 0], [1, 0, 0]]) # Due to numerical noise, different computers can obtain the barc vectors in different order. # So we will compare sets instead of ndarrays. np.around(workload, decimals=6, out=workload) np.around(workload_theory, decimals=6, out=workload_theory) np.around(nu, decimals=6, out=nu) np.around(nu_theory, decimals=6, out=nu_theory) workload_set = set(map(tuple, workload)) workload_theory_set = set(map(tuple, workload_theory)) nu_set = set(map(tuple, nu)) nu_theory_set = set(map(tuple, nu_theory)) np.testing.assert_almost_equal(load, load_theory, decimal=6) assert workload_set == workload_theory_set assert nu_set == nu_theory_set
def test_get_forbidden_actions_per_bottleneck_loop_2_queues(): env = examples.loop_2_queues(mu1=1, mu12=0.5, mu2=0.8, mu21=1, cost_per_buffer=np.array([[1], [0.1]]), demand_rate=np.array([[0.1], [0.95]])) load, workload_mat, nu = workload.compute_load_workload_matrix(env) xi_s = workload_mat[0, :] # Pooled resource. nu_s = nu[0, :] # Pooled resource. fa_s = BigStepPolicy.get_forbidden_activities_per_bottleneck( xi_s, nu_s, env.job_generator.buffer_processing_matrix, env.constituency_matrix) assert fa_s == [1] # We cannot send stuff back through mu12.
def test_return_no_workload_vectors_due_on_too_high_load_threshold_simple_routing_model(): """Example 6.2.2. from CTCN online, Example 6.2.4 from printed version. Returns workload vectors that correspond with threshold > 0.99, but there are none.""" alpha_r = 0.18 mu1 = 0.13 mu2 = 0.07 mu_r = 0.3 env = envex.simple_routing_model(alpha_r, mu1, mu2, mu_r) # Compute load, workload and sensitivity vectors sorted by load load_threshold = 0.99 load, workload, nu = wl.compute_load_workload_matrix(env, load_threshold=load_threshold) assert load.size == workload.size == nu.size == 0
def test_compute_network_load_and_workload_simple_reentrant_line_model_only_one_vector_required(): """Example 4.2.3. Simple re-entrant line from CTCN online.""" alpha1 = 0.3 mu1 = 0.67 mu2 = 0.35 mu3 = 0.67 env = envex.simple_reentrant_line_model(alpha1, mu1, mu2, mu3) # Compute load, workload and sensitivity vectors sorted by load num_wl_vec = 1 load, workload, nu = wl.compute_load_workload_matrix(env, num_wl_vec=num_wl_vec) # Compare results with theoretical result from CTCN book. load_theory = alpha1 * (1/mu1 + 1/mu3) workload_theory = np.array([[1/mu1 + 1/mu3, 1/mu3, 1/mu3]]) np.testing.assert_almost_equal(load, load_theory, decimal=6) np.testing.assert_almost_equal(workload, workload_theory, decimal=6)
def generate_workload_tuple(env: crw.ControlledRandomWalk, num_workload_vectors: int, load_threshold: float, solver: str) -> WorkloadTuple: """ Generate the workload tuple from the first relaxation. :param env: Environment (defining the topology and constraints of the network). :param num_workload_vectors: Number of leading workload vectors to choose. :param load_threshold: Lower bound on load to choose workload vectors. :param solver: Convex optimisation solver to be called when computing a feasible point inside the intersection of halfspaces. """ return compute_load_workload_matrix(env, num_workload_vectors, load_threshold, solver=solver)
def compute_minimal_draining_time_computing_workload_from_env( initial_state: types.StateSpace, env: ControlledRandomWalk, demand_rate: types.StateSpace) -> float: """ Computes the minimal draining time for an initial state. Perform Eq. (6.8) from CTCN book (online version). This equation consider only the xi_s which are workload vectors (o_s=1) and is not the general Eq. (7.4). Thus, it can only be used for push model. This method is not used by the algorithm, but just by the tests for checking the results of comparing the same magnitudes computed via different methods. """ workload_mat = compute_load_workload_matrix( env=env, num_wl_vec=None, load_threshold=None, feasible_tol=1e-10).workload_mat minimal_draining_time = compute_minimal_draining_time_computing_workload( initial_state, workload_mat, demand_rate) return minimal_draining_time
def test_cum_cost_computation_switching_curve_regime_homogenous_cost(): neg_log_discount_factor = - np.log(0.99999) env = examples.simple_reentrant_line_model(alpha1=0.33, mu1=0.7, mu2=0.34, mu3=0.7, cost_per_buffer=np.array([1, 1.001, 1])[:, None]) num_wl_vec = 2 load, workload_mat, nu = wl.compute_load_workload_matrix(env, num_wl_vec) strategic_idling_params = StrategicIdlingParams() workload_cov = np.array([[2, 0.5], [0.5, 3]]) kappa_w = np.sum(env.cost_per_buffer) * 10 kappa = 1e4 policy_params = BigStepPenaltyPolicyParams('cvx.CPLEX', False, kappa_w, kappa) policy_object = BigStepPolicy( env.cost_per_buffer, env.constituency_matrix, env.job_generator.demand_rate, env.job_generator.buffer_processing_matrix, workload_mat, nu, env.list_boundary_constraint_matrices, env.ind_surplus_buffers, policy_params) si_object = StrategicIdlingForesight(workload_mat=workload_mat, neg_log_discount_factor=neg_log_discount_factor, load=load, cost_per_buffer=env.cost_per_buffer, model_type=env.model_type, policy_object=policy_object, strategic_idling_params=strategic_idling_params, workload_cov=workload_cov) init_state = np.array([0, 0, 10000])[:, np.newaxis] init_w = workload_mat @ init_state si_object._current_state = init_state cw_vars = si_object._non_negative_workloads(init_w) cw_vars = super(StrategicIdlingForesight, si_object)._handle_switching_curve_regime(init_w, cw_vars) gto_cost = si_object._roll_out_gto_fluid_policy(cw_vars) std_hedging_cost = si_object._roll_out_hedgehog_fluid_policy(cw_vars) np.testing.assert_allclose(std_hedging_cost/1e6,1214,rtol=0.03) np.testing.assert_allclose(gto_cost/1e6,950,rtol=0.03) si_object.get_allowed_idling_directions(init_state) assert si_object._current_regime == "gto" assert si_object._original_target_dyn_bot_set == set([0,1])
def perform_test(env, num_batch, num_data, class_kind, job_gen_seed): """ This test is designed to be imported by compute asymptotic covariance classes. """ workload_tuple = workload.compute_load_workload_matrix(env) workload_cov_computer = class_kind(env.job_generator, env.constituency_matrix, workload_tuple.workload_mat) workload_cov_estimator = EstimateAsymptoticWorkloadCovBatchMeans(env) workload_cov_com = workload_cov_computer.compute_asymptotic_workload_cov() agent = SteadyStatePolicyAgent(env, agent_seed=job_gen_seed + 200, mpc_seed=job_gen_seed + 300) workload_cov_est = workload_cov_estimator.estimate_asymptotic_workload_cov( env.job_generator.buffer_processing_matrix, workload_tuple, num_batch, num_data, agent) assert np.linalg.norm(workload_cov_est - workload_cov_com) / np.linalg.norm(workload_cov_com) \ <= 0.2
def test_big_step_policy_safety_stock_active(): """ Resource is below safety stock threshold. Nonidling penalty is zero. Minimum cost is achieved by draining buffer 3. However, resource 1 will drain buffer 1, so buffer 2 reaches its safety stock level. """ state = np.array([0, 0, 100])[:, None] alpha1 = 8 mu1 = 20 mu2 = 10 mu3 = 20 safety_stocks_vec = 8 * np.ones((2, 1)) horizon = 22 cost_per_buffer = np.array([[1.5], [1], [2]]) z_star_true = np.array([0.4, 0, 0.6])[:, None] env = examples.simple_reentrant_line_model(alpha1, mu1, mu2, mu3, cost_per_buffer) load, workload_mat, nu = workload.compute_load_workload_matrix(env) kappa_w = 0 kappa = 1e2 k_idling_set = np.array([]) draining_bottlenecks = {} boolean_action_flag = False policy_params = BigStepPenaltyPolicyParams('cvx.CPLEX', boolean_action_flag, kappa_w, kappa) policy_cvx = BigStepPolicy(env.cost_per_buffer, env.constituency_matrix, env.job_generator.demand_rate, env.job_generator.buffer_processing_matrix, workload_mat, nu, env.list_boundary_constraint_matrices, env.ind_surplus_buffers, policy_params) kwargs = { 'safety_stocks_vec': safety_stocks_vec, 'k_idling_set': k_idling_set, 'draining_bottlenecks': draining_bottlenecks, 'horizon': horizon } z_star, _ = policy_cvx.get_policy(state, **kwargs) np.testing.assert_allclose(z_star, z_star_true, rtol=1e-4)
def get_simple_link_constrained_model(): cost_per_buffer = np.array([3, 1, 3, 1.5, 3]).reshape(-1, 1) param_overrides = dict(alpha1=4.8, mu12=2., mu13=4., mu25=2., mu32=4.5, mu34=1.8, mu35=2., mu45=1., mu5=7., cost_per_buffer=cost_per_buffer) _, env = scenarios.load_scenario('simple_link_constrained_model', 0, param_overrides) _, workload_mat, nu = workload.compute_load_workload_matrix(env, 6) env.workload_mat = workload_mat env.nu = nu return env
def compute_martingale( env: crw.ControlledRandomWalk, data_actions: List[snc_types.ActionSpace], data_states: List[snc_types.StateSpace]) -> snc_types.Matrix: """ Compute the difference between the workload at time t+1 and the workload at time t minus the drift and plus the idling process (w(t+1) - (w(t) - delta + I(t))). The result should be a martingale with zero mean. :param env: the environment to stepped through. :param data_actions: List of actions. :param data_states: List of states. """ # TODO: create a unit test for this function with a specific environment where we are sure that # the martingale has zero mean. workload_tuple = workload.compute_load_workload_matrix(env=env, num_wl_vec=None, load_threshold=None, feasible_tol=1e-10) workload_mat = workload_tuple.workload_mat nu = workload_tuple.nu w_process = workload_mat @ np.array(data_states).T sort_index = np.squeeze( validation_utils.workload_to_physical_resources_index(nu=nu)) constituency_matrix = env.constituency_matrix[sort_index, :] actions = np.array(data_actions).T idling = np.ones( (constituency_matrix.shape[0], actions.shape[1] - 1)) - constituency_matrix @ actions[:, :-1] rho = workload_mat @ env.job_generator.demand_rate delta_mat = np.tile(1. - rho, (1, w_process.shape[1] - 1)) w_tplusone = w_process[:, 1:] w_t = w_process[:, :-1] martingale = w_tplusone - w_t + delta_mat - idling martingale_mean = np.mean(martingale, axis=1) print("Martingale mean: ", martingale_mean) return martingale