Example #1
0
    def build(self):
        """
        Build the simulation components from the model.

        @return: A runnable simulation object
        @rtype: lems.sim.sim.Simulation
        """

        self.sim = Simulation()

        for component_id in self.model.default_runs:
            if component_id not in self.model.context.components:
                raise SimBuildError('Unable to find component \'{0}\' to run'\
                                    .format(component_id))
            component = self.model.context.components[component_id]

            runnable = self.build_runnable(component)
            self.sim.add_runnable(component.id, runnable)

        return self.sim
Example #2
0
    def build(self):
        """
        Build the simulation components from the model.

        @return: A runnable simulation object
        @rtype: lems.sim.sim.Simulation
        """

        self.sim = Simulation()

        for component_id in self.model.targets:
            if component_id not in self.model.components:
                raise SimBuildError("Unable to find target component '{0}'",
                                    component_id)
            component = self.model.fat_components[component_id]

            runnable = self.build_runnable(component)
            self.sim.add_runnable(runnable)

        return self.sim 
Example #3
0
    def build(self):
        """
        Build the simulation components from the model.

        @return: A runnable simulation object
        @rtype: lems.sim.sim.Simulation
        """

        self.sim = Simulation()

        for component_id in self.model.default_runs:
            if component_id not in self.model.context.components:
                raise SimBuildError("Unable to find component '{0}' to run".format(component_id))
            component = self.model.context.components[component_id]

            runnable = self.build_runnable(component)
            self.sim.add_runnable(component.id, runnable)

        return self.sim
Example #4
0
    def build(self):
        """
        Build the simulation components from the model.

        @return: A runnable simulation object
        @rtype: lems.sim.sim.Simulation
        """

        self.sim = Simulation()

        for component_id in self.model.targets:
            if component_id not in self.model.components:
                raise SimBuildError("Unable to find target component '{0}'",
                                    component_id)
            component = self.model.fat_components[component_id]

            runnable = self.build_runnable(component)
            self.sim.add_runnable(runnable)

        return self.sim 
