Source code for pysd.translators.xmile.xmile_section

"""
The Section class allows parsing a model section into Elements. The
final result can be exported to an AbstractSection class in order to
build a model in other language. A section could be either the main model
(without the macros), or a macro definition (not supported yet for Xmile).
"""
from typing import List, Union
from lxml import etree
from pathlib import Path

from ..structures.abstract_model import AbstractSection

from .xmile_element import ControlElement, SubscriptRange, Aux, Flow, Gf, Stock


[docs] class Section(): """ Section object allows parsing the elements of that section. Parameters ---------- name: str Section name. '__main__' for the main section or the macro name. path: pathlib.Path Section path. It should be the model name for main section and the clean macro name for a macro. section_type: str ('main' or 'macro') The section type. params: list List of params that takes the section. In the case of main section it will be an empty list. returns: list List of variables that returns the section. In the case of main section it will be an empty list. content_root: etree._Element Section parsed tree content. namespace: str The namespace of the section given after parsing its content with etree. split: bool If split is True the created section will split the variables depending on the views_dict. views_dict: dict The dictionary of the views. Giving the variables classified at any level in order to split them by files. """ _control_vars = ["initial_time", "final_time", "time_step", "saveper"] def __init__(self, name: str, path: Path, section_type: str, params: List[str], returns: List[str], content_root: etree._Element, namespace: str, split: bool, views_dict: Union[dict, None]): self.name = name self.path = path self.type = section_type self.params = params self.returns = returns self.content = content_root self.ns = {"ns": namespace} self.split = split self.views_dict = views_dict self.elements = None self.behaviors = {} def __str__(self): # pragma: no cover return "\nSection: %s\n" % self.name @property def _verbose(self) -> str: # pragma: no cover """Get section information.""" text = self.__str__() if self.elements: for element in self.elements: text += element._verbose else: text += self.content return text @property def verbose(self): # pragma: no cover """Print section information to standard output.""" print(self._verbose)
[docs] def parse(self, parse_all: bool = True) -> None: """ Parse section object. The subscripts of the section will be added to self subscripts. The variables defined as Flows, Auxiliary, Gf, and Stock will be converted in XmileElements. The control variables, if the section is __main__, will be converted to a ControlElement. Parameters ---------- parse_all: bool (optional) If True then the created VensimElement objects will be automatically parsed. Otherwise, this objects will only be added to self.elements but not parser. Default is True. """ # parse subscripts and components self.subscripts = self._parse_subscripts() self.components = self._parse_components() if self.name == "__main__": # parse control variables self.components += self._parse_control_vars() # Parse behavior section self.behaviors.update(self._parse_behavior()) if parse_all: [component.parse(self.behaviors) for component in self.components] # define elements for printting information self.elements = self.subscripts + self.components
def _parse_behavior(self) -> List[SubscriptRange]: """Parse the behavior the section.""" behaviors = { 'non_negative_stock': False, 'non_negative_flow': False } parsed_bhs = self.content.xpath("ns:behavior", namespaces=self.ns) if not parsed_bhs: return behaviors if parsed_bhs[0].xpath('ns:non_negative', namespaces=self.ns): behaviors['non_negative_stock'] = True behaviors['non_negative_flow'] = True return behaviors bhs_stock = parsed_bhs[0].xpath('ns:stock', namespaces=self.ns) bhs_flow = parsed_bhs[0].xpath('ns:flow', namespaces=self.ns) if bhs_stock and bhs_stock[0].xpath('ns:non_negative', namespaces=self.ns): behaviors['non_negative_stock'] = True if bhs_flow and bhs_flow[0].xpath('ns:non_negative', namespaces=self.ns): behaviors['non_negative_flow'] = True return behaviors def _parse_subscripts(self) -> List[SubscriptRange]: """Parse the subscripts of the section.""" subscripts = [ SubscriptRange( node.attrib["name"], [ sub.attrib["name"] for sub in node.xpath("ns:elem", namespaces=self.ns) ], []) # no subscript mapping implemented for node in self.content.xpath("ns:dimensions/ns:dim", namespaces=self.ns) ] self.subscripts_dict = { subr.name: subr.definition for subr in subscripts} return subscripts def _parse_components(self) -> List[Union[Flow, Aux, Gf, Stock]]: """ Parse model components. Four groups defined: Aux: auxiliary variables Flow: flows Gf: lookups Stock: integs """ # Add auxiliary variables components = [ Aux(node, self.ns, self.subscripts_dict) for node in self.content.xpath( "ns:model/ns:variables/ns:aux", namespaces=self.ns) if node.attrib["name"].lower().replace(" ", "_") not in self._control_vars] # Add flows components += [ Flow(node, self.ns, self.subscripts_dict) for node in self.content.xpath( "ns:model/ns:variables/ns:flow", namespaces=self.ns) if node.attrib["name"].lower().replace(" ", "_") not in self._control_vars] # Add lookups components += [ Gf(node, self.ns, self.subscripts_dict) for node in self.content.xpath( "ns:model/ns:variables/ns:gf", namespaces=self.ns) ] # Add stocks components += [ Stock(node, self.ns, self.subscripts_dict) for node in self.content.xpath( "ns:model/ns:variables/ns:stock", namespaces=self.ns) ] return components def _parse_control_vars(self) -> List[ControlElement]: """Parse control vars and rename them with Vensim standard.""" # Read the start time of simulation node = self.content.xpath('ns:sim_specs', namespaces=self.ns)[0] time_units = node.attrib['time_units']\ if 'time_units' in node.attrib else "" control_vars = [] # initial time of the simulation control_vars.append(ControlElement( name="INITIAL TIME", units=time_units, documentation="The initial time for the simulation.", eqn=node.xpath("ns:start", namespaces=self.ns)[0].text )) # final time of the simulation control_vars.append(ControlElement( name="FINAL TIME", units=time_units, documentation="The final time for the simulation.", eqn=node.xpath("ns:stop", namespaces=self.ns)[0].text )) # time step of simulation dt_node = node.xpath("ns:dt", namespaces=self.ns)[0] dt_eqn = "1/(" + dt_node.text + ")" if "reciprocal" in dt_node.attrib\ and dt_node.attrib["reciprocal"].lower() == "true"\ else dt_node.text control_vars.append(ControlElement( name="TIME STEP", units=time_units, documentation="The time step for the simulation.", eqn=dt_eqn )) # saving time of the simulation = time step control_vars.append(ControlElement( name="SAVEPER", units=time_units, documentation="The save time step for the simulation.", eqn="time_step" )) return control_vars
[docs] def get_abstract_section(self) -> AbstractSection: """ Get Abstract Section used for building. This, method should be called after parsing the section (self.parse). This method is automatically called by Model's get_abstract_model and automatically generates the AbstractSubscript ranges and merge the components in elements calling also the get_abstract_components method from each model component. Returns ------- AbstractSection: AbstractSection Abstract Section object that can be used for building the model in another language. """ return AbstractSection( name=self.name, path=self.path, type=self.type, params=self.params, returns=self.returns, subscripts=[ subs_range.get_abstract_subscript_range() for subs_range in self.subscripts ], elements=[ element.get_abstract_element() for element in self.components ], constraints=[], test_inputs=[], split=self.split, views_dict=self.views_dict )