class GeneralInfoMatrix(NodeBase): """Used to save matrix, and provide matrix accessor""" # distribution of full from port to port full_on_ports = NodeAttribute("i", slot_num=port_num * port_num) # distribution of full from vessel to port full_on_vessels = NodeAttribute("i", slot_num=vessel_num * port_num) # planed route info for vessels vessel_plans = NodeAttribute("i", slot_num=vessel_num * port_num) def __init__(self): # we cannot create matrix accessor here, since the attributes will be bind after frame setup, self._acc_dict = {} self._acc_dict["full_on_ports"] = MatrixAttributeAccessor( self, "full_on_ports", port_num, port_num) self._acc_dict["full_on_vessels"] = MatrixAttributeAccessor( self, "full_on_vessels", vessel_num, port_num) self._acc_dict["vessel_plans"] = MatrixAttributeAccessor( self, "vessel_plans", vessel_num, port_num) def __getitem__(self, key): if key in self._acc_dict: return self._acc_dict[key] return None
class DataCenter(NodeBase): """Cluster node definition in frame.""" id = NodeAttribute("i2") region_id = NodeAttribute("i2") zone_id = NodeAttribute("i2") total_machine_num = NodeAttribute("i") empty_machine_num = NodeAttribute("i") def __init__(self): self._id: int = 0 self._region_id: int = 0 self._zone_id: int = 0 self._total_machine_num: int = 0 self._name: str = "" self._cluster_list: List[int] = [] def set_init_state(self, id: int, region_id: int, zone_id: int, total_machine_num: int): """Set initialize state, that will be used after frame reset. Args: id (int): Region id. """ self._id = id self._region_id = region_id self._zone_id = zone_id self._total_machine_num = total_machine_num self.reset() def reset(self): """Reset to default value.""" self.id = self._id self.region_id = self._region_id self.zone_id = self._zone_id self.total_machine_num = self._total_machine_num self.empty_machine_num = self.total_machine_num @property def cluster_list(self) -> List[int]: return self._cluster_list @cluster_list.setter def cluster_list(self, cluster_list: List[int]): self._cluster_list = cluster_list @property def name(self) -> str: return self._name @name.setter def name(self, name: str): self._name = name
class Warehouse(NodeBase): inventories = NodeAttribute("i", TOTAL_PRODUCT_CATEGORIES) shortages = NodeAttribute("i", TOTAL_PRODUCT_CATEGORIES) def __init__(self): self._init_inventories = [ 100 * (i + 1) for i in range(TOTAL_PRODUCT_CATEGORIES) ] self._init_shortages = [0] * TOTAL_PRODUCT_CATEGORIES def reset(self): self.inventories[:] = self._init_inventories self.shortages[:] = self._init_shortages
class DummyNode(NodeBase): val = NodeAttribute("i")
class DynamicNode(NodeBase): b1 = NodeAttribute("f") b2 = NodeAttribute("d")
class StaticNode(NodeBase): a1 = NodeAttribute("i", 2) a2 = NodeAttribute("i2") a3 = NodeAttribute("i8")
class Station(NodeBase): """Station node definition in Frame""" bikes = NodeAttribute("i") # statistics features shortage = NodeAttribute("i") trip_requirement = NodeAttribute("i") fulfillment = NodeAttribute("i") capacity = NodeAttribute("i") id = NodeAttribute("i") # additional features weekday = NodeAttribute("i2") temperature = NodeAttribute("i2") # avg temp weather = NodeAttribute("i2") # 0: sunny, 1: rainy, 2: snowy, 3: sleet holiday = NodeAttribute("i2") # 0: holiday, 1: not holiday extra_cost = NodeAttribute("i") transfer_cost = NodeAttribute("i") failed_return = NodeAttribute("i") # min bikes between a frame min_bikes = NodeAttribute("i") def __init__(self): self._init_capacity = 0 # internal use for reset self._init_bikes = 0 # internal use for reset self._id = 0 # original id in data file def set_init_state(self, bikes: int, capacity: int, id: int): """set initialize state, usually for 1st using""" self._init_bikes = bikes self._init_capacity = capacity self._id = id self.reset() def reset(self): """reset to default value""" # when we reset frame, all the value will be set to 0, so we need these lines self.capacity = self._init_capacity self.bikes = self._init_bikes self.min_bikes = self._init_bikes self.id = self._id def _on_bikes_changed(self, value: int): """Update min bikes after bikes changed""" cur_min_bikes = self.min_bikes self.min_bikes = min(value, cur_min_bikes)
class Port(NodeBase): # The capacity of port for stocking containers. capacity = NodeAttribute("i") # Empty container volume on the port. empty = NodeAttribute("i") # Laden container volume on the port. full = NodeAttribute("i") # Empty containers, which are released to the shipper. # After loading cargo, laden containers will return to the port for onboarding. on_shipper = NodeAttribute("i") # Laden containers, which are delivered to the consignee. # After discharging cargo, empty containers will return to the port for reuse. on_consignee = NodeAttribute("i") # Shortage of empty container at current tick. # It happens, when the current empty container inventory of port cannot fulfill order requirements. shortage = NodeAttribute("i") # Accumulated shortage number to the current tick. acc_shortage = NodeAttribute("i") # Order booking number of a port at the current tick. booking = NodeAttribute("i") # Accumulated order booking number of a port to the current tick. acc_booking = NodeAttribute("i") # Fulfilled order number of a port at the current tick. fulfillment = NodeAttribute("i") # Accumulated fulfilled order number of a port to the current tick. acc_fulfillment = NodeAttribute("i") # Cost of transferring container, which also covers loading and discharging cost. transfer_cost = NodeAttribute("f") def __init__(self): self._name = None self._capacity = None self._empty = None @property def idx(self) -> int: """int: Index of this port. """ return self.index @property def name(self) -> str: """str: Name of this port. """ return self._name def set_init_state(self, name: str, capacity: int, empty: int): """Set initialize state for port, business engine will use these values to reset port at the end of each episode (reset). Args: name (str): Port name. capacity (int): Capacity of this port. empty (int): Default empty number on this port. """ self._name = name self._capacity = capacity self._empty = empty self.reset() def reset(self): """Reset port state to initializing. Note: Since frame reset will reset all the nodes' attributes to 0, we need to call set_init_state to store correct initial value. """ self.capacity = self._capacity self.empty = self._empty def _on_shortage_changed(self, value): self._update_fulfilment(value, self.booking) def _on_booking_changed(self, value): self._update_fulfilment(self.shortage, value) def _update_fulfilment(self, shortage: int, booking: int): # Update fulfillment. self.fulfillment = booking - shortage def __str__(self): return f"<Port index={self.index}, name={self._name}, capacity={self.capacity}, empty={self.empty}>"
class Station(NodeBase): """Station node definition in frame.""" bikes = NodeAttribute("i") # statistics features shortage = NodeAttribute("i") trip_requirement = NodeAttribute("i") fulfillment = NodeAttribute("i") capacity = NodeAttribute("i") id = NodeAttribute("i") # additional features weekday = NodeAttribute("i2") # avg temp temperature = NodeAttribute("i2") # 0: sunny, 1: rainy, 2: snowy, 3: sleet weather = NodeAttribute("i2") # 0: holiday, 1: not holiday holiday = NodeAttribute("i2") extra_cost = NodeAttribute("i") transfer_cost = NodeAttribute("i") failed_return = NodeAttribute("i") # min bikes between a frame min_bikes = NodeAttribute("i") def __init__(self): # internal use for reset self._init_capacity = 0 # internal use for reset self._init_bikes = 0 # original id in data file self._id = 0 def set_init_state(self, bikes: int, capacity: int, id: int): """Set initialize state, that will be used after frame reset. Args: bikes (int): Total bikes on this station. capacity (int): How many bikes this station can hold. id (int): Id of this station. """ self._init_bikes = bikes self._init_capacity = capacity self._id = id self.reset() def reset(self): """Reset to default value.""" # when we reset frame, all the value will be set to 0, so we need these lines self.capacity = self._init_capacity self.bikes = self._init_bikes self.min_bikes = self._init_bikes self.id = self._id def _on_bikes_changed(self, value: int): """Update min bikes after bikes changed""" cur_min_bikes = self.min_bikes self.min_bikes = min(value, cur_min_bikes)
class TestNode(NodeBase): a1 = NodeAttribute("i", 2, is_const=True)
class DynamicNode(NodeBase): b1 = NodeAttribute(AttributeType.Float) b2 = NodeAttribute(AttributeType.Double)
class StaticNode(NodeBase): a1 = NodeAttribute("i", 2) a2 = NodeAttribute(AttributeType.Short) a3 = NodeAttribute(AttributeType.Long)
class TestNode2(NodeBase): b = NodeAttribute("i", 20)
class TestNode1(NodeBase): a = NodeAttribute("i") b = NodeAttribute("i") c = NodeAttribute("i") d = NodeAttribute("i") e = NodeAttribute("i", 16)
class Port(NodeBase): # The capacity of port for stocking containers. capacity = NodeAttribute("i") # Empty container volume on the port. empty = NodeAttribute("i") # Laden container volume on the port. full = NodeAttribute("i") # Empty containers, which are released to the shipper. # After loading cargo, laden containers will return to the port for onboarding. on_shipper = NodeAttribute("i") # Laden containers, which are delivered to the consignee. # After discharging cargo, empty containers will return to the port for reuse. on_consignee = NodeAttribute("i") # Shortage of empty container at current tick. # It happens, when the current empty container inventory of port cannot fulfill order requirements. shortage = NodeAttribute("i") # Accumulated shortage number to the current tick. acc_shortage = NodeAttribute("i") # Order booking number of a port at the current tick. booking = NodeAttribute("i") # Accumulated order booking number of a port to the current tick. acc_booking = NodeAttribute("i") # Fulfilled order number of a port at the current tick. fulfillment = NodeAttribute("i") # Accumulated fulfilled order number of a port to the current tick. acc_fulfillment = NodeAttribute("i") # Cost of transferring container, which also covers loading and discharging cost. transfer_cost = NodeAttribute("f") def __init__(self): self._name = None self._capacity = None self._empty = None @property def idx(self) -> int: """ Index of this port """ return self.index @property def name(self) -> str: """ Name of this port """ return self._name def set_init_state(self, name: str, capacity: int, empty: int): self._name = name self._capacity = capacity self._empty = empty self.reset() def reset(self): self.capacity = self._capacity self.empty = self._empty def _on_shortage_changed(self, value): self._update_fulfilment(value, self.booking) def _on_booking_changed(self, value): self._update_fulfilment(self.shortage, value) def _update_fulfilment(self, shortage: int, booking: int): # Update fulfillment. self.fulfillment = booking - shortage def __str__(self): return f"<Port index={self.index}, name={self._name}, capacity={self.capacity}, empty={self.empty}>"
class TestNode(NodeBase): a1 = NodeAttribute("i", 1, is_list=True) a2 = NodeAttribute("i", 2, is_const=True) a3 = NodeAttribute("i")
class TestNode(NodeBase): a1 = NodeAttribute("i", 1, is_list=True)
class PhysicalMachine(NodeBase): """Physical machine node definition in frame.""" # Initial parameters. id = NodeAttribute("i") cpu_cores_capacity = NodeAttribute("i2") memory_capacity = NodeAttribute("i2") # Statistical features. cpu_cores_allocated = NodeAttribute("i2") memory_allocated = NodeAttribute("i2") cpu_utilization = NodeAttribute("f") energy_consumption = NodeAttribute("f") def __init__(self): """Internal use for reset.""" self._id = 0 self._init_cpu_cores_capacity = 0 self._init_memory_capacity = 0 # PM resource. self._live_vms: Set[int] = set() def update_cpu_utilization(self, vm: VirtualMachine = None, cpu_utilization: float = None): if vm is None and cpu_utilization is None: raise Exception( f"Wrong calling method {self.update_cpu_utilization.__name__}") if vm is not None: cpu_utilization = ( (self.cpu_cores_capacity * self.cpu_utilization + vm.cpu_cores_requirement * vm.cpu_utilization) / self.cpu_cores_capacity) self.cpu_utilization = round(max(0, cpu_utilization), 2) def set_init_state(self, id: int, cpu_cores_capacity: int, memory_capacity: int): """Set initialize state, that will be used after frame reset. Args: id (int): PM id, from 0 to N. N means the amount of PM, which can be set in config. cpu_cores_capacity (int): The capacity of cores of the PM, which can be set in config. memory_capacity (int): The capacity of memory of the PM, which can be set in config. """ self._id = id self._init_cpu_cores_capacity = cpu_cores_capacity self._init_memory_capacity = memory_capacity self.reset() def reset(self): """Reset to default value.""" # When we reset frame, all the value will be set to 0, so we need these lines. self.id = self._id self.cpu_cores_capacity = self._init_cpu_cores_capacity self.memory_capacity = self._init_memory_capacity self._live_vms.clear() self.cpu_cores_allocated = 0 self.memory_allocated = 0 self.cpu_utilization = 0.0 self.energy_consumption = 0.0 @property def live_vms(self) -> Set[int]: return self._live_vms def allocate_vms(self, vm_ids: List[int]): for vm_id in vm_ids: self._live_vms.add(vm_id) def deallocate_vms(self, vm_ids: List[int]): for vm_id in vm_ids: self._live_vms.remove(vm_id)
class TestNode(NodeBase): a1 = NodeAttribute("i", batch_number) a2 = NodeAttribute("i")
class Matrices(NodeBase): trips_adj = NodeAttribute("i", station_num * station_num) def reset(self): pass
class PhysicalMachine(NodeBase): """Physical machine node definition in frame.""" # Initial parameters. id = NodeAttribute("i") cpu_cores_capacity = NodeAttribute("i2") memory_capacity = NodeAttribute("i2") pm_type = NodeAttribute("i2") # Statistical features. cpu_cores_allocated = NodeAttribute("i2") memory_allocated = NodeAttribute("i2") cpu_utilization = NodeAttribute("f") energy_consumption = NodeAttribute("f") # PM type: non-oversubscribable is -1, empty: 0, oversubscribable is 1. oversubscribable = NodeAttribute("i2") region_id = NodeAttribute("i2") zone_id = NodeAttribute("i2") data_center_id = NodeAttribute("i2") cluster_id = NodeAttribute("i2") rack_id = NodeAttribute("i") def __init__(self): """Internal use for reset.""" self._id = 0 self._init_cpu_cores_capacity = 0 self._init_memory_capacity = 0 self._init_pm_type = 0 self._init_pm_state = 0 self._region_id = 0 self._zone_id = 0 self._data_center_id = 0 self._cluster_id = 0 self._rack_id = 0 # PM resource. self._live_vms: Set[int] = set() def update_cpu_utilization(self, vm: VirtualMachine = None, cpu_utilization: float = None): if vm is None and cpu_utilization is None: raise Exception( f"Wrong calling method {self.update_cpu_utilization.__name__}") if vm is not None: cpu_utilization = ( (self.cpu_cores_capacity * self.cpu_utilization + vm.cpu_cores_requirement * vm.cpu_utilization) / self.cpu_cores_capacity) self.cpu_utilization = round(max(0, cpu_utilization), 2) def set_init_state( self, id: int, cpu_cores_capacity: int, memory_capacity: int, pm_type: int, region_id: int, zone_id: int, data_center_id: int, cluster_id: int, rack_id: int, oversubscribable: PmState = 0, idle_energy_consumption: float = 0, ): """Set initialize state, that will be used after frame reset. Args: id (int): PM id, from 0 to N. N means the amount of PM, which can be set in config. cpu_cores_capacity (int): The capacity of cores of the PM, which can be set in config. memory_capacity (int): The capacity of memory of the PM, which can be set in config. pm_type (int): The type of the PM. region_id (int): The region's id where the PM locates in. zone_id (int): The zone's id where the PM locates in. data_center_id (int): The data center's id where the PM locates in. cluster_id (int): The cluster's id where the PM locates in. rack_id (int): The rack's id where the PM locates in. oversubscribable (int): The state of the PM: - non-oversubscribable: -1. - empty: 0. - oversubscribable: 1. """ self._id = id self._init_cpu_cores_capacity = cpu_cores_capacity self._init_memory_capacity = memory_capacity self._init_pm_type = pm_type self._init_pm_state = oversubscribable self._region_id = region_id self._zone_id = zone_id self._data_center_id = data_center_id self._cluster_id = cluster_id self._rack_id = rack_id self._idle_energy_consumption = idle_energy_consumption self.reset() def reset(self): """Reset to default value. Note: Since frame reset will reset all the node's attributes to 0, we need to call set_init_state to store correct initial value. """ # When we reset frame, all the value will be set to 0, so we need these lines. self.id = self._id self.cpu_cores_capacity = self._init_cpu_cores_capacity self.memory_capacity = self._init_memory_capacity self.pm_type = self._init_pm_type self.oversubscribable = self._init_pm_state self.region_id = self._region_id self.zone_id = self._zone_id self.data_center_id = self._data_center_id self.cluster_id = self._cluster_id self.rack_id = self._rack_id self._live_vms.clear() self.cpu_cores_allocated = 0 self.memory_allocated = 0 self.cpu_utilization = 0.0 self.energy_consumption = self._idle_energy_consumption @property def live_vms(self) -> Set[int]: return self._live_vms def allocate_vms(self, vm_ids: List[int]): for vm_id in vm_ids: self._live_vms.add(vm_id) def deallocate_vms(self, vm_ids: List[int]): for vm_id in vm_ids: self._live_vms.remove(vm_id)
class Vessel(NodeBase): # The capacity of vessel for transferring containers. capacity = NodeAttribute("i") # Empty container volume on the vessel. empty = NodeAttribute("i") # Laden container volume on the vessel. full = NodeAttribute("i") # Remaining space of the vessel. remaining_space = NodeAttribute("i") # Discharged empty container number for loading laden containers. early_discharge = NodeAttribute("i") # Which route current vessel belongs to. route_idx = NodeAttribute("i") # Stop port index in route, it is used to identify where is current vessel. # last_loc_idx == next_loc_idx means vessel parking at a port. last_loc_idx = NodeAttribute("i") next_loc_idx = NodeAttribute("i") past_stop_list = NodeAttribute("i", stop_nums[0]) past_stop_tick_list = NodeAttribute("i", stop_nums[0]) future_stop_list = NodeAttribute("i", stop_nums[1]) future_stop_tick_list = NodeAttribute("i", stop_nums[1]) def __init__(self): self._name = None self._capacity = None self._total_space = None self._container_volume = None self._route_idx = None self._empty = None @property def name(self) -> str: """str: Name of vessel (from config). """ return self._name @property def idx(self) -> int: """int: Index of vessel. """ return self.index def set_init_state(self, name: str, container_volume: float, capacity: int, route_idx: int, empty: int): """Initialize vessel info that will be used after frame reset. Args: name (str): Name of vessel. container_volume (float): Volume of each container. capacity (int): Capacity of this vessel. route_idx (int): The index of the route that this vessel belongs to. empty (int): Initial empty number of this vessel. """ self._name = name self._container_volume = container_volume self._total_space = floor(capacity / container_volume) self._capacity = capacity self._route_idx = route_idx self._empty = empty self.reset() def reset(self): """Reset states of vessel.""" self.capacity = self._capacity self.route_idx = self._route_idx self.empty = self._empty def set_stop_list(self, past_stop_list: list, future_stop_list: list): """Set the future stops (configured in config) when the vessel arrive at a port. Args: past_stop_list (list): List of past stop list tuple. future_stop_list (list): List of future stop list tuple. """ # update past and future stop info features = [(past_stop_list, self.past_stop_list, self.past_stop_tick_list), (future_stop_list, self.future_stop_list, self.future_stop_tick_list)] for feature in features: for i, stop in enumerate(feature[0]): tick = stop.arrive_tick if stop is not None else -1 port_idx = stop.port_idx if stop is not None else -1 feature[1][i] = port_idx feature[2][i] = tick def _on_empty_changed(self, value): self._update_remaining_space() def _on_full_changed(self, value): self._update_remaining_space() def _update_remaining_space(self): self.remaining_space = self._total_space - self.full - self.empty def __str__(self): return f"<Vessel Index={self.index}, capacity={self.capacity}, empty={self.empty}, full={self.full}>"
class Cluster(NodeBase): """Cluster node definition in frame.""" id = NodeAttribute("i2") region_id = NodeAttribute("i2") zone_id = NodeAttribute("i2") data_center_id = NodeAttribute("i2") # Total number of machines in the cluster. total_machine_num = NodeAttribute("i") # The number of empty machines in this cluster. A empty machine means that its allocated CPU cores are 0. empty_machine_num = NodeAttribute("i") def __init__(self): self._id: int = 0 self._region_id: int = 0 self._zone_id: int = 0 self._data_center_id: int = 0 self._total_machine_num: int = 0 self._cluster_type: str = "" self._rack_list: List[int] = [] def set_init_state(self, id: int, region_id: int, zone_id: int, data_center_id: int, total_machine_num: int): """Set initialize state, that will be used after frame reset. Args: id (int): Region id. """ self._id = id self._region_id = region_id self._zone_id = zone_id self._data_center_id = data_center_id self._total_machine_num = total_machine_num self.reset() def reset(self): """Reset to default value.""" self.id = self._id self.region_id = self._region_id self.zone_id = self._zone_id self.data_center_id = self._data_center_id self.total_machine_num = self._total_machine_num self.empty_machine_num = self.total_machine_num @property def rack_list(self) -> List[int]: return self._rack_list @rack_list.setter def rack_list(self, rack_list: List[int]): self._rack_list = rack_list @property def cluster_type(self) -> str: return self._cluster_type @cluster_type.setter def cluster_type(self, cluster_type: str): self._cluster_type = cluster_type