def __init__(self, sedml_experiment):
        """
        :rtype: None
        :raises: RuntimeError
        """
        self.name                = ''
        self.network             = None
        self.reportingInterval   = 0.0
        self.timeHorizon         = 0.0        
        self.log                 = daeLogs.daePythonStdOutLog()
        self.datareporter        = pyDataReporting.daeNoOpDataReporter() #pyDataReporting.daeTCPIPDataReporter()
        self.simulations         = []
        self.events_heap         = []
        self.average_firing_rate = {}
        self.raster_plot_data    = []
        self.pathParser          = CanonicalNameParser()
        self.report_variables    = {}
        self.output_plots        = []
        self.number_of_equations = 0
        self.number_of_neurones  = 0
        self.number_of_synapses  = 0
        self.spike_count         = 0
        self.minimal_delay       = 1.0E10

        heapify(self.events_heap)
        
        # Create daetoolsPointNeuroneNetwork object and the simulation runtime information
        self.processSEDMLExperiment(sedml_experiment)
        
        # Connect the DataReporter
        simName = self.name + strftime(" [%d.%m.%Y %H.%M.%S]", localtime())
        if not self.datareporter.Connect("", simName):
            raise RuntimeError('Cannot connect the data reporter')
        
        # Set the random number generators of the daetoolsComponentSetup
        daetoolsComponentSetup._random_number_generators = self.network.randomNumberGenerators

        # Setup neurones
        try:
            self.log.Enabled = False
            
            for group_name, group in self.network._groups.iteritems():
                for projection_name, projection in group._projections.iteritems():
                    if projection.minimal_delay < self.minimal_delay:
                        self.minimal_delay = projection.minimal_delay
                    
                for population_name, population in group._populations.iteritems():
                    print("Creating simulations for: {0}...".format(population_name))
                    for neurone in population.neurones:
                        simulation = daetoolsPointNeuroneSimulation(neurone, population._parameters, {})
                        neurone.events_heap = self.events_heap
                        self.simulations.append(simulation)
                        self.number_of_neurones += 1
                        for (synapse, params) in neurone.incoming_synapses:
                            self.number_of_synapses += synapse.Nitems                            
            
            self.simulations.sort()
            
            if self.minimal_delay < self.reportingInterval:
                raise RuntimeError('The minimal delay ({0}s) is greater than the reporting interval ({1}s)'.format(self.minimal_delay, self.reportingInterval))
            
        except:
            raise
        
        finally:
            self.log.Enabled = True
    def __init__(self, sedml_experiment):
        """
        :rtype: None
        :raises: RuntimeError
        """
        self.name = ''
        self.network = None
        self.reportingInterval = 0.0
        self.timeHorizon = 0.0
        self.log = daeLogs.daePythonStdOutLog()
        self.datareporter = pyDataReporting.daeNoOpDataReporter(
        )  #pyDataReporting.daeTCPIPDataReporter()
        self.simulations = []
        self.events_heap = []
        self.average_firing_rate = {}
        self.raster_plot_data = []
        self.pathParser = CanonicalNameParser()
        self.report_variables = {}
        self.output_plots = []
        self.number_of_equations = 0
        self.number_of_neurones = 0
        self.number_of_synapses = 0
        self.spike_count = 0
        self.minimal_delay = 1.0E10

        heapify(self.events_heap)

        # Create daetoolsPointNeuroneNetwork object and the simulation runtime information
        self.processSEDMLExperiment(sedml_experiment)

        # Connect the DataReporter
        simName = self.name + strftime(" [%d.%m.%Y %H.%M.%S]", localtime())
        if not self.datareporter.Connect("", simName):
            raise RuntimeError('Cannot connect the data reporter')

        # Set the random number generators of the daetoolsComponentSetup
        daetoolsComponentSetup._random_number_generators = self.network.randomNumberGenerators

        # Setup neurones
        try:
            self.log.Enabled = False

            for group_name, group in self.network._groups.iteritems():
                for projection_name, projection in group._projections.iteritems(
                ):
                    if projection.minimal_delay < self.minimal_delay:
                        self.minimal_delay = projection.minimal_delay

                for population_name, population in group._populations.iteritems(
                ):
                    print("Creating simulations for: {0}...".format(
                        population_name))
                    for neurone in population.neurones:
                        simulation = daetoolsPointNeuroneSimulation(
                            neurone, population._parameters, {})
                        neurone.events_heap = self.events_heap
                        self.simulations.append(simulation)
                        self.number_of_neurones += 1
                        for (synapse, params) in neurone.incoming_synapses:
                            self.number_of_synapses += synapse.Nitems

            self.simulations.sort()

            if self.minimal_delay < self.reportingInterval:
                raise RuntimeError(
                    'The minimal delay ({0}s) is greater than the reporting interval ({1}s)'
                    .format(self.minimal_delay, self.reportingInterval))

        except:
            raise

        finally:
            self.log.Enabled = True
