diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 56c1752408f425a87ac3eb07483ac2e35f9000f3..2f2b1ae629abf811792198a03429c39128ee0f9f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -22,14 +22,14 @@ pylint: allow_failure: true script: - pip install pylint - - pylint -d C0301,R0902,E0611,E0401 ./pyAmakCore --max-parents=9 + - pylint -d C0301,R0902,E0611,E0401,C0413,R0201 ./pyAmakCore/classes --max-parents=9 flake8: stage: Code Analysis allow_failure: true script: - pip install flake8 - - flake8 --verbose --max-line-length=120 --max-complexity 8 ./pyAmakCore + - flake8 --verbose --max-line-length=120 --max-complexity 8 ./pyAmakCore/classes isort: stage: Code Analysis diff --git a/note/cycle.txt b/note/cycle.txt deleted file mode 100644 index 47ad4b11a65ed7e9cc03153878a90ef53c03c950..0000000000000000000000000000000000000000 --- a/note/cycle.txt +++ /dev/null @@ -1,20 +0,0 @@ - - -init : - base - on_init - - syncro - - - -cycle : - - on cycle begin - syncro - - agent action - syncro - - on cycle end - syncro \ No newline at end of file diff --git a/note/how to use b/note/how to use deleted file mode 100644 index 94ec796203c44ddf49dde3ed98cd17efdeed89b5..0000000000000000000000000000000000000000 --- a/note/how to use +++ /dev/null @@ -1,13 +0,0 @@ -1 - create env -2 - create amas -3 - amas.start() - -to end : -amas.exit_program() - - -to start : -amas.put_token() - -to stop : -amas.take_token() \ No newline at end of file diff --git a/pyAmakCore/classes/agent.py b/pyAmakCore/classes/agent.py index 93e474f1db24be50a6735fe5789ecf56fe8dad9b..663518650295faed57a764bf60c19d99f0dedad8 100644 --- a/pyAmakCore/classes/agent.py +++ b/pyAmakCore/classes/agent.py @@ -7,12 +7,10 @@ from typing import List import sys import pathlib -from pyAmakCore.exception.override import ToOverrideWarning - sys.path.insert(0, str(pathlib.Path(__file__).parent)) +from pyAmakCore.exception.override import ToOverrideWarning from pyAmakCore.enumeration.agent_phase import Phase -from pyAmakCore.enumeration.executionPolicy import ExecutionPolicy class Agent: @@ -104,13 +102,25 @@ class Agent: """ self.__neighbours = [] + def get_criticality(self) -> float: + """ + return criticality + """ + return self.__criticality + def compute_criticality(self) -> float: """ compute_criticality """ return -inf - def _get_most_critical_neighbor(self, including_me: bool = False) -> 'Agent': + def set_criticality(self, criticality: float) -> None: + """ + set agent criticality to criticality + """ + self.__criticality = criticality + + def get_most_critical_neighbor(self, including_me: bool = False) -> 'Agent': """ Convenient method giving the most critical neighbor at a given moment """ @@ -129,10 +139,6 @@ class Agent: return criticalest[randint(0, len(criticalest) - 1)] - """ - Cycle - """ - def on_cycle_begin(self) -> None: """ this method is called by each agent at the start of their cycle @@ -163,24 +169,10 @@ class Agent: """ ToOverrideWarning("on_act") - def __phase1(self) -> None: + def next_phase(self) -> None: """ - this is the first phase of a cycle + set agent phase to the next phase """ - self.on_perceive() - self.compute_criticality() - self.__next_phase() - - def __phase2(self) -> None: - """ - this is the second phase of a cycle - """ - self.on_decide() - self.on_act() - self.compute_criticality() - self.__next_phase() - - def __next_phase(self): next_phase = { Phase.INITIALIZING: Phase.PERCEPTION, Phase.PERCEPTION: Phase.PERCEPTION_DONE, @@ -190,34 +182,11 @@ class Agent: } self.__phase = next_phase.get(self.__phase) - def run(self) -> None: - """ - Full cycle of an agent - """ - self.__next_phase() - - execution_policy = self.__amas.get_execution_policy() - - if execution_policy == ExecutionPolicy.TWO_PHASES: - if self.__phase == Phase.PERCEPTION: - self.on_cycle_begin() - self.__phase1() - return - - if self.__phase == Phase.DECISION_AND_ACTION: - self.__phase2() - self.on_cycle_end() - return - - if execution_policy == ExecutionPolicy.ONE_PHASE: - self.on_cycle_begin() - self.__phase1() - self.__next_phase() - self.__phase2() - self.on_cycle_end() - def __eq__(self, other: 'Agent') -> bool: - # check class + # should check class if other is None: return False return self.__id == other.__id + + def __repr__(self): + return str(self.__id) diff --git a/pyAmakCore/classes/amas.py b/pyAmakCore/classes/amas.py index 65b2835546bec4e944db7e81c8f69a4c544e9fb2..81ea8f3a8d17229b0b5857e4f9078c19450cff4c 100644 --- a/pyAmakCore/classes/amas.py +++ b/pyAmakCore/classes/amas.py @@ -1,56 +1,75 @@ """ Amas Class """ -from threading import Thread from typing import List + import sys import pathlib sys.path.insert(0, str(pathlib.Path(__file__).parent)) -from pyAmakCore.classes.schedulable import Schedulable -from pyAmakCore.classes.scheduler import Scheduler +from pyAmakCore.classes.tools.loggable import Loggable +from pyAmakCore.classes.tools.schedulable import Schedulable from pyAmakCore.classes.environment import Environment from pyAmakCore.classes.agent import Agent from pyAmakCore.enumeration.executionPolicy import ExecutionPolicy from pyAmakCore.exception.override import ToOverrideWarning -class Amas(Schedulable): +class Amas(Schedulable, Loggable): """ Amas Class """ - def __init__(self, environment: Environment) -> None: + def __init__(self, + environment: Environment, + execution_policy: ExecutionPolicy = ExecutionPolicy.ONE_PHASE + ) -> None: - super().__init__() + Schedulable.__init__(self) + Loggable.__init__(self) self.__environment: Environment = environment - self.__agents: List[Agent] = [] - self.__nbrcycle: int = 0 - self.scheduler: Scheduler = Scheduler() - self.scheduler.add_schedulable(self) - self.__environment.add_scheduler(self.scheduler) + self.__agents: List[Agent] = [] + self.__agent_to_add: List[Agent] = [] + self.__agent_to_remove: List[Agent] = [] - self.__execution_policy: ExecutionPolicy = ExecutionPolicy.ONE_PHASE + self.__execution_policy: ExecutionPolicy = execution_policy + self.on_initialization() self.on_initial_agents_creation() - for agent in self.__agents: + def add_pending_agent(self) -> List[Agent]: + """ + add pending agent into agent and return added agents + """ + for agent in self.__agent_to_add: agent.compute_criticality() + self.__agents.append(agent) + tmp = self.__agent_to_add + self.__agent_to_add = [] + return tmp - self.on_initialization() + def remove_pending_agent(self) -> List[Agent]: + """ + add pending agent into agent and return removed agents + """ + for agent in self.__agent_to_remove: + self.__agents.remove(agent) + tmp = self.__agent_to_remove + self.__agent_to_remove = [] - # tell scheduler that init is done - self.give_token_syncro() + return tmp - def add_agent(self, agent: 'Agent') -> None: + def add_agent(self, agent: Agent) -> None: """ add agent in the amas agents list without duplicate """ if agent in self.__agents: return - self.__agents.append(agent) + if agent in self.__agent_to_add: + return + self.__agent_to_add.append(agent) def add_agents(self, agents: List[Agent]) -> None: """ @@ -65,7 +84,9 @@ class Amas(Schedulable): """ if agent not in self.__agents: return - self.__agents.remove(agent) + if agent in self.__agent_to_remove: + return + self.__agent_to_remove.append(agent) def get_environment(self) -> Environment: """ @@ -85,91 +106,8 @@ class Amas(Schedulable): """ return self.__execution_policy - def set_execution_policy(self, execution_policy: ExecutionPolicy) -> None: - """ - set system execution_policy - """ - self.__execution_policy = execution_policy - - def get_cycle(self) -> int: - """ - return nbr of cycle - """ - return self.__nbrcycle - def on_initial_agents_creation(self) -> None: """ This method is called at the end of __init__() """ ToOverrideWarning("on_initial_agents_creation") - - def synchronization(self) -> None: - """ - Unlock Scheduler and wait for his response - """ - self.give_token_syncro() - self.scheduler.take_amas_token() - - def cycle(self) -> None: - """ - Main behavior of Amas - """ - print("Cycle : ", self.__nbrcycle) - self.__nbrcycle += 1 - - self.on_cycle_begin() - self.synchronization() - - threads = [] - # suffle - for agent in self.__agents: - current_thread = Thread(target=agent.run) - threads.append(current_thread) - current_thread.start() - - for thread in threads: - thread.join() - - if self.__execution_policy == ExecutionPolicy.TWO_PHASES: - threads = [] - for agent in self.__agents: - current_thread = Thread(target=agent.run) - threads.append(current_thread) - current_thread.start() - - for thread in threads: - thread.join() - - self.synchronization() - - self.on_cycle_end() - - def put_token(self) -> None: - """ - Tell scheduler to start - """ - self.scheduler.give_semaphore_start_stop() - - def take_token(self) -> None: - """ - Tell scheduler to stop - """ - self.scheduler.take_semaphore_start_stop() - - def exit_program(self) -> None: - """ - exit the program at the end of the cycle - """ - self.scheduler.exit_bool = True - - def set_sleep(self, sleep_time) -> None: - """ - set sleep between 2 cycles - """ - self.scheduler.sleep_time = sleep_time - - def start(self) -> None: - """ - launch the system - """ - self.scheduler.run() diff --git a/pyAmakCore/classes/communicating_agent.py b/pyAmakCore/classes/communicating_agent.py new file mode 100644 index 0000000000000000000000000000000000000000..d70cb0cd6636f4d142ca075a8355c78ccf11d118 --- /dev/null +++ b/pyAmakCore/classes/communicating_agent.py @@ -0,0 +1,122 @@ +""" +Class Mail Mailbox and CommunicatingAgent +""" +from typing import Any, List +import pathlib +import sys + +sys.path.insert(0, str(pathlib.Path(__file__).parent)) + +from pyAmakCore.exception.mailbox import ReceiverIsNotSelf, ReceiverDontExist +from pyAmakCore.classes.agent import Agent + + +class Mail: + """ + Class message + """ + + def __init__(self, id_sender: int, id_receiver: int, message: Any, sending_date: int) -> None: + self.__id_sender: int = id_sender + self.__id_receiver: int = id_receiver + self.__message: Any = message + self.__sending_date: int = sending_date + + def get_id_sender(self) -> int: + """ + return sender id + """ + return self.__id_sender + + def get_id_receiver(self) -> int: + """ + return receiver id + """ + return self.__id_receiver + + def get_message(self) -> Any: + """ + return message + """ + return self.__message + + def get_sending_date(self) -> int: + """ + return sending_date + """ + return self.__sending_date + + +class Mailbox: + """ + Each agent have 1 unique mailbox that they can call to send or receive mail + """ + + def __init__(self, owner_id: int, amas: 'Amas') -> None: + self.__mail_list: List['Mail'] = [] + self.__owner_id: int = owner_id + self.__amas: 'Amas' = amas + + def get_mail(self) -> 'Mail': + """ + return the next mail in the box, None if the mailBox is empty + """ + if len(self.__mail_list) == 0: + return None + mail = self.__mail_list.pop() + if mail.get_id_receiver() != self.__owner_id: + raise ReceiverIsNotSelf(self.__owner_id, mail.get_id_receiver()) + return mail + + def receive_mail(self, mail: 'Mail'): + """ + this method is called to put a mail in this mailbox + """ + self.__mail_list.append(mail) + + def send_message(self, message: Any, id_receiver: int) -> None: + """ + this method is called to send a message + """ + mail = Mail(self.__owner_id, id_receiver, message, self.__amas.get_cycle()) + + for agent in self.__amas.get_agents(): + if agent.get_id() == id_receiver: + return agent.receive_mail(mail) + raise ReceiverDontExist(id_receiver) + + +class CommunicatingAgent(Agent): + """ + Agent class that can communicate + """ + + def __init__(self, amas: 'Amas') -> None: + super().__init__(amas) + self.__mailbox: 'Mailbox' = Mailbox(self.get_id(), amas) + + def send_message(self, message: Any, id_receiver: int) -> None: + """ + send a message to another agent + """ + self.__mailbox.send_message(message, id_receiver) + + def receive_mail(self, mail: 'Mail') -> None: + """ + this method is called by another mailbox to add a mail in the mailbox + """ + self.__mailbox.receive_mail(mail) + + def read_mails(self) -> None: + """ + method that open all mail in the mailbox + """ + mail = self.__mailbox.get_mail() + while mail is not None: + self.read_mail(mail) + mail = self.__mailbox.get_mail() + + def read_mail(self, mail: 'Mail') -> None: + """ + This method should be override to make an action whenever you read a mail + """ diff --git a/pyAmakCore/classes/environment.py b/pyAmakCore/classes/environment.py index 06ff97236235c47ee56263bea333b9c0cf50075e..b811398e0659b7d460003f7ff86007c08f9dedf7 100644 --- a/pyAmakCore/classes/environment.py +++ b/pyAmakCore/classes/environment.py @@ -5,10 +5,10 @@ from random import seed import sys import pathlib + sys.path.insert(0, str(pathlib.Path(__file__).parent)) -from pyAmakCore.classes.schedulable import Schedulable -from pyAmakCore.classes.scheduler import Scheduler +from pyAmakCore.classes.tools.schedulable import Schedulable class Environment(Schedulable): @@ -19,10 +19,7 @@ class Environment(Schedulable): def __init__(self) -> None: self.set_seed() super().__init__() - self.scheduler: Scheduler = None self.on_initialization() - # tell scheduler that init is done - self.give_token_syncro() def set_seed(self): """ @@ -30,29 +27,3 @@ class Environment(Schedulable): """ seed() - def add_scheduler(self, scheduler: Scheduler) -> None: - """ - set scheduler pointer to scheduler - add add self to schedulables of scheduler - """ - self.scheduler = scheduler - self.scheduler.add_schedulable(self) - - def synchronization(self) -> None: - """ - Unlock Scheduler and wait for his response - """ - self.give_token_syncro() - self.scheduler.take_environment_token() - - def cycle(self) -> None: - """ - Main behavior of Environment - """ - self.on_cycle_begin() - self.synchronization() - - # mileu de cycle - self.synchronization() - - self.on_cycle_end() diff --git a/pyAmakCore/classes/scheduler.py b/pyAmakCore/classes/scheduler.py index 48a2248237d3c42ce1aed02190c90ac8b588a128..2326a7dcbf7836ef228781fa04259a7dcd1c801a 100644 --- a/pyAmakCore/classes/scheduler.py +++ b/pyAmakCore/classes/scheduler.py @@ -1,117 +1,96 @@ """ Scheduler class """ -from threading import Semaphore, Thread -from time import sleep from typing import List + import sys import pathlib +from threading import Thread + sys.path.insert(0, str(pathlib.Path(__file__).parent)) -from pyAmakCore.classes.schedulable import Schedulable +from pyAmakCore.classes.amas import Amas +from pyAmakCore.classes.thread_tool.schedulable_thread import SchedulableThread +from pyAmakCore.classes.thread_tool.amas_thread import AmasThread +from pyAmakCore.classes.tools.schedulable import Schedulable +from pyAmakCore.classes.scheduler_tool.scheduler_core import SchedulerCore -class Scheduler(): +class Scheduler(SchedulerCore): """ Scheduler class, to make sure that environment and amas are always sync together """ - def __init__(self) -> None: - - # List of all schedulables - self.__schedulables: List[Schedulable] = [] - # Sleep timer between 2 cycle - self.sleep_time: float = 0 + def __init__(self, amas: Amas) -> None: + SchedulerCore.__init__(self, amas) - # Semaphore for start/stop button - self.__semaphore_start_stop: Semaphore = Semaphore(0) - # Semaphore to tell amas to continue - self.__semaphore_amas: Semaphore = Semaphore(0) - # Semaphore to tell environment to continue - self.__semaphore_environment: Semaphore = Semaphore(0) + self.__schedulables: List[SchedulableThread] = [] + self.__schedulables_threads: List[Thread] = [] - self.exit_bool = False + self.__add_schedulables(amas, AmasThread) + self.__add_schedulables(amas.get_environment(), SchedulableThread) - def add_schedulable(self, schedulable: Schedulable) -> None: + def __add_schedulables(self, schedulable: Schedulable, cls) -> None: """ - add schedulable in schedulables list + add a schedulable in scheduler """ - self.__schedulables.append(schedulable) + schedulable_thread = cls(schedulable) + self.__schedulables.append(schedulable_thread) + current_thread = Thread(target=schedulable_thread.run) + self.__schedulables_threads.append(current_thread) + current_thread.start() - def take_amas_token(self) -> None: + def __wait_schedulables(self) -> None: """ - make amas wait here for a token + wait for all schedulable to release a token """ - self.__semaphore_amas.acquire() + for schedulable in self.__schedulables: + schedulable.action_done.acquire() - def give_amas_token(self) -> None: + def __start_schedulables(self) -> None: """ - unlock amas + wait for all schedulable to release a token """ - self.__semaphore_amas.release() + for schedulable in self.__schedulables: + schedulable.is_waiting.release() - def take_environment_token(self) -> None: + def first_part(self) -> None: """ - make environment wait here for a token + first part of a cycle """ - self.__semaphore_environment.acquire() + self.__start_schedulables() + # on cycle begin + self.__wait_schedulables() - def give_environment_token(self) -> None: + def main_part(self) -> None: """ - unlock environment + main part of a cycle """ - self.__semaphore_environment.release() + self.__start_schedulables() + # agent cycle + self.__wait_schedulables() - def take_semaphore_start_stop(self) -> None: + def last_part(self) -> None: """ - Scheduler will wait for a token to start the cycle + last part of a cycle """ - self.__semaphore_start_stop.acquire() + self.__start_schedulables() + # on cycle end + self.__wait_schedulables() - def give_semaphore_start_stop(self) -> None: + def run(self) -> None: """ - give a token to unlock Scheduler + override super run to close child thread before stopping """ - self.__semaphore_start_stop.release() + super().run() + self.__close_child() - def syncro_schedulable(self) -> None: + def __close_child(self) -> None: """ - wait for all schedulable to release a token + tell all child to shut down """ for schedulable in self.__schedulables: - schedulable.take_token_syncro() - - def run(self) -> None: - """ - main part of amak core - """ - # wait that all schedulable are ready - self.syncro_schedulable() - - while not self.exit_bool: - - self.__semaphore_start_stop.acquire() - self.__semaphore_start_stop.release() - - threads = [] - for schedulable in self.__schedulables: - current_thread = Thread(target=schedulable.run) - threads.append(current_thread) - current_thread.start() - - # on cycle begin - self.syncro_schedulable() - self.give_amas_token() - self.give_environment_token() - - # main part of the cycle - self.syncro_schedulable() - self.give_amas_token() - self.give_environment_token() - - # on cycle end - - for thread in threads: - thread.join() - - sleep(self.sleep_time) + schedulable.exit_bool = True + schedulable.is_waiting.release() + for thread in self.__schedulables_threads: + thread.join(0) diff --git a/pyAmakCore/classes/scheduler_mono_threading.py b/pyAmakCore/classes/scheduler_mono_threading.py new file mode 100644 index 0000000000000000000000000000000000000000..1f3bbc30bd288ce36591b8d53c8ba016e8ef52c9 --- /dev/null +++ b/pyAmakCore/classes/scheduler_mono_threading.py @@ -0,0 +1,99 @@ +""" +SchedulerMono class +""" +from random import shuffle +from typing import List + +import sys +import pathlib + + +sys.path.insert(0, str(pathlib.Path(__file__).parent)) + +from pyAmakCore.classes.scheduler_tool.scheduler_core import SchedulerCore +from pyAmakCore.classes.amas import Amas +from pyAmakCore.classes.agent import Agent +from pyAmakCore.classes.tools.schedulable import Schedulable +from pyAmakCore.classes.communicating_agent import CommunicatingAgent +from pyAmakCore.enumeration.executionPolicy import ExecutionPolicy + + +class SchedulerMono(SchedulerCore): + """ + Scheduler class, without threading + """ + + def __init__(self, amas: Amas) -> None: + SchedulerCore.__init__(self, amas) + + self.schedulables: List[Schedulable] = [] + + self.schedulables.append(self.amas) + self.amas.add_pending_agent() + self.schedulables.append(self.amas.get_environment()) + + def first_part(self) -> None: + """ + first part of a cycle + """ + self.amas.add_pending_agent() + + for schedulable in self.schedulables: + schedulable.on_cycle_begin() + + def main_part(self) -> None: + """ + main part of a cycle + """ + + def phase1(current_agent: Agent) -> None: + """ + this is the first phase of a cycle + """ + current_agent.next_phase() + current_agent.on_cycle_begin() + if isinstance(current_agent, CommunicatingAgent): + current_agent.read_mails() + + current_agent.on_perceive() + current_agent.set_criticality(current_agent.compute_criticality()) + current_agent.next_phase() + + def phase2(current_agent: Agent) -> None: + """ + this is the second phase of a cycle + """ + current_agent.next_phase() + current_agent.on_decide() + current_agent.on_act() + current_agent.set_criticality(current_agent.compute_criticality()) + current_agent.next_phase() + current_agent.on_cycle_end() + + agents: List[Agent] = self.amas.get_agents() + + if self.amas.get_execution_policy() == ExecutionPolicy.ONE_PHASE: + shuffle(agents) + for agent in agents: + phase1(agent) + phase2(agent) + else: + shuffle(agents) + for agent in agents: + phase1(agent) + shuffle(agents) + for agent in agents: + phase2(agent) + + def last_part(self) -> None: + """ + last part of a cycle + """ + for schedulable in self.schedulables: + schedulable.on_cycle_end() + + self.amas.remove_pending_agent() + self.amas.to_csv(self.amas.get_cycle(), self.amas.get_agents()) + + for schedulable in self.schedulables: + schedulable.cycle() diff --git a/pyAmakCore/classes/scheduler_tool/__init__.py b/pyAmakCore/classes/scheduler_tool/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/pyAmakCore/classes/scheduler_tool/callable.py b/pyAmakCore/classes/scheduler_tool/callable.py new file mode 100644 index 0000000000000000000000000000000000000000..f6f4a5163efae0eff9ab707f287bef727788eb33 --- /dev/null +++ b/pyAmakCore/classes/scheduler_tool/callable.py @@ -0,0 +1,41 @@ +""" +class Callable +""" +from threading import Semaphore + + +class Callable: + """ + Class that implement useful method to interact with the program + """ + + def __init__(self) -> None: + self.exit_bool: bool = False + self.sleep_time: float = 0 + + self.semaphore_start_stop: Semaphore = Semaphore(0) + + def exit_program(self) -> None: + """ + Exit the system as soon as possible + """ + self.exit_bool = True + self.semaphore_start_stop.release() + + def start(self) -> None: + """ + Unlock the scheduler + """ + self.semaphore_start_stop.release() + + def stop(self) -> None: + """ + Lock the scheduler + """ + self.semaphore_start_stop.acquire() + + def set_sleep(self, sleep_time: int) -> None: + """ + Set sleep value between 2 cycles + """ + self.sleep_time = sleep_time diff --git a/pyAmakCore/classes/scheduler_tool/savable.py b/pyAmakCore/classes/scheduler_tool/savable.py new file mode 100644 index 0000000000000000000000000000000000000000..fcffd31d7d9ea1f53ca2c4752f6bd7508c33036d --- /dev/null +++ b/pyAmakCore/classes/scheduler_tool/savable.py @@ -0,0 +1,41 @@ +""" +Savable Class +""" +import pickle +import sys +import pathlib + +sys.path.insert(0, str(pathlib.Path(__file__).parent)) + +from pyAmakCore.classes.amas import Amas + + +class Savable: + """ + Class that implement convenient method to save and load an amas + """ + def __init__(self, amas: Amas) -> None: + self.amas = amas + + def get_amas(self): + """ + return amas + """ + return self.amas + + def save(self) -> None: + """ + Save the current state of the system + """ + with open('filename.pickle', 'wb') as handle: + pickle.dump(self.amas, handle, protocol=pickle.HIGHEST_PROTOCOL) + + @classmethod + def load(cls) -> 'Savable': + """ + Load the last save of the system + """ + with open('filename.pickle', 'rb') as handle: + amas_object = pickle.load(handle) + + return cls(amas_object) \ No newline at end of file diff --git a/pyAmakCore/classes/scheduler_tool/scheduler_core.py b/pyAmakCore/classes/scheduler_tool/scheduler_core.py new file mode 100644 index 0000000000000000000000000000000000000000..15ef702597b505a1f5266d772d4ca3940dd96484 --- /dev/null +++ b/pyAmakCore/classes/scheduler_tool/scheduler_core.py @@ -0,0 +1,57 @@ +""" +Scheduler class +""" +from time import sleep + +import sys +import pathlib + +sys.path.insert(0, str(pathlib.Path(__file__).parent)) + +from pyAmakCore.classes.scheduler_tool.callable import Callable +from pyAmakCore.classes.scheduler_tool.savable import Savable +from pyAmakCore.classes.amas import Amas + + +class SchedulerCore(Callable, Savable): + """ + Core part of Scheduler + """ + + def __init__(self, amas: Amas) -> None: + + Callable.__init__(self) + Savable.__init__(self, amas) + + def first_part(self) -> None: + """ + first part of a cycle + """ + + def main_part(self) -> None: + """ + main part of a cycle + """ + + def last_part(self) -> None: + """ + last part of a cycle + """ + + def run(self) -> None: + """ + main part of amak core + """ + while not self.exit_bool: + print("Cycle : ", self.amas.get_cycle()) + + self.semaphore_start_stop.acquire() + if self.exit_bool: + break + self.semaphore_start_stop.release() + + self.first_part() + self.main_part() + self.last_part() + + sleep(self.sleep_time) diff --git a/pyAmakCore/classes/thread_tool/__init__.py b/pyAmakCore/classes/thread_tool/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/pyAmakCore/classes/thread_tool/agent_thread.py b/pyAmakCore/classes/thread_tool/agent_thread.py new file mode 100644 index 0000000000000000000000000000000000000000..493562d726a8ef2225310a8e4296fa9858025292 --- /dev/null +++ b/pyAmakCore/classes/thread_tool/agent_thread.py @@ -0,0 +1,74 @@ +""" +Class Agent thread +""" +from threading import Semaphore +import sys +import pathlib + + +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) + +from pyAmakCore.classes.communicating_agent import CommunicatingAgent +from pyAmakCore.classes.agent import Agent +from pyAmakCore.enumeration.executionPolicy import ExecutionPolicy + + +class AgentThread: + """ + thread class used to thread agent + """ + action_done = Semaphore(0) + execution_policy = ExecutionPolicy.ONE_PHASE + + def __init__(self, agent: Agent) -> None: + + self.agent: Agent = agent + self.is_waiting: Semaphore = Semaphore(0) + self.exit_bool: bool = False + + def phase1(self) -> None: + """ + this is the first phase of a cycle + """ + if isinstance(self.agent, CommunicatingAgent): + self.agent.read_mails() + + self.agent.on_perceive() + self.agent.set_criticality(self.agent.compute_criticality()) + self.agent.next_phase() + + def phase2(self) -> None: + """ + this is the second phase of a cycle + """ + self.agent.on_decide() + self.agent.on_act() + self.agent.set_criticality(self.agent.compute_criticality()) + self.agent.next_phase() + + def run(self) -> None: + """ + main part of an agent thread + """ + while not self.exit_bool: + + self.is_waiting.acquire() + if self.exit_bool: + return + + self.agent.next_phase() + self.agent.on_cycle_begin() + + self.phase1() + + if AgentThread.execution_policy == ExecutionPolicy.TWO_PHASES: + AgentThread.action_done.release() + self.is_waiting.acquire() + + self.agent.next_phase() + + self.phase2() + + self.agent.on_cycle_end() + + AgentThread.action_done.release() diff --git a/pyAmakCore/classes/thread_tool/amas_thread.py b/pyAmakCore/classes/thread_tool/amas_thread.py new file mode 100644 index 0000000000000000000000000000000000000000..e2516386afcb278a06c08854c7e70bcb6575d4c7 --- /dev/null +++ b/pyAmakCore/classes/thread_tool/amas_thread.py @@ -0,0 +1,125 @@ +""" +thread class for amas +""" +from threading import Thread +from typing import List + +import sys +import pathlib + + +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) + +from pyAmakCore.enumeration.executionPolicy import ExecutionPolicy +from pyAmakCore.classes.thread_tool.agent_thread import AgentThread +from pyAmakCore.classes.amas import Amas +from pyAmakCore.classes.agent import Agent +from pyAmakCore.classes.thread_tool.schedulable_thread import SchedulableThread + + +class AmasThread(SchedulableThread): + """ + thread class used to thread amas + """ + + def __init__(self, amas: Amas) -> None: + super().__init__(amas) + + self.agents: List[AgentThread] = [] + self.agents_thread: List[Thread] = [] + + AgentThread.execution_policy = self.schedulable.get_execution_policy() + + self.schedulable.add_pending_agent() + for agent in self.schedulable.get_agents(): + self.add_agent(agent) + + def add_agent(self, agent: Agent) -> None: + """ + make agent a thread and start the thread + """ + agent_thread = AgentThread(agent) + self.agents.append(agent_thread) + current_thread = Thread(target=agent_thread.run) + self.agents_thread.append(current_thread) + current_thread.start() + + def add_agents(self) -> None: + """ + if there are new agents, add them + """ + added_agent = self.schedulable.add_pending_agent() + for agent in added_agent: + self.add_agent(agent) + + def remove_agents(self) -> None: + """ + if some agents are removed, close their thread + """ + removed_agent = self.schedulable.remove_pending_agent() + for agent in removed_agent: + for i in range(len(self.agents)): + if agent == self.agents[i].agent: + self.agents[i].exit_bool = True + self.agents[i].is_waiting.release() + self.agents_thread[i].join() + + self.agents.remove(self.agents[i]) + self.agents_thread.remove(self.agents_thread[i]) + + def on_cycle_begin(self) -> None: + """ + start of cycle + """ + self.add_agents() + self.schedulable.on_cycle_begin() + + def main_cycle_part(self) -> None: + """ + main part of the cycle + """ + for agent in self.agents: + agent.is_waiting.release() + # agent cycle + + if self.schedulable.get_execution_policy() == ExecutionPolicy.TWO_PHASES: + # wait agents + for i in range(len(self.agents)): + AgentThread.action_done.acquire() + # start phase 2 for all agents + for agent in self.agents: + agent.is_waiting.release() + + for i in range(len(self.agents)): + AgentThread.action_done.acquire() + + def on_cycle_end(self) -> None: + """ + end of cycle + """ + self.schedulable.on_cycle_end() + + self.remove_agents() + self.schedulable.to_csv(self.schedulable.get_cycle(), self.schedulable.get_agents()) + + self.schedulable.cycle() + + def run(self) -> None: + """ + override run so when amas_thread try to stop, he closes all agents threads + """ + super().run() + self.close_child() + + def close_child(self) -> None: + """ + tell all child threads to close + """ + for agent in self.agents: + agent.exit_bool = True + agent.is_waiting.release() + + for thread in self.agents_thread: + thread.join(0) + + diff --git a/pyAmakCore/classes/thread_tool/schedulable_thread.py b/pyAmakCore/classes/thread_tool/schedulable_thread.py new file mode 100644 index 0000000000000000000000000000000000000000..9429e269b2c5ca42b94d28d3ac856236d3f1fee3 --- /dev/null +++ b/pyAmakCore/classes/thread_tool/schedulable_thread.py @@ -0,0 +1,61 @@ +""" +thread class for Schedulable +""" +from threading import Semaphore + +import sys +import pathlib + +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) + +from pyAmakCore.classes.tools.schedulable import Schedulable + + +class SchedulableThread: + """ + thread class used to thread schedulable + """ + + def __init__(self, schedulable: Schedulable) -> None: + + self.schedulable: Schedulable = schedulable + self.is_waiting: Semaphore = Semaphore(0) + self.exit_bool: bool = False + self.action_done: Semaphore = Semaphore(0) + + def on_cycle_begin(self) -> None: + """ + first part of the cycle + """ + self.schedulable.on_cycle_begin() + + def main_cycle_part(self) -> None: + """ + main part of the cycle + """ + + def on_cycle_end(self) -> None: + """ + last part of the cycle + """ + self.schedulable.on_cycle_end() + self.schedulable.cycle() + + def run(self) -> None: + """ + main part of a schedulable thread + """ + while not self.exit_bool: + self.is_waiting.acquire() + if self.exit_bool: + break + self.on_cycle_begin() + self.action_done.release() + + self.is_waiting.acquire() + self.main_cycle_part() + self.action_done.release() + + self.is_waiting.acquire() + self.on_cycle_end() + self.action_done.release() diff --git a/pyAmakCore/classes/tools/__init__.py b/pyAmakCore/classes/tools/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/pyAmakCore/classes/tools/loggable.py b/pyAmakCore/classes/tools/loggable.py new file mode 100644 index 0000000000000000000000000000000000000000..97584df4d166bcf7cea1e9d28e5392e4da7b59f4 --- /dev/null +++ b/pyAmakCore/classes/tools/loggable.py @@ -0,0 +1,61 @@ +""" +class allowing to save the state of the system at a given moment +""" +from os import path +from typing import List +from pandas import DataFrame + + +class Loggable: + """ + class Loggable + """ + def __init__(self): + self.__do_log = False + self.__file_path = None + self.__ignore_attribute: List[str] = ["_Agent__amas", "_Agent__environment"] + + def to_csv(self, cycle: int, var_list: List['Agent']) -> None: + """ + get cycle and agent list and print them + """ + if not self.__do_log: + return + + table = [{**{e: x[e] for e in x if e not in self.__ignore_attribute}, + **{'nombre_cycle': cycle}} for x in map(vars, var_list)] + dataframe = DataFrame(table) + + if self.__file_path is None: + print(dataframe.to_csv(index=False)) + else: + if path.exists(self.__file_path): + dataframe.to_csv(path_or_buf=self.__file_path, mode='a', header=False, index=False) + else: + dataframe.to_csv(path_or_buf=self.__file_path, index=False) + + def set_do_log(self, boolean: bool) -> None: + """ + tell the amas if it should log or not + """ + self.__do_log = boolean + + def set_file_path(self, path_to_file: str) -> None: + """ + specify path to csv + """ + self.__file_path = path_to_file + + def add_ignore_attribute(self, attribute: str) -> None: + """ + add attribute in ignored attribute + """ + self.__ignore_attribute.append(attribute) + + def remove_ignore_attribute(self, attribute: str) -> None: + """ + remove attribute in ignored attribute + """ + if attribute not in self.__ignore_attribute: + return + self.__ignore_attribute.remove(attribute) diff --git a/pyAmakCore/classes/schedulable.py b/pyAmakCore/classes/tools/schedulable.py similarity index 54% rename from pyAmakCore/classes/schedulable.py rename to pyAmakCore/classes/tools/schedulable.py index 45d906cfd98594f3447dec26f4d314c88aaaeecc..dda929cf0f60f62cc7238eaf134f03b92a33e5a2 100644 --- a/pyAmakCore/classes/schedulable.py +++ b/pyAmakCore/classes/tools/schedulable.py @@ -1,19 +1,34 @@ """ Schedulable interface """ -from threading import Semaphore import sys import pathlib -sys.path.insert(0, str(pathlib.Path(__file__).parent)) + +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) from pyAmakCore.exception.override import ToOverrideWarning -class Schedulable(): +class Schedulable: """ Class Schedulable """ - syncro_semaphore: Semaphore = Semaphore(0) + + def __init__(self): + + self.__nbr_cycle: int = 0 + + def get_cycle(self) -> int: + """ + return nbr_cycle + """ + return self.__nbr_cycle + + def cycle(self) -> None: + """ + add 1 to nbr_cycle + """ + self.__nbr_cycle += 1 def on_initialization(self) -> None: """ @@ -32,27 +47,3 @@ class Schedulable(): This method will be executed at the end of each cycle """ ToOverrideWarning("on_cycle_end") - - def run(self) -> None: - """ - Method that will be called by the thread - """ - self.cycle() - - def cycle(self) -> None: - """ - Main behavior of the Schedulable - """ - ToOverrideWarning("cycle") - - def take_token_syncro(self) -> None: - """ - Scheduler will wait here that the schedulable release a token - """ - self.syncro_semaphore.acquire() - - def give_token_syncro(self) -> None: - """ - Unlock scheduler - """ - self.syncro_semaphore.release() diff --git a/pyAmakCore/classes/tools/schedulerIHM.py b/pyAmakCore/classes/tools/schedulerIHM.py new file mode 100644 index 0000000000000000000000000000000000000000..a9358b53a8a0c64c1a56ddfd2decdab59c3b806a --- /dev/null +++ b/pyAmakCore/classes/tools/schedulerIHM.py @@ -0,0 +1,30 @@ +""" +Scheduler class that need to be used for pyAmakIhm +""" +import pathlib +import sys + +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) + +from pyAmakCore.classes.amas import Amas +from pyAmakCore.classes.scheduler import Scheduler + + +class SchedulerIHM(Scheduler): + """ + Convenient class to override while using pyAmakIHM + """ + + def __init__(self, amas: Amas): + self.__observer = None + super().__init__(amas) + + def last_part(self) -> None: + super().last_part() + self.__observer.updateCycle() + + def attach(self, observer: 'Controleur') -> None: + """ + set observer pointer to observer + """ + self.__observer = observer diff --git a/pyAmakCore/classes/tools/scheduler_monoIHM.py b/pyAmakCore/classes/tools/scheduler_monoIHM.py new file mode 100644 index 0000000000000000000000000000000000000000..827b998956b5bb7f150fd7d92035ffe74fb26e3a --- /dev/null +++ b/pyAmakCore/classes/tools/scheduler_monoIHM.py @@ -0,0 +1,31 @@ +""" +Scheduler class that need to be used for pyAmakIhm +""" +import pathlib +import sys + + +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) + +from pyAmakCore.classes.amas import Amas +from pyAmakCore.classes.scheduler_mono_threading import SchedulerMono + + +class SchedulerMonoIHM(SchedulerMono): + """ + Convenient class to override while using pyAmakIHM + """ + + def __init__(self, amas: Amas): + self.__observer = None + super().__init__(amas) + + def last_part(self) -> None: + super().last_part() + self.__observer.updateCycle() + + def attach(self, observer: 'Controleur') -> None: + """ + set observer pointer to observer + """ + self.__observer = observer diff --git a/pyAmakCore/enumeration/agent_phase.py b/pyAmakCore/enumeration/agent_phase.py index 56464f8aa0030c6e3fca7cc0ba585b617bbbaa5a..259a21e87d72f749556e4580280c7dc51a30c6ef 100644 --- a/pyAmakCore/enumeration/agent_phase.py +++ b/pyAmakCore/enumeration/agent_phase.py @@ -1,3 +1,6 @@ +""" +Agent phases +""" from enum import Enum, auto diff --git a/pyAmakCore/enumeration/executionPolicy.py b/pyAmakCore/enumeration/executionPolicy.py index 78fc09cd586362c974118a760a6dc53c0fbf6fd2..7adbd01392977e72e0a719d28a1ca57a5283e441 100644 --- a/pyAmakCore/enumeration/executionPolicy.py +++ b/pyAmakCore/enumeration/executionPolicy.py @@ -1,3 +1,6 @@ +""" +Agent execution policy +""" from enum import Enum, auto diff --git a/pyAmakCore/exception/mailbox.py b/pyAmakCore/exception/mailbox.py new file mode 100644 index 0000000000000000000000000000000000000000..f7e7dd00c1144b47debffb0eab4ea58b42e809c3 --- /dev/null +++ b/pyAmakCore/exception/mailbox.py @@ -0,0 +1,30 @@ +""" +exception for mailbox +""" + + +class ReceiverIsNotSelf(Exception): + """ + this exception is called whenever you received a mail that is not sent to you + """ + + def __init__(self, self_id, message_id): + self.self_id = self_id + self.message_id = message_id + super().__init__("Mail id is not self id") + + def __str__(self): + return f' [Mailbox] : Mail id is not self id -> {self.message_id} != {self.self_id}' + + +class ReceiverDontExist(Exception): + """ + this exception is called whenever you try to send a mail to someone that doesn't exist + """ + + def __init__(self, receiver_id): + self.receiver_id = receiver_id + super().__init__("receiver_id doesn't exist") + + def __str__(self): + return f' [Mailbox] : Receiver_id could not be found -> {self.receiver_id}' diff --git a/pyAmakCore/exception/override.py b/pyAmakCore/exception/override.py index 455dd26731c65c7596e5045dd33114843c91905c..4dcca40aab2646cd893104f5b93b74fc3d60b086 100644 --- a/pyAmakCore/exception/override.py +++ b/pyAmakCore/exception/override.py @@ -18,4 +18,7 @@ class ToOverrideWarning: @classmethod def enable_warning(cls, condition: bool) -> None: + """ + enable or disable custom warning for method that should be override + """ cls.__enable_warning = condition diff --git a/pyAmakCore/tests/memory_leak/main.py b/pyAmakCore/tests/memory_leak/main.py index 0210fbd3dc99c0d843fa42de40e0bced3df3e700..bf6126ad4094e2c0070beda58cda0a19adf4d0e1 100644 --- a/pyAmakCore/tests/memory_leak/main.py +++ b/pyAmakCore/tests/memory_leak/main.py @@ -1,42 +1,45 @@ +from pyAmakCore.classes.scheduler_mono_threading import SchedulerMono + +from pyAmakCore.exception.override import ToOverrideWarning +from pyAmakCore.classes.agent import Agent from pyAmakCore.classes.amas import Amas from pyAmakCore.classes.environment import Environment -from pyAmakCore.classes.agent import Agent +from pyAmakCore.classes.scheduler import Scheduler class SimpleAgent(Agent): - """ - test - """ + pass class SimpleAmas(Amas): - """ - test - """ + def on_initialization(self) -> None: + # self.set_do_log(True) + pass def on_initial_agents_creation(self) -> None: for i in range(10): self.add_agent(SimpleAgent(self)) - class SimpleEnv(Environment): - """ - test - """ + pass +import time +start_time = time.time() +ToOverrideWarning.enable_warning(False) env = SimpleEnv() amas = SimpleAmas(env) -amas.put_token() +#scheduler = Scheduler(amas) +scheduler = SchedulerMono(amas) -amas.start() +scheduler.start() +scheduler.run() +print("--- %s seconds ---" % (time.time() - start_time)) """ There are no visible memory leak in pyAmakCore -""" -""" TODO : We could test add agent / get most critical neighbor ... also -""" \ No newline at end of file +""" diff --git a/pyAmakCore/tests/test_agent/test_get_most_critical_neighbor.py b/pyAmakCore/tests/test_agent/test_get_most_critical_neighbor.py index 4dff5c44a84625b18c6826c1d0b88cd9a21b2a2c..c190a4a6be87f19a87bacd81998dcb1368adcd99 100644 --- a/pyAmakCore/tests/test_agent/test_get_most_critical_neighbor.py +++ b/pyAmakCore/tests/test_agent/test_get_most_critical_neighbor.py @@ -11,9 +11,6 @@ class SimpleAgent(Agent): def set_criticality(self, i): self._Agent__criticality = i - def get_most_critical(self, i): - return self._get_most_critical_neighbor(i) - class TestAgentGetMostCriticalNeighbor(TestCase): """ @@ -44,7 +41,7 @@ class TestAgentGetMostCriticalNeighbor(TestCase): n4.set_criticality(25) agent.add_neighbour(n4) - self.assertEqual(agent.get_most_critical(False), n4) + self.assertEqual(agent.get_most_critical_neighbor(False), n4) def test_get_most_critical_neighbor_with_self(self): Agent.reset_agent() @@ -70,7 +67,7 @@ class TestAgentGetMostCriticalNeighbor(TestCase): n4.set_criticality(25) agent.add_neighbour(n4) - self.assertEqual(agent.get_most_critical(True), agent) + self.assertEqual(agent.get_most_critical_neighbor(True), agent) if __name__ == '__main__': diff --git a/pyAmakCore/tests/test_agent/test_run.py b/pyAmakCore/tests/test_agent/test_run.py deleted file mode 100644 index 991cb562b9fa394cda7aff988ffaa8171da595e0..0000000000000000000000000000000000000000 --- a/pyAmakCore/tests/test_agent/test_run.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -test that run of Agent work as intended -""" -from unittest import TestCase, main - -from pyAmakCore.classes.agent import Agent -from pyAmakCore.classes.amas import Amas -from pyAmakCore.classes.environment import Environment -from pyAmakCore.enumeration.agent_phase import Phase -from pyAmakCore.enumeration.executionPolicy import ExecutionPolicy - - -class TestAgentRun(TestCase): - """ - Test class for Agent run - """ - - def test_run_one_phase(self): - """ - test that run() work and return correct phase with 1 phase policy - """ - Agent.reset_agent() - environment = Environment() - amas = Amas(environment) - amas.set_execution_policy(ExecutionPolicy.ONE_PHASE) - - agent = Agent(amas) - self.assertEqual(agent.get_phase(), Phase.INITIALIZING) - agent.run() - self.assertEqual(agent.get_phase(), Phase.DECISION_AND_ACTION_DONE) - agent.run() - self.assertEqual(agent.get_phase(), Phase.DECISION_AND_ACTION_DONE) - - def test_run_two_phase(self): - """ - test that run() work and return correct phase with 2 phase policy - """ - Agent.reset_agent() - environment = Environment() - amas = Amas(environment) - amas.set_execution_policy(ExecutionPolicy.TWO_PHASES) - - agent = Agent(amas) - self.assertEqual(agent.get_phase(), Phase.INITIALIZING) - agent.run() - self.assertEqual(agent.get_phase(), Phase.PERCEPTION_DONE) - agent.run() - self.assertEqual(agent.get_phase(), Phase.DECISION_AND_ACTION_DONE) - - -if __name__ == '__main__': - main() diff --git a/pyAmakCore/tests/test_amas/test_agent.py b/pyAmakCore/tests/test_amas/test_agent.py index 5726bce9394dd991bd47160002d645f03c2b2c9b..b5a9b187db038f3165b0ee0a2baaac9a722c03a5 100644 --- a/pyAmakCore/tests/test_amas/test_agent.py +++ b/pyAmakCore/tests/test_amas/test_agent.py @@ -8,6 +8,11 @@ from pyAmakCore.classes.amas import Amas from pyAmakCore.classes.environment import Environment from pyAmakCore.classes.agent import Agent +class SimplerAmas(Amas): + + def synchronization(self): + self._Amas__scheduler.give_amas_token() + super().synchronization() class TestAmasAgents(TestCase): """ @@ -33,30 +38,31 @@ class TestAmasAgents(TestCase): """ environment = Environment() - amas = Amas(environment) + amas = SimplerAmas(environment) agent1 = Agent(amas) agent2 = Agent(amas) agent3 = Agent(amas) - agent4 = Agent(amas) - agent5 = Agent(amas) - # add 1 agent amas.add_agent(agent1) + amas.add_pending_agent() self.assertEqual(amas.get_agents(), [agent1]) # don't remove previous agent amas.add_agent(agent2) + amas.add_pending_agent() self.assertEqual(amas.get_agents(), [agent1, agent2]) # add agent in good order amas.add_agent(agent3) + amas.add_pending_agent() self.assertEqual(amas.get_agents(), [agent1, agent2, agent3]) # don't add duplicate amas.add_agent(agent1) amas.add_agent(agent2) amas.add_agent(agent3) + amas.add_pending_agent() self.assertEqual(amas.get_agents(), [agent1, agent2, agent3]) def test_add_agents(self) -> None: @@ -65,7 +71,7 @@ class TestAmasAgents(TestCase): """ environment = Environment() - amas = Amas(environment) + amas = SimplerAmas(environment) agent1 = Agent(amas) agent2 = Agent(amas) @@ -74,36 +80,44 @@ class TestAmasAgents(TestCase): agent5 = Agent(amas) amas.add_agents([agent1, agent2, agent3, agent4, agent5]) + amas.add_pending_agent() self.assertEqual(amas.get_agents(), [agent1, agent2, agent3, agent4, agent5]) - amas = Amas(environment) + amas = SimplerAmas(environment) amas.add_agents([agent1, agent2]) + amas.add_pending_agent() self.assertEqual(amas.get_agents(), [agent1, agent2]) amas.add_agents([agent1, agent2, agent4, agent2, agent3]) + amas.add_pending_agent() self.assertEqual(amas.get_agents(), [agent1, agent2, agent4, agent3]) def test_remove_agent(self) -> None: environment = Environment() - amas = Amas(environment) + amas = SimplerAmas(environment) agent1 = Agent(amas) agent2 = Agent(amas) agent3 = Agent(amas) amas.remove_agent(agent2) + amas.remove_pending_agent() self.assertEqual(amas.get_agents(), []) amas.add_agents([agent1, agent2, agent3]) + amas.add_pending_agent() amas.remove_agent(agent2) + amas.remove_pending_agent() self.assertEqual(amas.get_agents(), [agent1, agent3]) amas.remove_agent(agent2) + amas.remove_pending_agent() self.assertEqual(amas.get_agents(), [agent1, agent3]) amas.remove_agent(agent1) amas.remove_agent(agent3) + amas.remove_pending_agent() self.assertEqual(amas.get_agents(), []) diff --git a/pyAmakCore/tests/test_amas/test_nbr_cycle.py b/pyAmakCore/tests/test_amas/test_nbr_cycle.py index 7bc3a8e2cb5e871c9b05c15fcf8898fe19eddb00..e4341b38daeb7eb8fa9f24a3bf6f2b0e86e3856e 100644 --- a/pyAmakCore/tests/test_amas/test_nbr_cycle.py +++ b/pyAmakCore/tests/test_amas/test_nbr_cycle.py @@ -12,7 +12,7 @@ from pyAmakCore.classes.environment import Environment class SimplerAmas(Amas): def synchronization(self): - self.scheduler.give_amas_token() + self._Amas__scheduler.give_amas_token() super().synchronization() diff --git a/pyAmakCore/tests/test_communicating_agent/__init__.py b/pyAmakCore/tests/test_communicating_agent/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/pyAmakCore/tests/test_communicating_agent/test_mail.py b/pyAmakCore/tests/test_communicating_agent/test_mail.py new file mode 100644 index 0000000000000000000000000000000000000000..444505df65d027c74ab77e81a8d50fa82d365a2f --- /dev/null +++ b/pyAmakCore/tests/test_communicating_agent/test_mail.py @@ -0,0 +1,32 @@ +""" +test that Mail work as intended +""" +from unittest import TestCase, main + +from pyAmakCore.classes.communicating_agent import Mail + + +class TestMail(TestCase): + """ + Test class Mail + """ + + def test_mail(self) -> None: + """ + Test mail init + """ + mail = Mail(1, 5, None, 0) + + self.assertEqual(mail.get_id_sender(), 1) + self.assertEqual(mail.get_id_receiver(), 5) + self.assertEqual(mail.get_message(), None) + self.assertEqual(mail.get_sending_date(), 0) + + mail = Mail(255, 0, "test", 12) + self.assertEqual(mail.get_id_sender(), 255) + self.assertEqual(mail.get_id_receiver(), 0) + self.assertEqual(mail.get_message(), "test") + self.assertEqual(mail.get_sending_date(), 12) + +if __name__ == '__main__': + main() diff --git a/pyAmakCore/tests/test_communicating_agent/test_mailbox.py b/pyAmakCore/tests/test_communicating_agent/test_mailbox.py new file mode 100644 index 0000000000000000000000000000000000000000..905820cd1ded49d0c0cca67ff5c6168ef34695c2 --- /dev/null +++ b/pyAmakCore/tests/test_communicating_agent/test_mailbox.py @@ -0,0 +1,45 @@ +""" +test that Mailbox work as intended +""" +from unittest import TestCase, main + +from pyAmakCore.classes.amas import Amas +from pyAmakCore.classes.communicating_agent import Mailbox +from pyAmakCore.classes.environment import Environment + + +class SimpleMailbox(Mailbox): + + def get_amas(self): + return self._Mailbox__amas + + def get_owner_id(self): + return self._Mailbox__owner_id + + def get_mail_list(self): + return self._Mailbox__mail_list + + +class TestMailbox(TestCase): + """ + Test class Mailbox + """ + + def test_init_mailbox(self) -> None: + """ + Test mailbox init + """ + + environment = Environment() + amas = Amas(environment) + + mailbox = SimpleMailbox(0, amas) + + self.assertEqual(mailbox.get_amas(), amas) + self.assertEqual(mailbox.get_owner_id(), 0) + self.assertEqual(mailbox.get_mail_list(), []) + self.assertEqual(mailbox.get_mail(), None) + + +if __name__ == '__main__': + main() diff --git a/release/changelog.txt b/release/changelog.txt new file mode 100644 index 0000000000000000000000000000000000000000..92fbf8586225a596d06bf490eb5cc7b5c492fd4a --- /dev/null +++ b/release/changelog.txt @@ -0,0 +1,17 @@ +v0.1.0: + WARNING : all previous example will no longer work in this version, and all v0.1.0+ example won't work in previous version + * Way better thread management + * Add save and load + +v0.0.5: + * Fix rare bugs : an agent that would be removed in the cycle could be called by another agent + * CSV : Add method : add_ignore_attribute(self, attribute: str) -> None, remove_ignore_attribute(self, attribute: str) -> None + to manage ignored attribute + +v0.0.4: + * add AmasIHM (old examples will no longer work from now on if using AmasIHM ) + +v0.0.3 : + * Add communicating agent + +v0.0.2 diff --git a/release/pyAmakCore-0.0.2-py3-none-any.whl b/release/pyAmakCore-0.0.2-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..6cdca8943d9a7dc1f3bb5b900aea91220a4a16e3 Binary files /dev/null and b/release/pyAmakCore-0.0.2-py3-none-any.whl differ diff --git a/release/pyAmakCore-0.0.3-py3-none-any.whl b/release/pyAmakCore-0.0.3-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..9a5070130b134c9adc743c60e39fb2ca73374c8e Binary files /dev/null and b/release/pyAmakCore-0.0.3-py3-none-any.whl differ diff --git a/release/pyAmakCore-0.0.4-py3-none-any.whl b/release/pyAmakCore-0.0.4-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..904c43441aa8ff18808817250b162d8ea6d94d88 Binary files /dev/null and b/release/pyAmakCore-0.0.4-py3-none-any.whl differ diff --git a/release/pyAmakCore-0.0.5-py3-none-any.whl b/release/pyAmakCore-0.0.5-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..1475c4083eac33230b399e4722b27ce678b43692 Binary files /dev/null and b/release/pyAmakCore-0.0.5-py3-none-any.whl differ diff --git a/release/pyAmakCore-0.1.0-py3-none-any.whl b/release/pyAmakCore-0.1.0-py3-none-any.whl new file mode 100644 index 0000000000000000000000000000000000000000..d177b6bcb2b542909ce76368d6b1cff0e4260035 Binary files /dev/null and b/release/pyAmakCore-0.1.0-py3-none-any.whl differ diff --git a/requirements.txt b/requirements.txt index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1411a4a0b5ab886adfb744e685d150151ab10023 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1 @@ +pandas \ No newline at end of file diff --git a/setup.py b/setup.py index a8ca080289a4566be251c03b56a40094f8028dd9..3d73d5dccd4434e871c2cf07eb5550230a49ab19 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ from setuptools import setup, find_packages setup( name='pyAmakCore', packages=find_packages(), - version='0.0.2', + version='0.1.0', description='AmakFramework in python', author='BE', install_requires=[],