Example #5
0
class SimulationBuilder(LEMSBase):
    """
    Simulation builder class.
    """
    debug = False

    def __init__(self, model):
        """
        Constructor.

        @param model: Model upon which the simulation is to be generated.
        @type model: lems.model.model.Model
        """

        self.model = model
        """ Model to be used for constructing the simulation.
        @type: lems.model.model.Model """

        self.sim = None
        """ Simulation built from the model.
        @type: lems.sim.sim.Simulation """

        self.current_record_target = None

        self.current_data_output = None


    def build(self):
        """
        Build the simulation components from the model.

        @return: A runnable simulation object
        @rtype: lems.sim.sim.Simulation
        """

        self.sim = Simulation()

        for component_id in self.model.targets:
            if component_id not in self.model.components:
                raise SimBuildError("Unable to find target component '{0}'",
                                    component_id)
            component = self.model.fat_components[component_id]

            runnable = self.build_runnable(component)
            self.sim.add_runnable(runnable)

        return self.sim 

    def build_runnable(self, component, parent = None, id_ = None):
        """
        Build a runnable component from a component specification and add
        it to the simulation.

        @param component: Component specification
        @type component: lems.model.component.FatComponent

        @param parent: Parent runnable component.
        @type parent: lems.sim.runnable.Runnable

        @param id_: Optional id for therunnable. If it's not passed in,
        the runnable will inherit the id of the component.

        @raise SimBuildError: Raised when a component reference cannot be
        resolved.
        """
        if self.debug: print("++++++++ Calling build_runnable of %s with parent %s"%(component, parent))

        if id_ == None:
            runnable = Runnable(component.id, component, parent)
        else:
            runnable = Runnable(id_, component, parent)

        simulation = component.simulation

        record_target_backup = self.current_record_target
        data_output_backup = self.current_data_output

        do = None
        for d in simulation.data_displays:
            do = d
        if do == None:
            for d in simulation.data_writers:
                do = d

        if do != None:
            self.current_data_output = do

        for parameter in component.parameters:
            runnable.add_instance_variable(parameter.name, parameter.numeric_value)

        derived_parameter_code = []
        
        derived_parameter_ordering = order_derived_parameters(component)
        
        for dpn in derived_parameter_ordering:
            derived_parameter = component.derived_parameters[dpn]
            runnable.add_derived_variable(derived_parameter.name)
            
            expression = self.build_expression_from_tree(runnable,
                                                        None,
                                                        derived_parameter.expression_tree)
            
            derived_parameter_code += ['self.{0} = ({1})'.format(
                        derived_parameter.name,
                        expression)]
            derived_parameter_code += ['self.{0}_shadow = ({1})'.format(
                        derived_parameter.name,
                        expression)]
       
        suffix = ''
        runnable.add_method('update_derived_parameters' + suffix, ['self'],
                            derived_parameter_code)
            
        for constant in component.constants:
            runnable.add_instance_variable(constant.name, constant.numeric_value)

        for text in component.texts:
            runnable.add_text_variable(text.name, text.value)
            
        for link in component.links:
            runnable.add_text_variable(link.name, link.value)

        for ep in component.event_ports:
            if ep.direction.lower() == 'in':
                runnable.add_event_in_port(ep.name)
            else:
                runnable.add_event_out_port(ep.name)

                
        dynamics = component.dynamics
        self.add_dynamics_1(component, runnable, dynamics, dynamics)

        for regime in dynamics.regimes:
            self.add_dynamics_1(component, runnable, regime, dynamics)
            
            if regime.initial:
                runnable.current_regime = regime.name

            rn = regime.name
            if rn not in runnable.regimes:
                runnable.add_regime(RunnableRegime(rn))
            r = runnable.regimes[rn]
            suffix = '_regime_' + rn

            if runnable.__dict__.has_key('update_state_variables' + suffix): 
                  r.update_state_variables = runnable.__dict__['update_state_variables' + suffix]
                  
            if runnable.__dict__.has_key('update_derived_variables' + suffix): 
                r.update_derived_variables = runnable.__dict__['update_derived_variables' + suffix]
            
            if runnable.__dict__.has_key('run_startup_event_handlers' + suffix): 
                r.run_startup_event_handlers = runnable.__dict__['run_startup_event_handlers' + suffix]
            
            if runnable.__dict__.has_key('run_preprocessing_event_handlers' + suffix): 
                r.run_preprocessing_event_handlers = runnable.__dict__['run_preprocessing_event_handlers' + suffix]
            
            if runnable.__dict__.has_key('run_postprocessing_event_handlers' + suffix): 
                r.run_postprocessing_event_handlers = runnable.__dict__['run_postprocessing_event_handlers' + suffix]

        self.process_simulation_specs(component, runnable, component.simulation)

        for child in component.child_components:
            child_runnable = self.build_runnable(child, runnable)
            runnable.add_child(child.id, child_runnable)

            for children in component.children:
                #GG - These conditions need more debugging.
                if children.type in child.types:
                    runnable.add_child_typeref(children.type, child_runnable)
                if children.multiple:
                    if children.type in child.types:
                        runnable.add_child_to_group(children.name, child_runnable)
                else:
                    if child_runnable.id == children.name:
                        runnable.add_child_typeref(children.name, child_runnable)

        for attachment in component.attachments:
            runnable.make_attachment(attachment.type, attachment.name)

        self.build_structure(component, runnable, component.structure)

        dynamics = component.dynamics
        self.add_dynamics_2(component, runnable,
                            dynamics, dynamics)
        for regime in dynamics.regimes:
            self.add_dynamics_2(component, runnable, regime, dynamics)

            if regime.name not in runnable.regimes:
                runnable.add_regime(RunnableRegime(regime.name))
            r = runnable.regimes[regime.name]
            suffix = '_regime_' + regime.name

            if runnable.__dict__.has_key('update_kinetic_scheme' + suffix): 
                r.update_kinetic_scheme = runnable.__dict__['update_kinetic_scheme' + suffix]

        self.add_recording_behavior(component, runnable)

        self.current_data_output = data_output_backup
        self.current_record_target = record_target_backup

        return runnable
    
    
    def build_event_connections(self, component, runnable, structure):
        """
        Adds event connections to a runnable component based on the structure
        specifications in the component model.

        @param component: Component model containing structure specifications.
        @type component: lems.model.component.FatComponent

        @param runnable: Runnable component to which structure is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param structure: The structure object to be used to add
        structure code in the runnable component.
        @type structure: lems.model.structure.Structure
        """
        if self.debug: print("\n++++++++ Calling build_event_connections of %s with runnable %s, parent %s"%(component.id, runnable.id, runnable.parent))
        # Process event connections
        for ec in structure.event_connections:
            if self.debug: print(ec.toxml())
            source = runnable.parent.resolve_path(ec.from_)
            target = runnable.parent.resolve_path(ec.to)
            if ec.receiver:
                receiver_template = self.build_runnable(ec.receiver,
                                                            target)
                                                            
                #receiver = copy.deepcopy(receiver_template)
                receiver = receiver_template.copy()
                receiver.id = "{0}__{1}__".format(component.id,
                                                  receiver_template.id)

                if ec.receiver_container:
                    target.add_attachment(receiver, ec.receiver_container)
                target.add_child(receiver_template.id, receiver)
                target = receiver
            else:
                source = runnable.resolve_path(ec.from_)
                target = runnable.resolve_path(ec.to)

            source_port = ec.source_port
            target_port = ec.target_port

            if not source_port:
                if len(source.event_out_ports) == 1:
                    source_port = source.event_out_ports[0]
                else:
                    raise SimBuildError(("No source event port "
                                         "uniquely identifiable"
                                         " in '{0}'").format(source.id))
            if not target_port:
                if len(target.event_in_ports) == 1:
                    target_port = target.event_in_ports[0]
                else:
                    raise SimBuildError(("No destination event port "
                                         "uniquely identifiable "
                                         "in '{0}'").format(target))
             
            if self.debug: print("register_event_out_callback\n   Source: %s, %s (port: %s) \n   -> %s, %s (port: %s)"%(source, id(source), source_port, target, id(target), target_port))
            source.register_event_out_callback(\
                source_port, lambda: target.inc_event_in(target_port))
                
            

    def build_structure(self, component, runnable, structure):
        """
        Adds structure to a runnable component based on the structure
        specifications in the component model.

        @param component: Component model containing structure specifications.
        @type component: lems.model.component.FatComponent

        @param runnable: Runnable component to which structure is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param structure: The structure object to be used to add
        structure code in the runnable component.
        @type structure: lems.model.structure.Structure
        """
        if self.debug: print("\n++++++++ Calling build_structure of %s with runnable %s, parent %s"%(component.id, runnable.id, runnable.parent))
        
        # Process single-child instantiations
        for ch in structure.child_instances:
            child_runnable = self.build_runnable(ch.referenced_component, runnable)
            runnable.add_child(child_runnable.id, child_runnable)

            runnable.add_child_typeref(ch.component, child_runnable)
            
        # Process multi-child instatiantions
        for mi in structure.multi_instantiates:
            template = self.build_runnable(mi.component,
                                           runnable)

            for i in range(mi.number):
                #instance = copy.deepcopy(template)
                instance = template.copy()
                instance.id = "{0}__{1}__{2}".format(component.id,
                                                     template.id,
                                                     i)
                runnable.array.append(instance)

        # Process foreach statements
        for fe in structure.for_eachs:
            self.build_foreach(component, runnable, fe)
        
        self.build_event_connections(component, runnable, structure)



    def build_foreach(self, component, runnable, foreach, name_mappings = {}):
        """
        Iterate over ForEach constructs and process nested elements.

        @param component: Component model containing structure specifications.
        @type component: lems.model.component.FatComponent

        @param runnable: Runnable component to which structure is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param foreach: The ForEach structure object to be used to add
        structure code in the runnable component.
        @type foreach: lems.model.structure.ForEach
        """
        if self.debug: print("\n++++++++ Calling build_foreach of %s with runnable %s, parent %s, name_mappings: %s"%(component.id, runnable.id, runnable.parent, name_mappings))

        target_array = runnable.resolve_path(foreach.instances)
        
        for target_runnable in target_array:
            if self.debug: print("Applying contents of for_each to %s, as %s"%(target_runnable.id, foreach.as_))
            name_mappings[foreach.as_] = target_runnable

            # Process foreach statements
            for fe2 in foreach.for_eachs:
                #print fe2.toxml()
                target_array2 = runnable.resolve_path(fe2.instances)

                for target_runnable2 in target_array2:
                    name_mappings[fe2.as_] = target_runnable2
                    self.build_foreach(component, runnable, fe2, name_mappings)

            # Process event connections
            for ec in foreach.event_connections:
                source = name_mappings[ec.from_]
                target = name_mappings[ec.to]

                source_port = ec.source_port
                target_port = ec.target_port

                if not source_port:
                    if len(source.event_out_ports) == 1:
                        source_port = source.event_out_ports[0]
                    else:
                        raise SimBuildError(("No source event port "
                                             "uniquely identifiable"
                                             " in '{0}'").format(source.id))
                if not target_port:
                    if len(target.event_in_ports) == 1:
                        target_port = target.event_in_ports[0]
                    else:
                        raise SimBuildError(("No destination event port "
                                             "uniquely identifiable "
                                             "in '{0}'").format(target))

                if self.debug: print("register_event_out_callback\n   Source: %s, %s (port: %s) \n   -> %s, %s (port: %s)"%(source, id(source), source_port, target, id(target), target_port))
                source.register_event_out_callback(\
                    source_port, lambda: target.inc_event_in(target_port))
                

    def add_dynamics_1(self, component, runnable, regime, dynamics):
        """
        Adds dynamics to a runnable component based on the dynamics
        specifications in the component model.

        This method builds dynamics necessary for building child components.

        @param component: Component model containing dynamics specifications.
        @type component: lems.model.component.FatComponent

        @param runnable: Runnable component to which dynamics is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param regime: The dynamics regime to be used to generate
        dynamics code in the runnable component.
        @type regime: lems.model.dynamics.Regime

        @param dynamics: Shared dynamics specifications.
        @type dynamics: lems.model.dynamics.Regime

        @raise SimBuildError: Raised when a time derivative expression refers
        to an undefined variable.

        @raise SimBuildError: Raised when there are invalid time
        specifications for the <Run> statement.

        @raise SimBuildError: Raised when the component reference for <Run>
        cannot be resolved.
        """

        if isinstance(regime, Dynamics) or regime.name == '':
            suffix = ''
        else:
            suffix = '_regime_' + regime.name

        if isinstance(regime, Regime) and regime.initial:
            runnable.new_regime = regime.name

        # Process state variables
        for sv in regime.state_variables:
            runnable.add_instance_variable(sv.name, 0)

        # Process time derivatives
        time_step_code = []
        for td in regime.time_derivatives:
            if td.variable not in regime.state_variables and td.variable not in dynamics.state_variables:
                raise SimBuildError(('Time derivative for undefined state '
                                     'variable {0} in component {1}').format(td.variable, component.id))

            exp = self.build_expression_from_tree(runnable,
                                                  regime,
                                                  td.expression_tree)
            time_step_code += ['self.{0} += dt * ({1})'.format(td.variable,
                                                               exp)]
        runnable.add_method('update_state_variables' + suffix, ['self', 'dt'],
                            time_step_code)

        # Process derived variables
        derived_variable_code = []
        derived_variables_ordering = order_derived_variables(regime)
        for dvn in derived_variables_ordering: #regime.derived_variables:
            if dvn in dynamics.derived_variables:
                dv = dynamics.derived_variables[dvn]
                runnable.add_derived_variable(dv.name)
                if dv.value:
                    derived_variable_code += ['self.{0} = ({1})'.format(
                        dv.name,
                        self.build_expression_from_tree(runnable,
                                                        regime,
                                                        dv.expression_tree))]
                elif dv.select:
                    if dv.reduce:
                        derived_variable_code += self.build_reduce_code(dv.name,
                                                                        dv.select,
                                                                        dv.reduce)
                    else:
                        derived_variable_code += ['self.{0} = (self.{1})'.format(
                            dv.name,
                            dv.select.replace('/', '.'))]
                else:
                    raise SimBuildError(('Inconsistent derived variable settings'
                                         'for {0}').format(dvn))
            elif dvn in dynamics.conditional_derived_variables:
                dv = dynamics.conditional_derived_variables[dvn]
                runnable.add_derived_variable(dv.name)
                derived_variable_code += self.build_conditional_derived_var_code(runnable,
                                                                                 regime,
                                                                                 dv)
            else:
                raise SimBuildError("Unknown derived variable '{0}' in '{1}'",
                                     dvn, runnable.id)
        runnable.add_method('update_derived_variables' + suffix, ['self'],
                            derived_variable_code)

        # Process event handlers
        pre_event_handler_code = []
        post_event_handler_code = []
        startup_event_handler_code = []
        on_entry_added = False
        for eh in regime.event_handlers:
            if isinstance(eh, OnStart):
                startup_event_handler_code += self.build_event_handler(runnable,
                                                                       regime,
                                                                       eh)
            elif isinstance(eh, OnCondition):
                post_event_handler_code += self.build_event_handler(runnable,
                                                                    regime,
                                                                    eh)
            else:
                if isinstance(eh, OnEntry):
                    on_entry_added = True
                pre_event_handler_code += self.build_event_handler(runnable,
                                                                   regime,
                                                                   eh)
        if isinstance(regime, Regime) and not on_entry_added:
            pre_event_handler_code += self.build_event_handler(runnable, regime, OnEntry())
            
        runnable.add_method('run_startup_event_handlers' + suffix, ['self'],
                            startup_event_handler_code)
        runnable.add_method('run_preprocessing_event_handlers' + suffix, ['self'],
                            pre_event_handler_code)
        runnable.add_method('run_postprocessing_event_handlers' + suffix, ['self'],
                            post_event_handler_code)

    def add_dynamics_2(self, component, runnable, regime, dynamics):
        """
        Adds dynamics to a runnable component based on the dynamics
        specifications in the component model.

        This method builds dynamics dependent on child components.

        @param component: Component model containing dynamics specifications.
        @type component: lems.model.component.FatComponent

        @param runnable: Runnable component to which dynamics is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param regime: The dynamics regime to be used to generate
        dynamics code in the runnable component.
        @type regime: lems.model.dynamics.Regime

        @param dynamics: Shared dynamics specifications.
        @type dynamics: lems.model.dynamics.Regime

        @raise SimBuildError: Raised when a time derivative expression refers
        to an undefined variable.

        @raise SimBuildError: Raised when there are invalid time
        specifications for the <Run> statement.

        @raise SimBuildError: Raised when the component reference for <Run>
        cannot be resolved.
        """

        if isinstance(regime, Dynamics) or regime.name == '':
            suffix = ''
        else:
            suffix = '_regime_' + regime.name

        # Process kinetic schemes
        ks_code = []
        for ks in regime.kinetic_schemes:
            
            raise NotImplementedError("KineticScheme element is not stable in PyLEMS yet, see https://github.com/LEMS/pylems/issues/15")
        
            try:
                ###nodes = {node.id:node for node in runnable.__dict__[ks.nodes]}
                nodes = {}
                for node in runnable.__dict__[ks.nodes]:
                    nodes[node.id] = node
                edges = runnable.__dict__[ks.edges]

                for edge in edges:
                    from_ = edge.__dict__[ks.edge_source]
                    to = edge.__dict__[ks.edge_target]

                    ks_code += [('self.{0}.{2} += dt * (-self.{3}.{4} * self.{0}.{2}_shadow'
                                 ' + self.{3}.{5} * self.{1}.{2}_shadow)').format(
                        from_, to, ks.state_variable, edge.id,
                        ks.forward_rate, ks.reverse_rate)]

                    ks_code += [('self.{1}.{2} += dt * (self.{3}.{4} * self.{0}.{2}_shadow'
                                 ' - self.{3}.{5} * self.{1}.{2}_shadow)').format(
                        from_, to, ks.state_variable, edge.id,
                        ks.forward_rate, ks.reverse_rate)]

                ks_code += ['sum = 0']
                for node in nodes:
                    nodes[node].__dict__[ks.state_variable] = 1.0 / len(nodes)
                    nodes[node].__dict__[ks.state_variable + '_shadow'] = 1.0 / len(nodes)
                    ks_code += ['sum += self.{0}.{1}'.format(node, ks.state_variable)]

                for node in nodes:
                    ks_code += ['self.{0}.{1} /= sum'.format(node, ks.state_variable)]

                for node in nodes:
                    ks_code += [('self.{0}.{1}_shadow = '
                                 'self.{0}.{1}').format(node,
                                                        ks.state_variable)]

            except Exception as e:
                raise SimBuildError(("Unable to construct kinetic scheme '{0}' "
                                     "for component '{1}' - {2}").format(ks.name,
                                                                         component.id,
                                                                         str(e)))

        runnable.add_method('update_kinetic_scheme' + suffix, ['self', 'dt'],
                            ks_code)

    def process_simulation_specs(self, component, runnable, simulation):
        """
        Process simulation-related aspects to a runnable component based on the
        dynamics specifications in the component model.

        @param component: Component model containing dynamics specifications.
        @type component: lems.model.component.FatComponent

        @param runnable: Runnable component to which dynamics is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param simulation: The simulation-related aspects to be implemented
        in the runnable component.
        @type simulation: lems.model.simulation.Simulation

        @raise SimBuildError: Raised when a time derivative expression refers
        to an undefined variable

        @raise SimBuildError: Raised when there are invalid time
        specifications for the <Run> statement.

        @raise SimBuildError: Raised when the component reference for <Run>
        cannot be resolved.
        """

        # Process runs
        for run in simulation.runs:
            cid = run.component.id + '_' + component.id

            target = self.build_runnable(run.component, runnable, cid)
            self.sim.add_runnable(target)
            self.current_record_target = target

            target.configure_time(run.increment,
                                  run.total)


    def convert_op(self, op):
        """
        Converts NeuroML arithmetic/logical operators to python equivalents.

        @param op: NeuroML operator
        @type op: string


        @return: Python operator
        @rtype: string
        """

        if op == '.gt.':
            return '>'
        elif op == '.ge.' or op == '.geq.':
            return '>='
        elif op == '.lt.':
            return '<'
        elif op == '.le.':
            return '<='
        elif op == '.eq.':
            return '=='
        elif op == '.neq.':   
            return '!='
        elif op == '.ne.':   # .neq. is preferred!
            return '!='
        elif op == '^':
            return '**'
        elif op == '.and.':
            return 'and'
        elif op == '.or.':
            return 'or'
        else:
            return op

    def convert_func(self, func):
        """
        Converts NeuroML arithmetic/logical functions to python equivalents.

        @param func: NeuroML function
        @type func: string


        @return: Python operator
        @rtype: string
        """

        if func == 'ln':
            return 'log'
        if func == 'random':
            return 'random.uniform'
        else:
            return func

    def build_expression_from_tree(self, runnable, regime, tree_node):
        """
        Recursively builds a Python expression from a parsed expression tree.

        @param runnable: Runnable object to which this expression would be added.
        @type runnable: lems.sim.runnable.Runnable

        @param regime: Dynamics regime being built.
        @type regime: lems.model.dynamics.Regime

        @param tree_node: Root node for the tree from which the expression
        is to be built.
        @type tree_node: lems.parser.expr.ExprNode

        @return: Generated Python expression.
        @rtype: string
        """

        component_type = self.model.component_types[runnable.component.type]
        dynamics = component_type.dynamics
        
        if tree_node.type == ExprNode.VALUE:
            if tree_node.value[0].isalpha():
                if tree_node.value == 't':
                    return 'self.time_completed'
                elif tree_node.value in component_type.requirements:
                    var_prefix = 'self'
                    v = tree_node.value

                    r = runnable

                    while (v not in r.instance_variables and
                           v not in r.derived_variables):
                        var_prefix = '{0}.{1}'.format(var_prefix, 'parent')
                        r = r.parent
                        if r == None:
                            raise SimBuildError("Unable to resolve required "
                                                "variable '{0}'".format(v))

                    return '{0}.{1}'.format(var_prefix, v)
                elif (tree_node.value in dynamics.derived_variables or (regime is not None and tree_node.value in regime.derived_variables)):
                    return 'self.{0}'.format(tree_node.value)
                else:
                    return 'self.{0}_shadow'.format(tree_node.value)
            else:
                return tree_node.value
        elif tree_node.type == ExprNode.FUNC1:
            pattern = '({0}({1}))'
            func = self.convert_func(tree_node.func)
            if 'random.uniform' in func:
                pattern = '({0}(0,{1}))'
            return pattern.format(\
                func,
                self.build_expression_from_tree(runnable,
                                                regime,
                                                tree_node.param))
        else:
            return '({0}) {1} ({2})'.format(\
                self.build_expression_from_tree(runnable,
                                                regime,
                                                tree_node.left),
                self.convert_op(tree_node.op),
                self.build_expression_from_tree(runnable,
                                                regime,
                                                tree_node.right))

    def build_event_handler(self, runnable, regime, event_handler):
        """
        Build event handler code.

        @param event_handler: Event handler object
        @type event_handler: lems.model.dynamics.EventHandler

        @return: Generated event handler code.
        @rtype: list(string)
        """

        if isinstance(event_handler, OnCondition):
            return self.build_on_condition(runnable, regime, event_handler)
        elif isinstance(event_handler, OnEvent):
            return self.build_on_event(runnable, regime, event_handler)
        elif isinstance(event_handler, OnStart):
            return self.build_on_start(runnable, regime, event_handler)
        elif isinstance(event_handler, OnEntry):
            return self.build_on_entry(runnable, regime, event_handler)
        else:
            return []

    def build_on_condition(self, runnable, regime, on_condition):
        """
        Build OnCondition event handler code.

        @param on_condition: OnCondition event handler object
        @type on_condition: lems.model.dynamics.OnCondition

        @return: Generated OnCondition code
        @rtype: list(string)
        """

        on_condition_code = []

        on_condition_code += ['if {0}:'.format(\
            self.build_expression_from_tree(runnable,
                                            regime,
                                            on_condition.expression_tree))]

        for action in on_condition.actions:
            code = self.build_action(runnable, regime, action)
            for line in code:
                on_condition_code += ['    ' + line]

        return on_condition_code

    def build_on_event(self, runnable, regime, on_event):
        """
        Build OnEvent event handler code.

        @param on_event: OnEvent event handler object
        @type on_event: lems.model.dynamics.OnEvent

        @return: Generated OnEvent code
        @rtype: list(string)
        """
        on_event_code = []

        if self.debug: on_event_code += ['print("Maybe handling something for %s ("+str(id(self))+")")'%(runnable.id),
                          'print("EICs ("+str(id(self))+"): "+str(self.event_in_counters))']
                          
        on_event_code += ['count = self.event_in_counters[\'{0}\']'.\
                          format(on_event.port),
                          'while count > 0:',
                          '    print("  Handling event")' if self.debug else '',
                          '    count -= 1']
        for action in on_event.actions:
            code = self.build_action(runnable, regime, action)
            for line in code:
                on_event_code += ['    ' + line]

        on_event_code += ['self.event_in_counters[\'{0}\'] = 0'.\
                          format(on_event.port),]

        return on_event_code

    def build_on_start(self, runnable, regime, on_start):
        """
        Build OnStart start handler code.

        @param on_start: OnStart start handler object
        @type on_start: lems.model.dynamics.OnStart

        @return: Generated OnStart code
        @rtype: list(string)
        """

        on_start_code = []

        for action in on_start.actions:
            code = self.build_action(runnable, regime, action)
            for line in code:
                on_start_code += [line]

        return on_start_code

    def build_on_entry(self, runnable, regime, on_entry):
        """
        Build OnEntry start handler code.

        @param on_entry: OnEntry start handler object
        @type on_entry: lems.model.dynamics.OnEntry

        @return: Generated OnEntry code
        @rtype: list(string)
        """

        on_entry_code = []

        on_entry_code += ['if self.current_regime != self.last_regime:']
        on_entry_code += ['    self.last_regime = self.current_regime']

        for action in on_entry.actions:
            code = self.build_action(runnable, regime, action)
            for line in code:
                on_entry_code += ['    ' + line]

        return on_entry_code

    def build_action(self, runnable, regime, action):
        """
        Build event handler action code.

        @param action: Event handler action object
        @type action: lems.model.dynamics.Action

        @return: Generated action code
        @rtype: string
        """

        if isinstance(action, StateAssignment):
            return self.build_state_assignment(runnable, regime, action)
        if isinstance(action, EventOut):
            return self.build_event_out(action)
        if isinstance(action, Transition):
            return self.build_transition(action)
        else:
            return ['pass']

    def build_state_assignment(self, runnable, regime, state_assignment):
        """
        Build state assignment code.

        @param state_assignment: State assignment object
        @type state_assignment: lems.model.dynamics.StateAssignment

        @return: Generated state assignment code
        @rtype: string
        """

        return ['self.{0} = {1}'.format(\
            state_assignment.variable,
            self.build_expression_from_tree(runnable,
                                            regime,
                                            state_assignment.expression_tree))]

    def build_event_out(self, event_out):
        """
        Build event out code.

        @param event_out: event out object
        @type event_out: lems.model.dynamics.EventOut

        @return: Generated event out code
        @rtype: string
        """

        event_out_code = ['if "{0}" in self.event_out_callbacks:'.format(event_out.port),
                          '    for c in self.event_out_callbacks[\'{0}\']:'.format(event_out.port),
                          '        c()']

        return event_out_code

    def build_transition(self, transition):
        """
        Build regime transition code.

        @param transition: Transition object
        @type transition: lems.model.dynamics.Transition

        @return: Generated transition code
        @rtype: string
        """

        return ["self.new_regime = '{0}'".format(transition.regime)]

    def build_reduce_code(self, result, select, reduce):
        """
        Builds a reduce operation on the selected target range.
        """

        select = select.replace('/', '.')
        select = select.replace(' ', '')
        if reduce == 'add':
            reduce_op = '+'
            acc_start = 0
        else:
            reduce_op = '*'
            acc_start = 1

        #bits = select.split('[*]')
        bits = re.split('\[.*\]', select)
        seps = re.findall('\[.*\]', select)

        code = ['self.{0} = {1}'.format(result, acc_start)]
        code += ['self.{0}_shadow = {1}'.format(result, acc_start)]

        code += ['try:']

        if len(bits) == 1:
            target = select
            code += ['    self.{0} = self.{1}'.format(result, target)]
            code += ['    self.{0}_shadow = self.{1}'.format(result, target)]
        elif len(bits) == 2:
            sep = seps[0][1:-1]

            if sep == '*':
                array = bits[0]
                ref = bits[1]

                code += ['    acc = {0}'.format(acc_start)]
                code += ['    for o in self.{0}:'.format(array)]
                code += ['        acc = acc {0} o{1}'.format(reduce_op, ref)]
                code += ['    self.{0} = acc'.format(result)]
                code += ['    self.{0}_shadow = acc'.format(result)]
            else:
                bits2 = sep.split('=')
                if len(bits2) > 1:
                    array = bits[0]
                    ref = bits[1]

                    code += ['    acc = {0}'.format(acc_start)]
                    code += ['    for o in self.{0}:'.format(array)]
                    code += ['        if o.{0} == {1}:'.format(bits2[0], bits2[1])]
                    code += ['            acc = acc {0} o{1}'.format(reduce_op, ref)]
                    code += ['    self.{0} = acc'.format(result)]
                    code += ['    self.{0}_shadow = acc'.format(result)]
                else:
                    raise SimbuildError("Invalid reduce target - '{0}'".format(select))
        else:
            raise SimbuildError("Invalid reduce target - '{0}'".format(select))

        code += ['except:']
        code += ['    pass']

        return code

    def build_conditional_derived_var_code(self, runnable, regime, dv):
        code = []
        el = ''
        for case in dv.cases:
            if case.condition_expression_tree:
                code += [el+'if {0}:'.format(self.build_expression_from_tree(runnable, 
                                                                          regime, 
                                                                          case.condition_expression_tree))]
                el='el'
                code += ['    self.{0} = {1}'.format(dv.name, self.build_expression_from_tree(runnable, 
                                                                                              regime, 
                                                                                              case.value_expression_tree))]
                                                                                              
        for case in dv.cases:
            if case.condition_expression_tree is None:
                code += ['else: ']
                code += ['    self.{0} = {1}'.format(dv.name, self.build_expression_from_tree(runnable, 
                                                                                              regime, 
                                                                                              case.value_expression_tree))]
        return code

    def add_recording_behavior(self, component, runnable):
        """
        Adds recording-related dynamics to a runnable component based on
        the dynamics specifications in the component model.

        @param component: Component model containing dynamics specifications.
        @type component: lems.model.component.FatComponent runnable: Runnable component to which dynamics is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @raise SimBuildError: Raised when a target for recording could not be
        found.
        """

        simulation = component.simulation

        for rec in simulation.records:
            rec.id = runnable.id
            self.current_record_target.add_variable_recorder(self.current_data_output, rec)
