Example #1
0
    def validate_type_entry(self, entry):
        """Validate typedef implementation components specifically."""
        check_fields(entry, ['name', 'type', 'params'])
        def_type = entry['type']

        # a '.' indicates it's an import, which will be checked later.
        if '.' in def_type:
            self.components.append(entry)
            return
        if def_type not in self.typedefs:
            raise exceptions.BadInputError(
                f"typedef {def_type} not found; typedef must "
                "be defined before being referenced")

        params = self.typedefs[def_type]['params']
        if len(params) < len(entry['params']):
            raise exceptions.BadInputError(
                f"params ({entry['params']}) present in component "
                "declaration not found in typedef decleration")

        # default arguments using syntax (parameter=default_value)
        for param in params:
            if '=' in param:
                default_param = param.split('=')
                if not default_param[1]:
                    raise exceptions.BadInputError(
                        "default argument operator ('=') used for "
                        f"param {param} but no argument found")
            elif param not in entry['params']:
                raise exceptions.BadInputError(
                    f"param {param} not found and default argument "
                    "not specified")

        self.components.append(entry)
Example #2
0
def check_fields(entry, fields):
    """Check that all provided fields are in entry, where entry is a PDL object."""
    if entry is None:
        raise exceptions.BadInputError("empty entry")
    for field in fields:
        if field not in entry:
            raise exceptions.BadInputError(
                f"field {field} required and not found")
        if entry[field] is None:
            # empty fields will be loaded in as None by pyyaml
            raise exceptions.BadInputError(
                f"field {field} required not to be empty")
    def parse_graphs(self):
        """Extract and store graph information for plumbing engine."""
        graphs = self.package.graphs()
        main_present = False

        # move main graph to end so that its settings take precedence
        for idx, entry in enumerate(graphs):
            if entry['name'] == 'main':
                main_present = True
                graphs.append(graphs.pop(idx))
                break
        if not main_present:
            raise exceptions.BadInputError("must have graph main")

        for entry in graphs:
            for graph_node, node_data in entry['nodes'].items():
                if 'initial_pressure' in node_data:
                    self.initial_pressures[graph_node] = (
                        node_data['initial_pressure'], False)
                if 'fixed_pressure' in node_data:
                    self.initial_pressures[graph_node] = (
                        node_data['fixed_pressure'], True)
                for component_name, component_node in node_data['components']:
                    if component_name not in self.mapping:
                        self.mapping[component_name] = {}

                    self.mapping[component_name][component_node] = graph_node

            self.initial_states.update(entry['states'])
Example #4
0
    def fill_typedef(self, namespace, component):
        """Fill in typedef template for components invoking a typedef."""
        name = component['type']
        component_name = component['name']

        if name.count('.') > 1:
            raise NotImplementedError(
                f"nested imports (in {name}) not supported yet")

        # handle imported components
        if '.' in name:
            # NOTE: we might eventually want to consider how well this will play with nested imports
            fields = name.split('.')
            namespace = fields[0]
            name = fields[-1]
        if name not in self.typedefs[namespace]:
            raise exceptions.BadInputError(f"invalid component type: {name}")

        # default arguments using syntax (parameter=default_value)
        for param in self.typedefs[namespace][name]['params']:
            if '=' in param:
                default_param = param.split('=')
                if default_param[0] not in component['params']:
                    component['params'][default_param[0]] = default_param[1]

        params = component['params']
        body = yaml.dump(self.typedefs[namespace][name])

        for var, value in params.items():
            body = body.replace(var, str(value))

        ret = yaml.safe_load(body)
        ret.pop('params')
        ret['name'] = component_name
        return ret
Example #5
0
    def validate(self):
        """Validate the PDL contents of the File."""

        for entry in self.body:
            e_type = list(entry.keys())[0]
            if e_type not in self.type_checks:
                raise exceptions.BadInputError(f"invalid input type {e_type}")

            body = entry[e_type]
            self.type_checks[e_type](body)
Example #6
0
    def validate_graph(self, entry):
        """Validate graph entries specifically."""
        check_fields(entry, ['name', 'nodes'])
        for node_name in entry['nodes']:
            node = entry['nodes'][node_name]
            if len(node) < 1 or 'components' not in node:
                raise exceptions.BadInputError(
                    f"invalid entry for {node_name}: {node}")

        self.graphs.append(entry)
Example #7
0
    def validate_component(self, entry):
        """Validate component entries specifically."""

        if 'type' in entry:
            self.validate_type_entry(entry)
            return

        check_fields(entry, ['name', 'edges'])

        if 'states' not in entry:
            for edge in entry['edges']:
                edge_value = entry['edges'][edge]
                if 'nodes' not in edge_value or 'teq' not in edge_value or len(
                        edge_value) != 2:
                    raise exceptions.BadInputError(
                        f'invalid single-state component syntax in {entry}')

        self.components.append(entry)