class daetoolsPointNeuroneNetworkSimulation(object):
    """
    Simulates a network of neurones (specified by a daetoolsPointNeuroneNetworkSimulation object) 
    and produces outputs according to information in a SED-ML experiment object.
    The daetoolsPointNeuroneNetworkSimulation object can be obtained from the SED-ML experiment object.
    """
    def __init__(self, sedml_experiment):
        """
        :rtype: None
        :raises: RuntimeError
        """
        self.name                = ''
        self.network             = None
        self.reportingInterval   = 0.0
        self.timeHorizon         = 0.0        
        self.log                 = daeLogs.daePythonStdOutLog()
        self.datareporter        = pyDataReporting.daeNoOpDataReporter() #pyDataReporting.daeTCPIPDataReporter()
        self.simulations         = []
        self.events_heap         = []
        self.average_firing_rate = {}
        self.raster_plot_data    = []
        self.pathParser          = CanonicalNameParser()
        self.report_variables    = {}
        self.output_plots        = []
        self.number_of_equations = 0
        self.number_of_neurones  = 0
        self.number_of_synapses  = 0
        self.spike_count         = 0
        self.minimal_delay       = 1.0E10

        heapify(self.events_heap)
        
        # Create daetoolsPointNeuroneNetwork object and the simulation runtime information
        self.processSEDMLExperiment(sedml_experiment)
        
        # Connect the DataReporter
        simName = self.name + strftime(" [%d.%m.%Y %H.%M.%S]", localtime())
        if not self.datareporter.Connect("", simName):
            raise RuntimeError('Cannot connect the data reporter')
        
        # Set the random number generators of the daetoolsComponentSetup
        daetoolsComponentSetup._random_number_generators = self.network.randomNumberGenerators

        # Setup neurones
        try:
            self.log.Enabled = False
            
            for group_name, group in self.network._groups.iteritems():
                for projection_name, projection in group._projections.iteritems():
                    if projection.minimal_delay < self.minimal_delay:
                        self.minimal_delay = projection.minimal_delay
                    
                for population_name, population in group._populations.iteritems():
                    print("Creating simulations for: {0}...".format(population_name))
                    for neurone in population.neurones:
                        simulation = daetoolsPointNeuroneSimulation(neurone, population._parameters, {})
                        neurone.events_heap = self.events_heap
                        self.simulations.append(simulation)
                        self.number_of_neurones += 1
                        for (synapse, params) in neurone.incoming_synapses:
                            self.number_of_synapses += synapse.Nitems                            
            
            self.simulations.sort()
            
            if self.minimal_delay < self.reportingInterval:
                raise RuntimeError('The minimal delay ({0}s) is greater than the reporting interval ({1}s)'.format(self.minimal_delay, self.reportingInterval))
            
        except:
            raise
        
        finally:
            self.log.Enabled = True

    def initializeAndSolveInitial(self):
        try:
            self.log.Enabled = False
            
            self.number_of_equations = 0
            for i, simulation in enumerate(self.simulations):
                print 'Setting up the neurone {0} out of {1} (total number of equations: {2})...'.format(i+1, self.number_of_neurones, self.number_of_equations), "\r",
                sys.stdout.flush()
                
                simulation.init(self.log, self.datareporter, self.reportingInterval, self.timeHorizon)
                self.number_of_equations += simulation.daesolver.NumberOfVariables
                
                simulation.SolveInitial()
                simulation.CleanUpSetupData()
        
        except:
            raise
        
        finally:
            print '\n'
            self.log.Enabled = True
        
        # Run the garbage collector to free some memory
        #print('garbage before collect:\n'.format(gc.garbage))
        collected = gc.collect()
        #print "Garbage collector: collected %d objects." % (collected)  
        #print('garbage after collect:\n'.format(gc.garbage))
    
    def run(self):
        # First create the reporting times list
        reporting_times = numpy.arange(self.reportingInterval, self.timeHorizon, self.reportingInterval)
        
        prev_time = 0.0
        # Iterate over the queue. The (delayed) events will be added to the queue as they are trigerred.
        for next_time in reporting_times:
            self.log.Message("Simulating from {0:.5f}s to {1:.5f}s...".format(prev_time, next_time), 0)
            
            try:
                while True:
                    # Get the first item from the heap
                    (t_event, inlet_event_port, target_neurone) = heappop(self.events_heap)
                    
                    # If out of the interval put it back
                    if t_event > next_time:
                        heappush(self.events_heap, (t_event, inlet_event_port, target_neurone))
                        break
                    
                    #print('{0} --> {1}s (trigger event on {2})'.format(target_neurone.Name, t_event, inlet_event_port.CanonicalName))
                    
                    # Integrate until next event time and report the data if there is a discontinuity
                    target_neurone.simulation.IntegrateUntilTime(t_event, pyActivity.eDoNotStopAtDiscontinuity, True)
                    
                    # Trigger the event and reinitialize the system
                    inlet_event_port.ReceiveEvent(t_event)
                    target_neurone.simulation.Reinitialize()
                    
                    self.spike_count += 1
            
            except IndexError:
                pass
                
            # Integrate each neurone until the *next_time* is reached and report the data
            for simulation in self.simulations:
                #print('{0}........ {1} {2} {3}'.format(simulation.m.Name, 
                #                                       simulation.CurrentTime, 
                #                                       '<' if simulation.CurrentTime < next_time else '=' , 
                #                                       next_time))
                if simulation.CurrentTime < next_time:
                    simulation.IntegrateUntilTime(next_time, pyActivity.eDoNotStopAtDiscontinuity, True)
                simulation.ReportData(next_time)
            
            # Set the progress
            self.log.SetProgress(100.0 * next_time / self.timeHorizon)
            prev_time = next_time
                    
        print('Simulation has ended successfuly.')
        print('Processing the results...')
        
        # Finally, process the results (generate 2D plots, raster plots, and other voodoo-mojo stuff)
        self.processResults()
    
    def processSEDMLExperiment(self, sedml_experiment):
        if len(sedml_experiment.tasks) != 1:
            raise RuntimeError('The number of SED-ML tasks must be one')
        sedml_task = sedml_experiment.tasks[0]
 
        self.name    = sedml_task.simulation.name
        ul_model     = sedml_task.model.getUserLayerModel()
        self.network = daetoolsPointNeuroneNetwork(ul_model)
        
        self.reportingInterval = float(sedml_task.simulation.outputEndTime - sedml_task.simulation.outputStartTime) / (sedml_task.simulation.numberOfPoints - 1)
        self.timeHorizon       = float(sedml_task.simulation.outputEndTime)
        
        for data_generator in sedml_experiment.data_generators:
            for variable in data_generator.variables:
                if variable.target:
                    try:
                        items = self.pathParser.parse(str(variable.target))
                    except Exception as e:
                        RuntimeError('Invalid SED-ML variable name: {0}'.format(variable.target))
                    
                    if len(items) != 3:
                        raise RuntimeError('Invalid SED-ML variable name: {0}'.format(variable.target))
                    
                    if items[0].Type != pathItem.typeID:
                        raise RuntimeError('Invalid SED-ML variable name: {0}'.format(variable.target))
                    group = self.network.getGroup(items[0].Name)       
                    
                    if items[1].Type != pathItem.typeIndexedID:
                        raise RuntimeError('Invalid SED-ML variable name: {0}'.format(variable.target))
                    population = group.getPopulation(items[1].Name)  
                    neurone = population.getNeurone(items[1].Index)
                    
                    if items[2].Type != pathItem.typeID:
                        raise RuntimeError('Invalid SED-ML variable name: {0}'.format(variable.target))
                    variable_name = items[2].Name
                    
                    if variable_name in neurone.nineml_aliases:
                        dae_variable = neurone.nineml_aliases[variable_name]
                    
                    elif variable_name in neurone.nineml_variables:
                        dae_variable = neurone.nineml_variables[variable_name]
                    
                    elif variable_name in neurone.nineml_inlet_ports:
                        dae_variable = neurone.nineml_inlet_ports[variable_name]
                    
                    elif variable_name in neurone.nineml_reduce_ports:
                        dae_variable = neurone.nineml_reduce_ports[variable_name]
                    
                    else:
                        raise RuntimeError('Cannot find SED-ML variable: {0}'.format(variable.target))
                        
                    self.report_variables[variable.target] = dae_variable
                    dae_variable.ReportingOn = True
                    
                elif variable.symbol:
                    if variable.symbol == 'urn:sedml:symbol:time':
                        self.report_variables['time'] = None
                    else:
                        raise RuntimeError('Unsupported SED-ML symbol: {0}'.format(variable.symbol))
                
                else:
                    raise RuntimeError('Both SED-ML variable symbol and target are None')
              
        for output in sedml_experiment.outputs:
            variable_names  = []
            x_label         = 'Time, s'
            y_labels        = []
            x_log           = False
            y_log           = False
            
            for curve in output.curves:
                if curve.logX:
                    x_log = True
                if curve.logY:
                    y_log = True
                
                x_dg = curve.xDataReference
                y_dg = curve.yDataReference
                
                if (len(x_dg.variables) != 1) or (not x_dg.variables[0].symbol) or (x_dg.variables[0].symbol != 'urn:sedml:symbol:time'):
                    raise RuntimeError('The number of variables in data referrence: {0} must be one with the symbol = urn:sedml:symbol:time'.format(x_dg.id))
                
                for variable in y_dg.variables:
                    if not variable.target:
                        raise RuntimeError('SED-ML variable: {0} target is invalid'.format(variable.name))
                    if not variable.target in self.report_variables:
                        raise RuntimeError('SED-ML variable {0} does not exist in the network'.format(variable.name))
                    
                    variable_names.append(self.report_variables[variable.target].CanonicalName)
                    y_labels.append(variable.name)
            
            self.output_plots.append( (output.name, variable_names, x_label, y_labels, x_log, y_log) )
        
    def processResults(self):
        results_dir = '{0} {1}'.format(self.name, strftime("[%d.%m.%Y %H.%M.%S]", localtime()))
        os.mkdir(results_dir)
        
        print('  Total number of equations: {0:>10d}'.format(self.number_of_equations))
        print('  Total number of neurones:  {0:>10d}'.format(self.number_of_neurones))
        print('  Total number of synapses:  {0:>10d}'.format(self.number_of_synapses))
        print('  Total number of spikes:    {0:>10d}'.format(self.spike_count))
        print('  Minimal network delay:    {0:>10.6f}s'.format(self.minimal_delay))
        
        # 1. Create a raster plot file (.ras)
        neurone_index = 0
        population_events = {}
        for group_name, group in self.network._groups.iteritems():
            for population_name, population in group._populations.iteritems():
                count = 0
                events = []
                for neurone in population.neurones:
                    event_port = neurone.getOutletEventPort()
                    count += len(event_port.Events)
                    for (t, data) in event_port.Events:
                        self.raster_plot_data.append((t, neurone_index))
                        events.append((t, neurone_index))
                    neurone_index += 1
                
                population_events[population_name] = sorted(events)
                self.average_firing_rate[population_name] = count / (self.timeHorizon * len(population.neurones))
                print('  [{0}] average firing rate: {1:.3f} Hz'.format(population_name, self.average_firing_rate[population_name]))
        
        self.raster_plot_data.sort()
        self.createRasterFile(os.path.join(results_dir, 'raster-plot.ras'), self.raster_plot_data, self.number_of_neurones)
        
        # 2. Create a raster plot image (.png)
        pop_times   = []
        pop_indexes = []
        pop_names   = []
        for i, (population_name, events) in enumerate(population_events.iteritems()):
            if len(events) > 0:
                pop_times.append( [item[0] for item in events] )
                pop_indexes.append( [item[1] for item in events] )
                pop_names.append( population_name )
        self.createRasterPlot(os.path.join(results_dir, 'raster-plot.png'), pop_times, pop_indexes, pop_names)
        
        # 3. Create 2D plots (.png)
        for (name, variable_names, x_label, y_labels, x_log, y_log) in self.output_plots:
            x_values = []
            y_values = []
            for var_canonical_name in variable_names:
                for variable in self.datareporter.Process.Variables:
                    if var_canonical_name == variable.Name:
                        x_values.append(variable.TimeValues)
                        y_values.append(variable.Values.reshape(len(variable.Values)))
            
            self.create2DPlot(os.path.join(results_dir, '{0}.png'.format(name)), x_values, y_values, x_label, y_labels, x_log, y_log)

    @staticmethod
    def createRasterFile(filename, data, n):
        """
        :param filename: string
        :param data: list of floats
        :param n: integer
        
        :rtype: None
        :raises: IOError
        """
        f = open(filename, "w")
        f.write('# size = {0}\n'.format(n))
        f.write('# first_index = {0}\n'.format(0))
        f.write('# first_id = {0}\n'.format(0))
        f.write('# n = {0}\n'.format(len(data)))
        f.write('# variable = spikes\n')
        f.write('# last_id = {0}\n'.format(n - 1))
        f.write('# dt = {0}\n'.format(0.0))
        f.write('# label = {0}\n'.format('spikes'))
        for t, index in data:
            f.write('%.14e\t%d\n' % (t, index))
        f.close()

    @staticmethod
    def createRasterPlot(filename, pop_times, pop_indexes, pop_names):
        """
        :param filename: string
        :param pop_times: 2D list (list of float lists)
        :param pop_indexes: 2D list (list of integer lists)
        :param pop_names: string list
        
        :rtype: None
        :raises: ValueError, IOError
        """
        font = {'family' : 'serif',
                'weight' : 'normal',
                'size'   : 8}
        matplotlib.rc('font', **font)
        params = {'axes.labelsize' : 10,
                  'legend.fontsize': 5,
                  'text.fontsize'  : 8,
                  'xtick.labelsize': 8,
                  'ytick.labelsize': 8,
                  'text.usetex':     False}
        matplotlib.rcParams.update(params)

        colors = ['red', 'blue', 'green', 'black', 'c', 'm', 'k', 'y']
        
        figure = Figure(figsize=(8, 6))
        canvas = FigureCanvas(figure)
        axes = figure.add_subplot(111)
        
        axes.set_xlabel('Time, s')
        axes.set_ylabel('Neurones')
        axes.grid(True, linestyle = '-', color = '0.75')
        
        i = 0
        min_times   = 1.0E10
        max_times   = 0.0
        min_indexes = 1E10
        max_indexes = 0
        for (times, indexes, name) in zip(pop_times, pop_indexes, pop_names):
            color = colors[i % len(colors)]
            if len(times) > 0:
                axes.scatter(times, indexes, label = name, marker = 's', s = 1, color = color)
                
                min_times   = min(min_times,   min(times))
                max_times   = max(max_times,   max(times))
                min_indexes = min(min_indexes, min(indexes))
                max_indexes = max(max_indexes, max(indexes)) + 1
            i += 1
        
        box = axes.get_position()
        axes.set_position([box.x0, box.y0, box.width * 0.9, box.height])
        axes.legend(loc = 'center left', bbox_to_anchor = (1, 0.5), ncol = 1, scatterpoints = 1, fancybox = True)
        
        axes.set_xbound(lower = min_times,   upper = max_times)
        axes.set_ybound(lower = min_indexes, upper = max_indexes)
        
        canvas.print_figure(filename, dpi = 300)        
    
    @staticmethod
    def create2DPlot(filename, x_values, y_values, x_label, y_labels, x_log, y_log):
        """
        :param filename: string
        :param x_values: 2D list (list of float lists)
        :param y_values: 2D list (list of float lists)
        :param x_label: string
        :param y_labels: string list
        :param x_log: bool
        :param y_log: bool
        
        :rtype: None
        :raises: ValueError, IOError
        """
        font = {'family' : 'serif',
                'weight' : 'normal',
                'size'   : 8}
        matplotlib.rc('font', **font)
        params = {'axes.labelsize':  9,
                  'legend.fontsize': 6,
                  'text.fontsize':   8,
                  'xtick.labelsize': 8,
                  'ytick.labelsize': 8,
                  'text.usetex':     False}
        matplotlib.rcParams.update(params)
        
        colors = ['blue', 'red', 'green', 'black', 'c', 'm', 'k', 'y']

        figure = Figure(figsize=(8, 6))
        canvas = FigureCanvas(figure)
        axes = figure.add_subplot(111)
        axes.set_xlabel(x_label)
        axes.grid(True, linestyle = '-', color = '0.75')
        
        i = 0
        for (x, y, label) in zip(x_values, y_values, y_labels):
            color = colors[i % len(colors)]
            axes.plot(x, y, label = label, 
                            color = color, 
                            linewidth = 0.5, 
                            linestyle = 'solid', 
                            marker  ='', 
                            markersize = 0, 
                            markerfacecolor = color, 
                            markeredgecolor = color)
            i += 1
            
        axes.legend(loc = 0, ncol = 1, fancybox = True)
        
        if x_log: 
            axes.set_xscale('log')
        else:
            axes.set_xscale('linear')
        if y_log:
            axes.set_yscale('log')
        else:
            axes.set_yscale('linear')
        
        canvas.print_figure(filename, dpi = 300)        
        
    def finalize(self):
        for simulation in self.simulations:
            simulation.Finalize()