Example #6
0
class SimulationBuilder(LEMSBase):
    """
    Simulation builder class.
    """
    debug = False

    def __init__(self, model):
        """
        Constructor.

        @param model: Model upon which the simulation is to be generated.
        @type model: lems.model.model.Model
        """

        self.model = model
        """ Model to be used for constructing the simulation.
        @type: lems.model.model.Model """

        self.sim = None
        """ Simulation built from the model.
        @type: lems.sim.sim.Simulation """

        self.current_record_target = None

        self.current_data_output = None


    def build(self):
        """
        Build the simulation components from the model.

        @return: A runnable simulation object
        @rtype: lems.sim.sim.Simulation
        """

        self.sim = Simulation()

        for component_id in self.model.targets:
            if component_id not in self.model.components:
                raise SimBuildError("Unable to find target component '{0}'",
                                    component_id)
            component = self.model.fat_components[component_id]

            runnable = self.build_runnable(component)
            self.sim.add_runnable(runnable)

        return self.sim 

    def build_runnable(self, component, parent = None, id_ = None):
        """
        Build a runnable component from a component specification and add
        it to the simulation.

        @param component: Component specification
        @type component: lems.model.component.FatComponent

        @param parent: Parent runnable component.
        @type parent: lems.sim.runnable.Runnable

        @param id_: Optional id for therunnable. If it's not passed in,
        the runnable will inherit the id of the component.

        @raise SimBuildError: Raised when a component reference cannot be
        resolved.
        """
        if self.debug: print("++++++++ Calling build_runnable of %s with parent %s"%(component, parent))

        if id_ == None:
            runnable = Runnable(component.id, component, parent)
        else:
            runnable = Runnable(id_, component, parent)

        simulation = component.simulation

        record_target_backup = self.current_record_target
        data_output_backup = self.current_data_output

        do = None
        for d in simulation.data_displays:
            do = d
        if do == None:
            for d in simulation.data_writers:
                do = d

        if do != None:
            self.current_data_output = do

        for parameter in component.parameters:
            runnable.add_instance_variable(parameter.name, parameter.numeric_value)
            
        
        for property in component.properties:
            print("\n\n*****************************************************************\n\n"+
                  "   Property element is not stable in PyLEMS yet, see https://github.com/LEMS/pylems/issues/16\n\n"+
                  "   Used in: %s\n\n"%property.toxml()+
                  "*****************************************************************\n\n\n")
            runnable.add_instance_variable(property.name, property.default_value)

        derived_parameter_code = []
        
        derived_parameter_ordering = order_derived_parameters(component)
        
        for dpn in derived_parameter_ordering:
            derived_parameter = component.derived_parameters[dpn]
            runnable.add_derived_variable(derived_parameter.name)
            
            expression = self.build_expression_from_tree(runnable,
                                                        None,
                                                        derived_parameter.expression_tree)
            
            derived_parameter_code += ['self.{0} = ({1})'.format(
                        derived_parameter.name,
                        expression)]
            derived_parameter_code += ['self.{0}_shadow = ({1})'.format(
                        derived_parameter.name,
                        expression)]
       
        suffix = ''
        runnable.add_method('update_derived_parameters' + suffix, ['self'],
                            derived_parameter_code)
            
        for constant in component.constants:
            runnable.add_instance_variable(constant.name, constant.numeric_value)

        for text in component.texts:
            runnable.add_text_variable(text.name, text.value)
            
        for link in component.links:
            runnable.add_text_variable(link.name, link.value)

        for ep in component.event_ports:
            if ep.direction.lower() == 'in':
                runnable.add_event_in_port(ep.name)
            else:
                runnable.add_event_out_port(ep.name)

                
        dynamics = component.dynamics
        self.add_dynamics_1(component, runnable, dynamics, dynamics)

        for regime in dynamics.regimes:
            self.add_dynamics_1(component, runnable, regime, dynamics)
            
            if regime.initial:
                runnable.current_regime = regime.name

            rn = regime.name
            if rn not in runnable.regimes:
                runnable.add_regime(RunnableRegime(rn))
            r = runnable.regimes[rn]
            suffix = '_regime_' + rn

            if runnable.__dict__.has_key('update_state_variables' + suffix): 
                  r.update_state_variables = runnable.__dict__['update_state_variables' + suffix]
                  
            if runnable.__dict__.has_key('update_derived_variables' + suffix): 
                r.update_derived_variables = runnable.__dict__['update_derived_variables' + suffix]
            
            if runnable.__dict__.has_key('run_startup_event_handlers' + suffix): 
                r.run_startup_event_handlers = runnable.__dict__['run_startup_event_handlers' + suffix]
            
            if runnable.__dict__.has_key('run_preprocessing_event_handlers' + suffix): 
                r.run_preprocessing_event_handlers = runnable.__dict__['run_preprocessing_event_handlers' + suffix]
            
            if runnable.__dict__.has_key('run_postprocessing_event_handlers' + suffix): 
                r.run_postprocessing_event_handlers = runnable.__dict__['run_postprocessing_event_handlers' + suffix]

        self.process_simulation_specs(component, runnable, component.simulation)

        for child in component.child_components:
            child_runnable = self.build_runnable(child, runnable)
            runnable.add_child(child.id, child_runnable)

            for children in component.children:
                #GG - These conditions need more debugging.
                if children.type in child.types:
                    runnable.add_child_typeref(children.type, child_runnable)
                if children.multiple:
                    if children.type in child.types:
                        runnable.add_child_to_group(children.name, child_runnable)
                else:
                    if child_runnable.id == children.name:
                        runnable.add_child_typeref(children.name, child_runnable)

        for attachment in component.attachments:
            runnable.make_attachment(attachment.type, attachment.name)

        self.build_structure(component, runnable, component.structure)

        dynamics = component.dynamics
        self.add_dynamics_2(component, runnable,
                            dynamics, dynamics)
        for regime in dynamics.regimes:
            self.add_dynamics_2(component, runnable, regime, dynamics)

            if regime.name not in runnable.regimes:
                runnable.add_regime(RunnableRegime(regime.name))
            r = runnable.regimes[regime.name]
            suffix = '_regime_' + regime.name

            if runnable.__dict__.has_key('update_kinetic_scheme' + suffix): 
                r.update_kinetic_scheme = runnable.__dict__['update_kinetic_scheme' + suffix]

        self.add_recording_behavior(component, runnable)

        self.current_data_output = data_output_backup
        self.current_record_target = record_target_backup

        return runnable
    
    
    def build_event_connections(self, component, runnable, structure):
        """
        Adds event connections to a runnable component based on the structure
        specifications in the component model.

        @param component: Component model containing structure specifications.
        @type component: lems.model.component.FatComponent

        @param runnable: Runnable component to which structure is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param structure: The structure object to be used to add
        structure code in the runnable component.
        @type structure: lems.model.structure.Structure
        """
        if self.debug: print("\n++++++++ Calling build_event_connections of %s with runnable %s, parent %s"%(component.id, runnable.id, runnable.parent))
        # Process event connections
        for ec in structure.event_connections:
            if self.debug: print(ec.toxml())
            source = runnable.parent.resolve_path(ec.from_)
            target = runnable.parent.resolve_path(ec.to)
            if ec.receiver:
                receiver_template = self.build_runnable(ec.receiver,
                                                            target)
                                                            
                #receiver = copy.deepcopy(receiver_template)
                receiver = receiver_template.copy()
                receiver.id = "{0}__{1}__".format(component.id,
                                                  receiver_template.id)

                if ec.receiver_container:
                    target.add_attachment(receiver, ec.receiver_container)
                target.add_child(receiver_template.id, receiver)
                target = receiver
            else:
                source = runnable.resolve_path(ec.from_)
                target = runnable.resolve_path(ec.to)

            source_port = ec.source_port
            target_port = ec.target_port

            if not source_port:
                if len(source.event_out_ports) == 1:
                    source_port = source.event_out_ports[0]
                else:
                    raise SimBuildError(("No source event port "
                                         "uniquely identifiable"
                                         " in '{0}'").format(source.id))
            if not target_port:
                if len(target.event_in_ports) == 1:
                    target_port = target.event_in_ports[0]
                else:
                    raise SimBuildError(("No destination event port "
                                         "uniquely identifiable "
                                         "in '{0}'").format(target))
             
            if self.debug: print("register_event_out_callback\n   Source: %s, %s (port: %s) \n   -> %s, %s (port: %s)"%(source, id(source), source_port, target, id(target), target_port))
            source.register_event_out_callback(\
                source_port, lambda: target.inc_event_in(target_port))
                
            

    def build_structure(self, component, runnable, structure):
        """
        Adds structure to a runnable component based on the structure
        specifications in the component model.

        @param component: Component model containing structure specifications.
        @type component: lems.model.component.FatComponent

        @param runnable: Runnable component to which structure is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param structure: The structure object to be used to add
        structure code in the runnable component.
        @type structure: lems.model.structure.Structure
        """
        if self.debug: print("\n++++++++ Calling build_structure of %s with runnable %s, parent %s"%(component.id, runnable.id, runnable.parent))
        
        # Process single-child instantiations
        for ch in structure.child_instances:
            child_runnable = self.build_runnable(ch.referenced_component, runnable)
            runnable.add_child(child_runnable.id, child_runnable)

            runnable.add_child_typeref(ch.component, child_runnable)
            
        # Process multi-child instatiantions
        for mi in structure.multi_instantiates:
            template = self.build_runnable(mi.component,
                                           runnable)

            for i in range(mi.number):
                #instance = copy.deepcopy(template)
                instance = template.copy()
                instance.id = "{0}__{1}__{2}".format(component.id,
                                                     template.id,
                                                     i)
                runnable.array.append(instance)

        # Process foreach statements
        for fe in structure.for_eachs:
            self.build_foreach(component, runnable, fe)
        
        self.build_event_connections(component, runnable, structure)



    def build_foreach(self, component, runnable, foreach, name_mappings = {}):
        """
        Iterate over ForEach constructs and process nested elements.

        @param component: Component model containing structure specifications.
        @type component: lems.model.component.FatComponent

        @param runnable: Runnable component to which structure is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param foreach: The ForEach structure object to be used to add
        structure code in the runnable component.
        @type foreach: lems.model.structure.ForEach
        """
        if self.debug: print("\n++++++++ Calling build_foreach of %s with runnable %s, parent %s, name_mappings: %s"%(component.id, runnable.id, runnable.parent, name_mappings))

        target_array = runnable.resolve_path(foreach.instances)
        
        for target_runnable in target_array:
            if self.debug: print("Applying contents of for_each to %s, as %s"%(target_runnable.id, foreach.as_))
            name_mappings[foreach.as_] = target_runnable

            # Process foreach statements
            for fe2 in foreach.for_eachs:
                #print fe2.toxml()
                target_array2 = runnable.resolve_path(fe2.instances)

                for target_runnable2 in target_array2:
                    name_mappings[fe2.as_] = target_runnable2
                    self.build_foreach(component, runnable, fe2, name_mappings)

            # Process event connections
            for ec in foreach.event_connections:
                source = name_mappings[ec.from_]
                target = name_mappings[ec.to]

                source_port = ec.source_port
                target_port = ec.target_port

                if not source_port:
                    if len(source.event_out_ports) == 1:
                        source_port = source.event_out_ports[0]
                    else:
                        raise SimBuildError(("No source event port "
                                             "uniquely identifiable"
                                             " in '{0}'").format(source.id))
                if not target_port:
                    if len(target.event_in_ports) == 1:
                        target_port = target.event_in_ports[0]
                    else:
                        raise SimBuildError(("No destination event port "
                                             "uniquely identifiable "
                                             "in '{0}'").format(target))

                if self.debug: print("register_event_out_callback\n   Source: %s, %s (port: %s) \n   -> %s, %s (port: %s)"%(source, id(source), source_port, target, id(target), target_port))
                source.register_event_out_callback(\
                    source_port, lambda: target.inc_event_in(target_port))
                

    def add_dynamics_1(self, component, runnable, regime, dynamics):
        """
        Adds dynamics to a runnable component based on the dynamics
        specifications in the component model.

        This method builds dynamics necessary for building child components.

        @param component: Component model containing dynamics specifications.
        @type component: lems.model.component.FatComponent

        @param runnable: Runnable component to which dynamics is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param regime: The dynamics regime to be used to generate
        dynamics code in the runnable component.
        @type regime: lems.model.dynamics.Regime

        @param dynamics: Shared dynamics specifications.
        @type dynamics: lems.model.dynamics.Regime

        @raise SimBuildError: Raised when a time derivative expression refers
        to an undefined variable.

        @raise SimBuildError: Raised when there are invalid time
        specifications for the <Run> statement.

        @raise SimBuildError: Raised when the component reference for <Run>
        cannot be resolved.
        """

        if isinstance(regime, Dynamics) or regime.name == '':
            suffix = ''
        else:
            suffix = '_regime_' + regime.name

        if isinstance(regime, Regime) and regime.initial:
            runnable.new_regime = regime.name

        # Process state variables
        for sv in regime.state_variables:
            runnable.add_instance_variable(sv.name, 0)

        # Process time derivatives
        time_step_code = []
        for td in regime.time_derivatives:
            if td.variable not in regime.state_variables and td.variable not in dynamics.state_variables:
                raise SimBuildError(('Time derivative for undefined state '
                                     'variable {0} in component {1}').format(td.variable, component.id))

            exp = self.build_expression_from_tree(runnable,
                                                  regime,
                                                  td.expression_tree)
            time_step_code += ['self.{0} += dt * ({1})'.format(td.variable,
                                                               exp)]
        runnable.add_method('update_state_variables' + suffix, ['self', 'dt'],
                            time_step_code)

        # Process derived variables
        derived_variable_code = []
        derived_variables_ordering = order_derived_variables(regime)
        for dvn in derived_variables_ordering: #regime.derived_variables:
            if dvn in dynamics.derived_variables:
                dv = dynamics.derived_variables[dvn]
                runnable.add_derived_variable(dv.name)
                if dv.value:
                    derived_variable_code += ['self.{0} = ({1})'.format(
                        dv.name,
                        self.build_expression_from_tree(runnable,
                                                        regime,
                                                        dv.expression_tree))]
                elif dv.select:
                    if dv.reduce:
                        derived_variable_code += self.build_reduce_code(dv.name,
                                                                        dv.select,
                                                                        dv.reduce)
                    else:
                        derived_variable_code += ['self.{0} = (self.{1})'.format(
                            dv.name,
                            dv.select.replace('/', '.'))]
                else:
                    raise SimBuildError(('Inconsistent derived variable settings'
                                         'for {0}').format(dvn))
            elif dvn in dynamics.conditional_derived_variables:
                dv = dynamics.conditional_derived_variables[dvn]
                runnable.add_derived_variable(dv.name)
                derived_variable_code += self.build_conditional_derived_var_code(runnable,
                                                                                 regime,
                                                                                 dv)
            else:
                raise SimBuildError("Unknown derived variable '{0}' in '{1}'",
                                     dvn, runnable.id)
        runnable.add_method('update_derived_variables' + suffix, ['self'],
                            derived_variable_code)

        # Process event handlers
        pre_event_handler_code = []
        post_event_handler_code = []
        startup_event_handler_code = []
        on_entry_added = False
        for eh in regime.event_handlers:
            if isinstance(eh, OnStart):
                startup_event_handler_code += self.build_event_handler(runnable,
                                                                       regime,
                                                                       eh)
            elif isinstance(eh, OnCondition):
                post_event_handler_code += self.build_event_handler(runnable,
                                                                    regime,
                                                                    eh)
            else:
                if isinstance(eh, OnEntry):
                    on_entry_added = True
                pre_event_handler_code += self.build_event_handler(runnable,
                                                                   regime,
                                                                   eh)
        if isinstance(regime, Regime) and not on_entry_added:
            pre_event_handler_code += self.build_event_handler(runnable, regime, OnEntry())
            
        runnable.add_method('run_startup_event_handlers' + suffix, ['self'],
                            startup_event_handler_code)
        runnable.add_method('run_preprocessing_event_handlers' + suffix, ['self'],
                            pre_event_handler_code)
        runnable.add_method('run_postprocessing_event_handlers' + suffix, ['self'],
                            post_event_handler_code)

    def add_dynamics_2(self, component, runnable, regime, dynamics):
        """
        Adds dynamics to a runnable component based on the dynamics
        specifications in the component model.

        This method builds dynamics dependent on child components.

        @param component: Component model containing dynamics specifications.
        @type component: lems.model.component.FatComponent

        @param runnable: Runnable component to which dynamics is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param regime: The dynamics regime to be used to generate
        dynamics code in the runnable component.
        @type regime: lems.model.dynamics.Regime

        @param dynamics: Shared dynamics specifications.
        @type dynamics: lems.model.dynamics.Regime

        @raise SimBuildError: Raised when a time derivative expression refers
        to an undefined variable.

        @raise SimBuildError: Raised when there are invalid time
        specifications for the <Run> statement.

        @raise SimBuildError: Raised when the component reference for <Run>
        cannot be resolved.
        """

        if isinstance(regime, Dynamics) or regime.name == '':
            suffix = ''
        else:
            suffix = '_regime_' + regime.name

        # Process kinetic schemes
        ks_code = []
        for ks in regime.kinetic_schemes:
            
            raise NotImplementedError("KineticScheme element is not stable in PyLEMS yet, see https://github.com/LEMS/pylems/issues/15")
        
            try:
                ###nodes = {node.id:node for node in runnable.__dict__[ks.nodes]}
                nodes = {}
                for node in runnable.__dict__[ks.nodes]:
                    nodes[node.id] = node
                edges = runnable.__dict__[ks.edges]

                for edge in edges:
                    from_ = edge.__dict__[ks.edge_source]
                    to = edge.__dict__[ks.edge_target]

                    ks_code += [('self.{0}.{2} += dt * (-self.{3}.{4} * self.{0}.{2}_shadow'
                                 ' + self.{3}.{5} * self.{1}.{2}_shadow)').format(
                        from_, to, ks.state_variable, edge.id,
                        ks.forward_rate, ks.reverse_rate)]

                    ks_code += [('self.{1}.{2} += dt * (self.{3}.{4} * self.{0}.{2}_shadow'
                                 ' - self.{3}.{5} * self.{1}.{2}_shadow)').format(
                        from_, to, ks.state_variable, edge.id,
                        ks.forward_rate, ks.reverse_rate)]

                ks_code += ['sum = 0']
                for node in nodes:
                    nodes[node].__dict__[ks.state_variable] = 1.0 / len(nodes)
                    nodes[node].__dict__[ks.state_variable + '_shadow'] = 1.0 / len(nodes)
                    ks_code += ['sum += self.{0}.{1}'.format(node, ks.state_variable)]

                for node in nodes:
                    ks_code += ['self.{0}.{1} /= sum'.format(node, ks.state_variable)]

                for node in nodes:
                    ks_code += [('self.{0}.{1}_shadow = '
                                 'self.{0}.{1}').format(node,
                                                        ks.state_variable)]

            except Exception as e:
                raise SimBuildError(("Unable to construct kinetic scheme '{0}' "
                                     "for component '{1}' - {2}").format(ks.name,
                                                                         component.id,
                                                                         str(e)))

        runnable.add_method('update_kinetic_scheme' + suffix, ['self', 'dt'],
                            ks_code)

    def process_simulation_specs(self, component, runnable, simulation):
        """
        Process simulation-related aspects to a runnable component based on the
        dynamics specifications in the component model.

        @param component: Component model containing dynamics specifications.
        @type component: lems.model.component.FatComponent

        @param runnable: Runnable component to which dynamics is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param simulation: The simulation-related aspects to be implemented
        in the runnable component.
        @type simulation: lems.model.simulation.Simulation

        @raise SimBuildError: Raised when a time derivative expression refers
        to an undefined variable

        @raise SimBuildError: Raised when there are invalid time
        specifications for the <Run> statement.

        @raise SimBuildError: Raised when the component reference for <Run>
        cannot be resolved.
        """

        # Process runs
        for run in simulation.runs:
            cid = run.component.id + '_' + component.id

            target = self.build_runnable(run.component, runnable, cid)
            self.sim.add_runnable(target)
            self.current_record_target = target

            target.configure_time(run.increment,
                                  run.total)


    def convert_op(self, op):
        """
        Converts NeuroML arithmetic/logical operators to python equivalents.

        @param op: NeuroML operator
        @type op: string


        @return: Python operator
        @rtype: string
        """

        if op == '.gt.':
            return '>'
        elif op == '.ge.' or op == '.geq.':
            return '>='
        elif op == '.lt.':
            return '<'
        elif op == '.le.':
            return '<='
        elif op == '.eq.':
            return '=='
        elif op == '.neq.':   
            return '!='
        elif op == '.ne.':   # .neq. is preferred!
            return '!='
        elif op == '^':
            return '**'
        elif op == '.and.':
            return 'and'
        elif op == '.or.':
            return 'or'
        else:
            return op

    def convert_func(self, func):
        """
        Converts NeuroML arithmetic/logical functions to python equivalents.

        @param func: NeuroML function
        @type func: string


        @return: Python operator
        @rtype: string
        """

        if func == 'ln':
            return 'log'
        elif func == 'random':
            return 'random.uniform'
        elif func == 'H':
            def heaviside_step(x):
                if x < 0: return 0
                elif x > 0: return 1
                else: return 0.5 
            return 'heaviside_step'
        else:
            return func

    def build_expression_from_tree(self, runnable, regime, tree_node):
        """
        Recursively builds a Python expression from a parsed expression tree.

        @param runnable: Runnable object to which this expression would be added.
        @type runnable: lems.sim.runnable.Runnable

        @param regime: Dynamics regime being built.
        @type regime: lems.model.dynamics.Regime

        @param tree_node: Root node for the tree from which the expression
        is to be built.
        @type tree_node: lems.parser.expr.ExprNode

        @return: Generated Python expression.
        @rtype: string
        """

        component_type = self.model.component_types[runnable.component.type]
        dynamics = component_type.dynamics
        
        if tree_node.type == ExprNode.VALUE:
            if tree_node.value[0].isalpha():
                if tree_node.value == 't':
                    return 'self.time_completed'
                elif tree_node.value in component_type.requirements:
                    var_prefix = 'self'
                    v = tree_node.value

                    r = runnable

                    while (v not in r.instance_variables and
                           v not in r.derived_variables):
                        var_prefix = '{0}.{1}'.format(var_prefix, 'parent')
                        r = r.parent
                        if r == None:
                            raise SimBuildError("Unable to resolve required "
                                                "variable '{0}'".format(v))

                    return '{0}.{1}'.format(var_prefix, v)
                elif (tree_node.value in dynamics.derived_variables or (regime is not None and tree_node.value in regime.derived_variables)):
                    return 'self.{0}'.format(tree_node.value)
                else:
                    return 'self.{0}_shadow'.format(tree_node.value)
            else:
                return tree_node.value
        elif tree_node.type == ExprNode.FUNC1:
            pattern = '({0}({1}))'
            func = self.convert_func(tree_node.func)
            if 'random.uniform' in func:
                pattern = '({0}(0,{1}))'
            return pattern.format(\
                func,
                self.build_expression_from_tree(runnable,
                                                regime,
                                                tree_node.param))
        else:
            return '({0}) {1} ({2})'.format(\
                self.build_expression_from_tree(runnable,
                                                regime,
                                                tree_node.left),
                self.convert_op(tree_node.op),
                self.build_expression_from_tree(runnable,
                                                regime,
                                                tree_node.right))

    def build_event_handler(self, runnable, regime, event_handler):
        """
        Build event handler code.

        @param event_handler: Event handler object
        @type event_handler: lems.model.dynamics.EventHandler

        @return: Generated event handler code.
        @rtype: list(string)
        """

        if isinstance(event_handler, OnCondition):
            return self.build_on_condition(runnable, regime, event_handler)
        elif isinstance(event_handler, OnEvent):
            return self.build_on_event(runnable, regime, event_handler)
        elif isinstance(event_handler, OnStart):
            return self.build_on_start(runnable, regime, event_handler)
        elif isinstance(event_handler, OnEntry):
            return self.build_on_entry(runnable, regime, event_handler)
        else:
            return []

    def build_on_condition(self, runnable, regime, on_condition):
        """
        Build OnCondition event handler code.

        @param on_condition: OnCondition event handler object
        @type on_condition: lems.model.dynamics.OnCondition

        @return: Generated OnCondition code
        @rtype: list(string)
        """

        on_condition_code = []

        on_condition_code += ['if {0}:'.format(\
            self.build_expression_from_tree(runnable,
                                            regime,
                                            on_condition.expression_tree))]

        for action in on_condition.actions:
            code = self.build_action(runnable, regime, action)
            for line in code:
                on_condition_code += ['    ' + line]

        return on_condition_code

    def build_on_event(self, runnable, regime, on_event):
        """
        Build OnEvent event handler code.

        @param on_event: OnEvent event handler object
        @type on_event: lems.model.dynamics.OnEvent

        @return: Generated OnEvent code
        @rtype: list(string)
        """
        on_event_code = []

        if self.debug: on_event_code += ['print("Maybe handling something for %s ("+str(id(self))+")")'%(runnable.id),
                          'print("EICs ("+str(id(self))+"): "+str(self.event_in_counters))']
                          
        on_event_code += ['count = self.event_in_counters[\'{0}\']'.\
                          format(on_event.port),
                          'while count > 0:',
                          '    print("  Handling event")' if self.debug else '',
                          '    count -= 1']
        for action in on_event.actions:
            code = self.build_action(runnable, regime, action)
            for line in code:
                on_event_code += ['    ' + line]

        on_event_code += ['self.event_in_counters[\'{0}\'] = 0'.\
                          format(on_event.port),]

        return on_event_code

    def build_on_start(self, runnable, regime, on_start):
        """
        Build OnStart start handler code.

        @param on_start: OnStart start handler object
        @type on_start: lems.model.dynamics.OnStart

        @return: Generated OnStart code
        @rtype: list(string)
        """

        on_start_code = []

        for action in on_start.actions:
            code = self.build_action(runnable, regime, action)
            for line in code:
                on_start_code += [line]

        return on_start_code

    def build_on_entry(self, runnable, regime, on_entry):
        """
        Build OnEntry start handler code.

        @param on_entry: OnEntry start handler object
        @type on_entry: lems.model.dynamics.OnEntry

        @return: Generated OnEntry code
        @rtype: list(string)
        """

        on_entry_code = []

        on_entry_code += ['if self.current_regime != self.last_regime:']
        on_entry_code += ['    self.last_regime = self.current_regime']

        for action in on_entry.actions:
            code = self.build_action(runnable, regime, action)
            for line in code:
                on_entry_code += ['    ' + line]

        return on_entry_code

    def build_action(self, runnable, regime, action):
        """
        Build event handler action code.

        @param action: Event handler action object
        @type action: lems.model.dynamics.Action

        @return: Generated action code
        @rtype: string
        """

        if isinstance(action, StateAssignment):
            return self.build_state_assignment(runnable, regime, action)
        if isinstance(action, EventOut):
            return self.build_event_out(action)
        if isinstance(action, Transition):
            return self.build_transition(action)
        else:
            return ['pass']

    def build_state_assignment(self, runnable, regime, state_assignment):
        """
        Build state assignment code.

        @param state_assignment: State assignment object
        @type state_assignment: lems.model.dynamics.StateAssignment

        @return: Generated state assignment code
        @rtype: string
        """

        return ['self.{0} = {1}'.format(\
            state_assignment.variable,
            self.build_expression_from_tree(runnable,
                                            regime,
                                            state_assignment.expression_tree))]

    def build_event_out(self, event_out):
        """
        Build event out code.

        @param event_out: event out object
        @type event_out: lems.model.dynamics.EventOut

        @return: Generated event out code
        @rtype: string
        """

        event_out_code = ['if "{0}" in self.event_out_callbacks:'.format(event_out.port),
                          '    for c in self.event_out_callbacks[\'{0}\']:'.format(event_out.port),
                          '        c()']

        return event_out_code

    def build_transition(self, transition):
        """
        Build regime transition code.

        @param transition: Transition object
        @type transition: lems.model.dynamics.Transition

        @return: Generated transition code
        @rtype: string
        """

        return ["self.new_regime = '{0}'".format(transition.regime)]

    def build_reduce_code(self, result, select, reduce):
        """
        Builds a reduce operation on the selected target range.
        """

        select = select.replace('/', '.')
        select = select.replace(' ', '')
        if reduce == 'add':
            reduce_op = '+'
            acc_start = 0
        else:
            reduce_op = '*'
            acc_start = 1

        #bits = select.split('[*]')
        bits = re.split('\[.*\]', select)
        seps = re.findall('\[.*\]', select)

        code = ['self.{0} = {1}'.format(result, acc_start)]
        code += ['self.{0}_shadow = {1}'.format(result, acc_start)]

        code += ['try:']

        if len(bits) == 1:
            target = select
            code += ['    self.{0} = self.{1}'.format(result, target)]
            code += ['    self.{0}_shadow = self.{1}'.format(result, target)]
        elif len(bits) == 2:
            sep = seps[0][1:-1]

            if sep == '*':
                array = bits[0]
                ref = bits[1]

                code += ['    acc = {0}'.format(acc_start)]
                code += ['    for o in self.{0}:'.format(array)]
                code += ['        acc = acc {0} o{1}'.format(reduce_op, ref)]
                code += ['    self.{0} = acc'.format(result)]
                code += ['    self.{0}_shadow = acc'.format(result)]
            else:
                bits2 = sep.split('=')
                if len(bits2) > 1:
                    array = bits[0]
                    ref = bits[1]

                    code += ['    acc = {0}'.format(acc_start)]
                    code += ['    for o in self.{0}:'.format(array)]
                    code += ['        if o.{0} == {1}:'.format(bits2[0], bits2[1])]
                    code += ['            acc = acc {0} o{1}'.format(reduce_op, ref)]
                    code += ['    self.{0} = acc'.format(result)]
                    code += ['    self.{0}_shadow = acc'.format(result)]
                else:
                    raise SimbuildError("Invalid reduce target - '{0}'".format(select))
        else:
            raise SimbuildError("Invalid reduce target - '{0}'".format(select))

        code += ['except:']
        code += ['    pass']

        return code

    def build_conditional_derived_var_code(self, runnable, regime, dv):
        code = []
        el = ''
        for case in dv.cases:
            if case.condition_expression_tree:
                code += [el+'if {0}:'.format(self.build_expression_from_tree(runnable, 
                                                                          regime, 
                                                                          case.condition_expression_tree))]
                el='el'
                code += ['    self.{0} = {1}'.format(dv.name, self.build_expression_from_tree(runnable, 
                                                                                              regime, 
                                                                                              case.value_expression_tree))]
                                                                                              
        for case in dv.cases:
            if case.condition_expression_tree is None:
                code += ['else: ']
                code += ['    self.{0} = {1}'.format(dv.name, self.build_expression_from_tree(runnable, 
                                                                                              regime, 
                                                                                              case.value_expression_tree))]
        return code
