diff --git a/DOCS.md b/DOCS.md index 44e7cda..2d8ec48 100644 --- a/DOCS.md +++ b/DOCS.md @@ -264,6 +264,8 @@ Types: `.` (`msg.prm`) - Allows to access the value of `prm` from `msg`. +`LOGS level: {debug, info, warning, error, critical}, name0: Any, name1: Any, ...` - Logs a message with specified `level` and `name0`, `name1`, ... as arguments. + ## Message Scope ### Parameters diff --git a/aasm/__init__.py b/aasm/__init__.py index 02992b8..017f2d3 100644 --- a/aasm/__init__.py +++ b/aasm/__init__.py @@ -1,7 +1,9 @@ """Agents Assembly translator""" -__version__ = "0.0.57" +__version__ = "0.0.58" from aasm.generating.code import Code +from aasm.modules.module import Module from aasm.generating.python_spade import get_spade_code +from aasm.generating.python_module import get_modules_for_target from aasm.utils.exception import PanicException diff --git a/aasm/generating/code.py b/aasm/generating/code.py index 8eaacaa..71d5bfc 100644 --- a/aasm/generating/code.py +++ b/aasm/generating/code.py @@ -4,15 +4,27 @@ class Code: - def __init__(self, agent_code_lines: List[str], graph_code_lines: List[str]): + def __init__( + self, + agent_code_lines: List[str], + graph_code_lines: List[str], + module_code_lines: List[str], + ): self.agent_code_lines: List[str] = agent_code_lines self.graph_code_lines: List[str] = graph_code_lines + self.module_code_lines: List[str] = module_code_lines self._iter_code_lines: List[str] | None = None self._iter_idx: int | None = None self._iter_num_code_lines: int | None = None def __iter__(self) -> Code: - self._iter_code_lines = self.agent_code_lines + ["\n"] + self.graph_code_lines + self._iter_code_lines = ( + self.agent_code_lines + + ["\n"] + + self.graph_code_lines + + ["\n"] + + self.module_code_lines + ) self._iter_num_code_lines = len(self._iter_code_lines) self._iter_idx = -1 return self diff --git a/aasm/generating/python_code.py b/aasm/generating/python_code.py index 8a94c43..e722439 100644 --- a/aasm/generating/python_code.py +++ b/aasm/generating/python_code.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import List +from typing import List, Set class PythonCode: @@ -8,6 +8,7 @@ def __init__(self, indent_size: int): self.indent_size = indent_size self.indent: int = 0 self.code_lines: List[str] = [] + self.required_imports: Set[str] = set() def indent_left(self) -> None: self.indent -= self.indent_size @@ -15,8 +16,15 @@ def indent_left(self) -> None: def indent_right(self) -> None: self.indent += self.indent_size - def add_line(self, line: str) -> None: - self.code_lines.append(self.indent * " " + line + "\n") + def add_line( + self, line: str, required_imports: Set[str] | None = None, add_newline=True + ) -> None: + if required_imports is not None: + self.required_imports.update(required_imports) + if add_newline: + self.code_lines.append(self.indent * " " + line + "\n") + else: + self.code_lines.append(self.indent * " " + line) def add_newline(self) -> None: self.add_line("") @@ -25,18 +33,9 @@ def add_newlines(self, count: int) -> None: for _ in range(count): self.add_newline() - def add_template(self, template: str, **kwargs: Any) -> None: - lines = template.render(kwargs).splitlines() - current_indent = 0 - for line in lines: - space_count = len(line) - len( - line.lstrip(" ") - ) # templates should have 4 space indents per pep8 - indent_count = space_count // 4 - if indent_count < current_indent: - for _ in range(current_indent - indent_count): - self.indent_left() - elif indent_count > current_indent: - for _ in range(indent_count - current_indent): - self.indent_right() - self.add_line(line.strip()) + def add_required_imports(self) -> None: + lines: List[str] = [] + for required_import in self.required_imports: + lines.append(f"import {required_import}\n") + lines.sort() + self.code_lines = lines + self.code_lines diff --git a/aasm/generating/python_graph.py b/aasm/generating/python_graph.py index 63fc8f3..448c4ea 100644 --- a/aasm/generating/python_graph.py +++ b/aasm/generating/python_graph.py @@ -13,6 +13,8 @@ ConnectionDistUniformAmount, MatrixGraph, StatisticalGraph, + BarabasiGraph, + InhomogenousRandomGraph, ) if TYPE_CHECKING: @@ -25,8 +27,48 @@ def __init__(self, indent_size: int, graph: Graph | None): if graph: self.add_required_imports() self.add_newlines(2) + self.add_class_definitions() + self.add_newlines(2) self.generate_graph(graph) + def add_class_definitions(self): + self.add_line("class Agent:") + self.indent_right() + self.add_line( + "def __init__(self, jid: str, type: str, connections = None, sim_id: str = " + "):" + ) + self.indent_right() + self.add_line("self.jid = jid") + self.add_line("self.type = type") + self.add_line("self.sim_id = sim_id") + self.add_line("if connections is not None:") + self.indent_right() + self.add_line("self.connections = connections") + self.indent_left() + self.add_line("else:") + self.indent_right() + self.add_line("self.connections = []") + self.indent_left() + self.indent_left() + self.add_newline() + self.add_line("def add_connection(self, connection: str):") + self.indent_right() + self.add_line("self.connections.append(connection)") + self.indent_left() + self.add_line("def to_dict(self):") + self.indent_right() + self.add_line("return {") + self.indent_right() + self.add_line("'jid': self.jid,") + self.add_line("'type': self.type,") + self.add_line("'connections': self.connections") + self.add_line("'sim_id': self.sim_id") + self.add_line("}") + self.indent_left() + self.indent_left() + self.indent_left() + def add_required_imports(self) -> None: self.add_line("import random") self.add_line("import uuid") @@ -40,18 +82,23 @@ def generate_graph(self, graph: Graph) -> None: case MatrixGraph(): self.add_matrix_graph(graph) + case BarabasiGraph(): + self.add_barabasi_graph(graph) + + case InhomogenousRandomGraph(): + self.add_irg_graph(graph) + case _: raise Exception(f"Unknown graph type: {graph.print()}") def add_statistical_graph(self, graph: StatisticalGraph) -> None: - self.add_line("def generate_graph_structure(domain):") + self.add_line('def generate_graph_structure(domain, sim_id=""):') self.indent_right() if not graph.agents: self.add_line("return []") self.indent_left() return - num_agents_expr: List[str] = [] for agent in graph.agents.values(): match agent.amount: @@ -71,7 +118,15 @@ def add_statistical_graph(self, graph: StatisticalGraph) -> None: num_agents_expr.append(f"_num_{agent.name}") self.add_line(f'num_agents = {" + ".join(num_agents_expr)}') + + self.add_line('if sim_id == "":') + self.indent_right() self.add_line("random_id = str(uuid.uuid4())[:5]") + self.indent_left() + self.add_line("else:") + self.indent_right() + self.add_line("random_id = sim_id") + self.indent_left() self.add_line('jids = [f"{i}_{random_id}@{domain}" for i in range(num_agents)]') self.add_line("agents = []") self.add_line("next_agent_idx = 0") @@ -114,6 +169,7 @@ def add_statistical_graph(self, graph: StatisticalGraph) -> None: self.add_line( '"connections": random.sample([other_jid for other_jid in jids if other_jid != jid], num_connections),' ) + self.add_line('"sim_id": random_id,') self.indent_left() self.add_line("})") self.add_line("next_agent_idx += 1") @@ -123,7 +179,7 @@ def add_statistical_graph(self, graph: StatisticalGraph) -> None: self.indent_left() def add_matrix_graph(self, graph: MatrixGraph): - self.add_line("def generate_graph_structure(domain):") + self.add_line('def generate_graph_structure(domain, sim_id=""):') self.indent_right() if not graph.agents: self.add_line("return []") @@ -135,15 +191,26 @@ def add_matrix_graph(self, graph: MatrixGraph): self.add_line(f"scale_factor = 1") self.add_line(f"n_agent_types = {len(graph.agents)}") self.add_line("graph_size = scale_factor * n_agent_types") + self.add_line('if sim_id == "":') + self.indent_right() self.add_line("random_id = str(uuid.uuid4())[:5]") + self.indent_left() + self.add_line("else:") + self.indent_right() + self.add_line("random_id = sim_id") + self.indent_left() self.add_line('jids = [f"{i}_{random_id}@{domain}" for i in range(graph_size)]') + self.add_line("agent_types = []") self.add_line("agents = []") self.add_line("indx_sets = []") for agent in graph.agents: + self.add_line(f"agent_types.append('{agent.name}')") adj_indx = [True if x == 1 else False for x in agent.adj_row.row] adj_indx = list(compress(range(len(adj_indx)), adj_indx)) self.add_line(f"indx_sets.append({adj_indx})") + self.add_line("agent_types = agent_types * scale_factor") + self.add_line("for base_agent_index in range(n_agent_types):") self.indent_right() self.add_line("indices = indx_sets[base_agent_index]") @@ -155,7 +222,9 @@ def add_matrix_graph(self, graph: MatrixGraph): self.indent_left() self.add_line("for shift in range(scale_factor):") self.indent_right() - self.add_line("jid = jids[base_agent_index + shift * n_agent_types]") + self.add_line("agent_idx = base_agent_index + shift * n_agent_types") + self.add_line("jid = jids[agent_idx]") + self.add_line("agent_type = agent_types[agent_idx]") self.add_line("connections = []") self.add_line("for i in indices:") self.indent_right() @@ -166,8 +235,9 @@ def add_matrix_graph(self, graph: MatrixGraph): self.add_line("agents.append({") self.indent_right() self.add_line('"jid": jid,') - self.add_line(f'"type": "{agent.name}",') + self.add_line(f'"type": agent_type,') self.add_line('"connections": connections,') + self.add_line('"sim_id": random_id,') self.indent_left() self.add_line("})") self.indent_left() @@ -175,3 +245,164 @@ def add_matrix_graph(self, graph: MatrixGraph): self.indent_left() self.add_line("return agents") self.indent_left() + + def add_barabasi_graph(self, graph: BarabasiGraph): + # generate jid list using draw by draw Barabasi-Albert algorithm for the provided graph with its m0 and m + self.add_line('def generate_graph_structure(domain, sim_id=""):') + self.indent_right() + if not graph.agents: + self.add_line("return []") + self.indent_left() + return + + self.add_line(f"agent_types = []") + + num_agents_expr: List[str] = [] + for agent in graph.agents.values(): + match agent.amount: + case AgentConstantAmount(): + self.add_line(f"_num_{agent.name} = {agent.amount.value}") + + case AgentPercentAmount(): + self.add_line( + f"_num_{agent.name} = round({agent.amount.value} / 100 * {graph.size})" + ) + + case _: + raise Exception( + f"Unknown agent amount type: {agent.amount.print()}" + ) + + num_agents_expr.append(f"_num_{agent.name}") + self.add_line(f"tmp = ['{agent.name}'] * _num_{agent.name}") + self.add_line("agent_types.extend(tmp)") + + self.add_line(f'num_agents = {" + ".join(num_agents_expr)}') + self.add_line("random.shuffle(agent_types)") + + self.add_line('if sim_id == "":') + self.indent_right() + self.add_line("random_id = str(uuid.uuid4())[:5]") + self.indent_left() + self.add_line("else:") + self.indent_right() + self.add_line("random_id = sim_id") + self.indent_left() + if graph.m_params is not None: + self.add_line(f"m0 = {graph.m_params.m0}") + self.add_line(f"m = {graph.m_params.m_inc}") + self.add_line('jids = [f"{i}_{random_id}@{domain}" for i in range(num_agents)]') + # self.add_line("agents = []") + self.add_line("connection_lists = []") + self.add_line("next_agent_idx = 0") + # begin by creating the initial complete graph + self.add_line("init_jids = random.choices(jids, k=m0)") + self.add_line("for jid in init_jids:") + self.indent_right() + self.add_line("copy = init_jids.copy()") + self.add_line("copy.remove(jid)") + self.add_line("connection_lists.append(copy)") + self.indent_left() + self.add_line("next_agent_idx += m0") + self.add_line("for i in range(m0, num_agents):") + self.indent_right() + self.add_line("to_connect = []") + self.add_line("ids = list(range(next_agent_idx))") + self.add_line("while len(to_connect) < m:") + self.indent_right() + self.add_line("weights = [len(connection_lists[i]) for i in ids]") + self.add_line("to_connect.append(random.choices(ids, weights=weights)[0])") + self.add_line("ids.remove(to_connect[-1])") + self.indent_left() + self.add_line("to_connect_jids = [jids[i] for i in to_connect]") + self.add_line("connection_lists.append(to_connect_jids)") + self.add_line("for id in to_connect:") + self.indent_right() + # two sided connections + self.add_line("connection_lists[id].append(jids[next_agent_idx])") + self.indent_left() + self.add_line("next_agent_idx += 1") + self.indent_left() + self.add_line("agents = []") + self.add_line("for i in range(num_agents):") + self.indent_right() + self.add_line("agents.append({") + self.indent_right() + self.add_line('"jid": jids[i],') + self.add_line('"type": agent_types[i],') + self.add_line('"connections": connection_lists[i],') + self.add_line('"sim_id": random_id,') + self.indent_left() + self.add_line("})") + self.indent_left() + self.add_line("return agents") + self.indent_left() + + def add_irg_graph(self, graph: InhomogenousRandomGraph): + self.add_line('def generate_graph_structure(domain, sim_id=""):') + self.indent_right() + if not graph.agents: + self.add_line("return []") + self.indent_left() + return + self.add_line("agent_types = []") + self.add_line("probo_matrix = {}") + num_agents_expr: List[str] = [] + for agent in graph.agents.values(): + self.add_line(f"probo_matrix['{agent.name}'] = []") + for probo in agent.amounts: + self.add_line(f"probo_matrix['{agent.name}'].append({probo.value})") + match agent.amount: + case AgentConstantAmount(): + self.add_line(f"_num_{agent.name} = {agent.amount.value}") + + case AgentPercentAmount(): + self.add_line( + f"_num_{agent.name} = round({agent.amount.value} / 100 * {graph.size})" + ) + + case _: + raise Exception( + f"Unknown agent amount type: {agent.amount.print()}" + ) + + num_agents_expr.append(f"_num_{agent.name}") + self.add_line(f"tmp = ['{agent.name}'] * _num_{agent.name}") + self.add_line("agent_types.extend(tmp)") + + self.add_line(f'num_agents = {" + ".join(num_agents_expr)}') + + self.add_line('if sim_id == "":') + self.indent_right() + self.add_line("random_id = str(uuid.uuid4())[:5]") + self.indent_left() + self.add_line("else:") + self.indent_right() + self.add_line("random_id = sim_id") + self.indent_left() + self.add_line('jids = [f"{i}_{random_id}@{domain}" for i in range(num_agents)]') + self.add_line(f"order = {graph.order}") + self.add_line("agents = []") + self.add_line("for i in range(num_agents):") + self.indent_right() + self.add_line( + "agents.append(Agent(jids[i], agent_types[i], sim_id = random_id))" + ) + self.indent_left() + self.add_line("for agent in agents:") + self.indent_right() + self.add_line("for agent2 in agents:") + self.indent_right() + self.add_line("if agent != agent2:") + self.indent_right() + self.add_line("conn_index = order.index(agent2.type)") + self.add_line("probo = probo_matrix[agent.type][conn_index]") + self.add_line("if random.random() * 100 < probo:") + self.indent_right() + self.add_line("agent.add_connection(agent2.jid)") + self.indent_left() + self.indent_left() + self.indent_left() + self.indent_left() + self.add_line("return [agent.to_dict() for agent in agents]") + self.indent_left() diff --git a/aasm/generating/python_module.py b/aasm/generating/python_module.py new file mode 100644 index 0000000..b3a004c --- /dev/null +++ b/aasm/generating/python_module.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, List + +from aasm.generating.python_code import PythonCode +from aasm.modules.module import Module + + +def get_modules_for_target( + module_code_lines: List[List[str]], target: str +) -> List[Module]: + """ + Get all modules from `module_code_lines` that target the given `target`. + + :param module_code_lines: the code lines of all modules + :param target: the target to filter by + + :return: the modules that target the given `target` + """ + target_modules = [] + for module_lines in module_code_lines: + module = Module(module_lines) + if module.does_target(target): + target_modules.append(module) + return target_modules + + +class PythonModule(PythonCode): + def __init__(self, indent_size, module: Module): + super().__init__(indent_size) + self.module: Module = module + self.target: str = "spade" + self.add_newlines(2) + self.create_module_header() + self.add_newline() + self.create_module_imports() + self.add_newline() + self.create_module_implementations() + + def create_module_header(self) -> None: + self.add_line(f"# Module: {self.module.name}") + for line in self.module.description: + self.add_line(f"# {line}", add_newline=False) + + def create_module_imports(self): + try: + preamble = self.module.preambles[self.target] + for line in preamble: + self.add_line(line) + except KeyError: + return + + def create_module_implementations(self): + for impl in self.module.impls: + if impl[0] == self.target: + self.add_line(f"def {impl[1]}():") + for line in self.module.impls[impl]: + self.add_line(line) + self.add_newline() diff --git a/aasm/generating/python_spade.py b/aasm/generating/python_spade.py index 65dff5e..990ca8f 100644 --- a/aasm/generating/python_spade.py +++ b/aasm/generating/python_spade.py @@ -5,6 +5,8 @@ from aasm.generating.code import Code from aasm.generating.python_code import PythonCode from aasm.generating.python_graph import PythonGraph +from aasm.generating.python_module import PythonModule + from aasm.intermediate.action import SendMessageAction from aasm.intermediate.argument import ( AgentParam, @@ -19,7 +21,11 @@ ) from aasm.intermediate.behaviour import MessageReceivedBehaviour from aasm.intermediate.block import Block -from aasm.intermediate.declaration import ConnectionDeclaration, FloatDeclaration +from aasm.intermediate.declaration import ( + ConnectionDeclaration, + FloatDeclaration, + ModuleVariableDeclaration, +) from aasm.intermediate.instruction import ( Add, AddElement, @@ -42,6 +48,12 @@ ListRead, ListWrite, Logarithm, + Logs, + LogsCritical, + LogsDebug, + LogsError, + LogsInfo, + LogsWarning, MathOperation, Modulo, Multiply, @@ -63,6 +75,7 @@ WhileLessThan, WhileLessThanOrEqual, WhileNotEqual, + ModuleInstruction, ) from aasm.parsing.parse import parse_lines @@ -72,10 +85,14 @@ from aasm.intermediate.argument import Argument from aasm.intermediate.behaviour import Behaviour from aasm.intermediate.message import Message as IntermediateMessage + from aasm.modules.module import Module def get_spade_code( - aasm_lines: List[str], indent_size: int = 4, debug: bool = False + aasm_lines: List[str], + indent_size: int = 4, + debug: bool = False, + modules: None | List[Module] = None, ) -> Code: """Generates SPADE code in Python from `aasm_lines`. @@ -90,6 +107,9 @@ def get_spade_code( debug: bool, optional Print the translator debug information to the standard output + modules: List[Module], optional + A list of Agents Assembly Modules compatible with SPADE platform + Returns ------- SPADE code along with the algorithm for the graph generation @@ -99,34 +119,52 @@ def get_spade_code( PanicException If an error is detected while parsing the `aasm_lines`. """ - parsed = parse_lines(aasm_lines, debug) + if modules is None: + modules = [] + parsed = parse_lines(aasm_lines, debug, modules) + module_code_lines = [] + for module in modules: + module_code_lines += PythonModule(indent_size, module).code_lines return Code( - PythonSpadeCode(indent_size, parsed.agents).code_lines, + PythonSpadeCode(indent_size, parsed.agents, modules).code_lines, PythonGraph(indent_size, parsed.graph).code_lines, + module_code_lines, ) class PythonSpadeCode(PythonCode): - def __init__(self, indent_size: int, agents: List[Agent]): + def __init__(self, indent_size: int, agents: List[Agent], modules: List[Module]): super().__init__(indent_size) - if agents: + self.modules = modules + self.target = "spade" + self.filter_modules() + self.add_module_imports() + for agent in agents: + self.add_newlines(2) + self.generate_agent(agent) self.add_required_imports() - for agent in agents: - self.add_newlines(2) - self.generate_agent(agent) - - def add_required_imports(self) -> None: - self.add_line("import copy") - self.add_line("import datetime") - self.add_line("import random") - self.add_line("import httpx") - self.add_line("import numpy") - self.add_line("import orjson") - self.add_line("import spade") - self.add_line("import sys") + + def filter_modules(self): + self.modules = [ + target_mod + for target_mod in self.modules + for target in target_mod.targets + if target.name == self.target + ] + + def add_module_imports(self): + for module in self.modules: + self.add_line(f"import {module.name}") + + # spade_modules = [] + # for target_mod in self.modules: + # for target in target_mod.targets: + # if target.name == self.target: + # spade_modules.append(target_mod) + # self.modules = spade_modules def generate_agent(self, agent: Agent) -> None: - self.add_line(f"class {agent.name}(spade.agent.Agent):") + self.add_line(f"class {agent.name}(spade.agent.Agent):", {"spade"}) self.indent_right() self.add_agent_constructor(agent) @@ -202,14 +240,16 @@ def add_agent_constructor(self, agent: Agent) -> None: mean = f"self.limit_number({dist_normal_float_param.mean})" std_dev = f"self.limit_number({dist_normal_float_param.std_dev})" self.add_line( - f'self.{name} = self.limit_number(kwargs.get("{name}", numpy.random.normal({mean}, {std_dev})))' + f'self.{name} = self.limit_number(kwargs.get("{name}", numpy.random.normal({mean}, {std_dev})))', + {"numpy"}, ) for dist_exp_float_param in agent.dist_exp_floats.values(): name = dist_exp_float_param.name lambda_ = f"self.limit_number({dist_exp_float_param.lambda_})" self.add_line( - f'self.{name} = self.limit_number(kwargs.get("{name}", numpy.random.exponential(self.limit_number(1 / {lambda_}))))' + f'self.{name} = self.limit_number(kwargs.get("{name}", numpy.random.exponential(self.limit_number(1 / {lambda_}))))', + {"numpy"}, ) for dist_uniform_float_param in agent.dist_unifrom_floats.values(): @@ -217,7 +257,8 @@ def add_agent_constructor(self, agent: Agent) -> None: a = f"self.limit_number({dist_uniform_float_param.a})" b = f"self.limit_number({dist_uniform_float_param.b})" self.add_line( - f'self.{name} = self.limit_number(kwargs.get("{name}", random.uniform({a}, {b})))' + f'self.{name} = self.limit_number(kwargs.get("{name}", random.uniform({a}, {b})))', + {"random"}, ) for enum_param in agent.enums.values(): @@ -229,7 +270,8 @@ def add_agent_constructor(self, agent: Agent) -> None: values = f'[{", ".join(value_list)}]' percentages = f'[{", ".join(percentage_list)}]' self.add_line( - f'self.{enum_param.name} = kwargs.get("{enum_param.name}", random.choices({values}, {percentages})[0])' + f'self.{enum_param.name} = kwargs.get("{enum_param.name}", random.choices({values}, {percentages})[0])', + {"random"}, ) for connection_list_param in agent.connection_lists.values(): @@ -270,17 +312,19 @@ def add_float_utils(self) -> None: def add_message_utils(self) -> None: self.add_line("def get_json_from_spade_message(self, msg):") self.indent_right() - self.add_line("return orjson.loads(msg.body)") + self.add_line("return orjson.loads(msg.body)", {"orjson"}) self.indent_left() self.add_newline() self.add_line("def get_spade_message(self, receiver_jid, body):") self.indent_right() - self.add_line("msg = spade.message.Message(to=receiver_jid)") + self.add_line("msg = spade.message.Message(to=receiver_jid)", {"spade"}) self.add_line('body["sender"] = str(self.jid)') self.add_line('msg.metadata["type"] = body["type"]') self.add_line('msg.metadata["performative"] = body["performative"]') - self.add_line('msg.body = str(orjson.dumps(body), encoding="utf-8")') + self.add_line( + 'msg.body = str(orjson.dumps(body), encoding="utf-8")', {"orjson"} + ) self.add_line("return msg") self.indent_left() @@ -293,7 +337,8 @@ def add_agent_setup(self, agent: Agent) -> None: self.indent_right() self.add_no_match_template("BackupBehaviour") self.add_line( - "self.add_behaviour(self.BackupBehaviour(start_at=datetime.datetime.now() + datetime.timedelta(seconds=self.backup_delay), period=self.backup_period), BackupBehaviour_template)" + "self.add_behaviour(self.BackupBehaviour(start_at=datetime.datetime.now() + datetime.timedelta(seconds=self.backup_delay), period=self.backup_period), BackupBehaviour_template)", + {"datetime"}, ) self.indent_left() @@ -306,7 +351,8 @@ def add_agent_setup(self, agent: Agent) -> None: for one_time_behaviour in agent.one_time_behaviours.values(): self.add_no_match_template(f"{one_time_behaviour.name}") self.add_line( - f"self.add_behaviour(self.{one_time_behaviour.name}(start_at=datetime.datetime.now() + datetime.timedelta(seconds={one_time_behaviour.delay})), {one_time_behaviour.name}_template)" + f"self.add_behaviour(self.{one_time_behaviour.name}(start_at=datetime.datetime.now() + datetime.timedelta(seconds={one_time_behaviour.delay})), {one_time_behaviour.name}_template)", + {"datetime"}, ) for cyclic_behaviour in agent.cyclic_behaviours.values(): @@ -317,7 +363,8 @@ def add_agent_setup(self, agent: Agent) -> None: for message_received_behaviour in agent.message_received_behaviours.values(): self.add_line( - f"{message_received_behaviour.name}_template = spade.template.Template()" + f"{message_received_behaviour.name}_template = spade.template.Template()", + {"spade"}, ) self.add_line( f'{message_received_behaviour.name}_template.set_metadata("type", "{message_received_behaviour.received_message.type}")' @@ -336,18 +383,22 @@ def add_agent_setup(self, agent: Agent) -> None: self.indent_left() def add_no_match_template(self, behaviour_name: str) -> None: - self.add_line(f"{behaviour_name}_template = spade.template.Template()") + self.add_line( + f"{behaviour_name}_template = spade.template.Template()", {"spade"} + ) self.add_line( f'{behaviour_name}_template.set_metadata("reserved", "no_message_match")' ) def add_backup_behaviour(self, agent: Agent) -> None: - self.add_line("class BackupBehaviour(spade.behaviour.PeriodicBehaviour):") + self.add_line( + "class BackupBehaviour(spade.behaviour.PeriodicBehaviour):", {"spade"} + ) self.indent_right() self.add_line("def __init__(self, start_at, period):") self.indent_right() self.add_line("super().__init__(start_at=start_at, period=period)") - self.add_line("self.http_client = httpx.AsyncClient(timeout=period)") + self.add_line("self.http_client = httpx.AsyncClient(timeout=period)", {"httpx"}) self.indent_left() self.add_newline() @@ -356,7 +407,8 @@ def add_backup_behaviour(self, agent: Agent) -> None: self.add_line("data = {") self.indent_right() self.add_line( - '"__timestamp__": int(datetime.datetime.timestamp(datetime.datetime.utcnow())),' + '"__timestamp__": int(datetime.datetime.timestamp(datetime.datetime.utcnow())),', + {"datetime"}, ) self.add_line('"jid": str(self.agent.jid),') self.add_line(f'"type": "{agent.name}",') @@ -416,7 +468,8 @@ def add_backup_behaviour(self, agent: Agent) -> None: self.add_line("try:") self.indent_right() self.add_line( - 'await self.http_client.post(self.agent.backup_url, headers={"Content-Type": "application/json"}, data=orjson.dumps(data))' + 'await self.http_client.post(self.agent.backup_url, headers={"Content-Type": "application/json"}, data=orjson.dumps(data))', + {"orjson"}, ) self.indent_left() self.add_line("except Exception as e:") @@ -452,7 +505,7 @@ def add_backup_behaviour(self, agent: Agent) -> None: self.indent_left() def add_agent_behaviour(self, behaviour: Behaviour, behaviour_type: str) -> None: - self.add_line(f"class {behaviour.name}({behaviour_type}):") + self.add_line(f"class {behaviour.name}({behaviour_type}):", {"spade"}) self.indent_right() for action in behaviour.actions.values(): @@ -543,7 +596,10 @@ def add_send_message(self, message: IntermediateMessage) -> None: def parse_arg(self, arg: Argument) -> str: match arg.type_in_op: case AgentParam(): - return f"self.agent.{arg.expr}" + if arg.expr == "self": + return "self.agent.jid" + else: + return f"self.agent.{arg.expr}" case EnumValue(): return f'"{arg.expr}"' @@ -585,6 +641,12 @@ def add_block(self, block: Block) -> None: value = f"{self.parse_arg(statement.value)}" self.add_line(f"{statement.name} = {value}") + case ModuleVariableDeclaration(): + self.add_line("") + self.add_line("# module variable declaration") + value = f"{self.parse_arg(statement.value)}" + self.add_line(f"{statement.name} = {value}") + case Subset(): self.add_line("") self.add_line("# subset") @@ -600,7 +662,8 @@ def add_block(self, block: Block) -> None: self.add_line("return") self.indent_left() self.add_line( - f"{dst_list} = [copy.deepcopy(elem) for elem in random.sample({src_list}, min({num}, {src_list_len}))]" + f"{dst_list} = [copy.deepcopy(elem) for elem in random.sample({src_list}, min({num}, {src_list_len}))]", + {"copy", "random"}, ) case Clear(): @@ -657,7 +720,8 @@ def add_block(self, block: Block) -> None: self.add_line("return") self.indent_left() self.add_line( - f'{msg} = copy.deepcopy(random.choice(list(filter(lambda msg: msg["type"] == {msg}["type"] and msg["performative"] == {msg}["performative"], {msg_list}))))' + f'{msg} = copy.deepcopy(random.choice(list(filter(lambda msg: msg["type"] == {msg}["type"] and msg["performative"] == {msg}["performative"], {msg_list}))))', + {"copy", "random"}, ) case Set() if isinstance(statement.value.type_in_op, Float): @@ -689,7 +753,8 @@ def add_block(self, block: Block) -> None: a = f"self.agent.limit_number({self.parse_arg(statement.a)})" b = f"self.agent.limit_number({self.parse_arg(statement.b)})" self.add_line( - f"{dst} = self.agent.limit_number(random.uniform({a}, {b}))" + f"{dst} = self.agent.limit_number(random.uniform({a}, {b}))", + {"random"}, ) case NormalDist(): @@ -708,7 +773,8 @@ def add_block(self, block: Block) -> None: self.add_line("return") self.indent_left() self.add_line( - f"{dst} = self.agent.limit_number(numpy.random.normal({mean}, {std_dev}))" + f"{dst} = self.agent.limit_number(numpy.random.normal({mean}, {std_dev}))", + {"numpy"}, ) case ExpDist(): @@ -726,7 +792,8 @@ def add_block(self, block: Block) -> None: self.add_line("return") self.indent_left() self.add_line( - f"{dst} = self.agent.limit_number(numpy.random.exponential(self.agent.limit_number(1 / {lambda_})))" + f"{dst} = self.agent.limit_number(numpy.random.exponential(self.agent.limit_number(1 / {lambda_})))", + {"numpy"}, ) case Comparaison(): @@ -926,7 +993,7 @@ def add_block(self, block: Block) -> None: ) self.add_line("return") self.indent_left() - self.add_line(f"random.shuffle({list_})") + self.add_line(f"random.shuffle({list_})", {"random"}) self.add_line( f"{list_} = {list_}[:int(self.agent.limit_number({list_len} - {num}))]" ) @@ -991,14 +1058,16 @@ def add_block(self, block: Block) -> None: self.add_line("") self.add_line("# sin") self.add_line( - f"{dst} = self.agent.limit_number(numpy.sin(numpy.deg2rad({degree})))" + f"{dst} = self.agent.limit_number(numpy.sin(numpy.deg2rad({degree})))", + {"numpy"}, ) case Cos(): self.add_line("") self.add_line("# cos") self.add_line( - f"{dst} = self.agent.limit_number(numpy.cos(numpy.deg2rad({degree})))" + f"{dst} = self.agent.limit_number(numpy.cos(numpy.deg2rad({degree})))", + {"numpy"}, ) case _: @@ -1016,7 +1085,8 @@ def add_block(self, block: Block) -> None: self.add_line("") self.add_line("# power") self.add_line( - f"{dst} = self.agent.limit_number(numpy.power({base}, {num}))" + f"{dst} = self.agent.limit_number(numpy.power({base}, {num}))", + {"numpy"}, ) case Logarithm(): @@ -1034,7 +1104,8 @@ def add_block(self, block: Block) -> None: numerator = f"self.agent.limit_number(numpy.log({num}))" denominator = f"self.agent.limit_number(numpy.log({base}))" self.add_line( - f"{dst} = self.agent.limit_number({numerator} / {denominator})" + f"{dst} = self.agent.limit_number({numerator} / {denominator})", + {"numpy"}, ) case _: @@ -1042,5 +1113,75 @@ def add_block(self, block: Block) -> None: f"Unknown exponentiation operation statement: {statement.print()}" ) + case Logs(): + logger_msg = '{ "jid": self.agent.jid, "agent": type(self.agent).__name__, "behaviour": type(self).__name__, "action": inspect.stack()[0].function' + for arg in statement.args: + name = arg.expr + "__" + arg.explain_type_in_op() + value = self.parse_arg(arg) + logger_msg += f', "{name}": {value}' + logger_msg += " }" + + match statement: + case LogsDebug(): + self.add_line("") + self.add_line("# logs (debug)") + self.add_line( + f"if self.agent.logger: self.agent.logger.debug({logger_msg})", + {"inspect"}, + ) + + case LogsInfo(): + self.add_line("") + self.add_line("# logs (info)") + self.add_line( + f"if self.agent.logger: self.agent.logger.info({logger_msg})", + {"inspect"}, + ) + + case LogsWarning(): + self.add_line("") + self.add_line("# logs (warning)") + self.add_line( + f"if self.agent.logger: self.agent.logger.warning({logger_msg})", + {"inspect"}, + ) + + case LogsError(): + self.add_line("") + self.add_line("# logs (error)") + self.add_line( + f"if self.agent.logger: self.agent.logger.error({logger_msg})", + {"inspect"}, + ) + + case LogsCritical(): + self.add_line("") + self.add_line("# logs (critical)") + self.add_line( + f"if self.agent.logger: self.agent.logger.critical({logger_msg})", + {"inspect"}, + ) + + case _: + raise Exception( + f"Unknown logs statement: {statement.print()}" + ) + case ModuleInstruction(): + self.add_line("") + self.add_line(f"# module instruction {statement.op_code}") + arguments_string = ", ".join( + [self.parse_arg(arg) for arg in statement.args] + ) + if statement.is_block: + self.add_line( + # NOTE: there are no while statements in the modules, all blocks are ifs + f"if {statement.module}.{statement.op_code}({arguments_string}):" + ) + else: + self.add_line( + f"{statement.module}.{statement.op_code}({arguments_string})" + ) + case _: + print(statement) raise Exception(f"Unknown statement: {statement.print()}") diff --git a/aasm/intermediate/action.py b/aasm/intermediate/action.py index ace5069..617e3b8 100644 --- a/aasm/intermediate/action.py +++ b/aasm/intermediate/action.py @@ -37,12 +37,21 @@ def is_declared_float(self, name: str) -> bool: def is_declared_connection(self, name: str) -> bool: return self.current_block.is_declared_connection(name) + def is_declared_module_variable(self, name: str) -> bool: + return self.current_block.is_declared_module_variable(name) + + def get_module_variable_type(self, name: str) -> str: + return self.current_block.get_module_variable_type(name) + def add_float_declaration(self, declaration: Declaration) -> None: self.current_block.add_float_declaration(declaration) def add_connection_declaration(self, declaration: Declaration) -> None: self.current_block.add_connection_declaration(declaration) + def add_module_variable_declaration(self, declaration: Declaration) -> None: + self.current_block.add_module_variable_declaration(declaration) + def add_instruction(self, instruction: Instruction) -> None: self.current_block.add_statement(instruction) diff --git a/aasm/intermediate/agent.py b/aasm/intermediate/agent.py index a3d79d9..5d80b37 100644 --- a/aasm/intermediate/agent.py +++ b/aasm/intermediate/agent.py @@ -100,7 +100,17 @@ def print(self) -> None: print(f"FloatListParam {self.name} = []") +class ModuleVariableParam: + def __init__(self, name: str, type: str): + self.name: str = name + self.type: str = type + + def print(self) -> None: + print(f"ModuleVariableParam {self.name}: {self.type}") + + class Agent: + RESERVED_CONNECTION_PARAMS = ["self"] RESERVED_CONNECTION_LIST_PARAMS = ["connections"] RESERVED_FLOAT_PARAMS = ["connCount", "msgRCount", "msgSCount"] @@ -114,12 +124,16 @@ def __init__(self, name: str): self.connection_lists: Dict[str, ConnectionListParam] = {} self.message_lists: Dict[str, MessageListParam] = {} self.float_lists: Dict[str, FloatListParam] = {} + self.module_variables: Dict[str, ModuleVariableParam] = {} self.setup_behaviours: Dict[str, SetupBehaviour] = {} self.one_time_behaviours: Dict[str, OneTimeBehaviour] = {} self.cyclic_behaviours: Dict[str, CyclicBehaviour] = {} self.message_received_behaviours: Dict[str, MessageReceivedBehaviour] = {} self._last_modified_behaviour: Behaviour | None = None + def get_module_variable_type(self, name): + return self.module_variables[name].type + @property def last_behaviour(self) -> Behaviour: if self._last_modified_behaviour is None: @@ -129,6 +143,7 @@ def last_behaviour(self) -> Behaviour: @property def param_names(self) -> List[str]: return [ + *Agent.RESERVED_CONNECTION_PARAMS, *Agent.RESERVED_CONNECTION_LIST_PARAMS, *Agent.RESERVED_FLOAT_PARAMS, *list(self.init_floats), @@ -139,6 +154,7 @@ def param_names(self) -> List[str]: *list(self.connection_lists), *list(self.message_lists), *list(self.float_lists), + *list(self.module_variables), ] @property @@ -183,6 +199,9 @@ def add_message_list(self, list_param: MessageListParam) -> None: def add_float_list(self, list_param: FloatListParam) -> None: self.float_lists[list_param.name] = list_param + def add_module_variable(self, variable_param: ModuleVariableParam) -> None: + self.module_variables[variable_param.name] = variable_param + def add_setup_behaviour(self, behaviour: SetupBehaviour) -> None: self.setup_behaviours[behaviour.name] = behaviour self._last_modified_behaviour = behaviour @@ -239,6 +258,8 @@ def print(self) -> None: message_list_param.print() for float_list_param in self.float_lists.values(): float_list_param.print() + for module_variable in self.module_variables.values(): + module_variable.print() for setup_behaviour in self.setup_behaviours.values(): setup_behaviour.print() for one_time_behaviour in self.one_time_behaviours.values(): diff --git a/aasm/intermediate/argument.py b/aasm/intermediate/argument.py index 8a225ee..b2fa2db 100644 --- a/aasm/intermediate/argument.py +++ b/aasm/intermediate/argument.py @@ -8,7 +8,7 @@ if TYPE_CHECKING: from typing import List as TypingList - from typing import Type + from typing import Type, Tuple from parsing.state import State @@ -97,14 +97,28 @@ class Literal(ArgumentType): ... +class ModuleVariable(ArgumentType): + ... + + class Argument: """Doesn't panic. Use in the action context.""" - def __init__(self, state: State, expr: str): + def __init__(self, state: State, expr: str, set_types: bool = True): self.expr: str = expr self.types: TypingList[ArgumentType] = [] self.type_in_op: ArgumentType | None = None - self.set_types(state) + if set_types: + self.set_types(state) + + def create_all_possible_types(self, state: State) -> TypingList[Argument]: + possible_types: TypingList[Argument] = [] + for type_ in self.types: + possible_type = Argument(state, self.expr, set_types=False) + possible_type.types = [type_] + possible_type.type_in_op = type_ + possible_types.append(possible_type) + return possible_types def set_types(self, state: State) -> None: self.check_agent_params(state) @@ -121,6 +135,9 @@ def check_agent_params(self, state: State) -> None: elif self.expr in state.last_agent.RESERVED_CONNECTION_LIST_PARAMS: self.types.append(self.compose(List, ConnectionList, AgentParam, Mutable)) + elif self.expr in state.last_agent.RESERVED_CONNECTION_PARAMS: + self.types.append(self.compose(Connection, AgentParam, Immutable)) + elif self.expr in state.last_agent.float_param_names: self.types.append(self.compose(Float, AgentParam, Mutable)) @@ -136,6 +153,11 @@ def check_agent_params(self, state: State) -> None: elif self.expr in state.last_agent.float_lists: self.types.append(self.compose(List, FloatList, AgentParam, Mutable)) + elif self.expr in state.last_agent.module_variables: + subtype = state.last_agent.get_module_variable_type(self.expr) + new_type = type(subtype, (ModuleVariable,), {}) + self.types.append(self.compose(new_type, AgentParam, Mutable)) + for enum_param in state.last_agent.enums.values(): for enum_value in enum_param.enum_values: if self.expr == enum_value.value: @@ -150,6 +172,14 @@ def check_action_variables(self, state: State) -> None: elif state.last_action.is_declared_connection(self.expr): self.types.append(self.compose(Connection, Declared, Mutable)) + elif state.last_action.is_declared_module_variable(self.expr): + try: + subtype = state.last_action.get_module_variable_type(self.expr) + new_type = type(subtype, (ModuleVariable,), {}) + self.types.append(self.compose(new_type, Declared, Mutable)) + except ValueError: + pass + def check_numerical_values(self) -> None: if is_float(self.expr): self.types.append(self.compose(Float, Immutable, Literal)) @@ -189,6 +219,19 @@ def check_received_message_params(self, state: State) -> None: self.compose(Connection, ReceivedMessageParam, Immutable) ) + elif ( + prop in state.last_behaviour.received_message.module_variable_params + ): + subtype = ( + state.last_behaviour.received_message.get_module_variable_type( + prop + ) + ) + new_type = type(subtype, (ModuleVariable,), {}) + self.types.append( + self.compose(new_type, ReceivedMessageParam, Immutable) + ) + elif self.expr.lower() == "rcv": self.types.append(self.compose(Message, ReceivedMessage, Immutable)) @@ -210,6 +253,13 @@ def check_send_message_params(self, state: State) -> None: self.compose(Connection, SendMessageParam, Mutable) ) + elif prop in state.last_action.send_message.module_variable_params: + subtype = state.last_action.send_message.get_module_variable_type( + prop + ) + new_type = type(subtype, (ModuleVariable,), {}) + self.types.append(self.compose(new_type, SendMessageParam, Mutable)) + elif self.expr.lower() == "send": self.types.append(self.compose(Message, SendMessage, Mutable)) @@ -227,17 +277,46 @@ def has_arg(self, argument_type: ArgumentType, key: str, value: str) -> bool: ... return False + def _check_subclasses(self, type_: ArgumentType, klass: Type[ArgumentType]) -> bool: + class_strings = [ + f"{type_class.__module__}.{type_class.__name__}" + for type_class in type_.__class__.mro() + ] + return any( + [ + f"{klass.__module__}.{klass.__name__}" == class_name + for class_name in class_strings + ] + ) + def has_type(self, *classes: Type[ArgumentType], **args: str) -> bool: for type_ in self.types: - if all([isinstance(type_, klass) for klass in classes]) and all( + if all([self._check_subclasses(type_, klass) for klass in classes]) and all( [self.has_arg(type_, key, value) for key, value in args.items()] ): return True return False + # def has_type(self, *classes: Type[ArgumentType], **args: str) -> bool: + # for type_ in self.types: + # all_classes = True + # for klass in classes: + # if not self._check_subclasses(type_, klass): + # all_classes = False + # break + # if all_classes: + # all_args = True + # for key, value in args.items(): + # if not self.has_arg(type_, key, value): + # all_args = False + # break + # if all_args: + # return True + # return False + def set_op_type(self, *classes: Type[ArgumentType], **args: str) -> None: for type_ in self.types: - if all([isinstance(type_, klass) for klass in classes]) and all( + if all([self._check_subclasses(type_, klass) for klass in classes]) and all( [self.has_arg(type_, key, value) for key, value in args.items()] ): self.type_in_op = type_ @@ -347,33 +426,61 @@ def list_n_removal_context(self, rhs: Argument) -> bool: return False # SET - def assignment_context(self, rhs: Argument) -> bool: - if self.has_type(Enum, Mutable) and rhs.has_type( - EnumValue, from_enum=self.expr - ): - self.set_op_type(Enum, Mutable) - rhs.set_op_type(EnumValue, from_enum=self.expr) - - elif self.has_type(Float, Mutable) and rhs.has_type(Float): - self.set_op_type(Float, Mutable) - rhs.set_op_type(Float) - - elif self.has_type(Message, Mutable) and rhs.has_type(MessageList): - self.set_op_type(Message, Mutable) - rhs.set_op_type(MessageList) - - elif self.has_type(SendMessageParam, Mutable) and rhs.has_type(Float): - self.set_op_type(SendMessageParam, Mutable) - rhs.set_op_type(Float) - - elif self.has_type(Connection, Mutable) and rhs.has_type(Connection): - self.set_op_type(Connection, Mutable) - rhs.set_op_type(Connection) - - else: - return False - - return True + def assignment_context(self, rhs: Argument, state: State) -> bool: + basic_assigment_types: TypingList[Tuple[Type, Type]] = [ + (Enum, EnumValue), + (Float, Float), + (Message, MessageList), + (Connection, Connection), + (ConnectionList, ConnectionList), + (MessageList, MessageList), + (FloatList, FloatList), + (SendMessageParam, Float), + (SendMessageParam, ModuleVariable), + ] + + found_flag = False + for assignment_possiblity in basic_assigment_types: + if self.has_type(assignment_possiblity[0], Mutable) and rhs.has_type( + assignment_possiblity[1] + ): + self.set_op_type(assignment_possiblity[0], Mutable) + rhs.set_op_type(assignment_possiblity[1]) + found_flag = True + break + + if self.has_type(ModuleVariable, Mutable) and rhs.has_type(ModuleVariable): + lhs_type_name = self._find_module_variable_type(state) + rhs_type_name = rhs._find_module_variable_type(state) + if lhs_type_name == "" or rhs_type_name == "": + return False + lhs_type = self.get_modvar_type(lhs_type_name) + rhs_type = rhs.get_modvar_type(rhs_type_name) + self.set_op_type(lhs_type, Mutable) + rhs.set_op_type(rhs_type) + found_flag = True + + return found_flag + + def _find_module_variable_type(self, state: State) -> str: + subtype = "" + if not self.has_type(ModuleVariable): + return subtype + elif self.expr in state.last_agent.module_variables: + subtype = state.last_agent.get_module_variable_type(self.expr) + elif state.last_action.is_declared_module_variable(self.expr): + subtype = state.last_action.get_module_variable_type(self.expr) + elif self.has_type(ReceivedMessageParam): + behav = state.last_behaviour + if isinstance(behav, MessageReceivedBehaviour): + subtype = behav.received_message.get_module_variable_type(self.expr) + elif self.has_type(SendMessageParam): + action = state.last_action + if isinstance(action, SendMessageAction): + # get the expr split after . character + split_expr = self.expr.split(".")[-1] + subtype = action.send_message.get_module_variable_type(split_expr) + return subtype # SUBS def list_subset_context(self, from_list: Argument, num: Argument) -> bool: @@ -506,6 +613,10 @@ def list_write_context(self, idx: Argument, value: Argument) -> bool: return True + def get_modvar_type(self, subtype: str) -> Type: + new_type = type(subtype, (ModuleVariable,), {}) + return new_type + def explain(self) -> str: types = f"{self.expr}: [ " for argument_type in self.types: @@ -514,10 +625,11 @@ def explain(self) -> str: types += " ]" return types + def explain_type_in_op(self) -> str: + return type(self.type_in_op).__name__ if self.type_in_op else "UNKNOWN" + def print(self) -> None: print(f"Argument {self.expr}") - print( - f"Type in op: {type(self.type_in_op).__name__ if self.type_in_op else 'UNKNOWN'}" - ) + print(f"Type in op: {self.explain_type_in_op()}") for argument_type in self.types: type(argument_type).__name__ diff --git a/aasm/intermediate/block.py b/aasm/intermediate/block.py index 60950f3..463b487 100644 --- a/aasm/intermediate/block.py +++ b/aasm/intermediate/block.py @@ -1,6 +1,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, List, Tuple + +from aasm.intermediate.declaration import ModuleVariableDeclaration if TYPE_CHECKING: from aasm.intermediate.declaration import Declaration @@ -12,6 +14,7 @@ def __init__( self, float_names: List[str] | None = None, connection_names: List[str] | None = None, + module_variables: List[Tuple[str, str]] | None = None, ): if float_names: self.float_names: List[str] = float_names @@ -23,6 +26,11 @@ def __init__( else: self.connection_names: List[str] = [] + if module_variables: + self.module_variables: List[Tuple[str, str]] = module_variables + else: + self.module_variables: List[Tuple[str, str]] = [] + def add_float_name(self, name: str) -> None: self.float_names.append(name) @@ -35,16 +43,31 @@ def add_connection_name(self, name: str) -> None: def is_connection_name(self, name: str) -> bool: return name in self.connection_names + def add_module_variable(self, name: str, type: str) -> None: + self.module_variables.append((name, type)) + + def is_module_variable_name(self, name: str) -> bool: + return any(name == mod_var[0] for mod_var in self.module_variables) + def get_declared_names(self) -> List[str]: - return [*self.float_names, *self.connection_names] + return [ + *self.float_names, + *self.connection_names, + *[modvar[0] for modvar in self.module_variables], + ] def get_copy(self) -> Declarations: - return Declarations(list(self.float_names), list(self.connection_names)) + return Declarations( + list(self.float_names), + list(self.connection_names), + list(self.module_variables), + ) def print(self) -> None: print("Declarations") print(f"float_names = {self.float_names}") print(f"connection_names = {self.connection_names}") + print(f"module_variable_names = {self.module_variables}") class Block: @@ -66,6 +89,9 @@ def is_declared_float(self, name: str) -> bool: def is_declared_connection(self, name: str) -> bool: return self._declarations.is_connection_name(name) + def is_declared_module_variable(self, name: str) -> bool: + return self._declarations.is_module_variable_name(name) + def add_float_declaration(self, declaration: Declaration) -> None: self._declarations.add_float_name(declaration.name) self.statements.append(declaration) @@ -74,6 +100,17 @@ def add_connection_declaration(self, declaration: Declaration) -> None: self._declarations.add_connection_name(declaration.name) self.statements.append(declaration) + def add_module_variable_declaration(self, declaration: Declaration) -> None: + assert isinstance(declaration, ModuleVariableDeclaration) + self._declarations.add_module_variable(declaration.name, declaration.subtype) + self.statements.append(declaration) + + def get_module_variable_type(self, name: str) -> str: + for modvar in self._declarations.module_variables: + if modvar[0] == name: + return modvar[1] + raise ValueError(f"Module variable {name} not found") + def add_statement(self, statement: Instruction | Block) -> None: self.statements.append(statement) diff --git a/aasm/intermediate/declaration.py b/aasm/intermediate/declaration.py index 79462e0..7cca9f0 100644 --- a/aasm/intermediate/declaration.py +++ b/aasm/intermediate/declaration.py @@ -32,3 +32,13 @@ def __init__(self, name: Argument, value: Argument): def print(self) -> None: print(f"ConnectionDeclaration: {self.name}") super().print() + + +class ModuleVariableDeclaration(Declaration): + def __init__(self, name: Argument, value: Argument, subtype: str): + super().__init__(name=name, value=value) + self.subtype: str = subtype + + def print(self) -> None: + print(f"ModuleVariableDeclaration: {self.name}: {self.subtype}") + super().print() diff --git a/aasm/intermediate/graph.py b/aasm/intermediate/graph.py index 3cb4c41..b22f594 100644 --- a/aasm/intermediate/graph.py +++ b/aasm/intermediate/graph.py @@ -1,6 +1,15 @@ from __future__ import annotations -from typing import Any, Dict +from typing import Any, Dict, List, Tuple, Callable, List + + +class MParameters: + def __init__(self, a: int, b: int): + self.m0 = a + self.m_inc = b + + def print(self) -> None: + print(f"MParameters values = {self.m0}, {self.m_inc}") class AgentAmount: @@ -76,7 +85,7 @@ def __init__(self, row: List[int]): def print(self) -> None: print("AdjRow") - for key, value in self.row.items(): + for key, value in enumerate(self.row): print(f"{key} = {value}") @@ -102,10 +111,36 @@ def print(self) -> None: self.connections.print() +class BarabasiAgent: + def __init__(self, name: str, amount: AgentAmount): + self.name = name + self.amount = amount + + def print(self) -> None: + print(f"BarabasiAgent name = {self.name}") + self.amount.print() + + +class InhomogeneousAgent: + def __init__( + self, + name: str, + amount: AgentConstantAmount, + conn_amounts: List[ConnectionConstantAmount], + ): + self.name = name + self.amount = amount + self.amounts = conn_amounts + + def print(self) -> None: + print(f"InhomogeneousAgent name = {self.name}") + for amount in self.amounts: + amount.print() + + class Graph: def __init__(self): self.size = None - self.scale = None def set_size(self, size: int) -> None: self.size = size @@ -113,11 +148,29 @@ def set_size(self, size: int) -> None: def is_size_defined(self) -> bool: return self.size is not None + def is_types_amount_defined(self) -> bool: + return False + + def is_agent_defined(self, agent_type: str) -> bool: + return False + + def set_types_amount(self, types_no: int) -> None: + raise NotImplementedError() + + def get_types_amount(self) -> int: + raise NotImplementedError() + def set_scale(self, scale: int) -> None: - self.scale = scale + raise NotImplementedError() def is_scale_defined(self) -> bool: - return self.scale is not None + return False + + def set_m(self, m: Tuple[int, int]) -> None: + raise NotImplementedError() + + def is_m_defined(self) -> bool: + return False def add_agent(self, graph_agent: Any) -> None: raise NotImplementedError() @@ -150,16 +203,51 @@ def print(self) -> None: agent.print() +class BarabasiGraph(Graph): + def __init__(self): + super().__init__() + self.agents: Dict[str, BarabasiAgent] = {} + self.m_params = None + + def add_agent(self, graph_agent: BarabasiAgent) -> None: + self.agents[graph_agent.name] = graph_agent + + def is_agent_defined(self, agent_type: str) -> bool: + return agent_type in self.agents + + def is_agent_percent_amount_used(self) -> bool: + for agent in self.agents.values(): + if isinstance(agent.amount, AgentPercentAmount): + return True + return False + + def set_m(self, m: Tuple[int, int]) -> None: + self.m_params = MParameters(*m) + + def is_m_defined(self) -> bool: + return self.m_params is not None + + def print(self) -> None: + super().print() + print("BarabasiGraph") + for agent in self.agents.values(): + agent.print() + + class MatrixGraph(Graph): def __init__(self): super().__init__() - self.agents = [] + self.agents: List[MatrixAgent] = [] + self.scale = None def add_agent(self, graph_agent: MatrixAgent) -> None: self.agents.append(graph_agent) def is_agent_defined(self, agent_type: str) -> bool: - return any(self.agents, lambda agent: agent.name == agent_type) + check_agent: Callable[[MatrixAgent], bool] = ( + lambda agent: agent.name == agent_type + ) + return all(check_agent(agent) for agent in self.agents) def set_scale(self, scale: int) -> None: self.scale = scale @@ -170,5 +258,45 @@ def is_scale_defined(self) -> bool: def print(self) -> None: super().print() print("MatrixGraph") + for agent in self.agents: + agent.print() + + +class InhomogenousRandomGraph(Graph): + def __init__(self): + super().__init__() + self.agents: Dict[str, InhomogeneousAgent] = {} + self.types_no = None + self.order: list[str] = [] + + def add_agent(self, graph_agent: InhomogeneousAgent) -> None: + self.order.append(graph_agent.name) + self.agents[graph_agent.name] = graph_agent + + def is_agent_defined(self, agent_type: str) -> bool: + return agent_type in self.agents + + def is_agent_percent_amount_used(self) -> bool: + check_agent: Callable[[InhomogeneousAgent], bool] = lambda amount: isinstance( + amount, AgentPercentAmount + ) + if any(check_agent(agent) for agent in self.agents.values()): + return True + return False + + def is_types_amount_defined(self) -> bool: + return self.types_no is not None + + def set_types_amount(self, types_no: int) -> None: + self.types_no = types_no + + def get_types_amount(self) -> int: + if self.types_no is None: + raise ValueError("Types amount is not defined") + return self.types_no + + def print(self) -> None: + super().print() + print("InhomogenousRandomGraph") for agent in self.agents.values(): agent.print() diff --git a/aasm/intermediate/instruction.py b/aasm/intermediate/instruction.py index bbddb37..3f9ec45 100644 --- a/aasm/intermediate/instruction.py +++ b/aasm/intermediate/instruction.py @@ -1,6 +1,7 @@ from __future__ import annotations from typing import TYPE_CHECKING +from typing import List as TypingList if TYPE_CHECKING: from aasm.intermediate.argument import Argument @@ -431,3 +432,77 @@ def print(self) -> None: self.dst.print() self.dividend.print() self.divisor.print() + + +class ModuleInstruction(Instruction): + def __init__( + self, + args: TypingList[Argument], + op_code: str = "", + module: str = "", + is_block=False, + ): + self.args = args + self.op_code = op_code + self.module = module + self.is_block = is_block + + def print(self) -> None: + print(f"{self.module}::{self.op_code}") + for arg in self.args: + arg.print() + + +class Logs(Instruction): + def __init__(self, args: TypingList[Argument]): + self.args = args + + def print(self) -> None: + print("Logs") + for arg in self.args: + arg.print() + + +class LogsDebug(Logs): + def __init__(self, args: TypingList[Argument]): + super().__init__(args=args) + + def print(self) -> None: + print("LogsDebug") + super().print() + + +class LogsInfo(Logs): + def __init__(self, args: TypingList[Argument]): + super().__init__(args=args) + + def print(self) -> None: + print("LogsInfo") + super().print() + + +class LogsWarning(Logs): + def __init__(self, args: TypingList[Argument]): + super().__init__(args=args) + + def print(self) -> None: + print("LogsWarning") + super().print() + + +class LogsError(Logs): + def __init__(self, args: TypingList[Argument]): + super().__init__(args=args) + + def print(self) -> None: + print("LogsError") + super().print() + + +class LogsCritical(Logs): + def __init__(self, args: TypingList[Argument]): + super().__init__(args=args) + + def print(self) -> None: + print("LogsCritical") + super().print() diff --git a/aasm/intermediate/message.py b/aasm/intermediate/message.py index 1794423..302c6ab 100644 --- a/aasm/intermediate/message.py +++ b/aasm/intermediate/message.py @@ -38,6 +38,17 @@ def print(self) -> None: super().print() +class ModuleVariableParam(MessageParam): + def __init__(self, name: str, type: str): + super().__init__(name) + self.type: str = type + + def print(self) -> None: + print(f"MessageModuleVariableParam") + super().print() + print(f"Type: {self.type}") + + class Message: RESERVED_CONNECTION_PARAMS = ["sender"] RESERVED_TYPE_PARAMS = ["type", "performative"] @@ -47,6 +58,7 @@ def __init__(self, msg_type: str, msg_performative: str): self.performative: str = msg_performative self.float_params: Dict[str, FloatParam] = {} self.connection_params: Dict[str, MessageParam] = {} + self.module_variable_params: Dict[str, ModuleVariableParam] = {} @property def param_names(self) -> List[str]: @@ -55,6 +67,7 @@ def param_names(self) -> List[str]: *Message.RESERVED_TYPE_PARAMS, *list(self.float_params), *list(self.connection_params), + *list(self.module_variable_params), ] @property @@ -69,6 +82,10 @@ def unset_params(self) -> List[str]: if not connection_param.is_value_set: unset_params.append(name) + for name, module_variable_param in self.module_variable_params.items(): + if not module_variable_param.is_value_set: + unset_params.append(name) + return unset_params def are_all_params_set(self) -> bool: @@ -78,6 +95,10 @@ def are_all_params_set(self) -> bool: connection_param.is_value_set for connection_param in self.connection_params.values() ] + + [ + module_variable_param.is_value_set + for module_variable_param in self.module_variable_params.values() + ] ) def param_exists(self, name: str) -> bool: @@ -89,9 +110,17 @@ def add_float(self, float_param: FloatParam) -> None: def add_connection(self, connection_param: ConnectionParam) -> None: self.connection_params[connection_param.name] = connection_param + def add_module_variable(self, module_variable_param: ModuleVariableParam) -> None: + self.module_variable_params[module_variable_param.name] = module_variable_param + + def get_module_variable_type(self, name: str) -> str: + return self.module_variable_params[name].type + def print(self) -> None: print(f"Message {self.type}/{self.performative}") for float_param in self.float_params.values(): float_param.print() for connection_param in self.connection_params.values(): connection_param.print() + for module_variable_param in self.module_variable_params.values(): + module_variable_param.print() diff --git a/aasm/intermediate/module.py b/aasm/intermediate/module.py new file mode 100644 index 0000000..6bbc5ca --- /dev/null +++ b/aasm/intermediate/module.py @@ -0,0 +1,3 @@ +class Module: + def __init__(self, name: str): + self.name: str = name diff --git a/aasm/modules/__init__.py b/aasm/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aasm/modules/instruction.py b/aasm/modules/instruction.py new file mode 100644 index 0000000..286b8b7 --- /dev/null +++ b/aasm/modules/instruction.py @@ -0,0 +1,132 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, List, Dict +from aasm.intermediate.argument import Argument +from aasm.modules.type import Type + +from aasm.utils.exception import PanicException + +from aasm.intermediate.argument import Mutable, Float, ModuleVariable +from aasm.intermediate.instruction import ModuleInstruction + +if TYPE_CHECKING: + from aasm.parsing.state import State + + +class Instruction: + def __init__( + self, + module_name: str, + available_types: List[Type], + opcode: str, + args: List[str], + ): + self.module = module_name + if opcode.endswith("*"): + self.opcode = opcode[:-1] + self.is_block = True + else: + self.opcode = opcode + self.is_block = False + self.available_types = available_types + self.args_dict: Dict[str, List[str]] = {} + self.arg_names = [] + self._parse_args(args) + + def _parse_args(self, args: List[str]): + current_var_name = "" + current_var_type = "" + first_arg = True + for arg in args: + if arg.endswith(":"): + if not first_arg: + self._validate_var_declaration(current_var_name) + current_var_name = arg[:-1] + self.arg_names.append(current_var_name) + current_var_type = "" + # verify that the variable name is not already used + if current_var_name in self.args_dict: + raise PanicException( + f"Error in module {self.module}, instruction {self.opcode}. Variable {current_var_name} already defined.", + f"Variable {current_var_name} is already defined.", + "Rename the variable.", + ) + self.args_dict[current_var_name] = [] + first_arg = False + else: + current_var_type = arg + self.args_dict[current_var_name].append(current_var_type) + + def _validate_var_declaration(self, current_var_name: str): + if len(self.args_dict[current_var_name]) == 0: + raise PanicException( + f"Error in module {self.module}, instruction {self.opcode}. Missing type for variable {current_var_name}.", + f"Variable {current_var_name} has no type specified.", + "Specify a type for the variable.", + ) + # verify that the types associated with current_var_name are valid + for var_type in self.args_dict[current_var_name]: + if var_type not in [tmp_type.name for tmp_type in self.available_types]: + raise PanicException( + f"Error in module {self.module}, instruction {self.opcode}. Type {var_type} is not defined.", + f"Type {var_type} is not defined.", + "Define the type.", + ) + + def op(self, state: State, arguments: List[str]) -> None: + state.require( + state.in_action, + "Not inside any action.", + f"{self.opcode} can be used inside actions.", + ) + state.require( + len(arguments) == len(self.args_dict.keys()), + f"Wrong number of arguments for {self.opcode}.", + f"Expected {len(self.args_dict.keys())}, got {len(arguments)}.", + ) + + # print(f"Parsing arguments from self.args_dict: {self.args_dict} and arguments: {arguments}") + parsed_args = [Argument(state, arg) for arg in arguments] + state.require( + self._validate_types_in_op_context(parsed_args), + f"Mismatched types in the {self.module}::{self.opcode} context.: {[arg.explain() for arg in parsed_args]}", + f"Refer to module documentation for further help.", + ) + + state.last_action.add_instruction( + ModuleInstruction(parsed_args, self.opcode, self.module, self.is_block) + ) + # for arg in parsed_args: + # arg.print() + if self.is_block: + state.last_action.start_block() + + def _validate_types_in_op_context(self, parsed_args) -> bool: + arg_idx = 0 + types_to_check = [] + for arg in parsed_args: + for arg_type in self.args_dict[self.arg_names[arg_idx]]: + if arg_type == "mut": + types_to_check.append(Mutable) + elif arg_type == "float": + types_to_check.append(Float) + else: + new_type = arg.get_modvar_type(arg_type) + types_to_check.append(new_type) + + for arg_type in types_to_check: + if not arg.has_type(arg_type): + return False + arg.set_op_type(*types_to_check) + arg_idx += 1 + + return True + + def __str__(self) -> str: + ret = f"{self.module}.{self.opcode}({', '.join(self.args_dict.keys())})" + if self.is_block: + ret += "[BLOCK]" + return ret + + def __repr__(self) -> str: + return str(self) diff --git a/aasm/modules/module.py b/aasm/modules/module.py new file mode 100644 index 0000000..ba35952 --- /dev/null +++ b/aasm/modules/module.py @@ -0,0 +1,197 @@ +from __future__ import annotations + +from typing import List, Dict, Tuple + +from aasm.modules.instruction import Instruction +from aasm.modules.type import Type +from aasm.utils.exception import PanicException + + +class Target: + def __init__(self, target: str): + self.name = target + + def __repr__(self): + return f"Target[{self.name}]" + + def __str__(self): + return f"Target[{self.name}]" + + +class Module: + def __init__(self, module_code_lines: List[str]): + self.name = None + # TODO: Change targets and types into classes + self.targets: List[Target] = [] + self.types: List[Type] = [] + self.instructions: List[Instruction] = [] + self.preambles: Dict[str, List[str]] = {} + self.impls: Dict[Tuple[str, str], List[str]] = {} + self.description: List[str] = [] + + self._in_targets = False + self._in_instructions = False + self._in_preamble = False + self._in_impl = False + self._in_types = False + self._in_description = False + self._current_target = None + self._current_instruction = None + + self._parse_module_code(module_code_lines) + # TODO: validate module -- check that all instructions are implemented for all targets, has a name etc. + # self._validate_module() + + def does_target(self, target: str) -> bool: + return target in [target.name for target in self.targets] + + def _reset_scope(self): + self._in_targets = False + self._in_instructions = False + self._in_preamble = False + self._in_impl = False + self._in_types = False + self._in_description = False + self._current_target = None + self._current_instruction = None + + def _parse_module_code(self, lines: List[str]): + for line in lines: + tokens = line.strip().split() + tokens = [token.strip().strip(",") for token in tokens] + match tokens: + case ["!name", name]: + self._reset_scope() + self.name = name + case ["!types"]: + self._reset_scope() + self._in_types = True + case ["!description"]: + self._reset_scope() + self._in_description = True + case ["!targets"]: + self._reset_scope() + self._in_targets = True + case ["!instructions"]: + self._reset_scope() + self._in_instructions = True + case ["!preamble", target]: + self._reset_scope() + self._in_preamble = True + self._current_target = target + case ["!impl", instruction, target]: + self._reset_scope() + self._in_impl = True + self._current_target = target + self._current_instruction = instruction + case _: + if len(tokens) == 0: + continue + elif tokens[0].startswith("#"): + continue + elif tokens[0].startswith("!"): + raise PanicException( + "Invalid line: " + line, + "Unkown module directive", + "Only module directives can start with !", + ) + elif self._in_targets: + if len(tokens) != 1: + raise PanicException( + "Invalid target line: " + line, + "Multiple tokens in target line", + "Target lines must have exactly one token: e.g. spade", + ) + self.targets.append(Target(tokens[0])) + elif self._in_instructions: + if self.name is None: + raise PanicException( + "Invalid instruction line: " + line, + "Module name is undefined", + "Module name must be defined before instructions. Define module name with !name [name]", + ) + else: + self.instructions.append( + Instruction( + self.name, self.types, tokens[0], tokens[1:] + ) + ) + elif self._in_preamble: + if self._current_target is None: + raise PanicException( + "Invalid preamble line: Target is undefined: " + line, + "Target is undefined", + "Target must be defined before preamble. Define target with !preamble [target]", + ) + self.preambles.setdefault(self._current_target, []).append(line) + elif self._in_impl: + if self._current_target is None: + raise PanicException( + "Invalid impl line: Target is undefined: " + line, + "Target is undefined", + "Target must be defined before impl. Define target with !impl [instruction] [target]", + ) + if self._current_instruction is None: + raise PanicException( + "Invalid impl line: Instruction is undefined: " + line, + "Instruction is undefined", + "Instruction must be defined before impl. Define instruction with !impl [instruction] [target]", + ) + self.impls.setdefault( + (self._current_target, self._current_instruction), [] + ).append(line) + elif self._in_types: + if len(tokens) != 1: + raise PanicException( + "Invalid type line: " + line, + "Multiple tokens in type line", + "Type lines must have exactly one token: e.g. int64", + ) + if self.name is None: + raise PanicException( + "Invalid instruction line: " + line, + "Module name is undefined", + "Module name must be defined before instructions. Define module name with !name [name]", + ) + else: + self.types.append(Type(tokens[0], self.name)) + elif self._in_description: + self.description.append(line) + else: + raise PanicException( + "Invalid line: " + line, + "Unkown line", + "Line is not a module directive, target, instruction, preamble or impl", + ) + + def __repr__(self): + return ( + f"Module[{self.name}] (" + + repr(self.targets) + + "\n" + + repr(self.types) + + "\n" + + repr(self.instructions) + + "\n" + + repr(self.preambles) + + "\n" + + repr(self.impls) + + ")" + ) + + def __str__(self): + return ( + f"Module[{self.name}] (\n" + + str(self.description) + + "\n" + + str(self.targets) + + "\n" + + str(self.types) + + "\n" + + str(self.instructions) + + "\n" + + str(self.preambles) + + "\n" + + str(self.impls) + + ")" + ) diff --git a/aasm/modules/type.py b/aasm/modules/type.py new file mode 100644 index 0000000..44f4bf2 --- /dev/null +++ b/aasm/modules/type.py @@ -0,0 +1,17 @@ +from aasm.intermediate.argument import ModuleVariable + + +class Type: + def __init__(self, name: str, module: str): + self.name = name + self.type_class = type(name, (ModuleVariable,), {}) + self.module = module + + def full_qualified_name(self) -> str: + return f"{self.module}::{self.name}" + + def __repr__(self): + return f"Type[{self.full_qualified_name()}]" + + def __str__(self): + return f"Type[{self.full_qualified_name()}]" diff --git a/aasm/parsing/op/decl.py b/aasm/parsing/op/decl.py index ed582ea..84a3b67 100644 --- a/aasm/parsing/op/decl.py +++ b/aasm/parsing/op/decl.py @@ -3,7 +3,11 @@ from typing import TYPE_CHECKING from aasm.intermediate.argument import Argument -from aasm.intermediate.declaration import ConnectionDeclaration, FloatDeclaration +from aasm.intermediate.declaration import ( + ConnectionDeclaration, + FloatDeclaration, + ModuleVariableDeclaration, +) from aasm.utils.validation import is_valid_name, print_invalid_names if TYPE_CHECKING: @@ -45,4 +49,9 @@ def op_DECL(state: State, name: str, category: str, value: str) -> None: ) case _: - state.panic(f"Incorrect declaration: DECL {name} {category} {value}") + if category in state.get_module_types(): + state.last_action.add_module_variable_declaration( + ModuleVariableDeclaration(name_arg, value_arg, category) + ) + else: + state.panic(f"Incorrect declaration: DECL {name} {category} {value}") diff --git a/aasm/parsing/op/defg.py b/aasm/parsing/op/defg.py index fcae3c0..257efd6 100644 --- a/aasm/parsing/op/defg.py +++ b/aasm/parsing/op/defg.py @@ -11,6 +11,8 @@ ConnectionDistUniformAmount, StatisticalAgent, StatisticalGraph, + BarabasiAgent, + BarabasiGraph, ) from aasm.utils.validation import is_float, is_int @@ -26,10 +28,12 @@ def op_DEFG(state: State, agent_name: str, amount: str, args: List[str]) -> None "Cannot define agent graph amount outside graph scope.", "Try defining new graphs using GRAPH.", ) + is_statistical = isinstance(state.last_graph, StatisticalGraph) + is_barabsi = isinstance(state.last_graph, BarabasiGraph) state.require( - isinstance(state.last_graph, StatisticalGraph), - "DEFG can be used with statistical graphs.", - "Define statistical graphs with GRAPH statistical.", + is_statistical ^ is_barabsi, + "DEFG can be used with statistical and barabasi graphs.", + "Define statistical graphs with GRAPH statistical|GRAPH barabasi-albert.", ) state.require(state.agent_exists(agent_name), f"Agent {agent_name} is not defined.") @@ -55,48 +59,51 @@ def op_DEFG(state: State, agent_name: str, amount: str, args: List[str]) -> None agent_amount = AgentConstantAmount(amount) - connection_amount: ConnectionAmount | None = None - match args: - case [value]: - state.require(is_int(value), f"{value} is not a valid integer.") - state.require( - int(value) >= 0, - f"{value} is not a valid connection amount.", - "Amount must be non-negative.", - ) - - connection_amount = ConnectionConstantAmount(value) - - case ["dist_normal", mean, std_dev]: - state.require(is_float(mean), f"{mean} is not a valid float.") - state.require(is_float(std_dev), f"{std_dev} is not a valid float.") - state.require( - float(std_dev) >= 0, - f"{std_dev} is not a valid standard deviation parameter.", - "Standard deviation must be non-negative.", - ) - - connection_amount = ConnectionDistNormalAmount(mean, std_dev) - - case ["dist_exp", lambda_]: - state.require(is_float(lambda_), f"{lambda_} is not a valid float.") - state.require( - float(lambda_) > 0, - f"{lambda_} is not a valid lambda parameter.", - "Lambda must be positive.", - ) - - connection_amount = ConnectionDistExpAmount(lambda_) - - case ["dist_uniform", a, b]: - state.require(is_float(a), f"{a} is not a valid float.") - state.require(is_float(b), f"{b} is not a valid float.") - - connection_amount = ConnectionDistUniformAmount(a, b) - - case _: - state.panic(f"Incorrect operation: DEFG {agent_name} {amount} {args}") - - state.last_graph.add_agent( - StatisticalAgent(agent_name, agent_amount, connection_amount) - ) + if is_statistical: + connection_amount: ConnectionAmount | None = None + match args: + case [value]: + state.require(is_int(value), f"{value} is not a valid integer.") + state.require( + int(value) >= 0, + f"{value} is not a valid connection amount.", + "Amount must be non-negative.", + ) + + connection_amount = ConnectionConstantAmount(value) + + case ["dist_normal", mean, std_dev]: + state.require(is_float(mean), f"{mean} is not a valid float.") + state.require(is_float(std_dev), f"{std_dev} is not a valid float.") + state.require( + float(std_dev) >= 0, + f"{std_dev} is not a valid standard deviation parameter.", + "Standard deviation must be non-negative.", + ) + + connection_amount = ConnectionDistNormalAmount(mean, std_dev) + + case ["dist_exp", lambda_]: + state.require(is_float(lambda_), f"{lambda_} is not a valid float.") + state.require( + float(lambda_) > 0, + f"{lambda_} is not a valid lambda parameter.", + "Lambda must be positive.", + ) + + connection_amount = ConnectionDistExpAmount(lambda_) + + case ["dist_uniform", a, b]: + state.require(is_float(a), f"{a} is not a valid float.") + state.require(is_float(b), f"{b} is not a valid float.") + + connection_amount = ConnectionDistUniformAmount(a, b) + + case _: + state.panic(f"Incorrect operation: DEFG {agent_name} {amount} {args}") + + state.last_graph.add_agent( + StatisticalAgent(agent_name, agent_amount, connection_amount) + ) + else: + state.last_graph.add_agent(BarabasiAgent(agent_name, agent_amount)) diff --git a/aasm/parsing/op/defnode.py b/aasm/parsing/op/defnode.py index 495b166..56c6fe0 100644 --- a/aasm/parsing/op/defnode.py +++ b/aasm/parsing/op/defnode.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING from aasm.intermediate.graph import AdjRow, MatrixAgent, MatrixGraph diff --git a/aasm/parsing/op/deftype.py b/aasm/parsing/op/deftype.py new file mode 100644 index 0000000..1c00914 --- /dev/null +++ b/aasm/parsing/op/deftype.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, List + +from aasm.intermediate.graph import ( + InhomogenousRandomGraph, + InhomogeneousAgent, + AgentConstantAmount, + ConnectionConstantAmount, +) + + +from aasm.utils.validation import is_int, is_float + +if TYPE_CHECKING: + from aasm.parsing.state import State + + +def op_DEFTYPE( + state: State, agent_name: str, amount: str, conn_amounts: List[str] +) -> None: + state.require(state.agent_exists(agent_name), f"Agent {agent_name} is not defined.") + state.require( + not state.last_graph.is_agent_defined(agent_name), + f"Agent {agent_name} is already defined.", + ) + state.require( + state.in_graph, + "Cannot define agent type outside of graph scope.", + "Try defining new graph using GRAPH.", + ) + state.require( + isinstance(state.last_graph, InhomogenousRandomGraph), + "DEFTYPE can be used only in irg graphs.", + "Define irg graphs with GRAPH irg.", + ) + state.require( + state.last_graph.is_types_amount_defined(), + "The amount of types is not defined.", + "Define the amount of types using TYPES.", + ) + agent_amount: AgentConstantAmount | None = None + state.require(is_int(amount), f"{amount} is not a valid integer.") + agent_amount = AgentConstantAmount(amount) + connection_amounts: List[ConnectionConstantAmount] = [] + state.require( + len(conn_amounts) == state.last_graph.get_types_amount(), + f"Amount of connection amounts ({len(conn_amounts)}) does not match the amount of types ({state.last_graph.get_types_amount()}).", + ) + for conn_amount in conn_amounts: + state.require( + is_float(conn_amount), f"{conn_amount} is not a valid floating number." + ) + state.require( + float(conn_amount) >= 0, + f"{conn_amount} is less than zero.", + "Connection amounts must be between 0 and 100.", + ) + state.require( + float(conn_amount) <= 100, + f"{conn_amount} is greater than 100.", + "Connection amounts must be between 0 and 100.", + ) + connection_amounts.append(ConnectionConstantAmount(conn_amount)) + + state.last_graph.add_agent( + InhomogeneousAgent(agent_name, agent_amount, connection_amounts) + ) diff --git a/aasm/parsing/op/graph.py b/aasm/parsing/op/graph.py index 8dec0c2..719f5ab 100644 --- a/aasm/parsing/op/graph.py +++ b/aasm/parsing/op/graph.py @@ -2,7 +2,12 @@ from typing import TYPE_CHECKING -from aasm.intermediate.graph import MatrixGraph, StatisticalGraph +from aasm.intermediate.graph import ( + MatrixGraph, + StatisticalGraph, + BarabasiGraph, + InhomogenousRandomGraph, +) if TYPE_CHECKING: from aasm.parsing.state import State @@ -33,6 +38,10 @@ def op_GRAPH(state: State, category: str) -> None: state.add_graph(StatisticalGraph()) case "matrix": state.add_graph(MatrixGraph()) + case "barabasi-albert": + state.add_graph(BarabasiGraph()) + case "irg": + state.add_graph(InhomogenousRandomGraph()) case _: state.panic(f"Incorrect operation: GRAPH {category}") @@ -45,12 +54,24 @@ def op_EGRAPH(state: State) -> None: ) if ( isinstance(state.last_graph, StatisticalGraph) - and state.last_graph.is_agent_percent_amount_used() - ): + or isinstance(state.last_graph, InhomogenousRandomGraph) + ) and state.last_graph.is_agent_percent_amount_used(): state.require( state.last_graph.is_size_defined(), "Graph size is not defined.", "Graph size must be defined to use agent percent amount.", ) + elif isinstance(state.last_graph, BarabasiGraph): + state.require( + state.last_graph.is_m_defined(), + "Graph MParameters is not defined.", + "Graph MParameters must be defined to use barabasi graph.", + ) + if state.last_graph.is_agent_percent_amount_used(): + state.require( + state.last_graph.is_size_defined(), + "Graph size is not defined.", + "Graph size must be defined to use agent percent amount.", + ) state.in_graph = False diff --git a/aasm/parsing/op/logs.py b/aasm/parsing/op/logs.py new file mode 100644 index 0000000..2f6efae --- /dev/null +++ b/aasm/parsing/op/logs.py @@ -0,0 +1,48 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, List + +from aasm.intermediate.argument import Argument +from aasm.intermediate.instruction import ( + LogsCritical, + LogsDebug, + LogsError, + LogsInfo, + LogsWarning, +) +from aasm.utils.validation import print_valid_logs_levels + +if TYPE_CHECKING: + from parsing.state import State + + +def op_LOGS(state: State, level: str, args: List[str]) -> None: + state.require( + state.in_action, "Not inside any action", f"LOGS can be used inside actions." + ) + + intermediate_args: List[Argument] = [] + for arg in [Argument(state, arg) for arg in args]: + intermediate_args.extend(arg.create_all_possible_types(state)) + + match level: + case "debug": + state.last_action.add_instruction(LogsDebug(intermediate_args)) + + case "info": + state.last_action.add_instruction(LogsInfo(intermediate_args)) + + case "warning": + state.last_action.add_instruction(LogsWarning(intermediate_args)) + + case "error": + state.last_action.add_instruction(LogsError(intermediate_args)) + + case "critical": + state.last_action.add_instruction(LogsCritical(intermediate_args)) + + case _: + state.panic( + f"Unexpected error: LOGS {level} {args}", + f"Supported LOGS levels are: {print_valid_logs_levels()}", + ) diff --git a/aasm/parsing/op/module.py b/aasm/parsing/op/module.py new file mode 100644 index 0000000..7e7ba38 --- /dev/null +++ b/aasm/parsing/op/module.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING +from aasm.intermediate.module import Module +from aasm.utils.validation import is_valid_name, print_invalid_names + +if TYPE_CHECKING: + from aasm.parsing.state import State + + +def op_MODULE(state: State, name: str) -> None: + state.require( + not state.in_agent, + "Cannot define modules inside agents.", + "First end current agent using EAGENT.", + ) + state.require( + not state.in_graph, + "Cannot define modules inside graphs.", + "First end current graph using EGRAPH.", + ) + state.require( + not state.module_exists(name), + f"Module {name} already exists in the current environment.", + ) + + state.require( + state.module_is_loaded(name), + f"Module {name} is not loaded.", + ) + + # NOTE: I don't think that's necessary for modules + # state.require( + # is_valid_name(name), + # f"{name} is not a correct name.", + # f"Names can only contain alphanumeric characters, underscores and cannot be: {print_invalid_names()}.", + # ) + state.add_module(Module(name)) diff --git a/aasm/parsing/op/mparams.py b/aasm/parsing/op/mparams.py new file mode 100644 index 0000000..427bba5 --- /dev/null +++ b/aasm/parsing/op/mparams.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from aasm.utils.validation import is_int + +if TYPE_CHECKING: + from aasm.parsing.state import State + + +def op_MPARAMS(state: State, m0: str, m_inc: str) -> None: + state.require( + state.in_graph, + "Not inside any graph", + "MPARAMS can only be used inside graphs.", + ) + state.require(not state.last_graph.is_m_defined(), "MPARAMS already defined.") + state.require(is_int(m0) and is_int(m_inc), "MPARAMS must be integers.") + m_begin = int(m0) + m_increment = int(m_inc) + state.require(m_begin >= 0 and m_increment >= 0, "MPARAMS must be non-negative.") + + state.last_graph.set_m((m_begin, m_increment)) diff --git a/aasm/parsing/op/prm.py b/aasm/parsing/op/prm.py index 017ef90..dd46418 100644 --- a/aasm/parsing/op/prm.py +++ b/aasm/parsing/op/prm.py @@ -10,8 +10,10 @@ from aasm.intermediate.agent import FloatListParam as AgentFloatListParam from aasm.intermediate.agent import InitFloatParam as AgentInitFloatParam from aasm.intermediate.agent import MessageListParam as AgentMessageListParam +from aasm.intermediate.agent import ModuleVariableParam as AgentModuleVariableParam from aasm.intermediate.message import ConnectionParam as MessageConnectionParam from aasm.intermediate.message import FloatParam as MessageFloatParam +from aasm.intermediate.message import ModuleVariableParam as MessageModuleVariableParam from aasm.utils.validation import ( is_float, is_valid_enum_list, @@ -100,7 +102,14 @@ def op_agent_PRM(state: State, name: str, category: str, args: List[str]) -> Non state.last_agent.add_enum(AgentEnumParam(name, enums)) case _: - state.panic(f"Incorrect operation: (agent) PRM {name} {category} {args}") + if category in state.get_module_types(): + state.last_agent.add_module_variable( + AgentModuleVariableParam(name, category) + ) + else: + state.panic( + f"Incorrect operation: (agent) PRM {name} {category} {args}" + ) def op_message_PRM(state: State, name: str, category: str) -> None: @@ -127,4 +136,9 @@ def op_message_PRM(state: State, name: str, category: str) -> None: state.last_message.add_connection(MessageConnectionParam(name)) case _: - state.panic(f"Incorrect operation: (message) PRM {name} {category}") + if category in state.get_module_types(): + state.last_message.add_module_variable( + MessageModuleVariableParam(name, category) + ) + else: + state.panic(f"Incorrect operation: (message) PRM {name} {category}") diff --git a/aasm/parsing/op/set.py b/aasm/parsing/op/set.py index 1b5b733..73993f8 100644 --- a/aasm/parsing/op/set.py +++ b/aasm/parsing/op/set.py @@ -16,7 +16,7 @@ def op_SET(state: State, arg1: str, arg2: str) -> None: lhs = Argument(state, arg1) rhs = Argument(state, arg2) state.require( - lhs.assignment_context(rhs), + lhs.assignment_context(rhs, state), "Mismatched types in the assignment context.", f"ARG1 {lhs.explain()}, ARG2 {rhs.explain()}", ) diff --git a/aasm/parsing/op/types.py b/aasm/parsing/op/types.py new file mode 100644 index 0000000..a85a5ff --- /dev/null +++ b/aasm/parsing/op/types.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from aasm.utils.validation import is_int + +from aasm.intermediate.graph import InhomogenousRandomGraph + +if TYPE_CHECKING: + from aasm.parsing.state import State + + +def op_TYPES(state: State, types: str) -> None: + state.require( + state.in_graph, "Not inside any graph.", "SIZE can be used inside graphs." + ) + state.require( + isinstance(state.last_graph, InhomogenousRandomGraph), + "TYPES can be used only in irg graphs.", + "Define irg graphs with GRAPH irg.", + ) + state.require( + not state.last_graph.is_types_amount_defined(), "Types are already defined." + ) + state.require(is_int(types), "Types must be an integer value.", "Example: TYPES 3") + state.require(int(types) >= 0, "Types cannot be a negative number.") + state.last_graph.set_types_amount(int(types)) diff --git a/aasm/parsing/parse.py b/aasm/parsing/parse.py index deebc72..e1b24d9 100644 --- a/aasm/parsing/parse.py +++ b/aasm/parsing/parse.py @@ -18,6 +18,7 @@ from aasm.parsing.op.len import op_LEN from aasm.parsing.op.list_inclusion import handle_list_inclusion from aasm.parsing.op.list_modification import handle_list_modification +from aasm.parsing.op.logs import op_LOGS from aasm.parsing.op.lr import op_LR from aasm.parsing.op.lw import op_LW from aasm.parsing.op.math import handle_math_statement @@ -32,15 +33,20 @@ from aasm.parsing.op.send import op_SEND from aasm.parsing.op.set import op_SET from aasm.parsing.op.size import op_SIZE +from aasm.parsing.op.mparams import op_MPARAMS from aasm.parsing.op.subs import op_SUBS +from aasm.parsing.op.deftype import op_DEFTYPE +from aasm.parsing.op.types import op_TYPES +from aasm.parsing.op.module import op_MODULE from aasm.parsing.state import State if TYPE_CHECKING: from aasm.parsing.state import ParsedData + from aasm.modules.module import Module -def parse_lines(lines: List[str], debug: bool) -> ParsedData: - state = State(lines, debug) +def parse_lines(lines: List[str], debug: bool, modules: List[Module]) -> ParsedData: + state = State(lines, modules, debug) for tokens in state.tokens_from_lines(): match tokens: case ["AGENT", name]: @@ -150,19 +156,41 @@ def parse_lines(lines: List[str], debug: bool) -> ParsedData: case ["SCALE", scale]: op_SCALE(state, scale) + case ["MPARAMS", m0, m_inc]: + op_MPARAMS(state, m0, m_inc) + case ["DEFG", agent_name, amount, *args]: op_DEFG(state, agent_name, amount, args) case ["DEFNODE", agent_name, row]: op_DEFNODE(state, agent_name, row) + case ["DEFTYPE", agent_type, amount, *args]: + op_DEFTYPE(state, agent_type, amount, args) + + case ["TYPES", amount]: + op_TYPES(state, amount) + case ["LR", dst, list_, idx]: op_LR(state, dst, list_, idx) case ["LW", list_, idx, value]: op_LW(state, list_, idx, value) - case _: - state.panic(f"Unknown tokens: {tokens}") + case ["LOGS", level, *args]: + op_LOGS(state, level, args) + + case ["MODULE", name]: + op_MODULE(state, name) + + case [OPCODE, *args]: + found = False + for module in state.loaded_modules: + for instruction in module.instructions: + if instruction.opcode == OPCODE: + found = True + instruction.op(state, args) + if not found: + state.panic(f"Unknown tokens: {tokens}") return state.get_parsed_data() diff --git a/aasm/parsing/state.py b/aasm/parsing/state.py index de44b06..88ac918 100644 --- a/aasm/parsing/state.py +++ b/aasm/parsing/state.py @@ -7,12 +7,15 @@ from aasm.preprocessor.preprocessor import Preprocessor from aasm.utils.exception import PanicException + if TYPE_CHECKING: from aasm.intermediate.action import Action from aasm.intermediate.agent import Agent from aasm.intermediate.behaviour import Behaviour from aasm.intermediate.graph import Graph from aasm.intermediate.message import Message + from aasm.intermediate.module import Module + from aasm.modules.module import Module as LoadedModule class ParsedData: @@ -25,7 +28,7 @@ def __init__( class State: - def __init__(self, lines: List[str], debug: bool): + def __init__(self, lines: List[str], modules: List[LoadedModule], debug: bool): self.debug: bool = debug self.preprocessor = Preprocessor(lines) self.lines: List[str] = self.preprocessor.run() @@ -38,6 +41,9 @@ def __init__(self, lines: List[str], debug: bool): self.agents: Dict[str, Agent] = {} self.messages: Dict[Tuple[str, str], Message] = {} self.graph: Graph | None = None + # TODO: Refactor this to a single module type + self.modules: Dict[str, Module] = {} + self.loaded_modules: List[LoadedModule] = modules @property def last_agent(self) -> Agent: @@ -70,6 +76,9 @@ def add_message(self, message: Message) -> None: def add_graph(self, graph: Graph) -> None: self.graph = graph + def add_module(self, module: Module) -> None: + self.modules[module.name] = module + def agent_exists(self, name: str) -> bool: return name in self.agents @@ -79,9 +88,29 @@ def message_exists(self, msg_type: str, msg_performative: str) -> bool: def graph_exists(self) -> bool: return self.graph is not None + def module_exists(self, name: str) -> bool: + return name in self.modules + + def module_is_loaded(self, name: str) -> bool: + return name in [module.name for module in self.loaded_modules] + def get_message_instance(self, msg_type: str, msg_performative: str) -> Message: return deepcopy(self.messages[(msg_type, msg_performative)]) + def get_module_types(self) -> List[str]: + ret_list = [] + for module in self.modules.values(): + loaded_module = self._find_module(module.name) + if loaded_module is not None: + ret_list += [mod_type.name for mod_type in loaded_module.types] + return ret_list + + def _find_module(self, name): + for module in self.loaded_modules: + if module.name == name: + return module + return None + def tokens_from_lines(self) -> Generator[list[str], None, None]: for line in self.lines: self.line_num += 1 diff --git a/aasm/translate.py b/aasm/translate.py index 651568d..5f10c40 100644 --- a/aasm/translate.py +++ b/aasm/translate.py @@ -5,15 +5,18 @@ from typing import TYPE_CHECKING, List, Tuple from aasm.generating.python_spade import get_spade_code +from aasm.generating.python_module import get_modules_for_target from aasm.utils.exception import PanicException if TYPE_CHECKING: from aasm.generating.code import Code + from aasm.modules.module import Module -def get_args() -> Tuple[str, str, bool]: +def get_args() -> Tuple[str, str, bool, List[str]]: parser = ArgumentParser() parser.add_argument("input_path", type=str, help="path to the input file") + parser.add_argument("-I", "--include", nargs="*", help="include paths") parser.add_argument( "-d", "--debug", action="store_true", help="toggle the compiler debug mode" ) @@ -21,7 +24,7 @@ def get_args() -> Tuple[str, str, bool]: "-o", "--output-path", type=str, default="out", help="the output file path" ) args = parser.parse_args() - return args.input_path, args.output_path, args.debug + return args.input_path, args.output_path, args.debug, args.include def get_input(input_path: str) -> List[str]: @@ -36,11 +39,25 @@ def save_output(output_path: str, code: Code) -> None: f.write(code_line) -def main(input_path: str, output_path: str, debug: bool) -> None: +def main( + input_path: str, output_path: str, debug: bool, includes: List[str] | None +) -> None: + loaded_module_files: List[List[str]] = [] + if includes is not None: + try: + for include in includes: + loaded_module_files.append(get_input(include)) + except PanicException as e: + e.print() + exit(1) + lines = get_input(input_path) start_time = datetime.now() try: - spade_code = get_spade_code(lines, indent_size=4, debug=debug) + spade_modules = get_modules_for_target(loaded_module_files, "spade") + spade_code = get_spade_code( + lines, indent_size=4, debug=debug, modules=spade_modules + ) except PanicException as e: e.print() exit(1) @@ -50,5 +67,5 @@ def main(input_path: str, output_path: str, debug: bool) -> None: if __name__ == "__main__": - input_path, output_path, debug = get_args() - main(input_path, output_path, debug) + input_path, output_path, debug, includes = get_args() + main(input_path, output_path, debug, includes) diff --git a/aasm/utils/validation.py b/aasm/utils/validation.py index f618740..cf855a1 100644 --- a/aasm/utils/validation.py +++ b/aasm/utils/validation.py @@ -60,11 +60,11 @@ def get_invalid_names() -> List[str]: "spade", "copy", "uuid", + "inspect", "get_json_from_spade_message", "get_spade_message", "logger", "any", - "sys", "limit_number", "int", "BackupBehaviour", @@ -72,6 +72,8 @@ def get_invalid_names() -> List[str]: "backup_period", "backup_delay", "setup", + "str", + "float", ] invalid_names.extend(keyword.kwlist) return invalid_names @@ -79,3 +81,11 @@ def get_invalid_names() -> List[str]: def print_invalid_names() -> str: return ", ".join(get_invalid_names()) + + +def get_valid_logs_levels() -> List[str]: + return ["debug", "info", "warning", "error", "critical"] + + +def print_valid_logs_levels() -> str: + return ", ".join(get_valid_logs_levels()) diff --git a/examples/facebook.aasm b/examples/basic/facebook.aasm similarity index 93% rename from examples/facebook.aasm rename to examples/basic/facebook.aasm index 6a3bc6d..1fc599e 100644 --- a/examples/facebook.aasm +++ b/examples/basic/facebook.aasm @@ -32,6 +32,7 @@ agent user ebehav eagent -graph statistical - defg user, 15, dist_exp, 0.1 +graph barabasi-albert + MPARAMS 5, 3 + defg user, 150 egraph diff --git a/examples/facebook_macro.aasm b/examples/basic/facebook_macro.aasm similarity index 100% rename from examples/facebook_macro.aasm rename to examples/basic/facebook_macro.aasm diff --git a/examples/wolfsheep.aasm b/examples/basic/wolfsheep.aasm similarity index 100% rename from examples/wolfsheep.aasm rename to examples/basic/wolfsheep.aasm diff --git a/examples/wolfsheep_macro.aasm b/examples/basic/wolfsheep_macro.aasm similarity index 100% rename from examples/wolfsheep_macro.aasm rename to examples/basic/wolfsheep_macro.aasm diff --git a/examples/graphs/barabasi-albert.aasm b/examples/graphs/barabasi-albert.aasm new file mode 100644 index 0000000..76b42dc --- /dev/null +++ b/examples/graphs/barabasi-albert.aasm @@ -0,0 +1,54 @@ +message facebook_post, query + prm photos, float +emessage + +agent user + prm friends, list, conn + prm num_seen_photos, float, init, 0 + + behav initialize, setup + action initialize_friends, modify_self + decl max_friends, float, 0 + len max_friends, connections + decl num_friends, float, 0 + rand num_friends, int, uniform, 0, max_friends + subs friends, connections, num_friends + eaction + ebehav + + behav facebook_activity, cyclic, 30 + action post_photos, send_msg, facebook_post, query + decl num_photos, float, 0 + rand num_photos, int, uniform, 21, 37 + set send.photos, num_photos + send friends + eaction + ebehav + + behav read_posts, msg_rcv, facebook_post, query + action update_seen_photos, modify_self + add num_seen_photos, rcv.photos + eaction + ebehav +eagent + +agent null +eagent + +agent media_source + behav facebook_activity, cyclic, 30 + action new_post, send_msg, facebook_post, query + decl idx, float, 0 + rand idx, int, uniform, 21, 37 + set send.photos, idx + send connections + eaction + ebehav + +eagent + +graph barabasi-albert + MPARAMS 5,3 + DEFG user, 50 + DEFG media_source, 10 +egraph diff --git a/examples/graphs/inhomogenous.aasm b/examples/graphs/inhomogenous.aasm new file mode 100644 index 0000000..febeed1 --- /dev/null +++ b/examples/graphs/inhomogenous.aasm @@ -0,0 +1,55 @@ +message facebook_post, query + prm photos, float +emessage + +agent user + prm friends, list, conn + prm num_seen_photos, float, init, 0 + + behav initialize, setup + action initialize_friends, modify_self + decl max_friends, float, 0 + len max_friends, connections + decl num_friends, float, 0 + rand num_friends, int, uniform, 0, max_friends + subs friends, connections, num_friends + eaction + ebehav + + behav facebook_activity, cyclic, 30 + action post_photos, send_msg, facebook_post, query + decl num_photos, float, 0 + rand num_photos, int, uniform, 21, 37 + set send.photos, num_photos + send friends + eaction + ebehav + + behav read_posts, msg_rcv, facebook_post, query + action update_seen_photos, modify_self + add num_seen_photos, rcv.photos + eaction + ebehav +eagent + +agent null +eagent + +agent media_source + behav facebook_activity, cyclic, 30 + action new_post, send_msg, facebook_post, query + decl idx, float, 0 + rand idx, int, uniform, 21, 37 + set send.photos, idx + send connections + eaction + ebehav + +eagent + +graph irg + TYPES 3 + DEFTYPE user, 20, 15, 50, 70 + DEFTYPE media_source, 2, 50, 10, 21 + DEFTYPE null, 1, 80, 0, 0 +egraph diff --git a/examples/graphs/matrix.aasm b/examples/graphs/matrix.aasm new file mode 100644 index 0000000..78d5dd1 --- /dev/null +++ b/examples/graphs/matrix.aasm @@ -0,0 +1,57 @@ +message facebook_post, query + prm photos, float +emessage + +agent user + prm friends, list, conn + prm num_seen_photos, float, init, 0 + + behav initialize, setup + action initialize_friends, modify_self + decl max_friends, float, 0 + len max_friends, connections + decl num_friends, float, 0 + rand num_friends, int, uniform, 0, max_friends + subs friends, connections, num_friends + eaction + ebehav + + behav facebook_activity, cyclic, 30 + action post_photos, send_msg, facebook_post, query + decl num_photos, float, 0 + rand num_photos, int, uniform, 21, 37 + set send.photos, num_photos + send friends + eaction + ebehav + + behav read_posts, msg_rcv, facebook_post, query + action update_seen_photos, modify_self + add num_seen_photos, rcv.photos + eaction + ebehav +eagent + +agent null +eagent + +agent media_source + behav facebook_activity, cyclic, 30 + action new_post, send_msg, facebook_post, query + decl idx, float, 0 + rand idx, int, uniform, 21, 37 + set send.photos, idx + send connections + eaction + ebehav + +eagent + +graph matrix + SCALE 3 + DEFNODE user, R01111 + DEFNODE user, R10111 + DEFNODE user, R10011 + DEFNODE media_source, R11100 + DEFNODE null, R10000 +egraph diff --git a/examples/graphs/statistical.aasm b/examples/graphs/statistical.aasm new file mode 100644 index 0000000..1d47bf1 --- /dev/null +++ b/examples/graphs/statistical.aasm @@ -0,0 +1,54 @@ +message facebook_post, query + prm photos, float +emessage + +agent user + prm friends, list, conn + prm num_seen_photos, float, init, 0 + + behav initialize, setup + action initialize_friends, modify_self + decl max_friends, float, 0 + len max_friends, connections + decl num_friends, float, 0 + rand num_friends, int, uniform, 0, max_friends + subs friends, connections, num_friends + eaction + ebehav + + behav facebook_activity, cyclic, 30 + action post_photos, send_msg, facebook_post, query + decl num_photos, float, 0 + rand num_photos, int, uniform, 21, 37 + set send.photos, num_photos + send friends + eaction + ebehav + + behav read_posts, msg_rcv, facebook_post, query + action update_seen_photos, modify_self + add num_seen_photos, rcv.photos + eaction + ebehav +eagent + +agent null +eagent + +agent media_source + behav facebook_activity, cyclic, 30 + action new_post, send_msg, facebook_post, query + decl idx, float, 0 + rand idx, int, uniform, 21, 37 + set send.photos, idx + send connections + eaction + ebehav + +eagent + +graph statistical + SIZE 150 + DEFG user, 20, 10 + DEFG media_source, 100%, dist_exp, 0.1 +egraph diff --git a/examples/modules/facebook_uuid.aasm b/examples/modules/facebook_uuid.aasm new file mode 100644 index 0000000..0351017 --- /dev/null +++ b/examples/modules/facebook_uuid.aasm @@ -0,0 +1,53 @@ +MODULE UUID + +message facebook_post, query + prm photos, float + prm msg_id, uuid +emessage + +agent user + prm friends, list, conn + prm num_seen_photos, float, init, 0 + + behav initialize, setup + action initialize_friends, modify_self + decl max_friends, float, 0 + len max_friends, connections + decl num_friends, float, 0 + rand num_friends, int, uniform, 0, max_friends + subs friends, connections, num_friends + eaction + ebehav + + behav facebook_activity, cyclic, 30 + action post_photos, send_msg, facebook_post, query + decl num_photos, float, 0 + DECL to_send_id, uuid, "" + rand num_photos, int, uniform, 21, 37 + GETUUID to_send_id + set send.photos, num_photos + set send.msg_id, to_send_id + send friends + eaction + ebehav + + behav read_posts, msg_rcv, facebook_post, query + action update_seen_photos, modify_self + add num_seen_photos, rcv.photos + DECL random_id, uuid, "" + GETUUID random_id + ISEQ rcv.msg_id, random_id + # this should never occur + CLR friends + EBLOCK + ISNEQ rcv.msg_id, random_id + add num_seen_photos, 1 + EBLOCK + eaction + ebehav +eagent + +graph barabasi-albert + MPARAMS 5, 3 + defg user, 150 +egraph diff --git a/examples/modules/uuid.aasm.mod b/examples/modules/uuid.aasm.mod new file mode 100644 index 0000000..5ec84f1 --- /dev/null +++ b/examples/modules/uuid.aasm.mod @@ -0,0 +1,38 @@ +!name UUID + +!description +Simple module to use uuids. +Provides functionality to utilise uuids. +targets: spade +Introduces one type: uuid. +Instructions: +GETUUID will generate a new uuid and store it in the declared variable. +ISEQ will compare two uuids and return true if they are equal. +ISNEQ will compare two uuids and return true if they are not equal. + +!targets +spade + +!types +uuid + +!instructions +GETUUID a: mut uuid +ISEQ* a: uuid, b: uuid +ISNEQ* a: uuid, b: uuid + +!preamble spade +import uuid + +!impl GETUUID spade +a = str(uuid.uuid4()) + +!impl ISEQ spade +if a == b: + return True +return False + +!impl ISNEQ spade +if a != b: + return True +return False