diff --git a/diploma_thesis/agents/__init__.py b/diploma_thesis/agents/__init__.py index 5547592..4305fd5 100644 --- a/diploma_thesis/agents/__init__.py +++ b/diploma_thesis/agents/__init__.py @@ -1,4 +1,4 @@ -from .machine import * -from .workcenter import * from .utils.phase import * +from .workcenter import WorkCenter, WorkCenterInput, from_cli as work_center_from_cli +from .machine import Machine, MachineInput, from_cli as machine_from_cli diff --git a/diploma_thesis/agents/base/agent.py b/diploma_thesis/agents/base/agent.py index 33ee342..3a658c2 100644 --- a/diploma_thesis/agents/base/agent.py +++ b/diploma_thesis/agents/base/agent.py @@ -4,36 +4,26 @@ import torch from agents.utils import Phase, EvaluationPhase -from .encoder import Encoder as STEncoder -from .model import Model as Brain +from .encoder import Encoder as StateEncoder, Input, State +from .model import Model, Action, Result -StateEncoder = TypeVar('StateEncoder', bound=STEncoder) -Model = TypeVar('Model', bound=Brain) - -class Agent(Generic[StateEncoder, Model], metaclass=ABCMeta): +class Agent(metaclass=ABCMeta): def __init__(self, - state_encoder: StateEncoder.State, - model: Model, + model: Model[Input, State, Action, Result], + state_encoder: StateEncoder[Input, State], memory): self.state_encoder = state_encoder self.model = model self.memory = memory self.phase = EvaluationPhase() - # TODO: Check if it works!!! - - assert isinstance(self.state_encoder.Input, self.model.Input), \ - f"State encoder input type {self.state_encoder.Input} does not match model input type {self.model.Input}" - assert isinstance(self.state_encoder.State, self.model.State), \ - f"State encoder state type {self.state_encoder.State} does not match model state type {self.model.State}" - def update(self, phase: Phase): self.phase = phase - @abstractmethod @property + @abstractmethod def is_trainable(self): pass @@ -42,18 +32,16 @@ def train_step(self): pass def store(self, - state: StateEncoder.State, - action: Model.Action, - next_state: StateEncoder.State, + state: State, + action: Action, + next_state: State, reward: torch.FloatTensor): pass - def schedule(self, state: StateEncoder.Input) -> Model.Record: - state = self.encode_state(state) - - return self.model(state, state) - - def encode_state(self, state: StateEncoder.Input) -> StateEncoder.State: - return self.state_encoder.encode(state) + def schedule(self, parameters: Input) -> Model.Record: + state = self.encode_state(parameters) + return self.model(state, parameters) + def encode_state(self, parameters: Input) -> State: + return self.state_encoder.encode(parameters) diff --git a/diploma_thesis/agents/cli.py b/diploma_thesis/agents/cli.py deleted file mode 100644 index e69de29..0000000 diff --git a/diploma_thesis/agents/machine/__init__.py b/diploma_thesis/agents/machine/__init__.py index 84e663c..77abd52 100644 --- a/diploma_thesis/agents/machine/__init__.py +++ b/diploma_thesis/agents/machine/__init__.py @@ -1,3 +1,15 @@ from .utils import Input as MachineInput -from .machine import Machine \ No newline at end of file +from .machine import Machine +from .static import StaticMachine + + +key_to_class = { + "static": StaticMachine +} + + +def from_cli(parameters) -> Machine: + cls = key_to_class[parameters['kind']] + + return cls.from_cli(parameters['parameters']) diff --git a/diploma_thesis/agents/machine/machine.py b/diploma_thesis/agents/machine/machine.py index b800117..0ec4298 100644 --- a/diploma_thesis/agents/machine/machine.py +++ b/diploma_thesis/agents/machine/machine.py @@ -1,15 +1,9 @@ from abc import ABCMeta -from typing import TypeVar from agents.base.agent import Agent -from .model import MachineModel -from .state import StateEncoder -MachineStateEncoder = TypeVar('MachineStateEncoder', bound=StateEncoder) -Model = TypeVar('Model', bound=MachineModel) - -class Machine(Agent[MachineStateEncoder, Model], metaclass=ABCMeta): +class Machine(Agent, metaclass=ABCMeta): pass diff --git a/diploma_thesis/agents/machine/model/__init__.py b/diploma_thesis/agents/machine/model/__init__.py index 6c5f28a..555871a 100644 --- a/diploma_thesis/agents/machine/model/__init__.py +++ b/diploma_thesis/agents/machine/model/__init__.py @@ -2,3 +2,13 @@ from .model import MachineModel from .static import StaticModel as StaticMachineModel from .rule import SchedulingRule + +key_to_class = { + "static": StaticMachineModel +} + + +def from_cli(parameters) -> MachineModel: + cls = key_to_class[parameters['kind']] + + return cls.from_cli(parameters['parameters']) \ No newline at end of file diff --git a/diploma_thesis/agents/machine/model/static.py b/diploma_thesis/agents/machine/model/static.py index f9dd334..e1c336d 100644 --- a/diploma_thesis/agents/machine/model/static.py +++ b/diploma_thesis/agents/machine/model/static.py @@ -1,7 +1,8 @@ from .model import MachineModel from agents.machine.state import PlainEncoder -from .rule import SchedulingRule +from .rule import SchedulingRule, ALL_SCHEDULING_RULES +from typing import Dict class StaticModel(MachineModel[PlainEncoder.State, None]): @@ -18,3 +19,9 @@ def __call__(self, state: State, parameters: MachineModel.Input) -> MachineModel action=None ) + @staticmethod + def from_cli(parameters: Dict): + rule = parameters['rule'] + rule = ALL_SCHEDULING_RULES[rule] + + return StaticModel(rule()) diff --git a/diploma_thesis/agents/machine/state/__init__.py b/diploma_thesis/agents/machine/state/__init__.py index f3f466f..6d888b1 100644 --- a/diploma_thesis/agents/machine/state/__init__.py +++ b/diploma_thesis/agents/machine/state/__init__.py @@ -1,3 +1,14 @@ from .encoder import StateEncoder from .plain import PlainEncoder + + +key_to_class = { + "plain": PlainEncoder +} + + +def from_cli(parameters) -> StateEncoder: + cls = key_to_class[parameters['kind']] + + return cls.from_cli(parameters.get('parameters', {})) diff --git a/diploma_thesis/agents/machine/state/plain.py b/diploma_thesis/agents/machine/state/plain.py index 50f6839..5d665e4 100644 --- a/diploma_thesis/agents/machine/state/plain.py +++ b/diploma_thesis/agents/machine/state/plain.py @@ -1,6 +1,7 @@ from .encoder import StateEncoder from dataclasses import dataclass +from typing import Dict class PlainEncoder(StateEncoder): @@ -11,3 +12,7 @@ class State: def encode(self, parameters: StateEncoder.Input) -> State: return self.State() + + @staticmethod + def from_cli(parameters: Dict): + return PlainEncoder() diff --git a/diploma_thesis/agents/machine/static.py b/diploma_thesis/agents/machine/static.py index 578644f..b45cd3b 100644 --- a/diploma_thesis/agents/machine/static.py +++ b/diploma_thesis/agents/machine/static.py @@ -1,14 +1,15 @@ from .machine import Machine -from .model import StaticMachineModel, SchedulingRule -from .state import PlainEncoder +from .model import MachineModel, from_cli as model_from_cli +from .state import StateEncoder, from_cli as state_encoder_from_cli +from typing import Dict -class StaticMachine(Machine[StaticMachineModel, PlainEncoder]): +class StaticMachine(Machine): - def __init__(self, rule: SchedulingRule): - super().__init__(model=StaticMachineModel(rule), state_encoder=PlainEncoder(), memory=None) + def __init__(self, model: MachineModel, state_encoder: StateEncoder): + super().__init__(model=model, state_encoder=state_encoder, memory=None) @property def is_trainable(self): @@ -16,3 +17,10 @@ def is_trainable(self): def train_step(self): pass + + @staticmethod + def from_cli(parameters: Dict): + model = model_from_cli(parameters['model']) + encoder = state_encoder_from_cli(parameters['encoder']) + + return StaticMachine(model=model, state_encoder=encoder) \ No newline at end of file diff --git a/diploma_thesis/agents/workcenter/__init__.py b/diploma_thesis/agents/workcenter/__init__.py index 7862d0a..b2277ec 100644 --- a/diploma_thesis/agents/workcenter/__init__.py +++ b/diploma_thesis/agents/workcenter/__init__.py @@ -1,3 +1,15 @@ from .utils import Input as WorkCenterInput -from .work_center import WorkCenter \ No newline at end of file +from .work_center import WorkCenter +from .static import StaticWorkCenter + + +key_to_class = { + "static": StaticWorkCenter +} + + +def from_cli(parameters) -> WorkCenter: + cls = key_to_class[parameters['kind']] + + return cls.from_cli(parameters['parameters']) diff --git a/diploma_thesis/agents/workcenter/model/__init__.py b/diploma_thesis/agents/workcenter/model/__init__.py index 1a5aff4..26beb10 100644 --- a/diploma_thesis/agents/workcenter/model/__init__.py +++ b/diploma_thesis/agents/workcenter/model/__init__.py @@ -1,3 +1,13 @@ from .model import WorkCenterModel from .static import StaticModel as StaticWorkCenterModel from .rule import RoutingRule + +key_to_class = { + "static": StaticWorkCenterModel +} + + +def from_cli(parameters) -> WorkCenterModel: + cls = key_to_class[parameters['kind']] + + return cls.from_cli(parameters['parameters']) diff --git a/diploma_thesis/agents/workcenter/model/rule/ct.py b/diploma_thesis/agents/workcenter/model/rule/ct.py index 2375b21..17b7cf9 100644 --- a/diploma_thesis/agents/workcenter/model/rule/ct.py +++ b/diploma_thesis/agents/workcenter/model/rule/ct.py @@ -1,5 +1,5 @@ -from routing_rule import * +from .routing_rule import * class CTRoutingRule(RoutingRule): diff --git a/diploma_thesis/agents/workcenter/model/rule/ea.py b/diploma_thesis/agents/workcenter/model/rule/ea.py index ba4f866..da52a9e 100644 --- a/diploma_thesis/agents/workcenter/model/rule/ea.py +++ b/diploma_thesis/agents/workcenter/model/rule/ea.py @@ -1,4 +1,4 @@ -from routing_rule import * +from .routing_rule import * class EARoutingRule(RoutingRule): diff --git a/diploma_thesis/agents/workcenter/model/rule/et.py b/diploma_thesis/agents/workcenter/model/rule/et.py index c2eac98..496871b 100644 --- a/diploma_thesis/agents/workcenter/model/rule/et.py +++ b/diploma_thesis/agents/workcenter/model/rule/et.py @@ -1,4 +1,4 @@ -from routing_rule import * +from .routing_rule import * class ETRoutingRule(RoutingRule): diff --git a/diploma_thesis/agents/workcenter/model/rule/random.py b/diploma_thesis/agents/workcenter/model/rule/random.py index 4338a6e..e453faf 100644 --- a/diploma_thesis/agents/workcenter/model/rule/random.py +++ b/diploma_thesis/agents/workcenter/model/rule/random.py @@ -1,4 +1,4 @@ -from routing_rule import * +from .routing_rule import * class RandomRoutingRule(RoutingRule): diff --git a/diploma_thesis/agents/workcenter/model/rule/sq.py b/diploma_thesis/agents/workcenter/model/rule/sq.py index 4bd333b..fc7fad8 100644 --- a/diploma_thesis/agents/workcenter/model/rule/sq.py +++ b/diploma_thesis/agents/workcenter/model/rule/sq.py @@ -1,4 +1,4 @@ -from routing_rule import * +from .routing_rule import * class SQRoutingRule(RoutingRule): diff --git a/diploma_thesis/agents/workcenter/model/rule/tt.py b/diploma_thesis/agents/workcenter/model/rule/tt.py index 00077df..98f7323 100644 --- a/diploma_thesis/agents/workcenter/model/rule/tt.py +++ b/diploma_thesis/agents/workcenter/model/rule/tt.py @@ -1,4 +1,4 @@ -from routing_rule import * +from .routing_rule import * class TTRoutingRule(RoutingRule): diff --git a/diploma_thesis/agents/workcenter/model/rule/ut.py b/diploma_thesis/agents/workcenter/model/rule/ut.py index 59ee336..1a5ba82 100644 --- a/diploma_thesis/agents/workcenter/model/rule/ut.py +++ b/diploma_thesis/agents/workcenter/model/rule/ut.py @@ -1,4 +1,4 @@ -from routing_rule import * +from .routing_rule import * class UTRoutingRule(RoutingRule): diff --git a/diploma_thesis/agents/workcenter/model/static.py b/diploma_thesis/agents/workcenter/model/static.py index 5d39be0..7a4640d 100644 --- a/diploma_thesis/agents/workcenter/model/static.py +++ b/diploma_thesis/agents/workcenter/model/static.py @@ -1,7 +1,8 @@ from .model import WorkCenterModel from agents.machine.state import PlainEncoder -from .rule import RoutingRule +from .rule import RoutingRule, ALL_ROUTING_RULES +from typing import Dict class StaticModel(WorkCenterModel[PlainEncoder.State, None]): @@ -18,3 +19,9 @@ def __call__(self, state: State, parameters: WorkCenterModel.Input) -> WorkCente action=None ) + @staticmethod + def from_cli(parameters: Dict): + rule = parameters['rule'] + rule = ALL_ROUTING_RULES[rule] + + return StaticModel(rule()) diff --git a/diploma_thesis/agents/workcenter/state/__init__.py b/diploma_thesis/agents/workcenter/state/__init__.py index 8aca975..59f8077 100644 --- a/diploma_thesis/agents/workcenter/state/__init__.py +++ b/diploma_thesis/agents/workcenter/state/__init__.py @@ -1,2 +1,13 @@ -from .plain import PlainEncoder from .encoder import StateEncoder +from .plain import PlainEncoder + + +key_to_class = { + "plain": PlainEncoder +} + + +def from_cli(parameters) -> StateEncoder: + cls = key_to_class[parameters['kind']] + + return cls.from_cli(parameters.get('parameters', {})) diff --git a/diploma_thesis/agents/workcenter/state/plain.py b/diploma_thesis/agents/workcenter/state/plain.py index 50f6839..5d665e4 100644 --- a/diploma_thesis/agents/workcenter/state/plain.py +++ b/diploma_thesis/agents/workcenter/state/plain.py @@ -1,6 +1,7 @@ from .encoder import StateEncoder from dataclasses import dataclass +from typing import Dict class PlainEncoder(StateEncoder): @@ -11,3 +12,7 @@ class State: def encode(self, parameters: StateEncoder.Input) -> State: return self.State() + + @staticmethod + def from_cli(parameters: Dict): + return PlainEncoder() diff --git a/diploma_thesis/agents/workcenter/static.py b/diploma_thesis/agents/workcenter/static.py index c81940b..fe0c25e 100644 --- a/diploma_thesis/agents/workcenter/static.py +++ b/diploma_thesis/agents/workcenter/static.py @@ -1,14 +1,15 @@ from .work_center import WorkCenter -from .model import StaticWorkCenterModel, RoutingRule -from .state import PlainEncoder +from .model import StaticWorkCenterModel, from_cli as model_from_cli +from .state import StateEncoder, from_cli as state_encoder_from_cli +from typing import Dict -class StaticMachine(WorkCenter[StaticWorkCenterModel, PlainEncoder]): +class StaticWorkCenter(WorkCenter): - def __init__(self, rule: RoutingRule): - super().__init__(model=StaticWorkCenterModel(rule), state_encoder=PlainEncoder(), memory=None) + def __init__(self, model: StaticWorkCenterModel, state_encoder: StateEncoder): + super().__init__(model=model, state_encoder=state_encoder, memory=None) @property def is_trainable(self): @@ -16,3 +17,11 @@ def is_trainable(self): def train_step(self): pass + + @staticmethod + def from_cli(parameters: Dict): + model = model_from_cli(parameters['model']) + encoder = state_encoder_from_cli(parameters['encoder']) + + return StaticWorkCenter(model=model, state_encoder=encoder) + diff --git a/diploma_thesis/agents/workcenter/work_center.py b/diploma_thesis/agents/workcenter/work_center.py index 9caccb6..c6b2171 100644 --- a/diploma_thesis/agents/workcenter/work_center.py +++ b/diploma_thesis/agents/workcenter/work_center.py @@ -1,15 +1,8 @@ from abc import ABCMeta -from typing import TypeVar from agents.base.agent import Agent -from .model import WorkCenterModel -from .state import StateEncoder -WorkCenterStateEncoder = TypeVar('WorkCenterStateEncoder', bound=StateEncoder) -Model = TypeVar('Model', bound=WorkCenterModel) - - -class WorkCenter(Agent[WorkCenterStateEncoder, Model], metaclass=ABCMeta): +class WorkCenter(Agent, metaclass=ABCMeta): pass diff --git a/diploma_thesis/cli.py b/diploma_thesis/cli.py index c0d5934..f70f7e2 100644 --- a/diploma_thesis/cli.py +++ b/diploma_thesis/cli.py @@ -4,14 +4,16 @@ import yaml -from workflows import SingleModel +from workflows import Simulation from workflows import Workflow def make_workflow(configuration: Dict) -> Workflow: - match configuration['task']['id']: - case "single_model": - return SingleModel(configuration) + configuration = configuration['task'] + + match configuration['kind']: + case "task": + return Simulation(configuration) case _: raise ValueError(f"Unknown workflow id {id}") diff --git a/diploma_thesis/configuration/simulate.yml b/diploma_thesis/configuration/simulate.yml deleted file mode 100644 index 1c468bd..0000000 --- a/diploma_thesis/configuration/simulate.yml +++ /dev/null @@ -1,31 +0,0 @@ - - -task: - id: 'single_model' - -problem: - timespan: 10000 - machines_per_workcenter: 1 - workcenter_count: 5 - -sampler: - id: 'dynamic' - parameters: - processing_times: [1, 50] - tightness: 1 - realistic_variance: 10 - uneveness: 4 - expected_utilization: 0.8 -# even_arrival_time: 1 - seed: 42 - -scheduling_model: - id: 'static' - parameters: - rule: 'edd' - -routing_model: - id: 'static' - parameters: - rule: 'ea' - diff --git a/diploma_thesis/configuration/simulation.yml b/diploma_thesis/configuration/simulation.yml new file mode 100644 index 0000000..fc3c4f6 --- /dev/null +++ b/diploma_thesis/configuration/simulation.yml @@ -0,0 +1,79 @@ + +simulation: &simulation + name: '' + kind: 'dynamic' + parameters: + configuration: + timespan: 5000 + machines_per_work_center: 10 + work_center_count: 10 + pre_assign_initial_jobs: True + breakdown_ratio: 0.0 + + job_sampler: + kind: 'dynamic' + parameters: + processing_times: [ 10, 50 ] + tightness: 0.5 + uneveness: 5 + realistic_variance: 10 + expected_utilization: 0.95 + seed: 42 + + +task: + kind: 'task' + + machine_agent: + kind: 'static' + parameters: + model: + kind: 'static' + parameters: + rule: 'fifo' + encoder: + kind: 'plain' + + work_center_agent: + kind: 'static' + parameters: + model: + kind: 'static' + parameters: + rule: 'ea' + encoder: + kind: 'plain' + + simulator: + # Can be 'td'. Important for reinforcement learning + kind: 'episodic' + + run: + timeline: + warmup: + - 10 + - 100 + + duration: 10000 + + machine_train_schedule: + pretrain_steps: 10 + train_interval: 100 + max_training_steps: 1000 + + work_center_train_schedule: + pretrain_steps: 10 + train_interval: 100 + max_training_steps: 1000 + + n_workers: 2 + simulations: + - <<: *simulation + - <<: *simulation + - <<: *simulation + - <<: *simulation + + evaluate: + n_workers: 2 + simulations: + - <<: *simulation diff --git a/diploma_thesis/environment/delegate.py b/diploma_thesis/environment/delegate.py index 5ad382b..843122e 100644 --- a/diploma_thesis/environment/delegate.py +++ b/diploma_thesis/environment/delegate.py @@ -5,56 +5,56 @@ class Delegate(metaclass=ABCMeta): @abstractmethod - def did_start_simulation(self, shop_floor_id: int): + def did_start_simulation(self, shop_floor_id: str): """ Will be triggered after the start of simulation """ ... @abstractmethod - def will_produce(self, shop_floor_id: int, job: Job, machine: Machine): + def will_produce(self, shop_floor_id: str, job: Job, machine: Machine): """ Will be triggered before the production of job on machine """ ... @abstractmethod - def did_produce(self, shop_floor_id: int, job: Job, machine: Machine): + def did_produce(self, shop_floor_id: str, job: Job, machine: Machine): """ Will be triggered after the production of job on machine """ ... @abstractmethod - def will_dispatch(self, shop_floor_id: int, job: Job, work_center: WorkCenter): + def will_dispatch(self, shop_floor_id: str, job: Job, work_center: WorkCenter): """ Will be triggered before dispatch of job on the work-center """ ... @abstractmethod - def did_dispatch(self, shop_floor_id: int, job: Job, work_center: WorkCenter, machine: Machine): + def did_dispatch(self, shop_floor_id: str, job: Job, work_center: WorkCenter, machine: Machine): """ Will be triggered after the dispatch of job to the machine """ ... @abstractmethod - def did_finish_dispatch(self, shop_floor_id: int, work_center: WorkCenter): + def did_finish_dispatch(self, shop_floor_id: str, work_center: WorkCenter): """ Will be triggered after the dispatch of job on the work-center """ ... @abstractmethod - def did_complete(self, shop_floor_id: int, job: Job): + def did_complete(self, shop_floor_id: str, job: Job): """ Will be triggered after the completion of job """ ... @abstractmethod - def did_finish_simulation(self, shop_floor_id: int): + def did_finish_simulation(self, shop_floor_id: str): """ Will be triggered after all jobs have been completed """ diff --git a/diploma_thesis/environment/machine.py b/diploma_thesis/environment/machine.py index 263fc7e..a787cd3 100644 --- a/diploma_thesis/environment/machine.py +++ b/diploma_thesis/environment/machine.py @@ -133,7 +133,7 @@ def __init__(self, environment: simpy.Environment, machine_idx: int, work_center self.state = State(machine_idx=machine_idx, work_center_idx=work_center_idx) self.history = History() - self.shop_floor = None + self._shop_floor = None # Events self.did_dispatch_event = self.environment.event() @@ -143,7 +143,7 @@ def __init__(self, environment: simpy.Environment, machine_idx: int, work_center self.is_on_event.succeed() def connect(self, shop_floor: 'environment.ShopFloor'): - self.shop_floor = shop_floor + self._shop_floor = shop_floor self.environment.process(self.produce()) def simulate(self): @@ -241,6 +241,10 @@ def forward(self, job: environment.Job): self.state.without_job(job.id, now=self.environment.now) self.shop_floor.forward(job, from_=self) + @property + def shop_floor(self): + return self._shop_floor() + @property def queue(self) -> List[environment.Job]: return self.state.queue diff --git a/diploma_thesis/environment/shop_floor.py b/diploma_thesis/environment/shop_floor.py index 4ad1721..0edff34 100644 --- a/diploma_thesis/environment/shop_floor.py +++ b/diploma_thesis/environment/shop_floor.py @@ -11,7 +11,7 @@ @dataclass class State: - idx: int = 0 + idx: str = 0 job_id: int = 0 @@ -83,7 +83,7 @@ class Configuration: delegate: 'environment.Delegate' environment: simpy.Environment = field(default_factory=simpy.Environment) - def __init__(self, idx: int, configuration: Configuration, logger: logging.Logger): + def __init__(self, idx: str, configuration: Configuration, logger: logging.Logger): self.id = id self.configuration = configuration self.logger = logger @@ -97,6 +97,8 @@ def __init__(self, idx: int, configuration: Configuration, logger: logging.Logge self.history = History() self._work_centers, self._machines = ShopFloorFactory(self.configuration, self).make() + self.did_finish_simulation_event = configuration.environment.event() + def simulate(self): self.delegate.did_start_simulation(self.state.idx) @@ -108,6 +110,8 @@ def simulate(self): self.configuration.environment.process(self.__dispatch_jobs__()) + return self.did_finish_simulation_event + @property def statistics(self) -> 'environment.Statistics': from .statistics import Statistics @@ -154,11 +158,11 @@ def forward(self, job: environment.Job, from_: environment.Machine): f"at {self.configuration.environment.now}. Jobs in the system {self.state.number_of_jobs_in_system}" ) - def schedule(self, machine: environment.Machine, now: int) -> environment.Job | environment.WaitInfo: + def schedule(self, machine: environment.Machine, now: int) -> 'environment.Job | environment.WaitInfo': return self.agent.schedule(self.state.idx, machine, now) def route( - self, job: environment.Job, work_center_idx: int, machines: List['environment.Machine'] + self, job: environment.Job, work_center_idx: int, machines: List['environment.Machine'] ) -> 'environment.Machine | None': return self.agent.route(self.state.idx, job, work_center_idx, machines) @@ -179,6 +183,7 @@ def did_finish_dispatch(self, work_center: environment.WorkCenter): def did_complete(self, job: environment.Job): self.delegate.did_complete(self.state.idx, job) + self.__test_if_finished__() def __assign_initial_jobs__(self): for work_center in self.work_centers: @@ -228,9 +233,11 @@ def __test_if_finished__(self): has_dispatched_all_jobs = len(self.history.jobs) >= self.configuration.sampler.number_of_jobs() is_no_jobs_in_system = self.state.number_of_jobs_in_system == 0 - if has_dispatched_all_jobs and is_no_jobs_in_system: + if not self.did_finish_simulation_event.triggered and has_dispatched_all_jobs and is_no_jobs_in_system: self.delegate.did_finish_simulation(self.state.idx) + self.did_finish_simulation_event.succeed() + def __dispatch__(self, job: environment.Job, work_center: environment.WorkCenter): self.state.with_new_job_in_system() diff --git a/diploma_thesis/environment/utils/__init__.py b/diploma_thesis/environment/utils/__init__.py index 6a25b12..03cd85b 100644 --- a/diploma_thesis/environment/utils/__init__.py +++ b/diploma_thesis/environment/utils/__init__.py @@ -1,5 +1,5 @@ from .shopfloor_factory import ShopFloorFactory from .production_log_factory import ProductionLogFactory -from .report_factory import ReportFactory +from .report_factory import ReportFactory, Report from .production_log_factory import LogEvent diff --git a/diploma_thesis/environment/utils/production_log_factory.py b/diploma_thesis/environment/utils/production_log_factory.py index 566882a..1775cb9 100644 --- a/diploma_thesis/environment/utils/production_log_factory.py +++ b/diploma_thesis/environment/utils/production_log_factory.py @@ -8,7 +8,7 @@ import pandas as pd from joblib import Parallel, delayed -from environment import Job, ShopFloor +import environment class LogEvent(StrEnum): @@ -34,7 +34,7 @@ class ProductionLog: event: str moment: float - def make(self, shop_floor: ShopFloor) -> pd.DataFrame: + def make(self, shop_floor: 'environment.ShopFloor') -> pd.DataFrame: """ Completes production logs for the shop-floor. @@ -64,7 +64,7 @@ def make(self, shop_floor: ShopFloor) -> pd.DataFrame: return df - def __make_production_logs_from_job__(self, job: Job) -> List[ProductionLog]: + def __make_production_logs_from_job__(self, job: environment.Job) -> List[ProductionLog]: result = [] result += [self.ProductionLog(job.id, 0, -1, -1, LogEvent.created, job.history.created_at)] diff --git a/diploma_thesis/environment/utils/report_factory.py b/diploma_thesis/environment/utils/report_factory.py index aee576b..2a5fb43 100644 --- a/diploma_thesis/environment/utils/report_factory.py +++ b/diploma_thesis/environment/utils/report_factory.py @@ -63,7 +63,7 @@ class ReportFactory: def __init__(self, statistics: 'environment.Statistics', - shop_floor: environment.ShopFloor, + shop_floor: 'environment.ShopFloor', time_predicate: 'environment.Statistics.Predicate.TimePredicate'): self.statistics = statistics self.shop_floor = shop_floor diff --git a/diploma_thesis/environment/utils/shopfloor_factory.py b/diploma_thesis/environment/utils/shopfloor_factory.py index 6c24d82..081cdcf 100644 --- a/diploma_thesis/environment/utils/shopfloor_factory.py +++ b/diploma_thesis/environment/utils/shopfloor_factory.py @@ -7,17 +7,19 @@ class ShopFloorFactory: - def __init__(self, configuration: environment.ShopFloor.Configuration, shop_floor: environment.shop_floor): + def __init__(self, configuration: 'environment.ShopFloor.Configuration', shop_floor: 'environment.ShopFloor'): self.configuration = configuration self.shop_floor = weakref.ref(shop_floor) def make(self): - work_centers, machines_per_wc = self.__make_working_units__() + result = self.__make_working_units__() + work_centers = result[0] + machines_per_wc = result[1] - machines = reduce(lambda x, y: x + y, machines_per_wc, []) + machines: List[environment.Machine] = reduce(lambda x, y: x + y, machines_per_wc, []) for work_center, machines_in_work_center in zip(work_centers, machines_per_wc): - work_center.connect(machines_in_work_center, self.shop_floor) + work_center.connect(shop_floor=self.shop_floor, machines=machines_in_work_center) for machine in machines: machine.connect(self.shop_floor) diff --git a/diploma_thesis/environment/work_center.py b/diploma_thesis/environment/work_center.py index d8324aa..ff67fac 100644 --- a/diploma_thesis/environment/work_center.py +++ b/diploma_thesis/environment/work_center.py @@ -46,12 +46,12 @@ def __init__(self, environment: simpy.Environment, work_center_idx: int): self.state = State(idx=work_center_idx) self.history = History() self._machines = [] - self.shop_floor = None + self._shop_floor = None self.on_route = environment.event() def connect(self, shop_floor: 'environment.ShopFloor', machines: List['environment.Machine']): - self.shop_floor = shop_floor + self._shop_floor = shop_floor self._machines = machines def simulate(self): @@ -77,14 +77,14 @@ def dispatch(self): self.shop_floor.will_dispatch(job, self) # TODO: React on None - machine = self.shop_floor.shopfloor.route(job, work_center_idx=self.state.idx, machines=self.machines) + machine = self.shop_floor.route(job, work_center_idx=self.state.idx, machines=self.machines) machine.receive(job) - self.shop_floor.shopfloor.did_dispatch(job, self, machine) + self.shop_floor.did_dispatch(job, self, machine) self.state.with_flushed_queue() - self.shop_floor.shopfloor.did_finish_dispatch(self) + self.shop_floor.did_finish_dispatch(self) self.on_route = self.environment.event() @@ -101,6 +101,10 @@ def receive(self, job: environment.Job): self.did_receive_job() + @property + def shop_floor(self): + return self._shop_floor() + @property def machines(self) -> List['environment.Machine']: return self._machines diff --git a/diploma_thesis/job_samplers/__init__.py b/diploma_thesis/job_samplers/__init__.py index d54bc10..93a8126 100644 --- a/diploma_thesis/job_samplers/__init__.py +++ b/diploma_thesis/job_samplers/__init__.py @@ -9,12 +9,12 @@ Sampler as DynamicJobSampler) from .static import (Sampler as StaticJobSampler) -key_to_sampler_builder = { +key_to_class = { "dynamic": DynamicJobSamplerFromCLI } -def from_cli_arguments(problem: Configuration, environment: simpy.Environment, configuration: Dict) -> 'JobSampler': - sampler = key_to_sampler_builder[configuration['id']] +def from_cli(problem: Configuration, environment: simpy.Environment, configuration: Dict) -> 'JobSampler': + cls = key_to_class[configuration['kind']] - return sampler.from_cli_arguments(problem, environment, configuration['parameters']) + return cls.from_cli(problem, environment, configuration['parameters']) diff --git a/diploma_thesis/job_samplers/dynamic/builder.py b/diploma_thesis/job_samplers/dynamic/builder.py index af4348f..9e928d3 100644 --- a/diploma_thesis/job_samplers/dynamic/builder.py +++ b/diploma_thesis/job_samplers/dynamic/builder.py @@ -68,7 +68,7 @@ def with_uniform_processing_times_and_realistic_variance(self, processing_times: assert variance > 0, "Variance must be positive" distribution = torch.distributions.Uniform(low=processing_times[0], high=processing_times[1]) - noise = torch.distributions.Normal(loc=0, scale=torch.sqrt(variance)) + noise = torch.distributions.Normal(loc=0, scale=torch.sqrt(torch.tensor(variance))) def sample(shape: Tuple[int]) -> torch.FloatTensor: times = distribution.sample(shape) + noise.sample(shape) diff --git a/diploma_thesis/job_samplers/dynamic/cli.py b/diploma_thesis/job_samplers/dynamic/cli.py index 589a4f3..9bce43a 100644 --- a/diploma_thesis/job_samplers/dynamic/cli.py +++ b/diploma_thesis/job_samplers/dynamic/cli.py @@ -35,9 +35,7 @@ def from_cli_arguments(args: dict): ) @staticmethod - def from_cli_arguments(problem: Configuration, - environment: simpy.Environment, - parameters: dict) -> JobSampler: + def from_cli(problem: Configuration, environment: simpy.Environment, parameters: dict) -> JobSampler: configuration = CLI.Configuration.from_cli_arguments(parameters) builder = Builder(problem, environment, configuration.seed) @@ -65,4 +63,4 @@ def from_cli_arguments(problem: Configuration, else: raise ValueError("Either expected_utilization or even_arrival_time must be provided") - return builder.sampler, configuration + return builder.sampler diff --git a/diploma_thesis/job_samplers/static/job_sampler.py b/diploma_thesis/job_samplers/static/job_sampler.py index 68db4dc..368fa29 100644 --- a/diploma_thesis/job_samplers/static/job_sampler.py +++ b/diploma_thesis/job_samplers/static/job_sampler.py @@ -3,6 +3,7 @@ from environment import Job, Configuration, JobSampler + class Sampler(JobSampler): def __init__(self, problem: Configuration, environment: simpy.Environment): diff --git a/diploma_thesis/simulator/__init__.py b/diploma_thesis/simulator/__init__.py index 4004c6e..b0ce432 100644 --- a/diploma_thesis/simulator/__init__.py +++ b/diploma_thesis/simulator/__init__.py @@ -2,3 +2,17 @@ from .simulator import Simulator from .episodic import EpisodicSimulator from .td import TDSimulator +from .configuration import RunConfiguration, EvaluateConfiguration +from agents import Machine, WorkCenter +from typing import Dict + +key_to_class = { + "episodic": EpisodicSimulator, + "td": TDSimulator +} + + +def from_cli(machine: Machine, work_center: WorkCenter, reward_model, logger, parameters: Dict): + cls = key_to_class[parameters['kind']] + + return cls(work_center, machine, reward_model, logger) diff --git a/diploma_thesis/simulator/cli.py b/diploma_thesis/simulator/cli.py deleted file mode 100644 index e69de29..0000000 diff --git a/diploma_thesis/simulator/configuration.py b/diploma_thesis/simulator/configuration.py new file mode 100644 index 0000000..c276ab8 --- /dev/null +++ b/diploma_thesis/simulator/configuration.py @@ -0,0 +1,65 @@ + +from .simulation import Simulation, from_cli_list +from dataclasses import dataclass, field +from typing import List, Dict +from logging import Logger + + +@dataclass +class RunConfiguration: + @dataclass + class TimelineSchedule: + # List of durations for warm up phases + warm_up_phases: List[float] = field(default_factory=list) + # Maximum Duration of simulation + duration: int = 1000 + + @staticmethod + def from_cli(parameters: Dict): + return RunConfiguration.TimelineSchedule( + warm_up_phases=parameters['warmup'], + duration=parameters['duration'] + ) + + @dataclass + class TrainSchedule: + pretrain_steps: int = 0 + train_interval: int = 0 + max_training_steps: int = 0 + + @staticmethod + def from_cli(parameters: Dict): + return RunConfiguration.TrainSchedule( + pretrain_steps=parameters['pretrain_steps'], + train_interval=parameters['train_interval'], + max_training_steps=parameters['max_training_steps'] + ) + + timeline: TimelineSchedule + machine_train_schedule: TrainSchedule + work_center_train_schedule: TrainSchedule + n_workers: int = 4 + simulations: List[Simulation] = field(default_factory=list) + + @classmethod + def from_cli(cls, logger: Logger, parameters: Dict): + return cls( + timeline=cls.TimelineSchedule.from_cli(parameters['timeline']), + machine_train_schedule=cls.TrainSchedule.from_cli(parameters['machine_train_schedule']), + work_center_train_schedule=cls.TrainSchedule.from_cli(parameters['work_center_train_schedule']), + n_workers=parameters['n_workers'], + simulations=from_cli_list(logger=logger, parameters=parameters['simulations']) + ) + + +@dataclass +class EvaluateConfiguration: + n_workers: int = 4 + simulations: List[Simulation] = field(default_factory=list) + + @classmethod + def from_cli(cls, logger: Logger, parameters: Dict): + return cls( + n_workers=parameters['n_workers'], + simulations=from_cli_list(logger=logger, parameters=parameters['simulations']) + ) diff --git a/diploma_thesis/simulator/episodic.py b/diploma_thesis/simulator/episodic.py index 6f7b029..e9923f8 100644 --- a/diploma_thesis/simulator/episodic.py +++ b/diploma_thesis/simulator/episodic.py @@ -13,34 +13,36 @@ class EpisodicSimulator(Simulator): After the simulation is finished returns are estimated and passed to the agent for training. """ - def schedule(self, shop_floor_id: int, machine: Machine, now: int) -> Job | WaitInfo: + def schedule(self, shop_floor_id: str, machine: Machine, now: int) -> Job | WaitInfo: parameters = MachineInput(machine, now) return self.machine.schedule(parameters).result - def route(self, shop_floor_id: int, job: Job, work_center_idx: int, machines: List[Machine]) -> 'Machine | None': + def route(self, shop_floor_id: str, job: Job, work_center_idx: int, machines: List[Machine]) -> 'Machine | None': parameters = WorkCenterInput(job, work_center_idx, machines) return self.work_center.schedule(parameters).result - def did_start_simulation(self, shop_floor_id: int): + def did_start_simulation(self, shop_floor_id: str): pass - def will_produce(self, shop_floor_id: int, job: Job, machine: Machine): + def will_produce(self, shop_floor_id: str, job: Job, machine: Machine): pass - def did_produce(self, shop_floor_id: int, job: Job, machine: Machine): + def did_produce(self, shop_floor_id: str, job: Job, machine: Machine): pass - def will_dispatch(self, shop_floor_id: int, job: Job, work_center: WorkCenter): + def will_dispatch(self, shop_floor_id: str, job: Job, work_center: WorkCenter): pass - def did_dispatch(self, shop_floor_id: int, job: Job, work_center: WorkCenter, machine: Machine): + def did_dispatch(self, shop_floor_id: str, job: Job, work_center: WorkCenter, machine: Machine): pass - def did_finish_dispatch(self, shop_floor_id: int, work_center: WorkCenter): + def did_finish_dispatch(self, shop_floor_id: str, work_center: WorkCenter): pass - def did_complete(self, shop_floor_id: int, job: Job): + def did_complete(self, shop_floor_id: str, job: Job): pass + def did_finish_simulation(self, shop_floor_id: str): + Simulator.did_finish_simulation(self, shop_floor_id) diff --git a/diploma_thesis/simulator/simulation/__init__.py b/diploma_thesis/simulator/simulation/__init__.py index 0f4a87e..1bd17c9 100644 --- a/diploma_thesis/simulator/simulation/__init__.py +++ b/diploma_thesis/simulator/simulation/__init__.py @@ -1,5 +1,32 @@ from .simulation import Simulation +from .dynamic_simulation import DynamicSimulation +from typing import Dict, List +from logging import Logger -def from_cli(): - pass \ No newline at end of file +key_to_class = { + 'dynamic': DynamicSimulation +} + + +def from_cli(logger: Logger, parameters: Dict): + cls = key_to_class[parameters['kind']] + + return cls.from_cli(name=parameters.get('name', ''), logger=logger, parameters=parameters['parameters']) + + +def from_cli_list(logger: Logger, parameters: List[Dict]): + simulation_names = [parameter.get('name', '') for parameter in parameters] + names = dict() + + for idx, key in enumerate(simulation_names): + if key in names: + names[key] += 1 + key += '[' + str(names[key]) + ']' + else: + names[key] = 1 + key += '[0]' + + parameters[idx]['name'] = key + + return [from_cli(logger, parameter) for parameter in parameters] diff --git a/diploma_thesis/simulator/simulation/dynamic_simulation.py b/diploma_thesis/simulator/simulation/dynamic_simulation.py index eafd292..364a57a 100644 --- a/diploma_thesis/simulator/simulation/dynamic_simulation.py +++ b/diploma_thesis/simulator/simulation/dynamic_simulation.py @@ -5,32 +5,49 @@ import environment from environment import Agent, Delegate, ShopFloor -from job_samplers import from_cli_arguments as job_sampler_from_cli_arguments +from job_samplers import from_cli as job_sampler_from_cli from .simulation import Simulation +from logging import Logger -@dataclass -class Configuration: - idx: int - configuration: Dict - sampler: Dict +class DynamicSimulation(Simulation): + @dataclass + class Configuration: + configuration: Dict + job_sampler: Dict -class DynamicSimulation(Simulation): + def __init__(self, name: str, logger: Logger, configuration: Configuration): + super().__init__(name, logger) - def __init__(self, configuration: Configuration): self.configuration = configuration - def run(self, shop_floor_idx: int, agent: Agent, delegate: Delegate, env: simpy.Environment): + @property + def shop_floor_id(self): + return self.name + + def run(self, agent: Agent, delegate: Delegate, env: simpy.Environment): problem = self.configuration.configuration problem = environment.Configuration.from_cli_arguments(problem) - sampler = job_sampler_from_cli_arguments(problem, env, self.configuration.sampler) + sampler = job_sampler_from_cli(problem, env, self.configuration.job_sampler) configuration = ShopFloor.Configuration( problem=problem, sampler=sampler, agent=agent, delegate=delegate, environment=env ) - shop_floor = ShopFloor(shop_floor_idx, configuration, None) + self.shop_floor = ShopFloor(self.shop_floor_id, configuration, self.logger) + + yield self.shop_floor.simulate() + + @classmethod + def from_cli(cls, name: str, logger: Logger, parameters: Dict): + return cls( + name=name, + logger=logger, + configuration=cls.Configuration( + configuration=parameters['configuration'], + job_sampler=parameters['job_sampler'] + ) + ) - shop_floor.simulate() diff --git a/diploma_thesis/simulator/simulation/simulation.py b/diploma_thesis/simulator/simulation/simulation.py index 944ce60..1a7af79 100644 --- a/diploma_thesis/simulator/simulation/simulation.py +++ b/diploma_thesis/simulator/simulation/simulation.py @@ -1,9 +1,21 @@ import simpy -from environment import Agent, Delegate +from abc import ABCMeta, abstractmethod +from environment import Agent, Delegate, ShopFloor +from logging import Logger -class Simulation: +class Simulation(metaclass=ABCMeta): - def run(self, shop_floor_idx: int, agent: Agent, delegate: Delegate, env: simpy.Environment): + def __init__(self, name: str, logger: Logger): + self.name = name + self.logger = logger.getChild(f'simulation {name}') + self.shop_floor: ShopFloor = None + + @property + @abstractmethod + def shop_floor_id(self): + pass + + def run(self, agent: Agent, delegate: Delegate, env: simpy.Environment): pass diff --git a/diploma_thesis/simulator/simulator.py b/diploma_thesis/simulator/simulator.py index e0a2c00..4bc568c 100644 --- a/diploma_thesis/simulator/simulator.py +++ b/diploma_thesis/simulator/simulator.py @@ -1,41 +1,16 @@ import logging -from abc import ABCMeta, abstractmethod -from dataclasses import dataclass, field -from typing import List, Callable +from abc import ABCMeta +from typing import Callable, List import simpy from agents import WorkCenter, Machine, TrainingPhase, EvaluationPhase, WarmUpPhase, Phase from environment import Delegate, Agent +from .configuration import RunConfiguration, EvaluateConfiguration from .reward import RewardModel from .simulation import Simulation -@dataclass -class TimelineSchedule: - warm_up_phases: List[float] = field(default_factory=list) - max_length: int = 1000 - - -@dataclass -class TrainSchedule: - pretrain_steps: int = 0 - train_interval: int = 0 - max_training_steps: int = 0 - - -@dataclass -class RunSchedule: - n_workers: int = 4 - simulations: List[Simulation] = field(default_factory=list) - - -@dataclass -class EvaluateSchedule: - n_workers: int = 4 - simulations: List[Simulation] = field(default_factory=list) - - class Simulator(Agent, Delegate, metaclass=ABCMeta): def __init__( @@ -47,11 +22,8 @@ def __init__( self.logger = logger self.environment = simpy.Environment() - def train( - self, timeline: TimelineSchedule, machine_train_schedule: TrainSchedule, - work_center_train_schedule: TrainSchedule, run_schedule: RunSchedule - ): - assert self.machine.is_trainable or self.work_center.is_trainable, 'At least one agent should be trainable' + def train(self, config: RunConfiguration): + # assert self.machine.is_trainable or self.work_center.is_trainable, 'At least one agent should be trainable' env = self.environment warmup_end = env.event() @@ -60,61 +32,65 @@ def train( run_end = env.event() all_of_event = simpy.AllOf(env, [warmup_end, machine_training_end, work_center_train_end, run_end]) - env.process(self.__main_timeline__(timeline, warmup_end, run_end)) + env.process(self.__main_timeline__(warm_up_event=warmup_end, run_event=run_end, configuration=config.timeline)) ids = ['machine', 'work_center'] - schedules = [machine_train_schedule, work_center_train_schedule] + schedules = [config.machine_train_schedule, config.work_center_train_schedule] end_training_events = [machine_training_end, work_center_train_end] train_steps = [self.machine.train_step, self.work_center.train_step] for idx, schedule, end_of_train_event, train_step in zip(ids, schedules, end_training_events, train_steps): env.process(self.__train_timeline__(idx, schedule, warmup_end, end_of_train_event, run_end, train_step)) - env.process(self.__run__(run_event=run_end, configuration=run_schedule)) - env.process(self.__terminate_if_needed__(run_event=run_end, delay=timeline.max_length)) + env.process(self.__run__(run_event=run_end, n_workers=config.n_workers, simulations=config.simulations)) + env.process(self.__terminate_if_needed__(run_event=run_end, delay=config.timeline.duration)) env.run(all_of_event) - def evaluate(self, configuration: EvaluateSchedule): - # self.environment.run(self.__run__(configuration)) - pass + def evaluate(self, configuration: EvaluateConfiguration): + run_end = self.environment.event() + + self.environment.process( + self.__run__(run_end, n_workers=configuration.n_workers, simulations=configuration.simulations) + ) - def __run__(self, run_event: simpy.Event, configuration: RunSchedule): - # TODO: - Verify - store = simpy.Store(self.environment, capacity=configuration.n_workers) + self.environment.run(run_end) - def produce(): - for idx, simulation in enumerate(configuration.simulations): - yield store.put((idx, simulation)) + def __run__(self, run_event: simpy.Event, n_workers: int, simulations: List[Simulation]): + resource = simpy.Resource(self.environment, capacity=n_workers) - yield store.put(None) + def consume(simulation: Simulation): + with resource.request() as req: + yield req - def consume(): - while True: - idx, simulation = yield store.get() + self.__log__(f'Simulation Started {simulation.shop_floor_id}') - if simulation is None: - break + yield self.environment.process(simulation.run(self, self, self.environment)) - simulation.run(idx, self, self, self.environment) + self.__log__(f'Simulation Finished {simulation.shop_floor_id}') - self.environment.process(produce()) + processes = [self.environment.process(consume(simulation)) for simulation in simulations] - yield self.environment.process(consume()) + yield simpy.AllOf(self.environment, events=processes) + + self.__log__(f'All simulations finished') try: run_event.succeed() except: pass - def __main_timeline__(self, warm_up_event: simpy.Event, run_event: simpy.Event, configuration: TimelineSchedule): + def __main_timeline__(self, + warm_up_event: simpy.Event, + run_event: simpy.Event, + configuration: RunConfiguration.TimelineSchedule): for index, phase in enumerate(configuration.warm_up_phases): self.__update__(WarmUpPhase(index)) - self.logger.info(f'Warm-up phase {index} started') + self.__log__(f'Warm-up phase {index} started') yield self.environment.timeout(phase) - self.logger.info('Warm-up finished') - self.logger.info('Training started') + self.__log__('Warm-up finished') + self.__log__('Training started') self.__update__(TrainingPhase()) @@ -122,14 +98,18 @@ def __main_timeline__(self, warm_up_event: simpy.Event, run_event: simpy.Event, yield run_event - self.logger.info('Training finished') + self.__log__('Training finished') self.__update__(EvaluationPhase()) def __train_timeline__( self, - id: str, configuration: TrainSchedule, warm_up_event: simpy.Event, training_event: simpy.Event, - run_event: simpy.Event, train_fn: Callable + name: str, + configuration: RunConfiguration.TrainSchedule, + warm_up_event: simpy.Event, + training_event: simpy.Event, + run_event: simpy.Event, + train_fn: Callable ): yield warm_up_event @@ -143,11 +123,13 @@ def __train_timeline__( and not training_event.triggered): yield self.environment.timeout(configuration.train_interval) + self.__log__(f'Training Step {training_steps} at {name} ', logging.DEBUG) + train_fn() training_steps += 1 if training_steps >= configuration.max_training_steps: - self.logger.info(f'End training {id} due to max training steps reached') + self.__log__(f'End training {name} due to max training steps reached') break try: @@ -155,12 +137,12 @@ def __train_timeline__( except: pass - self.logger.info(f'Training finished {id}') + self.__log__(f'Training finished {name}') def __terminate_if_needed__(self, run_event: simpy.Event, delay: float): - yield self.environment.timeout(float) + yield self.environment.timeout(delay) - self.logger.info('Terminating simulation due to max duration reached') + self.__log__('Terminating simulation due to max duration reached') try: run_event.succeed() @@ -171,5 +153,6 @@ def __update__(self, phase: Phase): self.work_center.update(phase) self.machine.update(phase) - def did_finish_simulation(self, shop_floor_id: int): - pass + def __log__(self, message: str, level: int = logging.INFO): + self.logger.log(level=level, msg=message) + diff --git a/diploma_thesis/simulator/td.py b/diploma_thesis/simulator/td.py index a0d0cc9..f7728c3 100644 --- a/diploma_thesis/simulator/td.py +++ b/diploma_thesis/simulator/td.py @@ -11,34 +11,36 @@ class TDSimulator(Simulator): possible """ - def schedule(self, shop_floor_id: int, machine: Machine, now: int) -> Job | WaitInfo: + def schedule(self, shop_floor_id: str, machine: Machine, now: int) -> Job | WaitInfo: parameters = MachineInput(machine, now) return self.machine.schedule(parameters).result - def route(self, shop_floor_id: int, job: Job, work_center_idx: int, machines: List[Machine]) -> 'Machine | None': + def route(self, shop_floor_id: str, job: Job, work_center_idx: int, machines: List[Machine]) -> 'Machine | None': parameters = WorkCenterInput(job, work_center_idx, machines) return self.work_center.schedule(parameters).result - def did_start_simulation(self, shop_floor_id: int): + def did_start_simulation(self, shop_floor_id: str): pass - def will_produce(self, shop_floor_id: int, job: Job, machine: Machine): + def will_produce(self, shop_floor_id: str, job: Job, machine: Machine): pass - def did_produce(self, shop_floor_id: int, job: Job, machine: Machine): + def did_produce(self, shop_floor_id: str, job: Job, machine: Machine): pass - def will_dispatch(self, shop_floor_id: int, job: Job, work_center: WorkCenter): + def will_dispatch(self, shop_floor_id: str, job: Job, work_center: WorkCenter): pass - def did_dispatch(self, shop_floor_id: int, job: Job, work_center: WorkCenter, machine: Machine): + def did_dispatch(self, shop_floor_id: str, job: Job, work_center: WorkCenter, machine: Machine): pass - def did_finish_dispatch(self, shop_floor_id: int, work_center: WorkCenter): + def did_finish_dispatch(self, shop_floor_id: str, work_center: WorkCenter): pass - def did_complete(self, shop_floor_id: int, job: Job): + def did_complete(self, shop_floor_id: str, job: Job): pass + def did_finish_simulation(self, shop_floor_id: str): + Simulator.did_finish_simulation(self, shop_floor_id) diff --git a/diploma_thesis/workflows/__init__.py b/diploma_thesis/workflows/__init__.py index e0b5f6c..8384b6b 100644 --- a/diploma_thesis/workflows/__init__.py +++ b/diploma_thesis/workflows/__init__.py @@ -1,4 +1,3 @@ from .workflow import Workflow -from .static_single_rule import SingleModel -from .static_rule_tournament import StaticRuleTournament +from .simulation import Simulation diff --git a/diploma_thesis/workflows/simulation.py b/diploma_thesis/workflows/simulation.py new file mode 100644 index 0000000..db1cd8e --- /dev/null +++ b/diploma_thesis/workflows/simulation.py @@ -0,0 +1,36 @@ + +from .workflow import Workflow +from typing import Dict + +from agents import work_center_from_cli, machine_from_cli +from simulator import from_cli as simulator_from_cli, RunConfiguration, EvaluateConfiguration + + +class Simulation(Workflow): + + def __init__(self, parameters: Dict): + super().__init__() + + self.parameters = parameters + + def run(self): + machine = machine_from_cli(parameters=self.parameters['machine_agent']) + work_center = work_center_from_cli(parameters=self.parameters['work_center_agent']) + # TODO: Implement Reward Model + simulator = simulator_from_cli( + machine=machine, + work_center=work_center, + reward_model=None, + logger=self.__make_logger__('Simulator', log_stdout=True), + parameters=self.parameters['simulator'] + ) + + if run_config := self.parameters.get('run'): + run_config = RunConfiguration.from_cli(self.__make_logger__('Run', log_stdout=False), run_config) + + simulator.train(run_config) + + if evaluate_config := self.parameters.get('evaluate'): + evaluate_config = EvaluateConfiguration.from_cli(self.__make_logger__('Evaluate'), evaluate_config) + + simulator.evaluate(evaluate_config) diff --git a/diploma_thesis/workflows/static_rule_tournament.py b/diploma_thesis/workflows/static_rule_tournament.py deleted file mode 100644 index 65e8578..0000000 --- a/diploma_thesis/workflows/static_rule_tournament.py +++ /dev/null @@ -1,46 +0,0 @@ -from environment.configuration import Configuration -from .workflow import Workflow - - -class StaticRuleTournament(Workflow): - - def __init__(self, problem: Configuration): - super().__init__() - - self.problem = problem - - def run(self): - pass - # # For single machine problem routing rule doesn't have any effect on scheduling - # if self.problem.machines_per_workcenter <= 1: - # routing_rules = routing_rules[:1] - # - # logger = self.__make_logger__(log_stdout=True) - # - # for routing_rule in routing_rules: - # for scheduling_rule.py in rule: - # logger.info(f'Start evaluating routing rule { routing_rule } and scheduling rule { scheduling_rule.py }') - # - # environment = simpy.Environment() - # - # routing_rule_parameters = routing_rule_parameters_db.get(routing_rule, {}) - # scheduling_rule_parameters = scheduling_rule_parameters_db.get(scheduling_rule.py, {}) - # - # configuration = ShopFloor.Configuration( - # environment=environment, - # problem=self.problem, - # routing_model=routing_rule(**routing_rule_parameters), - # scheduling_model=scheduling_rule.py(**scheduling_rule_parameters), - # sampler=self. - # ) - # - # shopfloor = ShopFloor( - # configuration=configuration, - # logger=self.__make_logger__('ShopFloor', log_stdout=False), - # ) - # - # shopfloor.simulate() - # - # environment.run(until=self.problem.timespan) - # - # logger.info(f'Finish evaluating routing rule {routing_rule} and scheduling rule {scheduling_rule.py}') diff --git a/diploma_thesis/workflows/static_single_rule.py b/diploma_thesis/workflows/static_single_rule.py deleted file mode 100644 index 78f6412..0000000 --- a/diploma_thesis/workflows/static_single_rule.py +++ /dev/null @@ -1,47 +0,0 @@ -from typing import Dict - -from .workflow import Workflow - - -class SingleModel(Workflow): - - def __init__(self, parameters: Dict): - super().__init__() - - self.parameters = parameters - - def run(self): - pass - # environment = simpy.Environment() - # - # problem = Configuration.from_cli_arguments(self.parameters['problem']) - # sampler = job_sampler_from_cli_arguments(problem, environment, self.parameters['sampler']) - # scheduling_model = scheduling_model_from_cli_arguments(self.parameters['scheduling_model']) - # routing_model = routing_model_from_cli_arguments(self.parameters['routing_model']) - # - # configuration = ShopFloor.Configuration( - # environment=environment, - # problem=problem, - # sampler=sampler, - # scheduling_rule.py=scheduling_model, - # routing_rule=routing_model - # ) - # - # shopfloor = ShopFloor( - # configuration, - # logger=self.__make_logger__('ShopFloor', log_stdout=True), - # delegate=self.make_delegate([scheduling_model, routing_model]) - # ) - # - # shopfloor.simulate() - # - # environment.run(until=problem.timespan) - # - # statistics = shopfloor.statistics - # - # predicate = statistics.Predicate - # time_predicate = predicate.TimePredicate(at=problem.timespan, kind=predicate.TimePredicate.Kind.less_than) - # - # report = statistics.report(time_predicate) - # - # print(report) diff --git a/diploma_thesis/workflows/utils/__init__.py b/diploma_thesis/workflows/utils/__init__.py deleted file mode 100644 index f44334b..0000000 --- a/diploma_thesis/workflows/utils/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ - -from .broadcast_shopfloor_delegate import BroadCastShopFloorDelegate \ No newline at end of file diff --git a/diploma_thesis/workflows/workflow.py b/diploma_thesis/workflows/workflow.py index 4faeab8..22c50c0 100644 --- a/diploma_thesis/workflows/workflow.py +++ b/diploma_thesis/workflows/workflow.py @@ -9,10 +9,12 @@ class Workflow(metaclass=ABCMeta): def run(self): pass + # TODO: Log environment time + def __make_logger__(self, name: str = 'Workflow', log_stdout: bool = False) -> logging.Logger: logger = logging.getLogger(name) logger.setLevel(logging.INFO) - formatter = logging.Formatter('%(asctime)s | %(levelname)s | %(message)s') + formatter = logging.Formatter('%(asctime)s | %(name)s | %(levelname)s | %(message)s') if log_stdout: stdout_handler = logging.StreamHandler(sys.stdout)