Example #7
0
class SimulationBuilder(LEMSBase):
    """
    Simulation builder class.
    """

    def __init__(self, model):
        """
        Constructor.

        @param model: Model upon which the simulation is to be generated.
        @type model: lems.model.model.Model
        """

        self.model = model
        """ Model to be used for constructing the simulation.
        @type: lems.model.model.Model """

        self.sim = None
        """ Simulation built from the model.
        @type: lems.sim.sim.Simulation """

        self.current_record_target = None


    def build(self):
        """
        Build the simulation components from the model.

        @return: A runnable simulation object
        @rtype: lems.sim.sim.Simulation
        """

        self.sim = Simulation()

        for component_id in self.model.targets:
            if component_id not in self.model.context.components:
                raise SimBuildError('Unable to find component \'{0}\' to run'\
                                    .format(component_id))
            component = self.model.context.components[component_id]

            runnable = self.build_runnable(component)
            self.sim.add_runnable(runnable)

        return self.sim

    def build_runnable(self, component, parent = None, id_ = None):
        """
        Build a runnable component from a component specification and add
        it to the simulation.

        @param component: Component specification
        @type component: lems.model.component.Component

        @param parent: Parent runnable component.
        @type parent: lems.sim.runnable.Runnable

        @param id_: Optional id for therunnable. If it's not passed in,
        the runnable will inherit the id of the component.

        @raise SimBuildError: Raised when a component reference cannot be
        resolved.
        """

        if id_ == None:
            runnable = Runnable(component.id, component, parent)
        else:
            runnable = Runnable(id_, component, parent)

        context = component.context
        record_target_backup = self.current_record_target

        for pn in context.parameters:
            p = context.parameters[pn]
            if p.numeric_value != None:
                runnable.add_instance_variable(p.name, p.numeric_value)
            elif p.dimension in ['__link__', '__text__']:
                runnable.add_text_variable(p.name, p.value)

        for port in context.event_in_ports:
            runnable.add_event_in_port(port)
        for port in context.event_out_ports:
            runnable.add_event_out_port(port)

        if context.selected_dynamics_profile:
            dynamics = context.selected_dynamics_profile
            self.add_dynamics_1(component, runnable,
                                dynamics.default_regime, dynamics.default_regime)

            for rn in dynamics.regimes:
                regime = dynamics.regimes[rn]
                self.add_dynamics_1(component, runnable, regime, dynamics.default_regime)

                if regime.initial:
                    runnable.current_regime = regime.name

                if rn not in runnable.regimes:
                    runnable.add_regime(Regime(rn))
                r = runnable.regimes[rn]
                suffix = '_regime_' + rn

                r.update_state_variables = runnable.__dict__['update_state_variables'
                                                             + suffix]
                r.update_derived_variables = runnable.__dict__['update_derived_variables'
                                                               + suffix]
                r.run_startup_event_handlers = runnable.__dict__['run_startup_event_handlers'
                                                                 + suffix]
                r.run_preprocessing_event_handlers = runnable.__dict__['run_preprocessing_event_handlers'
                                                                       + suffix]
                r.run_postprocessing_event_handlers = runnable.__dict__['run_postprocessing_event_handlers'
                                                                        + suffix]
        else:
            runnable.add_method('update_state_variables', ['self', 'dt'],
                                [])
            runnable.add_method('update_derived_variables', ['self'],
                                [])
            runnable.add_method('run_startup_event_handlers', ['self'],
                                [])
            runnable.add_method('run_preprocessing_event_handlers', ['self'],
                                [])
            runnable.add_method('run_postprocessing_event_handlers', ['self'],
                                [])

        self.process_simulation_specs(component, runnable, context.simulation)

        #for cn in context.components:
        for cn in context.components_ordering:
            child = context.components[cn]
            child_runnable = self.build_runnable(child, runnable)
            runnable.add_child(child.id, child_runnable)

            for cdn in context.children_defs:
                cdt = context.children_defs[cdn]
                #if cdt == child.component_type:
                if child.is_type(cdt):
                    runnable.add_child_to_group(cdn, child_runnable)

        for type_ in context.attachments:
            name = context.attachments[type_]
            runnable.make_attachment(type_, name)

        self.build_structure(component, runnable, context.structure)

        if context.selected_dynamics_profile:
            dynamics = context.selected_dynamics_profile
            self.add_dynamics_2(component, runnable,
                                dynamics.default_regime, dynamics.default_regime)
            for rn in dynamics.regimes:
                regime = dynamics.regimes[rn]
                self.add_dynamics_2(component, runnable, regime, dynamics.default_regime)

                if rn not in runnable.regimes:
                    runnable.add_regime(Regime(rn))
                r = runnable.regimes[rn]
                suffix = '_regime_' + rn

                r.update_kinetic_scheme = runnable.__dict__['update_kinetic_scheme'
                                                            + suffix]
        else:
            runnable.add_method('update_kinetic_scheme', ['self', 'dt'],
                                [])

        self.add_recording_behavior(component, runnable)

        self.current_record_target = record_target_backup

        return runnable

    def build_structure(self, component, runnable, structure):
        """
        Adds structure to a runnable component based on the structure
        specifications in the component model.

        @param component: Component model containing structure specifications.
        @type component: lems.model.component.Component

        @param runnable: Runnable component to which structure is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param structure: The structure object to be used to add
        structure code in the runnable component.
        @type structure: lems.model.structure.Structure
        """

        context = component.context

        # Process single-child instantiations
        for c in structure.single_child_defs:
            if c in context.component_refs:
                cref = context.component_refs[c]
                child = context.lookup_component(cref)
                child_runnable = self.build_runnable(child, runnable)
                runnable.add_child(c, child_runnable)

                for cdn in context.children_defs:
                    cdt = context.children_defs[cdn]
                    if cdt == child.component_type:
                        runnable.add_child_to_group(cdn, child_runnable)

            else:
                raise SimBuildError('Unable to find component ref \'{0}\' '
                                    'under \'{1}\''.format(\
                        c, runnable.id))


        # Process multi-child instatiantions
        for cparam in structure.multi_child_defs:
            sparam = structure.multi_child_defs[cparam]
            c1 = component
            c2 = context.lookup_component(cparam)
            template = self.build_runnable(context.lookup_component(cparam),
                                           runnable)

            for i in range(sparam):
                instance = copy.deepcopy(template)
                instance.id = "{0}__{1}__{2}".format(component.id,
                                                   template.id,
                                                   i)
                runnable.array.append(instance)

        # Process foreach statements
        if structure.foreach:
            self.build_foreach(component, runnable, structure)

        # Process event connections
        for event in structure.event_connections:
            if True:
            #try:
                source_pathvar = structure.with_mappings[event.source_path]
                target_pathvar = structure.with_mappings[event.target_path]

                source_path = context.lookup_path_parameter(source_pathvar)
                target_path = context.lookup_path_parameter(target_pathvar)

                source = runnable.parent.resolve_path(source_path)
                target = runnable.parent.resolve_path(target_path)

                if event.receiver:
                    receiver_component = context.lookup_component_ref(event.receiver)
                    receiver_template = self.build_runnable(receiver_component,
                                                            target)
                    receiver = copy.deepcopy(receiver_template)
                    receiver.id = "{0}__{1}__".format(component.id,
                                                    receiver_template.id)
                    target.add_attachment(receiver)
                    target = receiver

                source_port = context.lookup_text_parameter(event.source_port)
                target_port = context.lookup_text_parameter(event.target_port)

                if source_port == None:
                    if len(source.event_out_ports) == 1:
                        source_port = source.event_out_ports[0]
                    else:
                        raise SimBuildError(("No source event port "
                                             "uniquely identifiable"
                                             " in '{0}'").format(source.id))
                if target_port == None:
                    if len(target.event_in_ports) == 1:
                        target_port = target.event_in_ports[0]
                    else:
                        raise SimBuildError(("No destination event port "
                                             "uniquely identifiable "
                                             "in '{0}'").format(target.id))
                    #except Exception as e:
                    #raise e

            source.register_event_out_callback(\
                source_port, lambda: target.inc_event_in(target_port))

    def build_foreach(self, component, runnable, foreach, name_mappings = {}):
        """
        Iterate over ForEach constructs and process nested elements.

        @param component: Component model containing structure specifications.
        @type component: lems.model.component.Component

        @param runnable: Runnable component to which structure is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param foreach: The ForEach structure object to be used to add
        structure code in the runnable component.
        @type foreach: lems.model.structure.ForEach
        """

        context = component.context

        # Process foreach statements
        for fe in foreach.foreach:
            target_array = runnable.resolve_path(fe.target)

            for target_runnable in target_array:
                name_mappings[fe.name] = target_runnable
                self.build_foreach(component, runnable, fe, name_mappings)

        # Process event connections
        for event in foreach.event_connections:
            source = name_mappings[event.source_path]
            target = name_mappings[event.target_path]

            source_port = context.lookup_text_parameter(event.source_port)
            target_port = context.lookup_text_parameter(event.target_port)

            if source_port == None:
                if len(source.event_out_ports) == 1:
                    source_port = source.event_out_ports[0]
                else:
                    raise SimBuildError(("No source event port "
                                         "uniquely identifiable"
                                         " in '{0}'").format(source.id))
            if target_port == None:
                if len(target.event_in_ports) == 1:
                    target_port = target.event_in_ports[0]
                else:
                    raise SimBuildError(("No destination event port uniquely"
                                         "identifiable in '{0}'").format(target.id))

            source.register_event_out_callback(\
                source_port, lambda: target.inc_event_in(target_port))


    def add_dynamics_1(self, component, runnable, regime, default_regime):
        """
        Adds dynamics to a runnable component based on the dynamics
        specifications in the component model.

        This method builds dynamics necessary for building child components.

        @param component: Component model containing dynamics specifications.
        @type component: lems.model.component.Component

        @param runnable: Runnable component to which dynamics is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param regime: The dynamics regime to be used to generate
        dynamics code in the runnable component.
        @type regime: lems.model.dynamics.Regime

        @param default_regime: Shared dynamics specifications.
        @type default_regime: lems.model.dynamics.Regime

        @raise SimBuildError: Raised when a time derivative expression refers
        to an undefined variable.

        @raise SimBuildError: Raised when there are invalid time
        specifications for the <Run> statement.

        @raise SimBuildError: Raised when the component reference for <Run>
        cannot be resolved.
        """

        context = component.context
        if regime.name == '':
            suffix = ''
        else:
            suffix = '_regime_' + regime.name

        if regime.initial:
            runnable.new_regime = regime.name

        # Process state variables
        for svn in regime.state_variables:
            sv = regime.state_variables[svn]
            runnable.add_instance_variable(sv.name, 0)

        # Process time derivatives
        time_step_code = []
        for tdn in regime.time_derivatives:
            if tdn not in regime.state_variables and tdn not in default_regime.state_variables:
                raise SimBuildError(('Time derivative for undefined state '
                                     'variable {0}').format(tdn))

            td = regime.time_derivatives[tdn]
            exp = self.build_expression_from_tree(runnable,
                                                  context,
                                                  regime,
                                                  td.expression_tree)
            time_step_code += ['self.{0} += dt * ({1})'.format(td.variable,
                                                               exp)]
        runnable.add_method('update_state_variables' + suffix, ['self', 'dt'],
                            time_step_code)

        # Process derived variables
        derived_variable_code = []
        derived_variables_ordering = order_derived_variables(regime)
        for dvn in derived_variables_ordering: #regime.derived_variables:
            dv = regime.derived_variables[dvn]
            runnable.add_derived_variable(dv.name)
            if dv.value:
                derived_variable_code += ['self.{0} = ({1})'.format(
                        dv.name,
                        self.build_expression_from_tree(runnable,
                                                        context,
                                                        regime,
                                                        dv.expression_tree))]
            elif dv.select:
                if dv.reduce:
                    derived_variable_code += self.build_reduce_code(dv.name,
                                                                    dv.select,
                                                                    dv.reduce)
                else:
                    derived_variable_code += ['self.{0} = (self.{1})'.format(
                            dv.name,
                            dv.select.replace('/', '.'))]
            else:
                raise SimBuildError(('Inconsistent derived variable settings'
                                     'for {0}').format(dvn))
        runnable.add_method('update_derived_variables' + suffix, ['self'],
                            derived_variable_code)

        # Process event handlers
        pre_event_handler_code = []
        post_event_handler_code = []
        startup_event_handler_code = []
        for eh in regime.event_handlers:
            if eh.type == EventHandler.ON_START:
                startup_event_handler_code += self.build_event_handler(runnable,
                                                                       context,
                                                                       regime,
                                                                       eh)
            elif eh.type == EventHandler.ON_CONDITION:
                post_event_handler_code += self.build_event_handler(runnable,
                                                                    context,
                                                                    regime,
                                                                    eh)
            else:
                pre_event_handler_code += self.build_event_handler(runnable,
                                                                   context,
                                                                   regime,
                                                                   eh)
        runnable.add_method('run_startup_event_handlers' + suffix, ['self'],
                            startup_event_handler_code)
        runnable.add_method('run_preprocessing_event_handlers' + suffix, ['self'],
                            pre_event_handler_code)
        runnable.add_method('run_postprocessing_event_handlers' + suffix, ['self'],
                            post_event_handler_code)

    def add_dynamics_2(self, component, runnable, regime, default_regime):
        """
        Adds dynamics to a runnable component based on the dynamics
        specifications in the component model.

        This method builds dynamics dependent on child components.

        @param component: Component model containing dynamics specifications.
        @type component: lems.model.component.Component

        @param runnable: Runnable component to which dynamics is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param regime: The dynamics regime to be used to generate
        dynamics code in the runnable component.
        @type regime: lems.model.dynamics.Regime

        @param default_regime: Shared dynamics specifications.
        @type default_regime: lems.model.dynamics.Regime

        @raise SimBuildError: Raised when a time derivative expression refers
        to an undefined variable.

        @raise SimBuildError: Raised when there are invalid time
        specifications for the <Run> statement.

        @raise SimBuildError: Raised when the component reference for <Run>
        cannot be resolved.
        """

        context = component.context
        if regime.name == '':
            suffix = ''
        else:
            suffix = '_regime_' + regime.name

        # Process kinetic schemes
        ks_code = []
        for ksn in regime.kinetic_schemes:
            ks = regime.kinetic_schemes[ksn]

            try:
                nodes = {node.id:node for node in runnable.__dict__[ks.nodes]}
                edges = runnable.__dict__[ks.edges]

                for edge in edges:
                    from_ = edge.__dict__[ks.edge_source]
                    to = edge.__dict__[ks.edge_target]

                    ks_code += [('self.{0}.{2} += dt * (-self.{3}.{4} * self.{0}.{2}_shadow + '
                                 'self.{3}.{5} * self.{1}.{2}_shadow)').format(
                        from_, to, ks.state_variable, edge.id, ks.forward_rate, ks.reverse_rate)]

                    ks_code += [('self.{1}.{2} += dt * (self.{3}.{4} * self.{0}.{2}_shadow - '
                                 'self.{3}.{5} * self.{1}.{2}_shadow)').format(
                        from_, to, ks.state_variable, edge.id, ks.forward_rate, ks.reverse_rate)]

                ks_code += ['sum = 0']
                for node in nodes:
                    nodes[node].__dict__[ks.state_variable] = 1.0 / len(nodes)
                    nodes[node].__dict__[ks.state_variable + '_shadow'] = 1.0 / len(nodes)
                    ks_code += ['sum += self.{0}.{1}'.format(node, ks.state_variable)]

                for node in nodes:
                    ks_code += ['self.{0}.{1} /= sum'.format(node, ks.state_variable)]

                for node in nodes:
                    ks_code += ['self.{0}.{1}_shadow = self.{0}.{1}'.format(node, ks.state_variable)]

            except Exception as e:
                raise SimBuildError(("Unable to construct kinetic scheme '{0}' "
                                     "for component '{1}' - {2}").format(ks.name, component.id, str(e)))

        runnable.add_method('update_kinetic_scheme' + suffix, ['self', 'dt'],
                            ks_code)

    def process_simulation_specs(self, component, runnable, simulation):
        """
        Process simulation-related aspects to a runnable component based on the
        dynamics specifications in the component model.

        @param component: Component model containing dynamics specifications.
        @type component: lems.model.component.Component

        @param runnable: Runnable component to which dynamics is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param simulation: The simulation-related aspects to be implemented
        in the runnable component.
        @type simulation: lems.model.simulation.Simulation

        @raise SimBuildError: Raised when a time derivative expression refers
        to an undefined variable

        @raise SimBuildError: Raised when there are invalid time
        specifications for the <Run> statement.

        @raise SimBuildError: Raised when the component reference for <Run>
        cannot be resolved.
        """

        context = component.context

        # Process runs
        for rn in simulation.runs:
            run = simulation.runs[rn]
            c = context.lookup_component_ref(run.component)

            if c != None:
                #cid = c.id
                #if c.id in self.sim.runnables:
                #    idx = 2
                #    cid = c.id + '_' + str(idx)
                #    while cid in self.sim.runnables:
                #        idx = idx + 1
                #        cid = c.id + '_' + str(idx)
                cid = c.id + '_' + component.id

                target = self.build_runnable(c, runnable, cid)
                self.sim.add_runnable(target)
                self.current_record_target = target

                time_step = context.lookup_parameter(run.increment)
                time_total = context.lookup_parameter(run.total)
                if time_step != None and time_total != None:
                    target.configure_time(time_step.numeric_value,
                                          time_total.numeric_value)
            else:
                raise SimBuildError(('Invalid component reference {0} in '
                                     '<Run>').format(c.id))


    def convert_op(self, op):
        """
        Converts NeuroML arithmetic/logical operators to python equivalents.

        @param op: NeuroML operator
        @type op: string


        @return: Python operator
        @rtype: string
        """

        if op == '.gt.':
            return '>'
        elif op == '.ge.' or op == '.geq.':
            return '>='
        elif op == '.lt.':
            return '<'
        elif op == '.le.':
            return '<='
        elif op == '.eq.':
            return '=='
        elif op == '.ne.':
            return '!='
        elif op == '^':
            return '**'
        elif op == '.and.':
            return 'and'
        elif op == '.or.':
            return 'or'
        else:
            return op

    def build_expression_from_tree(self, runnable, context, regime, tree_node):
        """
        Recursively builds a Python expression from a parsed expression tree.

        @param runnable: Runnable object to which this expression would be added.
        @type runnable: lems.sim.runnable.Runnable

        @param context: Context from which variables are to be resolved.
        @type context: lems.model.context.Context

        @param regime: Dynamics regime being built.
        @type regime: lems.model.dynamics.Regime

        @param tree_node: Root node for the tree from which the expression
        is to be built.
        @type tree_node: lems.parser.expr.ExprNode

        @return: Generated Python expression.
        @rtype: string
        """

        default_regime = context.selected_dynamics_profile.default_regime

        if tree_node.type == ExprNode.VALUE:
            if tree_node.value[0].isalpha():
                if tree_node.value == 't':
                    return 'self.time_completed'
                elif tree_node.value in context.requirements:
                    var_prefix = 'self'
                    v = tree_node.value

                    r = runnable

                    while (v not in r.instance_variables and
                           v not in r.derived_variables):
                        var_prefix = '{0}.{1}'.format(var_prefix, 'parent')
                        r = r.parent
                        if r == None:
                            raise SimBuildError("Unable to resolve required "
                                                "variable '{0}'".format(v))

                    return '{0}.{1}'.format(var_prefix, v)
                elif (tree_node.value in regime.derived_variables or
                      tree_node.value in default_regime.derived_variables):
                    return 'self.{0}'.format(tree_node.value)
                else:
                    return 'self.{0}_shadow'.format(tree_node.value)
            else:
                return tree_node.value
        elif tree_node.type == ExprNode.FUNC1:
            return '({0}({1}))'.format(\
                tree_node.func,
                self.build_expression_from_tree(runnable,
                                                context,
                                                regime,
                                                tree_node.param))
        else:
            return '({0}) {1} ({2})'.format(\
                self.build_expression_from_tree(runnable,
                                                context,
                                                regime,
                                                tree_node.left),
                self.convert_op(tree_node.op),
                self.build_expression_from_tree(runnable,
                                                context,
                                                regime,
                                                tree_node.right))

    def build_event_handler(self, runnable, context, regime, event_handler):
        """
        Build event handler code.

        @param event_handler: Event handler object
        @type event_handler: lems.model.dynamics.EventHandler

        @return: Generated event handler code.
        @rtype: list(string)
        """

        if event_handler.type == EventHandler.ON_CONDITION:
            return self.build_on_condition(runnable, context, regime, event_handler)
        elif event_handler.type == EventHandler.ON_EVENT:
            return self.build_on_event(runnable, context, regime, event_handler)
        elif event_handler.type == EventHandler.ON_START:
            return self.build_on_start(runnable, context, regime, event_handler)
        elif event_handler.type == EventHandler.ON_ENTRY:
            return self.build_on_entry(runnable, context, regime, event_handler)
        else:
            return []

    def build_on_condition(self, runnable, context, regime, on_condition):
        """
        Build OnCondition event handler code.

        @param on_condition: OnCondition event handler object
        @type on_condition: lems.model.dynamics.OnCondition

        @return: Generated OnCondition code
        @rtype: list(string)
        """

        on_condition_code = []

        on_condition_code += ['if {0}:'.format(\
            self.build_expression_from_tree(runnable,
                                            context,
                                            regime,
                                            on_condition.expression_tree))]

        for action in on_condition.actions:
            code = self.build_action(runnable, context, regime, action)
            for line in code:
                on_condition_code += ['    ' + line]

        return on_condition_code

    def build_on_event(self, runnable, context, regime, on_event):
        """
        Build OnEvent event handler code.

        @param on_event: OnEvent event handler object
        @type on_event: lems.model.dynamics.OnEvent

        @return: Generated OnEvent code
        @rtype: list(string)
        """

        on_event_code = []

        on_event_code += ['count = self.event_in_counters[\'{0}\']'.\
                          format(on_event.port),
                          'while count > 0:',
                          '    count -= 1']
        for action in on_event.actions:
            code = self.build_action(runnable, context, regime, action)
            for line in code:
                on_event_code += ['    ' + line]

        on_event_code += ['self.event_in_counters[\'{0}\'] = 0'.\
                          format(on_event.port),]

        return on_event_code

    def build_on_start(self, runnable, context, regime, on_start):
        """
        Build OnStart start handler code.

        @param on_start: OnStart start handler object
        @type on_start: lems.model.dynamics.OnStart

        @return: Generated OnStart code
        @rtype: list(string)
        """

        on_start_code = []

        for action in on_start.actions:
            code = self.build_action(runnable, context, regime, action)
            for line in code:
                on_start_code += ['    ' + line]

        return on_start_code

    def build_on_entry(self, runnable, context, regime, on_entry):
        """
        Build OnEntry start handler code.

        @param on_start: OnStart start handler object
        @type on_start: lems.model.dynamics.OnStart

        @return: Generated OnStart code
        @rtype: list(string)
        """

        on_entry_code = []

        on_entry_code += ['if self.current_regime != self.last_regime:']
        on_entry_code += ['    self.last_regime = self.current_regime']

        for action in on_entry.actions:
            code = self.build_action(runnable, context, regime, action)
            for line in code:
                on_entry_code += ['    ' + line]

        return on_entry_code

    def build_action(self, runnable, context, regime, action):
        """
        Build event handler action code.

        @param action: Event handler action object
        @type action: lems.model.dynamics.Action

        @return: Generated action code
        @rtype: string
        """

        if action.type == Action.STATE_ASSIGNMENT:
            return self.build_state_assignment(runnable, context, regime, action)
        if action.type == Action.EVENT_OUT:
            return self.build_event_out(action)
        if action.type == Action.TRANSITION:
            return self.build_transition(action)
        else:
            return ['pass']

    def build_state_assignment(self, runnable, context, regime, state_assignment):
        """
        Build state assignment code.

        @param state_assignment: State assignment object
        @type state_assignment: lems.model.dynamics.StateAssignment

        @return: Generated state assignment code
        @rtype: string
        """

        return ['self.{0} = {1}'.format(\
            state_assignment.variable,
            self.build_expression_from_tree(runnable,
                                            context,
                                            regime,
                                            state_assignment.expression_tree))]

    def build_event_out(self, event_out):
        """
        Build event out code.

        @param event_out: event out object
        @type event_out: lems.model.dynamics.EventOut

        @return: Generated event out code
        @rtype: string
        """

        event_out_code = ['if "{0}" in self.event_out_callbacks:'.format(event_out.port),
                          '    for c in self.event_out_callbacks[\'{0}\']:'.format(event_out.port),
                          '        c()']

        return event_out_code

    def build_transition(self, transition):
        """
        Build regime transition code.

        @param transition: Transition object
        @type transition: lems.model.dynamics.Transition

        @return: Generated transition code
        @rtype: string
        """

        return ["self.new_regime = '{0}'".format(transition.regime)]

    def build_reduce_code(self, result, select, reduce):
        """
        Builds a reduce operation on the selected target range.
        """

        select = select.replace('/', '.')
        select = select.replace(' ', '')
        if reduce == 'add':
            reduce_op = '+'
            acc_start = 0
        else:
            reduce_op = '*'
            acc_start = 1

        bits = select.split('[*]')
        array = bits[0]
        ref = bits[1]

        code = ['acc = {0}'.format(acc_start)]
        code += ['for o in self.{0}:'.format(array)]
        code += ['    acc = acc {0} o{1}'.format(reduce_op, ref)]
        code += ['self.{0} = acc'.format(result)]
        code += ['self.{0}_shadow = acc'.format(result)]

        return code

    def add_recording_behavior(self, component, runnable):
        """
        Adds recording-related dynamics to a runnable component based on
        the dynamics specifications in the component model.

        @param component: Component model containing dynamics specifications.
        @type component: lems.model.component.Component

        @param runnable: Runnable component to which dynamics is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @raise SimBuildError: Raised when a target for recording could not be
        found.
        """

        context = component.context
        simulation = context.simulation

        for rn in simulation.records:
            rec = simulation.records[rn]
            if self.current_record_target == None:
                raise SimBuildError('No target available for '
                                    'recording variables')
            self.current_record_target.add_variable_recorder(rec)