class daetoolsPointNeuroneNetworkSimulation(object):
    """
    Simulates a network of neurones (specified by a daetoolsPointNeuroneNetworkSimulation object) 
    and produces outputs according to information in a SED-ML experiment object.
    The daetoolsPointNeuroneNetworkSimulation object can be obtained from the SED-ML experiment object.
    """
    def __init__(self, sedml_experiment):
        """
        :rtype: None
        :raises: RuntimeError
        """
        self.name = ''
        self.network = None
        self.reportingInterval = 0.0
        self.timeHorizon = 0.0
        self.log = daeLogs.daePythonStdOutLog()
        self.datareporter = pyDataReporting.daeNoOpDataReporter(
        )  #pyDataReporting.daeTCPIPDataReporter()
        self.simulations = []
        self.events_heap = []
        self.average_firing_rate = {}
        self.raster_plot_data = []
        self.pathParser = CanonicalNameParser()
        self.report_variables = {}
        self.output_plots = []
        self.number_of_equations = 0
        self.number_of_neurones = 0
        self.number_of_synapses = 0
        self.spike_count = 0
        self.minimal_delay = 1.0E10

        heapify(self.events_heap)

        # Create daetoolsPointNeuroneNetwork object and the simulation runtime information
        self.processSEDMLExperiment(sedml_experiment)

        # Connect the DataReporter
        simName = self.name + strftime(" [%d.%m.%Y %H.%M.%S]", localtime())
        if not self.datareporter.Connect("", simName):
            raise RuntimeError('Cannot connect the data reporter')

        # Set the random number generators of the daetoolsComponentSetup
        daetoolsComponentSetup._random_number_generators = self.network.randomNumberGenerators

        # Setup neurones
        try:
            self.log.Enabled = False

            for group_name, group in self.network._groups.iteritems():
                for projection_name, projection in group._projections.iteritems(
                ):
                    if projection.minimal_delay < self.minimal_delay:
                        self.minimal_delay = projection.minimal_delay

                for population_name, population in group._populations.iteritems(
                ):
                    print("Creating simulations for: {0}...".format(
                        population_name))
                    for neurone in population.neurones:
                        simulation = daetoolsPointNeuroneSimulation(
                            neurone, population._parameters, {})
                        neurone.events_heap = self.events_heap
                        self.simulations.append(simulation)
                        self.number_of_neurones += 1
                        for (synapse, params) in neurone.incoming_synapses:
                            self.number_of_synapses += synapse.Nitems

            self.simulations.sort()

            if self.minimal_delay < self.reportingInterval:
                raise RuntimeError(
                    'The minimal delay ({0}s) is greater than the reporting interval ({1}s)'
                    .format(self.minimal_delay, self.reportingInterval))

        except:
            raise

        finally:
            self.log.Enabled = True

    def initializeAndSolveInitial(self):
        try:
            self.log.Enabled = False

            self.number_of_equations = 0
            for i, simulation in enumerate(self.simulations):
                print 'Setting up the neurone {0} out of {1} (total number of equations: {2})...'.format(
                    i + 1, self.number_of_neurones,
                    self.number_of_equations), "\r",
                sys.stdout.flush()

                simulation.init(self.log, self.datareporter,
                                self.reportingInterval, self.timeHorizon)
                self.number_of_equations += simulation.daesolver.NumberOfVariables

                simulation.SolveInitial()
                simulation.CleanUpSetupData()

        except:
            raise

        finally:
            print '\n'
            self.log.Enabled = True

        # Run the garbage collector to free some memory
        #print('garbage before collect:\n'.format(gc.garbage))
        collected = gc.collect()
        #print "Garbage collector: collected %d objects." % (collected)
        #print('garbage after collect:\n'.format(gc.garbage))

    def run(self):
        # First create the reporting times list
        reporting_times = numpy.arange(self.reportingInterval,
                                       self.timeHorizon,
                                       self.reportingInterval)

        prev_time = 0.0
        # Iterate over the queue. The (delayed) events will be added to the queue as they are trigerred.
        for next_time in reporting_times:
            self.log.Message(
                "Simulating from {0:.5f}s to {1:.5f}s...".format(
                    prev_time, next_time), 0)

            try:
                while True:
                    # Get the first item from the heap
                    (t_event, inlet_event_port,
                     target_neurone) = heappop(self.events_heap)

                    # If out of the interval put it back
                    if t_event > next_time:
                        heappush(self.events_heap,
                                 (t_event, inlet_event_port, target_neurone))
                        break

                    #print('{0} --> {1}s (trigger event on {2})'.format(target_neurone.Name, t_event, inlet_event_port.CanonicalName))

                    # Integrate until next event time and report the data if there is a discontinuity
                    target_neurone.simulation.IntegrateUntilTime(
                        t_event, pyActivity.eDoNotStopAtDiscontinuity, True)

                    # Trigger the event and reinitialize the system
                    inlet_event_port.ReceiveEvent(t_event)
                    target_neurone.simulation.Reinitialize()

                    self.spike_count += 1

            except IndexError:
                pass

            # Integrate each neurone until the *next_time* is reached and report the data
            for simulation in self.simulations:
                #print('{0}........ {1} {2} {3}'.format(simulation.m.Name,
                #                                       simulation.CurrentTime,
                #                                       '<' if simulation.CurrentTime < next_time else '=' ,
                #                                       next_time))
                if simulation.CurrentTime < next_time:
                    simulation.IntegrateUntilTime(
                        next_time, pyActivity.eDoNotStopAtDiscontinuity, True)
                simulation.ReportData(next_time)

            # Set the progress
            self.log.SetProgress(100.0 * next_time / self.timeHorizon)
            prev_time = next_time

        print('Simulation has ended successfuly.')
        print('Processing the results...')

        # Finally, process the results (generate 2D plots, raster plots, and other voodoo-mojo stuff)
        self.processResults()

    def processSEDMLExperiment(self, sedml_experiment):
        if len(sedml_experiment.tasks) != 1:
            raise RuntimeError('The number of SED-ML tasks must be one')
        sedml_task = sedml_experiment.tasks[0]

        self.name = sedml_task.simulation.name
        ul_model = sedml_task.model.getUserLayerModel()
        self.network = daetoolsPointNeuroneNetwork(ul_model)

        self.reportingInterval = float(
            sedml_task.simulation.outputEndTime -
            sedml_task.simulation.outputStartTime) / (
                sedml_task.simulation.numberOfPoints - 1)
        self.timeHorizon = float(sedml_task.simulation.outputEndTime)

        for data_generator in sedml_experiment.data_generators:
            for variable in data_generator.variables:
                if variable.target:
                    try:
                        items = self.pathParser.parse(str(variable.target))
                    except Exception as e:
                        RuntimeError(
                            'Invalid SED-ML variable name: {0}'.format(
                                variable.target))

                    if len(items) != 3:
                        raise RuntimeError(
                            'Invalid SED-ML variable name: {0}'.format(
                                variable.target))

                    if items[0].Type != pathItem.typeID:
                        raise RuntimeError(
                            'Invalid SED-ML variable name: {0}'.format(
                                variable.target))
                    group = self.network.getGroup(items[0].Name)

                    if items[1].Type != pathItem.typeIndexedID:
                        raise RuntimeError(
                            'Invalid SED-ML variable name: {0}'.format(
                                variable.target))
                    population = group.getPopulation(items[1].Name)
                    neurone = population.getNeurone(items[1].Index)

                    if items[2].Type != pathItem.typeID:
                        raise RuntimeError(
                            'Invalid SED-ML variable name: {0}'.format(
                                variable.target))
                    variable_name = items[2].Name

                    if variable_name in neurone.nineml_aliases:
                        dae_variable = neurone.nineml_aliases[variable_name]

                    elif variable_name in neurone.nineml_variables:
                        dae_variable = neurone.nineml_variables[variable_name]

                    elif variable_name in neurone.nineml_inlet_ports:
                        dae_variable = neurone.nineml_inlet_ports[
                            variable_name]

                    elif variable_name in neurone.nineml_reduce_ports:
                        dae_variable = neurone.nineml_reduce_ports[
                            variable_name]

                    else:
                        raise RuntimeError(
                            'Cannot find SED-ML variable: {0}'.format(
                                variable.target))

                    self.report_variables[variable.target] = dae_variable
                    dae_variable.ReportingOn = True

                elif variable.symbol:
                    if variable.symbol == 'urn:sedml:symbol:time':
                        self.report_variables['time'] = None
                    else:
                        raise RuntimeError(
                            'Unsupported SED-ML symbol: {0}'.format(
                                variable.symbol))

                else:
                    raise RuntimeError(
                        'Both SED-ML variable symbol and target are None')

        for output in sedml_experiment.outputs:
            variable_names = []
            x_label = 'Time, s'
            y_labels = []
            x_log = False
            y_log = False

            for curve in output.curves:
                if curve.logX:
                    x_log = True
                if curve.logY:
                    y_log = True

                x_dg = curve.xDataReference
                y_dg = curve.yDataReference

                if (len(x_dg.variables) != 1
                    ) or (not x_dg.variables[0].symbol) or (
                        x_dg.variables[0].symbol != 'urn:sedml:symbol:time'):
                    raise RuntimeError(
                        'The number of variables in data referrence: {0} must be one with the symbol = urn:sedml:symbol:time'
                        .format(x_dg.id))

                for variable in y_dg.variables:
                    if not variable.target:
                        raise RuntimeError(
                            'SED-ML variable: {0} target is invalid'.format(
                                variable.name))
                    if not variable.target in self.report_variables:
                        raise RuntimeError(
                            'SED-ML variable {0} does not exist in the network'
                            .format(variable.name))

                    variable_names.append(
                        self.report_variables[variable.target].CanonicalName)
                    y_labels.append(variable.name)

            self.output_plots.append(
                (output.name, variable_names, x_label, y_labels, x_log, y_log))

    def processResults(self):
        results_dir = '{0} {1}'.format(
            self.name, strftime("[%d.%m.%Y %H.%M.%S]", localtime()))
        os.mkdir(results_dir)

        print('  Total number of equations: {0:>10d}'.format(
            self.number_of_equations))
        print('  Total number of neurones:  {0:>10d}'.format(
            self.number_of_neurones))
        print('  Total number of synapses:  {0:>10d}'.format(
            self.number_of_synapses))
        print('  Total number of spikes:    {0:>10d}'.format(self.spike_count))
        print('  Minimal network delay:    {0:>10.6f}s'.format(
            self.minimal_delay))

        # 1. Create a raster plot file (.ras)
        neurone_index = 0
        population_events = {}
        for group_name, group in self.network._groups.iteritems():
            for population_name, population in group._populations.iteritems():
                count = 0
                events = []
                for neurone in population.neurones:
                    event_port = neurone.getOutletEventPort()
                    count += len(event_port.Events)
                    for (t, data) in event_port.Events:
                        self.raster_plot_data.append((t, neurone_index))
                        events.append((t, neurone_index))
                    neurone_index += 1

                population_events[population_name] = sorted(events)
                self.average_firing_rate[population_name] = count / (
                    self.timeHorizon * len(population.neurones))
                print('  [{0}] average firing rate: {1:.3f} Hz'.format(
                    population_name,
                    self.average_firing_rate[population_name]))

        self.raster_plot_data.sort()
        self.createRasterFile(os.path.join(results_dir, 'raster-plot.ras'),
                              self.raster_plot_data, self.number_of_neurones)

        # 2. Create a raster plot image (.png)
        pop_times = []
        pop_indexes = []
        pop_names = []
        for i, (population_name,
                events) in enumerate(population_events.iteritems()):
            if len(events) > 0:
                pop_times.append([item[0] for item in events])
                pop_indexes.append([item[1] for item in events])
                pop_names.append(population_name)
        self.createRasterPlot(os.path.join(results_dir, 'raster-plot.png'),
                              pop_times, pop_indexes, pop_names)

        # 3. Create 2D plots (.png)
        for (name, variable_names, x_label, y_labels, x_log,
             y_log) in self.output_plots:
            x_values = []
            y_values = []
            for var_canonical_name in variable_names:
                for variable in self.datareporter.Process.Variables:
                    if var_canonical_name == variable.Name:
                        x_values.append(variable.TimeValues)
                        y_values.append(
                            variable.Values.reshape(len(variable.Values)))

            self.create2DPlot(
                os.path.join(results_dir, '{0}.png'.format(name)), x_values,
                y_values, x_label, y_labels, x_log, y_log)

    @staticmethod
    def createRasterFile(filename, data, n):
        """
        :param filename: string
        :param data: list of floats
        :param n: integer
        
        :rtype: None
        :raises: IOError
        """
        f = open(filename, "w")
        f.write('# size = {0}\n'.format(n))
        f.write('# first_index = {0}\n'.format(0))
        f.write('# first_id = {0}\n'.format(0))
        f.write('# n = {0}\n'.format(len(data)))
        f.write('# variable = spikes\n')
        f.write('# last_id = {0}\n'.format(n - 1))
        f.write('# dt = {0}\n'.format(0.0))
        f.write('# label = {0}\n'.format('spikes'))
        for t, index in data:
            f.write('%.14e\t%d\n' % (t, index))
        f.close()

    @staticmethod
    def createRasterPlot(filename, pop_times, pop_indexes, pop_names):
        """
        :param filename: string
        :param pop_times: 2D list (list of float lists)
        :param pop_indexes: 2D list (list of integer lists)
        :param pop_names: string list
        
        :rtype: None
        :raises: ValueError, IOError
        """
        font = {'family': 'serif', 'weight': 'normal', 'size': 8}
        matplotlib.rc('font', **font)
        params = {
            'axes.labelsize': 10,
            'legend.fontsize': 5,
            'text.fontsize': 8,
            'xtick.labelsize': 8,
            'ytick.labelsize': 8,
            'text.usetex': False
        }
        matplotlib.rcParams.update(params)

        colors = ['red', 'blue', 'green', 'black', 'c', 'm', 'k', 'y']

        figure = Figure(figsize=(8, 6))
        canvas = FigureCanvas(figure)
        axes = figure.add_subplot(111)

        axes.set_xlabel('Time, s')
        axes.set_ylabel('Neurones')
        axes.grid(True, linestyle='-', color='0.75')

        i = 0
        min_times = 1.0E10
        max_times = 0.0
        min_indexes = 1E10
        max_indexes = 0
        for (times, indexes, name) in zip(pop_times, pop_indexes, pop_names):
            color = colors[i % len(colors)]
            if len(times) > 0:
                axes.scatter(times,
                             indexes,
                             label=name,
                             marker='s',
                             s=1,
                             color=color)

                min_times = min(min_times, min(times))
                max_times = max(max_times, max(times))
                min_indexes = min(min_indexes, min(indexes))
                max_indexes = max(max_indexes, max(indexes)) + 1
            i += 1

        box = axes.get_position()
        axes.set_position([box.x0, box.y0, box.width * 0.9, box.height])
        axes.legend(loc='center left',
                    bbox_to_anchor=(1, 0.5),
                    ncol=1,
                    scatterpoints=1,
                    fancybox=True)

        axes.set_xbound(lower=min_times, upper=max_times)
        axes.set_ybound(lower=min_indexes, upper=max_indexes)

        canvas.print_figure(filename, dpi=300)

    @staticmethod
    def create2DPlot(filename, x_values, y_values, x_label, y_labels, x_log,
                     y_log):
        """
        :param filename: string
        :param x_values: 2D list (list of float lists)
        :param y_values: 2D list (list of float lists)
        :param x_label: string
        :param y_labels: string list
        :param x_log: bool
        :param y_log: bool
        
        :rtype: None
        :raises: ValueError, IOError
        """
        font = {'family': 'serif', 'weight': 'normal', 'size': 8}
        matplotlib.rc('font', **font)
        params = {
            'axes.labelsize': 9,
            'legend.fontsize': 6,
            'text.fontsize': 8,
            'xtick.labelsize': 8,
            'ytick.labelsize': 8,
            'text.usetex': False
        }
        matplotlib.rcParams.update(params)

        colors = ['blue', 'red', 'green', 'black', 'c', 'm', 'k', 'y']

        figure = Figure(figsize=(8, 6))
        canvas = FigureCanvas(figure)
        axes = figure.add_subplot(111)
        axes.set_xlabel(x_label)
        axes.grid(True, linestyle='-', color='0.75')

        i = 0
        for (x, y, label) in zip(x_values, y_values, y_labels):
            color = colors[i % len(colors)]
            axes.plot(x,
                      y,
                      label=label,
                      color=color,
                      linewidth=0.5,
                      linestyle='solid',
                      marker='',
                      markersize=0,
                      markerfacecolor=color,
                      markeredgecolor=color)
            i += 1

        axes.legend(loc=0, ncol=1, fancybox=True)

        if x_log:
            axes.set_xscale('log')
        else:
            axes.set_xscale('linear')
        if y_log:
            axes.set_yscale('log')
        else:
            axes.set_yscale('linear')

        canvas.print_figure(filename, dpi=300)

    def finalize(self):
        for simulation in self.simulations:
            simulation.Finalize()