diff --git a/kadmos/graph/graph_kadmos.py b/kadmos/graph/graph_kadmos.py index ceef836212a53b6a4174f1a63a1f3b5bfdcb04bc..14a298db93477073adf36fea0c4d0c7287979064 100644 --- a/kadmos/graph/graph_kadmos.py +++ b/kadmos/graph/graph_kadmos.py @@ -17,7 +17,7 @@ import matplotlib.pyplot as plt import numpy as np from lxml import etree -from lxml.etree import ElementTree, Element +from lxml.etree import ElementTree from datetime import datetime from copy import deepcopy @@ -29,7 +29,7 @@ from ..utilities import printing from ..utilities.general import transform_data_into_strings, transform_string_into_format, make_camel_case, \ unmake_camel_case, get_list_entries, open_file, make_plural, color_list, test_attr_cond, get_schema, translate_list from ..utilities.xml import Child, ChildGroup, ChildSequence, recursively_empty, recursively_stringify, \ - recursively_dictify + recursively_dictify, Element from ..utilities.testing import check from mixin_equation import EquationMixin @@ -902,6 +902,109 @@ class KadmosGraph(nx.DiGraph, EquationMixin, VistomsMixin): # Return return + def _create_cmdows_header(self, description, modification, creator, version, cmdows_version, + timestamp=datetime.now()): + """Method to create the CMDOWS header element + + :return: CMDOWS header element + :rtype: Element + """ + + # Create header + cmdows_header = Element('header') + cmdows_header.add('creator', creator) + cmdows_header.add('description', description) + cmdows_header.add('timestamp', timestamp.isoformat()) + cmdows_header.add('fileVersion', version) + cmdows_header.add('cmdowsVersion', cmdows_version) + + # Create header/update + cmdows_updates = cmdows_header.add('updates') + + # Create header/updates/update + cmdows_update = cmdows_updates.add('update') + cmdows_update.add('modification', modification) + cmdows_update.add('creator', creator) + cmdows_update.add('timestamp', timestamp.isoformat()) + cmdows_update.add('fileVersion', version) + cmdows_update.add('cmdowsVersion', cmdows_version) + + return cmdows_header + + def _create_cmdows_competences(self, graph, graph_design_competences): + """Method to create the CMDOWS designCompetences element + + :return: CMDOWS designCompetences element + :rtype: Element + """ + + # Create designCompetences + cmdows_design_competences = Element('designCompetences') + for graph_design_competence in graph_design_competences: + + # Create designCompetences/designCompetence + graph_design_competence_data = graph.node[graph_design_competence] + cmdows_design_competence = cmdows_design_competences.add('designCompetence') + cmdows_design_competence.set('uID', graph_design_competence) + cmdows_design_competence.add('ID', graph_design_competence_data.get('name')) + cmdows_design_competence.add('modeID', graph_design_competence_data.get('mode', 'main')) + cmdows_design_competence.add('instanceID', graph_design_competence_data.get('instance', '1.0')) + cmdows_design_competence.add('version', graph_design_competence_data.get('version', '1.0')) + cmdows_design_competence.add('label', graph_design_competence_data.get('label')) + + # Create designCompetences/designCompetence/inputs with childs + graph_inputs = graph.in_edges(graph_design_competence) + cmdows_inputs = cmdows_design_competence.add('inputs') + for graph_input in graph_inputs: + cmdows_input = cmdows_inputs.add('input') + cmdows_input.add('parameterUID', graph_input[0]) + + # Create designCompetences/designCompetence/outputs with childs + graph_outputs = graph.out_edges(graph_design_competence) + cmdows_outputs = cmdows_design_competence.add('outputs') + for graph_output in graph_outputs: + cmdows_output = cmdows_outputs.add('output') + cmdows_output.add('parameterUID', graph_output[1]) + + # Create designCompetences/designCompetence/metadata + # TODO: Make sure that the metadata dictionary is created somewhere else + # TODO: Also implement a check that only 'valid' information is stored in the metadata dictionary + graph_design_competence_data['metadata'] = { + 'general_info': { + 'description': graph_design_competence_data.get('description') + }, + 'performance_info': { + 'precision': graph_design_competence_data.get('precision'), + 'fidelity_level': graph_design_competence_data.get('level'), + 'run_time': graph_design_competence_data.get('run_time'), + + } + } + cmdows_design_competence.add_dict('metadata', graph_design_competence_data.get('metadata')) + + return cmdows_design_competences + + def _create_cmdows_parameters(self, graph): + """Method to create the CMDOWS parameters element + + :return: CMDOWS parameters element + :rtype: Element + """ + + # Create parameters + graph_parameters = graph.find_all_nodes(category='variable', attr_exclude=['architecture_role', + self.ARCHITECTURE_ROLES_VARS]) + cmdows_parameters = Element('parameters') + for graph_parameter in graph_parameters: + + # Create parameters/parameter + cmdows_parameter = cmdows_parameters.add('parameter') + cmdows_parameter.set('uID', graph_parameter) + graph_keys = ['label', 'note', 'description', 'unit', 'data_type'] + cmdows_parameter.add_sequence(graph.node[graph_parameter].items(), graph_keys) + + return cmdows_parameters + def _save_cmdows(self, file_path, description, creator, version, timestamp=datetime.now(), mpg=None, @@ -942,22 +1045,8 @@ class KadmosGraph(nx.DiGraph, EquationMixin, VistomsMixin): cmdows = Element('cmdows', attrib={cmdows_namespace: cmdows_schema}) # Create header - cmdows_header = Child(cmdows, 'header') - Child(cmdows_header, 'creator', creator) - Child(cmdows_header, 'description', description) - Child(cmdows_header, 'timestamp', timestamp.isoformat()) - Child(cmdows_header, 'fileVersion', version) - Child(cmdows_header, 'cmdowsVersion', cmdows_version) - # Create header/update - cmdows_updates = Child(cmdows_header, 'updates') - # Create header/updates/update - # TODO: Check for existing updates and append accordingly - cmdows_update = Child(cmdows_updates, 'update') - Child(cmdows_update, 'modification', modification) - Child(cmdows_update, 'creator', creator) - Child(cmdows_update, 'timestamp', timestamp.isoformat()) - Child(cmdows_update, 'fileVersion', version) - Child(cmdows_update, 'cmdowsVersion', cmdows_version) + cmdows.append(self._create_cmdows_header(description=description, modification=modification, creator=creator, + version=version, cmdows_version=cmdows_version, timestamp=timestamp)) # Create executableBlocks cmdows_executable_blocks = Child(cmdows, 'executableBlocks') @@ -968,70 +1057,12 @@ class KadmosGraph(nx.DiGraph, EquationMixin, VistomsMixin): graph_mathematical_functions = set(graph_executable_blocks).intersection(graph_mathematical_functions) graph_design_competences = set(graph_executable_blocks).difference(graph_mathematical_functions) # Create executableBlocks/designCompetences - cmdows_design_competences = Child(cmdows_executable_blocks, 'designCompetences') - for graph_design_competence in graph_design_competences: - # Create executableBlocks/designCompetences/designCompetence - cmdows_design_competence = Child(cmdows_design_competences, 'designCompetence') - Child(cmdows_design_competence, 'ID', graph.node[graph_design_competence].get('name')) - Child(cmdows_design_competence, 'modeID', graph.node[graph_design_competence].get('mode', 'main')) - # TODO: Support instanceID and version in KADMOS (also add to load function) - Child(cmdows_design_competence, 'instanceID', '1') - Child(cmdows_design_competence, 'version', '1') - cmdows_design_competence.set('uID', graph_design_competence) - # cmdows_design_competence.set('uID', graph.node[graph_designCompetence].get('name')+'['+ - # graph.node[graph_designCompetence].get('mode')+'][1][1.0]') - Child(cmdows_design_competence, 'label', graph.node[graph_design_competence].get('label')) - # Create executableBlocks/designCompetences/designCompetence/inputs with childs - # and create executableBlocks/designCompetences/designCompetence/outputs with childs - graph_inputs = graph.in_edges(graph_design_competence) - graph_outputs = graph.out_edges(graph_design_competence) - - # TODO: Move this function to another place and make it more generic (below the function is redefined too) - def inouts(tag, graph_inouts): - if tag == 'inputs': - ix = 0 - elif tag == 'outputs': - ix = 1 - cmdows_inouts = Element(tag) - for graph_inout in graph_inouts: - cmdows_inout = Child(cmdows_inouts, tag[:-1]) - # noinspection PyUnboundLocalVariable - Child(cmdows_inout, 'parameterUID', graph_inout[ix]) - return cmdows_inouts - - cmdows_design_competence.append(inouts('inputs', graph_inputs)) - cmdows_design_competence.append(inouts('outputs', graph_outputs)) - # Create executableBlocks/designCompetences/designCompetence/metadata - cmdows_metadata = Child(cmdows_design_competence, 'metadata') - graph_data = graph.node[graph_design_competence] - # Create executableBlocks/designCompetences/designCompetence/metadata/generalInfo - cmdows_general_info = Child(cmdows_metadata, 'generalInfo') - Child(cmdows_general_info, 'description', graph_data.get('description')) - # Create executableBlocks/designCompetences/designCompetence/metadata/performanceInfo - cmdows_performance_info = Child(cmdows_metadata, 'performanceInfo') - graph_keys = ['precision', 'level', 'run_time', 'standard_deviation'] - graph_dict = {'level': 'fidelityLevel'} - ChildSequence(cmdows_performance_info, graph_data.items(), graph_keys, graph_dict) - # Create executableBlocks/designCompetences/designCompetence/metadata/executionInfo - cmdows_execution_info = Child(cmdows_metadata, 'executionInfo') - Child(cmdows_execution_info, 'rce') - Child(cmdows_execution_info, 'optimus') - Child(cmdows_execution_info, 'python') - Child(cmdows_execution_info, 'matlab') - Child(cmdows_execution_info, 'windowsCommandLine') - Child(cmdows_execution_info, 'brics') + cmdows_executable_blocks.append(self._create_cmdows_competences(graph, graph_design_competences)) # Create executableBlocks/mathematicalFunctions - cmdows_executable_blocks.append(self._create_cmdows_equations()) + cmdows_executable_blocks.append(self._create_cmdows_equations(graph, graph_mathematical_functions)) + # Create parameters - cmdows_parameters = Child(cmdows, 'parameters') - graph_parameters = graph.find_all_nodes(category='variable', attr_exclude=['architecture_role', - self.ARCHITECTURE_ROLES_VARS]) - for graph_parameter in graph_parameters: - # Create parameters/parameter - cmdows_parameter = Child(cmdows_parameters, 'parameter') - cmdows_parameter.set('uID', graph_parameter) - graph_keys = ['label', 'description', 'note'] - ChildSequence(cmdows_parameter, graph.node[graph_parameter].items(), graph_keys) + cmdows.append(self._create_cmdows_parameters(graph)) # The following nodes are only generated for FPGs and MDGs if isinstance(graph, FundamentalProblemGraph) or isinstance(graph, MdaoDataGraph): diff --git a/kadmos/graph/mixin_equation.py b/kadmos/graph/mixin_equation.py index 5db73dfa2229b5733516a1fcc21a50a75ef3883a..0344fcc2b973a7c5df5677b0111f58b4ec9da9e8 100644 --- a/kadmos/graph/mixin_equation.py +++ b/kadmos/graph/mixin_equation.py @@ -2,9 +2,7 @@ import math import logging -from lxml.etree import Element - -from ..utilities.xml import Child +from ..utilities.xml import Child, Element # Settings for the logger @@ -170,7 +168,7 @@ class EquationMixin(object): return - def _create_cmdows_equations(self): + def _create_cmdows_equations(self, graph, graph_math_funcs): """Method to create the CMDOWS mathematicalFunctions element :type self: KadmosGraph @@ -179,27 +177,33 @@ class EquationMixin(object): :rtype: Element """ - graph_math_funcs = self._get_equation_nodes() + # Create mathematicalFunctions cmdows_math_funcs = Element('mathematicalFunctions') for graph_math_func in graph_math_funcs: - cmdows_math_func = Child(cmdows_math_funcs, 'mathematicalFunction', uID=graph_math_func) - Child(cmdows_math_func, 'label', self.node[graph_math_func].get('label', graph_math_func)) - graph_inputs = self.in_edges(graph_math_func, data=True) - cmdows_inputs = Child(cmdows_math_func, 'inputs') + + # Create mathematicalFunctions/mathematicalFunction + cmdows_math_func = cmdows_math_funcs.add('mathematicalFunction', uID=graph_math_func) + cmdows_math_func.add('label', graph.node[graph_math_func].get('label', graph_math_func)) + + # Create mathematicalFunctions/mathematicalFunction/inputs with childs + graph_inputs = graph.in_edges(graph_math_func, data=True) + cmdows_inputs = cmdows_math_func.add('inputs') for graph_input in graph_inputs: - cmdows_input = Child(cmdows_inputs, 'input') - Child(cmdows_input, 'parameterUID', graph_input[0]) - Child(cmdows_input, 'equationLabel', graph_input[2]['equation_label']) - graph_outputs = self.out_edges(graph_math_func, data=True) - cmdows_outputs = Child(cmdows_math_func, 'outputs') + cmdows_input = cmdows_inputs.add('input') + cmdows_input.add('parameterUID', graph_input[0]) + cmdows_input.add('equationLabel', graph_input[2]['equation_label']) + + # Create mathematicalFunctions/mathematicalFunction/outputs with childs + graph_outputs = graph.out_edges(graph_math_func, data=True) + cmdows_outputs = cmdows_math_func.add('outputs') for graph_output in graph_outputs: - cmdows_output = Child(cmdows_outputs, 'output') - Child(cmdows_output, 'parameterUID', graph_output[1]) + cmdows_output = cmdows_outputs.add('output') + cmdows_output.add('parameterUID', graph_output[1]) graph_equations = graph_output[2].get('equations') if graph_equations: - cmdows_equations = Child(cmdows_output, 'equations', uID=graph_math_func+'_equation') + cmdows_equations = cmdows_output.add('equations', uID=graph_math_func+'_equation') for graph_equation_language, graph_equation in graph_equations.iteritems(): - Child(cmdows_equations, 'equation', graph_equation, language=graph_equation_language) + cmdows_equations.add('equation', graph_equation, language=graph_equation_language) return cmdows_math_funcs diff --git a/kadmos/utilities/xml.py b/kadmos/utilities/xml.py index dbdf92037bb2e451b4c90f955cca97beda09dc69..0259ab89c0b73067bf177d2a73ba616841a77a0d 100644 --- a/kadmos/utilities/xml.py +++ b/kadmos/utilities/xml.py @@ -2,6 +2,7 @@ import re import ast import logging +from lxml import etree from lxml.etree import Element, SubElement from general import make_camel_case, unmake_camel_case, string_eval @@ -355,3 +356,94 @@ def merge(a, b): raise Exception('A problematic merge has occured. Please check consistency of the graph.') return a + + +class ExtendedElement(etree.ElementBase): + + def add(self, tag, content='', attrib={}, **extra): + """Method to add a new subelement to an existing element + + :param tag: The subelement tag + :type tag: str + :param content: The subelement text + :type content: str + :param attrib: An optional dictionary containing the subelement attributes + :param extra: Additional subelement attributes given as keyword arguments + :return: An element instance + :rtype: Element + """ + + child = SubElement(self, tag, attrib, **extra) + + if (content is not None and content) or content == 0: + try: + child.text = str(content) + except UnicodeEncodeError: + child.text = str(content.encode('ascii', 'replace')) + + return child + + def add_dict(self, tag, dictionary, attrib={}, **extra): + """Method to add a new subelement to an existing element based on a dictionary + + :param tag: The subelement tag + :type tag: str + :param dictionary: The dictionary representing the subelement contents + :type dictionary: dict + :param attrib: An optional dictionary containing the subelement attributes + :param extra: Additional subelement attributes given as keyword arguments + :return: An element instance + :rtype: Element + """ + + child = SubElement(self, tag, attrib, **extra) + + if type(dictionary) == dict: + for key, value in dictionary.iteritems(): + if type(value) == dict: + child.add_dict(make_camel_case(key), value) + else: + child.add(make_camel_case(key), value) + + return child + + def add_sequence(self, data, data_keys=None, data_dict=None): + """Method extending the previously defined add function such that sequences can easily be created + + :param data: The list of data to be written to the sequence in the form [[key1, value1], [key2, value2]] + :type data: list + :param data_keys: The list of keys to be written to the sequence in the form [key1, key3] + (if not set all data is written) + :type data_keys: list + :param data_dict: A dictionary between data_keys and tags in the form {'key1':'tag1'} + (if no translation is given the data key is converted to camel case notation) + :type data_dict: dict + """ + + # Checks + assert isinstance(data, list) + if data_keys: + assert isinstance(data_keys, list) + if data_dict: + assert isinstance(data_dict, dict) + + # Determine iterator + if data_keys: + dictionary = {key: value for key, value in data} + data = [[key, dictionary.get(key)] for key in data_keys] + + # Add child sequence + for d in data: + try: + tag = data_dict[d[0]] + except (KeyError, TypeError): + tag = make_camel_case(d[0]) + self.add(tag, d[1]) + + return + + +module_level_parser = etree.XMLParser() +module_level_parser.set_element_class_lookup(etree.ElementDefaultClassLookup(element=ExtendedElement)) +# noinspection PyRedeclaration +Element = module_level_parser.makeelement