Example #8
0
class SimulationBuilder(LEMSBase):
    """
    Simulation builder class.
    """
    def __init__(self, model):
        """
        Constructor.

        @param model: Model upon which the simulation is to be generated.
        @type model: lems.model.model.Model
        """

        self.model = model
        """ Model to be used for constructing the simulation.
        @type: lems.model.model.Model """

        self.sim = None
        """ Simulation built from the model.
        @type: lems.sim.sim.Simulation """

        self.current_record_target = None

    def build(self):
        """
        Build the simulation components from the model.

        @return: A runnable simulation object
        @rtype: lems.sim.sim.Simulation
        """

        self.sim = Simulation()

        for component_id in self.model.default_runs:
            if component_id not in self.model.context.components:
                raise SimBuildError('Unable to find component \'{0}\' to run'\
                                    .format(component_id))
            component = self.model.context.components[component_id]

            runnable = self.build_runnable(component)
            self.sim.add_runnable(component.id, runnable)

        return self.sim

    def build_runnable(self, component, parent=None):
        """
        Build a runnable component from a component specification and add
        it to the simulation.

        @param component: Component specification
        @type component: lems.model.component.Component

        @param parent: Parent runnable component.
        @type parent: lems.sim.runnable.Runnable

        @raise SimBuildError: Raised when a component reference cannot be
        resolved.
        """

        runnable = Runnable(component.id, parent)

        context = component.context
        record_target_backup = self.current_record_target

        for pn in context.parameters:
            p = context.parameters[pn]
            if p.numeric_value:
                runnable.add_instance_variable(p.name, p.numeric_value)
            else:
                pass
                ## if p.dimension == '__component_ref__':
                ##     ref = context.parent.lookup_component(p.value)
                ##     if ref == None:
                ##         raise SimBuildError(('Unable to resolve component '
                ##                              'reference {0}').\
                ##                             format(component_name))
                ##     self.sim.add_runnable(ref.id, self.build_runnable(ref))

        for port in context.event_in_ports:
            runnable.add_event_in_port(port)
        for port in context.event_out_ports:
            runnable.add_event_out_port(port)

        if context.selected_behavior_profile:
            self.add_runnable_behavior(component, runnable,
                                       context.selected_behavior_profile)
        else:
            runnable.add_method('update_state_variables', ['self', 'dt'], [])
            runnable.add_method('run_preprocessing_event_handlers', ['self'],
                                [])
            runnable.add_method('run_postprocessing_event_handlers', ['self'],
                                [])

        for cn in context.components:
            child = context.components[cn]
            runnable.add_child(child.id, self.build_runnable(child, runnable))

        self.build_structure(component, runnable, context.structure)

        if context.selected_behavior_profile:
            self.add_recording_behavior(component, runnable,
                                        context.selected_behavior_profile)

        self.current_record_target = record_target_backup

        return runnable

    def build_structure(self, component, runnable, structure):
        """
        Adds structure to a runnable component based on the structure
        specifications in the component model.

        @param component: Component model containing structure specifications.
        @type component: lems.model.component.Component

        @param runnable: Runnable component to which structure is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param structure: The structure object to be used to add
        structure code in the runnable component.
        @type structure: lems.model.structure.Structure
        """

        context = component.context

        # Process single-child instantiations
        # TBD

        # Process multi-child instatiantions
        for cparam in structure.multi_child_defs:
            sparam = structure.multi_child_defs[cparam]
            c1 = component
            c2 = context.lookup_component(cparam)
            template = self.build_runnable(context.lookup_component(cparam),
                                           component)

            for i in xrange(sparam):
                instance = copy.deepcopy(template)
                instance.id = "{0}#{1}#{2}".format(component.id, template.id,
                                                   i)
                runnable.array.append(instance)

        # Process event connections
        for (from_component, from_port, to_component,
             to_port) in structure.event_connections:
            self.add_event_connection(runnable, from_component, from_port,
                                      to_component, to_port)

    def add_event_connection(self, runnable, from_component, from_port,
                             to_component, to_port):
        if from_component in runnable.children:
            from_ = runnable.children[from_component]
        else:
            raise SimBuildError('Unable to find component \'{0}\' '
                                'under \'{1}\''.format(\
                                    from_component, runnable.id))

        if to_component in runnable.children:
            to = runnable.children[to_component]
        else:
            raise SimBuildError('Unable to find component \'{0}\' '
                                'under \'{1}\''.format(\
                                    to_component, runnable.id))

        from_.register_event_out_callback(\
            from_port, lambda: to.inc_event_in(to_port))

    def add_runnable_behavior(self, component, runnable, behavior_profile):
        """
        Adds behavior to a runnable component based on the behavior
        specifications in the component model.

        @param component: Component model containing behavior specifications.
        @type component: lems.model.component.Component

        @param runnable: Runnable component to which behavior is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param behavior_profile: The behavior profile to be used to generate
        behavior code in the runnable component.
        @type behavior_profile: lems.model.behavior.Behavior

        @raise SimBuildError: Raised when a time derivative expression refers
        to an undefined variable.

        @raise SimBuildError: Raised when there are invalid time
        specifications for the <Run> statement.

        @raise SimBuildError: Raised when the component reference for <Run>
        cannot be resolved.
        """

        context = component.context
        regime = behavior_profile.default_regime

        for svn in regime.state_variables:
            sv = regime.state_variables[svn]
            runnable.add_instance_variable(sv.name, 0)

        time_step_code = []
        for tdn in regime.time_derivatives:
            if tdn not in regime.state_variables:
                raise SimBuildError(('Time derivative for undefined state '
                                     'variable {0}').format(tdn))

            td = regime.time_derivatives[tdn]
            time_step_code += ['self.{0} += dt * ({1})'.format(td.variable,
                               self.build_expression_from_tree(\
                                   td.expression_tree))]
        runnable.add_method('update_state_variables', ['self', 'dt'],
                            time_step_code)

        pre_event_handler_code = []
        post_event_handler_code = []
        for eh in regime.event_handlers:
            if eh.type == EventHandler.ON_CONDITION:
                post_event_handler_code += self.build_event_handler(eh)
            else:
                pre_event_handler_code += self.build_event_handler(eh)
        runnable.add_method('run_preprocessing_event_handlers', ['self'],
                            pre_event_handler_code)
        runnable.add_method('run_postprocessing_event_handlers', ['self'],
                            post_event_handler_code)

        for rn in regime.runs:
            run = regime.runs[rn]
            c = context.lookup_component_ref(run.component)
            if c != None:
                target = self.build_runnable(c, self)
                self.sim.add_runnable(c.id, target)
                self.current_record_target = target
                time_step = context.lookup_parameter(run.increment)
                time_total = context.lookup_parameter(run.total)
                if time_step != None and time_total != None:
                    target.configure_time(time_step.numeric_value,
                                          time_total.numeric_value)
            else:
                raise SimBuildError(('Invalid component reference {0} in '
                                     '<Run>').format(c.id))

    def convert_op(self, op):
        """
        Converts NeuroML arithmetic/logical operators to python equivalents.

        @param op: NeuroML operator
        @type op: string

        @return: Python operator
        @rtype: string
        """

        if op == '.gt.':
            return '>'
        elif op == '.ge.':
            return '>='
        elif op == '.lt.':
            return '<'
        elif op == '.le.':
            return '<='
        elif op == '.eq.':
            return '=='
        elif op == '.ne.':
            return '!='
        else:
            return op

    def build_expression_from_tree(self, tree_node):
        """
        Recursively builds a Python expression from a parsed expression tree.

        @param tree_node: Root node for the tree from which the expression
        is to be built.
        @type tree_node: lems.parser.expr.ExprNode

        @return: Generated Python expression.
        @rtype: string
        """

        if tree_node.type == ExprNode.VALUE:
            if tree_node.value[0].isalpha():
                return 'self.{0}_shadow'.format(tree_node.value)
            else:
                return tree_node.value
        else:
            return '({0}) {1} ({2})'.format(\
                self.build_expression_from_tree(tree_node.left),
                self.convert_op(tree_node.op),
                self.build_expression_from_tree(tree_node.right))

    def build_event_handler(self, event_handler):
        """
        Build event handler code.

        @param event_handler: Event handler object
        @type event_handler: lems.model.behavior.EventHandler

        @return: Generated event handler code.
        @rtype: list(string)
        """

        if event_handler.type == EventHandler.ON_CONDITION:
            return self.build_on_condition(event_handler)
        elif event_handler.type == EventHandler.ON_EVENT:
            return self.build_on_event(event_handler)
        else:
            return []

    def build_on_condition(self, on_condition):
        """
        Build OnCondition event handler code.

        @param on_condition: OnCondition event handler object
        @type on_condition: lems.model.behavior.OnCondition

        @return: Generated OnCondition code
        @rtype: list(string)
        """

        on_condition_code = []

        on_condition_code += ['if {0}:'.format(\
            self.build_expression_from_tree(on_condition.expression_tree))]

        for action in on_condition.actions:
            on_condition_code += ['    ' + self.build_action(action)]

        return on_condition_code

    def build_on_event(self, on_event):
        """
        Build OnEvent event handler code.

        @param on_event: OnEvent event handler object
        @type on_event: lems.model.behavior.OnEvent

        @return: Generated OnEvent code
        @rtype: list(string)
        """

        on_event_code = []

        on_event_code += ['count = self.event_in_counters[\'{0}\']'.\
                          format(on_event.port),
                          'while count > 0:',
                          '    count -= 1']
        for action in on_event.actions:
            on_event_code += ['    ' + self.build_action(action)]
        on_event_code += ['self.event_in_counters[\'{0}\'] = 0'.\
                          format(on_event.port),]

        return on_event_code

    def build_action(self, action):
        """
        Build event handler action code.

        @param action: Event handler action object
        @type action: lems.model.behavior.Action

        @return: Generated action code
        @rtype: string
        """

        if action.type == Action.STATE_ASSIGNMENT:
            return self.build_state_assignment(action)
        if action.type == Action.EVENT_OUT:
            return self.build_event_out(action)
        else:
            return ''

    def build_state_assignment(self, state_assignment):
        """
        Build state assignment code.

        @param state_assignment: State assignment object
        @type state_assignment: lems.model.behavior.StateAssignment

        @return: Generated state assignment code
        @rtype: string
        """

        return 'self.{0} = {1}'.format(\
            state_assignment.variable,
            self.build_expression_from_tree(state_assignment.expression_tree))

    def build_event_out(self, event_out):
        """
        Build event out code.

        @param event_out: event out object
        @type event_out: lems.model.behavior.StateAssignment

        @return: Generated event out code
        @rtype: string
        """

        event_out_code = 'for c in self.event_out_callbacks[\'{0}\']: c()'.\
                         format(event_out.port)

        return event_out_code

    def add_recording_behavior(self, component, runnable, behavior_profile):
        """
        Adds recording-related behavior to a runnable component based on
        the behavior specifications in the component model.

        @param component: Component model containing behavior specifications.
        @type component: lems.model.component.Component

        @param runnable: Runnable component to which behavior is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param behavior_profile: The behavior profile to be used to generate
        behavior code in the runnable component.
        @type behavior_profile: lems.model.behavior.Behavior

        @raise SimBuildError: Raised when a target for recording could not be
        found.
        """

        context = component.context
        regime = behavior_profile.default_regime

        for rn in regime.records:
            rec = regime.records[rn]
            if self.current_record_target == None:
                raise SimBuildError('No target available for '
                                    'recording variables')
            self.current_record_target.add_variable_recorder(rec.quantity)
