From bb54f678a680fdd9803a26e8a565ef26f31ccdcd Mon Sep 17 00:00:00 2001 From: Afsoon Afzal Date: Tue, 30 Jul 2019 18:32:50 -0400 Subject: [PATCH 01/10] Adding kwargs to generate --- experiments/generate_missions.py | 2 +- houston/generator/base.py | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/experiments/generate_missions.py b/experiments/generate_missions.py index 5a2e3f6c..098449a5 100755 --- a/experiments/generate_missions.py +++ b/experiments/generate_missions.py @@ -45,7 +45,7 @@ def generate(num_missions: int, config = build_config(speedup) mission_generator = RandomMissionGenerator(sut, initial, environment, config, max_num_commands=max_num_commands) resource_limits = ResourceLimits(num_missions) - missions = mission_generator.generate(None, resource_limits) + missions = mission_generator.generate(seed, resource_limits) with open(output_file, "w") as f: mission_descriptions = list(map(Mission.to_dict, missions)) json.dump(mission_descriptions, f, indent=2) diff --git a/houston/generator/base.py b/houston/generator/base.py index b7a452bb..80fd01d2 100644 --- a/houston/generator/base.py +++ b/houston/generator/base.py @@ -29,7 +29,7 @@ def __iter__(self): """ return self - def __next__(self): + def __next__(self, **kwargs): """ Requests the next mission from the mission generator. """ @@ -38,7 +38,7 @@ def __next__(self): g.tick() if g.exhausted(): raise StopIteration - mission = self.__generator.generate_mission() + mission = self.__generator.generate_mission(**kwargs) g.tick() g.resource_usage.num_missions += 1 mission_num = g.resource_usage.num_missions @@ -205,7 +205,8 @@ def record_outcome(self, mission, outcome, coverage=None): def generate(self, seed: int, - resource_limits: ResourceLimits + resource_limits: ResourceLimits, + **kwargs ) -> List[Mission]: """ Generate missions and return them @@ -219,7 +220,7 @@ def generate(self, self.tick() # TODO use threads while True: - mission = stream.__next__() + mission = stream.__next__(**kwargs) missions.append(mission) except StopIteration: logger.info("Done with generating missions") From dc843eba5f3cbf64a3b422262a6bb1669e84f5ad Mon Sep 17 00:00:00 2001 From: Afsoon Afzal Date: Tue, 30 Jul 2019 18:33:43 -0400 Subject: [PATCH 02/10] Adding files for template-based missions --- .../generate_missions_templatebased.py | 62 +++++++++ houston/generator/template_based.py | 120 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100755 experiments/generate_missions_templatebased.py create mode 100644 houston/generator/template_based.py diff --git a/experiments/generate_missions_templatebased.py b/experiments/generate_missions_templatebased.py new file mode 100755 index 00000000..15f344ce --- /dev/null +++ b/experiments/generate_missions_templatebased.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +import json +import logging +import argparse +import random + +from houston.mission import Mission +from houston.generator.template_based import TemplateBasedMissionGenerator +from houston.generator.resources import ResourceLimits + +from settings import sut, initial, environment, build_config + + +def setup_logging() -> None: + log_to_stdout = logging.StreamHandler() + log_to_stdout.setLevel(logging.DEBUG) + logging.getLogger('houston').addHandler(log_to_stdout) + + +def parse_args(): + parser = argparse.ArgumentParser(description='Generates missions') + parser.add_argument('number_of_mission', type=int, action='store', + help='number of missions to be generated.') + parser.add_argument('max_num_commands', type=int, action='store', + help='maximum number of commands in a single mission.') + parser.add_argument('--speedup', action='store', type=int, + default=1, + help='simulation speedup that should be used') + parser.add_argument('--seed', action='store', type=int, + default=1000, + help='random seed to be used by random generator.') + parser.add_argument('--output', action='store', type=str, + default='missions.json', + help='the file where the results will be stored') + return parser.parse_args() + + +def generate(num_missions: int, + max_num_commands: int, + seed: int, + output_file: str, + speedup: int + ) -> None: + random.seed(seed) + config = build_config(speedup) + mission_generator = TemplateBasedMissionGenerator(sut, initial, environment, config, max_num_commands=max_num_commands) + resource_limits = ResourceLimits(num_missions) + missions = mission_generator.generate(seed, resource_limits, + template='TAKEOFF(alt:5.2)-.^*-LAND^2-.^1') + with open(output_file, "w") as f: + mission_descriptions = list(map(Mission.to_dict, missions)) + json.dump(mission_descriptions, f, indent=2) + + +if __name__ == "__main__": + setup_logging() + args = parse_args() + generate(num_missions=args.number_of_mission, + max_num_commands=args.max_num_commands, + seed=args.seed, + output_file=args.output, + speedup=args.speedup) diff --git a/houston/generator/template_based.py b/houston/generator/template_based.py new file mode 100644 index 00000000..1f143893 --- /dev/null +++ b/houston/generator/template_based.py @@ -0,0 +1,120 @@ +from typing import Type, Dict, Callable, Optional, Any +import logging +import attr +import re + +from .base import MissionGenerator +from ..mission import Mission +from ..system import System +from ..state import State +from ..environment import Environment +from ..configuration import Configuration +from ..command import Command + +logger = logging.getLogger(__name__) # type: logging.Logger +logger.setLevel(logging.DEBUG) + + +@attr.s +class CommandTemplate: + cmd = attr.ib(type=str) + repeats = attr.ib(type=int) + params = attr.ib(type=Optional[Dict[str, Any]], default=None) + + @staticmethod + def from_str(template_str: str) -> 'CommandTemplate': + regex = "(?P[a-zA-Z\.]+)(?P\(.*\))?(?P\^[\d\*]*)?" + matched = re.fullmatch(regex, template_str.strip()) + if not matched: + logger.error("Template is wrong %s", template_str) + return None + groups = matched.groupdict() + cmd = groups['cmd'] + params = None + repeats = 1 + if groups['params']: + pairs = groups['params'].strip()[1:-1].split(",") + params = {} + for pair in pairs: + splited = pair.split(":") + assert len(splited) == 2 + params[splited[0].strip()] = eval(splited[1]) + if groups['repeats']: + r = groups['repeats'][1:].strip() + if r == '*': + repeats = -1 + else: + repeats = int(r) + return CommandTemplate(cmd, repeats, params) + + def __str__(self): + s = "cmd: {}, params:{}, repeats: {}" + return s.format(self.cmd, + self.params, + self.repeats) + + +class TemplateBasedMissionGenerator(MissionGenerator): + def __init__(self, + system: Type[System], + initial_state: State, + env: Environment, + config: Configuration, + threads: int = 1, + command_generators: Optional[Dict[str, Callable]] = None, + max_num_commands: int = 10 + ) -> None: + super().__init__(system, threads, command_generators, max_num_commands) + self.__initial_state = initial_state + self.__env = env + self.__configuration = config + + @property + def initial_state(self): + """ + The initial state used by all missions produced by this generator. + """ + return self.__initial_state + + @property + def env(self): + """ + The environment used by all missions produced by this generator. + """ + return self.__env + + def generate_command(self, command_class: Type[Command]) -> Command: + generator = self.command_generator(command_class) + if generator is None: + return command_class.generate(self.rng) + # g = generator.generate_action_without_state + # return g(self.system, self.__env, self.rng) + return g(self.rng) + + def generate_mission(self, template: str): + command_templates = [CommandTemplate.from_str(t) \ + for t in template.split("-")] + logger.info("COMMANDS: %s", command_templates) + raise Exception + command_classes = list(self.system.commands.values()) + takeoff = None + for c in command_classes: + if "MAV_CMD_NAV_TAKEOFF" in c.uid: + takeoff = c + break + if not takeoff: + raise Exception("No TAKEOFF command found") + commands = [self.generate_command(takeoff)] + cmds_len = self.max_num_commands + for i in range(1, cmds_len): + next_allowed = commands[i - 1].__class__.get_next_allowed(self.system) # noqa: pycodestyle + if next_allowed: + command_class = self.rng.choice(next_allowed) + commands.append(self.generate_command(command_class)) + else: + break + return Mission(self.__configuration, + self.__env, + self.__initial_state, + commands, + self.system) From 69ce7db2b445163eecf4eb240ac4e8f342e03b92 Mon Sep 17 00:00:00 2001 From: Afsoon Afzal Date: Tue, 30 Jul 2019 21:07:33 -0400 Subject: [PATCH 03/10] Generate list of commands based on the template --- .../generate_missions_templatebased.py | 10 ++-- houston/generator/template_based.py | 54 +++++++++++++------ 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/experiments/generate_missions_templatebased.py b/experiments/generate_missions_templatebased.py index 15f344ce..441d3206 100755 --- a/experiments/generate_missions_templatebased.py +++ b/experiments/generate_missions_templatebased.py @@ -23,6 +23,8 @@ def parse_args(): help='number of missions to be generated.') parser.add_argument('max_num_commands', type=int, action='store', help='maximum number of commands in a single mission.') + parser.add_argument('template', type=str, action='store', + help='template of the mission.') parser.add_argument('--speedup', action='store', type=int, default=1, help='simulation speedup that should be used') @@ -39,14 +41,15 @@ def generate(num_missions: int, max_num_commands: int, seed: int, output_file: str, - speedup: int + speedup: int, + template: str ) -> None: random.seed(seed) config = build_config(speedup) mission_generator = TemplateBasedMissionGenerator(sut, initial, environment, config, max_num_commands=max_num_commands) resource_limits = ResourceLimits(num_missions) missions = mission_generator.generate(seed, resource_limits, - template='TAKEOFF(alt:5.2)-.^*-LAND^2-.^1') + template=template) with open(output_file, "w") as f: mission_descriptions = list(map(Mission.to_dict, missions)) json.dump(mission_descriptions, f, indent=2) @@ -59,4 +62,5 @@ def generate(num_missions: int, max_num_commands=args.max_num_commands, seed=args.seed, output_file=args.output, - speedup=args.speedup) + speedup=args.speedup, + template=args.template) diff --git a/houston/generator/template_based.py b/houston/generator/template_based.py index 1f143893..b089bb2c 100644 --- a/houston/generator/template_based.py +++ b/houston/generator/template_based.py @@ -10,11 +10,19 @@ from ..environment import Environment from ..configuration import Configuration from ..command import Command +from ..exceptions import HoustonException logger = logging.getLogger(__name__) # type: logging.Logger logger.setLevel(logging.DEBUG) +class FailedMissionGenerationException(HoustonException): + """ + Thrown whenever the mission generation fails to follow the + template. + """ + + @attr.s class CommandTemplate: cmd = attr.ib(type=str) @@ -95,24 +103,38 @@ def generate_mission(self, template: str): command_templates = [CommandTemplate.from_str(t) \ for t in template.split("-")] logger.info("COMMANDS: %s", command_templates) - raise Exception - command_classes = list(self.system.commands.values()) - takeoff = None - for c in command_classes: - if "MAV_CMD_NAV_TAKEOFF" in c.uid: - takeoff = c - break - if not takeoff: - raise Exception("No TAKEOFF command found") - commands = [self.generate_command(takeoff)] cmds_len = self.max_num_commands - for i in range(1, cmds_len): - next_allowed = commands[i - 1].__class__.get_next_allowed(self.system) # noqa: pycodestyle - if next_allowed: - command_class = self.rng.choice(next_allowed) - commands.append(self.generate_command(command_class)) - else: + cmds_len -= sum([c.repeats for c in command_templates \ + if c.repeats > 0]) + command_classes = list(self.system.commands.values()) + for tries in range(50): + commands = [] + try: + for ct in command_templates: + r = ct.repeats + if r <=0: + r = min(cmds_len, self.max_num_commands - len(commands)) + for i in range(r): + if commands: + next_allowed = commands[-1].__class__.get_next_allowed(self.system) # noqa: pycodestyle + else: + next_allowed = [cc for cc in command_classes] # Everything is allowed + + if ct.cmd != '.': + next_allowed = [cc for cc in next_allowed \ + if ct.cmd in cc.uid] + + if not next_allowed and ct.repeats > 0: + logger.debug("So far %s", commands) + raise FailedMissionGenerationException + command_class = self.rng.choice(next_allowed) + commands.append(self.generate_command(command_class)) break + except FailedMissionGenerationException: + logger.debug("Try %d failed", tries) + continue + logger.info("Generated mission: %s", commands) + raise Exception return Mission(self.__configuration, self.__env, self.__initial_state, From 771c477aae351f44bb3a16579e83414f80ea92e2 Mon Sep 17 00:00:00 2001 From: Afsoon Afzal Date: Tue, 30 Jul 2019 21:13:19 -0400 Subject: [PATCH 04/10] raise exception if it's not possible to generate mission: --- houston/generator/template_based.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/houston/generator/template_based.py b/houston/generator/template_based.py index b089bb2c..4eedf2ba 100644 --- a/houston/generator/template_based.py +++ b/houston/generator/template_based.py @@ -124,15 +124,21 @@ def generate_mission(self, template: str): next_allowed = [cc for cc in next_allowed \ if ct.cmd in cc.uid] - if not next_allowed and ct.repeats > 0: - logger.debug("So far %s", commands) - raise FailedMissionGenerationException + if not next_allowed: + if ct.repeats > 0: + logger.debug("So far %s", commands) + commands = [] + raise FailedMissionGenerationException + else: + break command_class = self.rng.choice(next_allowed) commands.append(self.generate_command(command_class)) break except FailedMissionGenerationException: logger.debug("Try %d failed", tries) continue + if not commands: + raise FailedMissionGenerationException("Mission generation failed") logger.info("Generated mission: %s", commands) raise Exception return Mission(self.__configuration, From ca0d16f4e9e6b674d176ea2f98a1c71ad90af0ce Mon Sep 17 00:00:00 2001 From: Afsoon Afzal Date: Tue, 30 Jul 2019 21:25:44 -0400 Subject: [PATCH 05/10] consider fixed parameters as part of template --- houston/command.py | 13 +++++++++++++ houston/generator/template_based.py | 13 +++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/houston/command.py b/houston/command.py index 6b97a049..9e505297 100644 --- a/houston/command.py +++ b/houston/command.py @@ -372,6 +372,19 @@ def generate(cls, rng: random.Random) -> 'Command': command = cls(**params) return command + @classmethod + def generate_fixed_params(cls, + fixed_params: Dict[str, Any], + rng: random.Random + ) -> 'Command': + params = {} + for p in cls.parameters: + if p.name in fixed_params.keys(): + params[p.name] = fixed_params[p.name] + else: + params[p.name] = p.generate(rng) + command = cls(**params) + return command @attr.s(frozen=True) class CommandOutcome(object): diff --git a/houston/generator/template_based.py b/houston/generator/template_based.py index 4eedf2ba..78f63ba1 100644 --- a/houston/generator/template_based.py +++ b/houston/generator/template_based.py @@ -31,7 +31,7 @@ class CommandTemplate: @staticmethod def from_str(template_str: str) -> 'CommandTemplate': - regex = "(?P[a-zA-Z\.]+)(?P\(.*\))?(?P\^[\d\*]*)?" + regex = "(?P[a-zA-Z\.\_]+)(?P\(.*\))?(?P\^[\d\*]*)?" matched = re.fullmatch(regex, template_str.strip()) if not matched: logger.error("Template is wrong %s", template_str) @@ -91,10 +91,14 @@ def env(self): """ return self.__env - def generate_command(self, command_class: Type[Command]) -> Command: + def generate_command(self, + command_class: Type[Command], + params: Dict[str, Any] + ) -> Command: generator = self.command_generator(command_class) if generator is None: - return command_class.generate(self.rng) + return command_class.generate_fixed_params(params, + self.rng) # g = generator.generate_action_without_state # return g(self.system, self.__env, self.rng) return g(self.rng) @@ -131,8 +135,9 @@ def generate_mission(self, template: str): raise FailedMissionGenerationException else: break + params = ct.params or {} command_class = self.rng.choice(next_allowed) - commands.append(self.generate_command(command_class)) + commands.append(self.generate_command(command_class, params)) break except FailedMissionGenerationException: logger.debug("Try %d failed", tries) From 8a06663b8b0a7339114723901872e9be75c24a44 Mon Sep 17 00:00:00 2001 From: Afsoon Afzal Date: Tue, 30 Jul 2019 21:50:07 -0400 Subject: [PATCH 06/10] return a mission. fix the bug of generating long commands --- houston/generator/template_based.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/houston/generator/template_based.py b/houston/generator/template_based.py index 78f63ba1..81f07185 100644 --- a/houston/generator/template_based.py +++ b/houston/generator/template_based.py @@ -113,11 +113,12 @@ def generate_mission(self, template: str): command_classes = list(self.system.commands.values()) for tries in range(50): commands = [] + max_nonfixed_commands = cmds_len try: for ct in command_templates: r = ct.repeats if r <=0: - r = min(cmds_len, self.max_num_commands - len(commands)) + r = self.rng.randint(0, max_nonfixed_commands) for i in range(r): if commands: next_allowed = commands[-1].__class__.get_next_allowed(self.system) # noqa: pycodestyle @@ -138,6 +139,8 @@ def generate_mission(self, template: str): params = ct.params or {} command_class = self.rng.choice(next_allowed) commands.append(self.generate_command(command_class, params)) + if ct.repeats <= 0: + max_nonfixed_commands -= 1 break except FailedMissionGenerationException: logger.debug("Try %d failed", tries) @@ -145,7 +148,6 @@ def generate_mission(self, template: str): if not commands: raise FailedMissionGenerationException("Mission generation failed") logger.info("Generated mission: %s", commands) - raise Exception return Mission(self.__configuration, self.__env, self.__initial_state, From 035ea17c0dcfb00213630651ec52690b55a7e5dd Mon Sep 17 00:00:00 2001 From: Afsoon Afzal Date: Tue, 30 Jul 2019 21:55:10 -0400 Subject: [PATCH 07/10] style fixes --- houston/command.py | 1 + houston/generator/template_based.py | 22 ++++++++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/houston/command.py b/houston/command.py index 9e505297..0cc7357a 100644 --- a/houston/command.py +++ b/houston/command.py @@ -386,6 +386,7 @@ def generate_fixed_params(cls, command = cls(**params) return command + @attr.s(frozen=True) class CommandOutcome(object): """ diff --git a/houston/generator/template_based.py b/houston/generator/template_based.py index 81f07185..c7816b5c 100644 --- a/houston/generator/template_based.py +++ b/houston/generator/template_based.py @@ -31,7 +31,7 @@ class CommandTemplate: @staticmethod def from_str(template_str: str) -> 'CommandTemplate': - regex = "(?P[a-zA-Z\.\_]+)(?P\(.*\))?(?P\^[\d\*]*)?" + regex = r"(?P[a-zA-Z\.\_]+)(?P\(.*\))?(?P\^[\d\*]*)?" # noqa: pycodestyle matched = re.fullmatch(regex, template_str.strip()) if not matched: logger.error("Template is wrong %s", template_str) @@ -104,12 +104,12 @@ def generate_command(self, return g(self.rng) def generate_mission(self, template: str): - command_templates = [CommandTemplate.from_str(t) \ - for t in template.split("-")] + command_templates = [CommandTemplate.from_str(t) + for t in template.split("-")] logger.info("COMMANDS: %s", command_templates) cmds_len = self.max_num_commands - cmds_len -= sum([c.repeats for c in command_templates \ - if c.repeats > 0]) + cmds_len -= sum([c.repeats for c in command_templates + if c.repeats > 0]) command_classes = list(self.system.commands.values()) for tries in range(50): commands = [] @@ -117,17 +117,18 @@ def generate_mission(self, template: str): try: for ct in command_templates: r = ct.repeats - if r <=0: + if r <= 0: r = self.rng.randint(0, max_nonfixed_commands) for i in range(r): if commands: next_allowed = commands[-1].__class__.get_next_allowed(self.system) # noqa: pycodestyle else: - next_allowed = [cc for cc in command_classes] # Everything is allowed + # everything is allowed + next_allowed = [cc for cc in command_classes] if ct.cmd != '.': - next_allowed = [cc for cc in next_allowed \ - if ct.cmd in cc.uid] + next_allowed = [cc for cc in next_allowed + if ct.cmd in cc.uid] if not next_allowed: if ct.repeats > 0: @@ -138,7 +139,8 @@ def generate_mission(self, template: str): break params = ct.params or {} command_class = self.rng.choice(next_allowed) - commands.append(self.generate_command(command_class, params)) + commands.append(self.generate_command(command_class, + params)) if ct.repeats <= 0: max_nonfixed_commands -= 1 break From 09f46c4401552868a5d2535bd38eef8e35a51fd7 Mon Sep 17 00:00:00 2001 From: Afsoon Afzal Date: Tue, 30 Jul 2019 22:10:14 -0400 Subject: [PATCH 08/10] Adding comments --- houston/generator/template_based.py | 41 ++++++++++++++++++++++++++--- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/houston/generator/template_based.py b/houston/generator/template_based.py index c7816b5c..ec14a5d2 100644 --- a/houston/generator/template_based.py +++ b/houston/generator/template_based.py @@ -25,12 +25,30 @@ class FailedMissionGenerationException(HoustonException): @attr.s class CommandTemplate: + """ + A CommandTemplate describes template for a set of commands. + """ + cmd = attr.ib(type=str) repeats = attr.ib(type=int) params = attr.ib(type=Optional[Dict[str, Any]], default=None) @staticmethod def from_str(template_str: str) -> 'CommandTemplate': + """ + Creates a CommandTemplate based on provided template + string. The template should take the following format: + `()^` + Example: + `TAKEOFF(alt: 10.3)^2` which means template is for + two `TAKEOFF` commands with parameter `alt` set to 10.3 + Providing the parameters and number of repeats are + optional. By default, number of repeats is set to 1. If `*` + is provided as number of repeates, it will be dynamically + decided. If command name is provided as `.` it means any + possible command. + """ + regex = r"(?P[a-zA-Z\.\_]+)(?P\(.*\))?(?P\^[\d\*]*)?" # noqa: pycodestyle matched = re.fullmatch(regex, template_str.strip()) if not matched: @@ -63,6 +81,11 @@ def __str__(self): class TemplateBasedMissionGenerator(MissionGenerator): + """ + Given a template for intended missions, this generator + generates missions. + """ + def __init__(self, system: Type[System], initial_state: State, @@ -104,22 +127,34 @@ def generate_command(self, return g(self.rng) def generate_mission(self, template: str): + """ + A list of CommandTemplate strings separated by `-` + should be provided as template. + Example: + TAKEOFF(alt: 10.3)-.^*-LAND-.^1 + + raises: + FailedMissionGenerationException: + It failed to generate a mission for this + template. + """ command_templates = [CommandTemplate.from_str(t) for t in template.split("-")] - logger.info("COMMANDS: %s", command_templates) cmds_len = self.max_num_commands cmds_len -= sum([c.repeats for c in command_templates if c.repeats > 0]) command_classes = list(self.system.commands.values()) for tries in range(50): + # try at most 50 times to generate a mission commands = [] max_nonfixed_commands = cmds_len try: for ct in command_templates: r = ct.repeats if r <= 0: + # number of repeats between 0 and max allowed r = self.rng.randint(0, max_nonfixed_commands) - for i in range(r): + for _ in range(r): if commands: next_allowed = commands[-1].__class__.get_next_allowed(self.system) # noqa: pycodestyle else: @@ -132,7 +167,7 @@ def generate_mission(self, template: str): if not next_allowed: if ct.repeats > 0: - logger.debug("So far %s", commands) + # logger.debug("So far %s", commands) commands = [] raise FailedMissionGenerationException else: From 752a506dece778a36bd8bc97d5200cba70829be4 Mon Sep 17 00:00:00 2001 From: Afsoon Afzal Date: Thu, 1 Aug 2019 17:24:37 -0400 Subject: [PATCH 09/10] Read templates from mutants file and generate missions --- .../generate_missions_templatebased.py | 74 +++++++++++++++---- houston/generator/template_based.py | 2 +- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/experiments/generate_missions_templatebased.py b/experiments/generate_missions_templatebased.py index 441d3206..38c57cd9 100755 --- a/experiments/generate_missions_templatebased.py +++ b/experiments/generate_missions_templatebased.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 import json +import yaml import logging import argparse import random @@ -11,9 +12,13 @@ from settings import sut, initial, environment, build_config -def setup_logging() -> None: +logger = logging.getLogger("houston") # type: logging.Logger +logger.setLevel(logging.DEBUG) + + +def setup_logging(verbose: bool = False) -> None: log_to_stdout = logging.StreamHandler() - log_to_stdout.setLevel(logging.DEBUG) + log_to_stdout.setLevel(logging.DEBUG if verbose else logging.INFO) logging.getLogger('houston').addHandler(log_to_stdout) @@ -23,8 +28,10 @@ def parse_args(): help='number of missions to be generated.') parser.add_argument('max_num_commands', type=int, action='store', help='maximum number of commands in a single mission.') - parser.add_argument('template', type=str, action='store', + parser.add_argument('-t', type=str, action='store', help='template of the mission.') + parser.add_argument('-f', type=str, action='store', + help='yaml file of mutants nad their templates.') parser.add_argument('--speedup', action='store', type=int, default=1, help='simulation speedup that should be used') @@ -34,33 +41,70 @@ def parse_args(): parser.add_argument('--output', action='store', type=str, default='missions.json', help='the file where the results will be stored') + parser.add_argument('--verbose', action='store_true', + default=False, + help='verbose logging.') return parser.parse_args() def generate(num_missions: int, max_num_commands: int, seed: int, - output_file: str, speedup: int, template: str ) -> None: - random.seed(seed) config = build_config(speedup) mission_generator = TemplateBasedMissionGenerator(sut, initial, environment, config, max_num_commands=max_num_commands) resource_limits = ResourceLimits(num_missions) missions = mission_generator.generate(seed, resource_limits, template=template) - with open(output_file, "w") as f: - mission_descriptions = list(map(Mission.to_dict, missions)) - json.dump(mission_descriptions, f, indent=2) + return missions + if __name__ == "__main__": - setup_logging() args = parse_args() - generate(num_missions=args.number_of_mission, - max_num_commands=args.max_num_commands, - seed=args.seed, - output_file=args.output, - speedup=args.speedup, - template=args.template) + setup_logging(args.verbose) + random.seed(args.seed) + with open(args.output, "w") as f: + pass + if args.t: + missions = generate(num_missions=args.number_of_mission, + max_num_commands=args.max_num_commands, + seed=args.seed, + speedup=args.speedup, + template=args.t) + elif args.f: + templates = [] + with open(args.f, "r") as f: + mutants = yaml.load(f, Loader=yaml.FullLoader) + for m in mutants: + template = m.get('mission-template') + if not template: + continue + if isinstance(template, str): + templates.append((template, args.number_of_mission)) + elif isinstance(template, list): + n_missions = args.number_of_mission + for i, t in enumerate(template): + if i == len(template)-1: + templates.append((t, n_missions)) + else: + r = random.randint(0, n_missions) + templates.append((t, r)) + n_missions -= r + logger.info("Number of templates: %d", len(templates)) + logger.info("AAAA %s", templates) + missions = [] + for template, num in templates: + missions.extend(generate(num_missions=num, + max_num_commands=args.max_num_commands, + seed=args.seed, + speedup=args.speedup, + template=template)) + else: + raise Exception("Provide either -t or -f") + + with open(args.output, "w") as f: + mission_descriptions = list(map(Mission.to_dict, missions)) + json.dump(mission_descriptions, f, indent=2) diff --git a/houston/generator/template_based.py b/houston/generator/template_based.py index ec14a5d2..e884c868 100644 --- a/houston/generator/template_based.py +++ b/houston/generator/template_based.py @@ -184,7 +184,7 @@ def generate_mission(self, template: str): continue if not commands: raise FailedMissionGenerationException("Mission generation failed") - logger.info("Generated mission: %s", commands) + logger.debug("Generated mission: %s", commands) return Mission(self.__configuration, self.__env, self.__initial_state, From a11b09b4c3d62c40ee0ad73056b3609940210afb Mon Sep 17 00:00:00 2001 From: Afsoon Afzal Date: Fri, 9 Aug 2019 11:43:37 -0400 Subject: [PATCH 10/10] Fixes generating parameters using circle-based generator --- .../generate_missions_templatebased.py | 21 ++++++++++++------- houston/ardu/command_factory.py | 8 +++++-- houston/command.py | 5 +++-- 3 files changed, 22 insertions(+), 12 deletions(-) diff --git a/experiments/generate_missions_templatebased.py b/experiments/generate_missions_templatebased.py index 38c57cd9..174b350c 100755 --- a/experiments/generate_missions_templatebased.py +++ b/experiments/generate_missions_templatebased.py @@ -74,6 +74,7 @@ def generate(num_missions: int, seed=args.seed, speedup=args.speedup, template=args.t) + mission_descriptions = list(map(Mission.to_dict, missions)) elif args.f: templates = [] with open(args.f, "r") as f: @@ -83,28 +84,32 @@ def generate(num_missions: int, if not template: continue if isinstance(template, str): - templates.append((template, args.number_of_mission)) + templates.append((template, args.number_of_mission, m['uid'])) elif isinstance(template, list): n_missions = args.number_of_mission for i, t in enumerate(template): if i == len(template)-1: - templates.append((t, n_missions)) + templates.append((t, n_missions, m['uid'])) else: r = random.randint(0, n_missions) - templates.append((t, r)) + templates.append((t, r, m['uid'])) n_missions -= r logger.info("Number of templates: %d", len(templates)) logger.info("AAAA %s", templates) - missions = [] - for template, num in templates: - missions.extend(generate(num_missions=num, + mission_descriptions = [] + for template, num, uid in templates: + missions = generate(num_missions=num, max_num_commands=args.max_num_commands, seed=args.seed, speedup=args.speedup, - template=template)) + template=template) + for n in missions: + new_dict = n.to_dict() + new_dict['mutant'] = uid + mission_descriptions.append(new_dict) else: raise Exception("Provide either -t or -f") + logger.info("Total number of missions: %d", len(mission_descriptions)) with open(args.output, "w") as f: - mission_descriptions = list(map(Mission.to_dict, missions)) json.dump(mission_descriptions, f, indent=2) diff --git a/houston/ardu/command_factory.py b/houston/ardu/command_factory.py index 6e549211..ef978c45 100644 --- a/houston/ardu/command_factory.py +++ b/houston/ardu/command_factory.py @@ -27,8 +27,12 @@ def circle_based_generator(cls: Type[Command], destination = dist.destination(origin, heading) prob_zero = 0.1 # the probability of generating 0.0 as the parameter value - params['lat'] = destination.latitude if rng.random() >= prob_zero else 0.0 - params['lon'] = destination.longitude if rng.random() >= prob_zero else 0.0 + if rng.random() >= prob_zero: + params['lat'] = destination.latitude + params['lon'] = destination.longitude + else: + params['lat'] = 0.0 + params['lon'] = 0.0 command = cls(**params) return command diff --git a/houston/command.py b/houston/command.py index 0cc7357a..fe5443d3 100644 --- a/houston/command.py +++ b/houston/command.py @@ -377,12 +377,13 @@ def generate_fixed_params(cls, fixed_params: Dict[str, Any], rng: random.Random ) -> 'Command': + new_cmd = cls.generate(rng) params = {} - for p in cls.parameters: + for p in new_cmd: if p.name in fixed_params.keys(): params[p.name] = fixed_params[p.name] else: - params[p.name] = p.generate(rng) + params[p.name] = new_cmd[p.name] command = cls(**params) return command