def extract_edges(entry):
    """
    Extract dict of {edge_name: (fwd_edge, back_edge)} from a component entry.

    fwd_edge and back_edge take the form (node1, node2, key), where key is unique among
    edges going between the same nodes.
    """
    name = entry['name']
    edge_dict = {}

    # edges_seen keeps track of edges between the same nodes. Takes form
    # {(node1, node2): key}, where key is the lowest integer that has been used
    # as a key for this set of nodes.
    edges_seen = {}
    for edge_name, edges in entry['edges'].items():
        if len(edges['nodes']) != 2:
            raise exceptions.BadInputError(
                f"malformed nodes entry ({edges['nodes']}) for edge {edge_name} in"
                + f" component {name}")

        # key will just be fwd or back, unless there are multiple edges between the same
        # two nodes, in which case the key will have a unique int appended.
        key = ''
        nodes = tuple(edges['nodes'])
        swapped_nodes = (nodes[1], nodes[0])
        if nodes in edges_seen:
            edges_seen[nodes] += 1
            key = edges_seen[nodes]
        elif swapped_nodes in edges_seen:
            edges_seen[swapped_nodes] += 1
            key = edges_seen[swapped_nodes]
        else:
            edges_seen[nodes] = 1

        node_1, node_2 = edges['nodes']

        fwd_edge = (node_1, node_2, 'fwd' + str(key))
        back_edge = (node_2, node_1, 'back' + str(key))

        edge_dict[edge_name] = (fwd_edge, back_edge)

    return edge_dict
Example #9
0
    def fill_blank_states(self, graph, default_states):
        """Fill in states field with default states if left blank."""
        if 'states' not in graph:
            graph['states'] = {}

        # set of components in this graph
        components = set()
        for node in graph['nodes'].values():
            for component in node['components']:
                components.add(component[0])

        for component in components:
            if component in graph['states']:
                continue

            if component not in default_states:
                raise exceptions.BadInputError(
                    f"missing component {component}: either a nonexistent or a "
                    "multi-state component")

            graph['states'][component] = default_states[component]
Example #10
0
    def __init__(self, path, input_type='f'):
        """
        Initialize a File object from a file's worth of PDL.

        The PDL file should contain exactly three fields:
          - name: the namespace that the file's contents belong in,
          - imports: a list of imports that the file's contents use,
          - body: the body of PDL.

        Parameters
        ----------

        path: string
            path should contain either the path to the file containing PDL,
            or a string that contains a valid PDL file by itself (mostly for
            testing purposes).

        input_type: char
            input_type indicates whether the argument provided to "path" is
            a file (f) path or a string (s).

        Instantiating a File automatically validates its contents; a successful
        initialization produces a ready-to-use File.

        Fields
        ------

        imports: list
            list of imports (by name) that are relevant to this file.

        typedefs: dict
            dict of {typedef name: typedef body}, used to access typedef
            definitions by name.

        components: list
            list of PDL component bodies, stored as objects.

        graphs: list
            list of PDL graph bodies, stored as objects.
        """
        if input_type == 'f':
            file = open(path, 'r')
        elif input_type == 's':
            file = path
        else:
            raise exceptions.BadInputError(f"invalid input type {input_type}")

        pdl = yaml.safe_load(file)

        self.type_checks = {
            'typedef': self.validate_typedef,
            'component': self.validate_component,
            'graph': self.validate_graph,
        }

        self.imports = []
        if 'import' in pdl:
            self.imports = pdl['import']

        self.namespace = pdl['name']
        self.body = pdl['body']
        self.typedefs = {}
        self.components = []
        self.graphs = []
        self.validate()
Example #11
0
    def __init__(self, files, import_paths=None):
        """
        Initialize a Package from one or more Files.

        A Package should have all the components of a complete plumbing engine system; from
        here no additional information will make it into the PlumbingEngine. Once instantiated,
        a Package's PDL is cleaned and ready to use.

        Parameters
        ----------

        files: iterable
            files is an iterable (usually a list) of one or more Files whose contents should go
            into the Package.
        """
        self.import_paths = copy.deepcopy(import_paths)
        if import_paths is None:
            self.import_paths = utils.default_paths
        self.importable_files = dict()

        imports_folder = []

        for import_path in self.import_paths:
            try:
                filenames = os.listdir(import_path)
                filenames = [
                    os.path.join(import_path, fname) for fname in filenames
                ]
                imports_folder.extend(filenames)

            except FileNotFoundError:
                imports_folder = []
                warnings.warn(
                    f"import directory {import_path} could not be found")

        for path in imports_folder:
            try:
                name = yaml.safe_load(open(path, 'r'))['name']

                if name in self.importable_files:
                    self.importable_files[name].add(path)
                else:
                    self.importable_files[name] = {path}
            except KeyError:
                warnings.warn(path + " does not describe a pdl file")

        if len(list(files)) < 1:
            raise exceptions.BadInputError(
                "cannot instantiate a Package with no Files")
        self.imports = []

        # dicts of {namespace: [entries]}, where entry is a PDL object. Organized like this to
        # reduce dict nesting; since this is a one time process it should be easy to keep
        # them synced.
        self.typedefs = {}
        self.component_dict = {}
        self.graph_dict = {}

        for file in files:
            # TODO(wendi): unused import detection
            self.imports.extend(copy.deepcopy(file.imports))

        for imp in set(self.imports):
            if imp not in self.importable_files:
                raise exceptions.BadInputError(f"invalid import: {imp}")
            for path in self.importable_files[imp]:
                files.append(top.File(path))

        # consolidate entry information from files
        for file in files:
            name = file.namespace
            if name not in self.typedefs:
                self.typedefs[name] = {}
                self.component_dict[name] = []
                self.graph_dict[name] = []
            self.typedefs[name].update(copy.deepcopy(file.typedefs))
            self.component_dict[name].extend(copy.deepcopy(file.components))
            self.graph_dict[name].extend(copy.deepcopy(file.graphs))

        self.clean()