Example #9
0
class SimulationBuilder(LEMSBase):
    """
    Simulation builder class.
    """

    def __init__(self, model):
        """
        Constructor.

        @param model: Model upon which the simulation is to be generated.
        @type model: lems.model.model.Model
        """

        self.model = model
        """ Model to be used for constructing the simulation.
        @type: lems.model.model.Model """

        self.sim = None
        """ Simulation built from the model.
        @type: lems.sim.sim.Simulation """

        self.current_record_target = None

    def build(self):
        """
        Build the simulation components from the model.

        @return: A runnable simulation object
        @rtype: lems.sim.sim.Simulation
        """

        self.sim = Simulation()

        for component_id in self.model.default_runs:
            if component_id not in self.model.context.components:
                raise SimBuildError("Unable to find component '{0}' to run".format(component_id))
            component = self.model.context.components[component_id]

            runnable = self.build_runnable(component)
            self.sim.add_runnable(component.id, runnable)

        return self.sim

    def build_runnable(self, component, parent=None):
        """
        Build a runnable component from a component specification and add
        it to the simulation.

        @param component: Component specification
        @type component: lems.model.component.Component

        @param parent: Parent runnable component.
        @type parent: lems.sim.runnable.Runnable

        @raise SimBuildError: Raised when a component reference cannot be
        resolved.
        """

        runnable = Runnable(component.id, parent)

        context = component.context
        record_target_backup = self.current_record_target

        for pn in context.parameters:
            p = context.parameters[pn]
            if p.numeric_value:
                runnable.add_instance_variable(p.name, p.numeric_value)
            else:
                pass
                ## if p.dimension == '__component_ref__':
                ##     ref = context.parent.lookup_component(p.value)
                ##     if ref == None:
                ##         raise SimBuildError(('Unable to resolve component '
                ##                              'reference {0}').\
                ##                             format(component_name))
                ##     self.sim.add_runnable(ref.id, self.build_runnable(ref))

        for port in context.event_in_ports:
            runnable.add_event_in_port(port)
        for port in context.event_out_ports:
            runnable.add_event_out_port(port)

        if context.selected_behavior_profile:
            self.add_runnable_behavior(component, runnable, context.selected_behavior_profile)
        else:
            runnable.add_method("update_state_variables", ["self", "dt"], [])
            runnable.add_method("run_preprocessing_event_handlers", ["self"], [])
            runnable.add_method("run_postprocessing_event_handlers", ["self"], [])

        for cn in context.components:
            child = context.components[cn]
            runnable.add_child(child.id, self.build_runnable(child, runnable))

        self.build_structure(component, runnable, context.structure)

        if context.selected_behavior_profile:
            self.add_recording_behavior(component, runnable, context.selected_behavior_profile)

        self.current_record_target = record_target_backup

        return runnable

    def build_structure(self, component, runnable, structure):
        """
        Adds structure to a runnable component based on the structure
        specifications in the component model.

        @param component: Component model containing structure specifications.
        @type component: lems.model.component.Component

        @param runnable: Runnable component to which structure is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param structure: The structure object to be used to add
        structure code in the runnable component.
        @type structure: lems.model.structure.Structure
        """

        context = component.context

        # Process single-child instantiations
        # TBD

        # Process multi-child instatiantions
        for cparam in structure.multi_child_defs:
            sparam = structure.multi_child_defs[cparam]
            c1 = component
            c2 = context.lookup_component(cparam)
            template = self.build_runnable(context.lookup_component(cparam), component)

            for i in xrange(sparam):
                instance = copy.deepcopy(template)
                instance.id = "{0}#{1}#{2}".format(component.id, template.id, i)
                runnable.array.append(instance)

        # Process event connections
        for (from_component, from_port, to_component, to_port) in structure.event_connections:
            self.add_event_connection(runnable, from_component, from_port, to_component, to_port)

    def add_event_connection(self, runnable, from_component, from_port, to_component, to_port):
        if from_component in runnable.children:
            from_ = runnable.children[from_component]
        else:
            raise SimBuildError("Unable to find component '{0}' " "under '{1}'".format(from_component, runnable.id))

        if to_component in runnable.children:
            to = runnable.children[to_component]
        else:
            raise SimBuildError("Unable to find component '{0}' " "under '{1}'".format(to_component, runnable.id))

        from_.register_event_out_callback(from_port, lambda: to.inc_event_in(to_port))

    def add_runnable_behavior(self, component, runnable, behavior_profile):
        """
        Adds behavior to a runnable component based on the behavior
        specifications in the component model.

        @param component: Component model containing behavior specifications.
        @type component: lems.model.component.Component

        @param runnable: Runnable component to which behavior is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param behavior_profile: The behavior profile to be used to generate
        behavior code in the runnable component.
        @type behavior_profile: lems.model.behavior.Behavior

        @raise SimBuildError: Raised when a time derivative expression refers
        to an undefined variable.

        @raise SimBuildError: Raised when there are invalid time
        specifications for the <Run> statement.

        @raise SimBuildError: Raised when the component reference for <Run>
        cannot be resolved.
        """

        context = component.context
        regime = behavior_profile.default_regime

        for svn in regime.state_variables:
            sv = regime.state_variables[svn]
            runnable.add_instance_variable(sv.name, 0)

        time_step_code = []
        for tdn in regime.time_derivatives:
            if tdn not in regime.state_variables:
                raise SimBuildError(("Time derivative for undefined state " "variable {0}").format(tdn))

            td = regime.time_derivatives[tdn]
            time_step_code += [
                "self.{0} += dt * ({1})".format(td.variable, self.build_expression_from_tree(td.expression_tree))
            ]
        runnable.add_method("update_state_variables", ["self", "dt"], time_step_code)

        pre_event_handler_code = []
        post_event_handler_code = []
        for eh in regime.event_handlers:
            if eh.type == EventHandler.ON_CONDITION:
                post_event_handler_code += self.build_event_handler(eh)
            else:
                pre_event_handler_code += self.build_event_handler(eh)
        runnable.add_method("run_preprocessing_event_handlers", ["self"], pre_event_handler_code)
        runnable.add_method("run_postprocessing_event_handlers", ["self"], post_event_handler_code)

        for rn in regime.runs:
            run = regime.runs[rn]
            c = context.lookup_component_ref(run.component)
            if c != None:
                target = self.build_runnable(c, self)
                self.sim.add_runnable(c.id, target)
                self.current_record_target = target
                time_step = context.lookup_parameter(run.increment)
                time_total = context.lookup_parameter(run.total)
                if time_step != None and time_total != None:
                    target.configure_time(time_step.numeric_value, time_total.numeric_value)
            else:
                raise SimBuildError(("Invalid component reference {0} in " "<Run>").format(c.id))

    def convert_op(self, op):
        """
        Converts NeuroML arithmetic/logical operators to python equivalents.

        @param op: NeuroML operator
        @type op: string

        @return: Python operator
        @rtype: string
        """

        if op == ".gt.":
            return ">"
        elif op == ".ge.":
            return ">="
        elif op == ".lt.":
            return "<"
        elif op == ".le.":
            return "<="
        elif op == ".eq.":
            return "=="
        elif op == ".ne.":
            return "!="
        else:
            return op

    def build_expression_from_tree(self, tree_node):
        """
        Recursively builds a Python expression from a parsed expression tree.

        @param tree_node: Root node for the tree from which the expression
        is to be built.
        @type tree_node: lems.parser.expr.ExprNode

        @return: Generated Python expression.
        @rtype: string
        """

        if tree_node.type == ExprNode.VALUE:
            if tree_node.value[0].isalpha():
                return "self.{0}_shadow".format(tree_node.value)
            else:
                return tree_node.value
        else:
            return "({0}) {1} ({2})".format(
                self.build_expression_from_tree(tree_node.left),
                self.convert_op(tree_node.op),
                self.build_expression_from_tree(tree_node.right),
            )

    def build_event_handler(self, event_handler):
        """
        Build event handler code.

        @param event_handler: Event handler object
        @type event_handler: lems.model.behavior.EventHandler

        @return: Generated event handler code.
        @rtype: list(string)
        """

        if event_handler.type == EventHandler.ON_CONDITION:
            return self.build_on_condition(event_handler)
        elif event_handler.type == EventHandler.ON_EVENT:
            return self.build_on_event(event_handler)
        else:
            return []

    def build_on_condition(self, on_condition):
        """
        Build OnCondition event handler code.

        @param on_condition: OnCondition event handler object
        @type on_condition: lems.model.behavior.OnCondition

        @return: Generated OnCondition code
        @rtype: list(string)
        """

        on_condition_code = []

        on_condition_code += ["if {0}:".format(self.build_expression_from_tree(on_condition.expression_tree))]

        for action in on_condition.actions:
            on_condition_code += ["    " + self.build_action(action)]

        return on_condition_code

    def build_on_event(self, on_event):
        """
        Build OnEvent event handler code.

        @param on_event: OnEvent event handler object
        @type on_event: lems.model.behavior.OnEvent

        @return: Generated OnEvent code
        @rtype: list(string)
        """

        on_event_code = []

        on_event_code += [
            "count = self.event_in_counters['{0}']".format(on_event.port),
            "while count > 0:",
            "    count -= 1",
        ]
        for action in on_event.actions:
            on_event_code += ["    " + self.build_action(action)]
        on_event_code += ["self.event_in_counters['{0}'] = 0".format(on_event.port)]

        return on_event_code

    def build_action(self, action):
        """
        Build event handler action code.

        @param action: Event handler action object
        @type action: lems.model.behavior.Action

        @return: Generated action code
        @rtype: string
        """

        if action.type == Action.STATE_ASSIGNMENT:
            return self.build_state_assignment(action)
        if action.type == Action.EVENT_OUT:
            return self.build_event_out(action)
        else:
            return ""

    def build_state_assignment(self, state_assignment):
        """
        Build state assignment code.

        @param state_assignment: State assignment object
        @type state_assignment: lems.model.behavior.StateAssignment

        @return: Generated state assignment code
        @rtype: string
        """

        return "self.{0} = {1}".format(
            state_assignment.variable, self.build_expression_from_tree(state_assignment.expression_tree)
        )

    def build_event_out(self, event_out):
        """
        Build event out code.

        @param event_out: event out object
        @type event_out: lems.model.behavior.StateAssignment

        @return: Generated event out code
        @rtype: string
        """

        event_out_code = "for c in self.event_out_callbacks['{0}']: c()".format(event_out.port)

        return event_out_code

    def add_recording_behavior(self, component, runnable, behavior_profile):
        """
        Adds recording-related behavior to a runnable component based on
        the behavior specifications in the component model.

        @param component: Component model containing behavior specifications.
        @type component: lems.model.component.Component

        @param runnable: Runnable component to which behavior is to be added.
        @type runnable: lems.sim.runnable.Runnable

        @param behavior_profile: The behavior profile to be used to generate
        behavior code in the runnable component.
        @type behavior_profile: lems.model.behavior.Behavior

        @raise SimBuildError: Raised when a target for recording could not be
        found.
        """

        context = component.context
        regime = behavior_profile.default_regime

        for rn in regime.records:
            rec = regime.records[rn]
            if self.current_record_target == None:
                raise SimBuildError("No target available for " "recording variables")
            self.current_record_target.add_variable_recorder(rec.quantity)