diff --git a/examples/config/natural_gas_proc_ranges.yaml b/examples/config/natural_gas_proc_ranges.yaml new file mode 100644 index 0000000..0acd686 --- /dev/null +++ b/examples/config/natural_gas_proc_ranges.yaml @@ -0,0 +1,58 @@ +# to specify which action should be used +defined_actions: + MSTR-000: + - Temperature + - Pressure + - Enthalpy + - Entropy + - MassFlow + - MolarFlow + - VolumetricFlow + Pre-Chiller: + - OutletTemperature: + range: [290, 305] + units: K + - HeatAdded + - Efficiency + Chiller-1: + - OutletTemperature + - HeatAdded + - Efficiency + HP Separator: + - SeparationPressure + Chiller-2: + - OutletTemperature + - HeatAdded + - Efficiency + Expander-1: + - RotationSpeed + - OutletPressure + - AdiabaticEfficiency + - AdiabaticHead + LP Separator: + - SeparationPressure + Chiller-3: + - OutletTemperature + - HeatAdded + - Efficiency + Condenser: + - OutletTemperature + - HeatAdded + - Efficiency + HEAT-052: + - OutletTemperature + - HeatAdded + - Efficiency + Chiller-4: + - OutletTemperature + - HeatAdded + - Efficiency + Compressor-1: + - RotationSpeed + - AdiabaticEfficiency + - AdiabaticHead + Compressor: + - RotationSpeed + - OutletPressure + - AdiabaticEfficiency + - AdiabaticHead diff --git a/examples/data_generator.py b/examples/data_generator.py index b7cb24b..b8c9bc9 100644 --- a/examples/data_generator.py +++ b/examples/data_generator.py @@ -5,7 +5,8 @@ from tqdm import tqdm import pro_gym from pro_gym.utils.utils import get_action_space, generate_random_actions, \ - create_csv_headers, generate_row_data, generate_fake_time + generate_batch_actions, \ + create_csv_headers, generate_row_data, generate_fake_time # arguments parser = argparse.ArgumentParser() @@ -15,6 +16,7 @@ parser.add_argument("--action-config", type=str, default="config/natural_gas_proc.yaml", help="path to define the action space") parser.add_argument("--random-range", type=float, default=0.2, help="random range 20 percent +-") parser.add_argument("--seed", type=int, default=123, help="random seed") +parser.add_argument("--random-method", type=str, default="uniform", help="random number generator method") if __name__ == "__main__": # get the arguments @@ -42,12 +44,24 @@ row_data = generate_row_data(init_vals, date_times_str[0]) # start to generate data writer.writerow(row_data) - for iter in tqdm(range(args.steps)): - # generate random actions - random_actions = generate_random_actions(action_space, init_vals, args.random_range) - # execute the random actions - obs_next, _, _, _ = env.step(random_actions) - row_data = generate_row_data(obs_next, date_times_str[iter+1]) - writer.writerow(row_data) - env.reset() + if args.random_method == "uniform": + for iter in tqdm(range(args.steps)): + # generate random actions + random_actions = generate_random_actions(action_space, init_vals, args.random_range) + # execute the random actions + obs_next, _, _, _ = env.step(random_actions) + row_data = generate_row_data(obs_next, date_times_str[iter+1]) + writer.writerow(row_data) + env.reset() + elif args.random_method == "lhs" or args.random_method == "sobol": + # generate batch actions + batch_actions = generate_batch_actions(action_space, init_vals, number_actions = args.steps, method = args.random_method) + # execute the individual random actions in series + for iter in tqdm(range(args.steps)): + obs_next, _, _, _ = env.step(batch_actions[iter]) + row_data = generate_row_data(obs_next, date_times_str[iter+1]) + writer.writerow(row_data) + env.reset() + else: + raise NotImplementedError("The method {} is not implemented".format(args.random_method)) csv_file.close() \ No newline at end of file diff --git a/pro_gym/utils/utils.py b/pro_gym/utils/utils.py index 1549ed2..f3bce1c 100644 --- a/pro_gym/utils/utils.py +++ b/pro_gym/utils/utils.py @@ -2,21 +2,73 @@ import yaml import numpy as np from datetime import datetime, timedelta +from scipy.stats import qmc -def generate_random_actions(action_space, init_val, random_range=0.2): - actions = {} +def generate_random_actions(action_space, init_val, random_range = 0.2, random_numbers = []): """ this function is used to generate random actions of the environment """ + + #determine number of available specs + number_specs = sum(len(unit) for unit in action_space.values()) + + # if random numbers are not provided, generate them + if len(random_numbers) == 0: + random_numbers = np.random.uniform(0, 1, number_specs) + else: #check if correct size and correct range [0,1] + assert len(random_numbers) == number_specs, "The number of random numbers provided does not match the number of specs" + assert all(0 <= x <= 1 for x in random_numbers), "The random numbers provided are not in the range [0,1]" + + actions = {} for unit_name in action_space.keys(): actions[unit_name] = {} for avail_spec in action_space[unit_name]: - init_val_ = init_val[unit_name][avail_spec] - upper = init_val_ * (1 + random_range) - lower = init_val_ * (1 - random_range) - sample_val = np.random.uniform(upper, lower) - # assign values - actions[unit_name][avail_spec] = sample_val + """ determine if avail_spec has a specific range """ + if isinstance(avail_spec, dict): + avail_spec_name, avail_spec_params = next(iter(avail_spec.items())) + if 'range' in avail_spec_params: + upper = avail_spec_params["range"][1] + lower = avail_spec_params["range"][0] + else: + init_val_ = init_val[unit_name][avail_spec_name] + upper = init_val_ * (1 + random_range) + lower = init_val_ * (1 - random_range) + actions[unit_name][avail_spec_name] = sample_random_value(random_numbers, upper, lower) + else: + init_val_ = init_val[unit_name][avail_spec] + upper = init_val_ * (1 + random_range) + lower = init_val_ * (1 - random_range) + actions[unit_name][avail_spec] = sample_random_value(random_numbers, upper, lower) return actions +def generate_batch_actions(action_space, init_val, random_range = 0.2, number_actions = 1, method = "uniform"): + """ this function is used to generate a batch of random actions """ + + #determine number of available specs + number_specs = sum(len(unit) for unit in action_space.values()) + + #generate random numbers with selected method + if method == "uniform": + random_numbers = np.random.uniform(0, 1, number_actions*number_specs) + elif method == "sobol": + random_numbers = qmc.Sobol(d = number_specs, scramble=True).random(n = number_actions).flatten() + elif method == "lhs": + random_numbers = qmc.LatinHypercube(d = number_specs, scramble=True).random(n = number_actions).flatten() + else: + raise NotImplementedError("The method {} is not implemented".format(method)) + + batch_actions = [] + for i in range(number_actions): + actions = generate_random_actions(action_space, init_val, random_range, random_numbers[i*number_specs:(i+1)*number_specs]) + batch_actions.append(actions) + return batch_actions + +def sample_random_value(random_numbers, upper, lower, delete = True): + """ this function generates a random value within the range of upper and lower """ + """ deletes the random number used if delete is True """ + sample_val = random_numbers[0] * (upper - lower) + lower + if delete: + random_numbers = np.delete(random_numbers, 0) + return sample_val + def get_action_space(yaml_path): """ this function is used to get the pre-defined action space in yaml file """ with open(yaml_path, "r") as file: